Skip to content

test: pin behavior that mutation testing found unguarded - #63

Merged
dmccoystephenson merged 1 commit into
mainfrom
test/pin-mutation-survivors
Nov 4, 2026
Merged

dmccoystephenson merged 1 commit into
mainfrom
test/pin-mutation-survivors

Conversation

@dmccoystephenson

@dmccoystephenson dmccoystephenson commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Summary

Stage B (unit-test expansion). The suite already reports 100% line and branch coverage, so coverage could no longer identify gaps. A throwaway survey of 30 hand-written mutants of graphik.py was run instead, and 6 survived. Two of those are equivalent mutants. The other four map to three real gaps, each pinned here:

  • getGameDisplay() returns the bound surface. Every existing test bound the display itself, so returning pygame.display.get_surface() went undetected. It would change the result for any instance bound to an off-screen surface.
  • drawText antialiases. Passing False to render left every test green, although antialiasing was assumed only in test comments. The behavior is now also documented in the drawText docstring and the README.
  • drawButton centers its label with floor division, on both axes. README.md gives the center as xpos + width // 2, but / passed the suite. The two differ by a pixel only with a fractional position and an odd size, and the new test uses that case. It compares against the documented drawText call pixel for pixel rather than hard-coding bounds, so font-rendering differences between pygame versions do not affect it.

No production behavior is changed. The change to graphik.py is a docstring note only.

Surviving mutants, judged equivalent: is not to != on the font session's display check (pygame.Surface compares by identity), and int(xpos) + (int(width)//2) for the label center (the same truncation is applied when Rect.center is assigned).

Test plan

  • python3 -m py_compile src/main/python/preponderous/graphik/graphik.py
  • Import smoke test: from preponderous.graphik import Graphik
  • SDL_VIDEODRIVER=dummy python3 -m pytest: 133 passed (130 before)
  • Mutation survey re-run: each of the four mutants named above is killed by the new tests
  • CI matrix (3.9 to 3.13)

Local validation used pygame 2.1.2 on Python 3.8, which is below the supported floor. CI is the check against current pygame versions.

Backlog

No tracking issue: the gaps were found during triage. Each open issue was skipped for a stated reason:

Not verified

The README's claim that graphik is unpublished on PyPI, and its wiki link, could not be checked because curl is not permitted in this dispatch.

This PR description was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).

🤖 Generated with Claude Code


drafted by Claude on behalf of Daniel Stephenson

The suite reports 100% line and branch coverage, yet a mutation survey
left four mutants alive. Pin the behavior each one could break:

- getGameDisplay() returns the bound surface, not the display
- drawText antialiases glyph edges
- drawButton centers its label with floor division

Note the antialiasing in the drawText docstring and the README, which
only a test comment mentioned before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dmccoystephenson

Copy link
Copy Markdown
Member Author

Self-review rubric, scored against the diff at 38e84e7 and CI run 41792331352. This is an inline review by the authoring session, not an independent one.

  • Scope: PASS. Three files changed: three tests, and one antialiasing note mirrored in the drawText docstring and the README. No unrelated churn.
  • Tests-new: PASS (not applicable). No public method was added.
  • Tests-fix: not applicable. No bug was fixed. The equivalent check for characterization tests was run instead: each of the four targeted mutants survived before this change and was killed after it, while python3 -m pytest passed 133 tests on the unmutated source.
  • Sibling structure: PASS. Each test sits beside the tests of the same method and uses _make_graphik and _rgb.
  • Sibling renames: PASS (not applicable). Nothing was renamed.
  • Docs: PASS. The README and docstring state the antialiasing that test_draw_text_antialiases_the_glyph_edges pins, and the label-center formula was already in the README.
  • Issue resolution: PASS (not applicable). No issue is referenced as resolved.
  • Manual validation: PASS. py_compile, the import smoke test and pytest (133 passed) are green locally, and all five CI jobs are green.
  • camelCase: PASS. No public method was added or renamed.
  • Backward-compat: PASS. No signature or behavior changed, so the consumer-impact gate does not apply.
  • Headless: PASS. All three tests ran under the dummy driver locally and in CI.
  • Version sync: PASS. No version string was touched.
  • No new deps: PASS. No import was added.

Do-not-auto-merge paths: none matched. .github/workflows/, LICENSE and the version source are untouched, and no file deletes more than 50 lines.

Finding, accepted without change:

  • src/test/python/preponderous/graphik/test_graphik.py:1237 — test_draw_button_centers_the_label_using_floor_division was shown to discriminate on pygame 2.1.2 only. The test passes on CI's newer pygame, but mutants were not run there. If a later pygame rounds Rect.center differently, the two centers could land on the same pixel and the test would go quiet for this mutant. It would not go falsely red, because it compares against the documented drawText call. This is judged acceptable, since a hard-coded pixel bound would be the more fragile alternative.

Verdict: ready to merge.

This review was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).


drafted by Claude on behalf of Daniel Stephenson

@dmccoystephenson
dmccoystephenson merged commit ed2a8d6 into main Nov 4, 2026
5 checks passed
@dmccoystephenson
dmccoystephenson deleted the test/pin-mutation-survivors branch November 4, 2026 07:28
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.

1 participant