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.
198 lines
5.7 KiB
Python
198 lines
5.7 KiB
Python
"""Tests for netlist module: builder pattern, rendering, template functions."""
|
|
|
|
import pytest
|
|
|
|
from mcltspice.netlist import (
|
|
Netlist,
|
|
buck_converter,
|
|
colpitts_oscillator,
|
|
common_emitter_amplifier,
|
|
differential_amplifier,
|
|
h_bridge,
|
|
inverting_amplifier,
|
|
ldo_regulator,
|
|
non_inverting_amplifier,
|
|
rc_lowpass,
|
|
voltage_divider,
|
|
)
|
|
|
|
|
|
class TestNetlistBuilder:
|
|
def test_add_resistor(self):
|
|
n = Netlist().add_resistor("R1", "in", "out", "10k")
|
|
assert len(n.components) == 1
|
|
assert n.components[0].name == "R1"
|
|
assert n.components[0].value == "10k"
|
|
|
|
def test_add_capacitor(self):
|
|
n = Netlist().add_capacitor("C1", "out", "0", "100n")
|
|
assert len(n.components) == 1
|
|
assert n.components[0].value == "100n"
|
|
|
|
def test_add_inductor(self):
|
|
n = Netlist().add_inductor("L1", "a", "b", "10u", series_resistance="0.1")
|
|
assert "Rser=0.1" in n.components[0].params
|
|
|
|
def test_chaining(self):
|
|
"""Builder methods return self for chaining."""
|
|
n = (
|
|
Netlist("Test")
|
|
.add_resistor("R1", "a", "b", "1k")
|
|
.add_capacitor("C1", "b", "0", "1n")
|
|
)
|
|
assert len(n.components) == 2
|
|
|
|
def test_add_voltage_source_dc(self):
|
|
n = Netlist().add_voltage_source("V1", "in", "0", dc="5")
|
|
assert "5" in n.components[0].value
|
|
|
|
def test_add_voltage_source_ac(self):
|
|
n = Netlist().add_voltage_source("V1", "in", "0", ac="1")
|
|
assert "AC 1" in n.components[0].value
|
|
|
|
def test_add_voltage_source_pulse(self):
|
|
n = Netlist().add_voltage_source(
|
|
"V1", "g", "0", pulse=("0", "5", "0", "1n", "1n", "5u", "10u")
|
|
)
|
|
rendered = n.render()
|
|
assert "PULSE(" in rendered
|
|
|
|
def test_add_voltage_source_sin(self):
|
|
n = Netlist().add_voltage_source(
|
|
"V1", "in", "0", sin=("0", "1", "1k")
|
|
)
|
|
rendered = n.render()
|
|
assert "SIN(" in rendered
|
|
|
|
def test_add_directive(self):
|
|
n = Netlist().add_directive(".tran 10m")
|
|
assert ".tran 10m" in n.directives
|
|
|
|
def test_add_meas(self):
|
|
n = Netlist().add_meas("tran", "vmax", "MAX V(out)")
|
|
assert any("vmax" in d for d in n.directives)
|
|
|
|
|
|
class TestNetlistRender:
|
|
def test_render_contains_title(self):
|
|
n = Netlist("My Circuit")
|
|
text = n.render()
|
|
assert "* My Circuit" in text
|
|
|
|
def test_render_contains_components(self):
|
|
n = (
|
|
Netlist()
|
|
.add_resistor("R1", "in", "out", "10k")
|
|
.add_capacitor("C1", "out", "0", "100n")
|
|
)
|
|
text = n.render()
|
|
assert "R1 in out 10k" in text
|
|
assert "C1 out 0 100n" in text
|
|
|
|
def test_render_contains_backanno_and_end(self):
|
|
n = Netlist()
|
|
text = n.render()
|
|
assert ".backanno" in text
|
|
assert ".end" in text
|
|
|
|
def test_render_includes_directive(self):
|
|
n = Netlist().add_directive(".ac dec 100 1 1meg")
|
|
text = n.render()
|
|
assert ".ac dec 100 1 1meg" in text
|
|
|
|
def test_render_includes_comment(self):
|
|
n = Netlist().add_comment("Test comment")
|
|
text = n.render()
|
|
assert "* Test comment" in text
|
|
|
|
def test_render_includes_lib(self):
|
|
n = Netlist().add_lib("LT1001")
|
|
text = n.render()
|
|
assert ".lib LT1001" in text
|
|
|
|
|
|
class TestTemplateNetlists:
|
|
"""All template functions should return valid Netlist objects."""
|
|
|
|
@pytest.mark.parametrize(
|
|
"factory",
|
|
[
|
|
voltage_divider,
|
|
rc_lowpass,
|
|
inverting_amplifier,
|
|
non_inverting_amplifier,
|
|
differential_amplifier,
|
|
common_emitter_amplifier,
|
|
buck_converter,
|
|
ldo_regulator,
|
|
colpitts_oscillator,
|
|
h_bridge,
|
|
],
|
|
)
|
|
def test_template_returns_netlist(self, factory):
|
|
n = factory()
|
|
assert isinstance(n, Netlist)
|
|
|
|
@pytest.mark.parametrize(
|
|
"factory",
|
|
[
|
|
voltage_divider,
|
|
rc_lowpass,
|
|
inverting_amplifier,
|
|
non_inverting_amplifier,
|
|
differential_amplifier,
|
|
common_emitter_amplifier,
|
|
buck_converter,
|
|
ldo_regulator,
|
|
colpitts_oscillator,
|
|
h_bridge,
|
|
],
|
|
)
|
|
def test_template_has_backanno_and_end(self, factory):
|
|
text = factory().render()
|
|
assert ".backanno" in text
|
|
assert ".end" in text
|
|
|
|
@pytest.mark.parametrize(
|
|
"factory",
|
|
[
|
|
voltage_divider,
|
|
rc_lowpass,
|
|
inverting_amplifier,
|
|
non_inverting_amplifier,
|
|
differential_amplifier,
|
|
common_emitter_amplifier,
|
|
buck_converter,
|
|
ldo_regulator,
|
|
colpitts_oscillator,
|
|
h_bridge,
|
|
],
|
|
)
|
|
def test_template_has_components(self, factory):
|
|
n = factory()
|
|
assert len(n.components) > 0
|
|
|
|
@pytest.mark.parametrize(
|
|
"factory",
|
|
[
|
|
voltage_divider,
|
|
rc_lowpass,
|
|
inverting_amplifier,
|
|
non_inverting_amplifier,
|
|
differential_amplifier,
|
|
common_emitter_amplifier,
|
|
buck_converter,
|
|
ldo_regulator,
|
|
colpitts_oscillator,
|
|
h_bridge,
|
|
],
|
|
)
|
|
def test_template_has_sim_directive(self, factory):
|
|
n = factory()
|
|
# Should have at least one directive starting with a sim type
|
|
sim_types = [".tran", ".ac", ".dc", ".op", ".noise", ".tf"]
|
|
text = n.render()
|
|
assert any(sim in text.lower() for sim in sim_types), (
|
|
f"No simulation directive found in {factory.__name__}"
|
|
)
|