Gate interface certifiers to connectors on this board (2.61.2).

USB-C/RJ45/PoE/DDR3 are the method, not a catalog. No connector means
no finding. Bare RJ45 is Ethernet only; PoE needs PoE evidence. HubAudio
has USB-C and PoE MagJack, not DDR. Skip-I unchanged.
This commit is contained in:
2026-09-21 12:24:54 +02:00
parent c6abdae762
commit bd3a6fd009
14 changed files with 84 additions and 97 deletions
+30 -30
View File
@@ -128,7 +128,8 @@ def _usbc_device_graph(*, ss: bool = False, usb2_value: bool = True) -> DesignGr
return DesignGraph(components=comps, nets=nets)
def test_no_connector_is_na_not_error():
def test_no_connector_emits_nothing():
"""No USB-C / no RJ45 / no PoE evidence → do not emit that certifier."""
g = DesignGraph(
components={
"U1": _comp("U1", ctype=ComponentType.IC, value="MCU", pins={"1": "GND"}),
@@ -136,16 +137,8 @@ def test_no_connector_is_na_not_error():
nets={"GND": _net("GND", NetType.GROUND, ("U1", "1"))},
)
findings = check_interface_classes(g)
for f in findings:
complete_finding(f)
assert _by_rule(findings, "PE-USBC-001")
assert _by_rule(findings, "PE-ETH-001")
assert _by_rule(findings, "PE-POE-001")
assert all(f.status != "ERROR" for f in findings)
assert all(f.finding_class == "INFO" for f in findings)
assert "N/A" in _by_rule(findings, "PE-USBC-001")[0].finding
assert "N/A" in _by_rule(findings, "PE-ETH-001")[0].finding
assert "N/A" in _by_rule(findings, "PE-POE-001")[0].finding
assert findings == []
assert not any((f.rule_id or "").startswith("PE-DDR") for f in findings)
def test_usbc_rd_vbus_gnd_certified():
@@ -166,6 +159,7 @@ def test_usbc_rd_vbus_gnd_certified():
assert ss.finding_class == "INFO"
assert "N/A" in ss.finding or "USB2" in ss.finding
assert all(f.status != "ERROR" for f in findings if f.designator == "J2")
assert not any((f.rule_id or "").startswith(("PE-ETH", "PE-POE", "PE-DDR")) for f in findings)
def test_usbc_missing_cc2_is_error():
@@ -429,13 +423,11 @@ def test_bare_rj45_does_not_invent_poe():
findings = check_interface_classes(g)
for f in findings:
complete_finding(f)
poe = _by_rule(findings, "PE-POE-001", "J1")[0]
assert poe.status != "ERROR"
assert poe.finding_class == "INFO"
assert "N/A" in poe.finding
assert not _by_rule(findings, "PE-POE-003", "J1") or _by_rule(
findings, "PE-POE-003", "J1",
)[0].status != "ERROR"
assert _by_rule(findings, "PE-ETH-001", "J1")
assert not any((f.rule_id or "").startswith("PE-POE") for f in findings)
assert not any((f.rule_id or "").startswith("PE-USBC") for f in findings)
assert not any((f.rule_id or "").startswith("PE-DDR") for f in findings)
assert all(f.status != "ERROR" for f in findings if (f.rule_id or "").startswith("PE-ETH"))
def test_poe_with_evidence_requires_magnetics_isolation():
@@ -494,10 +486,9 @@ def test_simple_project_usbc_not_false_error():
ss = _by_rule(findings, "PE-USBC-005", "J1")[0]
assert ss.status != "ERROR"
assert ss.evidence_status == "INSUFFICIENT" or "N/A" in ss.finding
assert _by_rule(findings, "PE-ETH-001")[0].status != "ERROR"
assert "N/A" in _by_rule(findings, "PE-ETH-001")[0].finding
assert _by_rule(findings, "PE-POE-001")[0].status != "ERROR"
assert "N/A" in _by_rule(findings, "PE-POE-001")[0].finding
assert not any((f.rule_id or "").startswith("PE-ETH") for f in findings)
assert not any((f.rule_id or "").startswith("PE-POE") for f in findings)
assert not any((f.rule_id or "").startswith("PE-DDR") for f in findings)
def test_hubaudio_like_usbc_usb2_and_poe_magjack():
@@ -538,6 +529,17 @@ def test_hubaudio_like_usbc_usb2_and_poe_magjack():
for f in findings
if (f.rule_id or "").startswith(("PE-USBC", "PE-ETH", "PE-POE"))
)
assert not any((f.rule_id or "").startswith("PE-DDR") for f in findings)
def test_hubaudio_bom_has_usbc_and_poe_rj45_not_ddr():
"""HubAudio actual parts: J2 USB-C USB2 16P, J1 PoE 10/100 MagJack, no DDR."""
from tests.paths import REPO_ROOT
bom = (REPO_ROOT / "tests" / "fixtures" / "hubaudio_kicad_bom.csv").read_text()
assert "USB_C_Receptacle_USB2.0_16P" in bom
assert "RJ45" in bom and "PoE" in bom
assert "DDR3" not in bom and "DDR2" not in bom and "DDR4" not in bom
assert "USB3" not in bom
def test_rule_catalog_shared_not_si():
@@ -551,16 +553,14 @@ def test_rule_catalog_shared_not_si():
assert rec.domain == "shared"
def test_run_pcb_checks_includes_interface_class():
def test_run_pcb_checks_skips_absent_interfaces():
findings = run_pcb_checks(DesignGraph(), {}, None)
ids = {f.rule_id for f in findings}
assert "PE-USBC-001" in ids
assert "PE-ETH-001" in ids
assert "PE-POE-001" in ids
if_f = [f for f in findings if (f.rule_id or "").startswith(("PE-USBC", "PE-ETH", "PE-POE"))]
assert if_f
assert all(f.status != "ERROR" for f in if_f)
assert all((f.finding_id or "").startswith("PCB-") for f in if_f)
assert "PE-USBC-001" not in ids
assert "PE-ETH-001" not in ids
assert "PE-POE-001" not in ids
if_f = [f for f in findings if (f.rule_id or "").startswith(("PE-USBC", "PE-ETH", "PE-POE", "PE-DDR"))]
assert if_f == []
def test_merge_drops_duplicate_pcb_interface_findings():