Add the GPLv3 text (the package declared the licence but never shipped it), a py.typed marker, and an sdist include/exclude allowlist so the distribution carries only src/, docs/, README, LICENSE and pyproject. reference/ is a clone of the GPL FreeRouting Java tree kept purely as a porting reference and must never be redistributed inside this package; tests, caches and build output are excluded too. Switch to the PEP 639 SPDX licence expression with license-files, widen the classifiers, and add an -o alias for -do. Rewrite the README around the engine matrix: what each track and pass actually does, and what this is not (no FreeRouting density parity on dense boards, and --diagonal is a recovery and shortening pass, not a diagonal-native search).
164 lines
5.2 KiB
Python
164 lines
5.2 KiB
Python
"""Tests for the freeroute CLI (Specctra DSN in, SES out).
|
|
|
|
Covers the engine selection contract: every engine must route a fixture from the
|
|
command line, and every option the selected engine does not implement must fail
|
|
loudly rather than being silently dropped.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from freeroute.cli import ENGINE_OPTIONS, main
|
|
|
|
FIXTURES = Path(__file__).parent / "dsn" / "fixtures"
|
|
FIXTURE = FIXTURES / "simple_2net.dsn"
|
|
CHANNEL_PACK = FIXTURES / "channel_pack.dsn"
|
|
SHOVE_NEEDED = FIXTURES / "shove_needed.dsn"
|
|
DIAGONAL_WIN = FIXTURES / "diagonal_win.dsn"
|
|
|
|
|
|
def test_cli_jar_compatible_flags_write_ses(tmp_path):
|
|
out = tmp_path / "out.ses"
|
|
rc = main(["-de", str(FIXTURE), "-do", str(out)])
|
|
assert rc == 0
|
|
text = out.read_text()
|
|
assert text.startswith("(session")
|
|
assert "(net" in text and "(wire" in text
|
|
|
|
|
|
def test_cli_short_output_alias(tmp_path):
|
|
out = tmp_path / "out.ses"
|
|
assert main([str(FIXTURE), "-o", str(out)]) == 0
|
|
assert out.read_text().startswith("(session")
|
|
|
|
|
|
def test_cli_positional_input_to_stdout(capsys):
|
|
rc = main([str(FIXTURE)])
|
|
assert rc == 0
|
|
assert "(session" in capsys.readouterr().out
|
|
|
|
|
|
def test_cli_missing_input_errors():
|
|
with pytest.raises(SystemExit):
|
|
main([])
|
|
|
|
|
|
def test_cli_unreadable_input_returns_2(capsys):
|
|
rc = main(["/nonexistent/board.dsn"])
|
|
assert rc == 2
|
|
assert "cannot read" in capsys.readouterr().err
|
|
|
|
|
|
def test_cli_bad_layers_errors(capsys):
|
|
with pytest.raises(SystemExit):
|
|
main([str(FIXTURE), "--layers", "top,bottom"])
|
|
assert "--layers must be comma-separated integers" in capsys.readouterr().err
|
|
|
|
|
|
# --- engine selection -------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize("engine", sorted(ENGINE_OPTIONS))
|
|
def test_cli_every_engine_routes_a_fixture(engine, tmp_path):
|
|
out = tmp_path / f"{engine}.ses"
|
|
rc = main([str(FIXTURE), "--engine", engine, "-do", str(out)])
|
|
assert rc == 0
|
|
text = out.read_text()
|
|
assert text.startswith("(session")
|
|
assert "(wire" in text
|
|
|
|
|
|
def test_cli_default_engine_is_grid(tmp_path):
|
|
"""The default must stay grid — the drop-in JAR replacement behaviour."""
|
|
default = tmp_path / "default.ses"
|
|
grid = tmp_path / "grid.ses"
|
|
assert main([str(FIXTURE), "-do", str(default)]) == 0
|
|
assert main([str(FIXTURE), "--engine", "grid", "-do", str(grid)]) == 0
|
|
assert default.read_text() == grid.read_text()
|
|
|
|
|
|
def test_cli_room_pack_routes(tmp_path):
|
|
out = tmp_path / "pack.ses"
|
|
rc = main([str(CHANNEL_PACK), "--engine", "room", "--pack", "-do", str(out)])
|
|
assert rc == 0
|
|
assert "(wire" in out.read_text()
|
|
|
|
|
|
def test_cli_room_shove_routes(tmp_path):
|
|
out = tmp_path / "shove.ses"
|
|
rc = main([str(SHOVE_NEEDED), "--engine", "room", "--shove", "-do", str(out)])
|
|
assert rc == 0
|
|
assert "(wire" in out.read_text()
|
|
|
|
|
|
def test_cli_exact_shove_routes(tmp_path):
|
|
out = tmp_path / "shove.ses"
|
|
rc = main([str(SHOVE_NEEDED), "--engine", "exact", "--shove", "-do", str(out)])
|
|
assert rc == 0
|
|
assert "(wire" in out.read_text()
|
|
|
|
|
|
def test_cli_exact_diagonal_routes(tmp_path):
|
|
out = tmp_path / "diag.ses"
|
|
rc = main([str(DIAGONAL_WIN), "--engine", "exact", "--diagonal", "-do", str(out)])
|
|
assert rc == 0
|
|
assert "(wire" in out.read_text()
|
|
|
|
|
|
def test_cli_max_passes_is_honoured_not_advisory(tmp_path):
|
|
out = tmp_path / "mp.ses"
|
|
rc = main([str(FIXTURE), "--engine", "exact", "-mp", "3", "-do", str(out)])
|
|
assert rc == 0
|
|
assert "(wire" in out.read_text()
|
|
|
|
|
|
# --- unsupported combinations must error, never be silently ignored ---------
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("argv", "flag"),
|
|
[
|
|
(["--engine", "grid", "--pack"], "--pack"),
|
|
(["--engine", "grid", "--shove"], "--shove"),
|
|
(["--engine", "grid", "--diagonal"], "--diagonal"),
|
|
(["--engine", "exact", "--pack"], "--pack"),
|
|
(["--engine", "room", "--diagonal"], "--diagonal"),
|
|
(["--engine", "room", "--no-rip-up"], "--no-rip-up"),
|
|
(["--engine", "room", "-mp", "5"], "-mp/--max-passes"),
|
|
],
|
|
)
|
|
def test_cli_unsupported_option_errors(argv, flag, capsys):
|
|
with pytest.raises(SystemExit) as excinfo:
|
|
main([str(FIXTURE), *argv])
|
|
assert excinfo.value.code == 2
|
|
err = capsys.readouterr().err
|
|
assert flag in err
|
|
assert "not supported by --engine" in err
|
|
assert "supported by:" in err
|
|
|
|
|
|
def test_cli_unknown_engine_errors(capsys):
|
|
with pytest.raises(SystemExit):
|
|
main([str(FIXTURE), "--engine", "quantum"])
|
|
assert "invalid choice" in capsys.readouterr().err
|
|
|
|
|
|
def test_cli_supported_combinations_are_accepted(tmp_path):
|
|
"""Every (engine, flag) pair the matrix claims to support must actually run."""
|
|
argv_for = {
|
|
"pack": ["--pack"],
|
|
"shove": ["--shove"],
|
|
"diagonal": ["--diagonal"],
|
|
"rip_up": ["--no-rip-up"],
|
|
"max_passes": ["-mp", "4"],
|
|
}
|
|
for engine, options in ENGINE_OPTIONS.items():
|
|
for option in options:
|
|
out = tmp_path / f"{engine}-{option}.ses"
|
|
rc = main([str(FIXTURE), "--engine", engine, *argv_for[option], "-do", str(out)])
|
|
assert rc == 0, f"{engine} + {option} failed"
|
|
assert out.read_text().startswith("(session")
|