"""No test file may sit where pytest will not collect it. pytest.ini sets `testpaths = tests`, so a test file anywhere else is never run by `pytest` and never run in CI. It still passes review, still gets maintained, and still reports nothing. That is not hypothetical. plugins/endpoints/tests/test_plugin.py arrived with a merge carrying 55 lines of delivery assertions and was collected ZERO times; run directly, five of its tests failed. plugins/printedparts/tests/test_plugin.py had been sitting uncollected for longer. Both are now under tests/. The failure mode is the worst kind: the suite is green, the coverage is imaginary, and the authorisation on a route can be deleted without anything going red. """ from pathlib import Path REPO = Path(__file__).resolve().parents[1] # Where pytest actually looks (pytest.ini: testpaths = tests). COLLECTED = REPO / 'tests' # Trees that legitimately hold test-shaped files pytest must not collect: # vendored dependencies, build output, and the frontend's own vitest specs. IGNORED = ('node_modules', 'venv', '.git', 'dist', '__pycache__', 'site-packages', '.plugins-staged', 'frontend') # The Windows installer stages a whole copy of the app under here. It is build # output, not source, and the export excludes it - caught by this test on its # first run, which is the sort of thing the ignore list exists for. IGNORED_TREES = ('deploy/windows/installer/bundle',) def test_every_test_file_lives_where_pytest_looks(): stray = [] for path in REPO.rglob('test_*.py'): if any(part in IGNORED for part in path.parts): continue relative = path.relative_to(REPO).as_posix() if any(relative.startswith(tree) for tree in IGNORED_TREES): continue if COLLECTED in path.parents: continue stray.append(path.relative_to(REPO)) assert not stray, ( 'These test files are never collected, because pytest.ini limits\n' 'collection to tests/. They pass review and report nothing:\n ' + '\n '.join(str(p) for p in sorted(stray)) + '\n\nMove them under tests/ (tests/test_plugins/ for plugin tests).' )