Files
FacereDataset/tools/epro2/tests/test_pro_writer.py
Knowit 61fd3ff072 tools/epro2/kicad: fix two --all crashes found running the other 4 Pro projects
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>
2026-04-29 00:48:46 +08:00

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}"