From b3f3987041ddf0aae86044f3fdd1364eecd26d46 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Wed, 7 Aug 2024 10:52:01 -0600 Subject: [PATCH] feat(NODE-5614): add support for explicit resource management (#4177) Co-authored-by: Neal Beeken --- .eslintignore | 2 + .eslintrc.json | 3 +- .evergreen/config.in.yml | 27 ++++ .evergreen/config.yml | 114 ++++++++++++++++- .evergreen/generate_evergreen_tasks.js | 113 ++++++++++++----- .evergreen/install-dependencies.sh | 4 + ...resource-management-feature-integration.sh | 18 +++ .evergreen/run-resource-management.sh | 5 + .evergreen/run-typescript.sh | 8 +- api-extractor.json | 3 +- etc/clean_definition_files.cjs | 2 +- etc/docs/build.ts | 2 +- package-lock.json | 18 +-- package.json | 7 +- src/beta.ts | 22 ++++ src/change_stream.ts | 27 +++- src/cursor/abstract_cursor.ts | 26 +++- src/index.ts | 2 + src/mongo_client.ts | 26 +++- src/resource_management.ts | 74 +++++++++++ src/sessions.ts | 23 +++- test/explicit-resource-management/.gitignore | 2 + .../.mocharc.json | 12 ++ .../explicit-resource-management/main.test.ts | 119 ++++++++++++++++++ .../explicit-resource-management/package.json | 22 ++++ .../tsconfig.json | 47 +++++++ test/manual/resource_management.test.ts | 89 +++++++++++++ test/mongodb.ts | 1 + test/readme.md | 17 +-- test/unit/index.test.ts | 1 + 30 files changed, 757 insertions(+), 79 deletions(-) create mode 100644 .evergreen/run-resource-management-feature-integration.sh create mode 100644 .evergreen/run-resource-management.sh create mode 100644 src/beta.ts create mode 100644 src/resource_management.ts create mode 100644 test/explicit-resource-management/.gitignore create mode 100644 test/explicit-resource-management/.mocharc.json create mode 100644 test/explicit-resource-management/main.test.ts create mode 100644 test/explicit-resource-management/package.json create mode 100644 test/explicit-resource-management/tsconfig.json create mode 100644 test/manual/resource_management.test.ts diff --git a/.eslintignore b/.eslintignore index 39a231fa1f..4689356e00 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,5 @@ lib test/disabled !etc/docs + +test/explicit-resource-management diff --git a/.eslintrc.json b/.eslintrc.json index 8f19ffd909..e959e07814 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -271,7 +271,8 @@ { // Settings for generated definition files "files": [ - "mongodb.d.ts" + "**/*.d.ts", + "lib/*.d.ts" ], "parser": "@typescript-eslint/parser", "rules": { diff --git a/.evergreen/config.in.yml b/.evergreen/config.in.yml index b4d1c00ac7..85c963ad7a 100644 --- a/.evergreen/config.in.yml +++ b/.evergreen/config.in.yml @@ -348,10 +348,36 @@ functions: PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} TS_VERSION: ${TS_VERSION} TS_CHECK: CHECK_TYPES + TYPES_VERSION: ${TYPES_VERSION} binary: bash args: - "${PROJECT_DIRECTORY}/.evergreen/run-typescript.sh" + "check resource management": + - command: subprocess.exec + type: test + params: + working_dir: "src" + timeout_secs: 60 + env: + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + binary: bash + args: + - "${PROJECT_DIRECTORY}/.evergreen/run-resource-management.sh" + + "check resource management feature integration": + - command: subprocess.exec + type: test + params: + working_dir: "src" + timeout_secs: 60 + env: + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + MONGODB_URI: ${MONGODB_URI} + binary: bash + args: + - "${PROJECT_DIRECTORY}/.evergreen/run-resource-management-feature-integration.sh" + "compile driver": - command: subprocess.exec type: test @@ -362,6 +388,7 @@ functions: PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} TS_VERSION: ${TS_VERSION} TS_CHECK: COMPILE_DRIVER + TYPES_VERSION: ${TYPES_VERSION} binary: bash args: - "${PROJECT_DIRECTORY}/.evergreen/run-typescript.sh" diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 184828452b..570435d3e2 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -312,9 +312,33 @@ functions: PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} TS_VERSION: ${TS_VERSION} TS_CHECK: CHECK_TYPES + TYPES_VERSION: ${TYPES_VERSION} binary: bash args: - ${PROJECT_DIRECTORY}/.evergreen/run-typescript.sh + check resource management: + - command: subprocess.exec + type: test + params: + working_dir: src + timeout_secs: 60 + env: + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + binary: bash + args: + - ${PROJECT_DIRECTORY}/.evergreen/run-resource-management.sh + check resource management feature integration: + - command: subprocess.exec + type: test + params: + working_dir: src + timeout_secs: 60 + env: + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + MONGODB_URI: ${MONGODB_URI} + binary: bash + args: + - ${PROJECT_DIRECTORY}/.evergreen/run-resource-management-feature-integration.sh compile driver: - command: subprocess.exec type: test @@ -325,6 +349,7 @@ functions: PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} TS_VERSION: ${TS_VERSION} TS_CHECK: COMPILE_DRIVER + TYPES_VERSION: ${TYPES_VERSION} binary: bash args: - ${PROJECT_DIRECTORY}/.evergreen/run-typescript.sh @@ -3459,7 +3484,45 @@ tasks: - {key: NPM_VERSION, value: '9'} - func: install dependencies - func: run lint checks - - name: check-types-typescript-next + - name: run-resource-management-no-async-dispose + tags: + - resource-management + commands: + - command: expansions.update + type: setup + params: + updates: + - {key: NODE_LTS_VERSION, value: v16.20.2} + - {key: NPM_VERSION, value: '9'} + - func: install dependencies + - func: check resource management + - name: run-resource-management-async-dispose + tags: + - resource-management + commands: + - command: expansions.update + type: setup + params: + updates: + - {key: NODE_LTS_VERSION, value: latest} + - {key: NPM_VERSION, value: '9'} + - func: install dependencies + - func: check resource management + - name: test-explicit-resource-management-feature-integration + tags: + - resource-management + commands: + - command: expansions.update + type: setup + params: + updates: + - {key: VERSION, value: latest} + - {key: TOPOLOGY, value: replica_set} + - {key: NODE_LTS_VERSION, value: latest} + - func: install dependencies + - func: bootstrap mongo-orchestration + - func: check resource management feature integration + - name: check-types-typescript-next-node-types-20.14.10 tags: - check-types-typescript-next - typescript-compilation @@ -3471,11 +3534,12 @@ tasks: - {key: NODE_LTS_VERSION, value: '16'} - {key: NPM_VERSION, value: '9'} - {key: TS_VERSION, value: next} + - {key: TYPES_VERSION, value: 20.14.10} - func: install dependencies - func: check types - - name: compile-driver-typescript-current + - name: check-types-typescript-current-node-types-20.14.10 tags: - - compile-driver-typescript-current + - check-types-typescript-current - typescript-compilation commands: - command: expansions.update @@ -3485,9 +3549,25 @@ tasks: - {key: NODE_LTS_VERSION, value: '16'} - {key: NPM_VERSION, value: '9'} - {key: TS_VERSION, value: current} + - {key: TYPES_VERSION, value: 20.14.10} - func: install dependencies - - func: compile driver - - name: check-types-typescript-current + - func: check types + - name: check-types-typescript-next-node-types-16.x + tags: + - check-types-typescript-next + - typescript-compilation + commands: + - command: expansions.update + type: setup + params: + updates: + - {key: NODE_LTS_VERSION, value: '16'} + - {key: NPM_VERSION, value: '9'} + - {key: TS_VERSION, value: next} + - {key: TYPES_VERSION, value: 16.x} + - func: install dependencies + - func: check types + - name: check-types-typescript-current-node-types-16.x tags: - check-types-typescript-current - typescript-compilation @@ -3499,9 +3579,10 @@ tasks: - {key: NODE_LTS_VERSION, value: '16'} - {key: NPM_VERSION, value: '9'} - {key: TS_VERSION, value: current} + - {key: TYPES_VERSION, value: 16.x} - func: install dependencies - func: check types - - name: check-types-typescript-4.4 + - name: check-types-typescript-4.4-node-types-18.11.9 tags: - check-types-typescript-4.4 - typescript-compilation @@ -3513,8 +3594,24 @@ tasks: - {key: NODE_LTS_VERSION, value: '16'} - {key: NPM_VERSION, value: '9'} - {key: TS_VERSION, value: '4.4'} + - {key: TYPES_VERSION, value: 18.11.9} - func: install dependencies - func: check types + - name: compile-driver-typescript-current-node-types-20.14.10 + tags: + - compile-driver-typescript-current + - typescript-compilation + commands: + - command: expansions.update + type: setup + params: + updates: + - {key: NODE_LTS_VERSION, value: '16'} + - {key: NPM_VERSION, value: '9'} + - {key: TS_VERSION, value: current} + - {key: TYPES_VERSION, value: 20.14.10} + - func: install dependencies + - func: compile driver - name: download-and-merge-coverage tags: [] commands: @@ -5171,3 +5268,8 @@ buildvariants: run_on: rhel80-large tasks: - test_atlas_task_group_search_indexes + - name: resource management tests + display_name: resource management tests + run_on: rhel80-large + tasks: + - .resource-management diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 5d2d2b366c..52d5d2124e 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -492,56 +492,93 @@ SINGLETON_TASKS.push( { func: 'run lint checks' } ] }, + { + name: 'run-resource-management-no-async-dispose', + tags: ['resource-management'], + commands: [ + updateExpansions({ + NODE_LTS_VERSION: "v16.20.2", + NPM_VERSION: 9 + }), + { func: 'install dependencies' }, + { func: 'check resource management' } + ] + }, + { + name: 'run-resource-management-async-dispose', + tags: ['resource-management'], + commands: [ + updateExpansions({ + NODE_LTS_VERSION: 'latest', + NPM_VERSION: 9 + }), + { func: 'install dependencies' }, + { func: 'check resource management' } + ] + }, + { + name: 'test-explicit-resource-management-feature-integration', + tags: ['resource-management'], + commands: [ + updateExpansions({ + VERSION: 'latest', + TOPOLOGY: 'replica_set', + NODE_LTS_VERSION: 'latest' + }), + { func: 'install dependencies' }, + { func: 'bootstrap mongo-orchestration' }, + { func: 'check resource management feature integration' } + ] + }, ...Array.from(makeTypescriptTasks()) ] ); function* makeTypescriptTasks() { - for (const TS_VERSION of ['next', 'current', '4.4']) { - // We don't compile on next, because compilation errors are likely. We do expect - // that the drivers types continue to work with next though. - if (TS_VERSION !== '4.4' && TS_VERSION !== 'next') { - yield { - name: `compile-driver-typescript-${TS_VERSION}`, - tags: [`compile-driver-typescript-${TS_VERSION}`, 'typescript-compilation'], - commands: [ - updateExpansions({ - NODE_LTS_VERSION: LOWEST_LTS, - NPM_VERSION: 9, - TS_VERSION - }), - { func: 'install dependencies' }, - { func: 'compile driver' } - ] - }; + function makeCompileTask(TS_VERSION, TYPES_VERSION) { + return { + name: `compile-driver-typescript-${TS_VERSION}-node-types-${TYPES_VERSION}`, + tags: [`compile-driver-typescript-${TS_VERSION}`, 'typescript-compilation'], + commands: [ + updateExpansions({ + NODE_LTS_VERSION: LOWEST_LTS, + NPM_VERSION: 9, + TS_VERSION, + TYPES_VERSION + }), + { func: 'install dependencies' }, + { func: 'compile driver' } + ] } - - yield { - name: `check-types-typescript-${TS_VERSION}`, + } + function makeCheckTypesTask(TS_VERSION, TYPES_VERSION) { + return { + name: `check-types-typescript-${TS_VERSION}-node-types-${TYPES_VERSION}`, tags: [`check-types-typescript-${TS_VERSION}`, 'typescript-compilation'], commands: [ updateExpansions({ NODE_LTS_VERSION: LOWEST_LTS, NPM_VERSION: 9, - TS_VERSION + TS_VERSION, + TYPES_VERSION }), { func: 'install dependencies' }, { func: 'check types' } ] - }; + } } - return { - name: 'run-typescript-next', - tags: ['run-typescript-next', 'typescript-compilation'], - commands: [ - updateExpansions({ - NODE_LTS_VERSION: LOWEST_LTS, - NPM_VERSION: 9 - }), - { func: 'install dependencies' }, - { func: 'run typescript next' } - ] - }; + + const typesVersion = require('../package.json').devDependencies['@types/node'].slice(1) + yield makeCheckTypesTask('next', typesVersion); + yield makeCheckTypesTask('current', typesVersion); + + yield makeCheckTypesTask('next', '16.x'); + yield makeCheckTypesTask('current', '16.x'); + + // typescript 4.4 only compiles our types with this particular version + yield makeCheckTypesTask('4.4', '18.11.9'); + + yield makeCompileTask('current', typesVersion); } BUILD_VARIANTS.push({ @@ -731,6 +768,13 @@ BUILD_VARIANTS.push({ tasks: ['test_atlas_task_group_search_indexes'] }); +BUILD_VARIANTS.push({ + name: 'resource management tests', + display_name: 'resource management tests', + run_on: DEFAULT_OS, + tasks: ['.resource-management'] +}); + // TODO(NODE-4575): unskip zstd and snappy on node 16 for (const variant of BUILD_VARIANTS.filter( variant => variant.expansions && [16, 18, 20].includes(variant.expansions.NODE_LTS_VERSION) @@ -755,6 +799,7 @@ fileData.tasks = (fileData.tasks || []) .concat(AUTH_DISABLED_TASKS) .concat(AWS_LAMBDA_HANDLER_TASKS) .concat(MONGOCRYPTD_CSFLE_TASKS); + fileData.buildvariants = (fileData.buildvariants || []).concat(BUILD_VARIANTS); fs.writeFileSync( diff --git a/.evergreen/install-dependencies.sh b/.evergreen/install-dependencies.sh index 54724f1607..515722b22b 100644 --- a/.evergreen/install-dependencies.sh +++ b/.evergreen/install-dependencies.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash set -o errexit # Exit the script with error if any of the commands fail +# allowed values: +## a nodejs major version (i.e., 16) +## 'latest' +## a full nodejs version, in the format v..patch NODE_LTS_VERSION=${NODE_LTS_VERSION:-16} # npm version can be defined in the environment for cases where we need to install # a version lower than latest to support EOL Node versions. diff --git a/.evergreen/run-resource-management-feature-integration.sh b/.evergreen/run-resource-management-feature-integration.sh new file mode 100644 index 0000000000..70854dae1d --- /dev/null +++ b/.evergreen/run-resource-management-feature-integration.sh @@ -0,0 +1,18 @@ +#! /bin/bash + +source "${PROJECT_DIRECTORY}/.evergreen/init-node-and-npm-env.sh" + +echo "Building driver..." +npm pack +echo "Building driver...finished." + +PACKAGE_FILE=$(ls mongodb-*.tgz) + +mv $PACKAGE_FILE mongodb-current.tgz + +echo "Node version: $(node -v)" +cd test/explicit-resource-management + +npm i +npm t +mv xunit.xml ../.. diff --git a/.evergreen/run-resource-management.sh b/.evergreen/run-resource-management.sh new file mode 100644 index 0000000000..ff7737b1c5 --- /dev/null +++ b/.evergreen/run-resource-management.sh @@ -0,0 +1,5 @@ +#! /bin/bash + +source "${PROJECT_DIRECTORY}/.evergreen/init-node-and-npm-env.sh" + +npm run check:resource-management diff --git a/.evergreen/run-typescript.sh b/.evergreen/run-typescript.sh index 87d47a559b..9bd212eb70 100755 --- a/.evergreen/run-typescript.sh +++ b/.evergreen/run-typescript.sh @@ -3,8 +3,6 @@ set -o errexit # Exit the script with error if any of the commands fail source "${PROJECT_DIRECTORY}/.evergreen/init-node-and-npm-env.sh" -set -o xtrace - case $TS_CHECK in COMPILE_DRIVER|CHECK_TYPES) # Ok ;; @@ -14,6 +12,7 @@ case $TS_CHECK in esac if [ -z "$TS_VERSION" ]; then echo "TS_VERSION must be set"; exit 1; fi +if [ -z "$TYPES_VERSION" ]; then echo "TYPES_VERSION must be set"; exit 1; fi if [ ! -f "mongodb.d.ts" ]; then echo "mongodb.d.ts should always exist because of the installation in prior steps but in case it doesn't, build it" @@ -31,10 +30,11 @@ function get_ts_version() { export TSC="./node_modules/typescript/bin/tsc" export TS_VERSION=$(get_ts_version) -# On old versions of TS we need to put the node types back to 18.11.19 -npm install --no-save --force typescript@"$TS_VERSION" "$(if [[ $TS_VERSION == '4.4' ]]; then echo "@types/node@18.11.19"; else echo ""; fi)" +npm install --no-save --force "typescript@$TS_VERSION" "@types/node@$TYPES_VERSION" echo "Typescript $($TSC -v)" +echo "Types: $(cat node_modules/@types/node/package.json | jq -r .version)" +echo "Nodejs: $(node -v)" # check resolution uses the default latest types echo "import * as mdb from '.'" > file.ts && node $TSC --noEmit --traceResolution file.ts | grep 'mongodb.d.ts' && rm file.ts diff --git a/api-extractor.json b/api-extractor.json index bda873c16b..bbf8b9d173 100644 --- a/api-extractor.json +++ b/api-extractor.json @@ -10,7 +10,8 @@ "dtsRollup": { "enabled": true, "untrimmedFilePath": "", - "publicTrimmedFilePath": "/.d.ts" + "publicTrimmedFilePath": "/.d.ts", + "betaTrimmedFilePath": "/lib/beta.d.ts" }, "tsdocMetadata": { "enabled": false diff --git a/etc/clean_definition_files.cjs b/etc/clean_definition_files.cjs index 1beb65d78f..3e5f9b7569 100755 --- a/etc/clean_definition_files.cjs +++ b/etc/clean_definition_files.cjs @@ -21,7 +21,7 @@ if (fs.existsSync(libPath)) { const definitionFiles = Array.from(walk(libPath)).filter(filePath => { return filePath.endsWith('.d.ts') || filePath.endsWith('.d.ts.map'); }); - for (const definitionFile of definitionFiles) { + for (const definitionFile of definitionFiles.filter(file => !file.endsWith('beta.d.ts'))) { fs.unlinkSync(definitionFile); } } diff --git a/etc/docs/build.ts b/etc/docs/build.ts index b0018499cc..65b744eac9 100755 --- a/etc/docs/build.ts +++ b/etc/docs/build.ts @@ -24,7 +24,7 @@ const RELEASES_JSON_FILE = './template/static/versions.json'; const copyGeneratedDocsToDocsFolder = () => exec(`cp -R temp/. ../../docs/.`); const removeTempDirectory = () => exec('rm -rf temp'); -const installDependencies = () => exec('npm i --no-save --legacy-peer-deps typedoc@0.24.8'); +const installDependencies = () => exec('npm i --no-save --legacy-peer-deps typedoc@0.26.5'); const buildDocs = ({ tag }: VersionSchema) => { const revision = tag === LATEST_TAG ? 'main' : `v${tag}.0`; return exec(`npm run build:typedoc -- --gitRevision ${revision}`); diff --git a/package-lock.json b/package-lock.json index 0fa708e66c..643f351d51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@types/express": "^4.17.21", "@types/kerberos": "^1.1.5", "@types/mocha": "^10.0.6", - "@types/node": "^20.12.7", + "@types/node": "^20.14.10", "@types/saslprep": "^1.0.3", "@types/semver": "^7.5.8", "@types/sinon": "^17.0.3", @@ -62,7 +62,7 @@ "source-map-support": "^0.5.21", "ts-node": "^10.9.2", "tsd": "^0.31.0", - "typescript": "5.0", + "typescript": "5.5", "typescript-cached-transpile": "^0.0.6", "v8-heapsnapshot": "^1.3.1", "yargs": "^17.7.2" @@ -2966,9 +2966,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.12.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", - "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "version": "20.14.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz", + "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -10218,16 +10218,16 @@ } }, "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" } }, "node_modules/typescript-cached-transpile": { diff --git a/package.json b/package.json index 989f2ce003..2ed27b2ccf 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@types/express": "^4.17.21", "@types/kerberos": "^1.1.5", "@types/mocha": "^10.0.6", - "@types/node": "^20.12.7", + "@types/node": "^20.14.10", "@types/saslprep": "^1.0.3", "@types/semver": "^7.5.8", "@types/sinon": "^17.0.3", @@ -110,7 +110,7 @@ "source-map-support": "^0.5.21", "ts-node": "^10.9.2", "tsd": "^0.31.0", - "typescript": "5.0", + "typescript": "5.5", "typescript-cached-transpile": "^0.0.6", "v8-heapsnapshot": "^1.3.1", "yargs": "^17.7.2" @@ -126,7 +126,7 @@ "scripts": { "build:evergreen": "node .evergreen/generate_evergreen_tasks.js", "build:ts": "node ./node_modules/typescript/bin/tsc", - "build:dts": "npm run build:ts && api-extractor run && node etc/clean_definition_files.cjs && eslint mongodb.d.ts --fix", + "build:dts": "npm run build:ts && api-extractor run && node etc/clean_definition_files.cjs && eslint --no-ignore --fix mongodb.d.ts lib/beta.d.ts", "build:docs": "./etc/docs/build.ts", "build:typedoc": "typedoc", "build:nightly": "node ./.github/scripts/nightly.mjs", @@ -145,6 +145,7 @@ "check:unit": "mocha test/unit", "check:ts": "node ./node_modules/typescript/bin/tsc -v && node ./node_modules/typescript/bin/tsc --noEmit", "check:atlas": "mocha --config test/manual/mocharc.json test/manual/atlas_connectivity.test.ts", + "check:resource-management": "mocha --config test/manual/mocharc.json test/manual/resource_management.test.ts", "check:drivers-atlas-testing": "mocha --config test/mocha_mongodb.json test/atlas/drivers_atlas_testing.test.ts", "check:adl": "mocha --config test/mocha_mongodb.json test/manual/atlas-data-lake-testing", "check:aws": "nyc mocha --config test/mocha_mongodb.json test/integration/auth/mongodb_aws.test.ts", diff --git a/src/beta.ts b/src/beta.ts new file mode 100644 index 0000000000..9d068328d4 --- /dev/null +++ b/src/beta.ts @@ -0,0 +1,22 @@ +import { type Document } from 'bson'; + +export * from './index'; + +/** + * @internal + * + * Since we don't bundle tslib helpers, we need to polyfill this method. + * + * This is used in the generated JS. Adapted from https://github.com/microsoft/TypeScript/blob/aafdfe5b3f76f5c41abeec412ce73c86da94c75f/src/compiler/factory/emitHelpers.ts#L1202. + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function __exportStar(mod: Document) { + for (const key of Object.keys(mod)) { + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return mod[key]; + } + }); + } +} diff --git a/src/change_stream.ts b/src/change_stream.ts index 4e46d6c013..edba6fbbba 100644 --- a/src/change_stream.ts +++ b/src/change_stream.ts @@ -18,6 +18,7 @@ import { type InferIdType, TypedEventEmitter } from './mongo_types'; import type { AggregateOptions } from './operations/aggregate'; import type { CollationOptions, OperationParent } from './operations/command'; import type { ReadPreference } from './read_preference'; +import { type AsyncDisposable, configureResourceManagement } from './resource_management'; import type { ServerSessionId } from './sessions'; import { filterOptions, getTopology, type MongoDBNamespace, squashError } from './utils'; @@ -544,9 +545,23 @@ export type ChangeStreamEvents< * @public */ export class ChangeStream< - TSchema extends Document = Document, - TChange extends Document = ChangeStreamDocument -> extends TypedEventEmitter> { + TSchema extends Document = Document, + TChange extends Document = ChangeStreamDocument + > + extends TypedEventEmitter> + implements AsyncDisposable +{ + /** + * @beta + * @experimental + * An alias for {@link ChangeStream.close|ChangeStream.close()}. + */ + declare [Symbol.asyncDispose]: () => Promise; + /** @internal */ + async asyncDispose() { + await this.close(); + } + pipeline: Document[]; /** * @remarks WriteConcern can still be present on the options because @@ -765,7 +780,9 @@ export class ChangeStream< return this[kClosed] || this.cursor.closed; } - /** Close the Change Stream */ + /** + * Frees the internal resources used by the change stream. + */ async close(): Promise { this[kClosed] = true; @@ -986,3 +1003,5 @@ export class ChangeStream< } } } + +configureResourceManagement(ChangeStream.prototype); diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index da08f1a1a6..1d69701916 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -17,6 +17,7 @@ import { GetMoreOperation } from '../operations/get_more'; import { KillCursorsOperation } from '../operations/kill_cursors'; import { ReadConcern, type ReadConcernLike } from '../read_concern'; import { ReadPreference, type ReadPreferenceLike } from '../read_preference'; +import { type AsyncDisposable, configureResourceManagement } from '../resource_management'; import type { Server } from '../sdam/server'; import { ClientSession, maybeClearPinnedConnection } from '../sessions'; import { type MongoDBNamespace, squashError } from '../utils'; @@ -124,9 +125,12 @@ export type AbstractCursorEvents = { /** @public */ export abstract class AbstractCursor< - TSchema = any, - CursorEvents extends AbstractCursorEvents = AbstractCursorEvents -> extends TypedEventEmitter { + TSchema = any, + CursorEvents extends AbstractCursorEvents = AbstractCursorEvents + > + extends TypedEventEmitter + implements AsyncDisposable +{ /** @internal */ private cursorId: Long | null; /** @internal */ @@ -275,6 +279,17 @@ export abstract class AbstractCursor< return !!this.cursorClient.topology?.loadBalanced; } + /** + * @beta + * @experimental + * An alias for {@link AbstractCursor.close|AbstractCursor.close()}. + */ + declare [Symbol.asyncDispose]: () => Promise; + /** @internal */ + async asyncDispose() { + await this.close(); + } + /** Returns current buffered documents length */ bufferedCount(): number { return this.documents?.length ?? 0; @@ -446,6 +461,9 @@ export abstract class AbstractCursor< } } + /** + * Frees any client-side resources used by the cursor. + */ async close(): Promise { await this.cleanup(); } @@ -916,3 +934,5 @@ class ReadableCursorStream extends Readable { ); } } + +configureResourceManagement(AbstractCursor.prototype); diff --git a/src/index.ts b/src/index.ts index efd0b9d055..19b2359379 100644 --- a/src/index.ts +++ b/src/index.ts @@ -76,6 +76,7 @@ export { MongoWriteConcernError, WriteConcernErrorResult } from './error'; +export { configureExplicitResourceManagement } from './resource_management'; export { AbstractCursor, // Actual driver classes exported @@ -522,6 +523,7 @@ export type { ReadPreferenceLikeOptions, ReadPreferenceOptions } from './read_preference'; +export type { AsyncDisposable } from './resource_management'; export type { ClusterTime, TimerQueue } from './sdam/common'; export type { Monitor, diff --git a/src/mongo_client.ts b/src/mongo_client.ts index b5cf49deb5..db16e556c1 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -34,6 +34,7 @@ import { executeOperation } from './operations/execute_operation'; import { RunAdminCommandOperation } from './operations/run_command'; import type { ReadConcern, ReadConcernLevel, ReadConcernLike } from './read_concern'; import { ReadPreference, type ReadPreferenceMode } from './read_preference'; +import { type AsyncDisposable, configureResourceManagement } from './resource_management'; import type { ServerMonitoringMode } from './sdam/monitor'; import type { TagSet } from './sdam/server_description'; import { readPreferenceServerSelector } from './sdam/server_selection'; @@ -344,7 +345,7 @@ const kOptions = Symbol('options'); * await client.insertOne({ name: 'spot', kind: 'dog' }); * ``` */ -export class MongoClient extends TypedEventEmitter { +export class MongoClient extends TypedEventEmitter implements AsyncDisposable { /** @internal */ s: MongoClientPrivate; /** @internal */ @@ -404,6 +405,17 @@ export class MongoClient extends TypedEventEmitter { this.checkForNonGenuineHosts(); } + /** + * @beta + * @experimental + * An alias for {@link MongoClient.close|MongoClient.close()}. + */ + declare [Symbol.asyncDispose]: () => Promise; + /** @internal */ + async asyncDispose() { + await this.close(); + } + /** @internal */ private checkForNonGenuineHosts() { const documentDBHostnames = this[kOptions].hosts.filter((hostAddress: HostAddress) => @@ -570,7 +582,15 @@ export class MongoClient extends TypedEventEmitter { } /** - * Close the client and its underlying connections + * Cleans up client-side resources used by the MongoCLient and . This includes: + * + * - Closes all open, unused connections (see note). + * - Ends all in-use sessions with {@link ClientSession#endSession|ClientSession.endSession()}. + * - Ends all unused sessions server-side. + * - Cleans up any resources being used for auto encryption if auto encryption is enabled. + * + * @remarks Any in-progress operations are not killed and any connections used by in progress operations + * will be cleaned up lazily as operations finish. * * @param force - Force close, emitting no events */ @@ -758,6 +778,8 @@ export class MongoClient extends TypedEventEmitter { } } +configureResourceManagement(MongoClient.prototype); + /** * Parsed Mongo Client Options. * diff --git a/src/resource_management.ts b/src/resource_management.ts new file mode 100644 index 0000000000..7e33f81334 --- /dev/null +++ b/src/resource_management.ts @@ -0,0 +1,74 @@ +/** + * @public + */ +export interface AsyncDisposable { + /** + * @beta + * @experimental + */ + [Symbol.asyncDispose](): Promise; + + /** + * @internal + * + * A method that wraps disposal semantics for a given resource in the class. + */ + asyncDispose(): Promise; +} + +/** @internal */ +export function configureResourceManagement(target: AsyncDisposable) { + Symbol.asyncDispose && + Object.defineProperty(target, Symbol.asyncDispose, { + value: async function asyncDispose(this: AsyncDisposable) { + await this.asyncDispose(); + }, + enumerable: false, + configurable: true, + writable: true + }); +} + +/** + * @beta + * @experimental + * + * Attaches `Symbol.asyncDispose` methods to the MongoClient, Cursors, sessions and change streams + * if Symbol.asyncDispose is defined. + * + * It's usually not necessary to call this method - the driver attempts to attach these methods + * itself when its loaded. However, sometimes the driver may be loaded before `Symbol.asyncDispose` + * is defined, in which case it is necessary to call this method directly. This can happen if the + * application is polyfilling `Symbol.asyncDispose`. + * + * Example: + * + * ```typescript + * import { configureExplicitResourceManagement, MongoClient } from 'mongodb/lib/beta'; + * + * Symbol.asyncDispose ??= Symbol('dispose'); + * load(); + * + * await using client = new MongoClient(...); + * ``` + */ +export function configureExplicitResourceManagement() { + // We must import lazily here, because there's a circular dependency between the resource management + // file and each resources' file. We could move `configureResourceManagement` to a separate + // function, but keeping all resource-management related code together seemed preferable and I chose + // lazy requiring of resources instead. + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { MongoClient } = require('./mongo_client'); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { ClientSession } = require('./sessions'); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { AbstractCursor } = require('./cursor/abstract_cursor'); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { ChangeStream } = require('./change_stream'); + + configureResourceManagement(MongoClient.prototype); + configureResourceManagement(ClientSession.prototype); + configureResourceManagement(AbstractCursor.prototype); + configureResourceManagement(ChangeStream.prototype); +} diff --git a/src/sessions.ts b/src/sessions.ts index 544bef897c..bd10e7e07c 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -27,6 +27,7 @@ import { executeOperation } from './operations/execute_operation'; import { RunAdminCommandOperation } from './operations/run_command'; import { ReadConcernLevel } from './read_concern'; import { ReadPreference } from './read_preference'; +import { type AsyncDisposable, configureResourceManagement } from './resource_management'; import { _advanceClusterTime, type ClusterTime, TopologyType } from './sdam/common'; import { isTransactionCommand, @@ -105,7 +106,10 @@ export interface EndSessionOptions { * NOTE: not meant to be instantiated directly. * @public */ -export class ClientSession extends TypedEventEmitter { +export class ClientSession + extends TypedEventEmitter + implements AsyncDisposable +{ /** @internal */ client: MongoClient; /** @internal */ @@ -255,7 +259,10 @@ export class ClientSession extends TypedEventEmitter { } /** - * Ends this session on the server + * Frees any client-side resources held by the current session. If a session is in a transaction, + * the transaction is aborted. + * + * Does not end the session on the server. * * @param options - Optional settings. Currently reserved for future use */ @@ -286,6 +293,16 @@ export class ClientSession extends TypedEventEmitter { maybeClearPinnedConnection(this, { force: true, ...options }); } } + /** + * @beta + * @experimental + * An alias for {@link ClientSession.endSession|ClientSession.endSession()}. + */ + declare [Symbol.asyncDispose]: () => Promise; + /** @internal */ + async asyncDispose() { + await this.endSession({ force: true }); + } /** * Advances the operationTime for a ClientSession. @@ -484,6 +501,8 @@ export class ClientSession extends TypedEventEmitter { } } +configureResourceManagement(ClientSession.prototype); + const MAX_WITH_TRANSACTION_TIMEOUT = 120000; const NON_DETERMINISTIC_WRITE_CONCERN_ERRORS = new Set([ 'CannotSatisfyWriteConcern', diff --git a/test/explicit-resource-management/.gitignore b/test/explicit-resource-management/.gitignore new file mode 100644 index 0000000000..b76821427a --- /dev/null +++ b/test/explicit-resource-management/.gitignore @@ -0,0 +1,2 @@ +*.js +package-lock.json diff --git a/test/explicit-resource-management/.mocharc.json b/test/explicit-resource-management/.mocharc.json new file mode 100644 index 0000000000..1d1c61fb5f --- /dev/null +++ b/test/explicit-resource-management/.mocharc.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/mocharc.json", + "extension": [ + "js" + ], + "reporter": "../tools/reporter/mongodb_reporter.js", + "recursive": true, + "timeout": 60000, + "failZero": true, + "sort": true, + "color": true +} diff --git a/test/explicit-resource-management/main.test.ts b/test/explicit-resource-management/main.test.ts new file mode 100644 index 0000000000..0ba824ad86 --- /dev/null +++ b/test/explicit-resource-management/main.test.ts @@ -0,0 +1,119 @@ + +import { describe, it } from 'mocha'; +import { AbstractCursor, ChangeStream, ClientSession, GridFSBucket, MongoClient } from 'mongodb/lib/beta'; +import { Readable } from 'stream'; +import { pipeline } from 'stream/promises'; +import { expect } from 'chai'; +import { setTimeout } from 'timers/promises'; +import { createReadStream } from 'fs'; +import { join } from 'path'; + +import * as sinon from 'sinon'; + +async function setUpCollection(client: MongoClient) { + const collection = client.db('foo').collection<{ name: string }>('bar'); + const documents: Array<{ name: string }> = Array.from({ length: 5 }).map(i => ({ + name: String(i) + })); + await collection.insertMany(documents) + return collection; +} + +describe('explicit resource management feature integration tests', function () { + const clientDisposeSpy = sinon.spy(MongoClient.prototype, Symbol.asyncDispose); + const sessionDisposeSpy = sinon.spy(ClientSession.prototype, Symbol.asyncDispose); + const changeStreamDisposeSpy = sinon.spy(ChangeStream.prototype, Symbol.asyncDispose); + const cursorDisposeSpy = sinon.spy(AbstractCursor.prototype, Symbol.asyncDispose); + const readableDisposeSpy = sinon.spy(Readable.prototype, Symbol.asyncDispose); + + afterEach(function(){ + clientDisposeSpy.resetHistory(); + sessionDisposeSpy.resetHistory(); + changeStreamDisposeSpy.resetHistory(); + cursorDisposeSpy.resetHistory(); + readableDisposeSpy.resetHistory(); + }) + describe('MongoClient', function () { + it('does not crash or error when used with await-using syntax', async function () { + { + await using client = new MongoClient(process.env.MONGODB_URI!); + await client.connect(); + } + expect(clientDisposeSpy.called).to.be.true; + }) + }) + + describe('Cursors', function () { + it('does not crash or error when used with await-using syntax', async function () { + { + await using client = new MongoClient(process.env.MONGODB_URI!); + await client.connect(); + + const collection = await setUpCollection(client); + + await using cursor = collection.find(); + await cursor.next(); + } + expect(cursorDisposeSpy.called).to.be.true; + }) + + describe('cursor streams', function () { + it('does not crash or error when used with await-using syntax', async function () { + { + await using client = new MongoClient(process.env.MONGODB_URI!); + await client.connect(); + + const collection = await setUpCollection(client); + + await using readable = collection.find().stream(); + } + expect(readableDisposeSpy.called).to.be.true; + }) + }) + }) + + describe('Sessions', function () { + it('does not crash or error when used with await-using syntax', async function () { + { + await using client = new MongoClient(process.env.MONGODB_URI!); + await client.connect(); + + await using session = client.startSession(); + } + expect(sessionDisposeSpy.called).to.be.true; + }) + }) + + describe('ChangeStreams', function () { + it('does not crash or error when used with await-using syntax', async function () { + { + await using client = new MongoClient(process.env.MONGODB_URI!); + await client.connect(); + + const collection = await setUpCollection(client); + await using cs = collection.watch(); + + setTimeout(1000).then(() => collection.insertOne({ name: 'bailey' })); + await cs.next(); + } + expect(changeStreamDisposeSpy.called).to.be.true; + }) + }); + + describe('GridFSDownloadStream', function () { + it('does not crash or error when used with await-using syntax', async function () { + { + await using client = new MongoClient(process.env.MONGODB_URI!); + await client.connect(); + + const bucket = new GridFSBucket(client.db('foo')); + const uploadStream = bucket.openUploadStream('foo.txt') + await pipeline(Readable.from("AAAAAAA".split('')), uploadStream); + + await using downloadStream = bucket.openDownloadStreamByName('foo.txt'); + } + + expect(readableDisposeSpy.called).to.be.true; + }) + }); +}) diff --git a/test/explicit-resource-management/package.json b/test/explicit-resource-management/package.json new file mode 100644 index 0000000000..adc23fb310 --- /dev/null +++ b/test/explicit-resource-management/package.json @@ -0,0 +1,22 @@ +{ + "name": "explicit-resource-management", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "run": "npm run build && node out/main.js", + "build": "tsc", + "test": "npm run build && mocha --config .mocharc.json main.test.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@types/chai": "^4.3.16", + "chai": "^4.4.1", + "mongodb": "../../mongodb-current.tgz", + "tslib": "^2.6.3", + "mocha": "^10.7.0", + "typescript": "5.5" + } +} diff --git a/test/explicit-resource-management/tsconfig.json b/test/explicit-resource-management/tsconfig.json new file mode 100644 index 0000000000..e11e12bbcd --- /dev/null +++ b/test/explicit-resource-management/tsconfig.json @@ -0,0 +1,47 @@ +{ + "include": [ + "main.test.ts" + ], + "exclude": [ + "main.test.js" + ], + "compilerOptions": { + "allowJs": true, + "checkJs": false, + "strict": true, + "alwaysStrict": true, + "target": "ES2021", + "module": "Node16", + "moduleResolution": "Node16", + "skipLibCheck": true, + "lib": [ + "es2021", + "ES2022.Error", + "ES2022.Object", + "ESNext.Disposable" + ], + // We don't make use of tslib helpers, all syntax used is supported by target engine + "importHelpers": true, + "noEmitHelpers": false, + // Never emit error filled code + "noEmitOnError": true, + "outDir": ".", + // We want the sourcemaps in a separate file + "inlineSourceMap": false, + "sourceMap": false, + // API-Extractor uses declaration maps to report problems in source, no need to distribute + "declaration": false, + "declarationMap": false, + // we include sources in the release + "inlineSources": false, + // Prevents web types from being suggested by vscode. + "types": [ + "node" + ], + "forceConsistentCasingInFileNames": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + // TODO(NODE-3659): Enable useUnknownInCatchVariables and add type assertions or remove unnecessary catch blocks + "useUnknownInCatchVariables": false + } +} diff --git a/test/manual/resource_management.test.ts b/test/manual/resource_management.test.ts new file mode 100644 index 0000000000..d92315ba3b --- /dev/null +++ b/test/manual/resource_management.test.ts @@ -0,0 +1,89 @@ +import { expect } from 'chai'; +import * as sinon from 'sinon'; + +import { AbstractCursor, ChangeStream, ClientSession, MongoClient } from '../mongodb'; + +describe('Symbol.asyncDispose implementation tests', function () { + let client: MongoClient; + + afterEach(async function () { + await client?.close(); + }); + + describe('Symbol.asyncDispose defined', function () { + beforeEach(function () { + if (!('asyncDispose' in Symbol)) { + this.currentTest.skipReason = 'Test must run with asyncDispose available.'; + this.skip(); + } + }); + + describe('MongoClient', function () { + it('the Symbol.asyncDispose method calls close()', async function () { + client = new MongoClient('mongodb://localhost:27017'); + + const spy = sinon.spy(client, 'close'); + await client[Symbol.asyncDispose](); + expect(spy.called).to.be.true; + }); + }); + + describe('ClientSession', function () { + it('the Symbol.asyncDispose method calls endSession()', async function () { + client = new MongoClient('mongodb://localhost:27017'); + const session = client.startSession(); + + const spy = sinon.spy(session, 'endSession'); + await session[Symbol.asyncDispose](); + expect(spy.called).to.be.true; + }); + }); + + describe('ChangeStreams', function () { + it('the Symbol.asyncDispose method calls close()', async function () { + client = new MongoClient('mongodb://localhost:27017'); + const changeStream = client.watch(); + + const spy = sinon.spy(changeStream, 'close'); + await changeStream[Symbol.asyncDispose](); + expect(spy.called).to.be.true; + }); + }); + + describe('cursors', function () { + it('the Symbol.asyncDispose method calls close()', async function () { + client = new MongoClient('mongodb://localhost:27017'); + const cursor = client.db('foo').collection('bar').find(); + + const spy = sinon.spy(cursor, 'close'); + await cursor[Symbol.asyncDispose](); + expect(spy.called).to.be.true; + }); + }); + }); + + describe('Symbol.asyncDispose not defined', function () { + beforeEach(function () { + if ('asyncDispose' in Symbol) { + this.currentTest.skipReason = 'Test must run without asyncDispose available.'; + this.skip(); + } + }); + + it('does not define symbol.asyncDispose on MongoClient', function () { + expect(MongoClient[Symbol.asyncDispose]).not.to.exist; + }); + + it('does not define symbol.asyncDispose on ClientSession', function () { + expect(ClientSession[Symbol.asyncDispose]).not.to.exist; + }); + + it('does not define symbol.asyncDispose on ChangeStream', function () { + expect(ChangeStream[Symbol.asyncDispose]).not.to.exist; + }); + + it('does not define symbol.asyncDispose on cursors', function () { + expect(AbstractCursor[Symbol.asyncDispose]).not.to.exist; + }); + }); +}); diff --git a/test/mongodb.ts b/test/mongodb.ts index 887c65d277..65562f56c7 100644 --- a/test/mongodb.ts +++ b/test/mongodb.ts @@ -187,6 +187,7 @@ export * from '../src/operations/update'; export * from '../src/operations/validate_collection'; export * from '../src/read_concern'; export * from '../src/read_preference'; +export * from '../src/resource_management'; export * from '../src/sdam/common'; export * from '../src/sdam/events'; export * from '../src/sdam/monitor'; diff --git a/test/readme.md b/test/readme.md index 78230d3585..40872c895c 100644 --- a/test/readme.md +++ b/test/readme.md @@ -32,6 +32,7 @@ Below is a summary of the types of test automation in this repo. | TypeScript Definition | `/test/types` | The TypeScript definition tests verify the type definitions are correct. | `npm run check:tsd` | | GitHub Actions | `/test/action` | Tests that run as GitHub Actions such as dependency checking. | Currently, only `npm run check:dependencies` but could be expanded to more in the future. | | Code Examples | `/test/integration/node-specific/examples` | Code examples that are also paired with tests that show they are working examples. | Currently, `npm run check:lambda` to test the AWS Lambda example with default auth and `npm run check:lambda:aws` to test the AWS Lambda example with AWS auth. | +| Explicit Resource Management | `/test/explicit-resource-management` | Tests that use explicit resource management with the driver's disposable resources. | `bash .evergreen/run-resource-management-feature-integration.sh` | ### Spec Tests @@ -101,7 +102,7 @@ You can prefix `npm test` with a `MONGODB_URI` environment variable to point the MONGODB_URI=mongodb://localhost:27017 npm test ``` -For a replica set, you might use: +For a replica set, you might use: ```sh MONGODB_URI=mongodb://localhost:31000,localhost:31001,localhost:31002/?replicaSet=rs npm test @@ -115,14 +116,14 @@ The easiest way to run a single test is by appending `.only()` to the test conte it.only('cool test', function() {}) ``` -Then, run the test using `npm run check:test` for a functional or integration test or +Then, run the test using `npm run check:test` for a functional or integration test or `npm run check:unit` for a unit test. See [Mocha's documentation][mocha-only] for more detailed information on `.only()`. Another way to run a single test is to use Mocha's `grep` flag. For functional or integration tests, run: ```sh npm run check:test -- -g -``` +``` For unit tests, run: ```sh npm run check:unit -- -g @@ -133,7 +134,7 @@ See the [Mocha documentation][mocha-grep] for information on the `grep` flag. [Evergreen][evergreen-wiki] is the continuous integration (CI) system we use. Evergreen builds are automatically run whenever a pull request is created or when commits are pushed to particular branches (e.g., `main`, `4.0`, and `3.6`). -Each Evergreen build runs the test suite against a variety of build variants that include a combination of topologies, special environments, and operating systems. By default, commits in pull requests only run a subset of the build variants in order to save time and resources. To configure a build, update `.evergreen/config.yml.in` and then generate a new Evergreen config via: +Each Evergreen build runs the test suite against a variety of build variants that include a combination of topologies, special environments, and operating systems. By default, commits in pull requests only run a subset of the build variants in order to save time and resources. To configure a build, update `.evergreen/config.yml.in` and then generate a new Evergreen config via: ```sh node .evergreen/generate_evergreen_tasks.js @@ -353,13 +354,13 @@ The following steps will walk you through how to start and test a load balancer. Create two `mongos` running on ports `27017` and `27018`: ```sh - mongos --configdb test/localhost:27217 --bind_ip localhost --setParameter enableTestCommands=1 --setParameter loadBalancerPort=27050 + mongos --configdb test/localhost:27217 --bind_ip localhost --setParameter enableTestCommands=1 --setParameter loadBalancerPort=27050 mongos --configdb test/localhost:27217 --port 27018 --bind_ip localhost --setParameter enableTestCommands=1 --setParameter loadBalancerPort=27051 ``` Initiate cluster on `mongos` in shell: ```sh - mongosh "mongodb://localhost:27017" --eval "sh.addShard('testing/localhost:27218,localhost:27219,localhost:27220')" + mongosh "mongodb://localhost:27017" --eval "sh.addShard('testing/localhost:27218,localhost:27219,localhost:27220')" mongosh "mongodb://localhost:27017" --eval "sh.enableSharding('test')" ``` 1. An alternative way to the fully manual cluster setup is to use `mlaunch`: @@ -434,7 +435,7 @@ The following steps will walk you through how to run the tests for CSFLE. 1. Install [MongoDB Client Encryption][npm-csfle] if you haven't already: ```sh npm install mongodb-client-encryption - ``` + ``` > **Note:** if developing changes in `mongodb-client-encryption`, you can link it locally using `etc/tooling/fle.sh`. @@ -599,7 +600,7 @@ lerna run test --scope @mongosh/service-provider-server npm i --no-save mongodb-client-encryption ``` 6. Launch a MongoDB server -7. Run the full suite: +7. Run the full suite: ```sh npm run check:test ``` diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts index 8dc43c9c74..980747c8c7 100644 --- a/test/unit/index.test.ts +++ b/test/unit/index.test.ts @@ -31,6 +31,7 @@ const EXPECTED_EXPORTS = [ 'ClientSession', 'Code', 'Collection', + 'configureExplicitResourceManagement', 'CommandFailedEvent', 'CommandStartedEvent', 'CommandSucceededEvent',