FreeRouting ships no unit tests for its geometry/router, so there is no
value-level oracle to port against. This adds a dev/test-only harness that runs
the reference JAR to route a DSN, letting freeroute's output be diffed against
the reference implementation — the router phase will assert connectivity parity
(same nets routed) via routed_net_set().
- tests/oracle.py: locate Java 21+ and a freerouting JAR (env overrides:
FREEROUTE_ORACLE_JAVA, FREEROUTING_JAR), route a DSN, and extract routed
connectivity from the SES. requires_oracle skips when no JVM/JAR is present,
so the suite stays Java-free.
- tests/test_oracle.py: routes a routable board end-to-end and checks the
connectivity extraction.
- tests/dsn/fixtures/kicad_routable.dsn: a real KiCad pcbnew-exported DSN
(Arduino_Mega template, path sanitized) that actually routes — the smd_demo
fixture leaves its nets unrouted.
- pyproject: pythonpath=["tests"] so the harness imports as `oracle`.
Ports the read path of FreeRouting's io/specctra/parser package to a
Java-free Python implementation:
- tokenizer: S-expression lexer mirroring SpecctraFileDescription.flex
(comments, quoted strings, the string_quote IGNORE_QUOTE directive,
case-insensitive keywords, and hash-prefixed names)
- sexp: nested S-expression tree builder
- shapes: rect/circle/polygon/path plus area-with-holes scopes
- model: typed dataclasses for layers, padstacks, images, placements,
nets, net classes, rules, keepouts
- reader: recursive-descent scope readers producing a DsnBoard, one
_read_* function per FreeRouting read_scope method
46 pytest cases cover the tokenizer, tree, shapes, and end-to-end
parsing against hand-crafted fixtures modeled on FreeRouting's own
test DSN files. Parses 90 of 91 upstream fixtures (the one failure is
a binary OLE file, not text DSN).