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.
Description
src/anthias_server/api/tests/test_v1_endpoints.pyfails intermittently underpytest -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:
Two failure signatures, both from the same cause:
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_dirfixture empties the real, shared asset directory in teardown: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:
(
test_xibo_import.py,test_screencloud_import.py,test_pisignage_import.py)Applied here,
cleanup_asset_dirpointsassetdirat a per-testtmp_pathand 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-nvalue.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.