Follow-up to #562. That commit fixed the actual cause of the intermittent
15-test failure in test_display_dirty_tracking.py -- the emulator's fixed TCP
port 8888, a machine-wide singleton that a concurrent pytest process takes
away. This adds the two things that would have made it a five-minute
diagnosis instead of a long one, and closes the other door into the same
failure.
Confirmed the module is order-independent as it stands, on this checkout:
pytest test/ -q, three times 115 failed / 4464 passed / 63 skipped,
byte-identical failure sets, the
module 21/21 passed each time
module forced last (197 files first) identical failure set
module forced first identical failure set
module after each of test_display_manager, test_display_controller,
test_display_controller_vegas_tick, test_skin_system, test_sports_scroll,
test_initial_update_budget, test_display_double_parity,
test_initializing_screen all pass
four concurrent processes on the file 21/21 each
And reproduced the original, to be sure the diagnosis in #562 is the whole
story. Holding 0.0.0.0:8888 from a separate process:
HEAD's test/conftest.py 21 passed
pre-#562 test/conftest.py 15 failed, 6 passed
The 15/6 split is not arbitrary: the six survivors are the only tests in the
file that never touch dm.matrix.
conftest.py: DisplayManager is a process-wide singleton and the RGBMatrix /
RGBMatrixOptions names it constructs through are module globals, bound once at
import. All three are shared by every test module in the run, so a module that
leaves an instance in _instance -- or leaves patch('src.display_manager.
RGBMatrix') standing -- changes what the NEXT module builds, invisibly, and
only in a full run. A module-scoped autouse fixture now resets the singleton
and restores either binding if a patch outlived its module. Module-scoped
rather than per-test so that files sharing one manager across their own tests
keep doing so; only the leak across the module boundary is cut. Autouse
fixtures are set up ahead of requested ones, so this is finalised after a
module's own DisplayManager fixture. Verified with a throwaway pair of probe
modules -- one leaks a patch and a singleton, the next asserts both are clean
-- which passed and were then removed.
test_display_dirty_tracking.py: _setup_matrix() swallows every construction
failure and falls back to matrix=None, so a broken environment arrived as
fifteen identical "'NoneType' object has no attribute 'SwapOnVSync'" errors
naming neither the fixture nor the cause. The fixture now fails once, and
says where to look; under a held port it reads
DisplayManager fell back to matrix=None: RGBMatrix construction raised...
Known causes: the emulator adapter losing a fixed TCP port to another
process -- see pytest_configure in test/conftest.py -- or a
patch('src.display_manager.RGBMatrix') leaked from an earlier test module.
with WinError 10048 in the captured log directly above it.
No regressions: full suite with both changes is 115 failed / 4464 passed /
63 skipped, failure set identical to the pre-change baseline. The 115 is the
pre-existing Windows-environment baseline (os.geteuid, POSIX modes, fcntl);
CI on Linux remains authoritative.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>