"""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_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")