From 4c9b0f8c236f3f9ac0c0d2230622c87a95487bb9 Mon Sep 17 00:00:00 2001 From: Dan Aprahamian Date: Tue, 30 Jan 2018 13:52:51 -0500 Subject: [PATCH 01/11] fix(collection): fix error when calling remove with no args (#1657) Calling remove with no arguments resulted in an error b/c of the lack of options. Fixes NODE-1287 --- lib/collection.js | 3 +++ test/functional/remove_tests.js | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/lib/collection.js b/lib/collection.js index d839374ee6e..d27929ac2ff 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -1230,6 +1230,9 @@ define.classMethod('removeMany', { callback: true, promise: true }); * @deprecated use deleteOne, deleteMany or bulkWrite */ Collection.prototype.remove = function(selector, options, callback) { + if (typeof options === 'function') (callback = options), (options = {}); + options = options || {}; + // Add ignoreUndfined if (this.s.options.ignoreUndefined) { options = shallowClone(options); diff --git a/test/functional/remove_tests.js b/test/functional/remove_tests.js index 5fa073b15bf..0ab78a94f49 100644 --- a/test/functional/remove_tests.js +++ b/test/functional/remove_tests.js @@ -153,4 +153,38 @@ describe('Remove', function() { }); } }); + + /** + * @ignore + */ + it('should not error on empty remove', { + metadata: { + requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } + }, + + // The actual test we wish to run + test: function(done) { + var self = this; + var client = self.configuration.newClient(self.configuration.writeConcernMax(), { + poolSize: 1 + }); + + client.connect(function(err, client) { + var db = client.db(self.configuration.db); + test.equal(null, err); + const collection = db.collection('remove_test'); + + collection.remove().then( + () => { + client.close(); + done(); + }, + err => { + client.close(); + done(err); + } + ); + }); + } + }); }); From f2bb62687e7d5f143846ee345053fac33ff846ed Mon Sep 17 00:00:00 2001 From: Dan Aprahamian Date: Tue, 30 Jan 2018 13:53:46 -0500 Subject: [PATCH 02/11] test(causalConsistency): adding an example test for causal consistency (#1651) Fixes NODE-1262 --- test/functional/examples_tests.js | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/functional/examples_tests.js b/test/functional/examples_tests.js index 052ac6b0a01..75a034adc6a 100644 --- a/test/functional/examples_tests.js +++ b/test/functional/examples_tests.js @@ -1,6 +1,7 @@ 'use strict'; var assert = require('assert'); +const expect = require('chai').expect; var co = require('co'); var test = require('./shared').assert; var setupDatabase = require('./shared').setupDatabase; @@ -1224,4 +1225,43 @@ describe('Examples', function() { }); } }); + + /** + * @ignore + */ + it('CausalConsistency', { + metadata: { requires: { topology: ['single'], mongodb: '>=3.6.0' } }, + + test: function(done) { + const configuration = this.configuration; + const client = configuration.newClient(configuration.writeConcernMax(), { poolSize: 1 }); + + client.connect(function(err, client) { + const cleanup = e => { + client.close(); + done(e); + }; + + if (err) return cleanup(err); + + const db = client.db(configuration.db); + const collection = db.collection('causalConsistencyExample'); + const session = client.startSession({ causalConsistency: true }); + + collection.insertOne({ darmok: 'jalad' }, { session }); + collection.updateOne({ darmok: 'jalad' }, { $set: { darmok: 'tanagra' } }, { session }); + + collection.find({}, { session }).toArray(function(err, data) { + try { + expect(err).to.equal(null); + expect(data).to.exist; + } catch (e) { + return cleanup(e); + } + + cleanup(); + }); + }); + } + }); }); From 33db3ca09e013dd7b091825d082be5581be4ae26 Mon Sep 17 00:00:00 2001 From: Dan Aprahamian Date: Tue, 30 Jan 2018 14:23:28 -0500 Subject: [PATCH 03/11] test(apm): making tests clean up after themselves (#1644) If one of these tests fails, it frequently causes false negatives for following tests. This change properly cleans up after ourselves. --- test/functional/apm_tests.js | 126 +++++++++++++++++------------------ 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/test/functional/apm_tests.js b/test/functional/apm_tests.js index cbd74a0c2fe..41438dfc8ee 100644 --- a/test/functional/apm_tests.js +++ b/test/functional/apm_tests.js @@ -28,16 +28,16 @@ describe('APM', function() { var callbackTriggered = false; var self = this; - var listener = require('../..').instrument(function(err) { + testListener = require('../..').instrument(function(err) { expect(err).to.be.null; callbackTriggered = true; }); - listener.on('started', function(event) { + testListener.on('started', function(event) { if (event.commandName === 'insert') started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if (event.commandName === 'insert') succeeded.push(event); }); @@ -57,7 +57,7 @@ describe('APM', function() { expect(succeeded.length).to.equal(1); expect(callbackTriggered).to.be.true; - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }); @@ -75,16 +75,16 @@ describe('APM', function() { var callbackTriggered = false; var self = this; - var listener = require('../..').instrument(function(err) { + testListener = require('../..').instrument(function(err) { expect(err).to.be.null; callbackTriggered = true; }); - listener.on('started', function(event) { + testListener.on('started', function(event) { if (event.commandName === 'insert') started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if (event.commandName === 'insert') succeeded.push(event); }); @@ -103,7 +103,7 @@ describe('APM', function() { expect(err).to.be.null; cursor.close(); // <-- Will cause error in APM module. - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }); @@ -192,17 +192,17 @@ describe('APM', function() { .then(function(r) { expect(r.insertedCount).to.equal(1); - var listener = require('../..').instrument(function(err) { + testListener = require('../..').instrument(function(err) { expect(err).to.be.null; }); - listener.on('started', function(event) { + testListener.on('started', function(event) { if (event.commandName === 'listIndexes' || event.commandName === 'find') { started.push(event); } }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if (event.commandName === 'listIndexes' || event.commandName === 'find') { succeeded.push(event); } @@ -223,7 +223,7 @@ describe('APM', function() { // Ensure command was not sent to the primary expect(started[0].connectionId.port).to.not.equal(started[1].connectionId.port); - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }); @@ -247,7 +247,7 @@ describe('APM', function() { var succeeded = []; var callbackTriggered = false; - var listener = require('../..').instrument( + testListener = require('../..').instrument( { operationIdGenerator: { next: function() { @@ -269,11 +269,11 @@ describe('APM', function() { } ); - listener.on('started', function(event) { + testListener.on('started', function(event) { if (event.commandName === 'insert') started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if (event.commandName === 'insert') succeeded.push(event); }); @@ -293,7 +293,7 @@ describe('APM', function() { expect(succeeded[0].duration).to.equal(0); expect(callbackTriggered).to.be.true; - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }); @@ -313,8 +313,8 @@ describe('APM', function() { var succeeded = []; var failed = []; - var listener = require('../..').instrument(); - listener.on('started', function(event) { + testListener = require('../..').instrument(); + testListener.on('started', function(event) { if ( event.commandName === 'find' || event.commandName === 'getMore' || @@ -323,7 +323,7 @@ describe('APM', function() { started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if ( event.commandName === 'find' || event.commandName === 'getMore' || @@ -332,7 +332,7 @@ describe('APM', function() { succeeded.push(event); }); - listener.on('failed', function(event) { + testListener.on('failed', function(event) { if ( event.commandName === 'find' || event.commandName === 'getMore' || @@ -387,7 +387,7 @@ describe('APM', function() { expect(started[0].operationId).to.equal(started[1].operationId); expect(started[0].operationId).to.equal(started[2].operationId); - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }) @@ -414,8 +414,8 @@ describe('APM', function() { var succeeded = []; var failed = []; - var listener = require('../..').instrument(); - listener.on('started', function(event) { + testListener = require('../..').instrument(); + testListener.on('started', function(event) { if ( event.commandName === 'find' || event.commandName === 'getMore' || @@ -424,7 +424,7 @@ describe('APM', function() { started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if ( event.commandName === 'find' || event.commandName === 'getMore' || @@ -433,7 +433,7 @@ describe('APM', function() { succeeded.push(event); }); - listener.on('failed', function(event) { + testListener.on('failed', function(event) { if ( event.commandName === 'find' || event.commandName === 'getMore' || @@ -474,7 +474,7 @@ describe('APM', function() { .catch(function() { expect(failed).to.have.length(1); - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }); @@ -496,8 +496,8 @@ describe('APM', function() { var started = []; var succeeded = []; - var listener = require('../..').instrument(); - listener.on('started', function(event) { + testListener = require('../..').instrument(); + testListener.on('started', function(event) { if ( event.commandName === 'insert' || event.commandName === 'update' || @@ -506,7 +506,7 @@ describe('APM', function() { started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if ( event.commandName === 'insert' || event.commandName === 'update' || @@ -536,7 +536,7 @@ describe('APM', function() { expect(succeeded[0].operationId).to.equal(succeeded[1].operationId); expect(succeeded[0].operationId).to.equal(succeeded[2].operationId); - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }) @@ -557,8 +557,8 @@ describe('APM', function() { var succeeded = []; var failed = []; - var listener = require('../..').instrument(); - listener.on('started', function(event) { + testListener = require('../..').instrument(); + testListener.on('started', function(event) { if ( event.commandName === 'find' || event.commandName === 'getMore' || @@ -568,7 +568,7 @@ describe('APM', function() { started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if ( event.commandName === 'find' || event.commandName === 'getMore' || @@ -578,7 +578,7 @@ describe('APM', function() { succeeded.push(event); }); - listener.on('failed', function(event) { + testListener.on('failed', function(event) { if ( event.commandName === 'find' || event.commandName === 'getMore' || @@ -619,7 +619,7 @@ describe('APM', function() { expect(started[0].operationId).to.equal(succeeded[0].operationId); // Remove instrumentation - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }) @@ -645,16 +645,16 @@ describe('APM', function() { var succeeded = []; var failed = []; - var listener = require('../..').instrument(); - listener.on('started', function(event) { + testListener = require('../..').instrument(); + testListener.on('started', function(event) { if (event.commandName === 'getnonce') started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if (event.commandName === 'getnonce') succeeded.push(event); }); - listener.on('failed', function(event) { + testListener.on('failed', function(event) { if (event.commandName === 'getnonce') failed.push(event); }); @@ -674,7 +674,7 @@ describe('APM', function() { expect(succeeded[0].reply).to.eql({}); // Remove instrumentation - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }); @@ -691,15 +691,15 @@ describe('APM', function() { var started = []; var succeeded = []; - var listener = require('../..').instrument(function(err) { + testListener = require('../..').instrument(function(err) { expect(err).to.not.exist; }); - listener.on('started', function(event) { + testListener.on('started', function(event) { if (event.commandName === 'update') started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if (event.commandName === 'update') succeeded.push(event); }); @@ -718,7 +718,7 @@ describe('APM', function() { expect(started[0].command.update).to.equal('apm_test_u_1'); expect(succeeded).to.have.length(1); - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }); @@ -735,15 +735,15 @@ describe('APM', function() { var started = []; var succeeded = []; - var listener = require('../..').instrument(function(err) { + testListener = require('../..').instrument(function(err) { expect(err).to.not.exist; }); - listener.on('started', function(event) { + testListener.on('started', function(event) { if (event.commandName === 'update') started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if (event.commandName === 'update') succeeded.push(event); }); @@ -762,7 +762,7 @@ describe('APM', function() { expect(started[0].command.update).to.equal('apm_test_u_2'); expect(succeeded).to.have.length(1); - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }); @@ -779,15 +779,15 @@ describe('APM', function() { var started = []; var succeeded = []; - var listener = require('../..').instrument(function(err) { + testListener = require('../..').instrument(function(err) { expect(err).to.not.exist; }); - listener.on('started', function(event) { + testListener.on('started', function(event) { if (event.commandName === 'delete') started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if (event.commandName === 'delete') succeeded.push(event); }); @@ -806,7 +806,7 @@ describe('APM', function() { expect(started[0].command.delete).to.equal('apm_test_u_3'); expect(succeeded).to.have.length(1); - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }); @@ -874,16 +874,16 @@ describe('APM', function() { var started = []; var succeeded = []; - var listener = require('../..').instrument(function(err) { + testListener = require('../..').instrument(function(err) { expect(err).to.not.exist; }); - listener.on('started', function(event) { + testListener.on('started', function(event) { if (event.commandName === 'aggregate' || event.commandName === 'getMore') started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if (event.commandName === 'aggregate' || event.commandName === 'getMore') succeeded.push(event); }); @@ -929,7 +929,7 @@ describe('APM', function() { expect(cursors[0].id.toString()).to.equal(cursors[1].id.toString()); expect(cursors[2].id.toString()).to.equal('0'); - listener.uninstrument(); + testListener.uninstrument(); client.close(); }); } @@ -944,15 +944,15 @@ describe('APM', function() { var started = []; var succeeded = []; - var listener = require('../..').instrument(function(err) { + testListener = require('../..').instrument(function(err) { expect(err).to.not.exist; }); - listener.on('started', function(event) { + testListener.on('started', function(event) { if (event.commandName === 'listCollections') started.push(event); }); - listener.on('succeeded', function(event) { + testListener.on('succeeded', function(event) { if (event.commandName === 'listCollections') succeeded.push(event); }); @@ -986,7 +986,7 @@ describe('APM', function() { // Check we have a cursor expect(cursors[0].id).to.exist; - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }); @@ -1245,16 +1245,16 @@ describe('APM', function() { scenario.tests.forEach(test => { it(test.description, function(done) { var MongoClient = require('../..'); - var listener = require('../../').instrument(); + testListener = require('../../').instrument(); MongoClient.connect(this.configuration.url(), function(err, client) { expect(err).to.not.exist; expect(client).to.exist; - executeOperation(client, listener, scenario, test, err => { + executeOperation(client, testListener, scenario, test, err => { expect(err).to.not.exist; - listener.uninstrument(); + testListener.uninstrument(); client.close(); done(); }); From 077b876ab8d90ea4938926b33c6ebdff3ae7d5eb Mon Sep 17 00:00:00 2001 From: Jessica Lord Date: Tue, 30 Jan 2018 15:15:29 -0500 Subject: [PATCH 04/11] Resync Connection String Spec Tests (#1656) * test(connection string): update to latest tests from specs * fix(url-parser): catch errors thrown by url parser --- lib/url_parser.js | 8 +- .../spec/connection-string/invalid-uris.json | 526 +++++++++--------- .../spec/connection-string/invalid-uris.yml | 38 +- 3 files changed, 306 insertions(+), 266 deletions(-) diff --git a/lib/url_parser.js b/lib/url_parser.js index f2df82fcc1b..ec3df2d8e3d 100644 --- a/lib/url_parser.js +++ b/lib/url_parser.js @@ -10,7 +10,13 @@ module.exports = function(url, options, callback) { if (typeof options === 'function') (callback = options), (options = {}); options = options || {}; - let result = parser.parse(url, true); + let result; + try { + result = parser.parse(url, true); + } catch (e) { + return callback(new Error('URL malformed, cannot be parsed')); + } + if (result.protocol !== 'mongodb:' && result.protocol !== 'mongodb+srv:') { return callback(new Error('Invalid schema, expected `mongodb` or `mongodb+srv`')); } diff --git a/test/functional/spec/connection-string/invalid-uris.json b/test/functional/spec/connection-string/invalid-uris.json index 3cffddaf584..2a182fac7e2 100644 --- a/test/functional/spec/connection-string/invalid-uris.json +++ b/test/functional/spec/connection-string/invalid-uris.json @@ -1,256 +1,274 @@ { - "tests": [ - { - "auth": null, - "description": "Empty string", - "hosts": null, - "options": null, - "uri": "", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Invalid scheme", - "hosts": null, - "options": null, - "uri": "mongo://localhost:27017", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Missing host", - "hosts": null, - "options": null, - "uri": "mongodb://", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Double colon in host identifier", - "hosts": null, - "options": null, - "uri": "mongodb://localhost::27017", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Double colon in host identifier and trailing slash", - "hosts": null, - "options": null, - "uri": "mongodb://localhost::27017/", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Double colon in host identifier with missing host and port", - "hosts": null, - "options": null, - "uri": "mongodb://::", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Double colon in host identifier with missing port", - "hosts": null, - "options": null, - "uri": "mongodb://localhost,localhost::", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Double colon in host identifier and second host", - "hosts": null, - "options": null, - "uri": "mongodb://localhost::27017,abc", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Invalid port (negative number) with hostname", - "hosts": null, - "options": null, - "uri": "mongodb://localhost:-1", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Invalid port (zero) with hostname", - "hosts": null, - "options": null, - "uri": "mongodb://localhost:0/", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Invalid port (positive number) with hostname", - "hosts": null, - "options": null, - "uri": "mongodb://localhost:65536", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Invalid port (positive number) with hostname and trailing slash", - "hosts": null, - "options": null, - "uri": "mongodb://localhost:65536/", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Invalid port (non-numeric string) with hostname", - "hosts": null, - "options": null, - "uri": "mongodb://localhost:foo", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Invalid port (negative number) with IP literal", - "hosts": null, - "options": null, - "uri": "mongodb://[::1]:-1", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Invalid port (zero) with IP literal", - "hosts": null, - "options": null, - "uri": "mongodb://[::1]:0/", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Invalid port (positive number) with IP literal", - "hosts": null, - "options": null, - "uri": "mongodb://[::1]:65536", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Invalid port (positive number) with IP literal and trailing slash", - "hosts": null, - "options": null, - "uri": "mongodb://[::1]:65536/", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Invalid port (non-numeric string) with IP literal", - "hosts": null, - "options": null, - "uri": "mongodb://[::1]:foo", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Missing delimiting slash between hosts and options", - "hosts": null, - "options": null, - "uri": "mongodb://example.com?w=1", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Incomplete key value pair for option", - "hosts": null, - "options": null, - "uri": "mongodb://example.com/?w", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Username with password containing an unescaped colon", - "hosts": null, - "options": null, - "uri": "mongodb://alice:foo:bar@127.0.0.1", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Username with password containing an unescaped colon", - "hosts": null, - "options": null, - "uri": "mongodb://alice:foo:bar@127.0.0.1", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Username containing an unescaped at-sign", - "hosts": null, - "options": null, - "uri": "mongodb://alice@@127.0.0.1", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Username with password containing an unescaped at-sign", - "hosts": null, - "options": null, - "uri": "mongodb://alice@foo:bar@127.0.0.1", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Username containing an unescaped slash", - "hosts": null, - "options": null, - "uri": "mongodb://alice/@127.0.0.1/db", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Username containing unescaped slash with password", - "hosts": null, - "options": null, - "uri": "mongodb://alice/bob:foo@127.0.0.1/db", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Username with password containing an unescaped slash", - "hosts": null, - "options": null, - "uri": "mongodb://alice:foo/bar@127.0.0.1/db", - "valid": false, - "warning": null - }, - { - "auth": null, - "description": "Host with unescaped slash", - "hosts": null, - "options": null, - "uri": "mongodb:///tmp/mongodb-27017.sock/", - "valid": false, - "warning": null - } - ] + "tests": [ + { + "description": "Empty string", + "uri": "", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Invalid scheme", + "uri": "mongo://localhost:27017", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Missing host", + "uri": "mongodb://", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Double colon in host identifier", + "uri": "mongodb://localhost::27017", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Double colon in host identifier and trailing slash", + "uri": "mongodb://localhost::27017/", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Double colon in host identifier with missing host and port", + "uri": "mongodb://::", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Double colon in host identifier with missing port", + "uri": "mongodb://localhost,localhost::", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Double colon in host identifier and second host", + "uri": "mongodb://localhost::27017,abc", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Invalid port (negative number) with hostname", + "uri": "mongodb://localhost:-1", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Invalid port (zero) with hostname", + "uri": "mongodb://localhost:0/", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Invalid port (positive number) with hostname", + "uri": "mongodb://localhost:65536", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Invalid port (positive number) with hostname and trailing slash", + "uri": "mongodb://localhost:65536/", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Invalid port (non-numeric string) with hostname", + "uri": "mongodb://localhost:foo", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Invalid port (negative number) with IP literal", + "uri": "mongodb://[::1]:-1", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Invalid port (zero) with IP literal", + "uri": "mongodb://[::1]:0/", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Invalid port (positive number) with IP literal", + "uri": "mongodb://[::1]:65536", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Invalid port (positive number) with IP literal and trailing slash", + "uri": "mongodb://[::1]:65536/", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Invalid port (non-numeric string) with IP literal", + "uri": "mongodb://[::1]:foo", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Missing delimiting slash between hosts and options", + "uri": "mongodb://example.com?w=1", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Incomplete key value pair for option", + "uri": "mongodb://example.com/?w", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Username with password containing an unescaped colon", + "uri": "mongodb://alice:foo:bar@127.0.0.1", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Username containing an unescaped at-sign", + "uri": "mongodb://alice@@127.0.0.1", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Username with password containing an unescaped at-sign", + "uri": "mongodb://alice@foo:bar@127.0.0.1", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Username containing an unescaped slash", + "uri": "mongodb://alice/@localhost/db", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Username containing unescaped slash with password", + "uri": "mongodb://alice/bob:foo@localhost/db", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Username with password containing an unescaped slash", + "uri": "mongodb://alice:foo/bar@localhost/db", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Host with unescaped slash", + "uri": "mongodb:///tmp/mongodb-27017.sock/", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "mongodb+srv with multiple service names", + "uri": "mongodb+srv://test5.test.mongodb.com,test6.test.mongodb.com", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "mongodb+srv with port number", + "uri": "mongodb+srv://test7.test.mongodb.com:27018", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Username with password containing an unescaped percent sign", + "uri": "mongodb://alice%foo:bar@127.0.0.1", + "valid": false, + "warning": null, + "hosts": null, + "auth": null, + "options": null + } + ] } diff --git a/test/functional/spec/connection-string/invalid-uris.yml b/test/functional/spec/connection-string/invalid-uris.yml index 7b25afd6a86..5ea569f172d 100644 --- a/test/functional/spec/connection-string/invalid-uris.yml +++ b/test/functional/spec/connection-string/invalid-uris.yml @@ -167,14 +167,6 @@ tests: hosts: ~ auth: ~ options: ~ - - - description: "Username with password containing an unescaped colon" - uri: "mongodb://alice:foo:bar@127.0.0.1" - valid: false - warning: ~ - hosts: ~ - auth: ~ - options: ~ - description: "Username containing an unescaped at-sign" uri: "mongodb://alice@@127.0.0.1" @@ -193,7 +185,7 @@ tests: options: ~ - description: "Username containing an unescaped slash" - uri: "mongodb://alice/@127.0.0.1/db" + uri: "mongodb://alice/@localhost/db" valid: false warning: ~ hosts: ~ @@ -201,7 +193,7 @@ tests: options: ~ - description: "Username containing unescaped slash with password" - uri: "mongodb://alice/bob:foo@127.0.0.1/db" + uri: "mongodb://alice/bob:foo@localhost/db" valid: false warning: ~ hosts: ~ @@ -209,7 +201,7 @@ tests: options: ~ - description: "Username with password containing an unescaped slash" - uri: "mongodb://alice:foo/bar@127.0.0.1/db" + uri: "mongodb://alice:foo/bar@localhost/db" valid: false warning: ~ hosts: ~ @@ -223,3 +215,27 @@ tests: hosts: ~ auth: ~ options: ~ + - + description: "mongodb+srv with multiple service names" + uri: "mongodb+srv://test5.test.mongodb.com,test6.test.mongodb.com" + valid: false + warning: ~ + hosts: ~ + auth: ~ + options: ~ + - + description: "mongodb+srv with port number" + uri: "mongodb+srv://test7.test.mongodb.com:27018" + valid: false + warning: ~ + hosts: ~ + auth: ~ + options: ~ + - + description: "Username with password containing an unescaped percent sign" + uri: "mongodb://alice%foo:bar@127.0.0.1" + valid: false + warning: ~ + hosts: ~ + auth: ~ + options: ~ From c519b9b959e6ee5ce18d8b662cfb7de6582f2e19 Mon Sep 17 00:00:00 2001 From: Nikita Savchenko Date: Wed, 31 Jan 2018 16:08:02 +0200 Subject: [PATCH 05/11] fix(jsdoc): mark db.collection callback as optional + typo fix (#1658) * fix(jsdoc): mark db.collection callback as optional * fix(jsdoc): typo --- lib/db.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/db.js b/lib/db.js index 4b3e6a0d0d1..1abd314c93c 100644 --- a/lib/db.js +++ b/lib/db.js @@ -393,7 +393,7 @@ var collectionKeys = [ ]; /** - * Fetch a specific collection (containing the actual collection information). If the application does not use strict mode you can + * Fetch a specific collection (containing the actual collection information). If the application does not use strict mode you * can use it without a callback in the following way: `var collection = db.collection('mycollection');` * * @method @@ -409,7 +409,7 @@ var collectionKeys = [ * @param {boolean} [options.strict=false] Returns an error if the collection does not exist * @param {object} [options.readConcern=null] Specify a read concern for the collection. (only MongoDB 3.2 or higher supported) * @param {object} [options.readConcern.level='local'] Specify a read concern level for the collection operations, one of [local|majority]. (only MongoDB 3.2 or higher supported) - * @param {Db~collectionResultCallback} callback The collection result callback + * @param {Db~collectionResultCallback} [callback] The collection result callback * @return {Collection} return the new Collection instance if not in strict mode */ Db.prototype.collection = function(name, options, callback) { From 1740d519d60d5fc8ef1d3533fcb0e0623ddcd5f4 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 1 Feb 2018 06:41:51 -0800 Subject: [PATCH 06/11] style(docs): minor grammatical fixes to README (#1660) The fixes are very few in quantity and only very minor issues can likely be ignored. > Missing comma after "However" >Hyphen missing --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 29d2dba39ec..62af88c2471 100644 --- a/README.md +++ b/README.md @@ -67,11 +67,11 @@ The MongoDB driver depends on several other packages. These are: The `kerberos` package is a C++ extension that requires a build environment to be installed on your system. You must be able to build Node.js itself in order to compile and install the `kerberos` module. Furthermore, the `kerberos` module requires the MIT Kerberos package to correctly compile on UNIX operating systems. Consult your UNIX operation system package manager for what libraries to install. -**Windows already contains the SSPI API used for Kerberos authentication. However you will need to install a full compiler tool chain using Visual Studio C++ to correctly install the Kerberos extension.** +**Windows already contains the SSPI API used for Kerberos authentication. However, you will need to install a full compiler tool chain using Visual Studio C++ to correctly install the Kerberos extension.** ### Diagnosing on UNIX -If you don’t have the build essentials, this module won’t build. In the case of Linux, you will need gcc, g++, Node.js with all the headers and Python. The easiest way to figure out what’s missing is by trying to build the Kerberos project. You can do this by performing the following steps. +If you don’t have the build-essentials, this module won’t build. In the case of Linux, you will need gcc, g++, Node.js with all the headers and Python. The easiest way to figure out what’s missing is by trying to build the Kerberos project. You can do this by performing the following steps. ```bash git clone https://github.com/mongodb-js/kerberos From 46e14d1ef829fd3e940209a980f58a2054c343a4 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 1 Feb 2018 15:25:17 -0800 Subject: [PATCH 07/11] docs(collection): add `arrayFilters` option to docs --- lib/collection.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/collection.js b/lib/collection.js index d27929ac2ff..c60b9c1b284 100644 --- a/lib/collection.js +++ b/lib/collection.js @@ -818,6 +818,7 @@ define.classMethod('insert', { callback: true, promise: true }); * @param {number} [options.wtimeout=null] The write concern timeout. * @param {boolean} [options.j=false] Specify a journal write concern. * @param {boolean} [options.bypassDocumentValidation=false] Allow driver to bypass schema validation in MongoDB 3.2 or higher. + * @param {Array} [options.arrayFilters=null] optional list of array filters referenced in filtered positional operators * @param {ClientSession} [options.session] optional session to use for this operation * @param {Collection~updateWriteOpCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed @@ -943,6 +944,7 @@ define.classMethod('replaceOne', { callback: true, promise: true }); * @param {(number|string)} [options.w=null] The write concern. * @param {number} [options.wtimeout=null] The write concern timeout. * @param {boolean} [options.j=false] Specify a journal write concern. + * @param {Array} [options.arrayFilters=null] optional list of array filters referenced in filtered positional operators * @param {ClientSession} [options.session] optional session to use for this operation * @param {Collection~updateWriteOpCallback} [callback] The command result callback * @return {Promise} returns Promise if no callback passed @@ -1049,6 +1051,7 @@ var updateDocuments = function(self, selector, document, options, callback) { * @param {boolean} [options.multi=false] Update one/all documents with operation. * @param {boolean} [options.bypassDocumentValidation=false] Allow driver to bypass schema validation in MongoDB 3.2 or higher. * @param {object} [options.collation=null] Specify collation (MongoDB 3.4 or higher) settings for update operation (see 3.4 documentation for available fields). + * @param {Array} [options.arrayFilters=null] optional list of array filters referenced in filtered positional operators * @param {ClientSession} [options.session] optional session to use for this operation * @param {Collection~writeOpCallback} [callback] The command result callback * @throws {MongoError} From 934a43a90141fbacebd4bcdd9151a4f6a8a8bc57 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Tue, 20 Feb 2018 22:35:10 +0100 Subject: [PATCH 08/11] fix(executeOperation): don't mutate options passed to commands When the topology believes it has session support, options passed to commands were getting mutated with the session added to them. It is our expectation that the driver will never be mutating arguments passed to it via the public facing API. This commit creates a new object and copies into it, then replaces in the args array. --- lib/utils.js | 3 ++- test/unit/sessions/collection_tests.js | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 688afb877f7..7bd0a1208c1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -383,7 +383,8 @@ const executeOperation = (topology, operation, args, options) => { opOptions = args[args.length - 2]; if (opOptions == null || opOptions.session == null) { session = topology.startSession(); - Object.assign(args[args.length - 2], { session: session }); + const optionsIndex = args.length - 2; + args[optionsIndex] = Object.assign({}, args[optionsIndex], { session: session }); } else if (opOptions.session && opOptions.session.hasEnded) { throw new MongoError('Use of expired sessions is not permitted'); } diff --git a/test/unit/sessions/collection_tests.js b/test/unit/sessions/collection_tests.js index dad1b4d4ac1..298e13822d5 100644 --- a/test/unit/sessions/collection_tests.js +++ b/test/unit/sessions/collection_tests.js @@ -49,5 +49,30 @@ describe('Sessions', function() { }); } }); + + it('does not mutate command options', { + metadata: { requires: { topology: 'single' } }, + + test: function() { + const options = Object.freeze({}); + test.server.setMessageHandler(request => { + const doc = request.document; + if (doc.ismaster) { + request.reply(mock.DEFAULT_ISMASTER_36); + } else if (doc.count) { + request.reply({ ok: 1 }); + } + }); + + return MongoClient.connect(`mongodb://${test.server.uri()}/test`).then(client => { + const coll = client.db('foo').collection('bar'); + + return coll.count({}, options).then(() => { + expect(options).to.deep.equal({}); + return client.close(); + }); + }); + } + }); }); }); From 7bd56371c008b1318098f5ca680fd84f0e8633f9 Mon Sep 17 00:00:00 2001 From: Juha Lindstedt Date: Fri, 23 Feb 2018 20:11:51 +0200 Subject: [PATCH 09/11] doc(sessions): update documentation for startSession `MongoClient.prototype.startSession` doesn't take a second argument, or return a Promise. It returns the ClientSession from the mongodb-core. --- lib/mongo_client.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/mongo_client.js b/lib/mongo_client.js index 80f20412f1e..46813d36e78 100644 --- a/lib/mongo_client.js +++ b/lib/mongo_client.js @@ -495,8 +495,7 @@ define.staticMethod('connect', { callback: true, promise: true }); * Starts a new session on the server * * @param {object} [options] optional settings for a driver session - * @param {MongoClient~sessionCallback} [callback] The callback called with a newly establish session, or an error if one occurred - * @return {Promise} if no callback is specified, a promise will be returned for the newly established session + * @return {ClientSession} the newly established session */ MongoClient.prototype.startSession = function(options) { options = options || {}; From b1f296ffb435b97320d91703796647b968aa4d4e Mon Sep 17 00:00:00 2001 From: Dan Aprahamian Date: Fri, 23 Feb 2018 15:17:31 -0500 Subject: [PATCH 10/11] fix(sessions): move active session tracking to topology base (#1665) Moves the tracking of active sessions to the topology base. Doing this allows us to ensure that all active and pooled sessions are ended when the topology closes, and that implicit sessions are tracked. Also adds a test case to make sure none of our unit tests are leaking sessions, and corrects many leaky tests. Also bumps version of mongodb-core Part of HELP-5384 --- .eslintrc | 3 +- lib/mongo_client.js | 16 +- lib/topologies/mongos.js | 2 + lib/topologies/replset.js | 19 +- lib/topologies/server.js | 2 + lib/topologies/topology_base.js | 20 +- package.json | 3 +- test/functional/apm_tests.js | 8 + test/functional/crud_api_tests.js | 2 +- test/functional/crud_spec_tests.js | 6 + test/functional/cursor_tests.js | 14 +- test/functional/cursorstream_tests.js | 26 +- test/functional/db_tests.js | 3 + test/functional/gridfs_stream_tests.js | 25 ++ test/functional/index_tests.js | 97 ++------ test/functional/insert_tests.js | 13 +- test/functional/operation_example_tests.js | 11 +- .../operation_generators_example_tests.js | 225 ++++++++---------- .../operation_promises_example_tests.js | 15 +- test/functional/session_leak_test.js | 92 +++++++ .../sharding_read_preference_tests.js | 21 +- test/unit/sessions/client_tests.js | 1 + test/unit/sessions/collection_tests.js | 2 + 23 files changed, 372 insertions(+), 254 deletions(-) create mode 100644 test/functional/session_leak_test.js diff --git a/.eslintrc b/.eslintrc index 2440ddd9083..283970bdeb9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,7 +7,8 @@ "mocha": true }, "globals": { - "Promise": true + "Promise": true, + "Set": true }, "parserOptions": { "ecmaVersion": 2017 diff --git a/lib/mongo_client.js b/lib/mongo_client.js index 46813d36e78..04a16375395 100644 --- a/lib/mongo_client.js +++ b/lib/mongo_client.js @@ -327,14 +327,6 @@ MongoClient.prototype.close = function(force, callback) { // Remove listeners after emit self.removeAllListeners('close'); - // If we have sessions, we want to send a single `endSessions` command for them, - // and then individually clean them up. They will be removed from the internal state - // when they emit their `ended` events. - if (this.s.sessions.length) { - this.topology.endSessions(this.s.sessions); - this.s.sessions.forEach(session => session.endSession({ skipCommand: true })); - } - // Callback after next event loop tick if (typeof callback === 'function') return process.nextTick(function() { @@ -507,13 +499,7 @@ MongoClient.prototype.startSession = function(options) { throw new MongoError('Current topology does not support sessions'); } - const session = this.topology.startSession(options); - session.once('ended', () => { - this.s.sessions = this.s.sessions.filter(s => s.equals(session)); - }); - - this.s.sessions.push(session); - return session; + return this.topology.startSession(options); }; var mergeOptions = function(target, source, flatten) { diff --git a/lib/topologies/mongos.js b/lib/topologies/mongos.js index e10168d1c55..b556b36195d 100644 --- a/lib/topologies/mongos.js +++ b/lib/topologies/mongos.js @@ -190,6 +190,8 @@ class Mongos extends TopologyBase { options: options, // Server Session Pool sessionPool: null, + // Active client sessions + sessions: [], // Promise library promiseLibrary: options.promiseLibrary || Promise }; diff --git a/lib/topologies/replset.js b/lib/topologies/replset.js index ad46c17df77..63fe02db054 100644 --- a/lib/topologies/replset.js +++ b/lib/topologies/replset.js @@ -206,6 +206,8 @@ class ReplSet extends TopologyBase { options: options, // Server Session Pool sessionPool: null, + // Active client sessions + sessions: [], // Promise library promiseLibrary: options.promiseLibrary || Promise }; @@ -371,22 +373,9 @@ class ReplSet extends TopologyBase { } close(forceClosed) { - var self = this; - // Call destroy on the topology - this.s.coreTopology.destroy({ - force: typeof forceClosed === 'boolean' ? forceClosed : false - }); - - // We need to wash out all stored processes - if (forceClosed === true) { - this.s.storeOptions.force = forceClosed; - this.s.store.flush(); - } + super.close(forceClosed); - var events = ['timeout', 'error', 'close', 'joined', 'left']; - events.forEach(function(e) { - self.removeAllListeners(e); - }); + ['timeout', 'error', 'close', 'joined', 'left'].forEach(e => this.removeAllListeners(e)); } } diff --git a/lib/topologies/server.js b/lib/topologies/server.js index 5823a11961f..cbf4c78ebd2 100644 --- a/lib/topologies/server.js +++ b/lib/topologies/server.js @@ -198,6 +198,8 @@ class Server extends TopologyBase { options: options, // Server Session Pool sessionPool: null, + // Active client sessions + sessions: [], // Promise library promiseLibrary: promiseLibrary || Promise }; diff --git a/lib/topologies/topology_base.js b/lib/topologies/topology_base.js index a5999b8e87d..c1654d10941 100644 --- a/lib/topologies/topology_base.js +++ b/lib/topologies/topology_base.js @@ -290,7 +290,13 @@ class TopologyBase extends EventEmitter { } startSession(options) { - return new ClientSession(this, this.s.sessionPool, options); + const session = new ClientSession(this, this.s.sessionPool, options); + session.once('ended', () => { + this.s.sessions = this.s.sessions.filter(s => !s.equals(session)); + }); + + this.s.sessions.push(session); + return session; } endSessions(sessions, callback) { @@ -388,6 +394,18 @@ class TopologyBase extends EventEmitter { } close(forceClosed) { + // If we have sessions, we want to send a single `endSessions` command for them, + // and then individually clean them up. They will be removed from the internal state + // when they emit their `ended` events. + if (this.s.sessions.length) { + this.endSessions(this.s.sessions.map(session => session.id)); + this.s.sessions.forEach(session => session.endSession({ skipCommand: true })); + } + + if (this.s.sessionPool) { + this.s.sessionPool.endAllPooledSessions(); + } + this.s.coreTopology.destroy({ force: typeof forceClosed === 'boolean' ? forceClosed : false }); diff --git a/package.json b/package.json index 246856de5f7..fce01c145af 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "official" ], "dependencies": { - "mongodb-core": "3.0.2" + "mongodb-core": "3.0.3" }, "devDependencies": { "betterbenchmarks": "^0.1.0", @@ -32,6 +32,7 @@ "mongodb-test-runner": "^1.1.18", "prettier": "^1.5.3", "semver": "5.4.1", + "sinon": "^4.3.0", "worker-farm": "^1.5.0" }, "author": "Christian Kvalheim", diff --git a/test/functional/apm_tests.js b/test/functional/apm_tests.js index 41438dfc8ee..f53a908d7e8 100644 --- a/test/functional/apm_tests.js +++ b/test/functional/apm_tests.js @@ -1036,6 +1036,10 @@ describe('APM', function() { // Get the result result = results.successes.shift(); + if (result.commandName === 'endSessions') { + result = results.successes.shift(); + } + // Validate the test expect(commandName).to.equal(result.commandName); // Do we have a getMore command @@ -1054,6 +1058,10 @@ describe('APM', function() { results.failures = filterSessionsCommands(results.failures); result = results.failures.shift(); + if (result.commandName === 'endSessions') { + result = results.failures.shift(); + } + // Validate the test expect(commandName).to.equal(result.commandName); } diff --git a/test/functional/crud_api_tests.js b/test/functional/crud_api_tests.js index de6d0246c42..de791c83d48 100644 --- a/test/functional/crud_api_tests.js +++ b/test/functional/crud_api_tests.js @@ -830,7 +830,7 @@ describe('CRUD API', function() { test.equal(null, err); // Delete all items with no selector - db.collection('t6_1').deleteMany(function(err) { + db.collection('t6_1').deleteMany({}, function(err) { test.equal(null, err); client.close(); diff --git a/test/functional/crud_spec_tests.js b/test/functional/crud_spec_tests.js index c8c03552b5d..28a91549429 100644 --- a/test/functional/crud_spec_tests.js +++ b/test/functional/crud_spec_tests.js @@ -39,6 +39,12 @@ describe('CRUD spec', function() { }); }); + afterEach(() => { + if (testContext.client) { + testContext.client.close(); + } + }); + describe('read', function() { readScenarios.forEach(function(scenarioData) { var scenarioName = scenarioData[0]; diff --git a/test/functional/cursor_tests.js b/test/functional/cursor_tests.js index f660e0e7f8c..1b9396d9841 100644 --- a/test/functional/cursor_tests.js +++ b/test/functional/cursor_tests.js @@ -1728,6 +1728,7 @@ describe('Cursor', function() { test.equal(1, items.length); test.equal(2, items[0].a); test.equal(undefined, items[0].x); + client.close(); done(); }); }); @@ -2296,9 +2297,9 @@ describe('Cursor', function() { if (count === 0) { var stream = collection.find({}, { tailable: true, awaitData: true }).stream(); - + // let index = 0; stream.on('data', function() { - // console.log("doc :: " + (index++)); + // console.log('doc :: ' + index++); }); stream.on('error', function(err) { @@ -2319,14 +2320,17 @@ describe('Cursor', function() { // Just hammer the server for (var i = 0; i < 100; i++) { + const id = i; process.nextTick(function() { - collection.insert({ id: i }, function(err) { + collection.insert({ id }, function(err) { test.equal(null, err); + + if (id === 99) { + setTimeout(() => client.close()); + } }); }); } - - setTimeout(() => client.close(), 800); } }); } diff --git a/test/functional/cursorstream_tests.js b/test/functional/cursorstream_tests.js index 7bcd6c29e69..3b30e80e9ac 100644 --- a/test/functional/cursorstream_tests.js +++ b/test/functional/cursorstream_tests.js @@ -70,9 +70,16 @@ describe('Cursor Streams', function() { // When the stream is done stream.on('end', function() { - expect(data).to.have.length(3000); - client.close(); - done(); + setTimeout(() => { + let err; + try { + expect(data).to.have.length(3000); + } catch (e) { + err = e; + } + client.close(); + done(err); + }, 1000); }); } }); @@ -139,9 +146,16 @@ describe('Cursor Streams', function() { // When the stream is done stream.on('end', function() { - expect(data).to.have.length(10000); - client.close(); - done(); + setTimeout(() => { + let err; + try { + expect(data).to.have.length(10000); + } catch (e) { + err = e; + } + client.close(); + done(err); + }, 1000); }); } }); diff --git a/test/functional/db_tests.js b/test/functional/db_tests.js index e2f967801a6..2a118286c7a 100644 --- a/test/functional/db_tests.js +++ b/test/functional/db_tests.js @@ -98,11 +98,13 @@ describe('Db', function() { coll.findOne({}, null, function() { //e - errors b/c findOne needs a query selector test.equal(1, count); + client.close(); done(); }); } catch (e) { process.nextTick(function() { test.equal(1, count); + client.close(); done(); }); } @@ -465,6 +467,7 @@ describe('Db', function() { return c.collectionName; }); test.notEqual(-1, collections.indexOf('node972.test')); + client.close(); done(); }); }); diff --git a/test/functional/gridfs_stream_tests.js b/test/functional/gridfs_stream_tests.js index 134292bed76..46de0ce4ceb 100644 --- a/test/functional/gridfs_stream_tests.js +++ b/test/functional/gridfs_stream_tests.js @@ -81,6 +81,7 @@ describe('GridFS Stream', function() { test.equal(error, null); test.equal(indexes.length, 2); test.equal(indexes[1].name, 'files_id_1_n_1'); + client.close(); done(); }); }); @@ -166,6 +167,7 @@ describe('GridFS Stream', function() { test.equal(error, null); test.equal(indexes.length, 2); test.equal(indexes[1].name, 'files_id_1_n_1'); + client.close(); done(); }); }); @@ -237,6 +239,7 @@ describe('GridFS Stream', function() { var hash = crypto.createHash('md5'); hash.update(license); test.equal(docs[0].md5, hash.digest('hex')); + client.close(); done(); }); }); @@ -283,6 +286,7 @@ describe('GridFS Stream', function() { downloadStream.on('error', function(err) { test.equal('ENOENT', err.code); + client.close(); client.close(); done(); }); @@ -333,6 +337,7 @@ describe('GridFS Stream', function() { downloadStream.on('end', function() { test.ok(gotData); + client.close(); done(); }); }); @@ -401,6 +406,7 @@ describe('GridFS Stream', function() { // care that we got between 1 and 3, and got the right result test.ok(gotData >= 1 && gotData <= 3); test.equal(str, 'pache'); + client.close(); done(); }); }); @@ -459,6 +465,7 @@ describe('GridFS Stream', function() { test.equal(error, null); test.equal(docs.length, 0); + client.close(); done(); }); }); @@ -521,6 +528,7 @@ describe('GridFS Stream', function() { // Fail if user tries to abort an aborted stream uploadStream.abort().then(null, function(error) { test.equal(error.toString(), 'Error: Cannot call abort() on a stream twice'); + client.close(); done(); }); }); @@ -582,6 +590,7 @@ describe('GridFS Stream', function() { // Fail if user tries to abort an aborted stream uploadStream.abort().then(null, function(error) { test.equal(error.toString(), 'Error: Cannot call abort() on a stream twice'); + client.close(); done(); }); }); @@ -642,6 +651,7 @@ describe('GridFS Stream', function() { downloadStream.on('end', function() { test.equal(downloadStream.s.cursor, null); if (finished.close) { + client.close(); return done(); } finished.end = true; @@ -649,6 +659,7 @@ describe('GridFS Stream', function() { downloadStream.on('close', function() { if (finished.end) { + client.close(); return done(); } finished.close = true; @@ -712,6 +723,7 @@ describe('GridFS Stream', function() { test.equal(error, null); test.equal(docs.length, 0); + client.close(); done(); }); }); @@ -756,6 +768,7 @@ describe('GridFS Stream', function() { sort: { _id: 1 } }); + client.close(); done(); }); // END @@ -811,6 +824,7 @@ describe('GridFS Stream', function() { test.equal(error, null); test.equal(docs.length, 0); + client.close(); done(); }); }); @@ -870,6 +884,7 @@ describe('GridFS Stream', function() { test.equal(error, null); test.equal(docs.length, 0); + client.close(); done(); }); }); @@ -918,6 +933,7 @@ describe('GridFS Stream', function() { bucket.find({}, { batchSize: 1 }).toArray(function(err, files) { test.equal(null, err); test.equal(1, files.length); + client.close(); done(); }); }); @@ -965,6 +981,7 @@ describe('GridFS Stream', function() { // Rename the file bucket.rename(id, 'renamed_it.dat', function(err) { expect(err).to.not.exist; + client.close(); done(); }); }); @@ -1009,6 +1026,7 @@ describe('GridFS Stream', function() { // As per spec, make sure we didn't actually fire a query // because the document length is 0 test.equal(stream.s.cursor, null); + client.close(); done(); }); }); @@ -1059,6 +1077,7 @@ describe('GridFS Stream', function() { } if (--num === 0) { + client.close(); done(); } }); @@ -1105,9 +1124,11 @@ describe('GridFS Stream', function() { download.on('error', function(error) { if (!testSpec.assert.error) { test.ok(false); + client.close(); done(); } test.ok(error.toString().indexOf(testSpec.assert.error) !== -1); + client.close(); done(); }); @@ -1115,10 +1136,12 @@ describe('GridFS Stream', function() { var result = testSpec.assert.result; if (!result) { test.ok(false); + client.close(); done(); } test.equal(res.toString('hex'), result.$hex); + client.close(); done(); }); }; @@ -1282,6 +1305,7 @@ describe('GridFS Stream', function() { // Fail if user tries to abort an aborted stream uploadStream.abort().then(null, function(error) { test.equal(error.toString(), 'Error: Cannot call abort() on a stream twice'); + client.close(); done(); }); }); @@ -1352,6 +1376,7 @@ describe('GridFS Stream', function() { // care that we got between 1 and 3, and got the right result test.ok(gotData >= 1 && gotData <= 3); test.equal(str, 'pache'); + client.close(); done(); }); }); diff --git a/test/functional/index_tests.js b/test/functional/index_tests.js index 6ad31e13ce0..2682a33cc5f 100644 --- a/test/functional/index_tests.js +++ b/test/functional/index_tests.js @@ -270,68 +270,6 @@ describe('Indexes', function() { } }); - /** - * @ignore - */ - it('shouldThrowErrorOnAttemptingSafeCreateIndexWithNoCallback', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - // The actual test we wish to run - test: function(done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { poolSize: 1 }); - client.connect(function(err, client) { - var db = client.db(configuration.db); - db.createCollection('shouldThrowErrorOnAttemptingSafeUpdateWithNoCallback', function( - err, - collection - ) { - try { - // insert a doc - collection.createIndex({ a: 1 }, configuration.writeConcernMax()); - test.ok(false); - } catch (err) {} // eslint-disable-line - - client.close(); - done(); - }); - }); - } - }); - - /** - * @ignore - */ - it('shouldThrowErrorOnAttemptingSafeEnsureIndexWithNoCallback', { - metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } - }, - - // The actual test we wish to run - test: function(done) { - var configuration = this.configuration; - var client = configuration.newClient(configuration.writeConcernMax(), { poolSize: 1 }); - client.connect(function(err, client) { - var db = client.db(configuration.db); - db.createCollection('shouldThrowErrorOnAttemptingSafeUpdateWithNoCallback', function( - err, - collection - ) { - try { - // insert a doc - collection.ensureIndex({ a: 1 }, configuration.writeConcernMax()); - test.ok(false); - } catch (err) {} // eslint-disable-line - - client.close(); - done(); - }); - }); - } - }); - /** * @ignore */ @@ -782,10 +720,16 @@ describe('Indexes', function() { collection.ensureIndex({ a: 1 }, configuration.writeConcernMax(), function(err) { test.equal(null, err); - collection.dropIndex('a_1'); - - client.close(); - done(); + collection + .dropIndex('a_1') + .then(() => { + client.close(); + done(); + }) + .catch(err => { + client.close(); + done(err); + }); }); }); }); @@ -1109,7 +1053,10 @@ describe('Indexes', function() { * @ignore */ it('should correctly error out due to driver close', { - metadata: { requires: { topology: ['single'] } }, + metadata: { + requires: { topology: ['single'] }, + sessions: { skipLeakTests: true } + }, // The actual test we wish to run test: function(done) { @@ -1118,14 +1065,16 @@ describe('Indexes', function() { client.connect(function(err, client) { var db = client.db(configuration.db); client.close(function() { - db.createCollection('nonexisting', { w: 1 }, function(err) { - test.ok(err != null); - db.collection('nonexisting', { strict: true }, function(err) { + setTimeout(() => { + db.createCollection('nonexisting', { w: 1 }, function(err) { test.ok(err != null); - db.collection('nonexisting', { strict: false }, function(err) { - // When set to false (default) it should not create an error - test.ok(err === null); - done(); + db.collection('nonexisting', { strict: true }, function(err) { + test.ok(err != null); + db.collection('nonexisting', { strict: false }, function(err) { + // When set to false (default) it should not create an error + test.ok(err === null); + setTimeout(() => done()); + }); }); }); }); diff --git a/test/functional/insert_tests.js b/test/functional/insert_tests.js index 183fba4560a..e2fa98399fe 100644 --- a/test/functional/insert_tests.js +++ b/test/functional/insert_tests.js @@ -1656,9 +1656,16 @@ describe('Insert', function() { client.connect(function(err, client) { var db = client.db(configuration.db); var collection = db.collection('shouldExecuteInsertWithNoCallbackAndWriteConcern'); - collection.insert({ a: { b: { c: 1 } } }); - client.close(); - done(); + collection.insert({ a: { b: { c: 1 } } }).then( + () => { + client.close(); + done(); + }, + err => { + client.close(); + done(err); + } + ); }); } }); diff --git a/test/functional/operation_example_tests.js b/test/functional/operation_example_tests.js index 903e12e590e..2efb1a0aedc 100644 --- a/test/functional/operation_example_tests.js +++ b/test/functional/operation_example_tests.js @@ -3170,7 +3170,8 @@ describe('Operation Examples', function() { */ it('shouldCorrectlyRenameCollection', { metadata: { - requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] } + requires: { topology: ['single', 'replicaset', 'sharded', 'ssl', 'heap', 'wiredtiger'] }, + sessions: { skipLeakTests: true } }, // The actual test we wish to run @@ -4352,12 +4353,14 @@ describe('Operation Examples', function() { test.ok(result); test.equal(null, err); + const oldClient = client; // Authenticate MongoClient.connect( 'mongodb://user:name@localhost:27017/integration_tests', function(err, client) { expect(err).to.exist; expect(client).to.not.exist; + oldClient.close(); done(); } ); @@ -8970,8 +8973,10 @@ describe('Operation Examples', function() { // When the stream is done stream.on('end', function() { - client.close(); - done(); + setTimeout(() => { + client.close(); + done(); + }, 1000); }); }); }); diff --git a/test/functional/operation_generators_example_tests.js b/test/functional/operation_generators_example_tests.js index ddd2d5ea675..2ca94f17cc1 100644 --- a/test/functional/operation_generators_example_tests.js +++ b/test/functional/operation_generators_example_tests.js @@ -1620,15 +1620,11 @@ describe('Operation (Generators)', function() { // Create a simple single field index yield collection.ensureIndex({ a: 1 }, configuration.writeConcernMax()); - setTimeout(function() { - return co(function*() { - // List all of the indexes on the collection - var indexes = yield collection.indexes(); - test.equal(3, indexes.length); + // List all of the indexes on the collection + var indexes = yield collection.indexes(); + test.equal(3, indexes.length); - client.close(); - }); - }, 1000); + client.close(); }); // END } @@ -1844,17 +1840,11 @@ describe('Operation (Generators)', function() { // BEGIN var collection = db.collection('simple_document_insert_collection_no_safe_with_generators'); // Insert a single document - collection.insertOne({ hello: 'world_no_safe' }); - - // Wait for a second before finishing up, to ensure we have written the item to disk - setTimeout(function() { - return co(function*() { - // Fetch the document - var item = yield collection.findOne({ hello: 'world_no_safe' }); - test.equal('world_no_safe', item.hello); - client.close(); - }); - }, 100); + yield collection.insertOne({ hello: 'world_no_safe' }); + + var item = yield collection.findOne({ hello: 'world_no_safe' }); + test.equal('world_no_safe', item.hello); + client.close(); }); // END } @@ -2346,7 +2336,10 @@ describe('Operation (Generators)', function() { * @ignore */ it('shouldCorrectlyRenameCollectionWithGenerators', { - metadata: { requires: { generators: true, topology: ['single'] } }, + metadata: { + requires: { generators: true, topology: ['single'] }, + sessions: { skipLeakTests: true } + }, // The actual test we wish to run test: function() { @@ -2477,17 +2470,12 @@ describe('Operation (Generators)', function() { // Fetch the collection var collection = db.collection('save_a_simple_document_with_generators'); // Save a document with no safe option - collection.save({ hello: 'world' }); - - // Wait for a second - setTimeout(function() { - return co(function*() { - // Find the saved document - var item = yield collection.findOne({ hello: 'world' }); - test.equal('world', item.hello); - client.close(); - }); - }, 2000); + yield collection.save({ hello: 'world' }); + + // Find the saved document + var item = yield collection.findOne({ hello: 'world' }); + test.equal('world', item && item.hello); + client.close(); }); // END } @@ -2591,19 +2579,14 @@ describe('Operation (Generators)', function() { yield collection.insertOne({ a: 1 }, configuration.writeConcernMax()); // Update the document with an atomic operator - collection.updateOne({ a: 1 }, { $set: { b: 2 } }); + yield collection.updateOne({ a: 1 }, { $set: { b: 2 } }); - // Wait for a second then fetch the document - setTimeout(function() { - return co(function*() { - // Fetch the document that we modified - var item = yield collection.findOne({ a: 1 }); - test.equal(1, item.a); - test.equal(2, item.b); + var item = yield collection.findOne({ a: 1 }); - client.close(); - }); - }, 1000); + test.equal(1, item.a); + test.equal(2, item.b); + + client.close(); }); // END } @@ -2947,54 +2930,52 @@ describe('Operation (Generators)', function() { readPreference: ReadPreference.PRIMARY }); - setTimeout(function() { - return co(function*() { - // Locate the entry - var collection = db.collection('test_eval_with_generators'); - var item = yield collection.findOne(); - test.equal(5, item.y); - tests_done(); - - // Evaluate a function with 2 parameters passed in - var result = yield db.eval('function (x, y) {return x + y;}', [2, 3]); - test.equal(5, result); - tests_done(); - - // Evaluate a function with no parameters passed in - result = yield db.eval('function () {return 5;}'); - test.equal(5, result); - tests_done(); - - // Evaluate a statement - result = yield db.eval('2 + 3;'); - test.equal(5, result); - tests_done(); - - // Evaluate a statement using the code object - result = yield db.eval(new Code('2 + 3;')); - test.equal(5, result); - tests_done(); - - // Evaluate a statement using the code object including a scope - result = yield db.eval(new Code('return i;', { i: 2 })); - test.equal(2, result); - tests_done(); - - // Evaluate a statement using the code object including a scope - result = yield db.eval(new Code('i + 3;', { i: 2 })); - test.equal(5, result); - tests_done(); - - try { - // Evaluate an illegal statement - yield db.eval('5 ++ 5;'); - } catch (err) { - test.ok(err instanceof Error); - test.ok(err.message != null); - tests_done(); - } - }); - }, 1000); + yield new Promise(resolve => setTimeout(resolve, 1000)); + + // Locate the entry + var collection = db.collection('test_eval_with_generators'); + var item = yield collection.findOne(); + test.equal(5, item.y); + tests_done(); + + // Evaluate a function with 2 parameters passed in + result = yield db.eval('function (x, y) {return x + y;}', [2, 3]); + test.equal(5, result); + tests_done(); + + // Evaluate a function with no parameters passed in + result = yield db.eval('function () {return 5;}'); + test.equal(5, result); + tests_done(); + + // Evaluate a statement + result = yield db.eval('2 + 3;'); + test.equal(5, result); + tests_done(); + + // Evaluate a statement using the code object + result = yield db.eval(new Code('2 + 3;')); + test.equal(5, result); + tests_done(); + + // Evaluate a statement using the code object including a scope + result = yield db.eval(new Code('return i;', { i: 2 })); + test.equal(2, result); + tests_done(); + + // Evaluate a statement using the code object including a scope + result = yield db.eval(new Code('i + 3;', { i: 2 })); + test.equal(5, result); + tests_done(); + + try { + // Evaluate an illegal statement + yield db.eval('5 ++ 5;'); + } catch (err) { + test.ok(err instanceof Error); + test.ok(err.message != null); + tests_done(); + } }); // END } @@ -3621,26 +3602,23 @@ describe('Operation (Generators)', function() { yield db.dropDatabase(); // Wait two seconds to let it replicate across - setTimeout(function() { - return co(function*() { - // Get the admin database - var dbs = yield db.admin().listDatabases(); - // Grab the databases - dbs = dbs.databases; - // Did we find the db - var found = false; - - // Check if we have the db in the list - for (var i = 0; i < dbs.length; i++) { - if (dbs[i].name === 'integration_tests_to_drop') found = true; - } + yield new Promise(resolve => setTimeout(resolve, 2000)); + // Get the admin database + var dbs = yield db.admin().listDatabases(); + // Grab the databases + dbs = dbs.databases; + // Did we find the db + var found = false; + + // Check if we have the db in the list + for (var i = 0; i < dbs.length; i++) { + if (dbs[i].name === 'integration_tests_to_drop') found = true; + } - // We should not find the databases - if (process.env['JENKINS'] == null) test.equal(false, found); + // We should not find the databases + if (process.env['JENKINS'] == null) test.equal(false, found); - client.close(); - }); - }, 2000); + client.close(); }); // END } @@ -6560,24 +6538,27 @@ describe('Operation (Generators)', function() { // Insert a document in the capped collection yield collection.insertMany(docs, configuration.writeConcernMax()); - var total = 0; - // Get the cursor - var cursor = collection - .find({}) - .addCursorFlag('tailable', true) - .addCursorFlag('awaitData', true); + yield new Promise(resolve => { + var total = 0; + // Get the cursor + var cursor = collection + .find({}) + .addCursorFlag('tailable', true) + .addCursorFlag('awaitData', true); - cursor.on('data', function() { - total = total + 1; + cursor.on('data', function() { + total = total + 1; - if (total === 1000) { - cursor.kill(); - } - }); + if (total === 1000) { + cursor.kill(); + } + }); - cursor.on('end', function() { - client.close(); + cursor.on('end', function() { + client.close(); + resolve(); + }); }); }); // END diff --git a/test/functional/operation_promises_example_tests.js b/test/functional/operation_promises_example_tests.js index d3d1ed584c6..824469a473f 100644 --- a/test/functional/operation_promises_example_tests.js +++ b/test/functional/operation_promises_example_tests.js @@ -2431,7 +2431,10 @@ describe('Operation (Promises)', function() { * @ignore */ it('shouldCorrectlyRenameCollectionWithPromises', { - metadata: { requires: { promises: true, topology: ['single'] } }, + metadata: { + requires: { promises: true, topology: ['single'] }, + sessions: { skipLeakTests: true } + }, // The actual test we wish to run test: function() { @@ -2538,8 +2541,14 @@ describe('Operation (Promises)', function() { }) .then(function(count) { test.equal(2, count); - client.close(); - }); + }) + .then( + () => client.close(), + e => { + client.close(); + throw e; + } + ); }); // END /* eslint-enable */ diff --git a/test/functional/session_leak_test.js b/test/functional/session_leak_test.js new file mode 100644 index 00000000000..554737e3c95 --- /dev/null +++ b/test/functional/session_leak_test.js @@ -0,0 +1,92 @@ +'use strict'; + +const expect = require('chai').expect; +const sinon = require('sinon'); +const core = require('mongodb-core'); +const MongoClient = require('../../lib/mongo_client'); +const ServerSessionPool = core.Sessions.ServerSessionPool; + +const sandbox = sinon.createSandbox(); + +let activeSessions, pooledSessions, activeSessionsBeforeClose; + +function getSessionLeakMetadata(currentTest) { + return (currentTest.metadata && currentTest.metadata.sessions) || {}; +} + +beforeEach('Session Leak Before Each - Set up clean test environment', () => { + sandbox.restore(); + activeSessions = new Set(); + pooledSessions = new Set(); + activeSessionsBeforeClose = new Set(); +}); + +beforeEach('Session Leak Before Each - setup session tracking', function() { + if (getSessionLeakMetadata(this.currentTest).skipLeakTests) { + return; + } + + const _acquire = ServerSessionPool.prototype.acquire; + sandbox.stub(ServerSessionPool.prototype, 'acquire').callsFake(function() { + const session = _acquire.apply(this, arguments); + activeSessions.add(session.id); + // console.log(`Active + ${JSON.stringify(session.id)} = ${activeSessions.size}`); + return session; + }); + + const _release = ServerSessionPool.prototype.release; + sandbox.stub(ServerSessionPool.prototype, 'release').callsFake(function(session) { + const id = session.id; + activeSessions.delete(id); + // console.log(`Active - ${JSON.stringify(id)} = ${activeSessions.size}`); + pooledSessions.add(id); + // console.log(`Pooled + ${JSON.stringify(id)} = ${activeSessions.size}`); + return _release.apply(this, arguments); + }); + + [core.Server, core.ReplSet, core.Mongos].forEach(topology => { + const _endSessions = topology.prototype.endSessions; + sandbox.stub(topology.prototype, 'endSessions').callsFake(function(sessions) { + sessions = Array.isArray(sessions) ? sessions : [sessions]; + + sessions.forEach(id => pooledSessions.delete(id)); + + return _endSessions.apply(this, arguments); + }); + }); + + const _close = MongoClient.prototype.close; + sandbox.stub(MongoClient.prototype, 'close').callsFake(function() { + activeSessionsBeforeClose = new Set(activeSessions); + + return _close.apply(this, arguments); + }); +}); + +afterEach('Session Leak After Each - ensure no leaks', function() { + if ( + this.currentTest.state === 'failed' || + getSessionLeakMetadata(this.currentTest).skipLeakTests + ) { + return; + } + + try { + expect( + activeSessionsBeforeClose.size, + `test is leaking ${activeSessionsBeforeClose.size} active sessions while running client` + ).to.equal(0); + + expect( + activeSessions.size, + `client close failed to clean up ${activeSessions.size} active sessions` + ).to.equal(0); + + expect( + pooledSessions.size, + `client close failed to clean up ${pooledSessions.size} pooled sessions` + ).to.equal(0); + } catch (e) { + this.test.error(e); + } +}); diff --git a/test/functional/sharding_read_preference_tests.js b/test/functional/sharding_read_preference_tests.js index 6e116ac195c..6e2dc6d179d 100644 --- a/test/functional/sharding_read_preference_tests.js +++ b/test/functional/sharding_read_preference_tests.js @@ -42,6 +42,8 @@ describe('Sharding (Read Preference)', function() { // Set debug level for the driver Logger.setLevel('debug'); + let gotMessage = false; + // Get the current logger Logger.setCurrentLogger(function(message, options) { if ( @@ -49,7 +51,7 @@ describe('Sharding (Read Preference)', function() { options.className === 'Cursor' && options.message.indexOf('"mode":"secondary"') !== -1 ) { - done(); + gotMessage = true; } }); @@ -59,11 +61,13 @@ describe('Sharding (Read Preference)', function() { function(err, item) { test.equal(null, err); test.equal(1, item.test); + test.ok(gotMessage); // Set error level for the driver Logger.setLevel('error'); // Close db connection client.close(); + done(); } ); }); @@ -106,6 +110,7 @@ describe('Sharding (Read Preference)', function() { // Set debug level for the driver Logger.setLevel('debug'); + let gotMessage = false; // Get the current logger Logger.setCurrentLogger(function(message, options) { if ( @@ -113,7 +118,7 @@ describe('Sharding (Read Preference)', function() { options.className === 'Cursor' && options.message.indexOf('"mode":"notsupported"') !== -1 ) { - done(); + gotMessage = true; } }); @@ -122,11 +127,13 @@ describe('Sharding (Read Preference)', function() { { readPreference: new ReadPreference('notsupported') }, function(err) { test.ok(err != null); + test.ok(gotMessage); // Set error level for the driver Logger.setLevel('error'); // Close db connection client.close(); + done(); } ); }); @@ -169,6 +176,7 @@ describe('Sharding (Read Preference)', function() { // Set debug level for the driver Logger.setLevel('debug'); + let gotMessage = false; // Get the current logger Logger.setCurrentLogger(function(message, options) { if ( @@ -178,7 +186,7 @@ describe('Sharding (Read Preference)', function() { '{"mode":"secondary","tags":[{"dc":"sf","s":"1"},{"dc":"ma","s":"2"}]}' ) !== -1 ) { - done(); + gotMessage = true; } }); @@ -192,10 +200,12 @@ describe('Sharding (Read Preference)', function() { }, function(err) { test.ok(err != null); + test.ok(gotMessage); // Set error level for the driver Logger.setLevel('error'); // Close db connection client.close(); + done(); } ); }); @@ -238,6 +248,7 @@ describe('Sharding (Read Preference)', function() { // Set debug level for the driver Logger.setLevel('debug'); + let gotMessage = false; // Get the current logger Logger.setCurrentLogger(function(message, options) { if ( @@ -246,7 +257,7 @@ describe('Sharding (Read Preference)', function() { options.message.indexOf('{"mode":"secondary","tags":[{"loc":"ny"},{"loc":"sf"}]}') !== -1 ) { - done(); + gotMessage = true; } }); @@ -261,10 +272,12 @@ describe('Sharding (Read Preference)', function() { function(err, item) { test.equal(null, err); test.equal(1, item.test); + test.ok(gotMessage); // Set error level for the driver Logger.setLevel('error'); // Close db connection client.close(); + done(); } ); }); diff --git a/test/unit/sessions/client_tests.js b/test/unit/sessions/client_tests.js index 00e371eaa29..99d9b876a36 100644 --- a/test/unit/sessions/client_tests.js +++ b/test/unit/sessions/client_tests.js @@ -60,6 +60,7 @@ describe('Sessions', function() { let session = client.startSession(); expect(session).to.exist; + session.endSession({ skipCommand: true }); client.close(); done(); }); diff --git a/test/unit/sessions/collection_tests.js b/test/unit/sessions/collection_tests.js index 298e13822d5..ebcffddebe2 100644 --- a/test/unit/sessions/collection_tests.js +++ b/test/unit/sessions/collection_tests.js @@ -44,6 +44,8 @@ describe('Sessions', function() { .then(() => { expect(findCommand.readConcern).to.have.keys(['level', 'afterClusterTime']); expect(findCommand.readConcern.afterClusterTime).to.eql(insertOperationTime); + + session.endSession({ skipCommand: true }); return client.close(); }); }); From ac0dec76824cfdd2f678357f10039e678e286e68 Mon Sep 17 00:00:00 2001 From: Dan Aprahamian Date: Fri, 23 Feb 2018 15:19:18 -0500 Subject: [PATCH 11/11] 3.0.3 --- HISTORY.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 569b54fac69..d219639b32b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,16 @@ + +## [3.0.3](https://github.com/mongodb/node-mongodb-native/compare/v3.0.2...v3.0.3) (2018-02-23) + + +### Bug Fixes + +* **collection:** fix error when calling remove with no args ([#1657](https://github.com/mongodb/node-mongodb-native/issues/1657)) ([4c9b0f8](https://github.com/mongodb/node-mongodb-native/commit/4c9b0f8)) +* **executeOperation:** don't mutate options passed to commands ([934a43a](https://github.com/mongodb/node-mongodb-native/commit/934a43a)) +* **jsdoc:** mark db.collection callback as optional + typo fix ([#1658](https://github.com/mongodb/node-mongodb-native/issues/1658)) ([c519b9b](https://github.com/mongodb/node-mongodb-native/commit/c519b9b)) +* **sessions:** move active session tracking to topology base ([#1665](https://github.com/mongodb/node-mongodb-native/issues/1665)) ([b1f296f](https://github.com/mongodb/node-mongodb-native/commit/b1f296f)) + + + ## [3.0.2](https://github.com/mongodb/node-mongodb-native/compare/v3.0.1...v3.0.2) (2018-01-29) diff --git a/package.json b/package.json index fce01c145af..cadbd5727a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mongodb", - "version": "3.0.2", + "version": "3.0.3", "description": "The official MongoDB driver for Node.js", "main": "index.js", "repository": {