| /** |
| * @fileoverview Tests for indexer.js. |
| */ |
| goog.module('protobuf.binary.IndexerTest'); |
| |
| goog.setTestOnly(); |
| |
| // Note to the reader: |
| // Since the index behavior changes with the checking level some of the tests |
| // in this file have to know which checking level is enabled to make correct |
| // assertions. |
| // Test are run in all checking levels. |
| const BinaryStorage = goog.require('protobuf.runtime.BinaryStorage'); |
| const BufferDecoder = goog.require('protobuf.binary.BufferDecoder'); |
| const WireType = goog.require('protobuf.binary.WireType'); |
| const {CHECK_CRITICAL_STATE} = goog.require('protobuf.internal.checks'); |
| const {Field, IndexEntry} = goog.require('protobuf.binary.field'); |
| const {buildIndex} = goog.require('protobuf.binary.indexer'); |
| const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper'); |
| |
| /** |
| * Returns the number of fields stored. |
| * |
| * @param {!BinaryStorage} storage |
| * @return {number} |
| */ |
| function getStorageSize(storage) { |
| let size = 0; |
| storage.forEach(() => void size++); |
| return size; |
| } |
| |
| /** |
| * @type {number} |
| */ |
| const PIVOT = 1; |
| |
| /** |
| * Asserts a single IndexEntry at a given field number. |
| * @param {!BinaryStorage} storage |
| * @param {number} fieldNumber |
| * @param {...!IndexEntry} expectedEntries |
| */ |
| function assertStorageEntries(storage, fieldNumber, ...expectedEntries) { |
| expect(getStorageSize(storage)).toBe(1); |
| |
| const entryArray = storage.get(fieldNumber).getIndexArray(); |
| expect(entryArray).not.toBeUndefined(); |
| expect(entryArray.length).toBe(expectedEntries.length); |
| |
| for (let i = 0; i < entryArray.length; i++) { |
| const storageEntry = entryArray[i]; |
| const expectedEntry = expectedEntries[i]; |
| |
| expect(storageEntry).toBe(expectedEntry); |
| } |
| } |
| |
| describe('Indexer does', () => { |
| it('return empty storage for empty array', () => { |
| const storage = buildIndex(createBufferDecoder(), PIVOT); |
| expect(storage).not.toBeNull(); |
| expect(getStorageSize(storage)).toBe(0); |
| }); |
| |
| it('throw for null array', () => { |
| expect( |
| () => buildIndex( |
| /** @type {!BufferDecoder} */ (/** @type {*} */ (null)), PIVOT)) |
| .toThrow(); |
| }); |
| |
| it('fail for invalid wire type (6)', () => { |
| expect(() => buildIndex(createBufferDecoder(0x0E, 0x01), PIVOT)) |
| .toThrowError('Invalid wire type: 6'); |
| }); |
| |
| it('fail for invalid wire type (7)', () => { |
| expect(() => buildIndex(createBufferDecoder(0x0F, 0x01), PIVOT)) |
| .toThrowError('Invalid wire type: 7'); |
| }); |
| |
| it('index varint', () => { |
| const data = createBufferDecoder(0x08, 0x01, 0x08, 0x01); |
| const storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 1), |
| Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 3)); |
| }); |
| |
| it('index varint with two bytes field number', () => { |
| const data = createBufferDecoder(0xF8, 0x01, 0x01); |
| const storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 31, |
| Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 2)); |
| }); |
| |
| it('fail for varints that are longer than 10 bytes', () => { |
| const data = createBufferDecoder( |
| 0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00); |
| if (CHECK_CRITICAL_STATE) { |
| expect(() => buildIndex(data, PIVOT)) |
| .toThrowError('Index out of bounds: index: 12 size: 11'); |
| } 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 storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 1)); |
| } |
| }); |
| |
| it('fail for varints with no data', () => { |
| const data = createBufferDecoder(0x08); |
| expect(() => buildIndex(data, PIVOT)).toThrow(); |
| }); |
| |
| it('index fixed64', () => { |
| const data = createBufferDecoder( |
| /* first= */ 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, |
| /* second= */ 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); |
| const storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1), |
| Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 10)); |
| }); |
| |
| it('fail for fixed64 data missing in input', () => { |
| const data = |
| createBufferDecoder(0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07); |
| if (CHECK_CRITICAL_STATE) { |
| expect(() => buildIndex(data, PIVOT)) |
| .toThrowError('Index out of bounds: index: 9 size: 8'); |
| } 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 storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1)); |
| } |
| }); |
| |
| it('fail for fixed64 tag that has no data after it', () => { |
| if (CHECK_CRITICAL_STATE) { |
| const data = createBufferDecoder(0x09); |
| expect(() => buildIndex(data, PIVOT)) |
| .toThrowError('Index out of bounds: index: 9 size: 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 data = createBufferDecoder(0x09); |
| const storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1)); |
| } |
| }); |
| |
| it('index delimited', () => { |
| const data = createBufferDecoder( |
| /* first= */ 0x0A, 0x02, 0x00, 0x01, /* second= */ 0x0A, 0x02, 0x00, |
| 0x01); |
| const storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 1), |
| Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 5)); |
| }); |
| |
| it('fail for length deliimted field data missing in input', () => { |
| const data = createBufferDecoder(0x0A, 0x04, 0x00, 0x01); |
| if (CHECK_CRITICAL_STATE) { |
| expect(() => buildIndex(data, PIVOT)) |
| .toThrowError('Index out of bounds: index: 6 size: 4'); |
| } 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 storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 1)); |
| } |
| }); |
| |
| it('fail for delimited tag that has no data after it', () => { |
| const data = createBufferDecoder(0x0A); |
| expect(() => buildIndex(data, PIVOT)).toThrow(); |
| }); |
| |
| it('index fixed32', () => { |
| const data = createBufferDecoder( |
| /* first= */ 0x0D, 0x01, 0x02, 0x03, 0x04, /* second= */ 0x0D, 0x01, |
| 0x02, 0x03, 0x04); |
| const storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1), |
| Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 6)); |
| }); |
| |
| it('fail for fixed32 data missing in input', () => { |
| const data = createBufferDecoder(0x0D, 0x01, 0x02, 0x03); |
| |
| if (CHECK_CRITICAL_STATE) { |
| expect(() => buildIndex(data, PIVOT)) |
| .toThrowError('Index out of bounds: index: 5 size: 4'); |
| } 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 storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1)); |
| } |
| }); |
| |
| it('fail for fixed32 tag that has no data after it', () => { |
| if (CHECK_CRITICAL_STATE) { |
| const data = createBufferDecoder(0x0D); |
| expect(() => buildIndex(data, PIVOT)) |
| .toThrowError('Index out of bounds: index: 5 size: 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 data = createBufferDecoder(0x0D); |
| const storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1)); |
| } |
| }); |
| |
| it('index group', () => { |
| const data = createBufferDecoder( |
| /* first= */ 0x0B, 0x08, 0x01, 0x0C, /* second= */ 0x0B, 0x08, 0x01, |
| 0x0C); |
| const storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1), |
| Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 5)); |
| }); |
| |
| it('index group and skips inner group', () => { |
| const data = |
| createBufferDecoder(0x0B, 0x0B, 0x08, 0x01, 0x0C, 0x08, 0x01, 0x0C); |
| const storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1)); |
| }); |
| |
| it('fail on unmatched stop group', () => { |
| const data = createBufferDecoder(0x0C, 0x01); |
| if (CHECK_CRITICAL_STATE) { |
| expect(() => buildIndex(data, PIVOT)) |
| .toThrowError('Found unmatched stop group.'); |
| } 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 storage = buildIndex(data, PIVOT); |
| |
| expect(getStorageSize(storage)).toBe(1); |
| const entryArray = storage.get(1).getIndexArray(); |
| expect(entryArray).not.toBeUndefined(); |
| expect(entryArray.length).toBe(1); |
| const entry = entryArray[0]; |
| |
| expect(Field.getWireType(entry)).toBe(WireType.END_GROUP); |
| expect(Field.getStartIndex(entry)).toBe(1); |
| |
| const entryArray2 = storage.get(0).getIndexArray(); |
| expect(entryArray2).not.toBeUndefined(); |
| expect(entryArray2.length).toBe(1); |
| const entry2 = entryArray2[0]; |
| |
| expect(Field.getWireType(entry2)).toBe(WireType.FIXED64); |
| expect(Field.getStartIndex(entry2)).toBe(2); |
| } |
| }); |
| |
| it('fail for groups without matching stop group', () => { |
| const data = createBufferDecoder(0x0B, 0x08, 0x01, 0x1C); |
| if (CHECK_CRITICAL_STATE) { |
| expect(() => buildIndex(data, PIVOT)) |
| .toThrowError('Expected stop group for fieldnumber 1 not found.'); |
| } 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 storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1)); |
| } |
| }); |
| |
| it('fail for groups without stop group', () => { |
| const data = createBufferDecoder(0x0B, 0x08, 0x01); |
| if (CHECK_CRITICAL_STATE) { |
| expect(() => buildIndex(data, PIVOT)).toThrowError('No end group found.'); |
| } 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 storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1)); |
| } |
| }); |
| |
| it('fail for group tag that has no data after it', () => { |
| const data = createBufferDecoder(0x0B); |
| if (CHECK_CRITICAL_STATE) { |
| expect(() => buildIndex(data, PIVOT)).toThrowError('No end group found.'); |
| } 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 storage = buildIndex(data, PIVOT); |
| assertStorageEntries( |
| storage, /* fieldNumber= */ 1, |
| Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1)); |
| } |
| }); |
| |
| it('index too large tag', () => { |
| const data = createBufferDecoder(0xF8, 0xFF, 0xFF, 0xFF, 0xFF); |
| expect(() => buildIndex(data, PIVOT)).toThrow(); |
| }); |
| |
| it('fail for varint tag that has no data after it', () => { |
| const data = createBufferDecoder(0x08); |
| expect(() => buildIndex(data, PIVOT)).toThrow(); |
| }); |
| }); |