Running the new --all on the remaining 4 Pro projects (X86 motherboard, 220V power supply, Taishan Pi, Liangshan Pi) surfaced two crash modes not covered by ESP-VoCat: 1. Odd inner-layer count → KiCad rejects the file at load with "3 is not a valid layer count". The 220V power boards have one used inner SIGNAL layer (3 copper total: F.Cu / In1.Cu / B.Cu), but KiCad requires an even copper count. Fixed pcb_writer to pad with one empty inner layer when the inner count is odd, so the total stays even (2, 4, 6, ...). 2. Two BOARDs sharing the same META.title — twin "显示板" boards in the 220V power project — landed in the same project directory and the second silently overwrote the first's .kicad_sch / .kicad_pcb / .kicad_pro. Fixed --all to detect title collisions and suffix every colliding basename with the BOARD uuid prefix (so both '显示板' boards become '显示板_52e8cc76' and '显示板_55d32906' rather than one quietly winning). 71 → 73 unit tests pass (test_odd_inner_signal_count_padded_to_even_total + test_duplicate_board_titles_get_distinct_basenames). Tangentially noted while running this: Taishan Pi and Liangshan Pi are Pro 2.x JSON, not EPRO2 streams — our replay layer reads the files but doesn't decode docType, so SCH/PCB grouping returns nothing. Pro 2.x needs a separate writer; out of scope for this commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
77 lines
3.3 KiB
Python
77 lines
3.3 KiB
Python
"""Project file emitter regression."""
|
|
|
|
import json
|
|
|
|
from tools.epro2.kicad.pro_writer import write_kicad_pro
|
|
|
|
|
|
def test_pro_carries_filename_and_root_sheet_uuid():
|
|
"""The .kicad_pro must record (a) its own filename so KiCad confirms
|
|
the basename matches the .kicad_sch / .kicad_pcb siblings, and (b)
|
|
the root sheet uuid in the `sheets` array — that's how KiCad binds
|
|
the project to the schematic root. A `sheets: []` pro file works
|
|
for "open a one-off PCB" but loses cross-tool navigation in the
|
|
schematic editor."""
|
|
text = write_kicad_pro("EchoEar-CoreBoard", root_sheet_uuid="abc-123")
|
|
j = json.loads(text)
|
|
assert j["meta"]["filename"] == "EchoEar-CoreBoard.kicad_pro"
|
|
assert j["sheets"] == [["abc-123", ""]]
|
|
|
|
|
|
def test_pro_without_root_uuid_emits_empty_sheets_array():
|
|
"""When called for a board that has only a PCB (the SCH was
|
|
DELETE_DOC), there's no root sheet uuid. Emit `sheets: []` so
|
|
the project file still parses — KiCad will simply not show a
|
|
schematic editor on open."""
|
|
text = write_kicad_pro("OnlyPcb", root_sheet_uuid=None)
|
|
j = json.loads(text)
|
|
assert j["sheets"] == []
|
|
|
|
|
|
def test_pro_top_level_keys_present_for_kicad_8():
|
|
"""KiCad 8 expects certain top-level keys to exist (it'll
|
|
backfill missing ones but with a "save changes?" prompt every
|
|
open). Smoke-test: assert the keys that GUI reads at startup."""
|
|
text = write_kicad_pro("X")
|
|
j = json.loads(text)
|
|
for key in ("board", "meta", "schematic", "sheets", "net_settings"):
|
|
assert key in j, f"missing top-level key: {key}"
|
|
|
|
|
|
def test_duplicate_board_titles_get_distinct_basenames():
|
|
"""Two BOARDs that happen to share a title (seen on the 220V power
|
|
project — twin '显示板' boards in the same EPRO2 project) must end up
|
|
in distinct project directories. Dedup uses the BOARD uuid prefix
|
|
so each .kicad_pro/.kicad_sch/.kicad_pcb trio stays self-contained."""
|
|
from tools.epro2.kicad.__main__ import _project_basename, _group_by_board
|
|
from tools.epro2.replay import Document, Project
|
|
|
|
p = Project(project_uuid="p")
|
|
for board_uuid, sch_uuid, pcb_uuid in [
|
|
("b1aaaaa1", "s1aaaaa1", "p1aaaaa1"),
|
|
("b2bbbbb2", "s2bbbbb2", "p2bbbbb2"),
|
|
]:
|
|
sch = Document(doc_uuid=sch_uuid, doc_type="SCH")
|
|
sch.objects["META"] = {"_type": "META", "title": "显示板", "board": board_uuid}
|
|
p.documents[sch_uuid] = sch
|
|
pcb = Document(doc_uuid=pcb_uuid, doc_type="PCB")
|
|
pcb.objects["META"] = {"_type": "META", "title": "显示板", "board": board_uuid}
|
|
p.documents[pcb_uuid] = pcb
|
|
|
|
boards = _group_by_board(p)
|
|
assert len(boards) == 2
|
|
# Re-implement the dedup logic the CLI uses (kept inline so we exercise
|
|
# exactly that path; if it changes, this test breaks loudly).
|
|
base_counts: dict[str, int] = {}
|
|
for slot in boards.values():
|
|
base_counts[_project_basename(slot["title"])] = (
|
|
base_counts.get(_project_basename(slot["title"]), 0) + 1
|
|
)
|
|
basenames = [
|
|
_project_basename(slot["title"])
|
|
if base_counts[_project_basename(slot["title"])] == 1
|
|
else f"{_project_basename(slot['title'])}_{board_id[:8]}"
|
|
for board_id, slot in boards.items()
|
|
]
|
|
assert len(set(basenames)) == 2, f"basenames collided: {basenames}"
|