freeroute/tests/test_oracle.py
Ryan Malloy 18f3afc8a5 Add FreeRouting JAR oracle harness for behaviour-level validation
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`.
2026-07-12 08:12:03 -06:00

42 lines
1.4 KiB
Python

"""Tests for the FreeRouting oracle harness (tests/oracle.py).
The routing test is gated on a live JVM + JAR and skips otherwise, so the suite
stays green without Java. When the maze router lands, its output SES will be
diffed against ``routed_net_set`` of the oracle SES here.
"""
from __future__ import annotations
from pathlib import Path
from oracle import HAS_ORACLE, requires_oracle, route_dsn, routed_net_set, routed_nets
FIXTURES = Path(__file__).parent / "dsn" / "fixtures"
ROUTABLE = FIXTURES / "kicad_routable.dsn"
def test_oracle_harness_imports():
# The harness must import and report availability even with no Java/JAR,
# so gated tests can skip cleanly instead of erroring at collection.
assert isinstance(HAS_ORACLE, bool)
@requires_oracle
def test_oracle_routes_and_reports_connectivity():
"""Reference FreeRouting routes a routable KiCad board and we can read it back.
Exercises the whole oracle path — JAR execution, SES production, and the
``routed_nets`` extraction that the router phase will diff against.
"""
ses = route_dsn(ROUTABLE, max_passes=5)
nets = routed_nets(ses)
assert nets, "SES network_out had no nets"
for counts in nets.values():
assert set(counts) == {"wires", "vias"}
routed = routed_net_set(ses)
assert routed, "oracle produced no routed nets on a routable board"
# Every routed net is a subset of all nets in the SES.
assert routed <= set(nets)