Rename package from mcp-ltspice/mcp_ltspice to mcltspice throughout: source directory, imports, pyproject.toml, tests, and README. Remove startup banner prints from main() since FastMCP handles its own banner and stdout is the MCP JSON-RPC transport. Point repo URL at git.supported.systems/MCP/mcltspice.
232 lines
8.3 KiB
Python
232 lines
8.3 KiB
Python
"""Tests for diff module: schematic comparison."""
|
|
|
|
|
|
|
|
from mcltspice.diff import (
|
|
ComponentChange,
|
|
SchematicDiff,
|
|
_diff_components,
|
|
_diff_directives,
|
|
_diff_nets,
|
|
_diff_wires,
|
|
diff_schematics,
|
|
)
|
|
from mcltspice.schematic import Component, Flag, Schematic, Text, Wire, write_schematic
|
|
|
|
|
|
def _make_schematic(**kwargs) -> Schematic:
|
|
"""Helper to build a Schematic with overrides."""
|
|
sch = Schematic()
|
|
sch.components = kwargs.get("components", [])
|
|
sch.wires = kwargs.get("wires", [])
|
|
sch.flags = kwargs.get("flags", [])
|
|
sch.texts = kwargs.get("texts", [])
|
|
return sch
|
|
|
|
|
|
class TestDiffComponents:
|
|
def test_added_component(self):
|
|
sch_a = _make_schematic(components=[
|
|
Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
|
|
attributes={"Value": "1k"}),
|
|
])
|
|
sch_b = _make_schematic(components=[
|
|
Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
|
|
attributes={"Value": "1k"}),
|
|
Component(name="C1", symbol="cap", x=256, y=176, rotation=0, mirror=False,
|
|
attributes={"Value": "100n"}),
|
|
])
|
|
changes = _diff_components(sch_a, sch_b)
|
|
added = [c for c in changes if c.change_type == "added"]
|
|
assert len(added) == 1
|
|
assert added[0].name == "C1"
|
|
|
|
def test_removed_component(self):
|
|
sch_a = _make_schematic(components=[
|
|
Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
|
|
attributes={"Value": "1k"}),
|
|
Component(name="R2", symbol="res", x=320, y=80, rotation=0, mirror=False,
|
|
attributes={"Value": "2.2k"}),
|
|
])
|
|
sch_b = _make_schematic(components=[
|
|
Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
|
|
attributes={"Value": "1k"}),
|
|
])
|
|
changes = _diff_components(sch_a, sch_b)
|
|
removed = [c for c in changes if c.change_type == "removed"]
|
|
assert len(removed) == 1
|
|
assert removed[0].name == "R2"
|
|
|
|
def test_modified_value(self):
|
|
sch_a = _make_schematic(components=[
|
|
Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
|
|
attributes={"Value": "1k"}),
|
|
])
|
|
sch_b = _make_schematic(components=[
|
|
Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
|
|
attributes={"Value": "2.2k"}),
|
|
])
|
|
changes = _diff_components(sch_a, sch_b)
|
|
modified = [c for c in changes if c.change_type == "modified"]
|
|
assert len(modified) == 1
|
|
assert modified[0].old_value == "1k"
|
|
assert modified[0].new_value == "2.2k"
|
|
|
|
def test_moved_component(self):
|
|
sch_a = _make_schematic(components=[
|
|
Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
|
|
attributes={"Value": "1k"}),
|
|
])
|
|
sch_b = _make_schematic(components=[
|
|
Component(name="R1", symbol="res", x=320, y=160, rotation=0, mirror=False,
|
|
attributes={"Value": "1k"}),
|
|
])
|
|
changes = _diff_components(sch_a, sch_b)
|
|
assert len(changes) == 1
|
|
assert changes[0].moved is True
|
|
|
|
def test_no_changes(self):
|
|
comp = Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
|
|
attributes={"Value": "1k"})
|
|
sch = _make_schematic(components=[comp])
|
|
changes = _diff_components(sch, sch)
|
|
assert len(changes) == 0
|
|
|
|
|
|
class TestDiffDirectives:
|
|
def test_added_directive(self):
|
|
sch_a = _make_schematic(texts=[
|
|
Text(80, 296, ".tran 10m", type="spice"),
|
|
])
|
|
sch_b = _make_schematic(texts=[
|
|
Text(80, 296, ".tran 10m", type="spice"),
|
|
Text(80, 320, ".meas tran vmax MAX V(out)", type="spice"),
|
|
])
|
|
changes = _diff_directives(sch_a, sch_b)
|
|
added = [c for c in changes if c.change_type == "added"]
|
|
assert len(added) == 1
|
|
|
|
def test_removed_directive(self):
|
|
sch_a = _make_schematic(texts=[
|
|
Text(80, 296, ".tran 10m", type="spice"),
|
|
Text(80, 320, ".op", type="spice"),
|
|
])
|
|
sch_b = _make_schematic(texts=[
|
|
Text(80, 296, ".tran 10m", type="spice"),
|
|
])
|
|
changes = _diff_directives(sch_a, sch_b)
|
|
removed = [c for c in changes if c.change_type == "removed"]
|
|
assert len(removed) == 1
|
|
|
|
def test_modified_directive(self):
|
|
sch_a = _make_schematic(texts=[
|
|
Text(80, 296, ".tran 10m", type="spice"),
|
|
])
|
|
sch_b = _make_schematic(texts=[
|
|
Text(80, 296, ".tran 50m", type="spice"),
|
|
])
|
|
changes = _diff_directives(sch_a, sch_b)
|
|
modified = [c for c in changes if c.change_type == "modified"]
|
|
assert len(modified) == 1
|
|
assert modified[0].old_text == ".tran 10m"
|
|
assert modified[0].new_text == ".tran 50m"
|
|
|
|
|
|
class TestDiffNets:
|
|
def test_added_nets(self):
|
|
sch_a = _make_schematic(flags=[Flag(80, 176, "0")])
|
|
sch_b = _make_schematic(flags=[Flag(80, 176, "0"), Flag(176, 176, "out")])
|
|
added, removed = _diff_nets(sch_a, sch_b)
|
|
assert "out" in added
|
|
assert len(removed) == 0
|
|
|
|
def test_removed_nets(self):
|
|
sch_a = _make_schematic(flags=[Flag(80, 176, "0"), Flag(176, 176, "out")])
|
|
sch_b = _make_schematic(flags=[Flag(80, 176, "0")])
|
|
added, removed = _diff_nets(sch_a, sch_b)
|
|
assert "out" in removed
|
|
assert len(added) == 0
|
|
|
|
|
|
class TestDiffWires:
|
|
def test_added_wires(self):
|
|
sch_a = _make_schematic(wires=[Wire(80, 96, 176, 96)])
|
|
sch_b = _make_schematic(wires=[
|
|
Wire(80, 96, 176, 96),
|
|
Wire(176, 176, 272, 176),
|
|
])
|
|
added, removed = _diff_wires(sch_a, sch_b)
|
|
assert added == 1
|
|
assert removed == 0
|
|
|
|
def test_removed_wires(self):
|
|
sch_a = _make_schematic(wires=[
|
|
Wire(80, 96, 176, 96),
|
|
Wire(176, 176, 272, 176),
|
|
])
|
|
sch_b = _make_schematic(wires=[Wire(80, 96, 176, 96)])
|
|
added, removed = _diff_wires(sch_a, sch_b)
|
|
assert added == 0
|
|
assert removed == 1
|
|
|
|
|
|
class TestSchematicDiff:
|
|
def test_has_changes_false(self):
|
|
diff = SchematicDiff()
|
|
assert diff.has_changes is False
|
|
|
|
def test_has_changes_true(self):
|
|
diff = SchematicDiff(wires_added=1)
|
|
assert diff.has_changes is True
|
|
|
|
def test_summary_no_changes(self):
|
|
diff = SchematicDiff()
|
|
assert "No changes" in diff.summary()
|
|
|
|
def test_to_dict(self):
|
|
diff = SchematicDiff(
|
|
component_changes=[
|
|
ComponentChange(name="R1", change_type="modified",
|
|
old_value="1k", new_value="2.2k")
|
|
]
|
|
)
|
|
d = diff.to_dict()
|
|
assert d["has_changes"] is True
|
|
assert len(d["component_changes"]) == 1
|
|
|
|
|
|
class TestDiffSchematicsIntegration:
|
|
"""Write two schematics to disk and compare them end-to-end."""
|
|
|
|
def test_full_diff(self, valid_schematic, tmp_path):
|
|
# Create "before" schematic
|
|
path_a = tmp_path / "before.asc"
|
|
write_schematic(valid_schematic, path_a)
|
|
|
|
# Create "after" schematic with a modified R1 value
|
|
modified = Schematic()
|
|
modified.flags = list(valid_schematic.flags)
|
|
modified.wires = list(valid_schematic.wires)
|
|
modified.texts = list(valid_schematic.texts)
|
|
modified.components = []
|
|
for comp in valid_schematic.components:
|
|
if comp.name == "R1":
|
|
new_comp = Component(
|
|
name=comp.name, symbol=comp.symbol,
|
|
x=comp.x, y=comp.y, rotation=comp.rotation, mirror=comp.mirror,
|
|
attributes={"Value": "4.7k"},
|
|
)
|
|
modified.components.append(new_comp)
|
|
else:
|
|
modified.components.append(comp)
|
|
|
|
path_b = tmp_path / "after.asc"
|
|
write_schematic(modified, path_b)
|
|
|
|
diff = diff_schematics(path_a, path_b)
|
|
assert diff.has_changes
|
|
r1_changes = [c for c in diff.component_changes if c.name == "R1"]
|
|
assert len(r1_changes) == 1
|
|
assert r1_changes[0].old_value == "1k"
|
|
assert r1_changes[0].new_value == "4.7k"
|