fix(mutmut): dotted-load doc_claim_scan/sources/structural + repo_badge_catalog for real mutation coverage (#292) - #305
Merged
Conversation
cdeust
force-pushed
the
fix-mutmut-sibling-import-292
branch
from
July 30, 2026 18:01
b89ceb5 to
8883188
Compare
…ining functions + repo_badge_catalog for real mutation coverage (#292) check_doc_claims.py bare-imports its siblings doc_claim_scan and doc_claim_structural; generate_repo_badges.py bare-imports repo_badge_catalog. A function's __module__ is fixed at definition time to whatever name it was imported under, and mutmut's trampoline only activates a mutant when that matches the dotted, path-derived name it generated the mutant under -- so every mutant in these bare-imported siblings reported "no tests" under a scoped run, even though the functions are exercised by real passing tests through the bare-imported path (same defect class as badge_render.py/check_badge_floor, issues #262/#280/#293, and doc_claim_sources.py's own instance of it, already fixed on main by #304/issue #235). Fix: dotted-load each sibling under its own "scripts.<name>" spec (mirroring the existing check_badge_floor/doc_claim_sources precedent) and add direct test classes that call through those dotted references instead of through gate's/gen's bare-imported copies. Exact assertEqual on every message string and dict field (not assertIn) to kill the underlying string-literal mutants directly, plus targeted continue-vs- break and split/rsplit/maxsplit tests for the loop- and parsing-shaped mutants exact-match assertions alone don't reach. Verified with a real tally, not the absence of an error: a scoped mutmut run (scripts/mutation_check.sh) against doc_claim_structural.py, doc_claim_scan.py, doc_claim_sources.py, repo_badge_catalog.py and both test files reports "0 surviving mutants" (301 mutants, 301 killed, 0 "no tests", 0 survived). Full suite green (6919 passed, 5 skipped, 121 subtests), doc-claims and badge-floor gates green, ruff clean. Closes #292 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u
Every prior fix in this defect family (badge_render/check_badge_floor #262/#280, doc_claim_sources #235, and #293 itself) has a matching CHANGELOG entry under [Unreleased] ### Fixed; this fix's own commit lacked one. Boy-scout (coding-standards.md §14): the gap was visible the moment the touched diff was compared against the repo's own established convention for this exact class of change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u
cdeust
force-pushed
the
fix-mutmut-sibling-import-292
branch
from
July 30, 2026 18:31
8883188 to
4f12c05
Compare
11 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
scripts/check_doc_claims.pybare-imports its siblingsdoc_claim_scan,doc_claim_sources,doc_claim_structural;scripts/generate_repo_badges.pybare-imports
repo_badge_catalog. A function's__module__is fixed atdefinition time to whatever name it was imported under
(
mutmut/mutation/trampoline.py,module != decorated_func.__module__), soevery mutant in these bare-imported siblings reported "no tests" under a
scoped mutmut run, even though the functions ARE exercised by real passing
tests through the bare-imported path. Same defect class as
badge_render.py/doc_claim_structural.check_badge_floor(issues #262,#280, #293).
Note on scope (updated after a concurrent-main conflict):
doc_claim_sources.py'sinstance of this exact defect was independently fixed by PR #304 (issue
#235), which merged to
mainwhile this PR was in flight. This PR's scopeis now the two remaining siblings —
doc_claim_scan.pyanddoc_claim_structural.py's remaining functions — plusrepo_badge_catalog.py.Rebased onto
mainpast #304 and removed this PR's now-duplicatedoc_claim_sourcesdotted-load + direct-test class in favor of #304'sCanonicalSourceDirectTests(verified equivalent coverage before deletingthe duplicate — see "Real tally" below).
Fix
"scripts.<name>"importlib.util.spec_from_file_locationspec (mirroring the existingcheck_badge_floor/doc_claim_sourcesprecedent already intests_py/scripts/test_check_doc_claims.py).DocClaimScanDirectTests,StructuralDirectTestsin
test_check_doc_claims.py;RepoBadgeCatalogDirectTestsintest_generate_repo_badges.py) that call through these dotted referencesinstead of through
gate's/gen's bare-imported copies, so mutmut'strampoline activates and attributes the mutant to a real test.
assertEqualon the full message string or thefull dict), not
assertIn— a partial match leaves string-literal anddict-key mutants unpinned. Added targeted
continue-vs-breaktests(multi-file scans where an earlier missing/skipped file must not cut the
loop short) for the loop-shaped mutants exact string matching alone
doesn't reach.
mutation-attribution artifact):
check_scanned_json_parses'sFileNotFoundErrorbranch (a missing scanned.jsonfile) had no test atall before this change, direct or indirect.
prior fix in this exact defect family (Mutation testing cannot attribute mutants for any scripts/ module (synthetic module names in tests_py/scripts) #262/fix(mutmut): dot the last scripts/ test-loader gap, make pip_constraint_sets.py mutation-testable (#262) #280's
badge_render/check_badge_floor, check_doc_claims.py: 25 surviving mutants in the canonical-source helpers #235'sdoc_claim_sources, and Any two PRs that add tests conflict on six files by construction (test count hard-coded) #293 itself) carriesan
[Unreleased] ### Fixedentry; this fix's own commit had shippedwithout one. Added, matching the established convention and content.
No production code changed — this is test-only, restoring correct mutation
attribution for functions that were already correct and already tested
(save the one genuine gap above).
Real tally, not the absence of an error
Rebased onto current
main(1bef0f5, past #304'sdoc_claim_sourcesfix)and re-run from scratch rather than trusting stale numbers. A scoped
mutmutrun (scripts/mutation_check.sh) against all four productionfiles (including
doc_claim_sources.py, to confirm #304's fix and thisPR's fix compose cleanly with no regression) and both test files, on the
rebased tree:
301 mutants generated, 301 killed, 0 reported "no tests" (🫥) — the
literal symptom #292 reports — and 0 survived (🙁), across all four
production files together (this PR's two remaining siblings plus #304's
already-merged
doc_claim_sourcesfix). Before any dotted-load fix existed,the equivalent scoped run against this PR's scope reported genuine
survivors once the "no tests" misattribution was fixed alone — confirming
both halves of the issue: the attribution defect was real, AND the
underlying functions needed additional test assertions to reach 0
survivors.
Completion Ledger
doc_claim_scandotted-load +DocClaimScanDirectTests(14 tests)tests_py/scripts/test_check_doc_claims.py::DocClaimScanDirectTestsdoc_claim_structuralremaining functions +StructuralDirectTests(17 tests, incl. 3 continue-vs-break tests + the new missing-json-file test)tests_py/scripts/test_check_doc_claims.py::StructuralDirectTestsrepo_badge_catalogdotted-load +RepoBadgeCatalogDirectTests(5 tests, exact-dict per badge)tests_py/scripts/test_generate_repo_badges.py::RepoBadgeCatalogDirectTests[Unreleased] ### Fixedentry for this fixdoc_claim_sources.py's dotted-load +CanonicalSourceDirectTestsarenot in this PR's diff — they already merged via PR #304 (issue #235);
this PR's own duplicate of that class was removed during the rebase after
confirming #304's tests cover the same 4 functions with equivalent
(exact-message) assertions.
Rules compliance (coding-standards.md)
test_launcher_deps.py1500,benchmark_harness.py1641); no production file touchedimportlib.util.spec_from_file_locationdotted-load mirrors the existing, reviewed precedentcheck_scanned_json_parses's missing-file branch) AND the missing CHANGELOG entry found on re-verification; removed this PR's own now-duplicate test class after #304 landed rather than leaving redundant coverage; no bypass usedGates run (quoted, on the rebased head)
pytest -q(full suite):6938 passed, 5 skipped, 121 subtests passed in 184.25s (0:03:04)pytest --collect-only -q:6943 tests collectedpython scripts/check_doc_claims.py --test-count 6943:doc claims OK (1 declared not-a-claim exemption(s))python scripts/generate_repo_badges.py --check --test-count 6943:badges OK (5 checked)ruff check ./ruff format --check .:All checks passed!/ all files already formatted.bestpractices.jsonparses; no unresolved conflict markers anywhere (verified: only false-positive hits were RST table-divider===lines and a docstring====divider, neither a labelled<<<<<<</>>>>>>>marker)tests_py/scripts/test_check_doc_claims.py tests_py/scripts/test_generate_repo_badges.py -q:156 passed, 34 subtests passedCloses #292