From afc884d786160481cb171557cf95582b33b1002f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 5 Feb 2026 13:52:10 -0600 Subject: [PATCH 01/13] PYTHON-5467 Add codecov integration (#2690) --- .evergreen/config.yml | 1 + .evergreen/generated_configs/functions.yml | 18 ++++++++++ .evergreen/generated_configs/variants.yml | 3 ++ .evergreen/scripts/generate_config.py | 22 ++++++++++-- .evergreen/scripts/upload-codecov.sh | 42 ++++++++++++++++++++++ .github/workflows/test-python.yml | 29 +++++++++++++++ .gitignore | 1 + pyproject.toml | 6 +++- 8 files changed, 119 insertions(+), 3 deletions(-) create mode 100755 .evergreen/scripts/upload-codecov.sh diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 91fa44277..1af19857c 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -38,6 +38,7 @@ post: # Disabled, causing timeouts # - func: "upload working dir" - func: "teardown system" + - func: "upload codecov" - func: "upload coverage" - func: "upload mo artifacts" - func: "upload test results" diff --git a/.evergreen/generated_configs/functions.yml b/.evergreen/generated_configs/functions.yml index bd983abb3..6fcda5e98 100644 --- a/.evergreen/generated_configs/functions.yml +++ b/.evergreen/generated_configs/functions.yml @@ -252,6 +252,24 @@ functions: - TOOLCHAIN_VERSION type: test + # Upload coverage codecov + upload codecov: + - command: subprocess.exec + params: + binary: bash + args: + - .evergreen/scripts/upload-codecov.sh + working_dir: src + include_expansions_in_env: + - CODECOV_TOKEN + - build_variant + - task_name + - github_commit + - github_pr_number + - github_pr_head_branch + - github_author + type: test + # Upload coverage upload coverage: - command: ec2.assume_role diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index 42a677609..edca05024 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -367,6 +367,9 @@ buildvariants: display_name: No C Ext RHEL8 run_on: - rhel87-small + expansions: + COVERAGE: "1" + NO_EXT: "1" # No server tests - name: no-server-rhel8 diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 04579c521..405125021 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -318,10 +318,10 @@ def create_green_framework_variants(): def create_no_c_ext_variants(): host = DEFAULT_HOST tasks = [".test-standard"] - expansions = dict() + expansions = dict(COVERAGE="1") handle_c_ext(C_EXTS[0], expansions) display_name = get_variant_name("No C Ext", host) - return [create_variant(tasks, display_name, host=host)] + return [create_variant(tasks, display_name, host=host, expansions=expansions)] def create_mod_wsgi_variants(): @@ -1077,6 +1077,24 @@ def create_upload_coverage_func(): return "upload coverage", [get_assume_role(), cmd] +def create_upload_coverage_codecov_func(): + # Upload the coverage xml report to codecov. + include_expansions = [ + "CODECOV_TOKEN", + "build_variant", + "task_name", + "github_commit", + "github_pr_number", + "github_pr_head_branch", + "github_author", + ] + args = [ + ".evergreen/scripts/upload-codecov.sh", + ] + upload_cmd = get_subprocess_exec(include_expansions_in_env=include_expansions, args=args) + return "upload codecov", [upload_cmd] + + def create_download_and_merge_coverage_func(): include_expansions = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"] args = [ diff --git a/.evergreen/scripts/upload-codecov.sh b/.evergreen/scripts/upload-codecov.sh new file mode 100755 index 000000000..a7fdb0371 --- /dev/null +++ b/.evergreen/scripts/upload-codecov.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# shellcheck disable=SC2154 +# Upload a coverate report to codecov. +set -eu + +HERE=$(dirname ${BASH_SOURCE:-$0}) +ROOT=$(dirname "$(dirname $HERE)") + +pushd $ROOT > /dev/null +export FNAME=coverage.xml + +if [ -z "${github_pr_number:-}" ]; then + echo "This is not a PR, not running codecov" + exit 0 +fi + +if [ ! -f ".coverage" ]; then + echo "There are no XML test results, not running codecov" + exit 0 +fi + +echo "Uploading..." +printf 'pr: %s\n' "$github_pr_number" +printf 'sha: %s\n' "$github_commit" +printf 'branch: %s:%s\n' "$github_author" "$github_pr_head_branch" +printf 'flag: %s-%s\n' "$build_variant" "$task_name" +printf 'file: %s\n' "$FNAME" +uv tool run --with "coverage[toml]" coverage xml +uv tool run --from codecov-cli codecovcli upload-process \ + --report-type coverage \ + --disable-search \ + --fail-on-error \ + --git-service github \ + --token ${CODECOV_TOKEN} \ + --pr ${github_pr_number} \ + --sha ${github_commit} \ + --branch "${github_author}:${github_pr_head_branch}" \ + --flag "${build_variant}-${task_name}" \ + --file $FNAME +echo "Uploading...done." + +popd > /dev/null diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 086e22fae..5c0bbe08e 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -79,6 +79,35 @@ jobs: - name: Run tests run: uv run --extra test pytest -v + coverage: + # This enables a coverage report for a given PR, which will be augmented by + # the combined codecov report uploaded in Evergreen. + runs-on: ubuntu-latest + + name: Coverage + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - name: Install uv + uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + with: + enable-cache: true + python-version: "3.10" + - id: setup-mongodb + uses: mongodb-labs/drivers-evergreen-tools@master + with: + version: "8.0" + - name: Install just + run: uv tool install rust-just + - name: Setup tests + run: COVERAGE=1 just setup-tests + - name: Run tests + run: just run-tests + - name: Generate xml report + run: uv tool run --with "coverage[toml]" coverage xml + - name: Upload test results to Codecov + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 doctest: runs-on: ubuntu-latest name: DocTest diff --git a/.gitignore b/.gitignore index 74ed0bbb7..cb4940a55 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ test/lambda/*.json # test results and logs xunit-results/ +coverage.xml server.log diff --git a/pyproject.toml b/pyproject.toml index 65cbeca8b..acc9fa5b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -239,7 +239,11 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?)|dummy.*)$" [tool.coverage.run] branch = true -source = ["pymongo", "bson", "gridfs" ] +include = [ + "pymongo/*", + "bson/*", + "gridfs/*" +] relative_files = true [tool.coverage.report] From d5e1777732e71b75a7860ba05a44e76de10b39d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 09:01:06 -0500 Subject: [PATCH 02/13] Bump astral-sh/setup-uv from 7.2.0 to 7.2.1 in the actions group (#2700) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test-python.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 5c0bbe08e..3f8090d35 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -68,7 +68,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -90,7 +90,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -116,7 +116,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -141,7 +141,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -160,7 +160,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -182,7 +182,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "${{matrix.python}}" @@ -203,7 +203,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: enable-cache: true python-version: "3.10" @@ -293,7 +293,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7 + uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 with: python-version: "3.9" - id: setup-mongodb From f28ab12db0bba5bbfa0006fda09bacb2aa6be838 Mon Sep 17 00:00:00 2001 From: Casey Clements Date: Fri, 6 Feb 2026 09:08:00 -0500 Subject: [PATCH 03/13] PYTHON-XXXX Fixed typo in Running Tests Locally section. (#2698) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eaf05111d..eb1c35fc8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -197,7 +197,7 @@ the pages will re-render and the browser will automatically refresh. version of Python, set `UV_PYTHON` before running `just install`. - Ensure you have started the appropriate Mongo Server(s). You can run `just run-server` with optional args to set up the server. All given options will be passed to - [`run-orchestration.sh`](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-orchestration.sh). Run `$DRIVERS_TOOLS/evergreen/run-orchestration.sh -h` + [`run-orchestration.sh`](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-orchestration.sh). Run `$DRIVERS_TOOLS/.evergreen/run-orchestration.sh -h` for a full list of options. - Run `just test` or `pytest` to run all of the tests. - Append `test/.py::::` to run From b1a0a1f10483a2c55614848479627d58472fa788 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 6 Feb 2026 10:29:37 -0600 Subject: [PATCH 04/13] PYTHON-5467 Fix codecov upload (#2701) --- .github/workflows/test-python.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 3f8090d35..388f68bbe 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -108,6 +108,8 @@ jobs: run: uv tool run --with "coverage[toml]" coverage xml - name: Upload test results to Codecov uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} doctest: runs-on: ubuntu-latest name: DocTest From fdb6a3291fb9b37e113ddf70d862c35969cae139 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 9 Feb 2026 13:55:08 -0600 Subject: [PATCH 05/13] PYTHON-5467 Fix codecov upload on Evergreen (#2702) --- .evergreen/generated_configs/functions.yml | 2 + .evergreen/scripts/generate_config.py | 2 + .evergreen/scripts/upload-codecov.sh | 47 ++++++++++++++-------- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/.evergreen/generated_configs/functions.yml b/.evergreen/generated_configs/functions.yml index 6fcda5e98..58bffbf92 100644 --- a/.evergreen/generated_configs/functions.yml +++ b/.evergreen/generated_configs/functions.yml @@ -268,6 +268,8 @@ functions: - github_pr_number - github_pr_head_branch - github_author + - is_patch + - branch_name type: test # Upload coverage diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 405125021..3375b9e14 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -1087,6 +1087,8 @@ def create_upload_coverage_codecov_func(): "github_pr_number", "github_pr_head_branch", "github_author", + "is_patch", + "branch_name", ] args = [ ".evergreen/scripts/upload-codecov.sh", diff --git a/.evergreen/scripts/upload-codecov.sh b/.evergreen/scripts/upload-codecov.sh index a7fdb0371..75bd9d9e2 100755 --- a/.evergreen/scripts/upload-codecov.sh +++ b/.evergreen/scripts/upload-codecov.sh @@ -9,34 +9,49 @@ ROOT=$(dirname "$(dirname $HERE)") pushd $ROOT > /dev/null export FNAME=coverage.xml -if [ -z "${github_pr_number:-}" ]; then - echo "This is not a PR, not running codecov" +if [ -n "${is_patch:-}" ]; then + echo "This is a patch build, not running codecov" exit 0 fi if [ ! -f ".coverage" ]; then - echo "There are no XML test results, not running codecov" + echo "There are no coverage results, not running codecov" exit 0 fi echo "Uploading..." -printf 'pr: %s\n' "$github_pr_number" printf 'sha: %s\n' "$github_commit" -printf 'branch: %s:%s\n' "$github_author" "$github_pr_head_branch" printf 'flag: %s-%s\n' "$build_variant" "$task_name" printf 'file: %s\n' "$FNAME" uv tool run --with "coverage[toml]" coverage xml -uv tool run --from codecov-cli codecovcli upload-process \ - --report-type coverage \ - --disable-search \ - --fail-on-error \ - --git-service github \ - --token ${CODECOV_TOKEN} \ - --pr ${github_pr_number} \ - --sha ${github_commit} \ - --branch "${github_author}:${github_pr_head_branch}" \ - --flag "${build_variant}-${task_name}" \ - --file $FNAME + +codecov_args=( + upload-process + --report-type coverage + --disable-search + --fail-on-error + --git-service github + --token "${CODECOV_TOKEN}" + --sha "${github_commit}" + --flag "${build_variant}-${task_name}" + --file "${FNAME}" +) + +if [ -n "${github_pr_number:-}" ]; then + printf 'branch: %s:%s\n' "$github_author" "$github_pr_head_branch" + printf 'pr: %s\n' "$github_pr_number" + + uv tool run --from codecov-cli codecovcli \ + "${codecov_args[@]}" \ + --pr "${github_pr_number}" \ + --branch "${github_author}:${github_pr_head_branch}" +else + printf 'branch: %s\n' "$branch_name" + + uv tool run --from codecov-cli codecovcli \ + "${codecov_args[@]}" \ + --branch "${branch_name}" +fi echo "Uploading...done." popd > /dev/null From 0441761872fcf4d6060b558b4cdd4a261377bcbf Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 9 Feb 2026 14:51:30 -0600 Subject: [PATCH 06/13] PYTHON-5715 Add appName to OIDC test failpoints (#2697) --- test/asynchronous/test_auth_oidc.py | 4 ++-- test/auth/unified/mongodb-oidc-no-retry.json | 18 ++++++++++++------ test/test_auth_oidc.py | 6 ++++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/test/asynchronous/test_auth_oidc.py b/test/asynchronous/test_auth_oidc.py index ff604f55a..3567d7706 100644 --- a/test/asynchronous/test_auth_oidc.py +++ b/test/asynchronous/test_auth_oidc.py @@ -104,7 +104,7 @@ class OIDCTestBase(AsyncPyMongoTestCase): @asynccontextmanager async def fail_point(self, command_args): - cmd_on = SON([("configureFailPoint", "failCommand")]) + cmd_on = dict(configureFailPoint="failCommand", appName="auth_oidc") cmd_on.update(command_args) client = AsyncMongoClient(self.uri_admin) await client.admin.command(cmd_on) @@ -112,7 +112,7 @@ class OIDCTestBase(AsyncPyMongoTestCase): yield finally: await client.admin.command( - "configureFailPoint", cmd_on["configureFailPoint"], mode="off" + "configureFailPoint", cmd_on["configureFailPoint"], mode="off", appName="auth_oidc" ) await client.close() diff --git a/test/auth/unified/mongodb-oidc-no-retry.json b/test/auth/unified/mongodb-oidc-no-retry.json index 0a8658455..b32ada172 100644 --- a/test/auth/unified/mongodb-oidc-no-retry.json +++ b/test/auth/unified/mongodb-oidc-no-retry.json @@ -25,7 +25,8 @@ "$$placeholder": 1 }, "retryReads": false, - "retryWrites": false + "retryWrites": false, + "appName": "mongodb-oidc-no-retry" }, "observeEvents": [ "commandStartedEvent", @@ -147,7 +148,8 @@ "failCommands": [ "find" ], - "errorCode": 391 + "errorCode": 391, + "appName": "mongodb-oidc-no-retry" } } } @@ -212,7 +214,8 @@ "failCommands": [ "insert" ], - "errorCode": 391 + "errorCode": 391, + "appName": "mongodb-oidc-no-retry" } } } @@ -289,7 +292,8 @@ "failCommands": [ "insert" ], - "closeConnection": true + "closeConnection": true, + "appName": "mongodb-oidc-no-retry" } } } @@ -321,7 +325,8 @@ "failCommands": [ "saslStart" ], - "errorCode": 18 + "errorCode": 18, + "appName": "mongodb-oidc-no-retry" } } } @@ -398,7 +403,8 @@ "failCommands": [ "saslStart" ], - "errorCode": 18 + "errorCode": 18, + "appName": "mongodb-oidc-no-retry" } } } diff --git a/test/test_auth_oidc.py b/test/test_auth_oidc.py index 1defe8200..e88e067b2 100644 --- a/test/test_auth_oidc.py +++ b/test/test_auth_oidc.py @@ -104,14 +104,16 @@ class OIDCTestBase(PyMongoTestCase): @contextmanager def fail_point(self, command_args): - cmd_on = SON([("configureFailPoint", "failCommand")]) + cmd_on = dict(configureFailPoint="failCommand", appName="auth_oidc") cmd_on.update(command_args) client = MongoClient(self.uri_admin) client.admin.command(cmd_on) try: yield finally: - client.admin.command("configureFailPoint", cmd_on["configureFailPoint"], mode="off") + client.admin.command( + "configureFailPoint", cmd_on["configureFailPoint"], mode="off", appName="auth_oidc" + ) client.close() From 36676384bd62782be3e752b735d7e7dfe87d5748 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 9 Feb 2026 19:39:05 -0600 Subject: [PATCH 07/13] PYTHON-5705 Improve fallback for PyOpenSSL windows system certs loading (#2688) --- pymongo/pyopenssl_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymongo/pyopenssl_context.py b/pymongo/pyopenssl_context.py index 67456d553..941d91cd1 100644 --- a/pymongo/pyopenssl_context.py +++ b/pymongo/pyopenssl_context.py @@ -357,7 +357,7 @@ class SSLContext: try: for storename in ("CA", "ROOT"): self._load_wincerts(storename) - except PermissionError: + except Exception: # Fall back to certifi self._load_certifi() elif _sys.platform == "darwin": From b60d266ad772b7a6b872fbef1aeebb722d78a855 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 17 Feb 2026 12:23:34 -0600 Subject: [PATCH 08/13] PYTHON-3898 Add coverage to all variants (#2705) --- .codecov.yml | 4 + .evergreen/generated_configs/functions.yml | 3 +- .evergreen/generated_configs/tasks.yml | 139 +++++++++------------ .evergreen/generated_configs/variants.yml | 3 +- .evergreen/scripts/generate_config.py | 40 ++++-- .evergreen/scripts/run_tests.py | 12 ++ .evergreen/scripts/setup_tests.py | 4 +- .evergreen/scripts/upload-codecov.sh | 18 +-- .evergreen/scripts/utils.py | 1 + justfile | 5 +- pyproject.toml | 1 - test/asynchronous/test_ssl.py | 17 +-- test/test_ssl.py | 17 +-- uv.lock | 21 +--- 14 files changed, 129 insertions(+), 156 deletions(-) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..5cbc2d3f5 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,4 @@ +# do not notify until at least 100 builds have been uploaded from the CI pipeline +# you can also set after_n_builds on comments independently +comment: + after_n_builds: 100 diff --git a/.evergreen/generated_configs/functions.yml b/.evergreen/generated_configs/functions.yml index 58bffbf92..9fbc1f850 100644 --- a/.evergreen/generated_configs/functions.yml +++ b/.evergreen/generated_configs/functions.yml @@ -250,6 +250,7 @@ functions: working_dir: src include_expansions_in_env: - TOOLCHAIN_VERSION + - COVERAGE type: test # Upload coverage codecov @@ -268,7 +269,7 @@ functions: - github_pr_number - github_pr_head_branch - github_author - - is_patch + - requester - branch_name type: test diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml index 60ee6ed13..6a460ce48 100644 --- a/.evergreen/generated_configs/tasks.yml +++ b/.evergreen/generated_configs/tasks.yml @@ -75,7 +75,7 @@ tasks: SUB_TEST_NAME: session-creds TOOLCHAIN_VERSION: 3.14t tags: [auth-aws, auth-aws-session-creds, free-threaded] - - name: test-auth-aws-rapid-web-identity-python3.14 + - name: test-auth-aws-rapid-web-identity-python3.14-cov commands: - func: run server vars: @@ -87,7 +87,8 @@ tasks: TEST_NAME: auth_aws SUB_TEST_NAME: web-identity TOOLCHAIN_VERSION: "3.14" - tags: [auth-aws, auth-aws-web-identity] + COVERAGE: "1" + tags: [auth-aws, auth-aws-web-identity, pr] - name: test-auth-aws-rapid-web-identity-session-name-python3.14 commands: - func: run server @@ -904,7 +905,7 @@ tasks: - ocsp-ecdsa - rapid - ocsp-staple - - name: test-ocsp-ecdsa-valid-cert-server-staples-latest-python3.14 + - name: test-ocsp-ecdsa-valid-cert-server-staples-latest-python3.14-cov commands: - func: run tests vars: @@ -913,11 +914,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.14" VERSION: latest + COVERAGE: "1" tags: - ocsp - ocsp-ecdsa - latest - ocsp-staple + - pr - name: test-ocsp-ecdsa-invalid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests @@ -1928,7 +1931,7 @@ tasks: - ocsp-rsa - rapid - ocsp-staple - - name: test-ocsp-rsa-valid-cert-server-staples-latest-python3.14 + - name: test-ocsp-rsa-valid-cert-server-staples-latest-python3.14-cov commands: - func: run tests vars: @@ -1937,11 +1940,13 @@ tasks: TEST_NAME: ocsp TOOLCHAIN_VERSION: "3.14" VERSION: latest + COVERAGE: "1" tags: - ocsp - ocsp-rsa - latest - ocsp-staple + - pr - name: test-ocsp-rsa-invalid-cert-server-staples-v4.4-python3.10-min-deps commands: - func: run tests @@ -2615,20 +2620,18 @@ tasks: - replica_set-auth-nossl - async - free-threaded - - name: test-server-version-python3.13-sync-auth-nossl-replica-set-cov + - name: test-server-version-python3.13-sync-auth-nossl-replica-set commands: - func: run server vars: AUTH: auth SSL: nossl TOPOLOGY: replica_set - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: nossl TOPOLOGY: replica_set - COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_sync tags: @@ -2636,20 +2639,18 @@ tasks: - python-3.13 - replica_set-auth-nossl - sync - - name: test-server-version-python3.12-async-auth-ssl-replica-set-cov + - name: test-server-version-python3.12-async-auth-ssl-replica-set commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: @@ -2657,20 +2658,18 @@ tasks: - python-3.12 - replica_set-auth-ssl - async - - name: test-server-version-python3.11-sync-auth-ssl-replica-set-cov + - name: test-server-version-python3.11-sync-auth-ssl-replica-set commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: @@ -2743,20 +2742,18 @@ tasks: - python-pypy3.11 - replica_set-noauth-ssl - async - - name: test-server-version-python3.14-sync-noauth-ssl-replica-set-cov + - name: test-server-version-python3.14-sync-noauth-ssl-replica-set commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set - COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_sync tags: @@ -2764,20 +2761,18 @@ tasks: - python-3.14 - replica_set-noauth-ssl - sync - - name: test-server-version-python3.14-async-auth-nossl-sharded-cluster-cov + - name: test-server-version-python3.14-async-auth-nossl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: @@ -2829,20 +2824,18 @@ tasks: - sharded_cluster-auth-ssl - async - pr - - name: test-server-version-python3.11-async-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.11-async-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: @@ -2850,20 +2843,18 @@ tasks: - python-3.11 - sharded_cluster-auth-ssl - async - - name: test-server-version-python3.12-async-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.12-async-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: @@ -2871,20 +2862,18 @@ tasks: - python-3.12 - sharded_cluster-auth-ssl - async - - name: test-server-version-python3.13-async-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.13-async-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_async tags: @@ -2892,20 +2881,18 @@ tasks: - python-3.13 - sharded_cluster-auth-ssl - async - - name: test-server-version-python3.14-async-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.14-async-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: @@ -2976,20 +2963,18 @@ tasks: - sharded_cluster-auth-ssl - sync - pr - - name: test-server-version-python3.11-sync-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.11-sync-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: @@ -2997,20 +2982,18 @@ tasks: - python-3.11 - sharded_cluster-auth-ssl - sync - - name: test-server-version-python3.12-sync-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.12-sync-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: @@ -3018,20 +3001,18 @@ tasks: - python-3.12 - sharded_cluster-auth-ssl - sync - - name: test-server-version-python3.13-sync-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.13-sync-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_sync tags: @@ -3039,20 +3020,18 @@ tasks: - python-3.13 - sharded_cluster-auth-ssl - sync - - name: test-server-version-python3.14-sync-auth-ssl-sharded-cluster-cov + - name: test-server-version-python3.14-sync-auth-ssl-sharded-cluster commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_sync tags: @@ -3099,20 +3078,18 @@ tasks: - python-pypy3.11 - sharded_cluster-auth-ssl - sync - - name: test-server-version-python3.12-async-noauth-nossl-sharded-cluster-cov + - name: test-server-version-python3.12-async-noauth-nossl-sharded-cluster commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: @@ -3120,20 +3097,18 @@ tasks: - python-3.12 - sharded_cluster-noauth-nossl - async - - name: test-server-version-python3.11-sync-noauth-nossl-sharded-cluster-cov + - name: test-server-version-python3.11-sync-noauth-nossl-sharded-cluster commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: sharded_cluster - COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_sync tags: @@ -3141,7 +3116,7 @@ tasks: - python-3.11 - sharded_cluster-noauth-nossl - sync - - name: test-server-version-python3.10-async-noauth-ssl-sharded-cluster-min-deps-cov + - name: test-server-version-python3.10-async-noauth-ssl-sharded-cluster-min-deps commands: - func: run server vars: @@ -3149,14 +3124,12 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster TEST_MIN_DEPS: "1" - COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: sharded_cluster TEST_MIN_DEPS: "1" - COVERAGE: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_async tags: @@ -3183,20 +3156,18 @@ tasks: - python-pypy3.11 - sharded_cluster-noauth-ssl - sync - - name: test-server-version-python3.13-async-auth-nossl-standalone-cov + - name: test-server-version-python3.13-async-auth-nossl-standalone commands: - func: run server vars: AUTH: auth SSL: nossl TOPOLOGY: standalone - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: nossl TOPOLOGY: standalone - COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_async tags: @@ -3204,20 +3175,18 @@ tasks: - python-3.13 - standalone-auth-nossl - async - - name: test-server-version-python3.12-sync-auth-nossl-standalone-cov + - name: test-server-version-python3.12-sync-auth-nossl-standalone commands: - func: run server vars: AUTH: auth SSL: nossl TOPOLOGY: standalone - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: nossl TOPOLOGY: standalone - COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_sync tags: @@ -3225,20 +3194,18 @@ tasks: - python-3.12 - standalone-auth-nossl - sync - - name: test-server-version-python3.11-async-auth-ssl-standalone-cov + - name: test-server-version-python3.11-async-auth-ssl-standalone commands: - func: run server vars: AUTH: auth SSL: ssl TOPOLOGY: standalone - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: standalone - COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: @@ -3246,7 +3213,7 @@ tasks: - python-3.11 - standalone-auth-ssl - async - - name: test-server-version-python3.10-sync-auth-ssl-standalone-min-deps-cov + - name: test-server-version-python3.10-sync-auth-ssl-standalone-min-deps commands: - func: run server vars: @@ -3254,14 +3221,12 @@ tasks: SSL: ssl TOPOLOGY: standalone TEST_MIN_DEPS: "1" - COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: standalone TEST_MIN_DEPS: "1" - COVERAGE: "1" TOOLCHAIN_VERSION: "3.10" TEST_NAME: default_sync tags: @@ -3293,18 +3258,20 @@ tasks: - standalone-noauth-nossl - async - pr - - name: test-server-version-pypy3.11-sync-noauth-nossl-standalone + - name: test-server-version-pypy3.11-sync-noauth-nossl-standalone-cov commands: - func: run server vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone + COVERAGE: "1" TOOLCHAIN_VERSION: pypy3.11 TEST_NAME: default_sync tags: @@ -3313,20 +3280,18 @@ tasks: - standalone-noauth-nossl - sync - pr - - name: test-server-version-python3.14-async-noauth-ssl-standalone-cov + - name: test-server-version-python3.14-async-noauth-ssl-standalone commands: - func: run server vars: AUTH: noauth SSL: ssl TOPOLOGY: standalone - COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: standalone - COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: @@ -4082,7 +4047,7 @@ tasks: - standalone-noauth-nossl - async - pypy - - name: test-standard-latest-python3.12-async-noauth-ssl-replica-set + - name: test-standard-latest-python3.12-async-noauth-ssl-replica-set-cov commands: - func: run server vars: @@ -4090,12 +4055,14 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.12" TEST_NAME: default_async tags: @@ -4128,7 +4095,7 @@ tasks: - replica_set-noauth-ssl - async - pypy - - name: test-standard-latest-python3.13-async-auth-ssl-sharded-cluster + - name: test-standard-latest-python3.13-async-auth-ssl-sharded-cluster-cov commands: - func: run server vars: @@ -4136,12 +4103,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" TEST_NAME: default_async tags: @@ -4151,7 +4120,7 @@ tasks: - sharded_cluster-auth-ssl - async - pr - - name: test-standard-latest-python3.11-async-noauth-nossl-standalone + - name: test-standard-latest-python3.11-async-noauth-nossl-standalone-cov commands: - func: run server vars: @@ -4159,12 +4128,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" TEST_NAME: default_async tags: @@ -4174,7 +4145,7 @@ tasks: - standalone-noauth-nossl - async - pr - - name: test-standard-latest-python3.14-async-noauth-nossl-standalone + - name: test-standard-latest-python3.14-async-noauth-nossl-standalone-cov commands: - func: run server vars: @@ -4182,12 +4153,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" TEST_NAME: default_async tags: @@ -4829,7 +4802,7 @@ tasks: - python-3.13 - standalone-noauth-nossl - noauth - - name: test-non-standard-latest-python3.14t-noauth-ssl-replica-set + - name: test-non-standard-latest-python3.14t-noauth-ssl-replica-set-cov commands: - func: run server vars: @@ -4837,12 +4810,14 @@ tasks: SSL: ssl TOPOLOGY: replica_set VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: ssl TOPOLOGY: replica_set VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: 3.14t tags: - test-non-standard @@ -4874,7 +4849,7 @@ tasks: - replica_set-noauth-ssl - noauth - pypy - - name: test-non-standard-latest-python3.14-auth-ssl-sharded-cluster + - name: test-non-standard-latest-python3.14-auth-ssl-sharded-cluster-cov commands: - func: run server vars: @@ -4882,12 +4857,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.14" tags: - test-non-standard @@ -4896,7 +4873,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pr - - name: test-non-standard-latest-python3.13-noauth-nossl-standalone + - name: test-non-standard-latest-python3.13-noauth-nossl-standalone-cov commands: - func: run server vars: @@ -4904,12 +4881,14 @@ tasks: SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: noauth SSL: nossl TOPOLOGY: standalone VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.13" tags: - test-non-standard @@ -5007,7 +4986,7 @@ tasks: - pypy # Test numpy tests - - name: test-numpy-python3.10 + - name: test-numpy-python3.10-python3.10 commands: - func: test numpy vars: @@ -5017,16 +4996,18 @@ tasks: - vector - python-3.10 - test-numpy - - name: test-numpy-python3.14 + - name: test-numpy-python3.14-python3.14-cov commands: - func: test numpy vars: TOOLCHAIN_VERSION: "3.14" + COVERAGE: "1" tags: - binary - vector - python-3.14 - test-numpy + - pr # Test standard auth tests - name: test-standard-auth-v4.2-python3.10-auth-ssl-sharded-cluster-min-deps @@ -5290,7 +5271,7 @@ tasks: - sharded_cluster-auth-ssl - auth - pypy - - name: test-standard-auth-latest-python3.11-auth-ssl-sharded-cluster + - name: test-standard-auth-latest-python3.11-auth-ssl-sharded-cluster-cov commands: - func: run server vars: @@ -5298,12 +5279,14 @@ tasks: SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" - func: run tests vars: AUTH: auth SSL: ssl TOPOLOGY: sharded_cluster VERSION: latest + COVERAGE: "1" TOOLCHAIN_VERSION: "3.11" tags: - test-standard-auth diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml index edca05024..4c9116d62 100644 --- a/.evergreen/generated_configs/variants.yml +++ b/.evergreen/generated_configs/variants.yml @@ -368,7 +368,6 @@ buildvariants: run_on: - rhel87-small expansions: - COVERAGE: "1" NO_EXT: "1" # No server tests @@ -420,6 +419,8 @@ buildvariants: run_on: - ubuntu2204-small batchtime: 1440 + expansions: + COVERAGE: "1" tags: [pr] - name: auth-oidc-macos tasks: diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py index 3375b9e14..b4760eab9 100644 --- a/.evergreen/scripts/generate_config.py +++ b/.evergreen/scripts/generate_config.py @@ -318,7 +318,7 @@ def create_green_framework_variants(): def create_no_c_ext_variants(): host = DEFAULT_HOST tasks = [".test-standard"] - expansions = dict(COVERAGE="1") + expansions = dict() handle_c_ext(C_EXTS[0], expansions) display_name = get_variant_name("No C Ext", host) return [create_variant(tasks, display_name, host=host, expansions=expansions)] @@ -344,8 +344,12 @@ def create_test_numpy_tasks(): tasks = [] for python in MIN_MAX_PYTHON: tags = ["binary", "vector", f"python-{python}", "test-numpy"] - task_name = get_task_name("test-numpy", python=python) - test_func = FunctionCall(func="test numpy", vars=dict(TOOLCHAIN_VERSION=python)) + vars = dict(TOOLCHAIN_VERSION=python) + if python == MIN_MAX_PYTHON[-1]: + tags.append("pr") + vars["COVERAGE"] = "1" + task_name = get_task_name("test-numpy", python=python, **vars) + test_func = FunctionCall(func="test numpy", vars=vars) tasks.append(EvgTask(name=task_name, tags=tags, commands=[test_func])) return tasks @@ -397,6 +401,7 @@ def create_oidc_auth_variants(): tags=["pr"], host=host, batchtime=BATCHTIME_DAY, + expansions=dict(COVERAGE="1"), ) ) return variants @@ -596,7 +601,7 @@ def create_server_version_tasks(): expansions["TEST_MIN_DEPS"] = "1" if "t" in python: tags.append("free-threaded") - if python not in PYPYS and "t" not in python: + if "pr" in tags: expansions["COVERAGE"] = "1" name = get_task_name( "test-server-version", @@ -661,6 +666,8 @@ def create_test_non_standard_tasks(): expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version) if python == ALL_PYTHONS[0]: expansions["TEST_MIN_DEPS"] = "1" + elif pr: + expansions["COVERAGE"] = "1" name = get_task_name("test-non-standard", python=python, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() @@ -703,6 +710,8 @@ def create_test_standard_auth_tasks(): expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version) if python == ALL_PYTHONS[0]: expansions["TEST_MIN_DEPS"] = "1" + elif pr: + expansions["COVERAGE"] = "1" name = get_task_name("test-standard-auth", python=python, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() @@ -741,6 +750,8 @@ def create_standard_tasks(): expansions = dict(AUTH=auth, SSL=ssl, TOPOLOGY=topology, VERSION=version) if python == ALL_PYTHONS[0]: expansions["TEST_MIN_DEPS"] = "1" + elif pr: + expansions["COVERAGE"] = "1" name = get_task_name("test-standard", python=python, sync=sync, **expansions) server_func = FunctionCall(func="run server", vars=expansions) test_vars = expansions.copy() @@ -810,8 +821,11 @@ def create_aws_tasks(): if "t" in python: tags.append("free-threaded") test_vars = dict(TEST_NAME="auth_aws", SUB_TEST_NAME=test_type, TOOLCHAIN_VERSION=python) - if python == ALL_PYTHONS[0]: + if python == MIN_MAX_PYTHON[0]: test_vars["TEST_MIN_DEPS"] = "1" + elif python == MIN_MAX_PYTHON[-1]: + tags.append("pr") + test_vars["COVERAGE"] = "1" name = get_task_name(f"{base_name}-{test_type}", **test_vars) test_func = FunctionCall(func="run tests", vars=test_vars) funcs = [server_func, assume_func, test_func] @@ -849,11 +863,11 @@ def create_oidc_tasks(): tasks = [] for sub_test in ["default", "azure", "gcp", "eks", "aks", "gke"]: vars = dict(TEST_NAME="auth_oidc", SUB_TEST_NAME=sub_test) - test_func = FunctionCall(func="run tests", vars=vars) - task_name = f"test-auth-oidc-{sub_test}" tags = ["auth_oidc"] if sub_test != "default": tags.append("auth_oidc_remote") + test_func = FunctionCall(func="run tests", vars=vars) + task_name = get_task_name(f"test-auth-oidc-{sub_test}", **vars) tasks.append(EvgTask(name=task_name, tags=tags, commands=[test_func])) return tasks @@ -903,14 +917,14 @@ def _create_ocsp_tasks(algo, variant, server_type, base_task_name): ) if python == ALL_PYTHONS[0]: vars["TEST_MIN_DEPS"] = "1" - test_func = FunctionCall(func="run tests", vars=vars) - tags = ["ocsp", f"ocsp-{algo}", version] if "disableStapling" not in variant: tags.append("ocsp-staple") - if algo == "valid-cert-server-staples" and version == "latest": + if base_task_name == "valid-cert-server-staples" and version == "latest": tags.append("pr") - + if "TEST_MIN_DEPS" not in vars: + vars["COVERAGE"] = "1" + test_func = FunctionCall(func="run tests", vars=vars) task_name = get_task_name(f"test-ocsp-{algo}-{base_task_name}", **vars) tasks.append(EvgTask(name=task_name, tags=tags, commands=[test_func])) @@ -1087,7 +1101,7 @@ def create_upload_coverage_codecov_func(): "github_pr_number", "github_pr_head_branch", "github_author", - "is_patch", + "requester", "branch_name", ] args = [ @@ -1230,7 +1244,7 @@ def create_run_tests_func(): def create_test_numpy_func(): - includes = ["TOOLCHAIN_VERSION"] + includes = ["TOOLCHAIN_VERSION", "COVERAGE"] test_cmd = get_subprocess_exec( include_expansions_in_env=includes, args=[".evergreen/just.sh", "test-numpy"] ) diff --git a/.evergreen/scripts/run_tests.py b/.evergreen/scripts/run_tests.py index 9c8101c5b..fcf5fe76c 100644 --- a/.evergreen/scripts/run_tests.py +++ b/.evergreen/scripts/run_tests.py @@ -4,7 +4,9 @@ import json import logging import os import platform +import shlex import shutil +import subprocess import sys from datetime import datetime from pathlib import Path @@ -202,6 +204,16 @@ def run() -> None: if os.environ.get("DEBUG_LOG"): TEST_ARGS.extend(f"-o log_cli_level={logging.DEBUG}".split()) + if os.environ.get("COVERAGE"): + binary = sys.executable.replace(os.sep, "/") + cmd = f"{binary} -m coverage run -m pytest {' '.join(TEST_ARGS)} {' '.join(sys.argv[1:])}" + result = subprocess.run(shlex.split(cmd), check=False) # noqa: S603 + cmd = f"{binary} -m coverage report" + subprocess.run(shlex.split(cmd), check=False) # noqa: S603 + if result.returncode != 0: + print(result.stderr) + sys.exit(result.returncode) + # Run local tests. ret = pytest.main(TEST_ARGS + sys.argv[1:]) if ret != 0: diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index 939423ffc..f3d86973b 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -431,6 +431,9 @@ def handle_test_env() -> None: # We do not want the default client_context to be initialized. write_env("DISABLE_CONTEXT") + if test_name == "numpy": + UV_ARGS.append("--with numpy") + if test_name == "perf": data_dir = ROOT / "specifications/source/benchmarking/data" if not data_dir.exists(): @@ -458,7 +461,6 @@ def handle_test_env() -> None: # Keep in sync with combine-coverage.sh. # coverage >=5 is needed for relative_files=true. UV_ARGS.append("--group coverage") - TEST_ARGS = f"{TEST_ARGS} --cov" write_env("COVERAGE") if opts.green_framework: diff --git a/.evergreen/scripts/upload-codecov.sh b/.evergreen/scripts/upload-codecov.sh index 75bd9d9e2..5c1d84c55 100755 --- a/.evergreen/scripts/upload-codecov.sh +++ b/.evergreen/scripts/upload-codecov.sh @@ -8,18 +8,20 @@ ROOT=$(dirname "$(dirname $HERE)") pushd $ROOT > /dev/null export FNAME=coverage.xml - -if [ -n "${is_patch:-}" ]; then - echo "This is a patch build, not running codecov" - exit 0 -fi +REQUESTER=${requester:-} if [ ! -f ".coverage" ]; then echo "There are no coverage results, not running codecov" exit 0 fi -echo "Uploading..." +if [[ "${REQUESTER}" == "github_pr" || "${REQUESTER}" == "commit" ]]; then + echo "Uploading codecov for $REQUESTER..." +else + echo "Error: requester must be 'github_pr' or 'commit', got '${REQUESTER}'" >&2 + exit 1 +fi + printf 'sha: %s\n' "$github_commit" printf 'flag: %s-%s\n' "$build_variant" "$task_name" printf 'file: %s\n' "$FNAME" @@ -40,18 +42,16 @@ codecov_args=( if [ -n "${github_pr_number:-}" ]; then printf 'branch: %s:%s\n' "$github_author" "$github_pr_head_branch" printf 'pr: %s\n' "$github_pr_number" - uv tool run --from codecov-cli codecovcli \ "${codecov_args[@]}" \ --pr "${github_pr_number}" \ --branch "${github_author}:${github_pr_head_branch}" else printf 'branch: %s\n' "$branch_name" - uv tool run --from codecov-cli codecovcli \ "${codecov_args[@]}" \ --branch "${branch_name}" fi -echo "Uploading...done." +echo "Uploading codecov for $REQUESTER... done." popd > /dev/null diff --git a/.evergreen/scripts/utils.py b/.evergreen/scripts/utils.py index 2bc9c720d..02238645c 100644 --- a/.evergreen/scripts/utils.py +++ b/.evergreen/scripts/utils.py @@ -44,6 +44,7 @@ TEST_SUITE_MAP = { "mockupdb": "mockupdb", "ocsp": "ocsp", "perf": "perf", + "numpy": "", } # Tests that require a sub test suite. diff --git a/justfile b/justfile index 082b6ea17..82b1ac91d 100644 --- a/justfile +++ b/justfile @@ -60,8 +60,9 @@ test *args="-v --durations=5 --maxfail=10": && resync uv run --extra test python -m pytest {{args}} [group('test')] -test-numpy: && resync - uv run --extra test --with numpy python -m pytest test/test_bson.py +test-numpy *args="": && resync + just setup-tests numpy {{args}} + just run-tests test/test_bson.py [group('test')] run-tests *args: && resync diff --git a/pyproject.toml b/pyproject.toml index acc9fa5b0..9b3287834 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,6 @@ dev = [] pip = ["pip>=20.2"] gevent = ["gevent>=21.12"] coverage = [ - "pytest-cov>=4.0.0", "coverage[toml]>=5,<=7.10.7" ] mockupdb = [ diff --git a/test/asynchronous/test_ssl.py b/test/asynchronous/test_ssl.py index 0ce3e8bba..7fe57e850 100644 --- a/test/asynchronous/test_ssl.py +++ b/test/asynchronous/test_ssl.py @@ -48,19 +48,11 @@ from pymongo.write_concern import WriteConcern _HAVE_PYOPENSSL = False try: - # All of these must be available to use PyOpenSSL - import OpenSSL - import requests - import service_identity - - # Ensure service_identity>=18.1 is installed - from service_identity.pyopenssl import verify_ip_address - - from pymongo.ocsp_support import _load_trusted_ca_certs + from pymongo import pyopenssl_context _HAVE_PYOPENSSL = True except ImportError: - _load_trusted_ca_certs = None # type: ignore + pass if HAVE_SSL: @@ -136,11 +128,6 @@ class TestClientSSL(AsyncPyMongoTestCase): def test_use_pyopenssl_when_available(self): self.assertTrue(HAVE_PYSSL) - @unittest.skipUnless(_HAVE_PYOPENSSL, "Cannot test without PyOpenSSL") - def test_load_trusted_ca_certs(self): - trusted_ca_certs = _load_trusted_ca_certs(CA_BUNDLE_PEM) - self.assertEqual(2, len(trusted_ca_certs)) - class TestSSL(AsyncIntegrationTest): saved_port: int diff --git a/test/test_ssl.py b/test/test_ssl.py index b1e9a65eb..77bb086ec 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -48,19 +48,11 @@ from pymongo.write_concern import WriteConcern _HAVE_PYOPENSSL = False try: - # All of these must be available to use PyOpenSSL - import OpenSSL - import requests - import service_identity - - # Ensure service_identity>=18.1 is installed - from service_identity.pyopenssl import verify_ip_address - - from pymongo.ocsp_support import _load_trusted_ca_certs + from pymongo import pyopenssl_context _HAVE_PYOPENSSL = True except ImportError: - _load_trusted_ca_certs = None # type: ignore + pass if HAVE_SSL: @@ -136,11 +128,6 @@ class TestClientSSL(PyMongoTestCase): def test_use_pyopenssl_when_available(self): self.assertTrue(HAVE_PYSSL) - @unittest.skipUnless(_HAVE_PYOPENSSL, "Cannot test without PyOpenSSL") - def test_load_trusted_ca_certs(self): - trusted_ca_certs = _load_trusted_ca_certs(CA_BUNDLE_PEM) - self.assertEqual(2, len(trusted_ca_certs)) - class TestSSL(IntegrationTest): saved_port: int diff --git a/uv.lock b/uv.lock index 7d0ff9fb0..78d0cc213 100644 --- a/uv.lock +++ b/uv.lock @@ -1562,7 +1562,6 @@ zstd = [ [package.dev-dependencies] coverage = [ { name = "coverage", extra = ["toml"] }, - { name = "pytest-cov" }, ] gevent = [ { name = "gevent" }, @@ -1612,10 +1611,7 @@ requires-dist = [ provides-extras = ["aws", "docs", "encryption", "gssapi", "ocsp", "snappy", "test", "zstd"] [package.metadata.requires-dev] -coverage = [ - { name = "coverage", extras = ["toml"], specifier = ">=5,<=7.10.7" }, - { name = "pytest-cov", specifier = ">=4.0.0" }, -] +coverage = [{ name = "coverage", extras = ["toml"], specifier = ">=5,<=7.10.7" }] dev = [] gevent = [{ name = "gevent", specifier = ">=21.12" }] mockupdb = [{ name = "mockupdb", git = "https://github.com/mongodb-labs/mongo-mockup-db?rev=master" }] @@ -1763,21 +1759,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] -[[package]] -name = "pytest-cov" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage", extra = ["toml"] }, - { name = "pluggy" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" From 6923641626216a58fb3a8132ab04cbd7ae260c2f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 18 Feb 2026 14:42:00 -0600 Subject: [PATCH 09/13] PYTHON-5729 Pin setuptools when using older gevent (#2708) --- .evergreen/run-tests.sh | 1 + .evergreen/scripts/setup_tests.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 095b7938d..0785bcf01 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -38,6 +38,7 @@ trap "cleanup_tests" SIGINT ERR # Start the test runner. echo "Running tests with UV_PYTHON=${UV_PYTHON:-}..." +echo "UV_ARGS=${UV_ARGS}" uv run ${UV_ARGS} --reinstall-package pymongo .evergreen/scripts/run_tests.py "$@" echo "Running tests with UV_PYTHON=${UV_PYTHON:-}... done." diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index f3d86973b..cdecb259a 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -466,6 +466,9 @@ def handle_test_env() -> None: if opts.green_framework: framework = opts.green_framework or os.environ["GREEN_FRAMEWORK"] UV_ARGS.append(f"--group {framework}") + if framework == "gevent" and opts.test_min_deps: + # PYTHON-5729. This can be removed when the min supported gevent is moved to 25.9.1. + UV_ARGS.append('--with "setuptools==81.0"') else: TEST_ARGS = f"-v --durations=5 {TEST_ARGS}" From cbd82e75e7e236f8e2fd087c6f6df894c859574d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 06:26:49 -0600 Subject: [PATCH 10/13] Bump the actions group with 2 updates (#2711) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test-python.yml | 18 +++++++++--------- .github/workflows/zizmor.yml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 388f68bbe..e8fb9b918 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -68,7 +68,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: ${{ matrix.python-version }} @@ -90,7 +90,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -118,7 +118,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -143,7 +143,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -162,7 +162,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -184,7 +184,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "${{matrix.python}}" @@ -205,7 +205,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: enable-cache: true python-version: "3.10" @@ -295,7 +295,7 @@ jobs: with: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v7 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7 with: python-version: "3.9" - id: setup-mongodb diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 26f75fa79..c64a9e32e 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -18,4 +18,4 @@ jobs: with: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@135698455da5c3b3e55f73f4419e481ab68cdd95 # v0.4.1 + uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0 From edd0e0698f827f4d274703590d43da2e3f98fbc0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 20 Feb 2026 11:56:30 -0600 Subject: [PATCH 11/13] PYTHON-5708 Temporarily skip some BSON encryption tests (#2709) --- test/asynchronous/test_encryption.py | 4 ++++ test/test_encryption.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index b1dbc73f3..9650f7043 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -876,6 +876,8 @@ class TestViews(AsyncEncryptionIntegrationTest): class TestCorpus(AsyncEncryptionIntegrationTest): + # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. + @async_client_context.require_version_max(6, 99) @unittest.skipUnless(any(AWS_CREDS.values()), "AWS environment credentials are not set") async def asyncSetUp(self): await super().asyncSetUp() @@ -1052,6 +1054,8 @@ class TestBsonSizeBatches(AsyncEncryptionIntegrationTest): client_encrypted: AsyncMongoClient listener: OvertCommandListener + # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. + @async_client_context.require_version_max(6, 99) async def asyncSetUp(self): await super().asyncSetUp() db = async_client_context.client.db diff --git a/test/test_encryption.py b/test/test_encryption.py index 88d37cfa0..af9f2e3df 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -872,6 +872,8 @@ class TestViews(EncryptionIntegrationTest): class TestCorpus(EncryptionIntegrationTest): + # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. + @client_context.require_version_max(6, 99) @unittest.skipUnless(any(AWS_CREDS.values()), "AWS environment credentials are not set") def setUp(self): super().setUp() @@ -1048,6 +1050,8 @@ class TestBsonSizeBatches(EncryptionIntegrationTest): client_encrypted: MongoClient listener: OvertCommandListener + # PYTHON-5708: Encryption tests sending large payloads fail on some mongocryptd versions. + @client_context.require_version_max(6, 99) def setUp(self): super().setUp() db = client_context.client.db From 908102d776ee5608f79df76098f6287d6edb6952 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 20 Feb 2026 13:02:52 -0600 Subject: [PATCH 12/13] PYTHON-5732 Use mongodb-runner in Evergreen Tests (#2703) --- .evergreen/scripts/run_server.py | 4 ++-- .evergreen/scripts/setup_tests.py | 3 ++- .evergreen/scripts/stop-server.sh | 4 ++-- .evergreen/scripts/utils.py | 2 +- CONTRIBUTING.md | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.evergreen/scripts/run_server.py b/.evergreen/scripts/run_server.py index a35fbb57a..9757eb3a4 100644 --- a/.evergreen/scripts/run_server.py +++ b/.evergreen/scripts/run_server.py @@ -12,7 +12,7 @@ def set_env(name: str, value: Any = "1") -> None: def start_server(): opts, extra_opts = get_test_options( - "Run a MongoDB server. All given flags will be passed to run-orchestration.sh in DRIVERS_TOOLS.", + "Run a MongoDB server. All given flags will be passed to run-mongodb.sh in DRIVERS_TOOLS.", require_sub_test_name=False, allow_extra_opts=True, ) @@ -51,7 +51,7 @@ def start_server(): elif opts.quiet: extra_opts.append("-q") - cmd = ["bash", f"{DRIVERS_TOOLS}/.evergreen/run-orchestration.sh", *extra_opts] + cmd = ["bash", f"{DRIVERS_TOOLS}/.evergreen/run-mongodb.sh", "start", *extra_opts] run_command(cmd, cwd=DRIVERS_TOOLS) diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py index cdecb259a..009de9f1d 100644 --- a/.evergreen/scripts/setup_tests.py +++ b/.evergreen/scripts/setup_tests.py @@ -324,7 +324,8 @@ def handle_test_env() -> None: version = os.environ.get("VERSION", "latest") cmd = [ "bash", - f"{DRIVERS_TOOLS}/.evergreen/run-orchestration.sh", + f"{DRIVERS_TOOLS}/.evergreen/run-mongodb.sh", + "start", "--ssl", "--version", version, diff --git a/.evergreen/scripts/stop-server.sh b/.evergreen/scripts/stop-server.sh index 7599387f5..045a655cb 100755 --- a/.evergreen/scripts/stop-server.sh +++ b/.evergreen/scripts/stop-server.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Stop a server that was started using run-orchestration.sh in DRIVERS_TOOLS. +# Stop a server that was started using run-mongodb.sh in DRIVERS_TOOLS. set -eu HERE=$(dirname ${BASH_SOURCE:-$0}) @@ -11,4 +11,4 @@ if [ -f $HERE/env.sh ]; then source $HERE/env.sh fi -bash ${DRIVERS_TOOLS}/.evergreen/stop-orchestration.sh +bash ${DRIVERS_TOOLS}/.evergreen/run-mongodb.sh stop diff --git a/.evergreen/scripts/utils.py b/.evergreen/scripts/utils.py index 02238645c..914cd9ac6 100644 --- a/.evergreen/scripts/utils.py +++ b/.evergreen/scripts/utils.py @@ -52,7 +52,7 @@ SUB_TEST_REQUIRED = ["auth_aws", "auth_oidc", "kms", "mod_wsgi", "perf"] EXTRA_TESTS = ["mod_wsgi", "aws_lambda", "doctest"] -# Tests that do not use run-orchestration directly. +# Tests that do not use run-mongodb directly. NO_RUN_ORCHESTRATION = [ "auth_oidc", "atlas_connect", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb1c35fc8..a4eafeddc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -197,7 +197,7 @@ the pages will re-render and the browser will automatically refresh. version of Python, set `UV_PYTHON` before running `just install`. - Ensure you have started the appropriate Mongo Server(s). You can run `just run-server` with optional args to set up the server. All given options will be passed to - [`run-orchestration.sh`](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-orchestration.sh). Run `$DRIVERS_TOOLS/.evergreen/run-orchestration.sh -h` + [`run-mongodb.sh`](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-mongodb.sh). Run `$DRIVERS_TOOLS/.evergreen/run-mongodb.sh start -h` for a full list of options. - Run `just test` or `pytest` to run all of the tests. - Append `test/.py::::` to run @@ -396,7 +396,7 @@ To run any of the test suites with minimum supported dependencies, pass `--test- - If adding new tests files that should only be run for that test suite, add a pytest marker to the file and add to the list of pytest markers in `pyproject.toml`. Then add the test suite to the `TEST_SUITE_MAP` in `.evergreen/scripts/utils.py`. If for some reason it is not a pytest-runnable test, add it to the list of `EXTRA_TESTS` instead. -- If the test uses Atlas or otherwise doesn't use `run-orchestration.sh`, add it to the `NO_RUN_ORCHESTRATION` list in +- If the test uses Atlas or otherwise doesn't use `run-mongodb.sh`, add it to the `NO_RUN_ORCHESTRATION` list in `.evergreen/scripts/utils.py`. - If there is something special required to run the local server or there is an extra flag that should always be set like `AUTH`, add that logic to `.evergreen/scripts/run_server.py`. From 84814b2a7275c7f6b8b5221e1ce071b358823e06 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 23 Feb 2026 13:18:24 -0500 Subject: [PATCH 13/13] PYTHON-5731 - Server selection deprioritization only for overload errors on replica sets (#2710) --- pymongo/asynchronous/mongo_client.py | 6 +- pymongo/synchronous/mongo_client.py | 6 +- test/asynchronous/test_retryable_reads.py | 78 +++++++++++++++++++++++ test/test_retryable_reads.py | 78 +++++++++++++++++++++++ 4 files changed, 166 insertions(+), 2 deletions(-) diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 4f3c43f23..7fa098390 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -2825,7 +2825,11 @@ class _ClientConnectionRetryable(Generic[T]): if self._last_error is None: self._last_error = exc - if self._server is not None: + if ( + self._server is not None + and self._client.topology_description.topology_type_name == "Sharded" + or exc.has_error_label("SystemOverloadedError") + ): self._deprioritized_servers.append(self._server) def _is_not_eligible_for_retry(self) -> bool: diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index cd0d19141..badbeac09 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -2815,7 +2815,11 @@ class _ClientConnectionRetryable(Generic[T]): if self._last_error is None: self._last_error = exc - if self._server is not None: + if ( + self._server is not None + and self._client.topology_description.topology_type_name == "Sharded" + or exc.has_error_label("SystemOverloadedError") + ): self._deprioritized_servers.append(self._server) def _is_not_eligible_for_retry(self) -> bool: diff --git a/test/asynchronous/test_retryable_reads.py b/test/asynchronous/test_retryable_reads.py index 47ac91b0f..6adfaaae1 100644 --- a/test/asynchronous/test_retryable_reads.py +++ b/test/asynchronous/test_retryable_reads.py @@ -261,6 +261,84 @@ class TestRetryableReads(AsyncIntegrationTest): self.assertEqual(command_docs[0]["lsid"], command_docs[1]["lsid"]) self.assertIsNot(command_docs[0], command_docs[1]) + @async_client_context.require_replica_set + @async_client_context.require_secondaries_count(1) + @async_client_context.require_failCommand_fail_point + @async_client_context.require_version_min(4, 4, 0) + async def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = await self.async_rs_or_single_client( + event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + ) + + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 6, + }, + } + await async_set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + await client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred on different servers. + assert listener.failed_events[0].connection_id != listener.succeeded_events[0].connection_id + + @async_client_context.require_replica_set + @async_client_context.require_secondaries_count(1) + @async_client_context.require_failCommand_fail_point + @async_client_context.require_version_min(4, 4, 0) + async def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_on_the_same_replicaset_server( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = await self.async_rs_or_single_client( + event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + ) + + # 2. Configure a fail point with the RetryableError error label. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError"], + "errorCode": 6, + }, + } + await async_set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + await client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred the same server. + assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + if __name__ == "__main__": unittest.main() diff --git a/test/test_retryable_reads.py b/test/test_retryable_reads.py index c9f72ae54..18cd669f1 100644 --- a/test/test_retryable_reads.py +++ b/test/test_retryable_reads.py @@ -259,6 +259,84 @@ class TestRetryableReads(IntegrationTest): self.assertEqual(command_docs[0]["lsid"], command_docs[1]["lsid"]) self.assertIsNot(command_docs[0], command_docs[1]) + @client_context.require_replica_set + @client_context.require_secondaries_count(1) + @client_context.require_failCommand_fail_point + @client_context.require_version_min(4, 4, 0) + def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = self.rs_or_single_client( + event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + ) + + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 6, + }, + } + set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred on different servers. + assert listener.failed_events[0].connection_id != listener.succeeded_events[0].connection_id + + @client_context.require_replica_set + @client_context.require_secondaries_count(1) + @client_context.require_failCommand_fail_point + @client_context.require_version_min(4, 4, 0) + def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_on_the_same_replicaset_server( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = self.rs_or_single_client( + event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + ) + + # 2. Configure a fail point with the RetryableError error label. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError"], + "errorCode": 6, + }, + } + set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred the same server. + assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + if __name__ == "__main__": unittest.main()