NUMERIC rows cite document title, revision, section/table, and page. USB Zdiff, Type-C Rp/Rd, 10BASE-T, and 1000BASE-T stay UNKNOWN. PDFs are not in git.
253 lines
8.3 KiB
Python
253 lines
8.3 KiB
Python
"""M0 protocol pack: parse, reject typical-as-standard, no invented ohms."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from backend.periscopex.models import ValidationReport
|
|
from backend.periscopex.protocol_catalog import (
|
|
CATALOG_PATH,
|
|
SCHEMA_PATH,
|
|
SCHEMA_VERSION,
|
|
ProtocolCatalogError,
|
|
build_m0_catalog,
|
|
empty_protocol_section,
|
|
fail_mandatory,
|
|
load_catalog,
|
|
map_protocol_outcome,
|
|
parse_catalog,
|
|
)
|
|
|
|
|
|
REQUIRED_LOGICAL = {
|
|
"usb2-ls-fs", "usb2-ls", "usb2-fs", "usb2-hs", "usb-c", "usb-c-usb2",
|
|
"usb3-x", "usb4", "100base-tx", "10base-t", "1000base-t",
|
|
"hdmi-1.4", "hdmi-2.0", "hdmi-frl",
|
|
"dvi",
|
|
"ddr-family", "ddr4", "lpddr-family", "lpddr4",
|
|
"hbm-family", "hbm3", "hyperbus", "qspi", "octal-spi", "emmc", "ufs",
|
|
"axi4", "axi4-lite", "ahb", "apb", "wishbone", "avalon-mm", "avalon-st",
|
|
"tilelink", "pcie", "cxl",
|
|
"pci", "pci-x", "ccix", "opencapi", "upi", "dmi",
|
|
"infinity-fabric", "hypertransport", "ucie",
|
|
"i2c", "can", "rgmii",
|
|
}
|
|
|
|
|
|
def test_committed_catalog_parses_and_matches_builder():
|
|
loaded = load_catalog()
|
|
rebuilt = parse_catalog(build_m0_catalog())
|
|
assert loaded.schema_version == SCHEMA_VERSION
|
|
assert loaded.model_dump() == rebuilt.model_dump()
|
|
assert CATALOG_PATH.is_file()
|
|
assert SCHEMA_PATH.is_file()
|
|
schema = json.loads(SCHEMA_PATH.read_text(encoding="utf-8"))
|
|
assert "properties" in schema
|
|
assert schema["properties"]["schema_version"]
|
|
|
|
|
|
def test_logical_and_physical_ids_are_disjoint():
|
|
cat = load_catalog()
|
|
logical = {p.id for p in cat.logical_protocols}
|
|
physical = {p.id for p in cat.physical_interfaces}
|
|
assert not (logical & physical)
|
|
assert REQUIRED_LOGICAL <= logical
|
|
for iface in cat.physical_interfaces:
|
|
assert iface.id != iface.logical_protocol_id
|
|
assert iface.pcb_relevant in {"YES", "NO", "CONDITIONAL"}
|
|
|
|
|
|
def test_amba_internal_is_not_pcb_routing():
|
|
cat = load_catalog()
|
|
axi = next(p for p in cat.physical_interfaces if p.id == "axi4-internal")
|
|
assert axi.pcb_relevant == "NO"
|
|
assert axi.physical_layer == "INTERNAL"
|
|
assert axi.logical_protocol_id == "axi4"
|
|
assert axi.constraints[0].value_kind == "NOT_APPLICABLE"
|
|
|
|
|
|
def test_ddr_has_no_universal_mm_or_ps():
|
|
cat = load_catalog()
|
|
ddr4 = next(p for p in cat.physical_interfaces if p.id == "ddr4-sdram-pcb")
|
|
assert ddr4.pcb_relevant == "YES"
|
|
for c in ddr4.constraints:
|
|
assert c.value_kind in {
|
|
"CONTROLLER_DEPENDENT", "PHY_DEPENDENT", "UNKNOWN",
|
|
"VENDOR_DEPENDENT", "MISSING_SOURCE", "NOT_APPLICABLE",
|
|
}
|
|
assert c.value is None
|
|
assert c.unit is None
|
|
|
|
|
|
def test_hbm_dq_not_pcb_relevant():
|
|
cat = load_catalog()
|
|
hbm = next(p for p in cat.physical_interfaces if p.id == "hbm3-package")
|
|
assert hbm.pcb_relevant == "NO"
|
|
|
|
|
|
def test_hdmi_mini_is_same_logical_different_connector():
|
|
cat = load_catalog()
|
|
a = next(p for p in cat.physical_interfaces if p.id == "hdmi-1.4-tmds-type-a")
|
|
mini = next(
|
|
p for p in cat.physical_interfaces if p.id == "hdmi-1.4-tmds-type-c-mini"
|
|
)
|
|
assert a.logical_protocol_id == mini.logical_protocol_id == "hdmi-1.4"
|
|
assert a.connector == "HDMI-A"
|
|
assert mini.connector == "HDMI-C-mini"
|
|
|
|
|
|
def test_catalog_numeric_requires_official_cite_no_invented_usb_90ohm():
|
|
cat = load_catalog()
|
|
usb_z = []
|
|
for iface in cat.physical_interfaces:
|
|
for c in iface.constraints:
|
|
if c.value_kind == "NUMERIC":
|
|
assert c.value is not None
|
|
assert c.source is not None
|
|
assert c.source.document and c.source.organization
|
|
assert c.source.revision
|
|
assert c.source.page
|
|
assert c.source.section or c.source.table
|
|
if (
|
|
iface.logical_protocol_id.startswith("usb")
|
|
and c.parameter == "differential_impedance"
|
|
):
|
|
usb_z.append(c)
|
|
assert c.value_kind != "NUMERIC"
|
|
assert c.value is None
|
|
assert usb_z
|
|
blob = json.dumps([c.model_dump() for c in usb_z])
|
|
assert "90" not in blob
|
|
|
|
|
|
def test_recommended_is_not_fail_mandatory():
|
|
cat = load_catalog()
|
|
recs = [
|
|
c
|
|
for iface in cat.physical_interfaces
|
|
for c in iface.constraints
|
|
if c.mandatory == "RECOMMENDED"
|
|
]
|
|
assert recs, "catalog should include at least one RECOMMENDED skeleton"
|
|
for c in recs:
|
|
assert fail_mandatory(c) is False
|
|
cls, status, _ = map_protocol_outcome("FAIL", c.mandatory)
|
|
assert status != "ERROR"
|
|
assert cls != "RULE"
|
|
|
|
|
|
def test_mandatory_fail_can_be_error():
|
|
cls, status, _ = map_protocol_outcome("FAIL", "MANDATORY")
|
|
assert cls == "RULE"
|
|
assert status == "ERROR"
|
|
|
|
|
|
def test_reject_typical_as_standard():
|
|
raw = build_m0_catalog()
|
|
iface = raw["physical_interfaces"][0]
|
|
typical = copy.deepcopy(iface["constraints"][0])
|
|
typical["id"] = "bad-typical"
|
|
typical["typical"] = True
|
|
typical["value_origin"] = "TYPICAL"
|
|
typical["source_type"] = "STANDARD"
|
|
typical["source_class"] = "NORMATIVE"
|
|
typical["mandatory"] = "MANDATORY"
|
|
typical["value_kind"] = "UNKNOWN"
|
|
typical["value"] = None
|
|
iface = copy.deepcopy(iface)
|
|
iface["id"] = "usb2-hs-typical-bad"
|
|
iface["constraints"] = [typical]
|
|
raw["physical_interfaces"].append(iface)
|
|
with pytest.raises(ProtocolCatalogError, match="typical"):
|
|
parse_catalog(raw)
|
|
|
|
|
|
def test_reject_constraint_without_source_type():
|
|
raw = build_m0_catalog()
|
|
iface = copy.deepcopy(raw["physical_interfaces"][0])
|
|
iface["id"] = "usb2-hs-no-source"
|
|
bad = copy.deepcopy(iface["constraints"][0])
|
|
bad["id"] = "no-source"
|
|
del bad["source_type"]
|
|
iface["constraints"] = [bad]
|
|
raw["physical_interfaces"].append(iface)
|
|
with pytest.raises(ProtocolCatalogError):
|
|
parse_catalog(raw)
|
|
|
|
|
|
def test_reject_numeric_without_cite():
|
|
raw = build_m0_catalog()
|
|
iface = copy.deepcopy(raw["physical_interfaces"][0])
|
|
iface["id"] = "usb2-hs-fake-z"
|
|
iface["constraints"] = [{
|
|
"id": "fake-zdiff",
|
|
"parameter": "differential_impedance",
|
|
"value_kind": "NUMERIC",
|
|
"value": 90.0,
|
|
"unit": "ohm",
|
|
"mandatory": "MANDATORY",
|
|
"source_type": "STANDARD",
|
|
"source_class": "NORMATIVE",
|
|
"source": None,
|
|
"typical": False,
|
|
"value_origin": "SPEC",
|
|
"conditions": [],
|
|
}]
|
|
raw["physical_interfaces"].append(iface)
|
|
with pytest.raises(ProtocolCatalogError, match="cite"):
|
|
parse_catalog(raw)
|
|
|
|
|
|
def test_reject_boolean_pcb_relevant():
|
|
raw = build_m0_catalog()
|
|
iface = copy.deepcopy(raw["physical_interfaces"][0])
|
|
iface["id"] = "usb2-hs-bool-pcb"
|
|
iface["pcb_relevant"] = True
|
|
raw["physical_interfaces"].append(iface)
|
|
with pytest.raises(ProtocolCatalogError):
|
|
parse_catalog(raw)
|
|
|
|
|
|
def test_reject_same_logical_and_physical_id():
|
|
raw = build_m0_catalog()
|
|
iface = copy.deepcopy(raw["physical_interfaces"][0])
|
|
iface["id"] = iface["logical_protocol_id"]
|
|
raw["physical_interfaces"].append(iface)
|
|
with pytest.raises(ProtocolCatalogError, match="differ"):
|
|
parse_catalog(raw)
|
|
|
|
|
|
def test_empty_report_section_has_no_z():
|
|
section = empty_protocol_section()
|
|
dumped = section.model_dump()
|
|
assert section.message == "nessun protocollo riconosciuto"
|
|
assert section.recognized_instances == []
|
|
assert dumped.get("max_level_reached") is None
|
|
blob = json.dumps(dumped)
|
|
assert "90" not in blob
|
|
assert "ohm" not in blob.lower()
|
|
assert "Zdiff" not in blob
|
|
assert "z0" not in blob.lower()
|
|
report = ValidationReport(
|
|
project="t",
|
|
timestamp="2026-09-22T00:00:00Z",
|
|
findings=[],
|
|
summary={"ERROR": 0, "WARNING": 0, "INFO": 0},
|
|
protocol_certification=dumped,
|
|
)
|
|
assert report.protocol_certification["message"] == "nessun protocollo riconosciuto"
|
|
|
|
|
|
def test_pcie_cxl_physical_packs_are_empty_numbers():
|
|
cat = load_catalog()
|
|
for pid in ("pcie-phy-pcb", "cxl-phy-pcb"):
|
|
iface = next(p for p in cat.physical_interfaces if p.id == pid)
|
|
assert iface.logical_protocol_id in {"pcie", "cxl"}
|
|
assert iface.required_checks
|
|
for c in iface.constraints:
|
|
assert c.value_kind in {"MISSING_SOURCE", "UNKNOWN"}
|
|
assert c.value is None
|