diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 370d3c5723..5b4499c25d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -131,6 +131,7 @@ repos: - ansible-compat>=2.2.0 - ansible-core - enrich + - filelock - flaky - pytest - rich>=11.0.0 @@ -157,6 +158,7 @@ repos: - ansible-core - docutils - enrich + - filelock - flaky - jsonschema>=4.9.0 - pytest diff --git a/pytest.ini b/pytest.ini index 4f037f4eaa..49db57feb7 100644 --- a/pytest.ini +++ b/pytest.ini @@ -60,3 +60,4 @@ xfail_strict = true markers = eco: Tests effects on a set of 3rd party ansible repositories formatting_fixtures: Test that regenerates and tests formatting fixtures (requires prettier on PATH) + serial: Run this test serially via filelock. diff --git a/setup.cfg b/setup.cfg index ad9a1d834c..e36b57f7cc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -97,13 +97,14 @@ docs = yamllint >= 1.26.3 test = coverage >= 6.3 - tomli >= 2.0.0 + filelock flaky >= 3.7.0 + psutil # soft-dep of pytest-xdist pytest >= 6.0.1 pytest-cov >= 2.10.1 pytest-plus >= 0.2 # for PYTEST_REQPASS pytest-xdist >= 2.1.0 - psutil # soft-dep of pytest-xdist + tomli >= 2.0.0 black # IDE support mypy # IDE support pylint # IDE support diff --git a/test/conftest.py b/test/conftest.py index 401fe09a03..b21b960685 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,9 +1,11 @@ """PyTest fixtures for testing the project.""" import os from contextlib import contextmanager -from typing import TYPE_CHECKING, Iterator +from typing import TYPE_CHECKING, Generator, Iterator import pytest +from _pytest.fixtures import FixtureRequest +from filelock import FileLock if TYPE_CHECKING: from typing import List # pylint: disable=ungrouped-imports @@ -50,3 +52,17 @@ def pytest_collection_modifyitems(items: "List[nodes.Item]", config: "Config") - item.add_marker(skip_other) elif not do_regenerate and "formatting_fixtures" in item.keywords: item.add_marker(skip_formatting_fixture) + + +@pytest.fixture(autouse=True) +def _block_on_serial_mark(request: FixtureRequest) -> Generator[None, None, None]: + """Ensure that tests with serial marker do not run at the same time.""" + # https://github.com/pytest-dev/pytest-xdist/issues/84 + # https://github.com/pytest-dev/pytest-xdist/issues/385 + os.makedirs(".tox", exist_ok=True) + if request.node.get_closest_marker("serial"): + # pylint: disable=abstract-class-instantiated + with FileLock(".tox/semaphore.lock"): + yield + else: + yield diff --git a/test/test_cli_role_paths.py b/test/test_cli_role_paths.py index 9fda3a5d80..ffa3f22ae2 100644 --- a/test/test_cli_role_paths.py +++ b/test/test_cli_role_paths.py @@ -71,6 +71,7 @@ def test_run_inside_role_dir(local_test_dir: str) -> None: assert "Use shell only when shell functionality is required" in result.stdout +@pytest.mark.serial() def test_run_role_three_dir_deep(local_test_dir: str) -> None: """Tests execution from deep inside a role.""" cwd = local_test_dir diff --git a/test/test_utils.py b/test/test_utils.py index c7ec8c2904..5ec89f1d0e 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -288,6 +288,7 @@ def test_logger_debug(caplog: LogCaptureFixture) -> None: assert expected_info in caplog.record_tuples +@pytest.mark.serial() def test_cli_auto_detect(capfd: CaptureFixture[str]) -> None: """Test that run without arguments it will detect and lint the entire repository.""" cmd = [