kicad-mcp/tests/test_freeroute_engines.py
Ryan Malloy 963206d548
Some checks failed
CI / Lint and Format (push) Has been cancelled
CI / Test Python 3.11 on macos-latest (push) Has been cancelled
CI / Test Python 3.12 on macos-latest (push) Has been cancelled
CI / Test Python 3.13 on macos-latest (push) Has been cancelled
CI / Test Python 3.10 on ubuntu-latest (push) Has been cancelled
CI / Test Python 3.11 on ubuntu-latest (push) Has been cancelled
CI / Test Python 3.12 on ubuntu-latest (push) Has been cancelled
CI / Test Python 3.13 on ubuntu-latest (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / Build Package (push) Has been cancelled
Route with freeroute's best available engine, not its grid default
freeroute's CLI defaults to the grid MVP, so mckicad was getting its weakest
router: the continuous room engine (routes channels the grid cannot, emits
exactly DRC-clean geometry) and channel packing (fits traces a greedy pass
drops) were unreachable.

Probe 'freeroute --help' once per CLI path and select the best engine the build
advertises, defaulting to --engine room --pack. freeroute exits 2 on an
unsupported (engine, flag) pair rather than ignoring it, so also gate each flag
on the engine's own option matrix (packing is room-only; 45-degree routing is
exact-track-only). An older freeroute that advertises no flags still gets the
bare -de/-do drop-in call. All of it is overridable via routing_config.
2026-07-13 18:16:32 -06:00

83 lines
2.6 KiB
Python

"""mckicad selects freeroute's best *available* engine, not its grid default.
freeroute's CLI exits 2 on an unsupported (engine, flag) combination rather than
ignoring it, and older freeroute builds don't advertise the flags at all — so the
command has to be built from both the CLI's probed capabilities and the engine's
own option matrix.
"""
from __future__ import annotations
from unittest.mock import MagicMock, patch
from mckicad.utils.freerouting import FreeRoutingEngine
ALL_FLAGS = {"--engine", "--pack", "--shove", "--diagonal"}
def _run_capturing(captured):
def _run(cmd, **_kwargs):
captured.append(cmd)
out = cmd[cmd.index("-do") + 1]
with open(out, "w") as f:
f.write("(session board)")
return MagicMock(returncode=0, stdout="", stderr="")
return _run
def _route(tmp_path, supported, config=None):
engine = FreeRoutingEngine()
dsn = tmp_path / "board.dsn"
dsn.write_text("(pcb)")
captured: list[list[str]] = []
with (
patch("mckicad.utils.freerouting.freeroute_supported_flags", return_value=supported),
patch("mckicad.utils.freerouting.subprocess.run", side_effect=_run_capturing(captured)),
):
ok, ses = engine._run_freeroute_cli(
"/usr/bin/freeroute", str(dsn), str(tmp_path), config
)
assert ok and ses
return captured[0]
def test_defaults_to_room_engine_with_packing(tmp_path):
cmd = _route(tmp_path, ALL_FLAGS)
assert "--engine" in cmd and cmd[cmd.index("--engine") + 1] == "room"
assert "--pack" in cmd
# 45-degree routing is an exact-track feature; sending it with room would
# make freeroute exit 2.
assert "--diagonal" not in cmd
def test_older_freeroute_gets_the_bare_invocation(tmp_path):
cmd = _route(tmp_path, set()) # a build that advertises no optional flags
assert "--engine" not in cmd
assert "--pack" not in cmd
assert cmd[1] == "-de" # still the plain JAR-compatible drop-in call
def test_incompatible_flag_is_skipped_not_sent(tmp_path):
cmd = _route(
tmp_path,
ALL_FLAGS,
{"freeroute_engine": "room", "freeroute_diagonal": True},
)
assert "--diagonal" not in cmd # room does not implement it
def test_exact_engine_can_use_diagonal(tmp_path):
cmd = _route(
tmp_path,
ALL_FLAGS,
{
"freeroute_engine": "exact",
"freeroute_pack": False,
"freeroute_diagonal": True,
},
)
assert cmd[cmd.index("--engine") + 1] == "exact"
assert "--diagonal" in cmd
assert "--pack" not in cmd # exact does not implement packing