mcltspice/tests/test_diff.py
2026-02-10 23:35:53 -07:00

235 lines
8.4 KiB
Python

"""Tests for diff module: schematic comparison."""
from pathlib import Path
import pytest
from mcp_ltspice.diff import (
ComponentChange,
DirectiveChange,
SchematicDiff,
_diff_components,
_diff_directives,
_diff_nets,
_diff_wires,
diff_schematics,
)
from mcp_ltspice.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"