Skip to content

Test suite flakes under pytest -n auto: cleanup_asset_dir wipes an asset directory shared by all xdist workers #3307

Description

@mickzijdel

Description

src/anthias_server/api/tests/test_v1_endpoints.py fails intermittently under pytest -n auto, which is how CI runs the suite. It passes 100% of the time serially, so the tests themselves are correct — the failures come from xdist workers sharing one directory.

Measured on a local run, same commit, no code changes between runs:

$ for i in 1 2 3 4; do pytest src/anthias_server/api/tests/test_v1_endpoints.py -n auto -q | tail -1; done
28 passed in 4.20s
1 failed, 27 passed in 3.75s
2 failed, 26 passed in 3.41s
1 failed, 27 passed, 1 error in 3.14s

$ pytest src/anthias_server/api/tests/test_v1_endpoints.py -q | tail -1
28 passed in 0.93s

Two failure signatures, both from the same cause:

E  FileNotFoundError: [Errno 2] No such file or directory: '<assetdir>/cb7013dbb18b4826afc4dabc794f04b8.tmp'
E  AssertionError: assert b'AAAA' == b'AAAABBBB'

The first is a staging file deleted between the upload creating it and the test reading it. The second is a chunked upload whose first chunk was removed before the second landed, so the reassembled file is short.

Cause

The cleanup_asset_dir fixture empties the real, shared asset directory in teardown:

@pytest.fixture
def cleanup_asset_dir() -> Iterator[None]:
    try:
        yield
    finally:
        asset_directory_path = Path(anthias_settings['assetdir'])
        for file in asset_directory_path.iterdir():
            file.unlink()

Every test in the module requests it, and settings['assetdir'] is one path shared by every xdist worker in the process pool. So whenever two upload tests overlap, the one that finishes first unlinks the other's in-flight staging files. Nothing is wrong with the assertions; they are observing another worker's teardown.

This is timing-dependent, so it will surface on CI as an unrelated red build on an innocent PR — the most expensive kind of flake, because the natural response is to re-run rather than investigate.

Proposed fix

Give each test its own directory instead of cleaning a shared one. Three sibling modules in the same package already do exactly this:

monkeypatch.setitem(settings, 'assetdir', str(tmp_path))

(test_xibo_import.py, test_screencloud_import.py, test_pisignage_import.py)

Applied here, cleanup_asset_dir points assetdir at a per-test tmp_path and drops the unlink loop entirely — pytest removes the directory itself. That makes the isolation the tests already assume real, removes the teardown, and keeps them working under any -n value.

Worth a look afterwards at whether any other suite mutates shared on-disk or Redis state the same way, since the same pattern would flake for the same reason.

Happy to send a PR for this if it looks right.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions