diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 40a497480c..0ed23b9d83 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -14,6 +14,9 @@ defaults: run: shell: bash -eux {0} +permissions: + contents: read + jobs: static: @@ -163,6 +166,36 @@ jobs: run: | just typing + integration_tests: + runs-on: ubuntu-latest + name: Integration Tests + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Install uv + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v5 + with: + enable-cache: true + python-version: "3.10" + - name: Install just + run: uv tool install rust-just + - name: Install dependencies + run: | + just install + - id: setup-mongodb + uses: mongodb-labs/drivers-evergreen-tools@master + - name: Run tests + run: | + just integration-tests + - id: setup-mongodb-ssl + uses: mongodb-labs/drivers-evergreen-tools@master + with: + ssl: true + - name: Run tests + run: | + just integration-tests + make_sdist: runs-on: ubuntu-latest name: "Make an sdist" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4f7b55817..a8881db9cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -411,6 +411,14 @@ a use the ticket number as the "reason" parameter to the decorator, e.g. `@flaky When running tests locally (not in CI), the `flaky` decorator will be disabled unless `ENABLE_FLAKY` is set. To disable the `flaky` decorator in CI, you can use `evergreen patch --param DISABLE_FLAKY=1`. +## Integration Tests + +The `integration_tests` directory has a set of scripts that verify the usage of PyMongo with downstream packages or frameworks. See the [README](./integration_tests/README.md) for more information. + +To run the tests, use `just integration_tests`. + +The tests should be able to run with and without SSL enabled. + ## Specification Tests The MongoDB [specifications repository](https://github.com/mongodb/specifications) diff --git a/integration_tests/README.md b/integration_tests/README.md new file mode 100644 index 0000000000..fb64a9066f --- /dev/null +++ b/integration_tests/README.md @@ -0,0 +1,42 @@ +# Integration Tests + +A set of tests that verify the usage of PyMongo with downstream packages or frameworks. + +Each test uses [PEP 723 inline metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata/) and can be run using `pipx` or `uv`. + +The `run.sh` convenience script can be used to run all of the files using `uv`. + +Here is an example header for the script with the inline dependencies: + +```python +# /// script +# dependencies = [ +# "uvloop>=0.18" +# ] +# requires-python = ">=3.10" +# /// +``` + +Here is an example of using the test helper function to create a configured client for the test: + + +```python +import asyncio +import sys +from pathlib import Path + +# Use pymongo from parent directory. +root = Path(__file__).parent.parent +sys.path.insert(0, str(root)) + +from test.asynchronous import async_simple_test_client # noqa: E402 + + +async def main(): + async with async_simple_test_client() as client: + result = await client.admin.command("ping") + assert result["ok"] + + +asyncio.run(main()) +``` diff --git a/integration_tests/run.sh b/integration_tests/run.sh new file mode 100755 index 0000000000..051e2b8a75 --- /dev/null +++ b/integration_tests/run.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Run all of the integration test files using `uv run`. +set -eu + +for file in integration_tests/test_*.py ; do + echo "-----------------" + echo "Running $file..." + uv run $file + echo "Running $file...done." + echo "-----------------" +done diff --git a/integration_tests/test_uv_loop.py b/integration_tests/test_uv_loop.py new file mode 100644 index 0000000000..88a3ad73ab --- /dev/null +++ b/integration_tests/test_uv_loop.py @@ -0,0 +1,27 @@ +# /// script +# dependencies = [ +# "uvloop>=0.18" +# ] +# requires-python = ">=3.10" +# /// +from __future__ import annotations + +import sys +from pathlib import Path + +import uvloop + +# Use pymongo from parent directory. +root = Path(__file__).parent.parent +sys.path.insert(0, str(root)) + +from test.asynchronous import async_simple_test_client # noqa: E402 + + +async def main(): + async with async_simple_test_client() as client: + result = await client.admin.command("ping") + assert result["ok"] + + +uvloop.run(main()) diff --git a/justfile b/justfile index 9b6cce62c9..f235346160 100644 --- a/justfile +++ b/justfile @@ -72,6 +72,10 @@ setup-tests *args="": teardown-tests: bash .evergreen/scripts/teardown-tests.sh +[group('test')] +integration-tests: + bash integration_tests/run.sh + [group('server')] run-server *args="": bash .evergreen/scripts/run-server.sh {{args}} diff --git a/test/__init__.py b/test/__init__.py index f3b66c20a9..1ee2c283d6 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1227,6 +1227,13 @@ def teardown(): print_running_clients() +@contextmanager +def simple_test_client(): + client_context.init() + yield client_context.client + client_context.client.close() + + def test_cases(suite): """Iterator over all TestCases within a TestSuite.""" for suite_or_case in suite._tests: diff --git a/test/asynchronous/__init__.py b/test/asynchronous/__init__.py index 7a6a23ed27..78d0576add 100644 --- a/test/asynchronous/__init__.py +++ b/test/asynchronous/__init__.py @@ -1243,6 +1243,13 @@ async def async_teardown(): print_running_clients() +@asynccontextmanager +async def async_simple_test_client(): + await async_client_context.init() + yield async_client_context.client + await async_client_context.client.close() + + def test_cases(suite): """Iterator over all TestCases within a TestSuite.""" for suite_or_case in suite._tests: diff --git a/tools/synchro.py b/tools/synchro.py index a4190529c4..e3d4835502 100644 --- a/tools/synchro.py +++ b/tools/synchro.py @@ -131,6 +131,7 @@ "async_create_barrier": "create_barrier", "async_barrier_wait": "barrier_wait", "async_joinall": "joinall", + "async_simple_test_client": "simple_test_client", "_async_create_connection": "_create_connection", "pymongo.asynchronous.srv_resolver._SrvResolver.get_hosts": "pymongo.synchronous.srv_resolver._SrvResolver.get_hosts", "dns.asyncresolver.resolve": "dns.resolver.resolve",