Skip to content

Restore 100% coverage and fix cli.main's mutable-default Game() - #3

Merged
BrandonZacharie merged 2 commits into
mainfrom
claude/review-chess-py-wktEg
May 23, 2026
Merged

Restore 100% coverage and fix cli.main's mutable-default Game()#3
BrandonZacharie merged 2 commits into
mainfrom
claude/review-chess-py-wktEg

Conversation

@BrandonZacharie

Copy link
Copy Markdown
Owner

Summary

Two changes that sit on top of what PR #2 merged:

  1. Restore 100% test coverage — the previous PR added new UI tests but didn't backfill the parts of chess.py and cli.py they didn't reach, so coveralls dropped below 100%. This brings every file back to full coverage on both the fast suite (make test) and the full sweep (make test-all).
  2. Fix the mutable-default Game() argument in cli.maindef main(window, game: Game = Game(), ...) evaluated the Game() default at module-import time, so every caller that omitted game= shared a single mutable engine. Today every production call site passes game= explicitly, so the bug is dormant — but fixing it now means we don't have to think about it later.

Commits

  • 930ed8f — Restore 100% coverage across chess.py, cli.py, and game/game.py.
    • Source-side: # pragma: no cover on the if __name__ == "__main__": guards in chess.py and cli.py (untestable wrapper invocations), plus the unreachable KeyError arm in cli.main's SELECT_PROM block (the PROMOTION_PIECE_ORDS filter from PR Add cli.py UI tests, mark slow PGN tests, fix promotion filter #2 guarantees the lookup succeeds; the except is left in as defensive code with a one-line note).
    • Test-side: 30+ new tests covering Menu title rendering, the optional A_DIM/A_BOLD third-tuple attribute, every previously-uncovered navigation key (PPAGE / NPAGE / HOME / END), every Chess.draw_* sub-menu, the full save/load entrypoints (save_pgn stub, save_json happy + failure paths, load_json, load_pgn, _save_json matrix, _load_pgn empty-file and generic-error branches, _load_json round-trip via Game.save), and the missing _fileprompt edit keys (DOWN/UP, RIGHT, VT, HOME, END, DC). Plus a synthetic ilog/elog overflow test that hits the 4-column break path in cli.py, a run() entrypoint test, and three direct unit tests in test_game.py that pick up queen-side castling parse, the =Q promotion suffix parse, and a small synthetic PGN exercising PGNFile.game()'s auto-promote callback. Removed one unused _run_fileprompt helper.
  • 8f72cf3 — Fix mutable-default Game() argument in cli.main. Default flipped to None with game = Game() inside the body. Two regression tests:
    • Behavioral: drives main() twice without game= and asserts the second call's first move isn't rejected as illegal (which it would be if state had leaked from the first call).
    • Structural: inspect.signature(main).parameters["game"].default is None — a future refactor that re-introduces Game() as the default trips this immediately.

Test plan

  • make test — fast suite (191 passed, 2 deselected) in ~1.6 s.
  • make test-all — full sweep including Carlsen/Nakamura; matches CI behavior and reaches 100% coverage (2884 statements, 0 missing).
  • coverage report after make test shows TOTAL 100%.
  • Manual: open a game, play a move, exit, start a new game from the menu — second game starts from the fresh starting position (would catch a regression of the shared-state bug).

https://claude.ai/code/session_01RnQvxtXfn9JFTN8jpNyriL


Generated by Claude Code

claude added 2 commits May 22, 2026 21:14
Backfills the test surface the new UI suite left uncovered so the
fast pytest run (`pytest -m 'not slow'`) finishes with no missed
lines. Three small additions in tests/test_game.py also pick up the
engine paths previously only exercised by the long Carlsen / Nakamura
PGN parses, so coverage is now 100% with or without the slow tests.

Source-side trims:
- `if __name__ == "__main__":` blocks on chess.py and cli.py marked
  `# pragma: no cover` — the wrapper(...) call inside is never
  reachable from the test harness.
- cli.main's SELECT_PROM KeyError branch marked pragma — after the
  PROMOTION_PIECE_ORDS gate, every letter that reaches the lookup is
  guaranteed to be in PIECE_NAME_TYPE_MAP. The except is left in as
  defensive code with a one-line note.

Test-side additions:
- Menu rendering: title row, items with the optional A_DIM/A_BOLD
  third-tuple attribute, and the previously-uncovered PPAGE / NPAGE /
  HOME / END navigation keys.
- Chess sub-menu drivers: draw_save_menu / draw_load_menu /
  draw_cfg_menu / draw_cfg_log_style_menu (both the choose-a-style
  and ESC-out branches) / draw_about.
- Chess save/load entrypoints: save_pgn stub, save_json (both the
  failed-handler-then-ESC path and the successful-handler-then-
  KeyboardInterrupt path), load_json, load_pgn.
- Chess._save_json full matrix: no active game, success, FileNotFound
  / generic exception branches.
- Chess._load_json: missing file, generic error, and a real round-
  trip via Game.save → _load_json.
- Chess._load_pgn: empty PGN file branch and the generic-exception
  branch.
- Chess._fileprompt edit keys: DOWN/UP, RIGHT, VT (Ctrl+K),
  HOME, END, DC (Delete).
- Removed an unused `_run_fileprompt` helper that was the last
  uncovered region in tests/test_chess_ui.py.
- cli.py: synthetic ilog/elog tests that drive the 4-column overflow
  break path; `TestRun.test_run_delegates_to_curses_wrapper_with_main`
  covers the run() entrypoint.
- tests/test_game.py: direct parse_pgn_move tests for queen-side
  castling (O-O-O) and the `=Q` promotion suffix, plus a small
  synthetic PGN exercising PGNFile.game()'s auto-promote callback.
`def main(window, game: Game = Game(), ...)` evaluated the Game()
default at module-import time, so every caller that omitted the
argument shared a single mutable Game. Today every production call
site (Chess.play) passes `game=` explicitly, so the bug is dormant —
but the moment a second default-args invocation lands (or someone
calls run() in the same process twice), state from one game would
leak into the next.

Change the default to None and construct a fresh Game inside the
function body. Adds two tests:
- a behavioral regression test that drives main() twice without
  passing `game=` and asserts a move on the second call isn't
  rejected as illegal (which it would be if state had leaked).
- a structural guard that asserts inspect.signature(main).parameters
  ["game"].default is None, so a future refactor that re-introduces
  `Game()` as the default trips the test immediately.
@BrandonZacharie
BrandonZacharie merged commit 3c387ad into main May 23, 2026
3 checks passed
@BrandonZacharie
BrandonZacharie deleted the claude/review-chess-py-wktEg branch May 23, 2026 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants