/**
 * @fileoverview Tests for kernel.js.
 */
goog.module('protobuf.runtime.KernelTest');

goog.setTestOnly();

const ByteString = goog.require('protobuf.ByteString');
const Int64 = goog.require('protobuf.Int64');
const InternalMessage = goog.require('protobuf.binary.InternalMessage');
const Kernel = goog.require('protobuf.runtime.Kernel');
const TestMessage = goog.require('protobuf.testing.binary.TestMessage');
// Note to the reader:
// Since the lazy accessor behavior changes with the checking level some of the
// tests in this file have to know which checking level is enable to make
// correct assertions.
const {CHECK_BOUNDS, CHECK_CRITICAL_STATE, CHECK_CRITICAL_TYPE, CHECK_TYPE, MAX_FIELD_NUMBER} = goog.require('protobuf.internal.checks');

/**
 * @param {...number} bytes
 * @return {!ArrayBuffer}
 */
function createArrayBuffer(...bytes) {
  return new Uint8Array(bytes).buffer;
}

describe('Kernel', () => {
  it('encodes none for the empty input', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    expect(accessor.serialize()).toEqual(new ArrayBuffer(0));
  });

  it('encodes and decodes max field number', () => {
    const accessor = Kernel.fromArrayBuffer(
        createArrayBuffer(0xF8, 0xFF, 0xFF, 0xFF, 0x0F, 0x01));
    expect(accessor.getBoolWithDefault(MAX_FIELD_NUMBER)).toBe(true);
    accessor.setBool(MAX_FIELD_NUMBER, false);
    expect(accessor.serialize())
        .toEqual(createArrayBuffer(0xF8, 0xFF, 0xFF, 0xFF, 0x0F, 0x00));
  });

  it('uses the default pivot point', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    expect(accessor.getPivot()).toBe(24);
  });

  it('makes the pivot point configurable', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0), 50);
    expect(accessor.getPivot()).toBe(50);
  });
});

describe('Kernel hasFieldNumber', () => {
  it('returns false for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    expect(accessor.hasFieldNumber(1)).toBe(false);
  });

  it('returns true for non-empty input', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.hasFieldNumber(1)).toBe(true);
  });

  it('returns false for empty array', () => {
    const accessor = Kernel.createEmpty();
    accessor.setPackedBoolIterable(1, []);
    expect(accessor.hasFieldNumber(1)).toBe(false);
  });

  it('returns true for non-empty array', () => {
    const accessor = Kernel.createEmpty();
    accessor.setPackedBoolIterable(1, [true]);
    expect(accessor.hasFieldNumber(1)).toBe(true);
  });

  it('updates value after write', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    expect(accessor.hasFieldNumber(1)).toBe(false);
    accessor.setBool(1, false);
    expect(accessor.hasFieldNumber(1)).toBe(true);
  });
});

describe('Kernel clear field does', () => {
  it('clear the field set', () => {
    const accessor = Kernel.createEmpty();
    accessor.setBool(1, true);
    accessor.clearField(1);

    expect(accessor.hasFieldNumber(1)).toEqual(false);
    expect(accessor.serialize()).toEqual(new ArrayBuffer(0));
    expect(accessor.getBoolWithDefault(1)).toEqual(false);
  });

  it('clear the field decoded', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.clearField(1);

    expect(accessor.hasFieldNumber(1)).toEqual(false);
    expect(accessor.serialize()).toEqual(new ArrayBuffer(0));
    expect(accessor.getBoolWithDefault(1)).toEqual(false);
  });

  it('clear the field read', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getBoolWithDefault(1)).toEqual(true);
    accessor.clearField(1);

    expect(accessor.hasFieldNumber(1)).toEqual(false);
    expect(accessor.serialize()).toEqual(new ArrayBuffer(0));
    expect(accessor.getBoolWithDefault(1)).toEqual(false);
  });

  it('clear set and copied fields without affecting the old', () => {
    const accessor = Kernel.createEmpty();
    accessor.setBool(1, true);

    const clonedAccessor = accessor.shallowCopy();
    clonedAccessor.clearField(1);

    expect(accessor.hasFieldNumber(1)).toEqual(true);
    expect(accessor.getBoolWithDefault(1)).toEqual(true);
    expect(clonedAccessor.hasFieldNumber(1)).toEqual(false);
    expect(clonedAccessor.serialize()).toEqual(new ArrayBuffer(0));
    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(false);
  });

  it('clear decoded and copied fields without affecting the old', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);

    const clonedAccessor = accessor.shallowCopy();
    clonedAccessor.clearField(1);

    expect(accessor.hasFieldNumber(1)).toEqual(true);
    expect(accessor.getBoolWithDefault(1)).toEqual(true);
    expect(clonedAccessor.hasFieldNumber(1)).toEqual(false);
    expect(clonedAccessor.serialize()).toEqual(new ArrayBuffer(0));
    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(false);
  });

  it('clear read and copied fields without affecting the old', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getBoolWithDefault(1)).toEqual(true);

    const clonedAccessor = accessor.shallowCopy();
    clonedAccessor.clearField(1);

    expect(accessor.hasFieldNumber(1)).toEqual(true);
    expect(accessor.getBoolWithDefault(1)).toEqual(true);
    expect(clonedAccessor.hasFieldNumber(1)).toEqual(false);
    expect(clonedAccessor.serialize()).toEqual(new ArrayBuffer(0));
    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(false);
  });

  it('clear the max field number', () => {
    const accessor = Kernel.createEmpty();
    accessor.setBool(MAX_FIELD_NUMBER, true);

    accessor.clearField(MAX_FIELD_NUMBER);

    expect(accessor.hasFieldNumber(MAX_FIELD_NUMBER)).toEqual(false);
    expect(accessor.getBoolWithDefault(MAX_FIELD_NUMBER)).toEqual(false);
  });
});

describe('Kernel shallow copy does', () => {
  it('work for singular fields', () => {
    const accessor = Kernel.createEmpty();
    accessor.setBool(1, true);
    accessor.setBool(MAX_FIELD_NUMBER, true);
    const clonedAccessor = accessor.shallowCopy();
    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(true);
    expect(clonedAccessor.getBoolWithDefault(MAX_FIELD_NUMBER)).toEqual(true);

    accessor.setBool(1, false);
    accessor.setBool(MAX_FIELD_NUMBER, false);
    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(true);
    expect(clonedAccessor.getBoolWithDefault(MAX_FIELD_NUMBER)).toEqual(true);
  });

  it('work for repeated fields', () => {
    const accessor = Kernel.createEmpty();

    accessor.addUnpackedBoolIterable(2, [true, true]);

    const clonedAccessor = accessor.shallowCopy();

    // Modify a repeated field after clone
    accessor.addUnpackedBoolElement(2, true);

    const array = Array.from(clonedAccessor.getRepeatedBoolIterable(2));
    expect(array).toEqual([true, true]);
  });

  it('work for repeated fields', () => {
    const accessor = Kernel.createEmpty();

    accessor.addUnpackedBoolIterable(2, [true, true]);

    const clonedAccessor = accessor.shallowCopy();

    // Modify a repeated field after clone
    accessor.addUnpackedBoolElement(2, true);

    const array = Array.from(clonedAccessor.getRepeatedBoolIterable(2));
    expect(array).toEqual([true, true]);
  });

  it('return the correct bytes after serialization', () => {
    const bytes = createArrayBuffer(0x08, 0x01, 0x10, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes, /* pivot= */ 1);
    const clonedAccessor = accessor.shallowCopy();

    accessor.setBool(1, false);

    expect(clonedAccessor.getBoolWithDefault(1)).toEqual(true);
    expect(clonedAccessor.serialize()).toEqual(bytes);
  });
});

describe('Kernel for singular boolean does', () => {
  it('return false for the empty input', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    expect(accessor.getBoolWithDefault(
               /* fieldNumber= */ 1))
        .toBe(false);
  });

  it('return the value from the input', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getBoolWithDefault(
               /* fieldNumber= */ 1))
        .toBe(true);
  });

  it('encode the value from the input', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.serialize()).toEqual(bytes);
  });

  it('encode the value from the input after read', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.getBoolWithDefault(
        /* fieldNumber= */ 1);
    expect(accessor.serialize()).toEqual(bytes);
  });

  it('return the value from multiple inputs', () => {
    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getBoolWithDefault(
               /* fieldNumber= */ 1))
        .toBe(false);
  });

  it('encode the value from multiple inputs', () => {
    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.serialize()).toEqual(bytes);
  });

  it('encode the value from multiple inputs after read', () => {
    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.getBoolWithDefault(/* fieldNumber= */ 1);
    expect(accessor.serialize()).toEqual(bytes);
  });

  it('return the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setBool(1, true);
    expect(accessor.getBoolWithDefault(
               /* fieldNumber= */ 1))
        .toBe(true);
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01, 0x08, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x08, 0x01);
    accessor.setBool(1, true);
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('return the bool value from cache', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getBoolWithDefault(
               /* fieldNumber= */ 1))
        .toBe(true);
    // Make sure the value is cached.
    bytes[1] = 0x00;
    expect(accessor.getBoolWithDefault(
               /* fieldNumber= */ 1))
        .toBe(true);
  });

  it('fail when getting bool value with other wire types', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => {
        accessor.getBoolWithDefault(/* fieldNumber= */ 1);
      }).toThrowError('Expected wire type: 0 but found: 1');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      expect(accessor.getBoolWithDefault(
                 /* fieldNumber= */ 1))
          .toBe(true);
    }
  });

  it('fail when setting bool value with out-of-range field number', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    if (CHECK_TYPE) {
      expect(() => accessor.setBool(MAX_FIELD_NUMBER + 1, false))
          .toThrowError('Field number is out of range: 536870912');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      accessor.setBool(MAX_FIELD_NUMBER + 1, false);
      expect(accessor.getBoolWithDefault(MAX_FIELD_NUMBER + 1)).toBe(false);
    }
  });

  it('fail when setting bool value with number value', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    const fakeBoolean = /** @type {boolean} */ (/** @type {*} */ (2));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => accessor.setBool(1, fakeBoolean))
          .toThrowError('Must be a boolean, but got: 2');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      accessor.setBool(1, fakeBoolean);
      expect(accessor.getBoolWithDefault(
                 /* fieldNumber= */ 1))
          .toBe(2);
    }
  });
});

describe('Kernel for singular message does', () => {
  it('return message from the input', () => {
    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const msg = accessor.getMessageOrNull(1, TestMessage.instanceCreator);
    expect(msg.getBoolWithDefault(1, false)).toBe(true);
  });

  it('return message from the input when pivot is set', () => {
    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes, /* pivot= */ 0);
    const msg = accessor.getMessageOrNull(1, TestMessage.instanceCreator);
    expect(msg.getBoolWithDefault(1, false)).toBe(true);
  });

  it('encode message from the input', () => {
    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.serialize()).toEqual(bytes);
  });

  it('encode message from the input after read', () => {
    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.getMessageOrNull(1, TestMessage.instanceCreator);
    expect(accessor.serialize()).toEqual(bytes);
  });

  it('return message from multiple inputs', () => {
    const bytes =
        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x10, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const msg = accessor.getMessageOrNull(1, TestMessage.instanceCreator);
    expect(msg.getBoolWithDefault(1, false)).toBe(true);
    expect(msg.getBoolWithDefault(2, false)).toBe(true);
  });

  it('encode message from multiple inputs', () => {
    const bytes =
        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x10, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.serialize()).toEqual(bytes);
  });

  it('encode message merged from multiple inputs after read', () => {
    const bytes =
        createArrayBuffer(0x0A, 0x02, 0x08, 0x01, 0x0A, 0x02, 0x10, 0x01);
    const expected = createArrayBuffer(0x0A, 0x04, 0x08, 0x01, 0x10, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.getMessageOrNull(1, TestMessage.instanceCreator);
    expect(accessor.serialize()).toEqual(expected);
  });

  it('return null for generic accessor', () => {
    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const accessor1 = accessor.getMessageAccessorOrNull(7);
    expect(accessor1).toBe(null);
  });

  it('return null for generic accessor when pivot is set', () => {
    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const accessor1 = accessor.getMessageAccessorOrNull(7, /* pivot= */ 0);
    expect(accessor1).toBe(null);
  });

  it('return generic accessor from the input', () => {
    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const accessor1 = accessor.getMessageAccessorOrNull(1);
    expect(accessor1.getBoolWithDefault(1, false)).toBe(true);
    // Second call returns a new instance, isn't cached.
    const accessor2 = accessor.getMessageAccessorOrNull(1);
    expect(accessor2.getBoolWithDefault(1, false)).toBe(true);
    expect(accessor2).not.toBe(accessor1);
  });

  it('return generic accessor from the cached input', () => {
    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const wrappedMessage =
        accessor.getMessageOrNull(1, TestMessage.instanceCreator);

    // Returns accessor from the cached wrapper instance.
    const accessor1 = accessor.getMessageAccessorOrNull(1);
    expect(accessor1.getBoolWithDefault(1, false)).toBe(true);
    expect(accessor1).toBe(
        (/** @type {!InternalMessage} */ (wrappedMessage)).internalGetKernel());

    // Second call returns exact same instance.
    const accessor2 = accessor.getMessageAccessorOrNull(1);
    expect(accessor2.getBoolWithDefault(1, false)).toBe(true);
    expect(accessor2).toBe(
        (/** @type {!InternalMessage} */ (wrappedMessage)).internalGetKernel());
    expect(accessor2).toBe(accessor1);
  });

  it('return message from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    const subaccessor = Kernel.fromArrayBuffer(bytes);
    const submsg1 = new TestMessage(subaccessor);
    accessor.setMessage(1, submsg1);
    const submsg2 = accessor.getMessage(1, TestMessage.instanceCreator);
    expect(submsg1).toBe(submsg2);
  });

  it('encode message from setter', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    const subaccessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    const subsubaccessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
    const subsubmsg = new TestMessage(subsubaccessor);
    subaccessor.setMessage(1, subsubmsg);
    const submsg = new TestMessage(subaccessor);
    accessor.setMessage(1, submsg);
    const expected = createArrayBuffer(0x0A, 0x04, 0x0A, 0x02, 0x08, 0x01);
    expect(accessor.serialize()).toEqual(expected);
  });

  it('encode message with multiple submessage from setter', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    const subaccessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    const subsubaccessor1 =
        Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
    const subsubaccessor2 =
        Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x02));

    const subsubmsg1 = new TestMessage(subsubaccessor1);
    const subsubmsg2 = new TestMessage(subsubaccessor2);

    subaccessor.setMessage(1, subsubmsg1);
    subaccessor.setMessage(2, subsubmsg2);

    const submsg = new TestMessage(subaccessor);
    accessor.setMessage(1, submsg);

    const expected = createArrayBuffer(
        0x0A, 0x08, 0x0A, 0x02, 0x08, 0x01, 0x12, 0x02, 0x08, 0x02);
    expect(accessor.serialize()).toEqual(expected);
  });

  it('leave hasFieldNumber unchanged after getMessageOrNull', () => {
    const accessor = Kernel.createEmpty();
    expect(accessor.hasFieldNumber(1)).toBe(false);
    expect(accessor.getMessageOrNull(1, TestMessage.instanceCreator))
        .toBe(null);
    expect(accessor.hasFieldNumber(1)).toBe(false);
  });

  it('serialize changes to submessages made with getMessageOrNull', () => {
    const intTwoBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x02);
    const accessor = Kernel.fromArrayBuffer(intTwoBytes);
    const mutableSubMessage =
        accessor.getMessageOrNull(1, TestMessage.instanceCreator);
    mutableSubMessage.setInt32(1, 10);
    const intTenBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x0A);
    expect(accessor.serialize()).toEqual(intTenBytes);
  });

  it('serialize additions to submessages made with getMessageOrNull', () => {
    const intTwoBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x02);
    const accessor = Kernel.fromArrayBuffer(intTwoBytes);
    const mutableSubMessage =
        accessor.getMessageOrNull(1, TestMessage.instanceCreator);
    mutableSubMessage.setInt32(2, 3);
    // Sub message contains the original field, plus the new one.
    expect(accessor.serialize())
        .toEqual(createArrayBuffer(0x0A, 0x04, 0x08, 0x02, 0x10, 0x03));
  });

  it('fail with getMessageOrNull if immutable message exist in cache', () => {
    const intTwoBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x02);
    const accessor = Kernel.fromArrayBuffer(intTwoBytes);

    const readOnly = accessor.getMessage(1, TestMessage.instanceCreator);
    if (CHECK_TYPE) {
      expect(() => accessor.getMessageOrNull(1, TestMessage.instanceCreator))
          .toThrow();
    } else {
      const mutableSubMessage =
          accessor.getMessageOrNull(1, TestMessage.instanceCreator);
      // The instance returned by getMessageOrNull is the exact same instance.
      expect(mutableSubMessage).toBe(readOnly);

      // Serializing the submessage does not write the changes
      mutableSubMessage.setInt32(1, 0);
      expect(accessor.serialize()).toEqual(intTwoBytes);
    }
  });

  it('change hasFieldNumber after getMessageAttach', () => {
    const accessor = Kernel.createEmpty();
    expect(accessor.hasFieldNumber(1)).toBe(false);
    expect(accessor.getMessageAttach(1, TestMessage.instanceCreator))
        .not.toBe(null);
    expect(accessor.hasFieldNumber(1)).toBe(true);
  });

  it('change hasFieldNumber after getMessageAttach when pivot is set', () => {
    const accessor = Kernel.createEmpty();
    expect(accessor.hasFieldNumber(1)).toBe(false);
    expect(accessor.getMessageAttach(
               1, TestMessage.instanceCreator, /* pivot= */ 1))
        .not.toBe(null);
    expect(accessor.hasFieldNumber(1)).toBe(true);
  });

  it('serialize submessages made with getMessageAttach', () => {
    const accessor = Kernel.createEmpty();
    const mutableSubMessage =
        accessor.getMessageAttach(1, TestMessage.instanceCreator);
    mutableSubMessage.setInt32(1, 10);
    const intTenBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x0A);
    expect(accessor.serialize()).toEqual(intTenBytes);
  });

  it('serialize additions to submessages using getMessageAttach', () => {
    const intTwoBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x02);
    const accessor = Kernel.fromArrayBuffer(intTwoBytes);
    const mutableSubMessage =
        accessor.getMessageAttach(1, TestMessage.instanceCreator);
    mutableSubMessage.setInt32(2, 3);
    // Sub message contains the original field, plus the new one.
    expect(accessor.serialize())
        .toEqual(createArrayBuffer(0x0A, 0x04, 0x08, 0x02, 0x10, 0x03));
  });

  it('fail with getMessageAttach if immutable message exist in cache', () => {
    const intTwoBytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x02);
    const accessor = Kernel.fromArrayBuffer(intTwoBytes);

    const readOnly = accessor.getMessage(1, TestMessage.instanceCreator);
    if (CHECK_TYPE) {
      expect(() => accessor.getMessageAttach(1, TestMessage.instanceCreator))
          .toThrow();
    } else {
      const mutableSubMessage =
          accessor.getMessageAttach(1, TestMessage.instanceCreator);
      // The instance returned by getMessageOrNull is the exact same instance.
      expect(mutableSubMessage).toBe(readOnly);

      // Serializing the submessage does not write the changes
      mutableSubMessage.setInt32(1, 0);
      expect(accessor.serialize()).toEqual(intTwoBytes);
    }
  });

  it('read default message return empty message with getMessage', () => {
    const bytes = new ArrayBuffer(0);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getMessage(1, TestMessage.instanceCreator)).toBeTruthy();
    expect(accessor.getMessage(1, TestMessage.instanceCreator).serialize())
        .toEqual(bytes);
  });

  it('read default message return null with getMessageOrNull', () => {
    const bytes = new ArrayBuffer(0);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getMessageOrNull(1, TestMessage.instanceCreator))
        .toBe(null);
  });

  it('read message preserve reference equality', () => {
    const bytes = createArrayBuffer(0x0A, 0x02, 0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const msg1 = accessor.getMessageOrNull(1, TestMessage.instanceCreator);
    const msg2 = accessor.getMessageOrNull(1, TestMessage.instanceCreator);
    const msg3 = accessor.getMessageAttach(1, TestMessage.instanceCreator);
    expect(msg1).toBe(msg2);
    expect(msg1).toBe(msg3);
  });

  it('fail when getting message with other wire types', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
    expect(() => accessor.getMessageOrNull(1, TestMessage.instanceCreator))
        .toThrow();
  });

  it('fail when submessage has incomplete data', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x08));
    expect(() => accessor.getMessageOrNull(1, TestMessage.instanceCreator))
        .toThrow();
  });

  it('fail when mutable submessage has incomplete data', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x08));
    expect(() => accessor.getMessageAttach(1, TestMessage.instanceCreator))
        .toThrow();
  });

  it('fail when getting message with null instance constructor', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0A, 0x02, 0x08, 0x01));
    const nullMessage = /** @type {function(!Kernel):!TestMessage} */
        (/** @type {*} */ (null));
    expect(() => accessor.getMessageOrNull(1, nullMessage)).toThrow();
  });

  it('fail when setting message value with null value', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    const fakeMessage = /** @type {!TestMessage} */ (/** @type {*} */ (null));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => accessor.setMessage(1, fakeMessage))
          .toThrowError('Given value is not a message instance: null');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      accessor.setMessage(1, fakeMessage);
      expect(accessor.getMessageOrNull(
                 /* fieldNumber= */ 1, TestMessage.instanceCreator))
          .toBeNull();
    }
  });
});

describe('Bytes access', () => {
  const simpleByteString = ByteString.fromArrayBuffer(createArrayBuffer(1));

  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getBytesWithDefault(1)).toEqual(ByteString.EMPTY);
  });

  it('returns the default from parameter', () => {
    const defaultByteString = ByteString.fromArrayBuffer(createArrayBuffer(1));
    const returnValue = ByteString.fromArrayBuffer(createArrayBuffer(1));
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getBytesWithDefault(1, defaultByteString))
        .toEqual(returnValue);
  });

  it('decodes value from wire', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x01));
    expect(accessor.getBytesWithDefault(1)).toEqual(simpleByteString);
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor = Kernel.fromArrayBuffer(
        createArrayBuffer(0x0A, 0x01, 0x00, 0x0A, 0x01, 0x01));
    expect(accessor.getBytesWithDefault(1)).toEqual(simpleByteString);
  });

  it('fails when getting value with other wire types', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
        0x09, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => {
        accessor.getBytesWithDefault(1);
      }).toThrowError('Expected wire type: 2 but found: 1');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      const arrayBuffer = createArrayBuffer(1);
      expect(accessor.getBytesWithDefault(1))
          .toEqual(ByteString.fromArrayBuffer(arrayBuffer));
    }
  });

  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(
          () => Kernel.createEmpty().getBytesWithDefault(-1, simpleByteString))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getBytesWithDefault(-1, simpleByteString))
          .toEqual(simpleByteString);
    }
  });

  it('returns the value from setter', () => {
    const bytes = createArrayBuffer(0x0A, 0x01, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setBytes(1, simpleByteString);
    expect(accessor.getBytesWithDefault(1)).toEqual(simpleByteString);
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x0A, 0x01, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x0A, 0x01, 0x01);
    accessor.setBytes(1, simpleByteString);
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns value from cache', () => {
    const bytes = createArrayBuffer(0x0A, 0x01, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getBytesWithDefault(1)).toEqual(simpleByteString);
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getBytesWithDefault(1)).toEqual(simpleByteString);
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setBytes(-1, simpleByteString))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setBytes(-1, simpleByteString);
      expect(accessor.getBytesWithDefault(-1)).toEqual(simpleByteString);
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setBytes(
              1, /** @type {!ByteString} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setBytes(
          1, /** @type {!ByteString} */ (/** @type {*} */ (null)));
      expect(accessor.getBytesWithDefault(1)).toEqual(null);
    }
  });
});

describe('Fixed32 access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getFixed32WithDefault(1)).toEqual(0);
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getFixed32WithDefault(1, 2)).toEqual(2);
  });

  it('decodes value from wire', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00));
    expect(accessor.getFixed32WithDefault(1)).toEqual(1);
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
        0x0D, 0x01, 0x00, 0x80, 0x00, 0x0D, 0x02, 0x00, 0x00, 0x00));
    expect(accessor.getFixed32WithDefault(1)).toEqual(2);
  });

  it('fails when getting value with other wire types', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x80, 0x80, 0x80, 0x00));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => {
        accessor.getFixed32WithDefault(1);
      }).toThrowError('Expected wire type: 5 but found: 0');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      expect(accessor.getFixed32WithDefault(1)).toEqual(8421504);
    }
  });

  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().getFixed32WithDefault(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getFixed32WithDefault(-1, 1)).toEqual(1);
    }
  });

  it('returns the value from setter', () => {
    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setFixed32(1, 2);
    expect(accessor.getFixed32WithDefault(1)).toEqual(2);
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00);
    accessor.setFixed32(1, 0);
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns value from cache', () => {
    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getFixed32WithDefault(1)).toBe(1);
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getFixed32WithDefault(1)).toBe(1);
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setFixed32(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setFixed32(-1, 1);
      expect(accessor.getFixed32WithDefault(-1)).toEqual(1);
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setFixed32(
              1, /** @type {number} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setFixed32(1, /** @type {number} */ (/** @type {*} */ (null)));
      expect(accessor.getFixed32WithDefault(1)).toEqual(null);
    }
  });

  it('throws in setter for negative value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(() => Kernel.createEmpty().setFixed32(1, -1)).toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setFixed32(1, -1);
      expect(accessor.getFixed32WithDefault(1)).toEqual(-1);
    }
  });
});

describe('Fixed64 access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(0));
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getFixed64WithDefault(1, Int64.fromInt(2)))
        .toEqual(Int64.fromInt(2));
  });

  it('decodes value from wire', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(1));
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x02, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(2));
  });

  if (CHECK_CRITICAL_STATE) {
    it('fails when getting value with other wire types', () => {
      const accessor = Kernel.fromArrayBuffer(
          createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
      expect(() => {
        accessor.getFixed64WithDefault(1);
      }).toThrow();
    });
  }

  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(
          () =>
              Kernel.createEmpty().getFixed64WithDefault(-1, Int64.fromInt(1)))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getFixed64WithDefault(-1, Int64.fromInt(1)))
          .toEqual(Int64.fromInt(1));
    }
  });

  it('returns the value from setter', () => {
    const bytes =
        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setFixed64(1, Int64.fromInt(2));
    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(2));
  });

  it('encode the value from setter', () => {
    const bytes =
        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes =
        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
    accessor.setFixed64(1, Int64.fromInt(0));
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns value from cache', () => {
    const bytes =
        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(1));
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getFixed64WithDefault(1)).toEqual(Int64.fromInt(1));
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setFixed64(-1, Int64.fromInt(1)))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setFixed64(-1, Int64.fromInt(1));
      expect(accessor.getFixed64WithDefault(-1)).toEqual(Int64.fromInt(1));
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setSfixed64(
              1, /** @type {!Int64} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setFixed64(1, /** @type {!Int64} */ (/** @type {*} */ (null)));
      expect(accessor.getFixed64WithDefault(1)).toEqual(null);
    }
  });
});

describe('Float access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getFloatWithDefault(1)).toEqual(0);
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getFloatWithDefault(1, 2)).toEqual(2);
  });

  it('decodes value from wire', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0D, 0x00, 0x00, 0x80, 0x3F));
    expect(accessor.getFloatWithDefault(1)).toEqual(1);
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
        0x0D, 0x00, 0x00, 0x80, 0x3F, 0x0D, 0x00, 0x00, 0x80, 0xBF));
    expect(accessor.getFloatWithDefault(1)).toEqual(-1);
  });

  if (CHECK_CRITICAL_STATE) {
    it('fails when getting float value with other wire types', () => {
      const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
          0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F));
      expect(() => {
        accessor.getFloatWithDefault(1);
      }).toThrow();
    });
  }


  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().getFloatWithDefault(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getFloatWithDefault(-1, 1)).toEqual(1);
    }
  });

  it('returns the value from setter', () => {
    const bytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x80, 0x3F);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setFloat(1, 1.6);
    expect(accessor.getFloatWithDefault(1)).toEqual(Math.fround(1.6));
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x80, 0x3F);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00);
    accessor.setFloat(1, 0);
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns float value from cache', () => {
    const bytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x80, 0x3F);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getFloatWithDefault(1)).toBe(1);
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getFloatWithDefault(1)).toBe(1);
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setFloat(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setFloat(-1, 1);
      expect(accessor.getFloatWithDefault(-1)).toEqual(1);
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setFloat(
              1, /** @type {number} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setFloat(1, /** @type {number} */ (/** @type {*} */ (null)));
      expect(accessor.getFloatWithDefault(1)).toEqual(0);
    }
  });

  it('throws in setter for value outside of float32 precision', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(() => Kernel.createEmpty().setFloat(1, Number.MAX_VALUE))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setFloat(1, Number.MAX_VALUE);
      expect(accessor.getFloatWithDefault(1)).toEqual(Infinity);
    }
  });
});

describe('Int32 access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getInt32WithDefault(1)).toEqual(0);
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getInt32WithDefault(1, 2)).toEqual(2);
  });

  it('decodes value from wire', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
    expect(accessor.getInt32WithDefault(1)).toEqual(1);
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01, 0x08, 0x02));
    expect(accessor.getInt32WithDefault(1)).toEqual(2);
  });

  it('fails when getting value with other wire types', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => {
        accessor.getInt32WithDefault(1);
      }).toThrowError('Expected wire type: 0 but found: 5');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      expect(accessor.getInt32WithDefault(1)).toEqual(0);
    }
  });

  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().getInt32WithDefault(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getInt32WithDefault(-1, 1)).toEqual(1);
    }
  });

  it('returns the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setInt32(1, 2);
    expect(accessor.getInt32WithDefault(1)).toEqual(2);
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x08, 0x00);
    accessor.setInt32(1, 0);
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns value from cache', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getInt32WithDefault(1)).toBe(1);
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getInt32WithDefault(1)).toBe(1);
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setInt32(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setInt32(-1, 1);
      expect(accessor.getInt32WithDefault(-1)).toEqual(1);
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setInt32(
              1, /** @type {number} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setInt32(1, /** @type {number} */ (/** @type {*} */ (null)));
      expect(accessor.getInt32WithDefault(1)).toEqual(null);
    }
  });
});

describe('Int64 access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(0));
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getInt64WithDefault(1, Int64.fromInt(2)))
        .toEqual(Int64.fromInt(2));
  });

  it('decodes value from wire', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(1));
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01, 0x08, 0x02));
    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(2));
  });

  it('fails when getting value with other wire types', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => {
        accessor.getInt64WithDefault(1);
      }).toThrowError('Expected wire type: 0 but found: 5');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(0));
    }
  });

  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(
          () => Kernel.createEmpty().getInt64WithDefault(-1, Int64.fromInt(1)))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getInt64WithDefault(-1, Int64.fromInt(1)))
          .toEqual(Int64.fromInt(1));
    }
  });

  it('returns the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setInt64(1, Int64.fromInt(2));
    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(2));
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x08, 0x00);
    accessor.setInt64(1, Int64.fromInt(0));
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns value from cache', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(1));
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getInt64WithDefault(1)).toEqual(Int64.fromInt(1));
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setInt64(-1, Int64.fromInt(1)))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setInt64(-1, Int64.fromInt(1));
      expect(accessor.getInt64WithDefault(-1)).toEqual(Int64.fromInt(1));
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setInt64(
              1, /** @type {!Int64} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setInt64(1, /** @type {!Int64} */ (/** @type {*} */ (null)));
      expect(accessor.getInt64WithDefault(1)).toEqual(null);
    }
  });
});

describe('Sfixed32 access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getSfixed32WithDefault(1)).toEqual(0);
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getSfixed32WithDefault(1, 2)).toEqual(2);
  });

  it('decodes value from wire', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00));
    expect(accessor.getSfixed32WithDefault(1)).toEqual(1);
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
        0x0D, 0x01, 0x00, 0x80, 0x00, 0x0D, 0x02, 0x00, 0x00, 0x00));
    expect(accessor.getSfixed32WithDefault(1)).toEqual(2);
  });

  it('fails when getting value with other wire types', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x80, 0x80, 0x80, 0x00));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => {
        accessor.getSfixed32WithDefault(1);
      }).toThrowError('Expected wire type: 5 but found: 0');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      expect(accessor.getSfixed32WithDefault(1)).toEqual(8421504);
    }
  });

  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().getSfixed32WithDefault(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getSfixed32WithDefault(-1, 1)).toEqual(1);
    }
  });

  it('returns the value from setter', () => {
    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setSfixed32(1, 2);
    expect(accessor.getSfixed32WithDefault(1)).toEqual(2);
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00);
    accessor.setSfixed32(1, 0);
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns value from cache', () => {
    const bytes = createArrayBuffer(0x0D, 0x01, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getSfixed32WithDefault(1)).toBe(1);
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getSfixed32WithDefault(1)).toBe(1);
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setSfixed32(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setSfixed32(-1, 1);
      expect(accessor.getSfixed32WithDefault(-1)).toEqual(1);
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setSfixed32(
              1, /** @type {number} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setSfixed32(1, /** @type {number} */ (/** @type {*} */ (null)));
      expect(accessor.getSfixed32WithDefault(1)).toEqual(null);
    }
  });
});

describe('Sfixed64 access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(0));
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getSfixed64WithDefault(1, Int64.fromInt(2)))
        .toEqual(Int64.fromInt(2));
  });

  it('decodes value from wire', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(1));
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
        0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x02, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(2));
  });

  if (CHECK_CRITICAL_STATE) {
    it('fails when getting value with other wire types', () => {
      const accessor = Kernel.fromArrayBuffer(
          createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
      expect(() => {
        accessor.getSfixed64WithDefault(1);
      }).toThrow();
    });
  }

  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(
          () =>
              Kernel.createEmpty().getSfixed64WithDefault(-1, Int64.fromInt(1)))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getSfixed64WithDefault(-1, Int64.fromInt(1)))
          .toEqual(Int64.fromInt(1));
    }
  });

  it('returns the value from setter', () => {
    const bytes =
        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setSfixed64(1, Int64.fromInt(2));
    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(2));
  });

  it('encode the value from setter', () => {
    const bytes =
        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes =
        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
    accessor.setSfixed64(1, Int64.fromInt(0));
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns value from cache', () => {
    const bytes =
        createArrayBuffer(0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(1));
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getSfixed64WithDefault(1)).toEqual(Int64.fromInt(1));
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setSfixed64(-1, Int64.fromInt(1)))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setSfixed64(-1, Int64.fromInt(1));
      expect(accessor.getSfixed64WithDefault(-1)).toEqual(Int64.fromInt(1));
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setSfixed64(
              1, /** @type {!Int64} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setSfixed64(1, /** @type {!Int64} */ (/** @type {*} */ (null)));
      expect(accessor.getSfixed64WithDefault(1)).toEqual(null);
    }
  });
});

describe('Sint32 access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getSint32WithDefault(1)).toEqual(0);
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getSint32WithDefault(1, 2)).toEqual(2);
  });

  it('decodes value from wire', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x02));
    expect(accessor.getSint32WithDefault(1)).toEqual(1);
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x03, 0x08, 0x02));
    expect(accessor.getSint32WithDefault(1)).toEqual(1);
  });

  it('fails when getting value with other wire types', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => {
        accessor.getSint32WithDefault(1);
      }).toThrowError('Expected wire type: 0 but found: 5');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      expect(accessor.getSint32WithDefault(1)).toEqual(0);
    }
  });

  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().getSint32WithDefault(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getSint32WithDefault(-1, 1)).toEqual(1);
    }
  });

  it('returns the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setSint32(1, 2);
    expect(accessor.getSint32WithDefault(1)).toEqual(2);
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x08, 0x00);
    accessor.setSint32(1, 0);
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns value from cache', () => {
    const bytes = createArrayBuffer(0x08, 0x02);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getSint32WithDefault(1)).toBe(1);
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getSint32WithDefault(1)).toBe(1);
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setSint32(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setSint32(-1, 1);
      expect(accessor.getSint32WithDefault(-1)).toEqual(1);
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setSint32(
              1, /** @type {number} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setSint32(1, /** @type {number} */ (/** @type {*} */ (null)));
      expect(accessor.getSint32WithDefault(1)).toEqual(null);
    }
  });
});

describe('SInt64 access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(0));
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getSint64WithDefault(1, Int64.fromInt(2)))
        .toEqual(Int64.fromInt(2));
  });

  it('decodes value from wire', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x02));
    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(1));
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01, 0x08, 0x02));
    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(1));
  });

  it('fails when getting value with other wire types', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => {
        accessor.getSint64WithDefault(1);
      }).toThrowError('Expected wire type: 0 but found: 5');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(0));
    }
  });

  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(
          () => Kernel.createEmpty().getSint64WithDefault(-1, Int64.fromInt(1)))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getSint64WithDefault(-1, Int64.fromInt(1)))
          .toEqual(Int64.fromInt(1));
    }
  });

  it('returns the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setSint64(1, Int64.fromInt(2));
    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(2));
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x08, 0x00);
    accessor.setSint64(1, Int64.fromInt(0));
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns value from cache', () => {
    const bytes = createArrayBuffer(0x08, 0x02);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(1));
    // Make sure the value is cached.
    bytes[1] = 0x00;
    expect(accessor.getSint64WithDefault(1)).toEqual(Int64.fromInt(1));
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setSint64(-1, Int64.fromInt(1)))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setInt64(-1, Int64.fromInt(1));
      expect(accessor.getSint64WithDefault(-1)).toEqual(Int64.fromInt(1));
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setSint64(
              1, /** @type {!Int64} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setSint64(1, /** @type {!Int64} */ (/** @type {*} */ (null)));
      expect(accessor.getSint64WithDefault(1)).toEqual(null);
    }
  });
});

describe('String access', () => {
  it('returns empty string for the empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getStringWithDefault(1)).toEqual('');
  });

  it('returns the default for the empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getStringWithDefault(1, 'bar')).toEqual('bar');
  });

  it('decodes value from wire', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0A, 0x01, 0x61));
    expect(accessor.getStringWithDefault(1)).toEqual('a');
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor = Kernel.fromArrayBuffer(
        createArrayBuffer(0x0A, 0x01, 0x60, 0x0A, 0x01, 0x61));
    expect(accessor.getStringWithDefault(1)).toEqual('a');
  });

  if (CHECK_CRITICAL_STATE) {
    it('fails when getting string value with other wire types', () => {
      const accessor =
          Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x02, 0x08, 0x08));
      expect(() => {
        accessor.getStringWithDefault(1);
      }).toThrow();
    });
  }


  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().getStringWithDefault(-1, 'a'))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getStringWithDefault(-1, 'a')).toEqual('a');
    }
  });

  it('returns the value from setter', () => {
    const bytes = createArrayBuffer(0x0A, 0x01, 0x61);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setString(1, 'b');
    expect(accessor.getStringWithDefault(1)).toEqual('b');
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x0A, 0x01, 0x61);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x0A, 0x01, 0x62);
    accessor.setString(1, 'b');
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns string value from cache', () => {
    const bytes = createArrayBuffer(0x0A, 0x01, 0x61);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getStringWithDefault(1)).toBe('a');
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getStringWithDefault(1)).toBe('a');
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_TYPE) {
      expect(() => Kernel.createEmpty().setString(-1, 'a'))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setString(-1, 'a');
      expect(accessor.getStringWithDefault(-1)).toEqual('a');
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setString(
              1, /** @type {string} */ (/** @type {*} */ (null))))
          .toThrowError('Must be string, but got: null');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setString(1, /** @type {string} */ (/** @type {*} */ (null)));
      expect(accessor.getStringWithDefault(1)).toEqual(null);
    }
  });
});

describe('Uint32 access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getUint32WithDefault(1)).toEqual(0);
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getUint32WithDefault(1, 2)).toEqual(2);
  });

  it('decodes value from wire', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
    expect(accessor.getUint32WithDefault(1)).toEqual(1);
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01, 0x08, 0x02));
    expect(accessor.getUint32WithDefault(1)).toEqual(2);
  });

  it('fails when getting value with other wire types', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => {
        accessor.getUint32WithDefault(1);
      }).toThrowError('Expected wire type: 0 but found: 5');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      expect(accessor.getUint32WithDefault(1)).toEqual(0);
    }
  });

  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().getUint32WithDefault(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getUint32WithDefault(-1, 1)).toEqual(1);
    }
  });

  it('returns the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setUint32(1, 2);
    expect(accessor.getUint32WithDefault(1)).toEqual(2);
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x08, 0x00);
    accessor.setUint32(1, 0);
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns value from cache', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getUint32WithDefault(1)).toBe(1);
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getUint32WithDefault(1)).toBe(1);
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setInt32(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setUint32(-1, 1);
      expect(accessor.getUint32WithDefault(-1)).toEqual(1);
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setUint32(
              1, /** @type {number} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setUint32(1, /** @type {number} */ (/** @type {*} */ (null)));
      expect(accessor.getUint32WithDefault(1)).toEqual(null);
    }
  });

  it('throws in setter for negative value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(() => Kernel.createEmpty().setUint32(1, -1)).toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setUint32(1, -1);
      expect(accessor.getUint32WithDefault(1)).toEqual(-1);
    }
  });
});

describe('Uint64 access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(0));
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getUint64WithDefault(1, Int64.fromInt(2)))
        .toEqual(Int64.fromInt(2));
  });

  it('decodes value from wire', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(1));
  });

  it('decodes value from wire with multiple values being present', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01, 0x08, 0x02));
    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(2));
  });

  it('fails when getting value with other wire types', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0D, 0x00, 0x00, 0x00, 0x00));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => {
        accessor.getUint64WithDefault(1);
      }).toThrowError('Expected wire type: 0 but found: 5');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(0));
    }
  });

  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(
          () => Kernel.createEmpty().getUint64WithDefault(-1, Int64.fromInt(1)))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getUint64WithDefault(-1, Int64.fromInt(1)))
          .toEqual(Int64.fromInt(1));
    }
  });

  it('returns the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setUint64(1, Int64.fromInt(2));
    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(2));
  });

  it('encode the value from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes = createArrayBuffer(0x08, 0x00);
    accessor.setUint64(1, Int64.fromInt(0));
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns value from cache', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(1));
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getUint64WithDefault(1)).toEqual(Int64.fromInt(1));
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setUint64(-1, Int64.fromInt(1)))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setUint64(-1, Int64.fromInt(1));
      expect(accessor.getUint64WithDefault(-1)).toEqual(Int64.fromInt(1));
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setUint64(
              1, /** @type {!Int64} */ (/** @type {*} */ (null))))
          .toThrow();
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setUint64(1, /** @type {!Int64} */ (/** @type {*} */ (null)));
      expect(accessor.getUint64WithDefault(1)).toEqual(null);
    }
  });
});

describe('Double access', () => {
  it('returns default value for empty input', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getDoubleWithDefault(1)).toEqual(0);
  });

  it('returns the default from parameter', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer());
    expect(accessor.getDoubleWithDefault(1, 2)).toEqual(2);
  });

  it('decodes value from wire', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F));
    expect(accessor.getDoubleWithDefault(1)).toEqual(1);
  });


  it('decodes value from wire with multiple values being present', () => {
    const accessor = Kernel.fromArrayBuffer(createArrayBuffer(
        0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x09, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF));
    expect(accessor.getDoubleWithDefault(1)).toEqual(-1);
  });

  if (CHECK_CRITICAL_STATE) {
    it('fails when getting double value with other wire types', () => {
      const accessor = Kernel.fromArrayBuffer(
          createArrayBuffer(0x0D, 0x00, 0x00, 0xF0, 0x3F));
      expect(() => {
        accessor.getDoubleWithDefault(1);
      }).toThrow();
    });
  }


  it('throws in getter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().getDoubleWithDefault(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      expect(Kernel.createEmpty().getDoubleWithDefault(-1, 1)).toEqual(1);
    }
  });

  it('returns the value from setter', () => {
    const bytes =
        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.setDouble(1, 2);
    expect(accessor.getDoubleWithDefault(1)).toEqual(2);
  });

  it('encode the value from setter', () => {
    const bytes =
        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const newBytes =
        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
    accessor.setDouble(1, 0);
    expect(accessor.serialize()).toEqual(newBytes);
  });

  it('returns string value from cache', () => {
    const bytes =
        createArrayBuffer(0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getDoubleWithDefault(1)).toBe(1);
    // Make sure the value is cached.
    bytes[2] = 0x00;
    expect(accessor.getDoubleWithDefault(1)).toBe(1);
  });

  it('throws in setter for invalid fieldNumber', () => {
    if (CHECK_BOUNDS) {
      expect(() => Kernel.createEmpty().setDouble(-1, 1))
          .toThrowError('Field number is out of range: -1');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setDouble(-1, 1);
      expect(accessor.getDoubleWithDefault(-1)).toEqual(1);
    }
  });

  it('throws in setter for invalid value', () => {
    if (CHECK_CRITICAL_TYPE) {
      expect(
          () => Kernel.createEmpty().setDouble(
              1, /** @type {number} */ (/** @type {*} */ (null))))
          .toThrowError('Must be a number, but got: null');
    } else {
      const accessor = Kernel.createEmpty();
      accessor.setDouble(1, /** @type {number} */ (/** @type {*} */ (null)));
      expect(accessor.getDoubleWithDefault(1)).toEqual(null);
    }
  });
});

describe('Kernel for singular group does', () => {
  it('return group from the input', () => {
    const bytes = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const msg = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
    expect(msg.getBoolWithDefault(1, false)).toBe(true);
  });

  it('return group from the input when pivot is set', () => {
    const bytes = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const msg = accessor.getGroupOrNull(1, TestMessage.instanceCreator, 0);
    expect(msg.getBoolWithDefault(1, false)).toBe(true);
  });

  it('encode group from the input', () => {
    const bytes = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.serialize()).toEqual(bytes);
  });

  it('encode group from the input after read', () => {
    const bytes = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.getGroupOrNull(1, TestMessage.instanceCreator);
    expect(accessor.serialize()).toEqual(bytes);
  });

  it('return last group from multiple inputs', () => {
    const bytes =
        createArrayBuffer(0x0B, 0x08, 0x00, 0x0C, 0x0B, 0x08, 0x01, 0x0C);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const msg = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
    expect(msg.getBoolWithDefault(1, false)).toBe(true);
  });

  it('removes duplicated group when serializing', () => {
    const bytes =
        createArrayBuffer(0x0B, 0x08, 0x00, 0x0C, 0x0B, 0x08, 0x01, 0x0C);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.getGroupOrNull(1, TestMessage.instanceCreator);
    expect(accessor.serialize())
        .toEqual(createArrayBuffer(0x0B, 0x08, 0x01, 0x0C));
  });

  it('encode group from multiple inputs', () => {
    const bytes =
        createArrayBuffer(0x0B, 0x08, 0x00, 0x0C, 0x0B, 0x08, 0x01, 0x0C);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.serialize()).toEqual(bytes);
  });

  it('encode group after read', () => {
    const bytes =
        createArrayBuffer(0x0B, 0x08, 0x00, 0x0C, 0x0B, 0x08, 0x01, 0x0C);
    const expected = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
    const accessor = Kernel.fromArrayBuffer(bytes);
    accessor.getGroupOrNull(1, TestMessage.instanceCreator);
    expect(accessor.serialize()).toEqual(expected);
  });

  it('return group from setter', () => {
    const bytes = createArrayBuffer(0x08, 0x01);
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    const subaccessor = Kernel.fromArrayBuffer(bytes);
    const submsg1 = new TestMessage(subaccessor);
    accessor.setGroup(1, submsg1);
    const submsg2 = accessor.getGroup(1, TestMessage.instanceCreator);
    expect(submsg1).toBe(submsg2);
  });

  it('encode group from setter', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    const subaccessor = Kernel.fromArrayBuffer(createArrayBuffer(0x08, 0x01));
    const submsg = new TestMessage(subaccessor);
    accessor.setGroup(1, submsg);
    const expected = createArrayBuffer(0x0B, 0x08, 0x01, 0x0C);
    expect(accessor.serialize()).toEqual(expected);
  });

  it('leave hasFieldNumber unchanged after getGroupOrNull', () => {
    const accessor = Kernel.createEmpty();
    expect(accessor.hasFieldNumber(1)).toBe(false);
    expect(accessor.getGroupOrNull(1, TestMessage.instanceCreator)).toBe(null);
    expect(accessor.hasFieldNumber(1)).toBe(false);
  });

  it('serialize changes to subgroups made with getGroupsOrNull', () => {
    const intTwoBytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
    const accessor = Kernel.fromArrayBuffer(intTwoBytes);
    const mutableSubMessage =
        accessor.getGroupOrNull(1, TestMessage.instanceCreator);
    mutableSubMessage.setInt32(1, 10);
    const intTenBytes = createArrayBuffer(0x0B, 0x08, 0x0A, 0x0C);
    expect(accessor.serialize()).toEqual(intTenBytes);
  });

  it('serialize additions to subgroups made with getGroupOrNull', () => {
    const intTwoBytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
    const accessor = Kernel.fromArrayBuffer(intTwoBytes);
    const mutableSubMessage =
        accessor.getGroupOrNull(1, TestMessage.instanceCreator);
    mutableSubMessage.setInt32(2, 3);
    // Sub group contains the original field, plus the new one.
    expect(accessor.serialize())
        .toEqual(createArrayBuffer(0x0B, 0x08, 0x02, 0x10, 0x03, 0x0C));
  });

  it('fail with getGroupOrNull if immutable group exist in cache', () => {
    const intTwoBytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
    const accessor = Kernel.fromArrayBuffer(intTwoBytes);

    const readOnly = accessor.getGroup(1, TestMessage.instanceCreator);
    if (CHECK_TYPE) {
      expect(() => accessor.getGroupOrNull(1, TestMessage.instanceCreator))
          .toThrow();
    } else {
      const mutableSubGropu =
          accessor.getGroupOrNull(1, TestMessage.instanceCreator);
      // The instance returned by getGroupOrNull is the exact same instance.
      expect(mutableSubGropu).toBe(readOnly);

      // Serializing the subgroup does not write the changes
      mutableSubGropu.setInt32(1, 0);
      expect(accessor.serialize()).toEqual(intTwoBytes);
    }
  });

  it('change hasFieldNumber after getGroupAttach', () => {
    const accessor = Kernel.createEmpty();
    expect(accessor.hasFieldNumber(1)).toBe(false);
    expect(accessor.getGroupAttach(1, TestMessage.instanceCreator))
        .not.toBe(null);
    expect(accessor.hasFieldNumber(1)).toBe(true);
  });

  it('change hasFieldNumber after getGroupAttach when pivot is set', () => {
    const accessor = Kernel.createEmpty();
    expect(accessor.hasFieldNumber(1)).toBe(false);
    expect(
        accessor.getGroupAttach(1, TestMessage.instanceCreator, /* pivot= */ 1))
        .not.toBe(null);
    expect(accessor.hasFieldNumber(1)).toBe(true);
  });

  it('serialize subgroups made with getGroupAttach', () => {
    const accessor = Kernel.createEmpty();
    const mutableSubGroup =
        accessor.getGroupAttach(1, TestMessage.instanceCreator);
    mutableSubGroup.setInt32(1, 10);
    const intTenBytes = createArrayBuffer(0x0B, 0x08, 0x0A, 0x0C);
    expect(accessor.serialize()).toEqual(intTenBytes);
  });

  it('serialize additions to subgroups using getMessageAttach', () => {
    const intTwoBytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
    const accessor = Kernel.fromArrayBuffer(intTwoBytes);
    const mutableSubGroup =
        accessor.getGroupAttach(1, TestMessage.instanceCreator);
    mutableSubGroup.setInt32(2, 3);
    // Sub message contains the original field, plus the new one.
    expect(accessor.serialize())
        .toEqual(createArrayBuffer(0x0B, 0x08, 0x02, 0x10, 0x03, 0x0C));
  });

  it('fail with getGroupAttach if immutable message exist in cache', () => {
    const intTwoBytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
    const accessor = Kernel.fromArrayBuffer(intTwoBytes);

    const readOnly = accessor.getGroup(1, TestMessage.instanceCreator);
    if (CHECK_TYPE) {
      expect(() => accessor.getGroupAttach(1, TestMessage.instanceCreator))
          .toThrow();
    } else {
      const mutableSubGroup =
          accessor.getGroupAttach(1, TestMessage.instanceCreator);
      // The instance returned by getMessageOrNull is the exact same instance.
      expect(mutableSubGroup).toBe(readOnly);

      // Serializing the submessage does not write the changes
      mutableSubGroup.setInt32(1, 0);
      expect(accessor.serialize()).toEqual(intTwoBytes);
    }
  });

  it('read default group return empty group with getGroup', () => {
    const bytes = new ArrayBuffer(0);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getGroup(1, TestMessage.instanceCreator)).toBeTruthy();
    expect(accessor.getGroup(1, TestMessage.instanceCreator).serialize())
        .toEqual(bytes);
  });

  it('read default group return null with getGroupOrNull', () => {
    const bytes = new ArrayBuffer(0);
    const accessor = Kernel.fromArrayBuffer(bytes);
    expect(accessor.getGroupOrNull(1, TestMessage.instanceCreator)).toBe(null);
  });

  it('read group preserve reference equality', () => {
    const bytes = createArrayBuffer(0x0B, 0x08, 0x02, 0x0C);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const msg1 = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
    const msg2 = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
    const msg3 = accessor.getGroupAttach(1, TestMessage.instanceCreator);
    expect(msg1).toBe(msg2);
    expect(msg1).toBe(msg3);
  });

  it('fail when getting group with null instance constructor', () => {
    const accessor =
        Kernel.fromArrayBuffer(createArrayBuffer(0x0A, 0x02, 0x08, 0x01));
    const nullMessage = /** @type {function(!Kernel):!TestMessage} */
        (/** @type {*} */ (null));
    expect(() => accessor.getGroupOrNull(1, nullMessage)).toThrow();
  });

  it('fail when setting group value with null value', () => {
    const accessor = Kernel.fromArrayBuffer(new ArrayBuffer(0));
    const fakeMessage = /** @type {!TestMessage} */ (/** @type {*} */ (null));
    if (CHECK_CRITICAL_TYPE) {
      expect(() => accessor.setGroup(1, fakeMessage))
          .toThrowError('Given value is not a message instance: null');
    } else {
      // Note in unchecked mode we produce invalid output for invalid inputs.
      // This test just documents our behavior in those cases.
      // These values might change at any point and are not considered
      // what the implementation should be doing here.
      accessor.setMessage(1, fakeMessage);
      expect(accessor.getGroupOrNull(
                 /* fieldNumber= */ 1, TestMessage.instanceCreator))
          .toBeNull();
    }
  });

  it('reads group in a longer buffer', () => {
    const bytes = createArrayBuffer(
        0x12, 0x20,  // 32 length delimited
        0x00,        // random values for padding start
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00,  // random values for padding end
        0x0B,  // Group tag
        0x08, 0x02, 0x0C);
    const accessor = Kernel.fromArrayBuffer(bytes);
    const msg1 = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
    const msg2 = accessor.getGroupOrNull(1, TestMessage.instanceCreator);
    expect(msg1).toBe(msg2);
  });
});
