Files
werkregie/doc/prs/2026-10-14-PR#64-mutation-tests.md
claude-code ae59358778
werkator build succeeded after 01:05
werkator/mutation build succeeded after 14:23
Mutation tests in each build, in full each night (#64)
Co-authored-by: claude-code <claude-code@javagil.de>
2026-10-14 12:06:17 +02:00

120 lines
5.7 KiB
Markdown

# PR#64: Mutation Tests in Each Build, in Full Each Night
> **WARNING:** This document describes only the change applied in this PR.
> It may already be outdated once the next PR is merged.
> Historic PR documentation is not maintained along with new PRs; treat it as a snapshot.
## Related Links
- [PR#59](https://git.javagil.de/mi/werkregie/pulls/59), which brought the other required checks.
- hs.admin.ng, whose split this PR follows: a partial run in each branch build, a full run each night.
## Problem
The shared workflow requires mutation tests for Kotlin: werkcrappit measures the Mutation-CRAP of each method,
and the gate `maxMutationCrap` fails the build above the limit.
werkregie has no mutation tests yet; they are the last required check it lacks.
A nightly run alone finds a weakly tested method only after the merge; each branch build shall find it before.
## Non-Goals
- No refactoring of the 67 methods above 15; they follow in PRs of their own.
- No public CodeCharta; it stays an artifact of the nightly build in Werkator.
- No thresholds of PIT itself, e.g. `mutationThreshold`; werkcrappit's gate decides.
## Specification
### Feature: mutation tests in each build, in full each night
#### Scenario#64.01: a branch build judges only the files the branch changes
&hellip; so that the mutation tests fit into each branch build.
- **Given** a branch that changes only the file `Summaries.kt`
- **When** Werkator builds the branch
- **Then** the build judges the Mutation-CRAP of the methods in `Summaries.kt` only, nested classes included
##### Verified by
- Manual: `pitestPartial mutationCrapCheck` with a changed `Summaries.kt`: 77 mutants, 43 s;
PIT selected 8 classes: `Summarizer`, `ChatSummarizer`, `Summaries` and their nested ones.
#### Scenario#64.02: a weakly tested method in a changed file fails the build
&hellip; so that each change leaves the files it touches at the target.
- **Given** a branch that changes the file `Summaries.kt`, whose method `Summaries.of` has a Mutation-CRAP of 16.4
- **When** Werkator builds the branch
- **Then** the build fails and names `Summaries.of`, since 16.4 is above 15
##### Verified by
- Manual: the same run failed with `Summaries.of … has 16.4282 > 15`.
#### Scenario#64.03: a branch without changed code judges nothing
&hellip; so that a change of documents or configuration costs no mutation tests.
- **Given** a branch that changes only documents and the build configuration
- **When** Werkator builds the branch
- **Then** the build judges no Mutation-CRAP and passes
##### Verified by
- Manual: `pitestPartial mutationCrapCheck` on this branch: `pitest` and `mutationCrapCheck` skipped.
- Werkator's build of this PR: the same.
#### Scenario#64.04: main is judged in full each night
&hellip; so that the CodeCharta shows all of main and no method gets worse unnoticed.
- **Given** main, whose highest Mutation-CRAP is 1806
- **When** Werkator builds main at night
- **Then** the build judges all methods and keeps the CodeCharta of all classes
##### Verified by
- Manual: `pitestFull mutationCrapCheck`: 187 classes, 3099 mutants, 12 min 54 s with 2 threads;
the highest value, 1806, is `GiteaSource.pullRequestItem`.
- Werkator's build `mutation` on main.
## Solution
- `dashboard/build.gradle.kts`: the plugins `info.solidsoft.pitest` with PIT 1.30.0 and Kotest's PIT extension,
and `de.javagil.werkcrappit` 0.26.12 from the Maven registry of git.javagil.de.
- `pitestPartial` mutates the classes of the Kotlin files the branch changes against the merge base with `origin/main`,
uncommitted changes included; `-PmutationBaseline` names another ref.
Per file it takes the top-level classes and the file class `<Name>Kt`, each with its nested classes.
Without a changed file, `pitest` and `mutationCrapCheck` are skipped.
- `pitestFull` mutates all classes.
- `dashboard/gradle/gradle-daemon-jvm.properties`, written by `updateDaemonJvm`: Gradle runs on Temurin 25 from Foojay,
since werkcrappit's plugin needs Java 21 and Werkator starts Gradle with the host's Java 17.
- The gate: 15 for `pitestPartial`, the target for Kotlin;
1806 for `pitestFull`, a ratchet from today's highest value, lowered with each refactoring.
- `.werkator.yml`:
- the build `default` runs `pitestPartial mutationCrapCheck` after `build`; its timeout rises from 10 to 20 min;
- the nightly build `mutation` runs `pitestFull mutationCrapCodeCharta mutationCrapCheck` on main at 04:00 UTC,
with the status `werkator/mutation`.
- `doc/agentic-workflow.md`, the master copy: each branch build mutates the classes the branch changes,
a nightly build all classes; only a branch build made too slow may leave them to the night, and the cause is fixed.
- `doc/risk-analysis.md` and `AGENTS.md` follow.
- Version 0.41.0.
PIT's incremental history, which hs.admin.ng uses, does not suit Kotest.
Without any change, a second run took 6 min 17 s: it kept only the 520 surviving and timed-out mutants
and ran the tests of all 1593 killed ones again.
The killing test of a Kotest spec reads e.g. `…JournalProposerTest.…JournalProposerTest/one entry per Taiga story…`,
which the history does not recognize as an unchanged test class.
## Open Questions
- [x] Shall the branch build use PIT's incremental history, as hs.admin.ng does?
No, chosen by the human after the measurement: `git diff` against the merge base with main.
- [x] Which limit holds in the branch build and each night?
Chosen by the human: 15 for the classes a branch changes; each night a ratchet from today's highest value.
## Follow-up PRs
- Refactorings and tests that bring each method of werkregie to a Mutation-CRAP of at most 15.
Each of these PRs lowers the limit of the nightly build to the new highest value.