Skip to content

fix(mutmut): dotted-load doc_claim_scan/sources/structural + repo_badge_catalog for real mutation coverage (#292) - #305

Merged
cdeust merged 2 commits into
mainfrom
fix-mutmut-sibling-import-292
Jul 30, 2026
Merged

fix(mutmut): dotted-load doc_claim_scan/sources/structural + repo_badge_catalog for real mutation coverage (#292)#305
cdeust merged 2 commits into
mainfrom
fix-mutmut-sibling-import-292

Conversation

@cdeust

@cdeust cdeust commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Summary

scripts/check_doc_claims.py bare-imports its siblings doc_claim_scan,
doc_claim_sources, doc_claim_structural; scripts/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
(mutmut/mutation/trampoline.py, module != decorated_func.__module__), so
every 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's
instance of this exact defect was independently fixed by PR #304 (issue
#235), which merged to main while this PR was in flight. This PR's scope
is now the two remaining siblings — doc_claim_scan.py and
doc_claim_structural.py's remaining functions — plus repo_badge_catalog.py.
Rebased onto main past #304 and removed this PR's now-duplicate
doc_claim_sources dotted-load + direct-test class in favor of #304's
CanonicalSourceDirectTests (verified equivalent coverage before deleting
the duplicate — see "Real tally" below).

Fix

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's doc_claim_sources fix)
and re-run from scratch rather than trusting stale numbers. A scoped
mutmut run (scripts/mutation_check.sh) against all four production
files (including doc_claim_sources.py, to confirm #304's fix and this
PR's fix compose cleanly with no regression) and both test files, on the
rebased tree:

>>> mutating: scripts/doc_claim_structural.py scripts/doc_claim_scan.py scripts/doc_claim_sources.py scripts/repo_badge_catalog.py | tests: tests_py/scripts/test_check_doc_claims.py tests_py/scripts/test_generate_repo_badges.py
    done (4 files mutated, 572 ignored, 0 unmodified)
97.21 mutations/second
🎉 301  🫥 0  ⏰ 0  🤔 0  🙁 0  🔇 0  🧙 0
>>> mutmut-reported survivors (must be empty, or documented equivalents):
  none — 0 surviving mutants 🎉

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_sources fix). 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

Path introduced Asserting test
doc_claim_scan dotted-load + DocClaimScanDirectTests (14 tests) tests_py/scripts/test_check_doc_claims.py::DocClaimScanDirectTests
doc_claim_structural remaining functions + StructuralDirectTests (17 tests, incl. 3 continue-vs-break tests + the new missing-json-file test) tests_py/scripts/test_check_doc_claims.py::StructuralDirectTests
repo_badge_catalog dotted-load + RepoBadgeCatalogDirectTests (5 tests, exact-dict per badge) tests_py/scripts/test_generate_repo_badges.py::RepoBadgeCatalogDirectTests
CHANGELOG [Unreleased] ### Fixed entry for this fix reviewable directly in the diff; matches the established per-issue convention (#262/#280/#235/#293)

doc_claim_sources.py's dotted-load + CanonicalSourceDirectTests are
not 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)

Rule Status
§4.1 file size (300/500) N/A — test files; repo convention already has test files well over 300/500 lines (test_launcher_deps.py 1500, benchmark_harness.py 1641); no production file touched
§4.2 method size (40 lines) pass — measured by AST on the rebased tree; no function over 40 lines
§7 local reasoning pass — no refused constructs; importlib.util.spec_from_file_location dotted-load mirrors the existing, reviewed precedent
§8 sourced constants N/A — no new constants introduced
§12 mutation testing pass — 0 surviving mutants, 0 "no tests" on the scoped 4-file run above, re-measured on the rebased tree (quoted, not asserted)
§14 boy-scout pass — fixed the one genuine pre-existing test gap found en route (check_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 used

Gates 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 collected
  • python 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.json parses; no unresolved conflict markers anywhere (verified: only false-positive hits were RST table-divider === lines and a docstring ==== divider, neither a labelled <<<<<<</>>>>>>> marker)
  • Targeted tests: tests_py/scripts/test_check_doc_claims.py tests_py/scripts/test_generate_repo_badges.py -q: 156 passed, 34 subtests passed

Closes #292

@cdeust
cdeust force-pushed the fix-mutmut-sibling-import-292 branch from b89ceb5 to 8883188 Compare July 30, 2026 18:01
cdeust and others added 2 commits July 30, 2026 20:24
…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
cdeust force-pushed the fix-mutmut-sibling-import-292 branch from 8883188 to 4f12c05 Compare July 30, 2026 18:31
@cdeust
cdeust merged commit 3d056be into main Jul 30, 2026
19 checks passed
@cdeust
cdeust deleted the fix-mutmut-sibling-import-292 branch July 30, 2026 19:03
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.

mutmut: sibling-imported scripts/ modules show every mutant as 'no tests' (bare-import __module__ mismatch)

1 participant