Skip to content

Commit 909eea0

Browse files
shaheinmclaude
andauthored
Narrow mutants.toml exclusions to line-specific patterns (#116)
Replace function-wide exclude_re patterns with line-specific patterns targeting only the 19 truly equivalent mutations. The previous config excluded 43 mutations (24 of which were already caught by tests). Remove 8 stale "Note:" comments from metrics.rs for mutations no longer excluded. Addresses Codex review feedback on PR #115. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 967c504 commit 909eea0

2 files changed

Lines changed: 38 additions & 36 deletions

File tree

core/.cargo/mutants.toml

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,48 @@
11
# Equivalent mutants that cannot be killed by any test.
22
#
3-
# These are mutations where the original and mutated code produce identical
4-
# observable output for ALL valid inputs. Common patterns:
3+
# Each exclusion targets a SPECIFIC mutation (by file:line:col) that produces
4+
# identical observable output for ALL valid inputs. Only 19 mutations are
5+
# excluded — all others are caught by tests or unviable.
56
#
6-
# 1. Max/min tracking: `> → >=` or `< → <=` where the assignment is idempotent
7-
# (setting max_depth = sample.depth when they're equal doesn't change the value).
8-
#
9-
# 2. Guard conditions: `> → >=` on zero-checks where the boundary case (e.g.,
10-
# dt_min = 0.0) produces the same result through a different code path
11-
# (e.g., dividing by 0 is guarded, and the else branch also returns 0).
12-
#
13-
# 3. Threshold guards: `> → >=` on epsilon comparisons (like `> 1e-10`) where
14-
# real-world data never produces a value exactly at the threshold.
15-
#
16-
# 4. uniffi-bindgen.rs is generated code outside our control.
7+
# If production code changes shift line numbers, stale patterns will stop
8+
# matching and the mutation will surface as MISSED, prompting an update.
179

1810
exclude_re = [
19-
# Generated UniFFI build script
11+
# ── Generated code ───────────────────────────────────────────────────
2012
"uniffi-bindgen\\.rs",
2113

22-
# metrics.rs: max/min tracking (idempotent assignment)
23-
"replace [<>] with [<>]= in DiveStats::compute$",
24-
"replace [<>] with [<>]= in SegmentStats::compute$",
14+
# ── metrics.rs: DiveStats::compute — idempotent max/min tracking ─────
15+
# Setting max/min = value when they're already equal is a no-op.
16+
"src/metrics\\.rs:141:.*replace > with >= in DiveStats::compute", # max_depth_m
17+
"src/metrics\\.rs:159:.*replace < with <= in DiveStats::compute", # min_temp_c
18+
"src/metrics\\.rs:162:.*replace > with >= in DiveStats::compute", # max_temp_c
19+
"src/metrics\\.rs:179:.*replace > with >= in DiveStats::compute", # max_ceiling_m
20+
"src/metrics\\.rs:186:.*replace > with >= in DiveStats::compute", # max_gf99
21+
# weight_sum > 0.0: weight_sum is always strictly positive when samples exist
22+
"src/metrics\\.rs:220:.*replace > with >= in DiveStats::compute",
23+
24+
# ── metrics.rs: DiveStats::compute_rates — boundary guards ───────────
25+
# dt_min > 0.0 guards: dt_min=0 only when timestamps are duplicated,
26+
# which never occurs in real dive profiles. Either way, rate=0.
27+
"src/metrics\\.rs:311:.*replace > with >= in DiveStats::compute_rates", # descent dt_min
28+
"src/metrics\\.rs:313:.*replace > with >= in DiveStats::compute_rates", # descent dt_min inner
29+
"src/metrics\\.rs:326:.*replace > with >= in DiveStats::compute_rates", # ascent dt_min
30+
# last_max_idx < samples.len()-1: boundary and len-1 arithmetic
31+
"src/metrics\\.rs:323:43:.*replace < with <= in DiveStats::compute_rates",
32+
"src/metrics\\.rs:323:59:.*replace - with \\+ in DiveStats::compute_rates",
33+
"src/metrics\\.rs:323:59:.*replace - with / in DiveStats::compute_rates",
2534

26-
# metrics.rs: compute_rates boundary guards (dt_min=0 → rate=0 either way)
27-
"replace [<>] with [<>]= in DiveStats::compute_rates$",
28-
# metrics.rs: samples.len() - 1 in ascent guard (always true or dt_min=0)
29-
"replace - with [+/] in DiveStats::compute_rates$",
35+
# ── metrics.rs: SegmentStats::compute — idempotent max/min tracking ──
36+
"src/metrics\\.rs:385:.*replace > with >= in SegmentStats::compute", # max_depth_m
37+
"src/metrics\\.rs:390:.*replace < with <= in SegmentStats::compute", # min_temp_c
38+
"src/metrics\\.rs:393:.*replace > with >= in SegmentStats::compute", # max_temp_c
3039

31-
# buhlmann.rs: leading compartment tie (exact FP equality never occurs)
32-
"replace > with >= in TissueState::surface_gf_and_leading$",
33-
# buhlmann.rs: threshold guards (real values never exactly at 1e-10)
34-
"replace > with >= in TissueState::compartment_gf$",
35-
"replace > with >= in compute_surface_gf$",
40+
# ── buhlmann.rs — FP threshold guards ────────────────────────────────
41+
# Leading compartment tie-break: exact FP equality across 16 compartments'
42+
# weighted N2+He GF values never occurs in practice
43+
"src/buhlmann\\.rs:153:.*replace > with >= in TissueState::surface_gf_and_leading",
44+
# denom > 1e-10: Bühlmann constants always give denom >> 1e-10
45+
"src/buhlmann\\.rs:180:.*replace > with >= in TissueState::compartment_gf",
46+
# dil_inert > 1e-10: gas fractions from real mixes never produce exactly 1e-10
47+
"src/buhlmann\\.rs:254:.*replace > with >= in compute_surface_gf",
3648
]

core/src/metrics.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,9 @@ impl DiveStats {
157157

158158
// Temperature stats
159159
if sample.temp_c < min_temp_c {
160-
// Note: <= is equivalent (idempotent assignment, excluded in mutants.toml)
161160
min_temp_c = sample.temp_c;
162161
}
163162
if sample.temp_c > max_temp_c {
164-
// Note: >= is equivalent (idempotent assignment, excluded in mutants.toml)
165163
max_temp_c = sample.temp_c;
166164
}
167165
temp_sum += sample.temp_c as f64;
@@ -179,15 +177,13 @@ impl DiveStats {
179177
deco_time_sec += deco_dt;
180178
}
181179
if ceiling > max_ceiling_m {
182-
// Note: >= is equivalent (idempotent assignment, excluded in mutants.toml)
183180
max_ceiling_m = ceiling;
184181
}
185182
}
186183

187184
// Max GF99
188185
if let Some(gf99) = sample.gf99 {
189186
if gf99 > max_gf99 {
190-
// Note: >= is equivalent (idempotent assignment, excluded in mutants.toml)
191187
max_gf99 = gf99;
192188
}
193189
}
@@ -222,7 +218,6 @@ impl DiveStats {
222218
};
223219

224220
let weighted_avg_depth_m = if weight_sum > 0.0 {
225-
// Note: >= is equivalent (weight_sum always positive, excluded in mutants.toml)
226221
(weighted_depth_sum / weight_sum) as f32
227222
} else {
228223
avg_depth_m
@@ -313,7 +308,6 @@ impl DiveStats {
313308
.unwrap_or(0);
314309

315310
// Descent: surface → first arrival at max depth
316-
// Note: boundary guards produce identical results (excluded in mutants.toml)
317311
let descent_rate = if first_max_idx > 0 {
318312
let dt_min = (samples[first_max_idx].t_sec - samples[0].t_sec) as f32 / 60.0;
319313
if dt_min > 0.0 {
@@ -326,7 +320,6 @@ impl DiveStats {
326320
};
327321

328322
// Ascent: last departure from max depth → surface
329-
// Note: boundary guards and len-1 arithmetic are equivalent (excluded in mutants.toml)
330323
let ascent_rate = if last_max_idx < samples.len() - 1 {
331324
let last = samples.last().unwrap();
332325
let dt_min = (last.t_sec - samples[last_max_idx].t_sec) as f32 / 60.0;
@@ -390,17 +383,14 @@ impl SegmentStats {
390383

391384
for (i, sample) in samples.iter().enumerate() {
392385
if sample.depth_m > max_depth_m {
393-
// Note: >= is equivalent (idempotent assignment, excluded in mutants.toml)
394386
max_depth_m = sample.depth_m;
395387
}
396388
depth_sum += sample.depth_m as f64;
397389

398390
if sample.temp_c < min_temp_c {
399-
// Note: <= is equivalent (idempotent assignment, excluded in mutants.toml)
400391
min_temp_c = sample.temp_c;
401392
}
402393
if sample.temp_c > max_temp_c {
403-
// Note: >= is equivalent (idempotent assignment, excluded in mutants.toml)
404394
max_temp_c = sample.temp_c;
405395
}
406396

0 commit comments

Comments
 (0)