| // Protocol Buffers - Google's data interchange format | |
| // Copyright 2008 Google Inc. All rights reserved. | |
| // https://developers.google.com/protocol-buffers/ | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are | |
| // met: | |
| // | |
| // * Redistributions of source code must retain the above copyright | |
| // notice, this list of conditions and the following disclaimer. | |
| // * Redistributions in binary form must reproduce the above | |
| // copyright notice, this list of conditions and the following disclaimer | |
| // in the documentation and/or other materials provided with the | |
| // distribution. | |
| // * Neither the name of Google Inc. nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| /** | |
| * @fileoverview Test cases for jspb's binary protocol buffer decoder. | |
| * | |
| * There are two particular magic numbers that need to be pointed out - | |
| * 2^64-1025 is the largest number representable as both a double and an | |
| * unsigned 64-bit integer, and 2^63-513 is the largest number representable as | |
| * both a double and a signed 64-bit integer. | |
| * | |
| * Test suite is written using Jasmine -- see http://jasmine.github.io/ | |
| * | |
| * @author aappleby@google.com (Austin Appleby) | |
| */ | |
| goog.require('goog.testing.asserts'); | |
| goog.require('jspb.BinaryConstants'); | |
| goog.require('jspb.BinaryDecoder'); | |
| goog.require('jspb.BinaryEncoder'); | |
| goog.require('jspb.utils'); | |
| /** | |
| * Tests encoding and decoding of unsigned types. | |
| * @param {Function} readValue | |
| * @param {Function} writeValue | |
| * @param {number} epsilon | |
| * @param {number} upperLimit | |
| * @param {Function} filter | |
| * @suppress {missingProperties|visibility} | |
| */ | |
| function doTestUnsignedValue(readValue, | |
| writeValue, epsilon, upperLimit, filter) { | |
| var encoder = new jspb.BinaryEncoder(); | |
| // Encode zero and limits. | |
| writeValue.call(encoder, filter(0)); | |
| writeValue.call(encoder, filter(epsilon)); | |
| writeValue.call(encoder, filter(upperLimit)); | |
| // Encode positive values. | |
| for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { | |
| writeValue.call(encoder, filter(cursor)); | |
| } | |
| var decoder = jspb.BinaryDecoder.alloc(encoder.end()); | |
| // Check zero and limits. | |
| assertEquals(filter(0), readValue.call(decoder)); | |
| assertEquals(filter(epsilon), readValue.call(decoder)); | |
| assertEquals(filter(upperLimit), readValue.call(decoder)); | |
| // Check positive values. | |
| for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { | |
| if (filter(cursor) != readValue.call(decoder)) throw 'fail!'; | |
| } | |
| // Encoding values outside the valid range should assert. | |
| assertThrows(function() {writeValue.call(encoder, -1);}); | |
| assertThrows(function() {writeValue.call(encoder, upperLimit * 1.1);}); | |
| } | |
| /** | |
| * Tests encoding and decoding of signed types. | |
| * @param {Function} readValue | |
| * @param {Function} writeValue | |
| * @param {number} epsilon | |
| * @param {number} lowerLimit | |
| * @param {number} upperLimit | |
| * @param {Function} filter | |
| * @suppress {missingProperties} | |
| */ | |
| function doTestSignedValue(readValue, | |
| writeValue, epsilon, lowerLimit, upperLimit, filter) { | |
| var encoder = new jspb.BinaryEncoder(); | |
| // Encode zero and limits. | |
| writeValue.call(encoder, filter(lowerLimit)); | |
| writeValue.call(encoder, filter(-epsilon)); | |
| writeValue.call(encoder, filter(0)); | |
| writeValue.call(encoder, filter(epsilon)); | |
| writeValue.call(encoder, filter(upperLimit)); | |
| var inputValues = []; | |
| // Encode negative values. | |
| for (var cursor = lowerLimit; cursor < -epsilon; cursor /= 1.1) { | |
| var val = filter(cursor); | |
| writeValue.call(encoder, val); | |
| inputValues.push(val); | |
| } | |
| // Encode positive values. | |
| for (var cursor = epsilon; cursor < upperLimit; cursor *= 1.1) { | |
| var val = filter(cursor); | |
| writeValue.call(encoder, val); | |
| inputValues.push(val); | |
| } | |
| var decoder = jspb.BinaryDecoder.alloc(encoder.end()); | |
| // Check zero and limits. | |
| assertEquals(filter(lowerLimit), readValue.call(decoder)); | |
| assertEquals(filter(-epsilon), readValue.call(decoder)); | |
| assertEquals(filter(0), readValue.call(decoder)); | |
| assertEquals(filter(epsilon), readValue.call(decoder)); | |
| assertEquals(filter(upperLimit), readValue.call(decoder)); | |
| // Verify decoded values. | |
| for (var i = 0; i < inputValues.length; i++) { | |
| assertEquals(inputValues[i], readValue.call(decoder)); | |
| } | |
| // Encoding values outside the valid range should assert. | |
| var pastLowerLimit = lowerLimit * 1.1; | |
| var pastUpperLimit = upperLimit * 1.1; | |
| if (pastLowerLimit !== -Infinity) { | |
| expect(() => void writeValue.call(encoder, pastLowerLimit)).toThrow(); | |
| } | |
| if (pastUpperLimit !== Infinity) { | |
| expect(() => void writeValue.call(encoder, pastUpperLimit)).toThrow(); | |
| } | |
| } | |
| describe('binaryDecoderTest', function() { | |
| /** | |
| * Tests the decoder instance cache. | |
| */ | |
| it('testInstanceCache', /** @suppress {visibility} */ function() { | |
| // Empty the instance caches. | |
| jspb.BinaryDecoder.instanceCache_ = []; | |
| // Allocating and then freeing a decoder should put it in the instance | |
| // cache. | |
| jspb.BinaryDecoder.alloc().free(); | |
| assertEquals(1, jspb.BinaryDecoder.instanceCache_.length); | |
| // Allocating and then freeing three decoders should leave us with three in | |
| // the cache. | |
| var decoder1 = jspb.BinaryDecoder.alloc(); | |
| var decoder2 = jspb.BinaryDecoder.alloc(); | |
| var decoder3 = jspb.BinaryDecoder.alloc(); | |
| decoder1.free(); | |
| decoder2.free(); | |
| decoder3.free(); | |
| assertEquals(3, jspb.BinaryDecoder.instanceCache_.length); | |
| }); | |
| describe('varint64', function() { | |
| var /** !jspb.BinaryEncoder */ encoder; | |
| var /** !jspb.BinaryDecoder */ decoder; | |
| var hashA = String.fromCharCode(0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00); | |
| var hashB = String.fromCharCode(0x12, 0x34, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00); | |
| var hashC = String.fromCharCode(0x12, 0x34, 0x56, 0x78, | |
| 0x87, 0x65, 0x43, 0x21); | |
| var hashD = String.fromCharCode(0xFF, 0xFF, 0xFF, 0xFF, | |
| 0xFF, 0xFF, 0xFF, 0xFF); | |
| beforeEach(function() { | |
| encoder = new jspb.BinaryEncoder(); | |
| encoder.writeVarintHash64(hashA); | |
| encoder.writeVarintHash64(hashB); | |
| encoder.writeVarintHash64(hashC); | |
| encoder.writeVarintHash64(hashD); | |
| encoder.writeFixedHash64(hashA); | |
| encoder.writeFixedHash64(hashB); | |
| encoder.writeFixedHash64(hashC); | |
| encoder.writeFixedHash64(hashD); | |
| decoder = jspb.BinaryDecoder.alloc(encoder.end()); | |
| }); | |
| it('reads 64-bit integers as hash strings', function() { | |
| assertEquals(hashA, decoder.readVarintHash64()); | |
| assertEquals(hashB, decoder.readVarintHash64()); | |
| assertEquals(hashC, decoder.readVarintHash64()); | |
| assertEquals(hashD, decoder.readVarintHash64()); | |
| assertEquals(hashA, decoder.readFixedHash64()); | |
| assertEquals(hashB, decoder.readFixedHash64()); | |
| assertEquals(hashC, decoder.readFixedHash64()); | |
| assertEquals(hashD, decoder.readFixedHash64()); | |
| }); | |
| it('reads split 64 bit integers', function() { | |
| function hexJoin(bitsLow, bitsHigh) { | |
| return `0x${(bitsHigh >>> 0).toString(16)}:0x${ | |
| (bitsLow >>> 0).toString(16)}`; | |
| } | |
| function hexJoinHash(hash64) { | |
| jspb.utils.splitHash64(hash64); | |
| return hexJoin(jspb.utils.split64Low, jspb.utils.split64High); | |
| } | |
| expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashA)); | |
| expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashB)); | |
| expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashC)); | |
| expect(decoder.readSplitVarint64(hexJoin)).toEqual(hexJoinHash(hashD)); | |
| expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashA)); | |
| expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashB)); | |
| expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashC)); | |
| expect(decoder.readSplitFixed64(hexJoin)).toEqual(hexJoinHash(hashD)); | |
| }); | |
| }); | |
| describe('sint64', function() { | |
| var /** !jspb.BinaryDecoder */ decoder; | |
| var hashA = | |
| String.fromCharCode(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); | |
| var hashB = | |
| String.fromCharCode(0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); | |
| var hashC = | |
| String.fromCharCode(0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21); | |
| var hashD = | |
| String.fromCharCode(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); | |
| beforeEach(function() { | |
| var encoder = new jspb.BinaryEncoder(); | |
| encoder.writeZigzagVarintHash64(hashA); | |
| encoder.writeZigzagVarintHash64(hashB); | |
| encoder.writeZigzagVarintHash64(hashC); | |
| encoder.writeZigzagVarintHash64(hashD); | |
| decoder = jspb.BinaryDecoder.alloc(encoder.end()); | |
| }); | |
| it('reads 64-bit integers as decimal strings', function() { | |
| const signed = true; | |
| expect(decoder.readZigzagVarint64String()) | |
| .toEqual(jspb.utils.hash64ToDecimalString(hashA, signed)); | |
| expect(decoder.readZigzagVarint64String()) | |
| .toEqual(jspb.utils.hash64ToDecimalString(hashB, signed)); | |
| expect(decoder.readZigzagVarint64String()) | |
| .toEqual(jspb.utils.hash64ToDecimalString(hashC, signed)); | |
| expect(decoder.readZigzagVarint64String()) | |
| .toEqual(jspb.utils.hash64ToDecimalString(hashD, signed)); | |
| }); | |
| it('reads 64-bit integers as hash strings', function() { | |
| expect(decoder.readZigzagVarintHash64()).toEqual(hashA); | |
| expect(decoder.readZigzagVarintHash64()).toEqual(hashB); | |
| expect(decoder.readZigzagVarintHash64()).toEqual(hashC); | |
| expect(decoder.readZigzagVarintHash64()).toEqual(hashD); | |
| }); | |
| it('reads split 64 bit zigzag integers', function() { | |
| function hexJoin(bitsLow, bitsHigh) { | |
| return `0x${(bitsHigh >>> 0).toString(16)}:0x${ | |
| (bitsLow >>> 0).toString(16)}`; | |
| } | |
| function hexJoinHash(hash64) { | |
| jspb.utils.splitHash64(hash64); | |
| return hexJoin(jspb.utils.split64Low, jspb.utils.split64High); | |
| } | |
| expect(decoder.readSplitZigzagVarint64(hexJoin)) | |
| .toEqual(hexJoinHash(hashA)); | |
| expect(decoder.readSplitZigzagVarint64(hexJoin)) | |
| .toEqual(hexJoinHash(hashB)); | |
| expect(decoder.readSplitZigzagVarint64(hexJoin)) | |
| .toEqual(hexJoinHash(hashC)); | |
| expect(decoder.readSplitZigzagVarint64(hexJoin)) | |
| .toEqual(hexJoinHash(hashD)); | |
| }); | |
| it('does zigzag encoding properly', function() { | |
| // Test cases directly from the protobuf dev guide. | |
| // https://engdoc.corp.google.com/eng/howto/protocolbuffers/developerguide/encoding.shtml?cl=head#types | |
| var testCases = [ | |
| {original: '0', zigzag: '0'}, | |
| {original: '-1', zigzag: '1'}, | |
| {original: '1', zigzag: '2'}, | |
| {original: '-2', zigzag: '3'}, | |
| {original: '2147483647', zigzag: '4294967294'}, | |
| {original: '-2147483648', zigzag: '4294967295'}, | |
| // 64-bit extremes, not in dev guide. | |
| {original: '9223372036854775807', zigzag: '18446744073709551614'}, | |
| {original: '-9223372036854775808', zigzag: '18446744073709551615'}, | |
| // None of the above catch: bitsLow < 0 && bitsHigh > 0 && bitsHigh < | |
| // 0x1FFFFF. The following used to be broken. | |
| {original: '72000000000', zigzag: '144000000000'}, | |
| ]; | |
| var encoder = new jspb.BinaryEncoder(); | |
| testCases.forEach(function(c) { | |
| encoder.writeZigzagVarint64String(c.original); | |
| }); | |
| var buffer = encoder.end(); | |
| var zigzagDecoder = jspb.BinaryDecoder.alloc(buffer); | |
| var varintDecoder = jspb.BinaryDecoder.alloc(buffer); | |
| testCases.forEach(function(c) { | |
| expect(zigzagDecoder.readZigzagVarint64String()).toEqual(c.original); | |
| expect(varintDecoder.readUnsignedVarint64String()).toEqual(c.zigzag); | |
| }); | |
| }); | |
| }); | |
| /** | |
| * Tests reading and writing large strings | |
| */ | |
| it('testLargeStrings', function() { | |
| var encoder = new jspb.BinaryEncoder(); | |
| var len = 150000; | |
| var long_string = ''; | |
| for (var i = 0; i < len; i++) { | |
| long_string += 'a'; | |
| } | |
| encoder.writeString(long_string); | |
| var decoder = jspb.BinaryDecoder.alloc(encoder.end()); | |
| assertEquals(long_string, decoder.readString(len)); | |
| }); | |
| /** | |
| * Test encoding and decoding utf-8. | |
| */ | |
| it('testUtf8', function() { | |
| var encoder = new jspb.BinaryEncoder(); | |
| var ascii = "ASCII should work in 3, 2, 1..."; | |
| var utf8_two_bytes = "©"; | |
| var utf8_three_bytes = "❄"; | |
| var utf8_four_bytes = "😁"; | |
| encoder.writeString(ascii); | |
| encoder.writeString(utf8_two_bytes); | |
| encoder.writeString(utf8_three_bytes); | |
| encoder.writeString(utf8_four_bytes); | |
| var decoder = jspb.BinaryDecoder.alloc(encoder.end()); | |
| assertEquals(ascii, decoder.readString(ascii.length)); | |
| assertEquals(utf8_two_bytes, decoder.readString(utf8_two_bytes.length)); | |
| assertEquals(utf8_three_bytes, decoder.readString(utf8_three_bytes.length)); | |
| assertEquals(utf8_four_bytes, decoder.readString(utf8_four_bytes.length)); | |
| }); | |
| /** | |
| * Verifies that misuse of the decoder class triggers assertions. | |
| */ | |
| it('testDecodeErrors', function() { | |
| // Reading a value past the end of the stream should trigger an assertion. | |
| var decoder = jspb.BinaryDecoder.alloc([0, 1, 2]); | |
| assertThrows(function() {decoder.readUint64()}); | |
| // Overlong varints should trigger assertions. | |
| decoder.setBlock([255, 255, 255, 255, 255, 255, | |
| 255, 255, 255, 255, 255, 0]); | |
| assertThrows(function() {decoder.readUnsignedVarint64()}); | |
| decoder.reset(); | |
| assertThrows(function() {decoder.readSignedVarint64()}); | |
| decoder.reset(); | |
| assertThrows(function() {decoder.readZigzagVarint64()}); | |
| decoder.reset(); | |
| assertThrows(function() {decoder.readUnsignedVarint32()}); | |
| }); | |
| /** | |
| * Tests encoding and decoding of unsigned integers. | |
| */ | |
| it('testUnsignedIntegers', function() { | |
| doTestUnsignedValue( | |
| jspb.BinaryDecoder.prototype.readUint8, | |
| jspb.BinaryEncoder.prototype.writeUint8, | |
| 1, 0xFF, Math.round); | |
| doTestUnsignedValue( | |
| jspb.BinaryDecoder.prototype.readUint16, | |
| jspb.BinaryEncoder.prototype.writeUint16, | |
| 1, 0xFFFF, Math.round); | |
| doTestUnsignedValue( | |
| jspb.BinaryDecoder.prototype.readUint32, | |
| jspb.BinaryEncoder.prototype.writeUint32, | |
| 1, 0xFFFFFFFF, Math.round); | |
| doTestUnsignedValue( | |
| jspb.BinaryDecoder.prototype.readUint64, | |
| jspb.BinaryEncoder.prototype.writeUint64, | |
| 1, Math.pow(2, 64) - 1025, Math.round); | |
| }); | |
| /** | |
| * Tests encoding and decoding of signed integers. | |
| */ | |
| it('testSignedIntegers', function() { | |
| doTestSignedValue( | |
| jspb.BinaryDecoder.prototype.readInt8, | |
| jspb.BinaryEncoder.prototype.writeInt8, | |
| 1, -0x80, 0x7F, Math.round); | |
| doTestSignedValue( | |
| jspb.BinaryDecoder.prototype.readInt16, | |
| jspb.BinaryEncoder.prototype.writeInt16, | |
| 1, -0x8000, 0x7FFF, Math.round); | |
| doTestSignedValue( | |
| jspb.BinaryDecoder.prototype.readInt32, | |
| jspb.BinaryEncoder.prototype.writeInt32, | |
| 1, -0x80000000, 0x7FFFFFFF, Math.round); | |
| doTestSignedValue( | |
| jspb.BinaryDecoder.prototype.readInt64, | |
| jspb.BinaryEncoder.prototype.writeInt64, | |
| 1, -Math.pow(2, 63), Math.pow(2, 63) - 513, Math.round); | |
| }); | |
| /** | |
| * Tests encoding and decoding of floats. | |
| */ | |
| it('testFloats', function() { | |
| /** | |
| * @param {number} x | |
| * @return {number} | |
| */ | |
| function truncate(x) { | |
| var temp = new Float32Array(1); | |
| temp[0] = x; | |
| return temp[0]; | |
| } | |
| doTestSignedValue( | |
| jspb.BinaryDecoder.prototype.readFloat, | |
| jspb.BinaryEncoder.prototype.writeFloat, | |
| jspb.BinaryConstants.FLOAT32_EPS, | |
| -jspb.BinaryConstants.FLOAT32_MAX, | |
| jspb.BinaryConstants.FLOAT32_MAX, | |
| truncate); | |
| doTestSignedValue( | |
| jspb.BinaryDecoder.prototype.readDouble, | |
| jspb.BinaryEncoder.prototype.writeDouble, | |
| jspb.BinaryConstants.FLOAT64_EPS * 10, | |
| -jspb.BinaryConstants.FLOAT64_MAX, | |
| jspb.BinaryConstants.FLOAT64_MAX, | |
| function(x) { return x; }); | |
| }); | |
| }); |