mcltspice/tests/test_drc.py
Ryan Malloy 9b418a06c5 Add SVG plotting, circuit tuning, 5 new templates, fix prompts
- SVG waveform plots (svg_plot.py): pure-SVG timeseries, Bode, spectrum
  generation with plot_waveform MCP tool — no matplotlib dependency
- Circuit tuning tool (tune_circuit): single-shot simulate → measure →
  compare targets → suggest adjustments workflow for iterative design
- 5 new circuit templates: Sallen-Key lowpass, boost converter,
  instrumentation amplifier, current mirror, transimpedance amplifier
  (both netlist and .asc schematic generators, 15 total templates)
- Fix all 6 prompts to return list[Message] per FastMCP 2.x spec
- Add ltspice://templates and ltspice://template/{name} resources
- Add troubleshoot_simulation prompt
- Integration tests for RC lowpass and non-inverting amp (2/4 pass;
  CE amp and Colpitts oscillator have pre-existing schematic bugs)
- 360 unit tests passing, ruff clean
2026-02-11 05:13:50 -07:00

128 lines
4.3 KiB
Python

"""Tests for drc module: design rule checks on schematic objects."""
from mcp_ltspice.drc import (
DRCResult,
DRCViolation,
Severity,
_check_duplicate_names,
_check_ground,
_check_simulation_directive,
)
from mcp_ltspice.schematic import Schematic, write_schematic
def _run_single_check(check_fn, schematic: Schematic) -> DRCResult:
"""Run a single DRC check function and return results."""
result = DRCResult()
check_fn(schematic, result)
return result
class TestGroundCheck:
def test_missing_ground_detected(self, schematic_no_ground):
result = _run_single_check(_check_ground, schematic_no_ground)
assert not result.passed
assert any(v.rule == "NO_GROUND" for v in result.violations)
def test_ground_present(self, valid_schematic):
result = _run_single_check(_check_ground, valid_schematic)
assert result.passed
assert len(result.violations) == 0
class TestSimDirectiveCheck:
def test_missing_sim_directive_detected(self, schematic_no_sim):
result = _run_single_check(_check_simulation_directive, schematic_no_sim)
assert not result.passed
assert any(v.rule == "NO_SIM_DIRECTIVE" for v in result.violations)
def test_sim_directive_present(self, valid_schematic):
result = _run_single_check(_check_simulation_directive, valid_schematic)
assert result.passed
class TestDuplicateNameCheck:
def test_duplicate_names_detected(self, schematic_duplicate_names):
result = _run_single_check(_check_duplicate_names, schematic_duplicate_names)
assert not result.passed
assert any(v.rule == "DUPLICATE_NAME" for v in result.violations)
def test_unique_names_pass(self, valid_schematic):
result = _run_single_check(_check_duplicate_names, valid_schematic)
assert result.passed
class TestDRCResult:
def test_passed_when_no_errors(self):
result = DRCResult()
result.violations.append(
DRCViolation(rule="TEST", severity=Severity.WARNING, message="warning only")
)
assert result.passed # Warnings don't cause failure
def test_failed_when_errors(self):
result = DRCResult()
result.violations.append(
DRCViolation(rule="TEST", severity=Severity.ERROR, message="error")
)
assert not result.passed
def test_summary_no_violations(self):
result = DRCResult(checks_run=5)
assert "passed" in result.summary().lower()
def test_summary_with_errors(self):
result = DRCResult(checks_run=5)
result.violations.append(
DRCViolation(rule="TEST", severity=Severity.ERROR, message="error")
)
assert "FAILED" in result.summary()
def test_to_dict(self):
result = DRCResult(checks_run=3)
result.violations.append(
DRCViolation(rule="NO_GROUND", severity=Severity.ERROR, message="No ground")
)
d = result.to_dict()
assert d["passed"] is False
assert d["error_count"] == 1
assert len(d["violations"]) == 1
def test_errors_and_warnings_properties(self):
result = DRCResult()
result.violations.append(
DRCViolation(rule="E1", severity=Severity.ERROR, message="err")
)
result.violations.append(
DRCViolation(rule="W1", severity=Severity.WARNING, message="warn")
)
result.violations.append(
DRCViolation(rule="I1", severity=Severity.INFO, message="info")
)
assert len(result.errors) == 1
assert len(result.warnings) == 1
class TestFullDRC:
"""Integration test: write a schematic to disk and run the full DRC pipeline."""
def test_valid_schematic_passes(self, valid_schematic, tmp_path):
"""A valid schematic should pass DRC with no errors."""
from mcp_ltspice.drc import run_drc
path = tmp_path / "valid.asc"
write_schematic(valid_schematic, path)
result = run_drc(path)
# May have warnings (floating nodes etc) but no errors
assert len(result.errors) == 0
def test_no_ground_fails(self, schematic_no_ground, tmp_path):
from mcp_ltspice.drc import run_drc
path = tmp_path / "no_ground.asc"
write_schematic(schematic_no_ground, path)
result = run_drc(path)
assert any(v.rule == "NO_GROUND" for v in result.errors)