Files
dojo_cate/tests/test_cli.py
T
eisterman 52d8af620b Add failed-Exam loss switch; keep -3 and accept the length (6.11, D50)
RulesConfig.exam_failure_loss (bots aware) with the sim axis --exam-loss.
Losing 1 or 0 stripes saves 3-5 minutes but weakens comebacks, so the
rules keep -3. Default games are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 17:51:13 +02:00

107 lines
4.1 KiB
Python

import csv
from pathlib import Path
import pytest
from dojo.cli import EXIT_DESIGN_ERRORS, EXIT_FORMAT_ERRORS, EXIT_OK, main
from dojo.grid import parse_grid
VALID = Path(__file__).parent / "fixtures" / "quadrant_valid.txt"
def test_validate_valid_board(capsys: pytest.CaptureFixture[str]) -> None:
assert main(["board", "validate", "--quadrant", str(VALID)]) == EXIT_OK
output = capsys.readouterr().out
assert "Shortest walk Safe Room -> Dojo: Q0 10, Q1 10, Q2 10, Q3 10" in output
assert output.rstrip().endswith("OK")
def test_validate_reports_design_errors(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
no_entrance = VALID.read_text(encoding="utf-8").replace(
"+ + + + + + +---+ +", "+ + + + + + +---+---+"
)
quadrant = tmp_path / "quadrant.txt"
quadrant.write_text(no_entrance, encoding="utf-8")
assert main(["board", "validate", "--quadrant", str(quadrant)]) == EXIT_DESIGN_ERRORS
assert "quadrant 0 has no Dojo entrance" in capsys.readouterr().out
def test_format_errors_and_missing_files_exit_with_format_code(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
malformed = tmp_path / "bad.txt"
malformed.write_text("+---+\n| . |\n", encoding="utf-8")
assert main(["board", "validate", "--quadrant", str(malformed)]) == EXIT_FORMAT_ERRORS
assert main(["board", "validate", "--quadrant", str(tmp_path / "none")]) == EXIT_FORMAT_ERRORS
assert "odd number of lines" in capsys.readouterr().err
def test_render_writes_a_parseable_full_board(tmp_path: Path) -> None:
output = tmp_path / "board.txt"
assert main(["board", "render", "--quadrant", str(VALID), "-o", str(output)]) == EXIT_OK
grid = parse_grid(output.read_text(encoding="utf-8"))
assert (grid.height, grid.width) == (16, 16)
def test_legend_next_to_the_quadrant_is_used_by_default(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
quadrant = tmp_path / "quadrant.txt"
quadrant.write_text(VALID.read_text(encoding="utf-8"), encoding="utf-8")
(tmp_path / "legend.toml").write_text("[tunnels.9]\nquadrant_offset = 1\n", encoding="utf-8")
assert main(["board", "validate", "--quadrant", str(quadrant)]) == EXIT_FORMAT_ERRORS
assert "legend links tunnel '9'" in capsys.readouterr().err
def test_sim_prints_a_summary_per_scenario_and_writes_games(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
games = tmp_path / "games.csv"
argv = ["sim", "--games", "2", "--exam", "c", "none", "--players", "3", "--jobs", "1"]
assert main([*argv, "--csv", str(games)]) == EXIT_OK
output = capsys.readouterr().out
assert "== exam:none p3 kiai+2 start0 fail-3 balanced,farmer,aggressive (2 games" in output
assert "== exam:c p3 kiai+2 start0" in output
rows = games.read_text(encoding="utf-8").splitlines()
assert rows[0].startswith("scenario,seed,lineup,winner_seat")
assert len(rows) == 1 + 4
def test_sim_writes_one_summary_row_per_scenario(tmp_path: Path) -> None:
path = tmp_path / "summary.csv"
argv = ["sim", "--games", "2", "--players", "3", "4", "--lineup", "casual", "--jobs", "1"]
argv += ["--exam-loss", "3", "0"]
assert main([*argv, "--summary-csv", str(path)]) == EXIT_OK
with path.open(encoding="utf-8", newline="") as file:
rows = list(csv.DictReader(file))
assert [row["scenario"] for row in rows] == [
"exam:c p3 kiai+2 start0 fail-3 casual",
"exam:c p3 kiai+2 start0 fail-0 casual",
"exam:c p4 kiai+2 start0 fail-3 casual",
"exam:c p4 kiai+2 start0 fail-0 casual",
]
assert {"rounds_mean", "square_gain_per_turn", "win_rate_casual"} <= rows[0].keys()
assert (rows[0]["seat3_win_rate"], rows[2]["seat3_win_rate"] != "") == ("", True)
@pytest.mark.parametrize(
"bad",
[["--lineup", "balanced,cheater"], ["--games", "0"], ["--exam", "b"], ["--exam-loss", "-1"]],
)
def test_sim_rejects_bad_arguments(bad: list[str], capsys: pytest.CaptureFixture[str]) -> None:
with pytest.raises(SystemExit) as exit_info:
main(["sim", *bad])
assert exit_info.value.code == 2
assert "error:" in capsys.readouterr().err