master
John Lauer Publish 1.1.1 552a6b7 18d ago
#!/usr/bin/env python3
"""Exercise `service-kicad pcb drc` / `sch erc` against a LIVE service using
the fixtures attached to adom/service-kicad#2 and #3.

    python3 verification/test_report_cli.py [path/to/service-kicad]

Needs network access to the service the CLI resolves (SERVICE_KICAD_URL
honoured). Asserts the count policy, the exit-code contract, the native
passthrough, and the ERC coordinate-scale hint. It does not deploy or
change anything.
"""
import json, os, subprocess, sys, tempfile

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
FIX = os.path.join(ROOT, "report", "tests", "fixtures")
CLI = sys.argv[1] if len(sys.argv) > 1 else os.path.join(ROOT, "target", "release", "service-kicad")
BOARD = os.path.join(FIX, "aperture-check.kicad_pcb")
SCH = os.path.join(FIX, "erc-source-scaled.kicad_sch")

def run(*args):
    p = subprocess.run([CLI, *args], capture_output=True, text=True)
    return p.returncode, p.stdout, p.stderr

failures = []
def check(name, cond, detail=""):
    print(("ok   " if cond else "FAIL ") + name + (f"  {detail}" if detail and not cond else ""))
    if not cond: failures.append(name)

# issue 3: counts from findings, explicit verdict, exit 5
code, out, err = run("pcb", "drc", BOARD)
check("drc: exit 5 on error findings", code == 5, f"exit={code} err={err}")
check("drc: verdict line says FAILED with 2 errors, 1 warning", out.startswith("DRC FAILED: 2 errors, 1 warning"), out.splitlines()[:1])
check("drc: provenance line has kicad version + sha256", "kicad 10." in out and "sha256" in out, out.splitlines()[1:2])
check("drc: DRC_FAILED hint", "hint [DRC_FAILED]" in out)

code, out, err = run("pcb", "drc", BOARD, "--exit-zero")
check("drc: --exit-zero exits 0 and says so", code == 0 and "--exit-zero" in err, f"exit={code}")

with tempfile.TemporaryDirectory() as d:
    j = os.path.join(d, "r.json"); n = os.path.join(d, "n.json")
    code, out, err = run("pcb", "drc", BOARD, "--format", "json", "--out", j)
    doc = json.load(open(j)); a = doc.get("adom_summary", {})
    check("drc json: adom_summary added, native keys intact", "violations" in doc and a.get("errors") == 2 and a.get("status") == "failed")
    check("drc json: exit_code in summary matches process", a.get("exit_code") == code == 5)
    check("drc json: input sha256 + service provenance", len(a.get("input", {}).get("sha256", "")) == 64 and a.get("service", {}).get("version"))
    code, out, err = run("pcb", "drc", BOARD, "--format", "native", "--out", n)
    nat = json.load(open(n))
    check("drc native: untouched report (no adom_summary), still exit 5", "adom_summary" not in nat and code == 5)

# issue 2: ERC scale hint, nothing rescaled
code, out, err = run("sch", "erc", SCH, "--format", "json")
doc = json.loads(out); a = doc["adom_summary"]
check("erc: exit 5 (incomplete circuit has real errors)", code == 5)
check("erc: scale check says scaled by 100", a["erc_scale"]["status"] == "scaled" and a["erc_scale"]["factor"] == 100.0, a["erc_scale"])
check("erc: ERC_COORDINATES_SCALED hint first", a["hints"][0]["code"] == "ERC_COORDINATES_SCALED")
first = doc["sheets"][0]["violations"][0]["items"][0]["pos"]
check("erc: raw positions preserved (not rescaled)", first["x"] < 5, first)

# malformed input: diagnosed locally, exit 1
with tempfile.TemporaryDirectory() as d:
    broken = os.path.join(d, "broken.kicad_sch")
    open(broken, "wb").write(open(SCH, "rb").read()[:2000])
    code, out, err = run("sch", "erc", broken)
    check("truncated schematic: exit 1 with INPUT_UNLOADABLE + paren balance", code == 1 and "INPUT_UNLOADABLE" in err and "paren balance" in err, err)
    wrong = os.path.join(d, "board.kicad_sch")
    open(wrong, "wb").write(open(BOARD, "rb").read())
    code, out, err = run("sch", "erc", wrong)
    check("board posted as schematic: named as such, exit 1", code == 1 and "not a schematic" in err, err)

print(f"\n{len(failures)} failure(s)" if failures else "\nall checks passed")
sys.exit(1 if failures else 0)