diff --git a/.circleci/config.yml b/.circleci/config.yml index fa0d3f2c952..6e089436920 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2987,7 +2987,7 @@ jobs: - run: name: Run owned integration contracts command: bash .circleci/scripts/run_integration.sh << parameters.suite >> - no_output_timeout: 25m + no_output_timeout: 15m - run: name: Stop owned database and Redis when: always diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 342952d44d4..f66ff7e74df 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,10 +1,11 @@ from __future__ import annotations import json -import os import hashlib +import os +from collections.abc import Sequence +from collections.abc import Iterator from importlib.metadata import version -from collections.abc import Generator, Iterator from pathlib import Path from typing import Final @@ -28,6 +29,26 @@ def pytest_configure(config: pytest.Config) -> None: config.addinivalue_line("markers", "integration: owned real-service integration contracts") config.addinivalue_line("markers", "covers(*ids): independently asserted behavior contracts") config.stash[REPORTS] = [] + config.pluginmanager.register(IntegrationReportPlugin(config)) + + +class IntegrationReportPlugin: + def __init__(self, config: pytest.Config) -> None: + self.config = config + + def pytest_runtest_logreport(self, report: pytest.TestReport) -> None: + self.config.stash[REPORTS].append(report) + + @pytest.hookimpl(optionalhook=True) + def pytest_xdist_node_collection_finished(self, node: object, ids: Sequence[str]) -> None: + owned_prefix: Final = "tests/integration/" + self.config.stash[COLLECTED] = tuple( + nodeid + for nodeid in ids + if nodeid.split("::", 1)[0].startswith(owned_prefix) + and len(Path(nodeid.split("::", 1)[0]).parts) > 2 + and Path(nodeid.split("::", 1)[0]).parts[2] in OWNED_DIRECTORIES + ) def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None: @@ -54,16 +75,9 @@ def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item config.stash[COLLECTED] = tuple(item.nodeid for item in owned) -@pytest.hookimpl(wrapper=True) -def pytest_runtest_makereport( - item: pytest.Item, call: pytest.CallInfo[None] -) -> Generator[None, pytest.TestReport, pytest.TestReport]: - report: Final = yield - item.config.stash[REPORTS].append(report) - return report - - def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None: + if hasattr(session.config, "workerinput"): + return destination: Final = os.environ.get("INTEGRATION_RESULTS_DIR") if destination is None: return diff --git a/tests/integration/run.py b/tests/integration/run.py index 759644f6ab6..f45164c5ca4 100644 --- a/tests/integration/run.py +++ b/tests/integration/run.py @@ -18,6 +18,7 @@ def main() -> int: parser.add_argument("--results", type=Path, default=Path("test-results/integration")) parser.add_argument("--seed", type=int, default=int(os.environ.get("INTEGRATION_SEED", "4106601"))) parser.add_argument("--order-seed", type=int, default=int(os.environ.get("INTEGRATION_ORDER_SEED", "0"))) + parser.add_argument("--workers", type=int, default=int(os.environ.get("INTEGRATION_WORKERS", "1"))) options: Final = parser.parse_args() root: Final = Path(__file__).resolve().parents[2] selected: Final = tuple( @@ -56,6 +57,11 @@ def main() -> int: f"--hypothesis-seed={options.seed}", f"--integration-order-seed={options.order_seed}", f"--junitxml={output / 'junit.xml'}", + *( + ("-n", str(options.workers)) + if options.workers > 1 + else () + ), ], cwd=root, env=environment,