diff --git a/.evergreen/config.yml b/.evergreen/config.yml index b3d2ee550db..fc766a75415 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -134,6 +134,24 @@ functions: MONGODB_API_VERSION="${MONGODB_API_VERSION}" \ NODE_VERSION=${NODE_VERSION} SKIP_DEPS=${SKIP_DEPS|1} NO_EXIT=${NO_EXIT|1} \ bash ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh + run serverless tests: + - command: timeout.update + params: + exec_timeout_secs: 1800 + - command: shell.exec + type: test + params: + working_dir: src + script: | + ${PREPARE_SHELL} + # Disable xtrace (just in case it was accidentally set). + set +x + MONGODB_URI="${MONGODB_URI}" \ + MONGODB_API_VERSION="${MONGODB_API_VERSION}" \ + AUTH="auth" SSL="ssl" SERVERLESS=1 \ + SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" \ + SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" \ + bash ${PROJECT_DIRECTORY}/.evergreen/run-serverless-tests.sh start-load-balancer: - command: shell.exec params: @@ -605,18 +623,12 @@ functions: - command: attach.xunit_results params: file: src/xunit.xml -pre: - - func: fetch source - - func: prepare resources - - func: windows fix - - func: fix absolute paths - - func: make files executable -post: - - func: upload test results - - func: cleanup -ignore: - - '*.md' tasks: + - name: test-serverless + tags: + - serverless + commands: + - func: run serverless tests - name: test-latest-server tags: - latest @@ -1689,6 +1701,51 @@ tasks: - func: run bson-ext test vars: NODE_LTS_NAME: erbium +task_groups: + - name: serverless_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + setup_group: + - func: fetch source + - func: prepare resources + - command: shell.exec + params: + shell: bash + script: | + ${PREPARE_SHELL} + set +o xtrace + SERVERLESS_DRIVERS_GROUP=${SERVERLESS_DRIVERS_GROUP} \ + SERVERLESS_API_PUBLIC_KEY=${SERVERLESS_API_PUBLIC_KEY} \ + SERVERLESS_API_PRIVATE_KEY=${SERVERLESS_API_PRIVATE_KEY} \ + bash ${DRIVERS_TOOLS}/.evergreen/serverless/create-instance.sh + - command: expansions.update + params: + file: serverless-expansion.yml + teardown_group: + - func: upload test results + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + set +o xtrace + SERVERLESS_DRIVERS_GROUP=${SERVERLESS_DRIVERS_GROUP} \ + SERVERLESS_API_PUBLIC_KEY=${SERVERLESS_API_PUBLIC_KEY} \ + SERVERLESS_API_PRIVATE_KEY=${SERVERLESS_API_PRIVATE_KEY} \ + SERVERLESS_INSTANCE_NAME=${SERVERLESS_INSTANCE_NAME} \ + bash ${DRIVERS_TOOLS}/.evergreen/serverless/delete-instance.sh + tasks: + - .serverless +pre: + - func: fetch source + - func: prepare resources + - func: windows fix + - func: fix absolute paths + - func: make files executable +post: + - func: upload test results + - func: cleanup +ignore: + - '*.md' buildvariants: - name: macos-1014-erbium display_name: macOS 10.14 Node Erbium @@ -1975,3 +2032,10 @@ buildvariants: - aws-4.4-auth-test-run-aws-auth-test-with-aws-credentials-as-environment-variables - aws-4.4-auth-test-run-aws-auth-test-with-aws-credentials-and-session-token-as-environment-variables - aws-4.4-auth-test-run-aws-ECS-auth-test + - name: ubuntu1804-test-serverless + display_name: Serverless Test + run_on: ubuntu1804-test + expansions: + NODE_LTS_NAME: erbium + tasks: + - serverless_task_group diff --git a/.evergreen/config.yml.in b/.evergreen/config.yml.in index 57ea9182ba9..c4b328940fa 100644 --- a/.evergreen/config.yml.in +++ b/.evergreen/config.yml.in @@ -155,6 +155,25 @@ functions: NODE_VERSION=${NODE_VERSION} SKIP_DEPS=${SKIP_DEPS|1} NO_EXIT=${NO_EXIT|1} \ bash ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh + "run serverless tests": + - command: timeout.update + params: + exec_timeout_secs: 1800 + - command: shell.exec + type: test + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + # Disable xtrace (just in case it was accidentally set). + set +x + MONGODB_URI="${MONGODB_URI}" \ + MONGODB_API_VERSION="${MONGODB_API_VERSION}" \ + AUTH="auth" SSL="ssl" SERVERLESS=1 \ + SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" \ + SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" \ + bash ${PROJECT_DIRECTORY}/.evergreen/run-serverless-tests.sh + "start-load-balancer": - command: shell.exec params: @@ -648,6 +667,47 @@ functions: params: file: "src/xunit.xml" +tasks: + - name: "test-serverless" + tags: ["serverless"] + commands: + - func: "run serverless tests" + +task_groups: + - name: serverless_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: "fetch source" + - func: "prepare resources" + - command: shell.exec + params: + shell: "bash" + script: | + ${PREPARE_SHELL} + set +o xtrace + SERVERLESS_DRIVERS_GROUP=${SERVERLESS_DRIVERS_GROUP} \ + SERVERLESS_API_PUBLIC_KEY=${SERVERLESS_API_PUBLIC_KEY} \ + SERVERLESS_API_PRIVATE_KEY=${SERVERLESS_API_PRIVATE_KEY} \ + bash ${DRIVERS_TOOLS}/.evergreen/serverless/create-instance.sh + - command: expansions.update + params: + file: serverless-expansion.yml + teardown_group: + - func: "upload test results" + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + set +o xtrace + SERVERLESS_DRIVERS_GROUP=${SERVERLESS_DRIVERS_GROUP} \ + SERVERLESS_API_PUBLIC_KEY=${SERVERLESS_API_PUBLIC_KEY} \ + SERVERLESS_API_PRIVATE_KEY=${SERVERLESS_API_PRIVATE_KEY} \ + SERVERLESS_INSTANCE_NAME=${SERVERLESS_INSTANCE_NAME} \ + bash ${DRIVERS_TOOLS}/.evergreen/serverless/delete-instance.sh + tasks: + - ".serverless" + pre: - func: "fetch source" - func: "prepare resources" diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 45b877e3c40..72d35a1f283 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -626,6 +626,17 @@ SINGLETON_TASKS.push({ ] }); +// special case for serverless testing +BUILD_VARIANTS.push({ + name: 'ubuntu1804-test-serverless', + display_name: 'Serverless Test', + run_on: 'ubuntu1804-test', + expansions: { + NODE_LTS_NAME: LOWEST_LTS + }, + tasks: ['serverless_task_group'] +}); + const fileData = yaml.safeLoad(fs.readFileSync(`${__dirname}/config.yml.in`, 'utf8')); fileData.tasks = (fileData.tasks || []).concat(BASE_TASKS).concat(TASKS).concat(SINGLETON_TASKS); fileData.buildvariants = (fileData.buildvariants || []).concat(BUILD_VARIANTS); diff --git a/.evergreen/run-serverless-tests.sh b/.evergreen/run-serverless-tests.sh new file mode 100644 index 00000000000..a19c0f15a19 --- /dev/null +++ b/.evergreen/run-serverless-tests.sh @@ -0,0 +1,13 @@ +#!/bin/bash +source "${PROJECT_DIRECTORY}/.evergreen/install-dependencies.sh" + +SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" \ +SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" \ +SERVERLESS=1 AUTH=auth SSL=ssl \ +MONGODB_URI=${MONGODB_URI} npx mocha \ + test/functional/crud_spec.test.js \ + test/functional/retryable_reads.test.js \ + test/functional/retryable_writes.test.js \ + test/functional/sessions.test.js \ + test/functional/transactions.test.js \ + test/functional/versioned-api.test.js diff --git a/test/functional/crud_spec.test.js b/test/functional/crud_spec.test.js index 2cda5ac4b22..00d0ab10a89 100644 --- a/test/functional/crud_spec.test.js +++ b/test/functional/crud_spec.test.js @@ -20,6 +20,9 @@ function enforceServerVersionLimits(requires, scenario) { if (versionLimits.length) { requires.mongodb = versionLimits.join(' '); } + if (scenario.serverless) { + requires.serverless = scenario.serverless; + } } function findScenarios() { @@ -35,7 +38,7 @@ const readScenarios = findScenarios('v1', 'read'); const writeScenarios = findScenarios('v1', 'write'); const testContext = {}; -describe('CRUD spec', function () { +describe('CRUD spec v1', function () { beforeEach(function () { const configuration = this.configuration; const client = configuration.newClient(); diff --git a/test/functional/sessions.test.js b/test/functional/sessions.test.js index 1a16c018cf2..d5a60d3159d 100644 --- a/test/functional/sessions.test.js +++ b/test/functional/sessions.test.js @@ -66,7 +66,11 @@ describe('Sessions', function () { }); describe('withSession', { - metadata: { requires: { mongodb: '>=3.6.0' } }, + metadata: { + requires: { + mongodb: '>=3.6.0' + } + }, test: function () { beforeEach(function () { return test.setup(this.configuration); diff --git a/test/functional/spec-runner/context.js b/test/functional/spec-runner/context.js index 04510637cae..1f3ae91354f 100644 --- a/test/functional/spec-runner/context.js +++ b/test/functional/spec-runner/context.js @@ -44,6 +44,12 @@ class TestRunnerContext { this.user = opts.user; this.password = opts.password; this.authSource = opts.authSource; + if (process.env.SERVERLESS) { + this.user = process.env.SERVERLESS_ATLAS_USER; + this.password = process.env.SERVERLESS_ATLAS_PASSWORD; + this.authSource = 'admin'; + this.serverless = true; + } this.sharedClient = null; this.failPointClients = []; this.appliedFailPoints = []; diff --git a/test/functional/spec-runner/index.js b/test/functional/spec-runner/index.js index d3a9ff45070..c80d971931a 100644 --- a/test/functional/spec-runner/index.js +++ b/test/functional/spec-runner/index.js @@ -7,6 +7,7 @@ const { EJSON } = require('bson'); const { isRecord } = require('../../../src/utils'); const TestRunnerContext = require('./context').TestRunnerContext; const resolveConnectionString = require('./utils').resolveConnectionString; +const { shouldRunServerlessTest } = require('../../tools/utils'); // Promise.try alternative https://stackoverflow.com/questions/60624081/promise-try-without-bluebird/60624164?noredirect=1#comment107255389_60624164 function promiseTry(callback) { @@ -110,7 +111,12 @@ function parseRunOn(runOn) { } const mongodb = version.join(' '); - return { topology, mongodb, authEnabled: !!config.authEnabled }; + return { + topology, + mongodb, + authEnabled: !!config.authEnabled, + serverless: config.serverless + }; }); } @@ -167,6 +173,13 @@ function shouldRunSpecTest(configuration, requires, spec, filter) { return false; } + if ( + requires.serverless && + !shouldRunServerlessTest(requires.serverless, !!process.env.SERVERLESS) + ) { + return false; + } + if ( spec.operations.some( op => op.name === 'waitForEvent' && op.arguments.event === 'PoolReadyEvent' @@ -191,20 +204,23 @@ function prepareDatabaseForSuite(suite, context) { if (context.skipPrepareDatabase) return Promise.resolve(); - const setupPromise = db - .admin() - .command({ killAllSessions: [] }) - .catch(err => { - if ( - err.message.match(/no such (cmd|command)/) || - err.message.match(/Failed to kill on some hosts/) || - err.code === 11601 - ) { - return; - } + // Note: killAllSession is not supported on serverless, see CLOUDP-84298 + const setupPromise = context.serverless + ? Promise.resolve() + : db + .admin() + .command({ killAllSessions: [] }) + .catch(err => { + if ( + err.message.match(/no such (cmd|command)/) || + err.message.match(/Failed to kill on some hosts/) || + err.code === 11601 + ) { + return; + } - throw err; - }); + throw err; + }); if (context.collectionName == null || context.dbName === 'admin') { return setupPromise; diff --git a/test/functional/transactions.test.js b/test/functional/transactions.test.js index 36c9ed304ef..61b29e9b9e0 100644 --- a/test/functional/transactions.test.js +++ b/test/functional/transactions.test.js @@ -97,16 +97,50 @@ describe('Transactions Spec Unified Tests', function () { } }); -describe('Transactions', function () { +const SKIP_TESTS = [ + // commitTransaction retry seems to be swallowed by mongos in these three cases + 'commitTransaction retry succeeds on new mongos', + 'commitTransaction retry fails on new mongos', + 'unpin after transient error within a transaction and commit', + // FIXME(NODE-3074): unskip count tests when spec tests have been updated + 'count', + // This test needs there to be multiple mongoses + // 'increment txnNumber', + // Skipping this until SPEC-1320 is resolved + // 'remain pinned after non-transient error on commit', + + // Will be implemented as part of NODE-2034 + 'Client side error in command starting transaction', + 'Client side error when transaction is in progress', + + // Will be implemented as part of NODE-2538 + 'abortTransaction only retries once with RetryableWriteError from server', + 'abortTransaction does not retry without RetryableWriteError label', + 'commitTransaction does not retry error without RetryableWriteError label', + 'commitTransaction retries once with RetryableWriteError from server' +]; + +describe('Transactions Spec Legacy Tests', function () { const testContext = new TransactionsRunnerContext(); - - [ - { name: 'spec tests', specPath: path.join('transactions', 'legacy') }, - { + const suitesToRun = [{ name: 'spec tests', specPath: path.join('transactions', 'legacy') }]; + // Note: convenient-api tests are skipped for serverless + if (!process.env.SERVERLESS) { + suitesToRun.push({ name: 'withTransaction spec tests', specPath: path.join('transactions', 'convenient-api') - } - ].forEach(suiteSpec => { + }); + } else { + // FIXME(NODE-3550): these tests should pass on serverless but currently fail + SKIP_TESTS.push( + 'abortTransaction only performs a single retry', + 'abortTransaction does not retry after Interrupted', + 'abortTransaction does not retry after WriteConcernError Interrupted', + 'commitTransaction does not retry error without RetryableWriteError label', + 'commitTransaction is not retried after UnsatisfiableWriteConcern error', + 'commitTransaction fails after Interrupted' + ); + } + suitesToRun.forEach(suiteSpec => { describe(suiteSpec.name, function () { const testSuites = loadSpecTests(suiteSpec.specPath); after(() => testContext.teardown()); @@ -115,29 +149,6 @@ describe('Transactions', function () { }); function testFilter(spec) { - const SKIP_TESTS = [ - // commitTransaction retry seems to be swallowed by mongos in these three cases - 'commitTransaction retry succeeds on new mongos', - 'commitTransaction retry fails on new mongos', - 'unpin after transient error within a transaction and commit', - // FIXME(NODE-3074): unskip count tests when spec tests have been updated - 'count', - // This test needs there to be multiple mongoses - // 'increment txnNumber', - // Skipping this until SPEC-1320 is resolved - // 'remain pinned after non-transient error on commit', - - // Will be implemented as part of NODE-2034 - 'Client side error in command starting transaction', - 'Client side error when transaction is in progress', - - // Will be implemented as part of NODE-2538 - 'abortTransaction only retries once with RetryableWriteError from server', - 'abortTransaction does not retry without RetryableWriteError label', - 'commitTransaction does not retry error without RetryableWriteError label', - 'commitTransaction retries once with RetryableWriteError from server' - ]; - return SKIP_TESTS.indexOf(spec.description) === -1; } @@ -158,7 +169,9 @@ describe('Transactions', function () { }); it('should provide a useful error if a Promise is not returned', { - metadata: { requires: { topology: ['replicaset', 'sharded'], mongodb: '>=4.1.5' } }, + metadata: { + requires: { topology: ['replicaset', 'sharded'], mongodb: '>=4.1.5', serverless: 'forbid' } + }, test: function (done) { function fnThatDoesntReturnPromise() { return false; @@ -173,7 +186,10 @@ describe('Transactions', function () { }); it('should return readable error if promise rejected with no reason', { - metadata: { requires: { topology: ['replicaset', 'sharded'], mongodb: '>=4.0.2' } }, + metadata: { + requires: { topology: ['replicaset', 'sharded'], mongodb: '>=4.0.2' }, + serverless: 'forbid' + }, test: function (done) { function fnThatReturnsBadPromise() { return Promise.reject(); diff --git a/test/functional/unified-spec-runner/entities.ts b/test/functional/unified-spec-runner/entities.ts index a7b3352a910..bf79ccf3947 100644 --- a/test/functional/unified-spec-runner/entities.ts +++ b/test/functional/unified-spec-runner/entities.ts @@ -4,8 +4,7 @@ import { Collection, GridFSBucket, Document, - HostAddress, - ServerApiVersion + HostAddress } from '../../../src/index'; import { ReadConcern } from '../../../src/read_concern'; import { WriteConcern } from '../../../src/write_concern'; @@ -33,6 +32,7 @@ import type { } from '../../../src/cmap/command_monitoring_events'; import { makeConnectionString, patchCollectionOptions, patchDbOptions } from './unified-utils'; import { expect } from 'chai'; +import { getEnvironmentalOptions } from '../../tools/utils'; import { TestConfiguration, trace } from './runner'; interface UnifiedChangeStream extends ChangeStream { @@ -52,15 +52,8 @@ export type CmapEvent = | ConnectionCheckedInEvent | ConnectionPoolClearedEvent; -function serverApiConfig() { - if (process.env.MONGODB_API_VERSION) { - return { version: process.env.MONGODB_API_VERSION as ServerApiVersion }; - } -} - function getClient(address) { - const serverApi = serverApiConfig(); - return new MongoClient(`mongodb://${address}`, serverApi ? { serverApi } : {}); + return new MongoClient(`mongodb://${address}`, getEnvironmentalOptions()); } type PushFunction = (e: CommandEvent | CmapEvent) => void; @@ -106,7 +99,9 @@ export class UnifiedMongoClient extends MongoClient { constructor(uri: string, description: ClientEntity) { super(uri, { monitorCommands: true, - serverApi: description.serverApi ? description.serverApi : serverApiConfig() + ...description.uriOptions, + ...getEnvironmentalOptions(), + ...(description.serverApi ? { serverApi: description.serverApi } : {}) }); this.commandEvents = []; @@ -116,6 +111,10 @@ export class UnifiedMongoClient extends MongoClient { ...(description.ignoreCommandMonitoringEvents ?? []), 'configureFailPoint' ]; + // FIXME(NODE-3549): hack to get tests passing, extra unexpected events otherwise + if (process.env.SERVERLESS) { + this.ignoredEvents.push('ping'); + } this.observedCommandEvents = (description.observeEvents ?? []) .map(e => UnifiedMongoClient.COMMAND_EVENT_NAME_LOOKUP[e]) .filter(e => !!e); diff --git a/test/functional/unified-spec-runner/runner.ts b/test/functional/unified-spec-runner/runner.ts index 33190c0f718..4a3ad3783a8 100644 --- a/test/functional/unified-spec-runner/runner.ts +++ b/test/functional/unified-spec-runner/runner.ts @@ -24,6 +24,10 @@ export function trace(message: string): void { } async function terminateOpenTransactions(client: MongoClient) { + // Note: killAllSession is not supported on serverless, see CLOUDP-84298 + if (process.env.SERVERLESS) { + return; + } // TODO(NODE-3491): on sharded clusters this has to be run on each mongos try { await client.db().admin().command({ killAllSessions: [] }); diff --git a/test/functional/unified-spec-runner/unified-utils.ts b/test/functional/unified-spec-runner/unified-utils.ts index 54c52ea734a..639ce9d5422 100644 --- a/test/functional/unified-spec-runner/unified-utils.ts +++ b/test/functional/unified-spec-runner/unified-utils.ts @@ -5,6 +5,7 @@ import { gte as semverGte, lte as semverLte } from 'semver'; import { CollectionOptions, DbOptions, MongoClient } from '../../../src'; import { isDeepStrictEqual } from 'util'; import { TestConfiguration } from './runner'; +import { shouldRunServerlessTest } from '../../tools/utils'; import ConnectionString from 'mongodb-connection-string-url'; const ENABLE_UNIFIED_TEST_LOGGING = false; @@ -61,6 +62,10 @@ export async function topologySatisfies( !!utilClient.options.authMechanism; } + if (r.serverless) { + ok &&= shouldRunServerlessTest(r.serverless, config.isServerless); + } + return ok; } diff --git a/test/tools/runner/config.js b/test/tools/runner/config.js index 91b3a5ee33b..b01ce842167 100644 --- a/test/tools/runner/config.js +++ b/test/tools/runner/config.js @@ -8,6 +8,7 @@ const { MongoClient } = require('../../../src/mongo_client'); const { Topology } = require('../../../src/sdam/topology'); const { TopologyType } = require('../../../src/sdam/common'); const { HostAddress } = require('../../../src/utils'); +const { getEnvironmentalOptions } = require('../utils'); /** * @typedef {Object} UrlOptions @@ -40,10 +41,10 @@ class TestConfiguration { const hostAddresses = hosts.map(HostAddress.fromString); this.version = context.version; this.clientSideEncryption = context.clientSideEncryption; - this.serverApi = context.serverApi; this.parameters = undefined; this.singleMongosLoadBalancerUri = context.singleMongosLoadBalancerUri; this.multiMongosLoadBalancerUri = context.multiMongosLoadBalancerUri; + this.isServerless = !!process.env.SERVERLESS; this.topologyType = this.isLoadBalanced ? TopologyType.LoadBalanced : context.topologyType; this.options = { hosts, @@ -60,6 +61,10 @@ class TestConfiguration { password: url.password }; } + if (context.serverlessCredentials) { + const { username, password } = context.serverlessCredentials; + this.options.auth = { username, password, authSource: 'admin' }; + } } get isLoadBalanced() { @@ -104,18 +109,18 @@ class TestConfiguration { } newClient(dbOptions, serverOptions) { - const defaultOptions = { minHeartbeatFrequencyMS: 100 }; - if (this.serverApi) { - Object.assign(defaultOptions, { serverApi: this.serverApi }); - } + serverOptions = Object.assign( + { minHeartbeatFrequencyMS: 100 }, + getEnvironmentalOptions(), + serverOptions + ); + // support MongoClient constructor form (url, options) for `newClient` if (typeof dbOptions === 'string') { - return new MongoClient(dbOptions, Object.assign(defaultOptions, serverOptions)); + return new MongoClient(dbOptions, serverOptions); } dbOptions = dbOptions || {}; - serverOptions = Object.assign({}, defaultOptions, serverOptions); - // Fall back let dbHost = (serverOptions && serverOptions.host) || this.options.host; const dbPort = (serverOptions && serverOptions.port) || this.options.port; @@ -231,6 +236,9 @@ class TestConfiguration { if (options.authSource) { url.searchParams.append('authSource', options.authSource); } + } else if (this.isServerless) { + url.searchParams.append('ssl', true); + url.searchParams.append('authSource', 'admin'); } let actualHostsString; diff --git a/test/tools/runner/filters/serverless_filter.js b/test/tools/runner/filters/serverless_filter.js new file mode 100755 index 00000000000..72288c8f576 --- /dev/null +++ b/test/tools/runner/filters/serverless_filter.js @@ -0,0 +1,37 @@ +'use strict'; +const { shouldRunServerlessTest } = require('../../utils'); + +/** + * Filter to allow to tests to run on serverless + * + * example: + * metadata: { + * requires: { + * serverless: 'forbid' + * } + * } + */ +class ServerlessFilter { + constructor() { + // Get environmental variables that are known + this.serverless = !!process.env.SERVERLESS; + } + + initializeFilter(client, context, callback) { + if (this.serverless) { + context.serverlessCredentials = { + username: process.env.SERVERLESS_ATLAS_USER, + password: process.env.SERVERLESS_ATLAS_PASSWORD + }; + } + callback(); + } + + filter(test) { + if (!test.metadata) return true; + if (!test.metadata.requires) return true; + return shouldRunServerlessTest(test.metadata.requires.serverless, this.serverless); + } +} + +module.exports = ServerlessFilter; diff --git a/test/tools/runner/index.js b/test/tools/runner/index.js index 4203d8c81a4..6db88ed1330 100644 --- a/test/tools/runner/index.js +++ b/test/tools/runner/index.js @@ -8,6 +8,7 @@ const path = require('path'); const fs = require('fs'); const { MongoClient } = require('../../../src'); const { TestConfiguration } = require('./config'); +const { getEnvironmentalOptions } = require('../utils'); const { eachAsync } = require('../../../src/utils'); const mock = require('../mock'); const wtfnode = require('wtfnode'); @@ -69,9 +70,11 @@ before(function (_done) { // )} topology` // ); - const options = MONGODB_API_VERSION ? { serverApi: MONGODB_API_VERSION } : {}; const loadBalanced = SINGLE_MONGOS_LB_URI && MULTI_MONGOS_LB_URI; - const client = new MongoClient(loadBalanced ? SINGLE_MONGOS_LB_URI : MONGODB_URI, options); + const client = new MongoClient( + loadBalanced ? SINGLE_MONGOS_LB_URI : MONGODB_URI, + getEnvironmentalOptions() + ); const done = err => client.close(err2 => _done(err || err2)); client.connect(err => { diff --git a/test/tools/utils.js b/test/tools/utils.js index 88930825be4..e03fee62f3d 100644 --- a/test/tools/utils.js +++ b/test/tools/utils.js @@ -258,6 +258,43 @@ function getSymbolFrom(target, symbolName, assertExists = true) { return symbol; } +function getEnvironmentalOptions() { + const options = {}; + if (process.env.MONGODB_API_VERSION) { + Object.assign(options, { + serverApi: { version: process.env.MONGODB_API_VERSION } + }); + } + if (process.env.SERVERLESS) { + Object.assign(options, { + auth: { + username: process.env.SERVERLESS_ATLAS_USER, + password: process.env.SERVERLESS_ATLAS_PASSWORD + }, + tls: true, + compressors: 'snappy,zlib' + }); + } + return options; +} + +function shouldRunServerlessTest(testRequirement, isServerless) { + if (!testRequirement) return true; + switch (testRequirement) { + case 'forbid': + // return true if the configuration is NOT serverless + return !isServerless; + case 'allow': + // always return true + return true; + case 'require': + // only return true if the configuration is serverless + return isServerless; + default: + throw new Error(`Invalid serverless filter: ${testRequirement}`); + } +} + module.exports = { EventCollector, makeTestFunction, @@ -266,5 +303,7 @@ module.exports = { ClassWithoutLogger, ClassWithUndefinedLogger, visualizeMonitoringEvents, - getSymbolFrom + getSymbolFrom, + getEnvironmentalOptions, + shouldRunServerlessTest };