Context
Issue #262 fixed mutation testing's inability to attribute mutants for any
scripts/ module — every tests_py/scripts/*.py test loaded its subject
under a synthetic/bare module name (_cortex_launcher_deps,
generate_pip_constraints, etc.), while mutmut keys mutant trampolines on
the path-derived dotted name (scripts.launcher_deps,
scripts.generate_pip_constraints). The fix renamed every affected test's
module handle to the dotted path mutmut expects, and widened
[tool.mutmut] also_copy to include uv.lock, requirements/, and
pyproject.toml (files generate_pip_constraints.py's test reads).
That PR did not change either module's own source — only how tests load
it — so it could not itself fix any of the survivors mutation testing now
reveals. Precedent for exactly this split: PR #236 ("make scripts/
mutation-reachable") filed its own discovered survivors as #235 rather than
folding an unrelated, much larger test-writing effort into the attribution
fix.
What mutation testing now shows (real tallies, not an early abort)
scripts/launcher_deps.py against tests_py/scripts/test_launcher_deps.py:
213 mutants, 128 killed, 85 survived, 0 no-tests, 0 timeout.
Concentrated in the orchestration/I-O functions the existing suite does not
exercise at the mutant-detection level: _importable (husk-detection
branches), _pins_satisfied (stamp/pin-set comparison edges),
_write_stamp, ensure_deps, and ensure_all_deps (lock/double-check/
constraint-passing branches).
scripts/generate_pip_constraints.py against
tests_py/scripts/test_generate_pip_constraints.py: 243 mutants, 95 killed,
65 survived, 83 no-tests (code paths, largely inside export()'s
uv subprocess plumbing and main()'s non---check write path, that the
current suite never calls at all — a coverage gap, not just a detection
gap).
Concentrated in declared_index_urls, lock_registries, serving_registries,
compose, _reject_unusable, stale, and main (mostly its non---check
branches and message-formatting edges).
Acceptance criteria
- Re-run
scripts/mutation_check.sh tests_py/scripts/test_launcher_deps.py scripts/launcher_deps.py
and scripts/mutation_check.sh tests_py/scripts/test_generate_pip_constraints.py scripts/generate_pip_constraints.py.
- Every surviving mutant in both files is triaged per coding-standards.md
§12.4: killed by a new/strengthened test, or documented as a provable
equivalent (comment at the mutant's line — same observable behavior,
e.g. a comparison operator flip on an unreachable branch).
- The 83 no-tests mutants in
generate_pip_constraints.py get real
coverage first (a mutant nothing executes cannot be triaged as
killed/equivalent) — likely via a stubbed/faked uv subprocess for
export()'s currently-untested lines, and a main() non---check
(write) path test.
- Final state: 0 survivors in both files (per §12.4), tracked via
scripts/mutation_check.sh.
References
Context
Issue #262 fixed mutation testing's inability to attribute mutants for any
scripts/module — everytests_py/scripts/*.pytest loaded its subjectunder a synthetic/bare module name (
_cortex_launcher_deps,generate_pip_constraints, etc.), while mutmut keys mutant trampolines onthe path-derived dotted name (
scripts.launcher_deps,scripts.generate_pip_constraints). The fix renamed every affected test'smodule handle to the dotted path mutmut expects, and widened
[tool.mutmut] also_copyto includeuv.lock,requirements/, andpyproject.toml(filesgenerate_pip_constraints.py's test reads).That PR did not change either module's own source — only how tests load
it — so it could not itself fix any of the survivors mutation testing now
reveals. Precedent for exactly this split: PR #236 ("make scripts/
mutation-reachable") filed its own discovered survivors as #235 rather than
folding an unrelated, much larger test-writing effort into the attribution
fix.
What mutation testing now shows (real tallies, not an early abort)
scripts/launcher_deps.pyagainsttests_py/scripts/test_launcher_deps.py:213 mutants, 128 killed, 85 survived, 0 no-tests, 0 timeout.
Concentrated in the orchestration/I-O functions the existing suite does not
exercise at the mutant-detection level:
_importable(husk-detectionbranches),
_pins_satisfied(stamp/pin-set comparison edges),_write_stamp,ensure_deps, andensure_all_deps(lock/double-check/constraint-passing branches).
scripts/generate_pip_constraints.pyagainsttests_py/scripts/test_generate_pip_constraints.py: 243 mutants, 95 killed,65 survived, 83 no-tests (code paths, largely inside
export()'suvsubprocess plumbing andmain()'s non---checkwrite path, that thecurrent suite never calls at all — a coverage gap, not just a detection
gap).
Concentrated in
declared_index_urls,lock_registries,serving_registries,compose,_reject_unusable,stale, andmain(mostly its non---checkbranches and message-formatting edges).
Acceptance criteria
scripts/mutation_check.sh tests_py/scripts/test_launcher_deps.py scripts/launcher_deps.pyand
scripts/mutation_check.sh tests_py/scripts/test_generate_pip_constraints.py scripts/generate_pip_constraints.py.§12.4: killed by a new/strengthened test, or documented as a provable
equivalent (comment at the mutant's line — same observable behavior,
e.g. a comparison operator flip on an unreachable branch).
generate_pip_constraints.pyget realcoverage first (a mutant nothing executes cannot be triaged as
killed/equivalent) — likely via a stubbed/faked
uvsubprocess forexport()'s currently-untested lines, and amain()non---check(write) path test.
scripts/mutation_check.sh.References
check_doc_claims.py)