Restore the 100% coverage gate by not measuring test modules
The lowered 98 gate existed only to absorb #[cfg(test)] failure
paths — panic! arms and ? branches that never run in a passing
suite. Excluding test modules from grcov measurement (they close
out every file, so an unterminated excl-start reaches EOF) leaves
the gate honest at 100. The two library lines that exclusion
exposed as genuinely unexercised — the missed-relabel span-slot
arms in the self-check conflict mapping — get direct tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01565B66CMh3HTjm3124SLpB
diff --git a/bin/coverage b/bin/coverage
index 0a67da7..fd5e027 100755
--- a/bin/coverage
+++ b/bin/coverage
@@ -5,6 +5,11 @@
# normal build artifacts never mix — mixing causes phantom uncovered
# lines. Override the gate with COVERAGE_THRESHOLD (defaults to 100).
#
+# `#[cfg(test)]` modules are excluded from measurement (they sit at
+# the tail of each file, so the unterminated excl-start runs to EOF):
+# their panic! arms and `?` branches never run in a passing suite and
+# would force a lowered gate for lines that are not library code.
+#
# Scaffolded into a project as bin/coverage; the `coverage` justfile
# recipe runs it. See the rust skill's coverage.md and scaffolding.md.
@@ -33,7 +38,7 @@ main() {
--keep-only 'src/**' \
--ignore 'src/bin/**' \
--excl-line 'cov-excl-line|unreachable!' \
- --excl-start 'cov-excl-start' \
+ --excl-start 'cov-excl-start|#\[cfg\(test\)\]' \
--excl-stop 'cov-excl-stop')
# Per-file breakdown: walk the covdir tree, printing leaf files only.
diff --git a/justfile b/justfile
index 06da833..5d2c7b2 100644
--- a/justfile
+++ b/justfile
@@ -9,12 +9,8 @@ check:
clippy:
cargo clippy --workspace -- -D warnings
-# The gate is 98 rather than 100 because grcov measures #[cfg(test)]
-# modules too, and their failure paths — panic! arms and `?` error
-# branches — never run in a passing suite. Library code is either
-# covered or carries an explicit cov-excl-line with reasoning.
coverage:
- COVERAGE_THRESHOLD=98 ./bin/coverage
+ ./bin/coverage
mutants:
#!/usr/bin/env bash
diff --git a/src/merge.rs b/src/merge.rs
index 2d63f0e..6e79cb4 100644
--- a/src/merge.rs
+++ b/src/merge.rs
@@ -1018,6 +1018,27 @@ mod tests {
};
assert!(conflicts.iter().any(|c| c.span_b.is_some()));
+ // Missed relabel from A: A renamed 1 to 2, the fake output
+ // kept 1.
+ let o = parse("[1]", "json")?;
+ let a = parse("[2]", "json")?;
+ let b = parse("[1]", "json")?;
+ let result = merge_to_text_with(&o, &a, &b, |_, _, _, _| "[1]".into())?;
+ let MergeResult::Conflicts(conflicts) = result else {
+ panic!("expected self-check conflicts");
+ };
+ assert!(conflicts.iter().any(|c| c.span_a.is_some()));
+
+ // Missed relabel from B, symmetrically.
+ let o = parse("[1]", "json")?;
+ let a = parse("[1]", "json")?;
+ let b = parse("[2]", "json")?;
+ let result = merge_to_text_with(&o, &a, &b, |_, _, _, _| "[1]".into())?;
+ let MergeResult::Conflicts(conflicts) = result else {
+ panic!("expected self-check conflicts");
+ };
+ assert!(conflicts.iter().any(|c| c.span_b.is_some()));
+
// Extra insertion: nobody wrote 9; the witness lives in M, so
// no span slot is filled.
let o = parse("[1]", "json")?;