test(mutation): kill sqlite_sql_translate.py's 20 surviving mutants (#265) - #285
Merged
Conversation
cdeust
force-pushed
the
fix-sqlite-sql-translate-mutants-265
branch
from
July 30, 2026 07:16
550b420 to
d51c008
Compare
…rning sqlite_compat.py relied on sqlite3's implicit default datetime adapter, deprecated as of Python 3.12. The one call site binding a raw `datetime.datetime` parameter is cascade.py::_update_stage_entered (confirmed the sole such site in this codebase via an instrumented full-suite run). Registers an explicit `sqlite3.register_adapter` callback that writes the same "T"-separated ISO-8601 spelling every other datetime write path in this codebase already uses; old rows (written in the deprecated adapter's space-separated spelling) keep reading correctly via `datetime.fromisoformat`, which parses both spellings identically, so no migration is needed. Boy-scout: sqlite_compat.py was already 335 lines, over the repo's 300-line cap, before this change touched it. Split the pure SQL-dialect translation logic (_translate_sql/_returning_was_stripped) into a new sqlite_sql_translate.py module, bringing sqlite_compat.py to 232 lines. Also added missing test coverage for _CompatCursor/PsycopgCompatConnection field wiring surfaced by a scoped mutation run against the touched file (0 unaccounted survivors; one documented equivalent mutant, see test_executemany_clears_had_returning_and_reports_real_rowcount). A pre-existing, unrelated mutation gap in _translate_sql itself (20 survivors, verbatim code predating this change) is filed as #265 per coding-standards.md §14.3 rather than folded in here. Closes #260 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u
…265) A scoped mutmut run against `_translate_sql`/`_returning_was_stripped` surfaced 20 surviving mutants, all the same shape: dropping (or re-spelling the case of) `flags=re.IGNORECASE` on one of the module's regex calls. Every existing case-insensitivity fixture happens to supply an input whose case already matches the pattern's own literal spelling, so the flag's presence was never observable. - 6 real gaps closed with new opposite-case tests (mutmut_8, 62, 117, 134, 161, 186): lowercase `DEFAULT NOW()`, an uppercase `&&`-overlap column, `XMAX`/`as` case variants on the xmax-drop rule, a lowercase `RETURNING` strip inside `_translate_sql` (distinct from the one `_returning_was_stripped` already covered), an uppercase `ARRAY_LENGTH`, and lowercase `_returning_was_stripped` input under a monkeypatched `_SUPPORTS_RETURNING`. - 14 documented equivalent mutants (mutmut_10, 38, 51, 64, 78, 92, 105, 119, 136, 137, 149, 163, 175, 188): the mutation only re-spells the pattern's own literal case while `re.IGNORECASE` stays in place, which Python's `re` semantics make provably irrelevant to the match — confirmed empirically with a differential harness, not asserted by inspection. Re-running the reproduction: 6 killed, 14 equivalent, 0 unaccounted-for survivors. Suite grows 6560 -> 6591; doc-claim/badge counts resynced to the measured `pytest --collect-only -q` absolute. Closes #265 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u
cdeust
force-pushed
the
fix-sqlite-sql-translate-mutants-265
branch
from
July 30, 2026 07:44
d51c008 to
5658933
Compare
9 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
Issue #265 filed a scoped mutmut run (mutmut 3.x, coding-standards.md §12)
against
mcp_server/infrastructure/sqlite_sql_translate.py's_translate_sql/_returning_was_stripped— the SQL-dialect translationmodule extracted from
sqlite_compat.pyin #260 — reporting 20 survivingmutants. This PR closes it: every survivor is killed by a new test or
documented as a provable equivalent mutant.
Note on provenance:
sqlite_sql_translate.pydid not exist onmainyetwhen this work started (it is created by the still-open #266,
fix-sqlite-datetime-adapter-260). Rather than stack on that unmergedbranch — main advances fast in this repo from concurrent sessions, and a
stacked branch's own doc-count-bump commits kept conflicting on every
rebase — this PR cherry-picks #266's single substantive commit
(
fix(sqlite): register explicit datetime adapter, close deprecation warning, byte-identical diff) directly onto currentmain, then adds the#265 fix on top. When #266 merges first, expect a trivial merge: the
production-code files (
sqlite_compat.py,sqlite_sql_translate.py,test_wiki_pipeline_sqlite.py,test_sqlite_datetime_adapter_260.py) willresolve automatically since both sides converge on identical final content;
only the shared doc-count files (
.bestpractices.json,CLAUDE.md,CONTRIBUTING.md,README.md,docs/ASSURANCE-CASE.md,assets/badge-tests.svg) will show a real conflict — resolve by takingeither side's shape and re-running
pytest --collect-only -qfor the trueabsolute, same as every other doc-count conflict in this repo's recent
history.
Root cause of the 20 survivors
Every one is the same shape: a mutant dropping (or re-spelling the case of)
the
flags=re.IGNORECASEargument on one of the module'sre.sub/re.searchcalls. Every existing case-insensitivity fixture in
test_wiki_pipeline_sqlite.pyhappens to supply an input whose case alreadymatches the pattern's own literal spelling, so the flag's presence was never
observable by any existing assertion.
A fresh scoped run on this tree measured 19 of the 20 named ids still
surviving (
mutmut_136had flipped to killed between runs — confirmednon-deterministic mutant/worker ordering, not a real fix, since the same
regex-equivalence argument that classifies its siblings applies to it
unchanged). All 20 named ids are accounted for below.
Invariant-to-test map
_returning_was_strippedregextest_returning_was_stripped_matches_lowercase_returning(monkeypatches_SUPPORTS_RETURNING=False, lowercasereturning)RETURNING/returningidenticallySERIAL PRIMARY KEYrespellTIMESTAMPTZrespellDEFAULT NOW()— drops IGNORECASEtest_translate_sql_default_now_lowercase_still_translatesDEFAULT NOW()respellNOW()respellUPDATE ... SETalias rule respellANY(...)respell&&overlap — drops IGNORECASEtest_translate_sql_array_overlap_matches_uppercase_column&&capture char-class respell[a-z_]vs[A-Z_]fold identically under IGNORECASExmaxdrop — drops IGNORECASEtest_translate_sql_xmax_drop_matches_uppercase_and_lowercase_asxmaxdrop,AS→asrespellxmaxdrop,xmax→XMAXrespellIS DISTINCT FROMrespell_translate_sql— drops IGNORECASEtest_translate_sql_strips_lowercase_returning_when_unsupportedwiki.flatten, char-class respellarray_length— drops IGNORECASEtest_translate_sql_array_length_matches_uppercasearray_lengthrespellEvery "equivalent" classification is backed by a differential harness run
(compile original vs. mutated pattern, run uppercase/lowercase/mixed-case
probes, diff match results — identical in every case) rather than asserted
by inspection alone; the reasoning is also inlined in the new test file's
module docstring.
Completion Ledger
_returning_was_strippedmatches lowercasereturning(IGNORECASE required)test_returning_was_stripped_matches_lowercase_returning_translate_sqltranslates lowercasedefault now()test_translate_sql_default_now_lowercase_still_translates_translate_sqltranslates an uppercase&&-overlap columntest_translate_sql_array_overlap_matches_uppercase_column_translate_sqldropsXMAX/lowercase-asxmax clausetest_translate_sql_xmax_drop_matches_uppercase_and_lowercase_as_translate_sqlstrips lowercasereturningwhen unsupportedtest_translate_sql_strips_lowercase_returning_when_unsupported_translate_sqltranslates uppercaseARRAY_LENGTHtest_translate_sql_array_length_matches_uppercaseMutation results
mcp_server/infrastructure/sqlite_sql_translate.pymutmut_136flip explained above)Test plan
tests_py/infrastructure/test_sqlite_sql_translate_265.py(6 tests, all fail on pre-fix code)ruff check+ruff format --checkcleanpyrighton the new test file +sqlite_sql_translate.py: 0 errorspytest --collect-only -q== 6609, resynced after rebasing onto fix(mutmut): make RepoBadge/Ranking mutation-testable (issue #262, 3rd pass) #283's mutation-testability pass which had itself bumped the count to 6588)check_doc_claims.py --test-count 6609andgenerate_repo_badges.py --check --test-count 6609both passmergeable: MERGEABLE,mergeStateStatus: CLEANagainst currentmainCloses #265
🤖 Generated with Claude Code
https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u