test(semantic): raise scoring.py mutation kill rate 53.4% -> 98.3% - #491
Merged
Merged
Conversation
Contributor
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Mutation testing of `semantic/scoring.py` with the mutation classes the brief names (`>=` -> `>`, `and` -> `or`, boolean-constant flips, threshold shifts) killed only 31 of 58 mutants. This adds 45 tests that kill 26 of the 27 survivors; the last one is proven equivalent, not left unexplained. The serious group was the risk-tier boundaries: `confidence < 0.70` could become `<=`, `>= 0.85` and `>= 0.90` could become `>`, and the literals 0.70 and 0.85 could be moved outright, with no test noticing. `risk_for` decides what the policy gate applies -- under `semantic_mode="auto"` a proposal runs without review only when `risk != "high"` -- so each of those mutants silently changes which repairs auto-apply. Pinned at, just below and just above every boundary. The second group was `calibration_features`: the evidence-string split indices, the evidence-kind guards, the 1.0/0.0 role-confidence pair, the coverage rounding place and the memory-support increment. These change no cleaning decision, but the record is hashed into `ActionConfidence.features_hash` and its docstring promises a report consumer can verify two actions were scored from identical evidence. A wrong feature breaks exactly that. Pinned field by field, including that two different evidence sets do not collide. The third group was the isotonic calibration table -- both end clamps, the interpolation, the [0.0, 1.0] output clamp, the 4-dp rounding, the table-present-but-curve-missing fallback and the once-only missing-table warning. All of it was uncovered because calibration ships as identity, so none of it runs on a default install. It is still the code that sets the confidence the gate reads once a table is installed, and `raw >= xs[-1]` -> `>` does not return a wrong number, it raises IndexError. Also records a fragility rather than hiding it: two features are recovered by string-scraping human-readable evidence prose, wrapped in an `except` that degrades to None, so rewording a sentence in experts.py would silently change the feature record. The parse, the wrong-kind rejection and the None degradation are now all asserted. Tests only; no src change.
kevincostner17
force-pushed
the
test/mutation-core
branch
from
September 20, 2026 15:04
8142515 to
a7aea7a
Compare
kevincostner17
added a commit
that referenced
this pull request
Sep 20, 2026
) `semantic/experts.py` is where semantic repairs are proposed, and it killed only 111 of 250 mutants. This adds 60 tests across four themes, killing 32 of 37 targeted mutants; the other 5 are proven equivalent with executable proofs, not assertions. Expert applicability guards. `info.free_text or info.identifier_like or info.boolean_like` could become `and`, and `numeric_like and not free_text` could become `or`, with nothing failing. These are the guards that keep an expert off identifier and free-text columns, so flipping them is the "ID-protection removed" class: an expert that runs on an identifier column rewrites "007" to 7, and the damage is unrecoverable from the output alone. Date day/month disambiguation. The `a > 12 and b <= 12` family decides whether 05/12 is May 12th or 5th December. Every boundary at 12 was movable. Three of the branch-3 mutants cannot be killed: they differ only in states already claimed by the earlier branches, proven by differential over the complete input domain (180,000 inputs each, zero differences). Currency and number parsing, including the ambiguity flag for "1,000" with no currency code -- the corpus trap this parser exists to handle. Two mutants here are equivalent: one comparison is unreachable outside a branch that guarantees both operands differ, and one `return True` is dead code, confirmed by line-level trace over 610,436 calls recording zero executions. Allowed values and the category tie-break, plus the frozen-ness of _DateResolution and dropna in the value counter. Also corrects PR #491. That PR reported 98.3% for scoring.py, but its verdicts came from a harness run with a stale-bytecode defect; the true figure at that commit was 96.6%. The masked survivor was `sort_keys=True` in features_hash -- the property that makes the digest a function of feature content rather than of dict literal order. A test for it is included here, and scoring.py now genuinely measures 98.3%. Tests only; no src change.
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.
What
Mutation testing of
semantic/scoring.pykilled only 31 of 58 mutants. This adds 45 tests that kill 26 of the 27 survivors; the last is proven equivalent, not left unexplained.All 58 mutation sites in the module,
not applied: 0. Mutation classes are the ones the brief names:>=→>,<→<=,and→or, boolean-constant flips, numeric-threshold shifts.Why the survivors mattered
Group 1 — risk-tier boundaries.
confidence < 0.70could become<=;>= 0.85and>= 0.90could become>; the literals0.70and0.85could be moved outright. Not one test noticed.risk_fordecides what the policy gate applies — undersemantic_mode="auto"a proposal is applied without review only whenrisk != "high". So every one of these mutants silently changes which repairs auto-apply. Each boundary is now pinned at the value, just below it and just above it.Group 2 — the audit feature record (
calibration_features). Theconfidence=/margin=evidence-string split indices, the evidence-kind guards, the1.0/0.0role-confidence pair,round(coverage, 6), and thesum(1 for …)memory-support increment.These change no cleaning decision — the record is hashed into
ActionConfidence.features_hashand never reaches the gate. They are still real: the docstring promises that "a report consumer can verify two actions were scored from identical evidence", and a silently wrong feature breaks precisely that, letting two actions scored from different evidence hash identically. Pinned field by field, including an explicit non-collision test.Group 3 — the isotonic calibration table. Both end clamps (
raw <= xs[0],raw >= xs[-1]), the interpolation, the[0.0, 1.0]output clamp, the 4-dp rounding, thetable is None or curve is Nonefallback, and the once-only "calibration table missing" warning.All of it was uncovered, because calibration ships as identity — without an installed table the version string is literally
"uncalibrated". It is nonetheless the code that sets the confidence the gate reads for anyone who installs a table. Worth notingraw >= xs[-1]→>does not merely return a wrong number: it raisesIndexError.The one surviving mutant is equivalent
frac = (raw - xs[lo]) / span if span else 0.0→0.05survives, and no test can kill it. The line is reached only whenxs[0] < raw < xs[-1];hi = bisect_right(xs, raw)is the first index withxs[hi] > rawstrictly, andxs[lo] = xs[hi-1] <= raw. Sospan > 0for every curve, including the repeated-x curvesfrom_jsonaccepts — the guard is dead code.Asserted over five awkward curves in
test_the_zero_span_guard_in_the_interpolator_is_unreachablerather than argued only in prose.A fragility recorded rather than hidden
calibration_featuresrecovers two numeric features by string-scraping the human-readable evidence prose:Both parses are wrapped in
except (IndexError, ValueError)that degrades toNone, so rewording an evidence sentence inexperts.pywould silently change the feature record — and thereforefeatures_hash— with nothing failing and nothing raised. The tests now pin the current parse (first marker, next token), the wrong-evidence-kind rejection, and the malformed-inputNonedegradation. The evidence-detail format is a load-bearing interface with no declared schema.Scope
Tests only — no
src/change. No behaviour changes, so no changelog entry and no compatibility impact.Verification
tests/test_scoring_boundaries.py— 45 passedruff check+ruff format --checkcleanTwo harness defects found and fixed during this work
Reported in the interest of not overstating the evidence — earlier figures from this harness are withdrawn.
The mutated file was not the imported file. The venv had
freshdatainstalled editable against a different worktree, and with asrc/layout pytest'spythonpath = ["."]does not shadow it — so the harness mutated a file nothing imported and every mutant trivially "survived". Fixed by forcingPYTHONPATH=<root>/srcinto the pytest subprocess.Mutate._hitshared one counter across all four mutation kinds while the driver indexed per kind. Most(kind, index)pairs therefore matched no node and were silently skipped: only 20 of this module's 58 sites ever ran. Fixed with per-kind counters and pre-order traversal matching the collector; the driver now printsNOT APPLIEDand the summary reports anot applied:count, so the failure cannot be silent again.Both baselines above were re-measured with the corrected harness over all 58 sites.