test(rotate_schematic_mirror): isolate sys.modules stubs to fix test pollution
`test_rotate_handler_no_crash` permanently replaced `sys.modules["schemas.tool_schemas"].TOOL_SCHEMAS` with `[]`, leaking into later tests. When test_wire_connectivity (or any test) ran after this one and did `from schemas.tool_schemas import TOOL_SCHEMAS`, it got the empty list and `TOOL_SCHEMAS["get_wire_connections"]` raised `TypeError: list indices must be integers or slices, not str`. Save the original sys.modules entries and restore them in a `finally` block so the stubs are scoped to the test body. Whole suite now passes (678 tests, previously 4 failed in TestSchema when run in suite order). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -6,13 +6,13 @@ Tests are split into two layers:
|
||||
2. Handler integration smoke test — patches SchematicManager away.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import math
|
||||
import textwrap
|
||||
import tempfile
|
||||
import importlib.util
|
||||
from unittest.mock import patch, MagicMock
|
||||
import textwrap
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import sexpdata
|
||||
from sexpdata import Symbol
|
||||
@@ -40,6 +40,7 @@ WireDragger = _wd_mod.WireDragger
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _parse(text: str) -> list:
|
||||
return sexpdata.loads(text)
|
||||
|
||||
@@ -80,6 +81,7 @@ def _make_sch(sym_extra: str = "", wires: str = "") -> list:
|
||||
# Tests: update_symbol_rotation_mirror
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_update_rotation_sets_angle():
|
||||
sch = _make_sch()
|
||||
result = WireDragger.update_symbol_rotation_mirror(sch, "Q1", 90.0, None)
|
||||
@@ -130,6 +132,7 @@ def test_update_unknown_reference_returns_false():
|
||||
# Tests: compute_pin_positions_for_rotation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_pin_positions_change_on_rotation():
|
||||
"""Pins at non-zero local offsets should move when the symbol rotates."""
|
||||
sch = _make_sch()
|
||||
@@ -179,6 +182,7 @@ def test_pin_positions_mirror_x_flips_x():
|
||||
# Integration smoke test: handler uses sexpdata, not kicad-skip
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_rotate_handler_no_crash(tmp_path):
|
||||
"""_handle_rotate_schematic_component should succeed without kicad-skip."""
|
||||
# Ensure python/ is on sys.path so commands.* imports resolve
|
||||
@@ -186,26 +190,44 @@ def test_rotate_handler_no_crash(tmp_path):
|
||||
if _python_dir not in sys.path:
|
||||
sys.path.insert(0, _python_dir)
|
||||
|
||||
# Stub heavy imports before loading kicad_interface
|
||||
for modname in ("pcbnew", "skip", "resources", "schemas",
|
||||
"resources.resource_definitions", "schemas.tool_schemas",
|
||||
"annotations"):
|
||||
sys.modules.setdefault(modname, MagicMock())
|
||||
sys.modules["resources.resource_definitions"].RESOURCE_DEFINITIONS = {}
|
||||
sys.modules["resources.resource_definitions"].handle_resource_read = MagicMock()
|
||||
sys.modules["schemas.tool_schemas"].TOOL_SCHEMAS = []
|
||||
|
||||
_pcbnew = sys.modules["pcbnew"]
|
||||
_pcbnew.__file__ = "/fake/pcbnew.so"
|
||||
_pcbnew.GetBuildVersion.return_value = "9.0.0"
|
||||
|
||||
ki_spec = importlib.util.spec_from_file_location(
|
||||
"kicad_interface_smoke",
|
||||
os.path.join(os.path.dirname(__file__), "..", "python", "kicad_interface.py"),
|
||||
# Stub heavy imports before loading kicad_interface. Save and restore
|
||||
# sys.modules state so we don't pollute already-imported real modules
|
||||
# (notably schemas.tool_schemas, whose TOOL_SCHEMAS dict is shared across
|
||||
# the test session).
|
||||
_stub_modnames = (
|
||||
"pcbnew",
|
||||
"skip",
|
||||
"resources",
|
||||
"schemas",
|
||||
"resources.resource_definitions",
|
||||
"schemas.tool_schemas",
|
||||
"annotations",
|
||||
)
|
||||
ki_mod = importlib.util.module_from_spec(ki_spec)
|
||||
ki_spec.loader.exec_module(ki_mod)
|
||||
KiCADInterface = ki_mod.KiCADInterface
|
||||
_saved_modules = {n: sys.modules.get(n) for n in _stub_modnames}
|
||||
try:
|
||||
for modname in _stub_modnames:
|
||||
sys.modules[modname] = MagicMock()
|
||||
sys.modules["resources.resource_definitions"].RESOURCE_DEFINITIONS = {}
|
||||
sys.modules["resources.resource_definitions"].handle_resource_read = MagicMock()
|
||||
sys.modules["schemas.tool_schemas"].TOOL_SCHEMAS = []
|
||||
|
||||
_pcbnew = sys.modules["pcbnew"]
|
||||
_pcbnew.__file__ = "/fake/pcbnew.so"
|
||||
_pcbnew.GetBuildVersion.return_value = "9.0.0"
|
||||
|
||||
ki_spec = importlib.util.spec_from_file_location(
|
||||
"kicad_interface_smoke",
|
||||
os.path.join(os.path.dirname(__file__), "..", "python", "kicad_interface.py"),
|
||||
)
|
||||
ki_mod = importlib.util.module_from_spec(ki_spec)
|
||||
ki_spec.loader.exec_module(ki_mod)
|
||||
KiCADInterface = ki_mod.KiCADInterface
|
||||
finally:
|
||||
for modname, mod in _saved_modules.items():
|
||||
if mod is None:
|
||||
sys.modules.pop(modname, None)
|
||||
else:
|
||||
sys.modules[modname] = mod
|
||||
|
||||
# Write a minimal schematic file
|
||||
sch_path = str(tmp_path / "test.kicad_sch")
|
||||
@@ -233,11 +255,13 @@ def test_rotate_handler_no_crash(tmp_path):
|
||||
f.write(sch_content)
|
||||
|
||||
iface = KiCADInterface.__new__(KiCADInterface)
|
||||
result = iface._handle_rotate_schematic_component({
|
||||
"schematicPath": sch_path,
|
||||
"reference": "R1",
|
||||
"angle": 90,
|
||||
})
|
||||
result = iface._handle_rotate_schematic_component(
|
||||
{
|
||||
"schematicPath": sch_path,
|
||||
"reference": "R1",
|
||||
"angle": 90,
|
||||
}
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["angle"] == 90
|
||||
|
||||
Reference in New Issue
Block a user