"""End-to-end tests for parse_dsn against fixture files and inline snippets.""" from __future__ import annotations from pathlib import Path import pytest from freeroute.dsn import parse_dsn from freeroute.dsn.reader import DsnParseError from freeroute.dsn.shapes import Circle, Rectangle from freeroute.dsn.shapes import Path as DsnPath FIXTURES = Path(__file__).parent / "fixtures" def load(name: str) -> str: return (FIXTURES / name).read_text() # --- empty_board.dsn --------------------------------------------------------- def test_empty_board_header_and_resolution(): board = parse_dsn(load("empty_board.dsn")) assert board.name == "freeroute-empty.dsn" assert board.parser.string_quote == '"' assert board.resolution.unit == "um" assert board.resolution.value == 10 assert board.unit == "um" def test_empty_board_layers(): board = parse_dsn(load("empty_board.dsn")) assert board.layer_names() == ["F.Cu", "B.Cu"] assert all(layer.is_signal for layer in board.layers) assert [layer.index for layer in board.layers] == [0, 1] def test_empty_board_boundary_is_pcb_path(): board = parse_dsn(load("empty_board.dsn")) assert isinstance(board.boundary, DsnPath) assert board.boundary.layer == "pcb" # closed rectangle: 5 points (10 coords), width prefix stripped assert len(board.boundary.coords) == 10 assert board.outlines == [] # --- smd_demo.dsn ------------------------------------------------------------ @pytest.fixture def smd(): return parse_dsn(load("smd_demo.dsn")) def test_smd_parser_scope(smd): assert smd.name == "smd-demo.dsn" assert smd.parser.host_cad == "freeroute-test" assert smd.parser.host_version == "1.0" def test_smd_structure_via_and_rules(smd): assert smd.via_padstack_names == ["Via[0-1]_600:300_um"] widths = [r.value for r in smd.structure_rules.width_rules] assert widths == [200.0] clearances = smd.structure_rules.clearance_rules assert clearances[0].value == 200.0 assert clearances[0].class_pairs == [] assert clearances[1].value == 50.0 assert clearances[1].class_pairs == ["smd_smd"] def test_smd_keepout(smd): assert len(smd.keepouts) == 1 ko = smd.keepouts[0] assert ko.kind == "keepout" assert ko.area.name == "no_route_zone" assert isinstance(ko.area.border, Rectangle) def test_smd_padstacks(smd): assert [p.name for p in smd.padstacks] == [ "Rect[T]Pad_800x200_um", "Via[0-1]_600:300_um", ] rect_pad = smd.padstack("Rect[T]Pad_800x200_um") assert isinstance(rect_pad.shapes[0], Rectangle) assert rect_pad.attach_allowed is False via_pad = smd.padstack("Via[0-1]_600:300_um") assert len(via_pad.shapes) == 2 assert all(isinstance(s, Circle) for s in via_pad.shapes) assert [s.layer for s in via_pad.shapes] == ["Top", "Bottom"] def test_smd_images_and_pins(smd): assert [img.name for img in smd.images] == ["MiniQFN-6", "0603"] qfn = smd.images[0] assert qfn.is_front is True assert len(qfn.pins) == 3 pin1 = qfn.pins[0] assert pin1.name == "1" assert pin1.padstack_name == "Rect[T]Pad_800x200_um" assert (pin1.x, pin1.y) == (-3000.0, 400.0) def test_smd_placement(smd): comps = {p.lib_name: p for p in smd.placements} assert set(comps) == {"MiniQFN-6", "0603"} u1 = comps["MiniQFN-6"].places[0] assert u1.name == "U1" assert (u1.x, u1.y) == (75000.0, -45000.0) assert u1.is_front is True assert u1.rotation == 90.0 r2 = comps["0603"].places[1] assert r2.name == "R2" assert r2.is_front is False assert r2.rotation == 180.0 def test_smd_nets_and_pin_refs(smd): assert [n.name for n in smd.nets] == ["NET_A", "NET_B"] net_a = smd.net("NET_A") assert [(p.component, p.pin) for p in net_a.pins] == [("U1", "1"), ("R2", "2")] def test_smd_net_class(smd): assert len(smd.net_classes) == 1 cls = smd.net_classes[0] assert cls.name == "default" assert cls.use_via == ["Via[0-1]_600:300_um"] assert [r.value for r in cls.width_rules] == [200.0] # --- error handling ---------------------------------------------------------- def test_non_pcb_top_scope_raises(): with pytest.raises(DsnParseError): parse_dsn("(session foo)") def test_pin_ref_splits_on_first_hyphen(): dsn = "(pcb b (network (net N (pins A-B-C X-1))))" board = parse_dsn(dsn) pins = board.net("N").pins assert (pins[0].component, pins[0].pin) == ("A", "B-C") assert (pins[1].component, pins[1].pin) == ("X", "1") def test_unplaced_component_has_no_coords(): dsn = '(pcb b (placement (component "LIB" (place U9))))' board = parse_dsn(dsn) place = board.placements[0].places[0] assert place.name == "U9" assert place.x is None and place.y is None