"""DSN -> board construction invariants, validated on a real KiCad board. There is no unit-test oracle for the board model, so these assert structural invariants against the parsed DSN (the source of truth) and — when a JVM + JAR are available — cross-check the board's net set against the reference router's routed net set. Fixture: ``tests/dsn/fixtures/kicad_routable.dsn`` (a KiCad pcbnew export). """ from __future__ import annotations from pathlib import Path import sys import pytest from freeroute.board import BasicBoard, ObstacleArea, Pin, build_board from freeroute.dsn import parse_dsn sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures" ROUTABLE = FIXTURES / "kicad_routable.dsn" @pytest.fixture(scope="module") def dsn(): return parse_dsn(ROUTABLE.read_text()) @pytest.fixture(scope="module") def board(dsn) -> BasicBoard: return build_board(dsn) # --- structural counts match the DSN ---------------------------------------- def test_layer_count_matches_dsn(dsn, board): assert board.get_layer_count() == len(dsn.layers) assert [layer.name for layer in board.layer_structure.arr] == [ layer.name for layer in dsn.layers ] def test_net_count_matches_dsn(dsn, board): assert len(board.nets) == len(dsn.nets) assert board.nets.names() == {n.name for n in dsn.nets} def test_pin_count_matches_placement(dsn, board): images = {img.name: img for img in dsn.images} expected = sum( len(images[pl.lib_name].pins) for pl in dsn.placements for place in pl.places if place.x is not None and pl.lib_name in images ) assert len(board.get_pins()) == expected assert expected > 0 def test_obstacle_count_matches_keepouts(dsn, board): # each keepout expands to one obstacle per applicable layer; with no keepouts # in this fixture the count is zero assert len(board.get_obstacle_areas()) >= len(dsn.keepouts) assert len(dsn.keepouts) == 0 assert board.get_obstacle_areas() == [] # --- per-item invariants ---------------------------------------------------- def test_every_pin_shape_contains_its_origin(board): pins = board.get_pins() assert pins for pin in pins: assert pin.shape_contains_origin(), f"pad shape missed origin for pin {pin.name}" def test_every_pin_reports_a_net(dsn, board): # this board's netlist connects every pad, so every pin has a net number for pin in board.get_pins(): assert pin.net_nos, f"pin {pin.name} has no net" for net_no in pin.net_nos: assert board.nets.get_by_number(net_no) is not None def test_items_report_correct_net_membership(board): for pin in board.get_pins(): for net_no in pin.net_nos: assert pin in board.get_connectable_items(net_no) def test_pin_layers_are_valid(board): n_layers = board.get_layer_count() for pin in board.get_pins(): assert pin.layers assert all(0 <= layer < n_layers for layer in pin.layers) def test_bounding_box_is_non_degenerate(board): assert not board.bounding_box.is_empty() assert board.bounding_box.dimension() == 2 # every pin location lies within the board bounding box for pin in board.get_pins(): assert board.bounding_box.contains(pin.location) def test_item_types(board): for item in board.get_items(): assert isinstance(item, (Pin, ObstacleArea)) # --- oracle cross-check ------------------------------------------------------ @pytest.mark.oracle def test_board_net_set_covers_oracle_routed_nets(board): from oracle import HAS_ORACLE, route_dsn, routed_net_set if not HAS_ORACLE: pytest.skip("FreeRouting oracle unavailable") # Routing the full board is slow; a couple of passes is enough to route most # nets, and a generous timeout absorbs JVM start-up variance. ses = route_dsn(ROUTABLE, max_passes=2, timeout=420) routed = routed_net_set(ses) assert routed, "oracle produced no routed nets" # every net the reference router routed exists on our constructed board assert routed <= board.nets.names()