Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
"""End-to-end tests for the ``akcli`` CLI dispatch (``cli.py``).
Drives :func:`akcli.cli.main` with explicit ``argv`` and asserts both
the exit code (per SPEC §8) and the stdout/stderr split (data vs logs).
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from akcli.cli import main
from akcli.errors import EXIT
FIXTURES = Path(__file__).parent / "fixtures"
def F(name: str) -> str:
return str(FIXTURES / name)
SHARED = "shared_name_label.SchDoc"
TWO_GND = "two_gnd_ports.SchDoc"
JUNC = "junction_cross.SchDoc"
# --------------------------------------------------------------------------- #
# global
# --------------------------------------------------------------------------- #
def test_version(capsys):
assert main(["--version"]) == EXIT["OK"]
out = capsys.readouterr().out
assert "akcli" in out
assert "protocol" in out
def test_no_command_is_usage_error(capsys):
assert main([]) == EXIT["USAGE"]
# --------------------------------------------------------------------------- #
# read
# --------------------------------------------------------------------------- #
def test_read_text(capsys):
assert main(["read", F(SHARED)]) == EXIT["OK"]
out = capsys.readouterr().out
assert "components: 4" in out
assert "STAT" in out
def test_read_json(capsys):
assert main(["read", "--json", F(SHARED)]) == EXIT["OK"]
out = capsys.readouterr().out
doc = json.loads(out)
assert doc["schema_version"] == "2.0"
assert doc["source_format"] == "altium"
assert len(doc["components"]) == 4
# net membership present
assert doc["nets"][0]["name"] == "STAT"
def test_read_md(capsys):
assert main(["read", "--md", F(SHARED)]) == EXIT["OK"]
out = capsys.readouterr().out
assert out.lstrip().startswith("# Schematic")
assert "| Designator |" in out
def test_read_kicad_works(capsys):
# KiCad reader is wired into `read`; a .kicad_sch parses
assert main(["read", F("kicad/board_v8.kicad_sch"), "--json"]) == EXIT["OK"]
out = capsys.readouterr().out
assert '"source_format"' in out and "kicad" in out
def test_read_missing_altium_file(capsys):
assert main(["read", F("does_not_exist.SchDoc")]) == EXIT["NOT_FOUND"]
# --------------------------------------------------------------------------- #
# net / component
# --------------------------------------------------------------------------- #
def test_net_query_named(capsys):
assert main(["net", F(SHARED), "STAT"]) == EXIT["OK"]
out = capsys.readouterr().out
assert out.startswith("STAT:")
for tok in ("U2.1", "U3.2", "R7.1", "R12.1"):
assert tok in out
def test_net_list_all_json(capsys):
# the list branch shares `nets`' envelope + net shape: ONE parser for both
assert main(["net", "--json", F(TWO_GND)]) == EXIT["OK"]
doc = json.loads(capsys.readouterr().out)
assert doc["schema_version"] == "1"
assert doc["source"].endswith(".SchDoc")
assert doc["total"] == doc["returned"] == 1 and doc["truncated"] is False
(net,) = doc["nets"]
assert net["name"] == "GND"
assert all(isinstance(m, str) and "." in m for m in net["members"])
def test_net_missing_name(capsys):
assert main(["net", F(SHARED), "NOPE"]) == EXIT["QUERY_MISS"]
assert "no net" in capsys.readouterr().err
def test_net_missing_name_json(capsys):
assert main(["net", "--json", F(SHARED), "NOPE"]) == EXIT["QUERY_MISS"]
doc = json.loads(capsys.readouterr().out)
assert doc == {"schema_version": "1", "found": False, "query": "NOPE",
"kind": "net", "source": F(SHARED)}
def test_net_named_json_shares_nets_shape(capsys):
# named branch: same envelope family + "REF.PIN" members as `nets`,
# plus the single-net diagnostics (is_named / source_names / ...)
assert main(["net", "--json", F(SHARED), "STAT"]) == EXIT["OK"]
doc = json.loads(capsys.readouterr().out)
assert doc["schema_version"] == "1" and doc["query"] == "STAT"
(net,) = doc["nets"]
assert net["name"] == "STAT"
assert "U3.2" in net["members"] # "REF.PIN" strings, not [d,p]
assert net["is_named"] is True
def test_net_at_reverse_lookup(capsys):
assert main(["net", F(SHARED), "--at", "U3.2"]) == EXIT["OK"]
assert "STAT" in capsys.readouterr().out
def test_net_at_json(capsys):
assert main(["net", "--json", F(SHARED), "--at", "U3.2"]) == EXIT["OK"]
doc = json.loads(capsys.readouterr().out)
assert doc["pin"] == "U3.2"
assert doc["net"]["name"] == "STAT"
def test_net_at_unknown_pin_lists_valid_pins(capsys):
assert main(["net", F(SHARED), "--at", "U3.99"]) == EXIT["QUERY_MISS"]
err = capsys.readouterr().err
assert "valid pins:" in err
def test_net_at_unknown_component_did_you_mean(capsys):
assert main(["net", "--json", F(SHARED), "--at", "U99.1"]) \
== EXIT["QUERY_MISS"]
captured = capsys.readouterr()
doc = json.loads(captured.out)
assert doc["found"] is False and doc["kind"] == "pin"
def test_net_same_yes_and_no(capsys):
assert main(["net", F(SHARED), "--same", "U2.1", "U3.2"]) == EXIT["OK"]
assert "yes" in capsys.readouterr().out
# a non-connected pair still exits 0: the boolean IS the answer
board = str(FIXTURES / "corpus" / "power_entry.kicad_sch")
assert main(["net", "--json", board, "--same", "C1.2", "F1.1"]) \
== EXIT["OK"]
doc = json.loads(capsys.readouterr().out)
assert doc["same"] is False and doc["net"] is None
assert doc["nets"]["C1.2"] == "GND"
def test_net_why_geometry_on_kicad(capsys):
board = str(FIXTURES / "corpus" / "power_entry.kicad_sch")
assert main(["net", "--json", board, "--why", "C1.2"]) == EXIT["OK"]
doc = json.loads(capsys.readouterr().out)
assert doc["pin"] == "C1.2" and doc["net"]["name"] == "GND"
assert doc["attachment"]["label_on_tip"] is True
assert doc["off_grid_50mil"] is False
assert doc["nearest"]["label"]["dist_mil"] == 0.0
def test_net_why_non_kicad_answers_membership_only(capsys):
assert main(["net", "--json", F(SHARED), "--why", "U3.2"]) == EXIT["OK"]
doc = json.loads(capsys.readouterr().out)
assert doc["net"]["name"] == "STAT"
assert doc["attachment"] is None # geometry needs .kicad_sch
assert any("kicad_sch" in h for h in doc["hints"])
def test_net_query_flags_are_exclusive(capsys):
assert main(["net", F(SHARED), "STAT", "--at", "U3.2"]) == EXIT["USAGE"]
def test_component_pin_to_net(capsys):
assert main(["component", F(SHARED), "U3"]) == EXIT["OK"]
out = capsys.readouterr().out
assert "component: U3" in out
assert "-> STAT" in out
def test_component_json(capsys):
assert main(["component", "--json", F(SHARED), "U3"]) == EXIT["OK"]
doc = json.loads(capsys.readouterr().out)
assert doc["designator"] == "U3"
assert doc["pin_nets"]["2"] == "STAT"
def test_component_not_found(capsys):
assert main(["component", F(SHARED), "Q99"]) == EXIT["QUERY_MISS"]
assert "no component" in capsys.readouterr().err
def test_component_not_found_json(capsys):
assert main(["component", "--json", F(SHARED), "Q99"]) == EXIT["QUERY_MISS"]
doc = json.loads(capsys.readouterr().out)
assert doc["found"] is False and doc["kind"] == "component"
# --------------------------------------------------------------------------- #
# check
# --------------------------------------------------------------------------- #
def test_check_findings_exit_one(capsys):
# bom check finds missing values/footprints -> WARNING findings -> exit 1
assert main(["check", "--bom", F(SHARED)]) == EXIT["FINDINGS"]
out = capsys.readouterr().out
assert "# metadata" in out
assert "# findings" in out
def test_check_exit_zero_forces_zero(capsys):
assert main(["check", "--bom", "--exit-zero", F(SHARED)]) == EXIT["OK"]
def test_check_json(capsys):
assert main(["check", "--bom", "--json", F(SHARED)]) in (EXIT["OK"], EXIT["FINDINGS"])
doc = json.loads(capsys.readouterr().out)
assert "metadata" in doc and "findings" in doc
def test_check_power_clean_is_zero(capsys):
# two_gnd_ports: power check emits only NOTE (no rails) -> not actionable
rc = main(["check", "--power", F(TWO_GND)])
assert rc == EXIT["OK"]
def test_check_erc_now_runs(capsys):
# erc.py is implemented now: --erc must RUN (no longer "gracefully skipped").
rc = main(["check", "--erc", F(TWO_GND)])
assert rc in (EXIT["OK"], EXIT["FINDINGS"])
err = capsys.readouterr().err
assert "ERC check unavailable" not in err # the skip path is gone
# --------------------------------------------------------------------------- #
# diff
# --------------------------------------------------------------------------- #
def test_diff_identical_is_clean(capsys):
# identical schematic -> only NOTE (low confidence) -> not actionable -> 0
assert main(["diff", F(SHARED), F(SHARED)]) == EXIT["OK"]
def test_diff_different_exit_one(capsys):
assert main(["diff", F(SHARED), F(TWO_GND)]) == EXIT["FINDINGS"]
out = capsys.readouterr().out
assert "DIFF_" in out
def test_diff_json(capsys):
main(["diff", "--json", F(SHARED), F(TWO_GND)])
doc = json.loads(capsys.readouterr().out)
assert "summary" in doc
assert "component_changes" in doc
# --------------------------------------------------------------------------- #
# pinmap
# --------------------------------------------------------------------------- #
def test_pinmap_mcu_flag(capsys):
# U3 pin 2 sits on STAT -> a clean PINMAP info finding, exit 0
assert main(["pinmap", "--mcu", "U3", F(SHARED)]) == EXIT["OK"]
out = capsys.readouterr().out
assert "STAT" in out
def test_pinmap_no_mcu_warns(capsys):
# no config + no --mcu -> PINMAP_NO_MCU WARNING -> exit 1
assert main(["pinmap", F(SHARED)]) == EXIT["FINDINGS"]
def test_pinmap_expected_csv_mismatch(tmp_path, capsys):
csvfile = tmp_path / "expected.csv"
csvfile.write_text("pin,signal\n2,WRONG_NET\n", encoding="utf-8")
rc = main(["pinmap", "--mcu", "U3", "--expected", str(csvfile), F(SHARED)])
assert rc == EXIT["FINDINGS"]
out = capsys.readouterr().out
assert "WRONG_NET" in out
def test_pinmap_expected_json_match(tmp_path, capsys):
jf = tmp_path / "expected.json"
jf.write_text(json.dumps({"2": "STAT"}), encoding="utf-8")
rc = main(["pinmap", "--mcu", "U3", "--expected", str(jf), F(SHARED)])
assert rc == EXIT["OK"]
assert "matches" in capsys.readouterr().out
# --------------------------------------------------------------------------- #
# export
# --------------------------------------------------------------------------- #
def test_export_protel_stdout(capsys):
assert main(["export", "--format", "protel", F(TWO_GND)]) == EXIT["OK"]
out = capsys.readouterr().out
assert "(\nGND\n" in out
assert "U1-1" in out
def test_export_csv_stdout(capsys):
assert main(["export", "--format", "csv", F(TWO_GND)]) == EXIT["OK"]
out = capsys.readouterr().out
assert out.splitlines()[0] == "net,ref,pin"
def test_export_to_file(tmp_path, capsys):
dest = tmp_path / "out.net"
assert main(["export", "--format", "kicad", "-o", str(dest), F(TWO_GND)]) == EXIT["OK"]
text = dest.read_text(encoding="utf-8")
assert text.startswith('(export (version "E")')
# data went to the file, not stdout
assert capsys.readouterr().out == ""
def test_export_default_format(capsys):
# default is protel
assert main(["export", F(JUNC)]) == EXIT["OK"]
assert "[" in capsys.readouterr().out
# --------------------------------------------------------------------------- #
# plan / draw (KiCad op-list executor; full coverage in test_draw_cli.py)
# --------------------------------------------------------------------------- #
@pytest.mark.parametrize("cmd", ["plan", "draw"])
def test_plan_draw_need_a_target(cmd, capsys):
# No target / no --ops -> usage error (exit 2), not a stub notice.
assert main([cmd]) == EXIT["USAGE"]
# --------------------------------------------------------------------------- #
# format handling
# --------------------------------------------------------------------------- #
def test_check_on_kicad_runs(capsys):
# KiCad is supported now; check runs on a .kicad_sch and returns OK or FINDINGS
rc = main(["check", F("kicad/board_v8.kicad_sch")])
assert rc in (EXIT["OK"], EXIT["FINDINGS"])
2126c3284This file is pinned to the selected release. Download the original to open it in your design tool.