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:
@@ -2,7 +2,7 @@
|
||||
|
||||
Favor: perfect golden match; citation rate ignores deterministic findings;
|
||||
simple_project graph still has U1/U2/U3, the two I2C pull-up keys,
|
||||
and USB-C class certifiers on J1 (Ethernet/PoE N/A).
|
||||
and USB-C class certifiers on J1 (no Ethernet/PoE — simple_project has no RJ45).
|
||||
Against: extra finding drops precision; missing golden key drops recall;
|
||||
Unverified quotes are citation misses, not hits.
|
||||
"""
|
||||
@@ -88,9 +88,9 @@ def test_simple_project_eval_matches_committed_golden():
|
||||
assert scores.graph_ok, scores.graph_errors
|
||||
assert scores.precision == 1.0
|
||||
assert scores.recall == 1.0
|
||||
assert scores.finding_count == 10
|
||||
assert scores.finding_count == 8
|
||||
assert scores.by_status["WARNING"] == 2
|
||||
assert scores.by_status["INFO"] == 8
|
||||
assert scores.by_status["INFO"] == 6
|
||||
|
||||
|
||||
def test_simple_project_eval_rejects_truncated_graph(tmp_path: Path):
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -48,7 +48,7 @@ def test_simple_project_without_pcb_has_no_ps_plc():
|
||||
|
||||
def test_simple_project_eval_has_no_placement_keys():
|
||||
scores = eval_simple_project(SIMPLE)
|
||||
assert scores.finding_count == 10
|
||||
assert scores.finding_count == 8
|
||||
assert scores.precision == 1.0
|
||||
assert scores.recall == 1.0
|
||||
assert not any(k.startswith("PE-PLC-") for k in scores.extra_keys)
|
||||
|
||||
@@ -54,7 +54,7 @@ def test_simple_project_without_pcb_has_no_ps_si_001():
|
||||
|
||||
def test_simple_project_eval_has_no_si_keys():
|
||||
scores = eval_simple_project(SIMPLE)
|
||||
assert scores.finding_count == 10
|
||||
assert scores.finding_count == 8
|
||||
assert scores.precision == 1.0
|
||||
assert scores.recall == 1.0
|
||||
assert not any(
|
||||
|
||||
@@ -160,9 +160,9 @@ async def test_concurrency_bounded_by_knob_and_charging_isolated(workspace, monk
|
||||
assert review_n == len(IC_MPNS) # all 5 reviewed
|
||||
if_ids = {
|
||||
f.get("rule_id") for f in report["findings"]
|
||||
if (f.get("rule_id") or "").startswith(("PE-USBC", "PE-ETH", "PE-POE"))
|
||||
if (f.get("rule_id") or "").startswith(("PE-USBC", "PE-ETH", "PE-POE", "PE-DDR"))
|
||||
}
|
||||
assert "PE-USBC-001" in if_ids # N/A certifier, not a 6th IC review
|
||||
assert not if_ids # five ICs, no USB-C/RJ45/DDR — do not invent those certifiers
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user