Some checks are pending
CI / Build Package (push) Blocked by required conditions
CI / Lint and Format (push) Waiting to run
CI / Test Python 3.11 on macos-latest (push) Waiting to run
CI / Test Python 3.12 on macos-latest (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / Test Python 3.13 on macos-latest (push) Waiting to run
CI / Test Python 3.10 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.11 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.12 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.13 on ubuntu-latest (push) Waiting to run
Add find_freeroute_cli() + _run_freeroute_cli(): run_freerouting now tries the Java-free freeroute autorouter first (freeroute's CLI is -de/-do compatible) and falls back to the JAR for boards freeroute cannot yet route. freeroute is invoked as a subprocess, not imported, keeping its GPL license cleanly separated from mckicad (MIT). Raises a clear error only when neither backend is available. End-to-end verified: DSN -> freeroute -> SES -> native applier = 141 segments on the Arduino_Mega board, fully headless and Java-free.
70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
"""The router prefers the native freeroute CLI and falls back to the JAR."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from mckicad.utils.freerouting import FreeRoutingEngine, FreeRoutingError
|
|
|
|
|
|
def _jar_run_writes_ses(cmd, **_kwargs):
|
|
"""Simulate the FreeRouting JAR: write a .ses into the -do output directory."""
|
|
outdir = cmd[cmd.index("-do") + 1]
|
|
with open(os.path.join(outdir, "board.ses"), "w") as f:
|
|
f.write("(session board)")
|
|
return MagicMock(returncode=0, stdout="", stderr="")
|
|
|
|
|
|
def test_prefers_freeroute_when_available(tmp_path):
|
|
eng = FreeRoutingEngine(freerouting_jar_path="/fake/freerouting.jar")
|
|
with (
|
|
patch("mckicad.utils.freerouting.find_freeroute_cli", return_value="/usr/bin/freeroute"),
|
|
patch.object(eng, "_run_freeroute_cli", return_value=(True, "/out/board.ses")) as fr,
|
|
patch("mckicad.utils.freerouting.subprocess.run") as jar_run,
|
|
):
|
|
ok, ses = eng.run_freerouting("/tmp/board.dsn", "/out")
|
|
|
|
assert ok and ses == "/out/board.ses"
|
|
fr.assert_called_once()
|
|
jar_run.assert_not_called() # the JAR is never invoked when freeroute succeeds
|
|
|
|
|
|
def test_falls_back_to_jar_when_freeroute_fails(tmp_path):
|
|
jar = tmp_path / "freerouting.jar"
|
|
jar.write_text("x")
|
|
eng = FreeRoutingEngine(freerouting_jar_path=str(jar))
|
|
with (
|
|
patch("mckicad.utils.freerouting.find_freeroute_cli", return_value="/usr/bin/freeroute"),
|
|
patch.object(eng, "_run_freeroute_cli", return_value=(False, None)),
|
|
patch("mckicad.utils.freerouting.subprocess.run", side_effect=_jar_run_writes_ses),
|
|
):
|
|
ok, ses = eng.run_freerouting("/tmp/board.dsn", str(tmp_path))
|
|
|
|
assert ok and ses.endswith("board.ses")
|
|
|
|
|
|
def test_uses_jar_when_freeroute_absent(tmp_path):
|
|
jar = tmp_path / "freerouting.jar"
|
|
jar.write_text("x")
|
|
eng = FreeRoutingEngine(freerouting_jar_path=str(jar))
|
|
with (
|
|
patch("mckicad.utils.freerouting.find_freeroute_cli", return_value=None),
|
|
patch("mckicad.utils.freerouting.subprocess.run", side_effect=_jar_run_writes_ses),
|
|
):
|
|
ok, ses = eng.run_freerouting("/tmp/board.dsn", str(tmp_path))
|
|
|
|
assert ok
|
|
|
|
|
|
def test_raises_when_no_router_available(tmp_path):
|
|
eng = FreeRoutingEngine()
|
|
with (
|
|
patch("mckicad.utils.freerouting.find_freeroute_cli", return_value=None),
|
|
patch.object(eng, "find_freerouting_jar", return_value=None),
|
|
pytest.raises(FreeRoutingError),
|
|
):
|
|
eng.run_freerouting("/tmp/board.dsn", str(tmp_path))
|