Found via #567 (PR #574). tests/test_0741_expression_arithmetic_units.py reached development as a converted script with no test functions at all, whose entire body ran at module level — including uw.Model().set_reference_quantities(length=2900 km, time=1 Myr), which switches the units system on process-wide, during pytest collection.
The consequence was subtle and expensive: a function-scoped reset cannot protect a module-scoped fixture (pytest builds higher-scoped fixtures first), so any test file whose module-scoped fixture ran first in its worker built its mesh under dimensional coordinates. test_0761_point_locator's field was sampled 2.9 million times too far apart — worst error 1.6747e+00 against a clean 2.9e-15. It only showed up under pytest-xdist because --dist loadfile decides which file is first in a process, which is why it looked like a parallelism bug and blocked enabling CI parallelism for days.
PR #574 fixes the instance (module-scoped autouse reset, and 0741 rewritten as real tests) and pins the consequence with test_0742. It does not pin the practice.
The ask
A collection-time guard that fails when a test module mutates global state at import. Candidates, cheapest first:
- a
conftest.py hook that snapshots the relevant globals (the default uw.Model, the units/scaling registry, any process-wide PETSc options UW3 sets) before and after collection, and fails with the offending module named;
- a check that every
tests/test_*.py contains at least one test function — a file with none is either dead or a script, and both are worth knowing about (this alone would have caught 0741);
- the same snapshot applied per-module during collection, which localises the culprit rather than reporting a global diff.
Related instances of the same family, so this is not a one-off: tests/test_0050_utils.py runs two Stokes solves at module scope (#505), which is why whole-directory collection hangs and why pytest -m "level_1 and tier_a" without a path dies silently. Both files predate any of today's work.
Related: #567/#574 (the instance), #505 (the sibling), #570 (the coverage audit — a file with no tests is invisible to it too).
Underworld development team with AI support from Claude Code
Found via #567 (PR #574).
tests/test_0741_expression_arithmetic_units.pyreacheddevelopmentas a converted script with no test functions at all, whose entire body ran at module level — includinguw.Model().set_reference_quantities(length=2900 km, time=1 Myr), which switches the units system on process-wide, during pytest collection.The consequence was subtle and expensive: a function-scoped reset cannot protect a module-scoped fixture (pytest builds higher-scoped fixtures first), so any test file whose module-scoped fixture ran first in its worker built its mesh under dimensional coordinates.
test_0761_point_locator's field was sampled 2.9 million times too far apart — worst error 1.6747e+00 against a clean 2.9e-15. It only showed up underpytest-xdistbecause--dist loadfiledecides which file is first in a process, which is why it looked like a parallelism bug and blocked enabling CI parallelism for days.PR #574 fixes the instance (module-scoped autouse reset, and 0741 rewritten as real tests) and pins the consequence with
test_0742. It does not pin the practice.The ask
A collection-time guard that fails when a test module mutates global state at import. Candidates, cheapest first:
conftest.pyhook that snapshots the relevant globals (the defaultuw.Model, the units/scaling registry, any process-wide PETSc options UW3 sets) before and after collection, and fails with the offending module named;tests/test_*.pycontains at least one test function — a file with none is either dead or a script, and both are worth knowing about (this alone would have caught 0741);Related instances of the same family, so this is not a one-off:
tests/test_0050_utils.pyruns two Stokes solves at module scope (#505), which is why whole-directory collection hangs and whypytest -m "level_1 and tier_a"without a path dies silently. Both files predate any of today's work.Related: #567/#574 (the instance), #505 (the sibling), #570 (the coverage audit — a file with no tests is invisible to it too).
Underworld development team with AI support from Claude Code