From 3c5e71a1cb28b695bc2eec4c3927ef6af56835a8 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 14 Oct 2024 07:32:38 -0500 Subject: [PATCH 1/5] PYTHON-4862 Fix handling of interrupt_loop in unified test runner (#1924) --- test/asynchronous/unified_format.py | 8 +++++++- test/unified_format.py | 8 +++++++- test/unified_format_shared.py | 5 ----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index 4c3742295..42bda59cb 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -36,7 +36,6 @@ from test.asynchronous import ( unittest, ) from test.unified_format_shared import ( - IS_INTERRUPTED, KMS_TLS_OPTS, PLACEHOLDER_MAP, SKIP_CSOT_TESTS, @@ -104,6 +103,13 @@ from pymongo.write_concern import WriteConcern _IS_SYNC = False +IS_INTERRUPTED = False + + +def interrupt_loop(): + global IS_INTERRUPTED + IS_INTERRUPTED = True + async def is_run_on_requirement_satisfied(requirement): topology_satisfied = True diff --git a/test/unified_format.py b/test/unified_format.py index 6a19082b8..13ab0af69 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -36,7 +36,6 @@ from test import ( unittest, ) from test.unified_format_shared import ( - IS_INTERRUPTED, KMS_TLS_OPTS, PLACEHOLDER_MAP, SKIP_CSOT_TESTS, @@ -104,6 +103,13 @@ from pymongo.write_concern import WriteConcern _IS_SYNC = True +IS_INTERRUPTED = False + + +def interrupt_loop(): + global IS_INTERRUPTED + IS_INTERRUPTED = True + def is_run_on_requirement_satisfied(requirement): topology_satisfied = True diff --git a/test/unified_format_shared.py b/test/unified_format_shared.py index d11624476..f1b908a7a 100644 --- a/test/unified_format_shared.py +++ b/test/unified_format_shared.py @@ -139,11 +139,6 @@ elif OIDC_ENV == "gcp": } -def interrupt_loop(): - global IS_INTERRUPTED - IS_INTERRUPTED = True - - def with_metaclass(meta, *bases): """Create a base class with a metaclass. From 9ba780cac256720be5c3c5051c7f8a19d27693d5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 14 Oct 2024 07:34:01 -0500 Subject: [PATCH 2/5] PYTHON-4861 Ensure hatch is isolated in Evergreen (#1923) --- .evergreen/hatch.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.evergreen/hatch.sh b/.evergreen/hatch.sh index db0da2f4d..8f862c39d 100644 --- a/.evergreen/hatch.sh +++ b/.evergreen/hatch.sh @@ -18,17 +18,22 @@ if [ -n "$SKIP_HATCH" ]; then run_hatch() { bash ./.evergreen/run-tests.sh } -elif $PYTHON_BINARY -m hatch --version; then - run_hatch() { - $PYTHON_BINARY -m hatch run "$@" - } -else # No toolchain hatch present, set up virtualenv before installing hatch +else # Set up virtualenv before installing hatch # Use a random venv name because the encryption tasks run this script multiple times in the same run. ENV_NAME=hatchenv-$RANDOM createvirtualenv "$PYTHON_BINARY" $ENV_NAME # shellcheck disable=SC2064 trap "deactivate; rm -rf $ENV_NAME" EXIT HUP python -m pip install -q hatch + + # Ensure hatch does not write to user or global locations. + touch hatch_config.toml + HATCH_CONFIG=$(pwd)/hatch_config.toml + export HATCH_CONFIG + hatch config restore + hatch config set dirs.data ".hatch/data" + hatch config set dirs.cache ".hatch/cache" + run_hatch() { python -m hatch run "$@" } From 3cc722e9105d5818d57739d623d985d69b0eb626 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 14 Oct 2024 14:05:22 -0500 Subject: [PATCH 3/5] PYTHON-4838 Generate OCSP build variants using shrub.py (#1910) --- .evergreen/config.yml | 174 +++++++++++++++++++++----- .evergreen/scripts/generate_config.py | 167 ++++++++++++++++++++++++ 2 files changed, 308 insertions(+), 33 deletions(-) create mode 100644 .evergreen/scripts/generate_config.py diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 1ef875150..dee4b608e 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -2826,42 +2826,150 @@ buildvariants: - "test-6.0-standalone" - "test-5.0-standalone" -- matrix_name: "ocsp-test" - matrix_spec: - platform: rhel8 - python-version: ["3.9", "3.10", "pypy3.9", "pypy3.10"] - mongodb-version: ["4.4", "5.0", "6.0", "7.0", "8.0", "latest"] - auth: "noauth" - ssl: "ssl" - display_name: "OCSP test ${platform} ${python-version} ${mongodb-version}" - batchtime: 20160 # 14 days +# OCSP test matrix. +- name: ocsp-test-rhel8-v4.4-py3.9 tasks: - - name: ".ocsp" - -- matrix_name: "ocsp-test-windows" - matrix_spec: - platform: windows - python-version-windows: ["3.9", "3.10"] - mongodb-version: ["4.4", "5.0", "6.0", "7.0", "8.0", "latest"] - auth: "noauth" - ssl: "ssl" - display_name: "OCSP test ${platform} ${python-version-windows} ${mongodb-version}" - batchtime: 20160 # 14 days + - name: .ocsp + display_name: OCSP test RHEL8 v4.4 py3.9 + run_on: + - rhel87-small + batchtime: 20160 + expansions: + AUTH: noauth + SSL: ssl + TOPOLOGY: server + VERSION: "4.4" + PYTHON_BINARY: /opt/python/3.9/bin/python3 +- name: ocsp-test-rhel8-v5.0-py3.10 tasks: - # Windows MongoDB servers do not staple OCSP responses and only support RSA. - - name: ".ocsp-rsa !.ocsp-staple" - -- matrix_name: "ocsp-test-macos" - matrix_spec: - platform: macos - mongodb-version: ["4.4", "5.0", "6.0", "7.0", "8.0", "latest"] - auth: "noauth" - ssl: "ssl" - display_name: "OCSP test ${platform} ${mongodb-version}" - batchtime: 20160 # 14 days + - name: .ocsp + display_name: OCSP test RHEL8 v5.0 py3.10 + run_on: + - rhel87-small + batchtime: 20160 + expansions: + AUTH: noauth + SSL: ssl + TOPOLOGY: server + VERSION: "5.0" + PYTHON_BINARY: /opt/python/3.10/bin/python3 +- name: ocsp-test-rhel8-v6.0-py3.11 tasks: - # macOS MongoDB servers do not staple OCSP responses and only support RSA. - - name: ".ocsp-rsa !.ocsp-staple" + - name: .ocsp + display_name: OCSP test RHEL8 v6.0 py3.11 + run_on: + - rhel87-small + batchtime: 20160 + expansions: + AUTH: noauth + SSL: ssl + TOPOLOGY: server + VERSION: "6.0" + PYTHON_BINARY: /opt/python/3.11/bin/python3 +- name: ocsp-test-rhel8-v7.0-py3.12 + tasks: + - name: .ocsp + display_name: OCSP test RHEL8 v7.0 py3.12 + run_on: + - rhel87-small + batchtime: 20160 + expansions: + AUTH: noauth + SSL: ssl + TOPOLOGY: server + VERSION: "7.0" + PYTHON_BINARY: /opt/python/3.12/bin/python3 +- name: ocsp-test-rhel8-v8.0-py3.13 + tasks: + - name: .ocsp + display_name: OCSP test RHEL8 v8.0 py3.13 + run_on: + - rhel87-small + batchtime: 20160 + expansions: + AUTH: noauth + SSL: ssl + TOPOLOGY: server + VERSION: "8.0" + PYTHON_BINARY: /opt/python/3.13/bin/python3 +- name: ocsp-test-rhel8-rapid-pypy3.9 + tasks: + - name: .ocsp + display_name: OCSP test RHEL8 rapid pypy3.9 + run_on: + - rhel87-small + batchtime: 20160 + expansions: + AUTH: noauth + SSL: ssl + TOPOLOGY: server + VERSION: rapid + PYTHON_BINARY: /opt/python/pypy3.9/bin/python3 +- name: ocsp-test-rhel8-latest-pypy3.10 + tasks: + - name: .ocsp + display_name: OCSP test RHEL8 latest pypy3.10 + run_on: + - rhel87-small + batchtime: 20160 + expansions: + AUTH: noauth + SSL: ssl + TOPOLOGY: server + VERSION: latest + PYTHON_BINARY: /opt/python/pypy3.10/bin/python3 +- name: ocsp-test-win64-v4.4-py3.9 + tasks: + - name: .ocsp-rsa !.ocsp-staple + display_name: OCSP test Win64 v4.4 py3.9 + run_on: + - windows-64-vsMulti-small + batchtime: 20160 + expansions: + AUTH: noauth + SSL: ssl + TOPOLOGY: server + VERSION: "4.4" + PYTHON_BINARY: C:/python/Python39/python.exe +- name: ocsp-test-win64-v8.0-py3.13 + tasks: + - name: .ocsp-rsa !.ocsp-staple + display_name: OCSP test Win64 v8.0 py3.13 + run_on: + - windows-64-vsMulti-small + batchtime: 20160 + expansions: + AUTH: noauth + SSL: ssl + TOPOLOGY: server + VERSION: "8.0" + PYTHON_BINARY: C:/python/Python313/python.exe +- name: ocsp-test-macos-v4.4-py3.9 + tasks: + - name: .ocsp-rsa !.ocsp-staple + display_name: OCSP test macOS v4.4 py3.9 + run_on: + - macos-14 + batchtime: 20160 + expansions: + AUTH: noauth + SSL: ssl + TOPOLOGY: server + VERSION: "4.4" + PYTHON_BINARY: /Library/Frameworks/Python.Framework/Versions/3.9/bin/python3 +- name: ocsp-test-macos-v8.0-py3.13 + tasks: + - name: .ocsp-rsa !.ocsp-staple + display_name: OCSP test macOS v8.0 py3.13 + run_on: + - macos-14 + batchtime: 20160 + expansions: + AUTH: noauth + SSL: ssl + TOPOLOGY: server + VERSION: "8.0" + PYTHON_BINARY: /Library/Frameworks/Python.Framework/Versions/3.13/bin/python3 - matrix_name: "oidc-auth-test" matrix_spec: diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py new file mode 100644 index 000000000..e98e527b7 --- /dev/null +++ b/.evergreen/scripts/generate_config.py @@ -0,0 +1,167 @@ +# /// script +# requires-python = ">=3.9" +# dependencies = [ +# "shrub.py>=3.2.0", +# "pyyaml>=6.0.2" +# ] +# /// + +# Note: Run this file with `hatch run`, `pipx run`, or `uv run`. +from __future__ import annotations + +from dataclasses import dataclass +from itertools import cycle, product, zip_longest +from typing import Any + +from shrub.v3.evg_build_variant import BuildVariant +from shrub.v3.evg_project import EvgProject +from shrub.v3.evg_task import EvgTaskRef +from shrub.v3.shrub_service import ShrubService + +############## +# Globals +############## + +ALL_VERSIONS = ["4.0", "4.4", "5.0", "6.0", "7.0", "8.0", "rapid", "latest"] +CPYTHONS = ["3.9", "3.10", "3.11", "3.12", "3.13"] +PYPYS = ["pypy3.9", "pypy3.10"] +ALL_PYTHONS = CPYTHONS + PYPYS +BATCHTIME_WEEK = 10080 +HOSTS = dict() + + +@dataclass +class Host: + name: str + run_on: str + display_name: str + + +HOSTS["rhel8"] = Host("rhel8", "rhel87-small", "RHEL8") +HOSTS["win64"] = Host("win64", "windows-64-vsMulti-small", "Win64") +HOSTS["macos"] = Host("macos", "macos-14", "macOS") + + +############## +# Helpers +############## + + +def create_variant( + task_names: list[str], + display_name: str, + *, + python: str | None = None, + version: str | None = None, + host: str | None = None, + **kwargs: Any, +) -> BuildVariant: + """Create a build variant for the given inputs.""" + task_refs = [EvgTaskRef(name=n) for n in task_names] + kwargs.setdefault("expansions", dict()) + expansions = kwargs.pop("expansions", dict()).copy() + host = host or "rhel8" + run_on = [HOSTS[host].run_on] + name = display_name.replace(" ", "-").lower() + if python: + expansions["PYTHON_BINARY"] = get_python_binary(python, host) + if version: + expansions["VERSION"] = version + expansions = expansions or None + return BuildVariant( + name=name, + display_name=display_name, + tasks=task_refs, + expansions=expansions, + run_on=run_on, + **kwargs, + ) + + +def get_python_binary(python: str, host: str) -> str: + """Get the appropriate python binary given a python version and host.""" + if host == "win64": + is_32 = python.startswith("32-bit") + if is_32: + _, python = python.split() + base = "C:/python/32" + else: + base = "C:/python" + python = python.replace(".", "") + return f"{base}/Python{python}/python.exe" + + if host == "rhel8": + return f"/opt/python/{python}/bin/python3" + + if host == "macos": + return f"/Library/Frameworks/Python.Framework/Versions/{python}/bin/python3" + + raise ValueError(f"no match found for python {python} on {host}") + + +def get_display_name(base: str, host: str, version: str, python: str) -> str: + """Get the display name of a variant.""" + if version not in ["rapid", "latest"]: + version = f"v{version}" + if not python.startswith("pypy"): + python = f"py{python}" + return f"{base} {HOSTS[host].display_name} {version} {python}" + + +def zip_cycle(*iterables, empty_default=None): + """Get all combinations of the inputs, cycling over the shorter list(s).""" + cycles = [cycle(i) for i in iterables] + for _ in zip_longest(*iterables): + yield tuple(next(i, empty_default) for i in cycles) + + +############## +# Variants +############## + + +def create_ocsp_variants() -> list[BuildVariant]: + variants = [] + batchtime = BATCHTIME_WEEK * 2 + expansions = dict(AUTH="noauth", SSL="ssl", TOPOLOGY="server") + base_display = "OCSP test" + + # OCSP tests on rhel8 with all servers v4.4+ and all python versions. + versions = [v for v in ALL_VERSIONS if v != "4.0"] + for version, python in zip_cycle(versions, ALL_PYTHONS): + host = "rhel8" + variant = create_variant( + [".ocsp"], + get_display_name(base_display, host, version, python), + python=python, + version=version, + host=host, + expansions=expansions, + batchtime=batchtime, + ) + variants.append(variant) + + # OCSP tests on Windows and MacOS. + # MongoDB servers on these hosts do not staple OCSP responses and only support RSA. + for host, version in product(["win64", "macos"], ["4.4", "8.0"]): + python = CPYTHONS[0] if version == "4.4" else CPYTHONS[-1] + variant = create_variant( + [".ocsp-rsa !.ocsp-staple"], + get_display_name(base_display, host, version, python), + python=python, + version=version, + host=host, + expansions=expansions, + batchtime=batchtime, + ) + variants.append(variant) + + return variants + + +################## +# Generate Config +################## + +project = EvgProject(tasks=None, buildvariants=create_ocsp_variants()) +print(ShrubService.generate_yaml(project)) # noqa: T201 From a911245bde1377c485f06dfd5373d159b7e8aff7 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Mon, 14 Oct 2024 15:06:42 -0700 Subject: [PATCH 4/5] PYTHON-4866 Fix test_command_cursor_to_list_csot_applied (#1926) --- test/asynchronous/test_cursor.py | 14 ++++++-------- test/test_cursor.py | 14 ++++++-------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/test/asynchronous/test_cursor.py b/test/asynchronous/test_cursor.py index e79ad0064..ee0a757ed 100644 --- a/test/asynchronous/test_cursor.py +++ b/test/asynchronous/test_cursor.py @@ -1412,12 +1412,11 @@ class TestCursor(AsyncIntegrationTest): self.assertEqual(len(docs), 2) async def test_to_list_csot_applied(self): - client = await self.async_single_client(timeoutMS=500) + client = await self.async_single_client(timeoutMS=500, w=1) + coll = client.pymongo.test # Initialize the client with a larger timeout to help make test less flakey with pymongo.timeout(10): - await client.admin.command("ping") - coll = client.pymongo.test - await coll.insert_many([{} for _ in range(5)]) + await coll.insert_many([{} for _ in range(5)]) cursor = coll.find({"$where": delay(1)}) with self.assertRaises(PyMongoError) as ctx: await cursor.to_list() @@ -1454,12 +1453,11 @@ class TestCursor(AsyncIntegrationTest): @async_client_context.require_failCommand_blockConnection async def test_command_cursor_to_list_csot_applied(self): - client = await self.async_single_client(timeoutMS=500) + client = await self.async_single_client(timeoutMS=500, w=1) + coll = client.pymongo.test # Initialize the client with a larger timeout to help make test less flakey with pymongo.timeout(10): - await client.admin.command("ping") - coll = client.pymongo.test - await coll.insert_many([{} for _ in range(5)]) + await coll.insert_many([{} for _ in range(5)]) fail_command = { "configureFailPoint": "failCommand", "mode": {"times": 5}, diff --git a/test/test_cursor.py b/test/test_cursor.py index 7c073bf35..7a6dfc942 100644 --- a/test/test_cursor.py +++ b/test/test_cursor.py @@ -1403,12 +1403,11 @@ class TestCursor(IntegrationTest): self.assertEqual(len(docs), 2) def test_to_list_csot_applied(self): - client = self.single_client(timeoutMS=500) + client = self.single_client(timeoutMS=500, w=1) + coll = client.pymongo.test # Initialize the client with a larger timeout to help make test less flakey with pymongo.timeout(10): - client.admin.command("ping") - coll = client.pymongo.test - coll.insert_many([{} for _ in range(5)]) + coll.insert_many([{} for _ in range(5)]) cursor = coll.find({"$where": delay(1)}) with self.assertRaises(PyMongoError) as ctx: cursor.to_list() @@ -1445,12 +1444,11 @@ class TestCursor(IntegrationTest): @client_context.require_failCommand_blockConnection def test_command_cursor_to_list_csot_applied(self): - client = self.single_client(timeoutMS=500) + client = self.single_client(timeoutMS=500, w=1) + coll = client.pymongo.test # Initialize the client with a larger timeout to help make test less flakey with pymongo.timeout(10): - client.admin.command("ping") - coll = client.pymongo.test - coll.insert_many([{} for _ in range(5)]) + coll.insert_many([{} for _ in range(5)]) fail_command = { "configureFailPoint": "failCommand", "mode": {"times": 5}, From 9e38c54fa03d0f719a43ff023894c2a1ad9b5480 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Mon, 14 Oct 2024 15:25:21 -0700 Subject: [PATCH 5/5] PYTHON-4861 Fix HATCH_CONFIG on cygwin (#1927) --- .evergreen/hatch.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.evergreen/hatch.sh b/.evergreen/hatch.sh index 8f862c39d..6f3d36b38 100644 --- a/.evergreen/hatch.sh +++ b/.evergreen/hatch.sh @@ -29,6 +29,9 @@ else # Set up virtualenv before installing hatch # Ensure hatch does not write to user or global locations. touch hatch_config.toml HATCH_CONFIG=$(pwd)/hatch_config.toml + if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin + HATCH_CONFIG=$(cygpath -m "$HATCH_CONFIG") + fi export HATCH_CONFIG hatch config restore hatch config set dirs.data ".hatch/data"