From e58f63e0f17cf306c809d6c1ae59a8f08df112ca Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 13 Jul 2026 02:43:35 -0600 Subject: [PATCH] Add dense fixture and shove tests Adds shove_needed.dsn: one signal layer (no vias), where NET_A routes as a straight wall that boxes NET_C out under greedy ordering. Tests assert the contrast: without shove NET_C is dropped (result still DRC-clean, just incomplete); with shove NET_A is moved aside and both nets connect, the exact has_violation check is clean, endpoints stay on pads, and the result is deterministic. Regression tests confirm shove is a no-op on the simple/ crossing/ripup/kicad boards (same connectivity, DRC-clean, zero shoves). Oracle-gated parity is included (skips when the JAR cannot route the synthetic single-layer board). --- tests/dsn/fixtures/shove_needed.dsn | 64 +++++++++++++++ tests/route/test_shove.py | 117 ++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 tests/dsn/fixtures/shove_needed.dsn create mode 100644 tests/route/test_shove.py diff --git a/tests/dsn/fixtures/shove_needed.dsn b/tests/dsn/fixtures/shove_needed.dsn new file mode 100644 index 0000000..68a73bf --- /dev/null +++ b/tests/dsn/fixtures/shove_needed.dsn @@ -0,0 +1,64 @@ +(pcb "shove_needed.dsn" + (parser + (string_quote ") + (space_in_quoted_tokens on) + (host_cad "freeroute-test") + (host_version "1.0") + ) + (resolution um 10) + (unit um) + (structure + (layer F.Cu + (type signal) + (property + (index 0) + ) + ) + (layer B.Cu + (type power) + (property + (index 1) + ) + ) + (boundary + (path pcb 0 0 0 200000 0 200000 -30000 0 -30000 0 0) + ) + (rule + (width 2000) + (clearance 2000) + ) + ) + (library + (padstack Rect_Pad + (shape (rect F.Cu -1000 -1000 1000 1000)) + (attach off) + ) + (image PAD + (pin Rect_Pad 1 0 0) + ) + ) + (placement + (component PAD + (place A1 3000 -15000 front 0) + (place A2 197000 -15000 front 0) + (place C1 100000 -6000 front 0) + (place C2 100000 -24000 front 0) + ) + ) + (network + (net NET_A + (pins A1-1 A2-1) + ) + (net NET_C + (pins C1-1 C2-1) + ) + (class default + (rule + (width 2000) + (clearance 2000) + ) + ) + ) + (wiring + ) +) diff --git a/tests/route/test_shove.py b/tests/route/test_shove.py new file mode 100644 index 0000000..54bbed6 --- /dev/null +++ b/tests/route/test_shove.py @@ -0,0 +1,117 @@ +"""Tests for shove-and-retry on the exact-geometry track. + +Shove moves an existing trace aside to make room for a net that would otherwise +be dropped, without ever producing a clearance violation. + +The dense fixture ``shove_needed.dsn`` has one signal layer (no vias): with +greedy net ordering NET_A routes as a straight wall that boxes NET_C out, so +without shove NET_C is dropped; with shove NET_A is moved aside and both +connect, DRC-clean. (Rip-up would also reroute NET_A here; the shove test uses +greedy ordering to isolate the shove mechanism.) +""" + +from __future__ import annotations + +from pathlib import Path +import sys + +import pytest + +from freeroute.dsn import parse_dsn +from freeroute.dsn.sexp import parse +from freeroute.route import route, route_dsn_board_exact +from freeroute.route.pipeline import _rule_clearance_dsn + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures" +SHOVE = FIXTURES / "shove_needed.dsn" +REGRESSION = ["simple_2net", "crossing_2net", "ripup_needed", "kicad_routable"] + + +def _exact(fixture, shove): + return route_dsn_board_exact(parse_dsn(fixture.read_text()), shove=shove) + + +# --- the headline shove contrast (greedy ordering isolates the mechanism) ---- + + +def test_without_shove_a_net_is_dropped(): + # greedy exact (no shove) cannot fit NET_C past NET_A's wall + off, _, _ = route_dsn_board_exact(parse_dsn(SHOVE.read_text()), rip_up=False, shove=False) + assert len(off.routed_net_numbers) < 2 + assert off.drc_clean # still clean, just incomplete + + +def test_shove_connects_all_and_stays_drc_clean(): + on, scale, _ = route_dsn_board_exact(parse_dsn(SHOVE.read_text()), rip_up=False, shove=True) + assert on.routed_net_numbers == {1, 2} # both nets connected + assert on.shoves >= 1 # at least one trace was moved aside + assert on.drc_clean + clearance = round(_rule_clearance_dsn(parse_dsn(SHOVE.read_text())) * scale) + assert on.tree.has_violation(clearance) is None # exact DRC check + + +def test_shove_result_is_deterministic(): + a, _, _ = route_dsn_board_exact(parse_dsn(SHOVE.read_text()), rip_up=False, shove=True) + b, _, _ = route_dsn_board_exact(parse_dsn(SHOVE.read_text()), rip_up=False, shove=True) + + def key(r): + return { + n: [(layer, [(p.x, p.y) for p in pts]) for layer, pts in segs] + for n, segs in r.result.wires.items() + } + + assert key(a) == key(b) + + +def test_shoved_trace_endpoints_still_on_pads(): + from freeroute.board import build_board + + on, _, _ = route_dsn_board_exact(parse_dsn(SHOVE.read_text()), rip_up=False, shove=True) + board = build_board(parse_dsn(SHOVE.read_text())) + pins_by_net: dict[int, set[tuple[int, int]]] = {} + for pin in board.get_pins(): + for net_no in pin.net_nos: + pins_by_net.setdefault(net_no, set()).add((pin.location.x, pin.location.y)) + for net_no, segments in on.result.wires.items(): + pts = [(p.x, p.y) for _, seg in segments for p in seg] + assert {pts[0], pts[-1]} == pins_by_net[net_no] + + +def test_shove_emits_valid_ses(): + ses = route(SHOVE.read_text(), engine="exact", shove=True) + top = parse(ses) + assert top.head == "session" + nets = top.child("routes").child("network_out").children("net") + assert {n.values()[0].text for n in nets} == {"NET_A", "NET_C"} + + +# --- regression: shove is a no-op where it isn't needed ---------------------- + + +@pytest.mark.parametrize("name", REGRESSION) +def test_shove_does_not_change_or_break_normal_boards(name): + fixture = FIXTURES / f"{name}.dsn" + off, _, _ = _exact(fixture, shove=False) + on, _, _ = _exact(fixture, shove=True) + # same connectivity, both DRC-clean, and no shove was needed + assert on.routed_net_numbers == off.routed_net_numbers + assert off.drc_clean and on.drc_clean + assert on.shoves == 0 + + +# --- oracle parity ---------------------------------------------------------- + + +@pytest.mark.oracle +def test_shove_connectivity_parity_with_oracle(): + from oracle import HAS_ORACLE, route_dsn, routed_net_set + + if not HAS_ORACLE: + pytest.skip("FreeRouting oracle unavailable") + ours = routed_net_set(route(SHOVE.read_text(), engine="exact", shove=True)) + theirs = routed_net_set(route_dsn(SHOVE, max_passes=3, timeout=300)) + if not theirs: + pytest.skip("reference JAR routed nothing on the synthetic single-layer board") + assert theirs <= ours