@@ -75,15 +75,15 @@ exclude_re = [
7575 # column x-range rather than sitting side-by-side. Therefore both mutants
7676 # are byte-identical for every renderer-reachable layout while the other
7777 # pick_anchors arithmetic remains in scope.
78- " edges[.]rs:152( :[0-9]+)? : replace > with >= in pick_anchors" ,
78+ " edges[.]rs:[0-9]+ :[0-9]+: replace > with >= in pick_anchors" ,
7979 # E6: classify_strengthening's `||` guard
8080 # (`old==Unparseable || new==Unparseable -> return None`) is equivalent
8181 # under `&&`. When exactly one side is Unparseable the `&&` mutant skips
8282 # the early return, but `classify_pair(Unparseable, _)` / `(_, Unparseable)`
8383 # has no matching arm and falls to `_ => None` ? identical observable
8484 # result for every input. The two-unparseable case is also caught earlier
8585 # by `if old == new { return None }`. No test can distinguish the mutant.
86- " check_strengthening[.]rs:233( :[0-9]+)? : replace [|][|] with && in classify_strengthening" ,
86+ " check_strengthening[.]rs:[0-9]+ :[0-9]+: replace [|][|] with && in classify_strengthening" ,
8787 # E7: the Rayon `*_par_*_threshold()` functions in parallel_config.rs
8888 # (vespertide-planner + vespertide-query) are perf-only dispatch knobs.
8989 # Replacing the returned threshold with 0 or 1 only changes WHEN the
@@ -97,51 +97,51 @@ exclude_re = [
9797 # ModifyColumnDefault for the same column), so they can never be equal.
9898 # `< -> <=` therefore has no reachable input that distinguishes it ?
9999 # equivalent. The `&&` operator on the same line remains in the gate.
100- " ordering[.]rs:410( :[0-9]+)? : replace < with <= in sort_enum_default_dependencies" ,
100+ " ordering[.]rs:[0-9]+ :[0-9]+: replace < with <= in sort_enum_default_dependencies" ,
101101 # E9: the `len() < *_par_*_threshold()` seq-vs-parallel DISPATCH comparisons.
102102 # Mutating `<` to ==/>/<= only changes which execution path is taken; the
103103 # sequential and Rayon paths produce identical results (covered by the
104104 # validate/diff/build result tests). Perf-only ? same category as E1.
105- " validate/plan[.]rs:46( :[0-9]+)? : replace < with (==|>|<=) in find_plan_violations" ,
106- " validate/plan[.]rs:167( :[0-9]+)? : replace < with (==|>|<=) in find_missing_fill_with" ,
107- " validate/plan[.]rs:280( :[0-9]+)? : replace < with (==|>|<=) in find_missing_enum_fill_with" ,
108- " validate/schema[.]rs:77( :[0-9]+)? : replace < with (==|>|<=) in find_schema_violations" ,
109- " diff/mod[.]rs:49( :[0-9]+)? : replace < with (==|>|<=) in diff_schemas" ,
110- " builder/mod[.]rs:63( :[0-9]+)? : replace < with (==|>|<=) in build_plan_queries" ,
105+ " validate/plan[.]rs:[0-9]+ :[0-9]+: replace < with (==|>|<=) in find_plan_violations" ,
106+ " validate/plan[.]rs:[0-9]+ :[0-9]+: replace < with (==|>|<=) in find_missing_fill_with" ,
107+ " validate/plan[.]rs:[0-9]+ :[0-9]+: replace < with (==|>|<=) in find_missing_enum_fill_with" ,
108+ " validate/schema[.]rs:[0-9]+ :[0-9]+: replace < with (==|>|<=) in find_schema_violations" ,
109+ " diff/mod[.]rs:[0-9]+ :[0-9]+: replace < with (==|>|<=) in diff_schemas" ,
110+ " builder/mod[.]rs:[0-9]+ :[0-9]+: replace < with (==|>|<=) in build_plan_queries" ,
111111 # E10: the pairwise contradiction loop `for j in (i + 1)..preds.len()`.
112112 # The `+ -> *` mutant yields `i * 1 == i`, so the inner loop additionally
113113 # visits the (i, i) self-pair `check_pair(col, p, p)`. A predicate never
114114 # contradicts itself (two identical Compare/In/Between/IsNull predicates
115115 # are always mutually satisfiable), so every added self-pair returns None
116116 # and the observable result is unchanged ? equivalent.
117- " check_self_contradiction[.]rs:120( :[0-9]+)? : replace [+] with [*] in find_contradiction" ,
117+ " check_self_contradiction[.]rs:[0-9]+ :[0-9]+: replace [+] with [*] in find_contradiction" ,
118118 # E11: is_definitely_mismatch's `(Json, _) => false` and
119119 # `(Custom { .. }, _) => false` arms return exactly the same value as the
120120 # match's `_ => false` catch-all. Deleting either arm makes that input
121121 # fall straight through to `_ => false` ? identical observable result,
122122 # equivalent.
123- " check_type_mismatch[.]rs:366( :[0-9]+)? : delete match arm .* in is_definitely_mismatch" ,
124- " check_type_mismatch[.]rs:395( :[0-9]+)? : delete match arm .* in is_definitely_mismatch" ,
123+ " check_type_mismatch[.]rs:[0-9]+ :[0-9]+: delete match arm .* in is_definitely_mismatch" ,
124+ " check_type_mismatch[.]rs:[0-9]+ :[0-9]+: delete match arm .* in is_definitely_mismatch" ,
125125 # E12: in find_primary_key_additions, `n_existing == 0 => NewColumns`.
126126 # The function acts ONLY on `ExistingColumns`; both NewColumns and the
127127 # `_ => Mixed` fallback hit `if !matches!(kind, ExistingColumns) continue;`.
128128 # Deleting the `0` arm sends n_existing==0 to Mixed (PK columns always
129129 # number >=1, so `0 == len` is unreachable) ? same `continue`, equivalent.
130- " pk_additions[.]rs:115( :[0-9]+)? : delete match arm 0 in find_primary_key_additions" ,
130+ " pk_additions[.]rs:[0-9]+ :[0-9]+: delete match arm 0 in find_primary_key_additions" ,
131131 # E13: in clear_index_fields, `filtered.len() < names.len()` gates whether
132132 # the multi-index array is rewritten after removing a constraint name. When
133133 # the name is absent, `filtered` is a clone of `names` (same elements, same
134134 # order), so the `<= ` branch would set `col.index = Some(Array(filtered))`
135135 # to a value byte-identical to the untouched original ? no observable
136136 # difference, equivalent.
137- " constraint_ops[.]rs:148( :[0-9]+)? : replace < with <= in clear_index_fields" ,
137+ " constraint_ops[.]rs:[0-9]+ :[0-9]+: replace < with <= in clear_index_fields" ,
138138 # E14: the loader's `paths.len() < LOAD_FILES_PAR_THRESHOLD` seq-vs-parallel
139139 # dispatch. Mutating `<` only changes whether files are loaded sequentially
140140 # or via Rayon; the loaded result is identical. Perf-only, same as E1/E9.
141- " loader/src/migrations[.]rs:21( :[0-9]+)? : replace < with (==|>|<=) in load_migrations" ,
142- " loader/src/migrations[.]rs:65( :[0-9]+)? : replace < with (==|>|<=) in load_migrations_from_dir" ,
143- " loader/src/models[.]rs:43( :[0-9]+)? : replace < with (==|>|<=) in load_models_recursive" ,
144- " loader/src/models[.]rs:145( :[0-9]+)? : replace < with (==|>|<=) in load_models_recursive_internal" ,
141+ " loader/src/migrations[.]rs:[0-9]+ :[0-9]+: replace < with (==|>|<=) in load_migrations" ,
142+ " loader/src/migrations[.]rs:[0-9]+ :[0-9]+: replace < with (==|>|<=) in load_migrations_from_dir" ,
143+ " loader/src/models[.]rs:[0-9]+ :[0-9]+: replace < with (==|>|<=) in load_models_recursive" ,
144+ " loader/src/models[.]rs:[0-9]+ :[0-9]+: replace < with (==|>|<=) in load_models_recursive_internal" ,
145145 # E15: pure `println!` display helpers (revision fill_with header/footer and
146146 # narrowing strategy descriptions). No return value or logic; replacing the
147147 # body with () or deleting a match arm only changes stdout decoration. Same
@@ -175,9 +175,58 @@ exclude_re = [
175175 # scanner's exponent loop condition. (Caught/missed mutants in these
176176 # functions stay in the gate; only the non-terminating ones are excluded.)
177177 " check_expr_parser[.]rs:[0-9]+:[0-9]+: replace [+]= with (-=|[*]=|/=) in tokenize_spanned" ,
178- " check_expr_parser[.]rs:408(:[0-9]+)?: .* in tokenize_spanned" ,
178+ # The scanner's `while i < bytes.len() && ..` bounds guards. Relaxing the
179+ # bound is the same non-advancing class as the `+=` mutants above: the
180+ # cursor stops being the loop's decreasing measure and the scan never
181+ # terminates. Narrowed from a `.*`-in-tokenize_spanned wildcard so any
182+ # OTHER mutation in the scanner still has to be killed by a test.
183+ " check_expr_parser[.]rs:[0-9]+:[0-9]+: replace < with (<=|==|>) in tokenize_spanned" ,
179184 " check_expr_parser[.]rs:[0-9]+:[0-9]+: replace [+]= with (-=|[*]=|/=) in Parser::parse_predicate" ,
180185 " layout[.]rs:[0-9]+:[0-9]+: replace [+]= with (-=|[*]=|/=) in rebalance_groups" ,
186+ # Same non-termination class in the migration-filename byte scanner:
187+ # `render_migration_name` walks `i` (and the inner width cursor `j`) forward
188+ # by 1 or 2 per step. `*= 1` is the identity, and `-=` walks backwards into
189+ # an underflow, so either way the cursor stops being the loop's decreasing
190+ # measure and the scan never reaches `bytes.len()`. The scanner's comparison
191+ # and slice-bound mutants stay in the gate - `%012` and `%06v` cover those.
192+ " cli/src/utils[.]rs:[0-9]+:[0-9]+: replace [+]= with (-=|[*]=|/=) in render_migration_name" ,
193+ # E19: `String::with_capacity` / `Vec::reserve` sizing arithmetic. These
194+ # mutants change only how much the buffer pre-allocates; every one of these
195+ # functions then pushes the same bytes in the same order, so the returned
196+ # value is byte-identical and no assertion can distinguish them. (The
197+ # buffer-push helpers themselves stay in the gate - only the capacity
198+ # expression is excluded.)
199+ " naming/src/lib[.]rs:[0-9]+:[0-9]+: replace [+] with (-|[*]) in build_constraint_name" ,
200+ " schema/names[.]rs:[0-9]+:[0-9]+: replace ([+]|[*]) with ([*]|[+]) in join_column_names" ,
201+ " sql/helpers[.]rs:[0-9]+:[0-9]+: replace [+] with (-|[*]) in quote_ident_into" ,
202+ # E20: the loader's generic `paths.len() < LOAD_FILES_PAR_THRESHOLD`
203+ # seq-vs-parallel dispatch, extracted into `map_paths_with_threshold` by the
204+ # performance wave. Same perf-only category as E1/E7/E9/E14 - both arms map
205+ # the same closure over the same paths and collect the same result.
206+ " loader/src/parallel_config[.]rs:[0-9]+:[0-9]+: replace < with (==|>|<=) in map_paths_with_threshold" ,
207+ # E21: `sorted_column_refs` skips the sort for 0- or 1-element slices, which
208+ # are already sorted. Every mutant of that guard only changes WHETHER
209+ # `sort_unstable` runs on a slice too short to reorder, so the returned
210+ # order is identical either way.
211+ " diff/constraints[.]rs:[0-9]+:[0-9]+: replace > with (<|==|>=) in sorted_column_refs" ,
212+ # E22: `compute_integer_enum_remapping`'s empty-side fast path
213+ # (`from_items.is_empty() || to_items.is_empty()`). Under `&&` only the
214+ # both-empty case short-circuits, but the fall-through still yields an empty
215+ # map either way: an empty `from_items` iterates zero times, and an empty
216+ # `to_items` produces an empty `to_by_name` that matches no name. The guard
217+ # skips work, it does not change the result.
218+ " diff/columns[.]rs:[0-9]+:[0-9]+: replace [|][|] with && in compute_integer_enum_remapping" ,
219+ # E23: `build_create_table`'s `matches!(backend, MySql) || !has_unique_constraints`
220+ # picks between passing the constraint slice through by reference and
221+ # cloning a unique-free copy. `TableConstraint::Unique` dispatches to
222+ # `add_mysql_unique_constraint`, which no-ops on PG/SQLite, so both arms emit
223+ # the same CREATE TABLE - the guard only decides whether the clone is paid
224+ # for. (The `!` inside the partitioning filter on the next lines stays in the
225+ # gate: dropping it would keep only the uniques and lose the PK, which
226+ # `test_create_table_unique_alongside_primary_key` asserts against.) Anchored
227+ # on the COLUMN of the guard's `!` so the filter's `!` a few lines below is
228+ # not swept up - the two are indistinguishable by mutation text alone.
229+ " sql/create_table[.]rs:[0-9]+:77: delete ! in build_create_table" ,
181230]
182231
183232# Focus on logic-heavy crates. Exclude:
@@ -198,3 +247,4 @@ exclude_globs = [
198247
199248# 3× baseline test time per mutant (handles proptest variance)
200249timeout_multiplier = 3.0
250+
0 commit comments