diff --git a/.evergreen/install-dependencies.sh b/.evergreen/install-dependencies.sh index acfd7f2fe11..e856ee76f48 100644 --- a/.evergreen/install-dependencies.sh +++ b/.evergreen/install-dependencies.sh @@ -9,6 +9,7 @@ NPM_TMP_DIR="${NODE_ARTIFACTS_PATH}/tmp" # this needs to be explicitly exported for the nvm install below export NVM_DIR="${NODE_ARTIFACTS_PATH}/nvm" +export XDG_CONFIG_HOME=${NODE_ARTIFACTS_PATH} # create node artifacts path if needed mkdir -p ${NODE_ARTIFACTS_PATH} @@ -16,9 +17,9 @@ mkdir -p ${NPM_CACHE_DIR} mkdir -p "${NPM_TMP_DIR}" # install Node.js -curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash +curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.3/install.sh | bash [ -s "${NVM_DIR}/nvm.sh" ] && \. "${NVM_DIR}/nvm.sh" -nvm install --lts=${NODE_LTS_NAME} +nvm install --no-progress --lts=${NODE_LTS_NAME} # setup npm cache in a local directory cat < .npmrc diff --git a/package.json b/package.json index f35763b4770..b5718568b8c 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "check:atlas": "node test/tools/atlas_connectivity_tests.js", "check:bench": "node test/benchmarks/driverBench", "check:coverage": "nyc mocha --timeout 60000 --recursive test/functional test/unit", - "check:lint": "eslint index.js lib test", + "check:lint": "eslint -v && eslint index.js lib test", "check:test": "mocha --recursive test/functional test/unit", "check:types": "tsc -p tsconfig.check.json", "release": "standard-version -i HISTORY.md", diff --git a/test/functional/change_stream.test.js b/test/functional/change_stream.test.js index c60dd0044d7..e2e7b8ce16d 100644 --- a/test/functional/change_stream.test.js +++ b/test/functional/change_stream.test.js @@ -2,7 +2,7 @@ const assert = require('assert'); const { Transform } = require('stream'); const { MongoError, MongoNetworkError } = require('../../lib/error'); -const { setupDatabase, withTempDb, delay } = require('./shared'); +const { delay, setupDatabase, withClient } = require('./shared'); const co = require('co'); const mock = require('mongodb-mock-server'); const chai = require('chai'); @@ -2599,65 +2599,59 @@ describe('Change Streams', function() { }); describe('tryNext', function() { + function withTemporaryCollectionOnDb(database, testFn) { + return withClient((client, done) => { + const db = client.db(database); + db.createCollection('test', { w: 'majority' }, (err, collection) => { + if (err) return done(err); + testFn(collection, () => db.dropDatabase(done)); + }); + }); + } it('should return null on single iteration of empty cursor', { metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, - test: function() { - return withTempDb( - 'testTryNext', - { w: 'majority' }, - this.configuration.newClient(), - db => done => { - const changeStream = db.collection('test').watch(); - tryNext(changeStream, (err, doc) => { - expect(err).to.not.exist; - expect(doc).to.not.exist; + test: withTemporaryCollectionOnDb('testTryNext', (collection, done) => { + const changeStream = collection.watch(); + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.not.exist; - changeStream.close(done); - }); - } - ); - } + changeStream.close(done); + }); + }) }); it('should iterate a change stream until first empty batch', { metadata: { requires: { topology: 'replicaset', mongodb: '>=3.6' } }, - test: function() { - return withTempDb( - 'testTryNext', - { w: 'majority' }, - this.configuration.newClient(), - db => done => { - const collection = db.collection('test'); - const changeStream = collection.watch(); - waitForStarted(changeStream, () => { - collection.insertOne({ a: 42 }, err => { - expect(err).to.not.exist; + test: withTemporaryCollectionOnDb('testTryNext', (collection, done) => { + const changeStream = collection.watch(); + waitForStarted(changeStream, () => { + collection.insertOne({ a: 42 }, err => { + expect(err).to.not.exist; - collection.insertOne({ b: 24 }, err => { - expect(err).to.not.exist; - }); - }); + collection.insertOne({ b: 24 }, err => { + expect(err).to.not.exist; }); + }); + }); - tryNext(changeStream, (err, doc) => { - expect(err).to.not.exist; - expect(doc).to.exist; + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.exist; - tryNext(changeStream, (err, doc) => { - expect(err).to.not.exist; - expect(doc).to.exist; + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.exist; - tryNext(changeStream, (err, doc) => { - expect(err).to.not.exist; - expect(doc).to.not.exist; + tryNext(changeStream, (err, doc) => { + expect(err).to.not.exist; + expect(doc).to.not.exist; - changeStream.close(done); - }); - }); + changeStream.close(done); }); - } - ); - } + }); + }); + }) }); }); diff --git a/test/functional/index.test.js b/test/functional/index.test.js index 83772ee665f..c51d9d252e6 100644 --- a/test/functional/index.test.js +++ b/test/functional/index.test.js @@ -1,9 +1,6 @@ 'use strict'; -var test = require('./shared').assert; -var setupDatabase = require('./shared').setupDatabase; -const expect = require('chai').expect; -const withClient = require('./shared').withClient; -const withMonitoredClient = require('./shared').withMonitoredClient; +const { expect } = require('chai'); +const { assert: test, setupDatabase, withClient, withMonitoredClient } = require('./shared'); describe('Indexes', function() { before(function() { @@ -1208,20 +1205,18 @@ describe('Indexes', function() { function throwErrorTest(testCommand) { return { metadata: { requires: { mongodb: '<4.4' } }, - test: function() { - return withClient(this.configuration.newClient(), client => done => { - const db = client.db('test'); - const collection = db.collection('commitQuorum'); - testCommand(db, collection, (err, result) => { - expect(err).to.exist; - expect(err.message).to.equal( - '`commitQuorum` option for `createIndexes` not supported on servers < 4.4' - ); - expect(result).to.not.exist; - done(); - }); + test: withClient((client, done) => { + const db = client.db('test'); + const collection = db.collection('commitQuorum'); + testCommand(db, collection, (err, result) => { + expect(err).to.exist; + expect(err.message).to.equal( + '`commitQuorum` option for `createIndexes` not supported on servers < 4.4' + ); + expect(result).to.not.exist; + done(); }); - } + }) }; } it( diff --git a/test/functional/logger.test.js b/test/functional/logger.test.js index 079b4a15f81..c698f43b717 100644 --- a/test/functional/logger.test.js +++ b/test/functional/logger.test.js @@ -1,6 +1,6 @@ 'use strict'; -var expect = require('chai').expect; -var connectToDb = require('./shared').connectToDb; +const expect = require('chai').expect; +const { withClient } = require('./shared'); const Logger = require('../../lib/logger'); describe('Logger', function() { @@ -54,27 +54,20 @@ describe('Logger', function() { it('should not fail with undefined id', { metadata: { requires: { topology: ['single'] } }, - test: function(done) { - var self = this; - + test: function() { // set a custom logger per http://mongodb.github.io/node-mongodb-native/2.0/tutorials/logging/ Logger.setCurrentLogger(function() {}); Logger.setLevel('debug'); - connectToDb('mongodb://localhost:27017/test', self.configuration.db, function( - err, - db, - client - ) { - expect(err).to.not.exist; - + return withClient('mongodb://localhost:27017/test', (client, done) => { + const db = client.db(this.configuration.db); // perform any operation that gets logged db.collection('foo').findOne({}, function(err) { expect(err).to.not.exist; // Clean up Logger.reset(); - client.close(done); + done(); }); }); } @@ -86,16 +79,9 @@ describe('Logger', function() { it('should correctly log cursor', { metadata: { requires: { topology: ['single'] } }, - test: function(done) { - var self = this; - - connectToDb('mongodb://localhost:27017/test', self.configuration.db, function( - err, - db, - client - ) { - expect(err).to.not.exist; - + test: function() { + return withClient('mongodb://localhost:27017/test', (client, done) => { + const db = client.db(this.configuration.db); // Status var logged = false; @@ -120,7 +106,7 @@ describe('Logger', function() { // Clean up Logger.reset(); - client.close(done); + done(); }); }); } @@ -132,35 +118,28 @@ describe('Logger', function() { it('should pass the logLevel down through the options', { metadata: { requires: { topology: ['single'] } }, - test: function(done) { - var self = this; - + test: function() { Logger.filter('class', ['Cursor']); var logged = false; - connectToDb( - 'mongodb://localhost:27017/test', - self.configuration.db, - { + return withClient('mongodb://localhost:27017/test', (client, done) => { + const db = client.db(this.configuration.db, { loggerLevel: 'debug', logger: function() { logged = true; } - }, - function(err, db, client) { - expect(err).to.not.exist; + }); - // perform any operation that gets logged - db.collection('foo').findOne({}, function(err) { - expect(err).to.not.exist; - expect(logged).to.be.true; + // perform any operation that gets logged + db.collection('foo').findOne({}, function(err) { + expect(err).to.not.exist; + expect(logged).to.be.true; - // Clean up - Logger.reset(); - client.close(done); - }); - } - ); + // Clean up + Logger.reset(); + done(); + }); + }); } }); }); diff --git a/test/functional/shared.js b/test/functional/shared.js index 7b03073e43f..92e22d46d92 100644 --- a/test/functional/shared.js +++ b/test/functional/shared.js @@ -1,9 +1,52 @@ 'use strict'; -const MongoClient = require('../../').MongoClient; const expect = require('chai').expect; +// helpers for using chai.expect in the assert style +const assert = { + equal: function(a, b) { + expect(a).to.equal(b); + }, + + deepEqual: function(a, b) { + expect(a).to.eql(b); + }, + + strictEqual: function(a, b) { + expect(a).to.eql(b); + }, + + notEqual: function(a, b) { + expect(a).to.not.equal(b); + }, + + ok: function(a) { + expect(a).to.be.ok; + }, + + throws: function(func) { + expect(func).to.throw; + } +}; + +function delay(timeout) { + return new Promise(function(resolve) { + setTimeout(function() { + resolve(); + }, timeout); + }); +} + +function dropCollection(dbObj, collectionName) { + return dbObj.dropCollection(collectionName).catch(ignoreNsNotFound); +} + function filterForCommands(commands, bag) { + if (typeof commands === 'function') { + return function(event) { + if (commands(event.commandName)) bag.push(event); + }; + } commands = Array.isArray(commands) ? commands : [commands]; return function(event) { if (commands.indexOf(event.commandName) !== -1) bag.push(event); @@ -11,22 +54,19 @@ function filterForCommands(commands, bag) { } function filterOutCommands(commands, bag) { + if (typeof commands === 'function') { + return function(event) { + if (!commands(event.commandName)) bag.push(event); + }; + } commands = Array.isArray(commands) ? commands : [commands]; return function(event) { if (commands.indexOf(event.commandName) === -1) bag.push(event); }; } -function connectToDb(url, db, options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; - } - - MongoClient.connect(url, options || {}, function(err, client) { - if (err) return callback(err); - callback(null, client.db(db), client); - }); +function ignoreNsNotFound(err) { + if (!err.message.match(/ns not found/)) throw err; } function setupDatabase(configuration, dbsToClean) { @@ -57,8 +97,31 @@ function setupDatabase(configuration, dbsToClean) { ); } -function makeCleanupFn(client) { - return function(err) { +/** @typedef {(client: MongoClient) => Promise | (client: MongoClient, done: Function) => void} withClientCallback */ +/** + * Safely perform a test with provided MongoClient, ensuring client won't leak. + * + * @param {string|MongoClient} [client] if not provided, `withClient` must be bound to test function `this` + * @param {withClientCallback} callback the test function + */ +function withClient(client, callback) { + let connectionString; + if (arguments.length === 1) { + callback = client; + client = undefined; + } else { + if (typeof client === 'string') { + connectionString = client; + client = undefined; + } + } + + if (callback.length === 2) { + const cb = callback; + callback = client => new Promise(resolve => cb(client, resolve)); + } + + function cleanup(err) { return new Promise((resolve, reject) => { try { client.close(closeErr => { @@ -72,76 +135,58 @@ function makeCleanupFn(client) { return reject(err || e); } }); - }; -} + } + + function lambda() { + if (!client) { + client = this.configuration.newClient(connectionString); + } + return client + .connect() + .then(callback) + .then(err => { + cleanup(); + if (err) { + throw err; + } + }, cleanup); + } -function withTempDb(name, options, client, operation, errorHandler) { - return withClient( - client, - client => done => { - const db = client.db(name, options); - operation.call(this, db)(() => db.dropDatabase(done)); - }, - errorHandler - ); + if (this && this.configuration) { + return lambda.call(this); + } + return lambda; } +/** @typedef {(client: MongoClient, events: Array, done: Function) => void} withMonitoredClientCallback */ /** - * Safely perform a test with provided MongoClient, ensuring client won't leak. + * Perform a test with a monitored MongoClient that will filter for certain commands. * - * @param {MongoClient} client - * @param {Function|Promise} operation - * @param {Function|Promise} [errorHandler] + * @param {string|Array|Function} commands commands to filter for + * @param {object} [options] options to pass on to configuration.newClient + * @param {object} [options.queryOptions] connection string options + * @param {object} [options.clientOptions] MongoClient options + * @param {withMonitoredClientCallback} callback the test function */ -function withClient(client, operation, errorHandler) { - const cleanup = makeCleanupFn(client); - - return client - .connect() - .then(operation, errorHandler) - .then(() => cleanup(), cleanup); -} - -var assert = { - equal: function(a, b) { - expect(a).to.equal(b); - }, - - deepEqual: function(a, b) { - expect(a).to.eql(b); - }, - - strictEqual: function(a, b) { - expect(a).to.eql(b); - }, - - notEqual: function(a, b) { - expect(a).to.not.equal(b); - }, - - ok: function(a) { - expect(a).to.be.ok; - }, - - throws: function(func) { - expect(func).to.throw; +function withMonitoredClient(commands, options, callback) { + if (arguments.length === 2) { + callback = options; + options = {}; } -}; - -var delay = function(timeout) { - return new Promise(function(resolve) { - setTimeout(function() { - resolve(); - }, timeout); - }); -}; - -function ignoreNsNotFound(err) { - if (!err.message.match(/ns not found/)) throw err; -} - -function dropCollection(dbObj, collectionName) { - return dbObj.dropCollection(collectionName).catch(ignoreNsNotFound); + if (!Object.prototype.hasOwnProperty.call(callback, 'prototype')) { + throw new Error('withMonitoredClient callback can not be arrow function'); + } + return function() { + const monitoredClient = this.configuration.newClient( + Object.assign({}, options.queryOptions), + Object.assign({ monitorCommands: true }, options.clientOptions) + ); + const events = []; + monitoredClient.on('commandStarted', filterForCommands(commands, events)); + return withClient(monitoredClient, (client, done) => + callback.bind(this)(client, events, done) + )(); + }; } /** @@ -210,59 +255,15 @@ class EventCollector { } } -/** - * Perform a test with a monitored MongoClient that will filter for certain commands. - * - * @param {string|Array} commands commands to filter for - * @param {object} [options] options to pass on to configuration.newClient - * @param {object} [options.queryOptions] connection string options - * @param {object} [options.clientOptions] MongoClient options - * @param {withMonitoredClientCallback} callback the test function - */ -function withMonitoredClient(commands, options, callback) { - if (arguments.length === 2) { - callback = options; - options = {}; - } - if (!Object.prototype.hasOwnProperty.call(callback, 'prototype')) { - throw new Error('withMonitoredClient callback can not be arrow function'); - } - return function(done) { - const configuration = this.configuration; - const client = configuration.newClient( - Object.assign({}, options.queryOptions), - Object.assign({ monitorCommands: true }, options.clientOptions) - ); - const events = []; - client.on('commandStarted', filterForCommands(commands, events)); - client.connect((err, client) => { - expect(err).to.not.exist; - function _done(err) { - client.close(err2 => done(err || err2)); - } - callback.bind(this)(client, events, _done); - }); - }; -} - -/** - * @callback withMonitoredClientCallback - * @param {MongoClient} client monitored client - * @param {Array} events record of monitored commands - * @param {Function} done trigger end of test and cleanup - */ - module.exports = { - connectToDb, - setupDatabase, assert, delay, - withClient, - withMonitoredClient, - withTempDb, + dropCollection, filterForCommands, filterOutCommands, ignoreNsNotFound, - dropCollection, + setupDatabase, + withClient, + withMonitoredClient, EventCollector }; diff --git a/test/functional/shared.test.js b/test/functional/shared.test.js index f8ae10268ed..fdeabeb3a63 100644 --- a/test/functional/shared.test.js +++ b/test/functional/shared.test.js @@ -36,7 +36,7 @@ describe('shared test utilities', function() { return innerDone(); }); }).bind(this); - encapsulatedTest(fakeDone); + encapsulatedTest().then(fakeDone); }); it('should propagate passed error to done', function(done) { @@ -59,7 +59,7 @@ describe('shared test utilities', function() { return innerDone(new Error('hello world')); }); }).bind(this); - encapsulatedTest(fakeDone); + encapsulatedTest().catch(fakeDone); }); it('should call done and close connection with promise', function(done) { @@ -82,7 +82,7 @@ describe('shared test utilities', function() { return innerDone(); }); }).bind(this); - encapsulatedTest(fakeDone); + encapsulatedTest().then(fakeDone); }); it('should propagate passed error to done from promise', function(done) { @@ -106,7 +106,7 @@ describe('shared test utilities', function() { return innerDone(new Error('hello world')); }); }).bind(this); - encapsulatedTest(fakeDone); + encapsulatedTest().catch(fakeDone); }); }); });