From 41b710700004b965828f2128bfbfdb573e4972cd Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Tue, 21 Jun 2022 15:00:59 -0400 Subject: [PATCH 01/12] deleted float parser and added special test cases for Double --- src/float_parser.ts | 152 ----------------------------- src/parser/serializer.ts | 5 +- test/node/bson_corpus.spec.test.js | 5 - test/node/double_tests.js | 59 +++++++++++ 4 files changed, 61 insertions(+), 160 deletions(-) delete mode 100644 src/float_parser.ts diff --git a/src/float_parser.ts b/src/float_parser.ts deleted file mode 100644 index c881f4cf..00000000 --- a/src/float_parser.ts +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) 2008, Fair Oaks Labs, Inc. -// All rights reserved. -// -// 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 Fair Oaks Labs, 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. -// -// -// Modifications to writeIEEE754 to support negative zeroes made by Brian White - -type NumericalSequence = { [index: number]: number }; - -export function readIEEE754( - buffer: NumericalSequence, - offset: number, - endian: 'big' | 'little', - mLen: number, - nBytes: number -): number { - let e: number; - let m: number; - const bBE = endian === 'big'; - const eLen = nBytes * 8 - mLen - 1; - const eMax = (1 << eLen) - 1; - const eBias = eMax >> 1; - let nBits = -7; - let i = bBE ? 0 : nBytes - 1; - const d = bBE ? 1 : -1; - let s = buffer[offset + i]; - - i += d; - - e = s & ((1 << -nBits) - 1); - s >>= -nBits; - nBits += eLen; - for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8); - - m = e & ((1 << -nBits) - 1); - e >>= -nBits; - nBits += mLen; - for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8); - - if (e === 0) { - e = 1 - eBias; - } else if (e === eMax) { - return m ? NaN : (s ? -1 : 1) * Infinity; - } else { - m = m + Math.pow(2, mLen); - e = e - eBias; - } - return (s ? -1 : 1) * m * Math.pow(2, e - mLen); -} - -export function writeIEEE754( - buffer: NumericalSequence, - value: number, - offset: number, - endian: 'big' | 'little', - mLen: number, - nBytes: number -): void { - let e: number; - let m: number; - let c: number; - const bBE = endian === 'big'; - let eLen = nBytes * 8 - mLen - 1; - const eMax = (1 << eLen) - 1; - const eBias = eMax >> 1; - const rt = mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0; - let i = bBE ? nBytes - 1 : 0; - const d = bBE ? -1 : 1; - const s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; - - value = Math.abs(value); - - if (isNaN(value) || value === Infinity) { - m = isNaN(value) ? 1 : 0; - e = eMax; - } else { - e = Math.floor(Math.log(value) / Math.LN2); - if (value * (c = Math.pow(2, -e)) < 1) { - e--; - c *= 2; - } - if (e + eBias >= 1) { - value += rt / c; - } else { - value += rt * Math.pow(2, 1 - eBias); - } - if (value * c >= 2) { - e++; - c /= 2; - } - - if (e + eBias >= eMax) { - m = 0; - e = eMax; - } else if (e + eBias >= 1) { - m = (value * c - 1) * Math.pow(2, mLen); - e = e + eBias; - } else { - m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); - e = 0; - } - } - - if (isNaN(value)) m = 0; - - while (mLen >= 8) { - buffer[offset + i] = m & 0xff; - i += d; - m /= 256; - mLen -= 8; - } - - e = (e << mLen) | m; - - if (isNaN(value)) e += 8; - - eLen += mLen; - - while (eLen > 0) { - buffer[offset + i] = e & 0xff; - i += d; - e /= 256; - eLen -= 8; - } - - buffer[offset + i - d] |= s * 128; -} diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index da94e1c4..53edf608 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -9,7 +9,6 @@ import type { Double } from '../double'; import { ensureBuffer } from '../ensure_buffer'; import { BSONError, BSONTypeError } from '../error'; import { isBSONType } from '../extended_json'; -import { writeIEEE754 } from '../float_parser'; import type { Int32 } from '../int_32'; import { Long } from '../long'; import { Map } from '../map'; @@ -119,7 +118,7 @@ function serializeNumber( index = index + numberOfWrittenBytes; buffer[index++] = 0; // Write float - writeIEEE754(buffer, value, index, 'little', 52, 8); + buffer.writeDoubleLE(value, index); // Adjust index index = index + 8; } @@ -487,7 +486,7 @@ function serializeDouble( buffer[index++] = 0; // Write float - writeIEEE754(buffer, value.value, index, 'little', 52, 8); + buffer.writeDoubleLE(value.value, index); // little endian by default // Adjust index index = index + 8; diff --git a/test/node/bson_corpus.spec.test.js b/test/node/bson_corpus.spec.test.js index b40d6c03..62dbf94b 100644 --- a/test/node/bson_corpus.spec.test.js +++ b/test/node/bson_corpus.spec.test.js @@ -121,11 +121,6 @@ describe('BSON Corpus', function () { describe('valid-bson', function () { for (const v of valid) { it(v.description, function () { - if (v.description === 'NaN with payload') { - // TODO(NODE-3630): remove custom float parser so we can handle the NaN payload data - this.skip(); - } - if ( v.description === 'All BSON types' && scenario._filename === 'multi-type-deprecated' diff --git a/test/node/double_tests.js b/test/node/double_tests.js index 9c690174..daae0e17 100644 --- a/test/node/double_tests.js +++ b/test/node/double_tests.js @@ -35,4 +35,63 @@ describe('Double', function () { }); } }); + + describe('specialValues', () => { + function twiceSerialized(value) { + let serializedDouble = BSON.serialize({ d: new Double(value) }); + let deserializedDouble = BSON.deserialize(serializedDouble, { promoteValues: true }); + let newVal = deserializedDouble.d; + return newVal; + } + + it('inf', () => { + let value = Infinity; + let orig = new Double(value).valueOf(); + let newVal = twiceSerialized(value); + expect(orig).to.equal(newVal); + }); + + it('-inf', () => { + let value = -Infinity; + let orig = new Double(value).valueOf(); + let newVal = twiceSerialized(value); + expect(orig).to.equal(newVal); + }); + + it('NaN', () => { + let value = NaN; + let newVal = twiceSerialized(value); + expect(Number.isNaN(newVal)).to.equal(true); + }); + + it('NaN with payload', () => { + let buffer = Buffer.from('120000000000F87F', 'hex'); + let value = buffer.readDoubleLE(0); + let serializedDouble = BSON.serialize({ d: new Double(value) }); + expect(serializedDouble.subarray(7, 15)).to.deep.equal(buffer); + let { d: newVal } = BSON.deserialize(serializedDouble, { promoteValues: true }); + expect(Number.isNaN(newVal)).to.equal(true); + }); + + it('0', () => { + let value = 0; + let orig = new Double(value).valueOf(); + let newVal = twiceSerialized(value); + expect(orig).to.equal(newVal); + }); + + it('-0', () => { + let value = -0; + let orig = new Double(value).valueOf(); + let newVal = twiceSerialized(value); + expect(orig).to.equal(newVal); + }); + + it('Number.EPSILON', () => { + let value = Number.EPSILON; + let orig = new Double(value).valueOf(); + let newVal = twiceSerialized(value); + expect(orig).to.equal(newVal); + }); + }); }); From 1c1774e2ac10e9178b9d250b905b4f56e2769d9a Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Thu, 23 Jun 2022 15:35:44 -0400 Subject: [PATCH 02/12] Added node version handling for NaN w payload test cases --- test/node/bson_corpus.spec.test.js | 5 +++++ test/node/double_tests.js | 6 +++++- test/node/tools/utils.js | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/test/node/bson_corpus.spec.test.js b/test/node/bson_corpus.spec.test.js index 62dbf94b..3cdfa6c5 100644 --- a/test/node/bson_corpus.spec.test.js +++ b/test/node/bson_corpus.spec.test.js @@ -3,6 +3,7 @@ const Buffer = require('buffer').Buffer; const BSON = require('../register-bson'); +const { getNodeMajor } = require('./tools/utils'); const BSONError = BSON.BSONError; const EJSON = BSON.EJSON; @@ -121,6 +122,10 @@ describe('BSON Corpus', function () { describe('valid-bson', function () { for (const v of valid) { it(v.description, function () { + if (v.description === 'NaN with payload' && getNodeMajor() < 10) { + this.skip(); + } + if ( v.description === 'All BSON types' && scenario._filename === 'multi-type-deprecated' diff --git a/test/node/double_tests.js b/test/node/double_tests.js index daae0e17..8812f9f8 100644 --- a/test/node/double_tests.js +++ b/test/node/double_tests.js @@ -1,6 +1,7 @@ 'use strict'; const BSON = require('../register-bson'); +const { getNodeMajor } = require('./tools/utils'); const Double = BSON.Double; describe('Double', function () { @@ -64,7 +65,10 @@ describe('Double', function () { expect(Number.isNaN(newVal)).to.equal(true); }); - it('NaN with payload', () => { + it('NaN with payload', function () { + if (getNodeMajor() < 10) { + this.skip(); + } let buffer = Buffer.from('120000000000F87F', 'hex'); let value = buffer.readDoubleLE(0); let serializedDouble = BSON.serialize({ d: new Double(value) }); diff --git a/test/node/tools/utils.js b/test/node/tools/utils.js index 788c04ea..b441ea27 100644 --- a/test/node/tools/utils.js +++ b/test/node/tools/utils.js @@ -158,6 +158,11 @@ exports.isNode6 = function () { return process.version.split('.')[0] === 'v6'; }; +exports.getNodeMajor = function () { + // eslint-disable-next-line no-undef + return Number(process.versions.node.split('.')[0]); +}; + const getSymbolFrom = function (target, symbolName, assertExists) { if (assertExists == null) assertExists = true; From 39c0addc6dea8181c085b6a375b6c5076def2ace Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Thu, 23 Jun 2022 15:44:17 -0400 Subject: [PATCH 03/12] Limited explicit Double casting --- src/parser/serializer.ts | 2 +- test/node/double_tests.js | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index 53edf608..7a98be4d 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -486,7 +486,7 @@ function serializeDouble( buffer[index++] = 0; // Write float - buffer.writeDoubleLE(value.value, index); // little endian by default + buffer.writeDoubleLE(value.value, index); // Adjust index index = index + 8; diff --git a/test/node/double_tests.js b/test/node/double_tests.js index 8812f9f8..cf507079 100644 --- a/test/node/double_tests.js +++ b/test/node/double_tests.js @@ -39,7 +39,7 @@ describe('Double', function () { describe('specialValues', () => { function twiceSerialized(value) { - let serializedDouble = BSON.serialize({ d: new Double(value) }); + let serializedDouble = BSON.serialize({ d: value }); let deserializedDouble = BSON.deserialize(serializedDouble, { promoteValues: true }); let newVal = deserializedDouble.d; return newVal; @@ -47,16 +47,14 @@ describe('Double', function () { it('inf', () => { let value = Infinity; - let orig = new Double(value).valueOf(); let newVal = twiceSerialized(value); - expect(orig).to.equal(newVal); + expect(value).to.equal(newVal); }); it('-inf', () => { let value = -Infinity; - let orig = new Double(value).valueOf(); let newVal = twiceSerialized(value); - expect(orig).to.equal(newVal); + expect(value).to.equal(newVal); }); it('NaN', () => { @@ -71,7 +69,7 @@ describe('Double', function () { } let buffer = Buffer.from('120000000000F87F', 'hex'); let value = buffer.readDoubleLE(0); - let serializedDouble = BSON.serialize({ d: new Double(value) }); + let serializedDouble = BSON.serialize({ d: value }); expect(serializedDouble.subarray(7, 15)).to.deep.equal(buffer); let { d: newVal } = BSON.deserialize(serializedDouble, { promoteValues: true }); expect(Number.isNaN(newVal)).to.equal(true); @@ -80,22 +78,21 @@ describe('Double', function () { it('0', () => { let value = 0; let orig = new Double(value).valueOf(); - let newVal = twiceSerialized(value); + let newVal = twiceSerialized(orig); expect(orig).to.equal(newVal); }); it('-0', () => { let value = -0; let orig = new Double(value).valueOf(); - let newVal = twiceSerialized(value); + let newVal = twiceSerialized(orig); expect(orig).to.equal(newVal); }); it('Number.EPSILON', () => { let value = Number.EPSILON; - let orig = new Double(value).valueOf(); let newVal = twiceSerialized(value); - expect(orig).to.equal(newVal); + expect(value).to.equal(newVal); }); }); }); From 37c8edb5253a469a64ff5a8076f4c09b5bc47415 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Thu, 23 Jun 2022 17:06:57 -0400 Subject: [PATCH 04/12] Limited double wrapping, added skipped -0 test case --- test/node/double_tests.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test/node/double_tests.js b/test/node/double_tests.js index cf507079..4a358cf5 100644 --- a/test/node/double_tests.js +++ b/test/node/double_tests.js @@ -77,16 +77,24 @@ describe('Double', function () { it('0', () => { let value = 0; - let orig = new Double(value).valueOf(); + let orig = new Double(value); let newVal = twiceSerialized(orig); - expect(orig).to.equal(newVal); + expect(value).to.equal(newVal); }); it('-0', () => { let value = -0; - let orig = new Double(value).valueOf(); + let orig = new Double(value); let newVal = twiceSerialized(orig); - expect(orig).to.equal(newVal); + expect(Object.is(newVal, -0)).to.be.true; + }); + + // TODO (NODE-4335): -0 should be serialized as double + it.skip('-0 serializes as Double', () => { + let value = -0; + let serializedDouble = BSON.serialize({ d: value }); + let type = serializedDouble[5]; + expect(type).to.not.equal(BSON.BSON_DATA_NUMBER); }); it('Number.EPSILON', () => { From fb76f7d8ad02dcbf23d16af92ffdcf0cdda7e1b9 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Tue, 28 Jun 2022 15:44:55 -0400 Subject: [PATCH 05/12] perf optimizations, buffer index and deserialization fix --- etc/benchmarks/main.mjs | 11 ++++++++++- src/parser/deserializer.ts | 6 ++++-- src/parser/serializer.ts | 12 ++++++++++-- test/node/bson_corpus.spec.test.js | 4 ++-- test/node/double_tests.js | 9 ++++++--- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/etc/benchmarks/main.mjs b/etc/benchmarks/main.mjs index 8fd6d858..1c9f632d 100644 --- a/etc/benchmarks/main.mjs +++ b/etc/benchmarks/main.mjs @@ -9,7 +9,7 @@ console.log(); //////////////////////////////////////////////////////////////////////////////////////////////////// await runner({ - skip: false, + skip: true, name: 'deserialize({ oid, string }, { validation: { utf8: false } })', iterations, setup(libs) { @@ -58,6 +58,15 @@ await runner({ } }); +await runner({ + skip: false, + name: 'Double Serialization', + iterations, + run(i, bson) { + bson.lib.serialize({ d: 2.3 }); + } +}); + // End console.log( 'Total time taken to benchmark:', diff --git a/src/parser/deserializer.ts b/src/parser/deserializer.ts index 411731e7..2dae4721 100644 --- a/src/parser/deserializer.ts +++ b/src/parser/deserializer.ts @@ -263,10 +263,12 @@ function deserializeObject( (buffer[index++] << 16) | (buffer[index++] << 24); } else if (elementType === constants.BSON_DATA_NUMBER && promoteValues === false) { - value = new Double(buffer.readDoubleLE(index)); + const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); + value = new Double(dv.getFloat64(index, true)); index = index + 8; } else if (elementType === constants.BSON_DATA_NUMBER) { - value = buffer.readDoubleLE(index); + const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); + value = dv.getFloat64(index, true); index = index + 8; } else if (elementType === constants.BSON_DATA_DATE) { const lowBits = diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index 7a98be4d..78ac8b40 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -78,6 +78,12 @@ function serializeString( return index; } +const SPACE_FOR_FLOAT64 = new Uint8Array(8); +const DV_FOR_FLOAT64 = new DataView( + SPACE_FOR_FLOAT64.buffer, + SPACE_FOR_FLOAT64.byteOffset, + SPACE_FOR_FLOAT64.byteLength +); function serializeNumber( buffer: Buffer, key: string, @@ -118,7 +124,8 @@ function serializeNumber( index = index + numberOfWrittenBytes; buffer[index++] = 0; // Write float - buffer.writeDoubleLE(value, index); + DV_FOR_FLOAT64.setFloat64(0, value, true); + buffer.set(SPACE_FOR_FLOAT64, index); // Adjust index index = index + 8; } @@ -486,7 +493,8 @@ function serializeDouble( buffer[index++] = 0; // Write float - buffer.writeDoubleLE(value.value, index); + DV_FOR_FLOAT64.setFloat64(0, value.value, true); + buffer.set(SPACE_FOR_FLOAT64, index); // Adjust index index = index + 8; diff --git a/test/node/bson_corpus.spec.test.js b/test/node/bson_corpus.spec.test.js index 3cdfa6c5..4709eb45 100644 --- a/test/node/bson_corpus.spec.test.js +++ b/test/node/bson_corpus.spec.test.js @@ -3,7 +3,7 @@ const Buffer = require('buffer').Buffer; const BSON = require('../register-bson'); -const { getNodeMajor } = require('./tools/utils'); +const { getNodeMajor, isBrowser } = require('./tools/utils'); const BSONError = BSON.BSONError; const EJSON = BSON.EJSON; @@ -122,7 +122,7 @@ describe('BSON Corpus', function () { describe('valid-bson', function () { for (const v of valid) { it(v.description, function () { - if (v.description === 'NaN with payload' && getNodeMajor() < 10) { + if (v.description === 'NaN with payload' && !isBrowser() && getNodeMajor() < 10) { this.skip(); } diff --git a/test/node/double_tests.js b/test/node/double_tests.js index 4a358cf5..0059340f 100644 --- a/test/node/double_tests.js +++ b/test/node/double_tests.js @@ -1,7 +1,7 @@ 'use strict'; const BSON = require('../register-bson'); -const { getNodeMajor } = require('./tools/utils'); +const { getNodeMajor, isBrowser } = require('./tools/utils'); const Double = BSON.Double; describe('Double', function () { @@ -64,11 +64,14 @@ describe('Double', function () { }); it('NaN with payload', function () { - if (getNodeMajor() < 10) { + if (!isBrowser() && getNodeMajor() < 10) { this.skip(); } let buffer = Buffer.from('120000000000F87F', 'hex'); - let value = buffer.readDoubleLE(0); + + const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); + let value = dv.getFloat64(0, true); + let serializedDouble = BSON.serialize({ d: value }); expect(serializedDouble.subarray(7, 15)).to.deep.equal(buffer); let { d: newVal } = BSON.deserialize(serializedDouble, { promoteValues: true }); From e45f7d07d85bd2dd75a25e0e7151f9baf98c81cb Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Tue, 28 Jun 2022 16:42:50 -0400 Subject: [PATCH 06/12] deserialization perf checks --- etc/benchmarks/main.mjs | 34 +++++++++++++++++++++++++++++++++- src/parser/deserializer.ts | 7 +++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/etc/benchmarks/main.mjs b/etc/benchmarks/main.mjs index 1c9f632d..2931c139 100644 --- a/etc/benchmarks/main.mjs +++ b/etc/benchmarks/main.mjs @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import { performance } from 'perf_hooks'; +import bson from '../../lib/bson.js'; import { runner, systemInfo, getCurrentLocalBSON } from './lib_runner.mjs'; const iterations = 1_000_000; @@ -59,7 +60,7 @@ await runner({ }); await runner({ - skip: false, + skip: true, name: 'Double Serialization', iterations, run(i, bson) { @@ -67,6 +68,37 @@ await runner({ } }); +await runner({ + skip: false, + name: 'Double Deserialization', + iterations, + setup(libs) { + const bson = getCurrentLocalBSON(libs); + return bson.lib.serialize({ d: 2.3 }); + }, + run(i, bson, serialized_double) { + bson.lib.deserialize(serialized_double); + } +}); + +await runner({ + skip: false, + name: 'Many Doubles Deserialization', + iterations, + setup(libs) { + const bson = getCurrentLocalBSON(libs); + let doubles = Object.fromEntries( + Array.from({ length: 1000 }, i => { + return [`a_${i}`, 2.3]; + }) + ); + return bson.lib.serialize(doubles); + }, + run(i, bson, serialized_doubles) { + bson.lib.deserialize(serialized_doubles); + } +}); + // End console.log( 'Total time taken to benchmark:', diff --git a/src/parser/deserializer.ts b/src/parser/deserializer.ts index 2dae4721..1da8a985 100644 --- a/src/parser/deserializer.ts +++ b/src/parser/deserializer.ts @@ -197,6 +197,7 @@ function deserializeObject( let isPossibleDBRef = isArray ? false : null; // While we have more left data left keep parsing + const dataview = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); while (!done) { // Read the type const elementType = buffer[index++]; @@ -263,12 +264,10 @@ function deserializeObject( (buffer[index++] << 16) | (buffer[index++] << 24); } else if (elementType === constants.BSON_DATA_NUMBER && promoteValues === false) { - const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); - value = new Double(dv.getFloat64(index, true)); + value = new Double(dataview.getFloat64(index, true)); index = index + 8; } else if (elementType === constants.BSON_DATA_NUMBER) { - const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); - value = dv.getFloat64(index, true); + value = dataview.getFloat64(index, true); index = index + 8; } else if (elementType === constants.BSON_DATA_DATE) { const lowBits = From af47143052911239774a5338895e4ccc7e232303 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Wed, 29 Jun 2022 11:20:00 -0400 Subject: [PATCH 07/12] removed accidental import --- etc/benchmarks/main.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/etc/benchmarks/main.mjs b/etc/benchmarks/main.mjs index 2931c139..e6530891 100644 --- a/etc/benchmarks/main.mjs +++ b/etc/benchmarks/main.mjs @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import { performance } from 'perf_hooks'; -import bson from '../../lib/bson.js'; import { runner, systemInfo, getCurrentLocalBSON } from './lib_runner.mjs'; const iterations = 1_000_000; From 81d7544de68d7f115b873389e151fe4f6bba1234 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Fri, 1 Jul 2022 14:24:39 -0400 Subject: [PATCH 08/12] double test refactoring and PR fixes --- test/node/bson_corpus.spec.test.js | 5 - test/node/double_tests.js | 155 +++++++++++++---------------- 2 files changed, 71 insertions(+), 89 deletions(-) diff --git a/test/node/bson_corpus.spec.test.js b/test/node/bson_corpus.spec.test.js index 4709eb45..62dbf94b 100644 --- a/test/node/bson_corpus.spec.test.js +++ b/test/node/bson_corpus.spec.test.js @@ -3,7 +3,6 @@ const Buffer = require('buffer').Buffer; const BSON = require('../register-bson'); -const { getNodeMajor, isBrowser } = require('./tools/utils'); const BSONError = BSON.BSONError; const EJSON = BSON.EJSON; @@ -122,10 +121,6 @@ describe('BSON Corpus', function () { describe('valid-bson', function () { for (const v of valid) { it(v.description, function () { - if (v.description === 'NaN with payload' && !isBrowser() && getNodeMajor() < 10) { - this.skip(); - } - if ( v.description === 'All BSON types' && scenario._filename === 'multi-type-deprecated' diff --git a/test/node/double_tests.js b/test/node/double_tests.js index 0059340f..8f8965bd 100644 --- a/test/node/double_tests.js +++ b/test/node/double_tests.js @@ -1,109 +1,96 @@ 'use strict'; const BSON = require('../register-bson'); -const { getNodeMajor, isBrowser } = require('./tools/utils'); const Double = BSON.Double; -describe('Double', function () { - describe('Constructor', function () { - var value = 42.3456; +describe('BSON Double Precision', function () { + context('class Double', function () { + describe('constructor()', function () { + const value = 42.3456; - it('Primitive number', function (done) { - expect(new Double(value).valueOf()).to.equal(value); - done(); - }); - - it('Number object', function (done) { - expect(new Double(new Number(value)).valueOf()).to.equal(value); - done(); - }); - }); + it('Primitive number', function (done) { + expect(new Double(value).valueOf()).to.equal(value); + done(); + }); - describe('toString', () => { - it('should serialize to a string', () => { - const testNumber = Math.random() * Number.MAX_VALUE; - const double = new Double(testNumber); - expect(double.toString()).to.equal(testNumber.toString()); + it('Number object', function (done) { + expect(new Double(new Number(value)).valueOf()).to.equal(value); + done(); + }); }); - const testRadices = [2, 8, 10, 16, 22]; - - for (const radix of testRadices) { - it(`should support radix argument: ${radix}`, () => { + describe('#toString()', () => { + it('should serialize to a string', () => { const testNumber = Math.random() * Number.MAX_VALUE; const double = new Double(testNumber); - expect(double.toString(radix)).to.equal(testNumber.toString(radix)); + expect(double.toString()).to.equal(testNumber.toString()); }); - } - }); - - describe('specialValues', () => { - function twiceSerialized(value) { - let serializedDouble = BSON.serialize({ d: value }); - let deserializedDouble = BSON.deserialize(serializedDouble, { promoteValues: true }); - let newVal = deserializedDouble.d; - return newVal; - } - - it('inf', () => { - let value = Infinity; - let newVal = twiceSerialized(value); - expect(value).to.equal(newVal); - }); - it('-inf', () => { - let value = -Infinity; - let newVal = twiceSerialized(value); - expect(value).to.equal(newVal); - }); + const testRadices = [2, 8, 10, 16, 22]; - it('NaN', () => { - let value = NaN; - let newVal = twiceSerialized(value); - expect(Number.isNaN(newVal)).to.equal(true); - }); - - it('NaN with payload', function () { - if (!isBrowser() && getNodeMajor() < 10) { - this.skip(); + for (const radix of testRadices) { + it(`should support radix argument: ${radix}`, () => { + const testNumber = Math.random() * Number.MAX_VALUE; + const double = new Double(testNumber); + expect(double.toString(radix)).to.equal(testNumber.toString(radix)); + }); } - let buffer = Buffer.from('120000000000F87F', 'hex'); - - const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); - let value = dv.getFloat64(0, true); - - let serializedDouble = BSON.serialize({ d: value }); - expect(serializedDouble.subarray(7, 15)).to.deep.equal(buffer); - let { d: newVal } = BSON.deserialize(serializedDouble, { promoteValues: true }); - expect(Number.isNaN(newVal)).to.equal(true); }); + }); - it('0', () => { - let value = 0; - let orig = new Double(value); - let newVal = twiceSerialized(orig); - expect(value).to.equal(newVal); + function serializeThenDeserialize(value) { + const serializedDouble = BSON.serialize({ d: value }); + const deserializedDouble = BSON.deserialize(serializedDouble, { promoteValues: false }); + return deserializedDouble.d; + } + + const testCases = [ + { name: 'Infinity', input: new Double(Infinity) }, + { name: '-Infinity', input: new Double(-Infinity) }, + { name: 'Number.EPSILON', input: new Double(Number.EPSILON) }, + { name: 'Zero', input: new Double(0) }, + { name: 'Double (Negative Zero)', input: new Double(-0) } + ]; + + for (const { name, input } of testCases) { + it(`should return Double from serialize-deserialize roundtrip when value is: ${name}`, () => { + const outcome = serializeThenDeserialize(input); + expect(outcome.value).to.equal(input.value); + expect(Object.is(outcome.value, input.value)).to.be.true; }); + } - it('-0', () => { - let value = -0; - let orig = new Double(value); - let newVal = twiceSerialized(orig); - expect(Object.is(newVal, -0)).to.be.true; - }); + it('should preserve NaN value in serialize-deserialize roundtrip', () => { + const value = NaN; + const newVal = serializeThenDeserialize(value); + expect(Number.isNaN(newVal.value)).to.equal(true); + }); - // TODO (NODE-4335): -0 should be serialized as double - it.skip('-0 serializes as Double', () => { - let value = -0; - let serializedDouble = BSON.serialize({ d: value }); - let type = serializedDouble[5]; - expect(type).to.not.equal(BSON.BSON_DATA_NUMBER); + context('NaN with Payload', function () { + const NanPayloadBuffer = Buffer.from('120000000000F87F', 'hex'); + const NanPayloadDV = new DataView( + NanPayloadBuffer.buffer, + NanPayloadBuffer.byteOffset, + NanPayloadBuffer.byteLength + ); + const NanPayloadDouble = NanPayloadDV.getFloat64(0, true); + const serializedNanPayloadDouble = BSON.serialize({ d: NanPayloadDouble }); + + it('should keep payload in serialize-deserialize roundtrip', function () { + expect(serializedNanPayloadDouble.subarray(7, 15)).to.deep.equal(NanPayloadBuffer); }); - it('Number.EPSILON', () => { - let value = Number.EPSILON; - let newVal = twiceSerialized(value); - expect(value).to.equal(newVal); + it('should preserve NaN value in serialize-deserialize roundtrip', function () { + const { d: newVal } = BSON.deserialize(serializedNanPayloadDouble, { promoteValues: true }); + expect(Number.isNaN(newVal)).to.equal(true); }); }); + it('NODE-4335: does not preserve -0 in serialize-deserialize roundtrip if JS number is used', () => { + // TODO (NODE-4335): -0 should be serialized as double + const value = -0; + const serializedDouble = BSON.serialize({ d: value }); + const type = serializedDouble[4]; + expect(type).to.not.equal(BSON.BSON_DATA_NUMBER); + expect(type).to.equal(BSON.BSON_DATA_INT); + }); }); From f3f5e63e46ae72ca2e06acd4085d7a0b9d09c46a Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Tue, 5 Jul 2022 11:11:25 -0400 Subject: [PATCH 09/12] fixed regression bug logic and removed extra function --- test/node/double_tests.js | 8 +++++--- test/node/tools/utils.js | 5 ----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/test/node/double_tests.js b/test/node/double_tests.js index 8f8965bd..d063cd3e 100644 --- a/test/node/double_tests.js +++ b/test/node/double_tests.js @@ -85,12 +85,14 @@ describe('BSON Double Precision', function () { expect(Number.isNaN(newVal)).to.equal(true); }); }); - it('NODE-4335: does not preserve -0 in serialize-deserialize roundtrip if JS number is used', () => { + + it('NODE-4335: does not preserve -0 in serialize-deserialize roundtrip if JS number is used', function () { // TODO (NODE-4335): -0 should be serialized as double + this.skip(); const value = -0; const serializedDouble = BSON.serialize({ d: value }); const type = serializedDouble[4]; - expect(type).to.not.equal(BSON.BSON_DATA_NUMBER); - expect(type).to.equal(BSON.BSON_DATA_INT); + expect(type).to.equal(BSON.BSON_DATA_NUMBER); + expect(type).to.not.equal(BSON.BSON_DATA_INT); }); }); diff --git a/test/node/tools/utils.js b/test/node/tools/utils.js index b441ea27..788c04ea 100644 --- a/test/node/tools/utils.js +++ b/test/node/tools/utils.js @@ -158,11 +158,6 @@ exports.isNode6 = function () { return process.version.split('.')[0] === 'v6'; }; -exports.getNodeMajor = function () { - // eslint-disable-next-line no-undef - return Number(process.versions.node.split('.')[0]); -}; - const getSymbolFrom = function (target, symbolName, assertExists) { if (assertExists == null) assertExists = true; From 95b1af6a5fdcdf09e041c16603cb26608bc5a1dc Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Tue, 5 Jul 2022 13:59:41 -0400 Subject: [PATCH 10/12] Added comment and reversed logic on -0 Node-4335 test --- test/node/double_tests.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/node/double_tests.js b/test/node/double_tests.js index d063cd3e..1db5fe7f 100644 --- a/test/node/double_tests.js +++ b/test/node/double_tests.js @@ -88,11 +88,11 @@ describe('BSON Double Precision', function () { it('NODE-4335: does not preserve -0 in serialize-deserialize roundtrip if JS number is used', function () { // TODO (NODE-4335): -0 should be serialized as double - this.skip(); + // This test is demonstrating the behavior of -0 being serialized as an int32 something we do NOT want to unintentionally change, but may want to change in the future, which the above ticket serves to track. const value = -0; const serializedDouble = BSON.serialize({ d: value }); const type = serializedDouble[4]; - expect(type).to.equal(BSON.BSON_DATA_NUMBER); - expect(type).to.not.equal(BSON.BSON_DATA_INT); + expect(type).to.not.equal(BSON.BSON_DATA_NUMBER); + expect(type).to.equal(BSON.BSON_DATA_INT); }); }); From 3d4b8a7894bfe7afba0391781ecca0576b5af130 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Tue, 5 Jul 2022 16:52:32 -0400 Subject: [PATCH 11/12] Additional test case changes --- test/node/double_tests.js | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/test/node/double_tests.js b/test/node/double_tests.js index 1db5fe7f..934b4a4d 100644 --- a/test/node/double_tests.js +++ b/test/node/double_tests.js @@ -45,27 +45,22 @@ describe('BSON Double Precision', function () { } const testCases = [ - { name: 'Infinity', input: new Double(Infinity) }, - { name: '-Infinity', input: new Double(-Infinity) }, - { name: 'Number.EPSILON', input: new Double(Number.EPSILON) }, - { name: 'Zero', input: new Double(0) }, - { name: 'Double (Negative Zero)', input: new Double(-0) } + { name: 'Infinity', doubleVal: new Double(Infinity), testVal: Infinity }, + { name: '-Infinity', doubleVal: new Double(-Infinity), testVal: -Infinity }, + { name: 'Number.EPSILON', doubleVal: new Double(Number.EPSILON), testVal: Number.EPSILON }, + { name: 'Zero', doubleVal: new Double(0), testVal: 0 }, + { name: 'Negative Zero', doubleVal: new Double(-0), testVal: -0 }, + { name: 'NaN', doubleVal: new Double(NaN), testVal: NaN } ]; - for (const { name, input } of testCases) { - it(`should return Double from serialize-deserialize roundtrip when value is: ${name}`, () => { - const outcome = serializeThenDeserialize(input); - expect(outcome.value).to.equal(input.value); - expect(Object.is(outcome.value, input.value)).to.be.true; + for (const { name, doubleVal, testVal } of testCases) { + it(`should preserve the input value ${name} in Double serialize-deserialize roundtrip`, () => { + const roundTrippedVal = serializeThenDeserialize(doubleVal); + expect(Object.is(doubleVal.value, testVal)).to.be.true; + expect(Object.is(roundTrippedVal.value, doubleVal.value)).to.be.true; }); } - it('should preserve NaN value in serialize-deserialize roundtrip', () => { - const value = NaN; - const newVal = serializeThenDeserialize(value); - expect(Number.isNaN(newVal.value)).to.equal(true); - }); - context('NaN with Payload', function () { const NanPayloadBuffer = Buffer.from('120000000000F87F', 'hex'); const NanPayloadDV = new DataView( @@ -82,7 +77,7 @@ describe('BSON Double Precision', function () { it('should preserve NaN value in serialize-deserialize roundtrip', function () { const { d: newVal } = BSON.deserialize(serializedNanPayloadDouble, { promoteValues: true }); - expect(Number.isNaN(newVal)).to.equal(true); + expect(newVal).to.be.NaN; }); }); From 4d7ff2f5f5b9a509b46327f43fa7c5d7aa5950f2 Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB Date: Wed, 6 Jul 2022 12:34:03 -0400 Subject: [PATCH 12/12] removed done and added comment --- test/node/double_tests.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/node/double_tests.js b/test/node/double_tests.js index 934b4a4d..5346f6cf 100644 --- a/test/node/double_tests.js +++ b/test/node/double_tests.js @@ -8,14 +8,12 @@ describe('BSON Double Precision', function () { describe('constructor()', function () { const value = 42.3456; - it('Primitive number', function (done) { + it('Primitive number', function () { expect(new Double(value).valueOf()).to.equal(value); - done(); }); - it('Number object', function (done) { + it('Number object', function () { expect(new Double(new Number(value)).valueOf()).to.equal(value); - done(); }); }); @@ -69,6 +67,7 @@ describe('BSON Double Precision', function () { NanPayloadBuffer.byteLength ); const NanPayloadDouble = NanPayloadDV.getFloat64(0, true); + // Using promoteValues: false (returning raw BSON) in order to be able to check that payload remains intact const serializedNanPayloadDouble = BSON.serialize({ d: NanPayloadDouble }); it('should keep payload in serialize-deserialize roundtrip', function () {