Skip to content

Nothing prevents a test module from mutating global state at import — add a collection-time guard #575

Description

@lmoresi

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

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