Skip to content

test(rivet-core): kill 35+ surviving mutants from sharded mutation testing#218

Merged
avrabe merged 1 commit intomainfrom
fix/mutants-rivet-core-survivors
Apr 26, 2026
Merged

test(rivet-core): kill 35+ surviving mutants from sharded mutation testing#218
avrabe merged 1 commit intomainfrom
fix/mutants-rivet-core-survivors

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented Apr 26, 2026

Summary

Surviving mutants from the latest sharded cargo mutants -p rivet-core run on main (commit 8a5a4a7, run 24941881359, shards 1 + 2 of 4) clustered in two areas:

  1. Howard-Hinnant civil_from_days arithmetic in embed.rs::epoch_to_ymd_hm and reqif.rs::reqif_creation_timestamp — every replace //%/+/-/* survived because the wrappers consumed SystemTime::now() directly with no deterministic input path. ~60 mutants between the two.
  2. HTML renderers in embed.rs (render_coverage, render_diagnostics, render_matrix_table, find_rule_for_types, render_query, render_group) — boundary thresholds, equality predicates, match guards.

This PR:

  • Refactors reqif_creation_timestamp to call a deterministic helper epoch_secs_to_iso8601(secs: u64) -> String (mirroring the epoch_days_to_ymd extraction already in export.rs).
  • Adds 8 known-input/known-output tests for epoch_to_ymd_hm and 7 for epoch_secs_to_iso8601, covering Unix epoch, 2024-01-01 / 2024-04-25 / 2024-12-31, 2024-02-15 (mp = 11 branch), 2004-02-29 (doe/1460=1, doe/36524=0), 2200-03-01 (doe/36524=2), 2400-02-29 (the only era-boundary date that exercises - doe/146096). Together these pin every arithmetic position in the algorithm.
  • Adds renderer tests for the 25 / 55 / 85 / 100% bar-class ladders in render_coverage and render_matrix_table, plus targeted tests for find_rule_for_types (both legs of &&, both ==), render_diagnostics severity filter + summary counts, render_query truncation-note > boundary and id-column wrapping, render_group empty-second-arg guard, and the <details> block emission in render_coverage when uncovered_ids is empty.
  • Adds reqif.rs tests: Adapter::id() / name() identity; parse_reqif recognising ReqIF.Name and ReqIF.ChapterName as titles (not as fields); empty-value guards (must NOT overwrite defaults); enum-valued status and tags arms; REL-N counter incrementing per link; mixed-extension directory walk picking up .reqif AND .xml files in import_reqif_directory.

Local cargo-mutants verification (against this branch)

Region Before After
embed.rs::epoch_to_ymd_hm 5 missed (out of ~33 mutants) 0 missed (106 / 106 caught for the broader regex match)
reqif.rs::reqif_creation_timestamp + epoch_secs_to_iso8601 ~27 missed 0 missed (80 / 80 caught)
embed.rs HTML renderers targeted via shard-1 missed.txt covered (full-file mutation rerun in progress)

cargo test -p rivet-core --lib goes from 780 to 816 (+36 deterministic tests; Miri-tagged where they touch quick-xml).

Coverage notes for follow-up

Shards 0 / 3 of the CI mutation run timed out at 45 min and never uploaded artifacts. Their files (commits.rs, coverage.rs, coverage_evidence.rs, compliance.rs, convergence.rs, validate.rs, etc.) contain mutants we could not enumerate from CI. This PR addresses the survivors we DO have data for; a follow-up should either (a) further shrink the shards once arithmetic-heavy modules are pinned, or (b) skip surveys of the epoch_* helpers in cargo mutants config so the budget covers the more semantic modules.

Test plan

  • cargo test -p rivet-core --lib passes (816 tests).
  • cargo mutants -p rivet-core --file rivet-core/src/embed.rs --regex 'epoch_to_ymd_hm' reports 106 / 106 caught.
  • cargo mutants -p rivet-core --file rivet-core/src/reqif.rs --regex 'reqif_creation_timestamp|epoch_secs_to_iso8601' reports 80 / 80 caught.
  • CI mutation shards 1 + 2 of 4 surface strictly fewer survivors than the previous main run.

🤖 Generated with Claude Code

Surviving mutants from the sharded mutation-testing run on main
(8a5a4a7, run 24941881359, shards 1+2 of 4) clustered in two areas:

1. Howard-Hinnant civil_from_days arithmetic in
   `embed.rs::epoch_to_ymd_hm` and `reqif.rs::reqif_creation_timestamp`
   (~60 surviving arithmetic mutants between them — every replace
   `/` / `%` / `+` / `-` / `*` survived because the wrappers wrap
   `SystemTime::now()` directly, with no deterministic input path).

2. HTML renderers in `embed.rs` (`render_coverage`,
   `render_diagnostics`, `render_matrix_table`, `find_rule_for_types`,
   `render_query`, `render_group`) — boundary thresholds, equality
   predicates, match guards.

This commit:

- Refactors `reqif_creation_timestamp` to call a deterministic helper
  `epoch_secs_to_iso8601(secs: u64) -> String` so the algorithm can be
  pinned by unit tests (mirrors the `epoch_days_to_ymd` extraction
  already in export.rs).
- Adds 8 known-input/known-output tests for `epoch_to_ymd_hm` and 7
  for `epoch_secs_to_iso8601`, covering Unix epoch, 2024-01-01,
  2024-12-31 23:59:59, 2024-04-25 12:34:56, 2024-02-15 (mp = 11
  branch), 2004-02-29 (doe/1460 = 1, doe/36524 = 0 boundary),
  2200-03-01 (doe/36524 = 2 boundary), 2400-02-29 (doe = 146096 — the
  only era-boundary date that exercises the `- doe/146096` term).
  These together pin every arithmetic position in the algorithm.
- Adds renderer-coverage tests for the 25 / 55 / 85 / 100 % bar-class
  thresholds in `render_coverage` and `render_matrix_table`, plus
  targeted tests for `find_rule_for_types` (both legs of the `&&`,
  both `==`), `render_diagnostics` severity filter + summary counts,
  `render_query` truncation-note `>` boundary and id-column wrapping,
  `render_group` empty-second-arg guard, and the `<details>` block
  emission in `render_coverage` when uncovered_ids is empty.
- Adds reqif tests for: Adapter::id() / name() string identity;
  `parse_reqif` recognising `ReqIF.Name` and `ReqIF.ChapterName` as
  title (not as a generic field); empty-value guards on
  `ReqIF.ForeignID` / `ReqIF.Name` (must NOT overwrite defaults with
  blanks); enum-valued `status` and `tags` arms; `REL-N` counter
  incrementing per link in `build_reqif_with_schema`; mixed-extension
  directory walk picking up both `.reqif` AND `.xml` files in
  `import_reqif_directory`.

Local cargo-mutants verification (against this commit):
- `cargo mutants -p rivet-core --file rivet-core/src/embed.rs --regex
  'epoch_to_ymd_hm'` — 106 / 106 caught (was 5 missed pre-fix).
- `cargo mutants -p rivet-core --file rivet-core/src/reqif.rs --regex
  'reqif_creation_timestamp|epoch_secs_to_iso8601'` — 80 / 80 caught
  (entire arithmetic block was previously surviving).

Test count: rivet-core --lib goes from 780 to 816 (+36 tests, all
deterministic and Miri-tagged where they hit XML / quick-xml paths).

Refs: REQ-004
Verifies: REQ-004
@github-actions
Copy link
Copy Markdown

📐 Rivet artifact delta

No artifact changes in this PR. Code-only changes (renderer, CLI wiring, tests) don't touch the artifact graph.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Rivet Criterion Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: f59ecf4 Previous: 58801b6 Ratio
store_lookup/100 2116 ns/iter (± 5) 1718 ns/iter (± 16) 1.23
store_lookup/1000 25820 ns/iter (± 191) 19320 ns/iter (± 203) 1.34
validate/10000 15980477 ns/iter (± 1421545) 12656985 ns/iter (± 862501) 1.26
traceability_matrix/1000 60186 ns/iter (± 979) 41047 ns/iter (± 91) 1.47
query/100 783 ns/iter (± 5) 630 ns/iter (± 1) 1.24
query/1000 7708 ns/iter (± 147) 5229 ns/iter (± 124) 1.47

This comment was automatically generated by workflow using github-action-benchmark.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 26, 2026

Codecov Report

❌ Patch coverage is 99.55947% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rivet-core/src/reqif.rs 99.07% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@avrabe avrabe merged commit c328d5f into main Apr 26, 2026
22 of 28 checks passed
@avrabe avrabe deleted the fix/mutants-rivet-core-survivors branch April 26, 2026 13:08
avrabe added a commit that referenced this pull request Apr 26, 2026
…merge

style(rivet-core): cargo fmt drift after PR #218 merge
avrabe added a commit that referenced this pull request Apr 26, 2026
Follow-up to PR #218 which addressed embed.rs + reqif.rs survivors
from the 4-shard rivet-core mutation matrix. The 16-shard config
(this branch's previous commit) now lets every shard complete
without timeout, exposing survivors in the semantic modules.

Local cargo-mutants runs against main produced these survivor counts;
this commit's tests drive each to zero (verified locally with the
same `cargo mutants -p rivet-core --file <module>.rs` command):

  module                     before  after
  coverage_evidence.rs        10      0
  compliance.rs               21      0
  convergence.rs               6      0
  links.rs                    21      1*
  store.rs                     2      0
  ─────────────────────────  ─────  ─────
  total                       60      1*

(*) The remaining links.rs survivor is the `&&` between
    `forward == other.forward` and `backward == other.backward` on
    line 104. It is an EQUIVALENT mutant: `LinkGraph::backward` is
    derived from `forward` during `build()`, so any forward
    difference always implies a backward difference. No external
    test can distinguish `&&` from `||` for this clause. The
    companion clause on line 105 (`broken == other.broken`) IS
    killed because `broken` is independent of forward/backward.

Tests added (each pins one or more named mutants):

  coverage_evidence.rs:
   - computed_percentage_partial_value
       (kills f64-const, *↔+/, /↔*/% in computed_percentage)
   - computed_percentage_total_zero_returns_one_hundred
   - computed_percentage_total_nonzero_full_coverage
   - coverage_store_is_empty_true_on_new
   - coverage_store_is_empty_false_after_insert

  compliance.rs:
   - is_eu_ai_act_loaded_requires_both_anchor_types
       (kills && → ||, constant-false on the loader)
   - compute_compliance_partial_section_arithmetic
       (kills += ↔ -=/*=, > ↔ ==/<=/>= , == ↔ != ,
        * ↔ +/, / ↔ %/* across compute_compliance)
   - compute_compliance_overall_pct_when_total_required_zero

  convergence.rs:
   - signature_message_hash_uses_xor_not_or_or_and
       (kills ^= ↔ |=/&= in simple_hash)
   - failure_signature_display_writes_inner_string
   - retry_strategy_guidance_returns_distinct_messages
       (kills constant-string replacement on guidance())
   - retry_strategy_display_uses_guidance

  links.rs (new tests module — file had no prior tests):
   - debug_fmt_writes_struct_name
   - partial_eq_distinguishes_distinct_graphs
   - node_map_returns_artifact_indices
   - backlinks_of_type_filters_by_type
   - has_cycles_distinguishes_acyclic_and_cyclic
   - orphans_lists_only_artifacts_with_no_links
   - reachable_traverses_only_matching_link_type

  store.rs:
   - store_is_empty_distinguishes_empty_and_populated

Per CLAUDE.md, every commit touching `rivet-core/src/` requires
artifact trailers.

Verifies: REQ-002, REQ-004, REQ-009, REQ-010

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant