test: the sys.modules guard now sees the write, not its spelling (#398)

The guard introduced by #394 matched the literal
sys.modules['custom_components... and therefore never looked at
pure_imports.py, which writes through a variable — the third instance of
the #389 class walked straight past the check created for it.

The guard now inspects the write itself and decides by the key: a whole
literal or the literal head of an f-string is safe unless it starts with
custom_components (that is how tests register homeassistant.*, hp_pure.*
and houseplan.trails); anything else — a variable, a concatenation,
setdefault/update — counts as a violation whenever the file is able to
name the package at all, i.e. contains a custom_components. literal. A
file that never names the package cannot poison it through a variable,
so restoring a snapshot stays legal.

load_pure now removes what it registered. Removing its own name is not
enough: relative imports pull neighbours in, so junction_limits leaves
wall_segment_model and coordinate_canonicalization behind. It removes
the whole custom_components difference accumulated during exec_module,
in a finally, and a repeated call still works.

pure_imports.py is a named exemption of the static guard precisely
because that guard cannot see the cleanup — so the cleanup is proven by
an executable test instead, and the mutant pure-imports-stops-cleaning
reddens it. Both mutants were run by hand.

User-Visible: no
Issue: #398
This commit is contained in:
Codex
2026-08-31 03:56:01 +03:00
parent 2b3cabe7e0
commit d9b6766362
5 changed files with 220 additions and 7 deletions
+22
View File
@@ -746,6 +746,28 @@ const MUTANT_DEFINITIONS = [
replace: " this._persistDecorStyle();\n }, 0);",
}],
},
{
id: 'sysmodules-guard-blind-to-variable',
guard: 'node --test test/backend-test-hygiene.test.mjs',
because: 'a guard that only sees string literals let the third instance of '
+ 'the #389 class walk past it (#398 AC1)',
patches: [{
file: 'test/backend-test-hygiene.test.mjs',
find: ' if (start === null) { if (namesThePackage) hits.push(match[0]); continue; }',
replace: ' if (start === null) { continue; }',
}],
},
{
id: 'pure-imports-stops-cleaning',
guard: 'python3 -m pytest tests_backend/test_backend_quality.py -q -p no:cacheprovider',
because: 'pure_imports is allowed to write into sys.modules only because it '
+ 'removes the entries again; without that the exemption is a hole (#398 AC8)',
patches: [{
file: 'tests_backend/pure_imports.py',
find: ' del sys.modules[key]',
replace: ' pass',
}],
},
{
id: 'device-echo-keeps-local-noncanonical',
guard: 'node demo/smoke_device_position_history.mjs',