"""Tests for netlist import tools."""
import json
import os
import textwrap
import pytest
@pytest.mark.unit
class TestParseKicadXml:
"""Tests for KiCad XML netlist parsing."""
def test_parse_basic_netlist(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
xml_content = textwrap.dedent("""\
10k
Resistor_SMD:R_0402
4.7k
Resistor_SMD:R_0402
100nF
Capacitor_SMD:C_0402
""")
path = os.path.join(tmp_output_dir, "test.net")
with open(path, "w") as f:
f.write(xml_content)
result = import_netlist(source_path=path)
assert result["success"] is True
assert result["format_detected"] == "kicad_xml"
assert result["statistics"]["net_count"] == 3
assert result["statistics"]["component_count"] == 3
assert result["statistics"]["connection_count"] == 6
# Check nets structure
nets = result["nets"]
assert "GND" in nets
assert ["R1", "2"] in nets["GND"]
assert ["C1", "2"] in nets["GND"]
assert "VCC" in nets
assert len(nets["VCC"]) == 2
# Check components
assert "R1" in result["components"]
assert result["components"]["R1"]["value"] == "10k"
assert result["components"]["R1"]["lib"] == "Device"
def test_auto_detects_xml_format(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
xml = ''
path = os.path.join(tmp_output_dir, "test.xml")
with open(path, "w") as f:
f.write(xml)
result = import_netlist(source_path=path, format="auto")
assert result["success"] is True
assert result["format_detected"] == "kicad_xml"
def test_malformed_xml_returns_error(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
path = os.path.join(tmp_output_dir, "bad.net")
with open(path, "w") as f:
f.write("")
result = import_netlist(source_path=path)
assert result["success"] is False
assert "XML parse error" in result["error"]
def test_verify_connectivity_compatible(self, tmp_output_dir):
"""The nets dict is directly usable as verify_connectivity expected param."""
from mckicad.tools.netlist import import_netlist
xml = textwrap.dedent("""\
IC
""")
path = os.path.join(tmp_output_dir, "compat.net")
with open(path, "w") as f:
f.write(xml)
result = import_netlist(source_path=path)
assert result["success"] is True
# verify_connectivity expects: {"NET1": [["U1", "1"], ["R1", "2"]]}
nets = result["nets"]
assert "NET1" in nets
assert isinstance(nets["NET1"], list)
for pin_pair in nets["NET1"]:
assert isinstance(pin_pair, list)
assert len(pin_pair) == 2
@pytest.mark.unit
class TestParseCsv:
"""Tests for CSV/TSV netlist parsing."""
def test_parse_csv_netlist(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
csv_content = textwrap.dedent("""\
Reference,Pin,Net
R1,1,VCC
R1,2,GND
C1,1,VCC
C1,2,GND
""")
path = os.path.join(tmp_output_dir, "test.csv")
with open(path, "w") as f:
f.write(csv_content)
result = import_netlist(source_path=path)
assert result["success"] is True
assert result["format_detected"] == "csv"
assert result["statistics"]["net_count"] == 2
assert result["statistics"]["component_count"] == 2
assert result["statistics"]["connection_count"] == 4
assert ["R1", "1"] in result["nets"]["VCC"]
assert ["C1", "2"] in result["nets"]["GND"]
def test_parse_tsv_netlist(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
tsv_content = "Reference\tPin\tNet\nR1\t1\tVCC\nR1\t2\tGND\n"
path = os.path.join(tmp_output_dir, "test.tsv")
with open(path, "w") as f:
f.write(tsv_content)
result = import_netlist(source_path=path, format="csv")
assert result["success"] is True
assert result["statistics"]["connection_count"] == 2
def test_csv_missing_columns(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
csv_content = "Name,Value\nR1,10k\n"
path = os.path.join(tmp_output_dir, "bad.csv")
with open(path, "w") as f:
f.write(csv_content)
result = import_netlist(source_path=path, format="csv")
assert result["success"] is False
assert "Reference" in result["error"]
def test_csv_alternative_column_names(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
csv_content = "Designator,Pin Number,Net Name\nU1,3,SDA\nU1,4,SCL\n"
path = os.path.join(tmp_output_dir, "alt.csv")
with open(path, "w") as f:
f.write(csv_content)
result = import_netlist(source_path=path, format="csv")
assert result["success"] is True
assert "SDA" in result["nets"]
assert "SCL" in result["nets"]
@pytest.mark.unit
class TestImportNetlistErrors:
"""Tests for error handling in import_netlist."""
def test_nonexistent_file(self):
from mckicad.tools.netlist import import_netlist
result = import_netlist(source_path="/tmp/nonexistent_12345.net")
assert result["success"] is False
assert "not found" in result["error"]
def test_empty_file(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
path = os.path.join(tmp_output_dir, "empty.net")
with open(path, "w") as f:
f.write("")
result = import_netlist(source_path=path)
assert result["success"] is False
assert "empty" in result["error"]
def test_unsupported_format(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
path = os.path.join(tmp_output_dir, "test.net")
with open(path, "w") as f:
f.write("some content")
result = import_netlist(source_path=path, format="spice")
assert result["success"] is False
assert "Unsupported" in result["error"]
def test_unknown_format_auto(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
path = os.path.join(tmp_output_dir, "mystery.dat")
with open(path, "w") as f:
f.write("random binary garbage 12345")
result = import_netlist(source_path=path)
assert result["success"] is False
assert "auto-detect" in result["error"]
@pytest.mark.unit
class TestOutputFile:
"""Tests for output file writing."""
def test_writes_output_json(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
xml = textwrap.dedent("""\
1k
""")
src = os.path.join(tmp_output_dir, "test.net")
with open(src, "w") as f:
f.write(xml)
out = os.path.join(tmp_output_dir, "output.json")
result = import_netlist(source_path=src, output_path=out)
assert result["success"] is True
assert result["output_path"] == out
assert os.path.isfile(out)
with open(out) as f:
data = json.load(f)
assert "nets" in data
assert "GND" in data["nets"]
def test_default_output_path(self, tmp_output_dir):
from mckicad.tools.netlist import import_netlist
xml = ''
src = os.path.join(tmp_output_dir, "test.net")
with open(src, "w") as f:
f.write(xml)
result = import_netlist(source_path=src)
assert result["success"] is True
assert "output_path" in result
assert os.path.isfile(result["output_path"])