Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
"""`akcli ops from-intent` CLI + the round-trip property (the acceptance test).
The heart of the feature: a design document generates an op-list that `plan`
accepts, `draw --apply` writes, and the drawn sheet's netlist EQUALS the
design's nets table — asserted three ways (normalized `nets
--intent-snapshot`, `check --intent <the same design file>` with zero
findings, and the layout lint clean of overlap classes).
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from akcli import ops
from akcli.drivers import kicad_cli
from tests._cli_harness import json_of, run_cli
ROOT = Path(__file__).resolve().parents[1]
SYMS = ROOT / "tests" / "fixtures" / "kicad" / "symbols"
GENERIC = str(SYMS / "Generic.kicad_sym")
DEVICE = str(SYMS / "Device.kicad_sym")
_DESIGN = {
"protocol_version": 1,
"nets": {
"RAIL_A": ["U1.2", "C1.1", "J1.1"],
"RAIL_IN": ["U1.3", "J1.2"],
"GND": ["U1.1", "C1.2", "J1.4", "U2.1"],
"SIG_N": ["U2.2", "J1.3"],
},
"components": {
"U1": {"lib_id": "Generic:REG3", "value": "REG-ADJ"},
"C1": {"lib_id": "Device:C", "value": "10u"},
"J1": {"lib_id": "Generic:CONN_1x4"},
"U2": {"lib_id": "Generic:DUAL_BUF"},
},
}
def _write_design(tmp_path: Path, doc: dict | None = None) -> Path:
p = tmp_path / "design.json"
p.write_text(json.dumps(doc or _DESIGN), encoding="utf-8", newline="\n")
return p
def _generate(tmp_path: Path, design: Path) -> Path:
out = tmp_path / "ops.json"
rc, _, err = run_cli(["ops", "from-intent", str(design), "-o", str(out),
"--symbols", GENERIC, "--symbols", DEVICE])
assert rc == 0, err
assert "from-intent:" in err # stderr summary, stdout untouched
return out
# --------------------------------------------------------------------------- #
# CLI surface
# --------------------------------------------------------------------------- #
def test_stdout_is_the_oplist(tmp_path):
design = _write_design(tmp_path)
rc, out, err = run_cli(["ops", "from-intent", str(design),
"--symbols", GENERIC, "--symbols", DEVICE])
assert rc == 0, err
doc = json.loads(out) # stdout is pure op-list JSON
assert doc["protocol_version"] == ops.PROTOCOL_VERSION and doc["ops"]
def test_output_file_matches_stdout(tmp_path):
design = _write_design(tmp_path)
rc, out, _ = run_cli(["ops", "from-intent", str(design),
"--symbols", GENERIC, "--symbols", DEVICE])
assert rc == 0
op_file = _generate(tmp_path, design)
assert json.loads(op_file.read_text(encoding="utf-8")) == json.loads(out)
def test_missing_design_arg_is_usage(tmp_path):
rc, _, err = run_cli(["ops", "from-intent"])
assert rc == 2
assert "design" in err
def test_missing_design_file_exits_4(tmp_path):
rc, _, _ = run_cli(["ops", "from-intent", str(tmp_path / "nope.json")])
assert rc == 4
def test_refusal_surfaces_design_unsupported(tmp_path):
design = _write_design(tmp_path, {
"protocol_version": 1,
"nets": {"A": ["U1.IN"]}, # pin by NAME
"components": {"U1": {"lib_id": "Generic:REG3"}},
})
rc, _, err = run_cli(["ops", "from-intent", str(design),
"--symbols", GENERIC])
assert rc == 6
assert "DESIGN_UNSUPPORTED" in err and "U1.3" in err
def test_unknown_lib_id_exits_6_symbol_not_found(tmp_path):
design = _write_design(tmp_path, {
"protocol_version": 1,
"nets": {"A": ["U1.1"]},
"components": {"U1": {"lib_id": "No:Such"}},
})
rc, _, err = run_cli(["ops", "from-intent", str(design)])
assert rc == 6
assert "SYMBOL_NOT_FOUND" in err
# --------------------------------------------------------------------------- #
# the round-trip property
# --------------------------------------------------------------------------- #
def _round_trip(tmp_path: Path, design_doc: dict) -> tuple[Path, Path]:
"""design -> ops -> plan (accepted) -> fresh sheet -> draw --apply."""
design = _write_design(tmp_path, design_doc)
op_file = _generate(tmp_path, design)
target = tmp_path / "board.kicad_sch"
rc, _, err = run_cli(["new", str(target)])
assert rc == 0, err
plan = json_of(["plan", str(target), "--ops", str(op_file),
"--symbols", GENERIC, "--symbols", DEVICE, "--json"],
expect=0)
assert plan["status"] == "dry-run"
assert all(op["status"] == "ok" for op in plan["ops"])
drew = json_of(["draw", str(target), "--ops", str(op_file),
"--symbols", GENERIC, "--symbols", DEVICE,
"--apply", "--json"], expect=0)
assert drew["status"] == "applied"
return design, target
def test_round_trip_nets_equal_and_intent_clean(tmp_path):
design, target = _round_trip(tmp_path, _DESIGN)
# (1) the drawn netlist ≡ the design's nets table (normalized: named
# nets only, sorted members)
rc, out, err = run_cli(["nets", str(target), "--intent-snapshot", "-"])
assert rc == 0, err
snapshot = json.loads(out)
assert ({k: sorted(v) for k, v in snapshot["nets"].items()}
== {k: sorted(v) for k, v in _DESIGN["nets"].items()})
# (2) the SAME design file asserts clean over the drawn sheet
verdict = json_of(["check", str(target), "--intent", str(design),
"--json"], expect=0)
assert verdict["findings"] == []
# (3) layout: no ERROR-class findings, and none of the overlap classes
# the generator's cell sizing exists to prevent (even as warnings)
layout = json_of(["check", str(target), "--layout", "--json"],
expect=(0, 1))
assert not [f for f in layout["findings"]
if f["severity"] in ("error", "critical")]
assert not [f for f in layout["findings"]
if f["code"] in ("LAYOUT_SYMBOL_OVERLAP",
"LAYOUT_LABEL_OVER_SYMBOL",
"LAYOUT_LABEL_OVERLAP")]
def test_round_trip_reapply_is_byte_identical(tmp_path):
"""The generated op-list obeys the writer's idempotency invariant."""
_, target = _round_trip(tmp_path, _DESIGN)
before = target.read_bytes()
rc, _, err = run_cli(["draw", str(target), "--ops",
str(tmp_path / "ops.json"),
"--symbols", GENERIC, "--symbols", DEVICE,
"--apply"])
assert rc == 0, err
assert target.read_bytes() == before
def test_round_trip_grouped(tmp_path):
doc = dict(_DESIGN)
doc["layout"] = {"group": "FRONTEND", "origin": [2000, 2000]}
design, target = _round_trip(tmp_path, doc)
verdict = json_of(["check", str(target), "--intent", str(design),
"--json"], expect=0)
assert verdict["findings"] == []
# group membership persisted on the sheet (hidden Group property)
rc, out, _ = run_cli(["groups", str(target)])
assert rc == 0 and "FRONTEND" in out
def test_round_trip_drawn_file_accepted_by_kicad_cli(tmp_path):
"""A real ``kicad-cli`` (CI KiCad job) must accept the generated sheet."""
if not kicad_cli.available():
pytest.skip("kicad-cli not installed (runs in the CI KiCad job)")
_, target = _round_trip(tmp_path, _DESIGN)
report = kicad_cli.erc(str(target))
assert report is not None
2126c3284This file is pinned to the selected release. Download the original to open it in your design tool.