Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
"""Tests for the error-code registry and exit-code table (errors.py)."""
from __future__ import annotations
import pytest
from akcli.errors import (
_CODE_EXIT,
ERROR_CODES,
EXIT,
REMEDIATION,
AkcliError,
as_error,
exit_for_code,
fail,
remediation_for,
to_exit,
)
def test_required_codes_present():
required = {
"ALTIUM_BAD_MAGIC", "ALTIUM_FAT_CYCLE", "ALTIUM_OOB_SECTOR",
"ALTIUM_BAD_SECTOR_SHIFT", "ALTIUM_ALLOC_GUARD", "ALTIUM_MALFORMED",
"ALTIUM_UNSUPPORTED",
"KICAD_SEXPR_DEPTH", "KICAD_SEXPR_UNTERMINATED", "KICAD_SEXPR_TOOBIG",
"KICAD_SEXPR_BAD_NUMBER", "KICAD_BAD_ENCODING", "INTERNAL",
"SYMBOL_NOT_FOUND", "BAD_ANGLE", "NON_ORTHOGONAL_WIRE", "OFF_GRID",
"OVERLAP", "VERIFY_FAILED", "OP_UNSUPPORTED", "HIERARCHICAL_UNSUPPORTED",
"PROTOCOL_MISMATCH", "TARGET_LOCKED", "PATH_OUTSIDE_ROOT",
"FILE_UNREADABLE",
# design-doc -> op-list generation (`akcli ops from-intent`)
"DESIGN_UNSUPPORTED",
# functional groups (envelope `groups` + per-op `group` tag)
"GROUP_UNKNOWN", "GROUP_NO_ORIGIN",
# document integrity shared by the writer and connectivity.verify:
# two power ports on one coordinate, a (sheet) with no child file
"POWER_PORT_STACKED", "SHEET_FILE_MISSING",
# sheet-tree root resolution (hierarchy-aware net-diff scope)
"PROJECT_ROOT_AMBIGUOUS", "PROJECT_ROOT_MISMATCH",
# project scaffolding (`akcli project new` / `project init`)
"PROJECT_EXISTS", "PROJECT_FILE_INVALID", "PROJECT_FILE_MISSING",
# multi-sheet op-list envelope + write transaction
"SHEET_UNKNOWN", "INSTANCE_REFS_REQUIRED", "TXN_ROLLBACK_FAILED",
"KICAD_CLI_TIMEOUT", "KICAD_CLI_MISSING", "BAD_CONFIG",
# ngspice engine + the jlc networked surface
"NGSPICE_MISSING", "NGSPICE_FAILED", "NETWORK",
# sim deck build / sim.json (raised before the engine is ever loaded)
"SIM_NO_GROUND", "SIM_NODE_COLLISION", "SIM_NODE_COUNT_MISMATCH",
"SIM_VALUE_INVALID", "SIM_OP_ASSERT_UNSUPPORTED",
# binary-fetch integrity codes
"BINFETCH_DOWNLOAD", "BINFETCH_CHECKSUM",
# reserved transport codes (the Altium live driver was deleted; nothing
# raises these, but the published error/exit tables stay frozen)
"BRIDGE_BUSY", "BRIDGE_TIMEOUT",
}
assert required <= ERROR_CODES
assert len(ERROR_CODES) == len(required)
def test_exit_table_values():
assert EXIT["OK"] == 0
assert EXIT["FINDINGS"] == 1
assert EXIT["USAGE"] == 2
assert EXIT["PARSE"] == 3
assert EXIT["NOT_FOUND"] == 4
assert EXIT["UNSUPPORTED_FORMAT"] == 5
assert EXIT["OPLIST"] == 6
assert EXIT["TOOL_MISSING"] == 7
assert EXIT["QUERY_MISS"] == 8
def test_fail_raises_akclierror_with_code():
with pytest.raises(AkcliError) as ei:
fail("ALTIUM_BAD_MAGIC", "bad header")
assert ei.value.code == "ALTIUM_BAD_MAGIC"
assert "bad header" in ei.value.message
def test_fail_rejects_unknown_code():
with pytest.raises(AkcliError) as ei:
fail("NOT_A_REAL_CODE", "x")
assert ei.value.code == "ALTIUM_MALFORMED"
def test_exit_mapping_categories():
assert exit_for_code("ALTIUM_FAT_CYCLE") == EXIT["PARSE"]
assert exit_for_code("KICAD_SEXPR_DEPTH") == EXIT["PARSE"]
assert exit_for_code("OP_UNSUPPORTED") == EXIT["OPLIST"]
assert exit_for_code("DESIGN_UNSUPPORTED") == EXIT["OPLIST"]
assert exit_for_code("PROTOCOL_MISMATCH") == EXIT["OPLIST"]
assert exit_for_code("BAD_CONFIG") == EXIT["USAGE"]
assert exit_for_code("PATH_OUTSIDE_ROOT") == EXIT["USAGE"]
assert exit_for_code("KICAD_CLI_MISSING") == EXIT["TOOL_MISSING"]
assert exit_for_code("BRIDGE_BUSY") == EXIT["OPLIST"]
# a refused write is op-list class, whether the op stacked a power port or
# pointed a sheet at a child that does not exist
assert exit_for_code("POWER_PORT_STACKED") == EXIT["OPLIST"]
assert exit_for_code("SHEET_FILE_MISSING") == EXIT["OPLIST"]
assert exit_for_code("BRIDGE_TIMEOUT") == EXIT["TOOL_MISSING"]
# sim deck-build refusals are op-list class (nothing ran, exit 6) — never
# exit 7, which would read as "install a tool"; a bad sim.json is usage (2).
assert exit_for_code("SIM_NO_GROUND") == EXIT["OPLIST"]
assert exit_for_code("SIM_NODE_COLLISION") == EXIT["OPLIST"]
assert exit_for_code("SIM_VALUE_INVALID") == EXIT["OPLIST"]
assert exit_for_code("SIM_OP_ASSERT_UNSUPPORTED") == EXIT["USAGE"]
def test_every_code_has_exit_mapping():
"""Every ERROR code must have its OWN ``_CODE_EXIT`` entry (exact 1:1,
mirroring the REMEDIATION census below) — ``exit_for_code`` silently
falls back to ``EXIT["PARSE"]`` for an unmapped code, so merely asserting
the return value is an int (the previous version of this test) can never
catch a new error code that ships without its exit-code mapping."""
missing = sorted(ERROR_CODES - set(_CODE_EXIT))
assert not missing, f"ERROR codes without a _CODE_EXIT mapping: {missing}"
stale = sorted(set(_CODE_EXIT) - ERROR_CODES)
assert not stale, f"_CODE_EXIT entries for unknown codes: {stale}"
for code in ERROR_CODES:
assert isinstance(exit_for_code(code), int)
def test_to_exit_for_filenotfound():
assert to_exit(FileNotFoundError(2, "no", "x")) == EXIT["NOT_FOUND"]
assert to_exit(ValueError("?")) == EXIT["PARSE"]
assert to_exit(AkcliError("KICAD_CLI_TIMEOUT")) == EXIT["TOOL_MISSING"]
def test_every_error_code_has_a_remediation_hint():
"""The agent contract: no error path is hint-less (SPEC agent surface)."""
missing = sorted(ERROR_CODES - set(REMEDIATION))
assert not missing, f"ERROR codes without a remediation hint: {missing}"
stale = sorted(set(REMEDIATION) - ERROR_CODES)
assert not stale, f"remediation hints for unknown codes: {stale}"
for code, hint in REMEDIATION.items():
assert hint.strip(), f"empty remediation for {code}"
def test_remediation_for_lookup():
assert remediation_for("BAD_ANGLE") == REMEDIATION["BAD_ANGLE"]
assert remediation_for("NOT_A_CODE") is None
assert remediation_for(None) is None
def test_as_error_line_format():
e = AkcliError("OP_UNSUPPORTED", "no such op")
assert e.as_error_line() == "ERROR: OP_UNSUPPORTED: no such op"
assert as_error(e) == "ERROR: OP_UNSUPPORTED: no such op"
assert as_error(ValueError("boom")).startswith("ERROR: ValueError")
assert e.exit_code == EXIT["OPLIST"]
2126c3284This file is pinned to the selected release. Download the original to open it in your design tool.