Refactors _build_connectivity() into a two-layer state builder so the union-find internals (pin_at, label_at, wire_segments) are accessible to new analysis tools without duplicating the 200-line connectivity engine. New tools: - audit_wiring: trace all wires connected to a component, report per-pin net membership with wire segment coordinates and connected pins - remove_wires_by_criteria: bulk-remove wires by coordinate filters (y, x, min/max ranges, tolerance) with dry_run preview support - verify_connectivity: compare actual wiring against an expected net-to-pin mapping, report matches/mismatches/missing nets New sexp_parser utilities: - parse_wire_segments: extract (wire ...) blocks with start/end/uuid - remove_sexp_blocks_by_uuid: atomically remove blocks by UUID set
263 lines
8.4 KiB
Python
263 lines
8.4 KiB
Python
"""Tests for schematic analysis tools."""
|
|
|
|
import pytest
|
|
|
|
from tests.conftest import requires_sch_api
|
|
|
|
|
|
@requires_sch_api
|
|
@pytest.mark.unit
|
|
class TestRunSchematicErc:
|
|
"""Tests for the run_schematic_erc tool."""
|
|
|
|
def test_erc_on_populated_schematic(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import run_schematic_erc
|
|
|
|
result = run_schematic_erc(schematic_path=populated_schematic)
|
|
assert result["success"] is True
|
|
assert "violation_count" in result or "error" not in result
|
|
|
|
def test_erc_invalid_path(self):
|
|
from mckicad.tools.schematic_analysis import run_schematic_erc
|
|
|
|
result = run_schematic_erc(schematic_path="/tmp/nonexistent.kicad_sch")
|
|
assert result["success"] is False
|
|
|
|
|
|
@requires_sch_api
|
|
@pytest.mark.unit
|
|
class TestAnalyzeConnectivity:
|
|
"""Tests for the analyze_connectivity tool."""
|
|
|
|
def test_connectivity_on_populated(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import analyze_connectivity
|
|
|
|
result = analyze_connectivity(schematic_path=populated_schematic)
|
|
assert result["success"] is True
|
|
assert "net_count" in result or "error" not in result
|
|
|
|
def test_connectivity_invalid_path(self):
|
|
from mckicad.tools.schematic_analysis import analyze_connectivity
|
|
|
|
result = analyze_connectivity(schematic_path="/tmp/nonexistent.kicad_sch")
|
|
assert result["success"] is False
|
|
|
|
|
|
@requires_sch_api
|
|
@pytest.mark.unit
|
|
class TestCheckPinConnection:
|
|
"""Tests for the check_pin_connection tool."""
|
|
|
|
def test_check_existing_pin(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import check_pin_connection
|
|
|
|
result = check_pin_connection(
|
|
schematic_path=populated_schematic,
|
|
reference="R1",
|
|
pin="1",
|
|
)
|
|
# May succeed or fail depending on kicad-sch-api version
|
|
assert "success" in result
|
|
|
|
def test_check_nonexistent_pin(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import check_pin_connection
|
|
|
|
result = check_pin_connection(
|
|
schematic_path=populated_schematic,
|
|
reference="Z99",
|
|
pin="1",
|
|
)
|
|
assert "success" in result
|
|
|
|
|
|
@requires_sch_api
|
|
@pytest.mark.unit
|
|
class TestVerifyPinsConnected:
|
|
"""Tests for the verify_pins_connected tool."""
|
|
|
|
def test_verify_two_pins(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import verify_pins_connected
|
|
|
|
result = verify_pins_connected(
|
|
schematic_path=populated_schematic,
|
|
ref1="R1",
|
|
pin1="1",
|
|
ref2="R2",
|
|
pin2="1",
|
|
)
|
|
# May succeed or fail depending on kicad-sch-api version
|
|
assert "success" in result
|
|
|
|
|
|
@requires_sch_api
|
|
@pytest.mark.unit
|
|
class TestGetComponentPins:
|
|
"""Tests for the get_component_pins tool."""
|
|
|
|
def test_get_pins(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import get_component_pins
|
|
|
|
result = get_component_pins(
|
|
schematic_path=populated_schematic,
|
|
reference="R1",
|
|
)
|
|
assert "success" in result
|
|
|
|
def test_get_pins_nonexistent(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import get_component_pins
|
|
|
|
result = get_component_pins(
|
|
schematic_path=populated_schematic,
|
|
reference="Z99",
|
|
)
|
|
assert result["success"] is False
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestExportValidation:
|
|
"""Tests for input validation in export tools."""
|
|
|
|
def test_export_netlist_invalid_path(self):
|
|
from mckicad.tools.schematic_analysis import export_netlist
|
|
|
|
result = export_netlist(schematic_path="/tmp/nonexistent.kicad_sch")
|
|
assert result["success"] is False
|
|
|
|
def test_export_pdf_invalid_path(self):
|
|
from mckicad.tools.schematic_analysis import export_schematic_pdf
|
|
|
|
result = export_schematic_pdf(schematic_path="/tmp/nonexistent.kicad_sch")
|
|
assert result["success"] is False
|
|
|
|
def test_export_netlist_bad_format(self):
|
|
from mckicad.tools.schematic_analysis import export_netlist
|
|
|
|
result = export_netlist(
|
|
schematic_path="/tmp/test.kicad_sch",
|
|
format="invalid_format",
|
|
)
|
|
assert result["success"] is False
|
|
assert "Unsupported" in result.get("error", "")
|
|
|
|
|
|
@requires_sch_api
|
|
@pytest.mark.unit
|
|
class TestAuditWiring:
|
|
"""Tests for the audit_wiring tool."""
|
|
|
|
def test_audit_existing_component(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import audit_wiring
|
|
|
|
result = audit_wiring(
|
|
schematic_path=populated_schematic,
|
|
reference="R1",
|
|
)
|
|
assert result["success"] is True
|
|
assert result["reference"] == "R1"
|
|
assert "pin_nets" in result or "pin_nets_preview" in result
|
|
assert result.get("pin_count", 0) > 0
|
|
|
|
def test_audit_nonexistent_component(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import audit_wiring
|
|
|
|
result = audit_wiring(
|
|
schematic_path=populated_schematic,
|
|
reference="Z99",
|
|
)
|
|
assert result["success"] is False
|
|
|
|
def test_audit_invalid_path(self):
|
|
from mckicad.tools.schematic_analysis import audit_wiring
|
|
|
|
result = audit_wiring(
|
|
schematic_path="/tmp/nonexistent.kicad_sch",
|
|
reference="R1",
|
|
)
|
|
assert result["success"] is False
|
|
|
|
def test_audit_empty_reference(self):
|
|
from mckicad.tools.schematic_analysis import audit_wiring
|
|
|
|
result = audit_wiring(
|
|
schematic_path="/tmp/nonexistent.kicad_sch",
|
|
reference="",
|
|
)
|
|
assert result["success"] is False
|
|
|
|
def test_audit_pin_net_structure(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import audit_wiring
|
|
|
|
result = audit_wiring(
|
|
schematic_path=populated_schematic,
|
|
reference="R1",
|
|
)
|
|
if result["success"]:
|
|
pin_nets = result.get("pin_nets", result.get("pin_nets_preview", []))
|
|
for pn in pin_nets:
|
|
assert "pin" in pn
|
|
assert "net" in pn
|
|
assert "wire_count" in pn
|
|
assert "wires" in pn
|
|
assert "connected_pins" in pn
|
|
|
|
|
|
@requires_sch_api
|
|
@pytest.mark.unit
|
|
class TestVerifyConnectivity:
|
|
"""Tests for the verify_connectivity tool."""
|
|
|
|
def test_verify_with_matching_net(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import (
|
|
analyze_connectivity,
|
|
verify_connectivity,
|
|
)
|
|
|
|
# First get actual connectivity to build a valid expected map
|
|
conn = analyze_connectivity(schematic_path=populated_schematic)
|
|
assert conn["success"] is True
|
|
|
|
# Try to verify with an empty expected — should fail validation
|
|
result = verify_connectivity(
|
|
schematic_path=populated_schematic,
|
|
expected={},
|
|
)
|
|
assert result["success"] is False
|
|
|
|
def test_verify_missing_net(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import verify_connectivity
|
|
|
|
result = verify_connectivity(
|
|
schematic_path=populated_schematic,
|
|
expected={"NONEXISTENT_NET": [["U99", "1"]]},
|
|
)
|
|
assert result["success"] is True
|
|
assert result["failed"] >= 1
|
|
# Should report as missing_net or missing pin
|
|
statuses = {r["status"] for r in result["results"]}
|
|
assert statuses & {"missing_net", "mismatch"}
|
|
|
|
def test_verify_invalid_path(self):
|
|
from mckicad.tools.schematic_analysis import verify_connectivity
|
|
|
|
result = verify_connectivity(
|
|
schematic_path="/tmp/nonexistent.kicad_sch",
|
|
expected={"NET": [["R1", "1"]]},
|
|
)
|
|
assert result["success"] is False
|
|
|
|
def test_verify_result_structure(self, populated_schematic):
|
|
from mckicad.tools.schematic_analysis import verify_connectivity
|
|
|
|
result = verify_connectivity(
|
|
schematic_path=populated_schematic,
|
|
expected={"TEST_NET": [["R1", "1"]]},
|
|
)
|
|
assert result["success"] is True
|
|
assert "verified" in result
|
|
assert "failed" in result
|
|
assert "total" in result
|
|
assert "results" in result
|
|
for r in result["results"]:
|
|
assert "net" in r
|
|
assert "status" in r
|