diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 88c3c900d3..4dd3ae886c 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -49,6 +49,7 @@ jobs: strategy: matrix: python-version: ["3.11", "3.12", "3.13", "3.14"] + suite: [unit, e2e] fail-fast: false defaults: run: @@ -68,12 +69,14 @@ jobs: uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2 - name: Cache DSH configuration dependencies + if: matrix.suite == 'unit' uses: ./.github/actions/cache-pnpm with: working-directory: integrations/dsh/plugins/powercontext/tests/config-runtime cache-dependency-glob: integrations/dsh/plugins/powercontext/tests/config-runtime/pnpm-lock.yaml - name: Install and resolve native DSH configuration test APIs + if: matrix.suite == 'unit' run: | pnpm --dir integrations/dsh/plugins/powercontext/tests/config-runtime install --frozen-lockfile boot="$(node --input-type=module -e "import { createRequire } from 'node:module'; import { resolve } from 'node:path'; const require = createRequire(resolve('integrations/dsh/plugins/powercontext/tests/config-runtime/package.json')); process.stdout.write(require.resolve('@deepseek-ai/dsh-app-boot'))")" @@ -81,11 +84,13 @@ jobs: echo "DSH_TEST_CONFIG_BOOT=$boot" >> "$GITHUB_ENV" - name: Run unit tests + if: matrix.suite == 'unit' env: PYTEST_ADDOPTS: --require-dsh-config-runtime run: make unit-test - name: Run end-to-end tests + if: matrix.suite == 'e2e' run: make e2e-test evaluation-tests: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d442d3e2a9..78d6e4ac85 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,7 +96,9 @@ make unit-test ``` Use `make e2e-test` for cross-component behavior, `make contract-test` for OpenAPI changes, and -`make docs-test` for documentation changes. `make test` runs the complete pytest suite. +`make docs-test` for documentation changes. `make test` runs the complete pytest suite. These pytest targets +spread tests across one worker per CPU with pytest-xdist; pass `PYTEST_WORKERS=0` to run them in a single process, +for example when debugging with `pdb`. Before raising a pull request you should also run tox when a change may affect supported Python versions. This requires you to have the relevant Python versions installed. The same version matrix runs in CI. diff --git a/Makefile b/Makefile index 4e5ca470c8..5c5dd5af4c 100644 --- a/Makefile +++ b/Makefile @@ -40,18 +40,21 @@ workflow-actions-check: ## Verify third-party GitHub Actions use immutable commi @echo "🚀 Static type checking: Running ty for the Pydantic AI integration" @uv run ty check integrations/pydantic-ai/src +# pytest-xdist worker count; set PYTEST_WORKERS=0 to run tests in a single process. +PYTEST_WORKERS ?= auto + .PHONY: test test: ## Test the code with pytest @echo "🚀 Testing code: Running pytest" - @uv run python -m pytest --doctest-modules + @uv run python -m pytest -n $(PYTEST_WORKERS) --doctest-modules .PHONY: unit-test unit-test: ## Run tests that do not cross the Server boundary end to end. - @uv run python -m pytest --doctest-modules --ignore=tests/e2e + @uv run python -m pytest -n $(PYTEST_WORKERS) --doctest-modules --ignore=tests/e2e .PHONY: e2e-test e2e-test: ## Run CLI to Client SDK to Server end-to-end tests. - @uv run python -m pytest tests/e2e + @uv run python -m pytest -n $(PYTEST_WORKERS) tests/e2e .PHONY: evaluation-unit-test evaluation-unit-test: ## Run the evaluation project's unit tests, the work-continuity benchmark included. diff --git a/e2e/bub/uv.lock b/e2e/bub/uv.lock index 8ea831aa9a..0a2d290bea 100644 --- a/e2e/bub/uv.lock +++ b/e2e/bub/uv.lock @@ -1711,6 +1711,7 @@ dev = [ { name = "prek", specifier = ">=0.2.0" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-cov", specifier = ">=7,<8" }, + { name = "pytest-xdist", specifier = ">=3.6,<4" }, { name = "pyyaml", specifier = ">=6,<7" }, { name = "ruff", specifier = ">=0.15.7" }, { name = "sqlalchemy", specifier = ">=2,<3" }, diff --git a/pyproject.toml b/pyproject.toml index 05b46360fa..0ea7d7f720 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -144,6 +144,7 @@ dev = [ "pytest>=9.0.2", "time-machine==3.5.0", "pytest-cov>=7,<8", + "pytest-xdist>=3.6,<4", "pyyaml>=6,<7", "prek>=0.2.0", "tox-uv>=1.33.4", diff --git a/uv.lock b/uv.lock index e5b5443670..7dea1d5ceb 100644 --- a/uv.lock +++ b/uv.lock @@ -868,6 +868,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + [[package]] name = "executing" version = "2.2.1" @@ -1216,7 +1225,7 @@ name = "importlib-metadata" version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } wheels = [ @@ -2395,8 +2404,8 @@ name = "opendalfs" version = "0.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "fsspec" }, - { name = "opendal" }, + { name = "fsspec", marker = "python_full_version >= '3.12'" }, + { name = "opendal", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/23/fdb4d34f08ac68a27b2d553256e13b15ce139916d36bab33f9a0e71e8770/opendalfs-0.1.0.tar.gz", hash = "sha256:5ceaeccc0852ef10c8ef2ce20e5927dbc18083909c06550ad36f81bcacb5774d", size = 75112, upload-time = "2026-08-26T05:30:33.497Z" } wheels = [ @@ -2819,6 +2828,7 @@ dev = [ { name = "prek" }, { name = "pytest" }, { name = "pytest-cov" }, + { name = "pytest-xdist" }, { name = "pyyaml" }, { name = "ruff" }, { name = "sqlalchemy" }, @@ -2936,6 +2946,7 @@ dev = [ { name = "prek", specifier = ">=0.2.0" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-cov", specifier = ">=7,<8" }, + { name = "pytest-xdist", specifier = ">=3.6,<4" }, { name = "pyyaml", specifier = ">=6,<7" }, { name = "ruff", specifier = ">=0.15.7" }, { name = "sqlalchemy", specifier = ">=2,<3" }, @@ -2962,8 +2973,8 @@ name = "powercontext-connector-opendal" version = "0.0.1" source = { editable = "integrations/opendal" } dependencies = [ - { name = "opendalfs" }, - { name = "powercontext", extra = ["client"] }, + { name = "opendalfs", marker = "python_full_version >= '3.12'" }, + { name = "powercontext", extra = ["client"], marker = "python_full_version >= '3.12'" }, ] [package.metadata] @@ -3474,6 +3485,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0"