werkator build succeeded after 00:34
Step 3 of 8 from section 8.3 of the specification. - Classes are read only from PIT's code roots (PIT's own code-path rule, never dependency JARs); differing copies are ambiguous, a missing class makes the scope incomplete. - JaCoCo's analysis is matched against the methods the class file declares (ASM): methods JaCoCo filtered are EXCLUDED with proof (`jacoco-filtered:bridge|synthetic|generated-annotation|other`), abstract/native methods get no complexity. - PIT's `excludedMethods` → EXCLUDED with `pit-excluded-method:<glob>`. - Every method of the selected classes appears in the provisional report (schemaVersion 0) with a score or a reason. - ADR 0002, feature 03, MC-008 to MC-012 mapped; MC-013 open. CI on werkator.javagil.de: green (52 s), 66 unit and 8 functional tests. PR-doc: `docs/prs/2026-10-01-PR#4-method-inventory.md` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: mhoennig <michael@hoennig.de> Reviewed-on: #4
60 lines
4.3 KiB
Markdown
60 lines
4.3 KiB
Markdown
# ADR 0001: PIT Integration
|
|
|
|
- Status: accepted; the class lookup is refined by [ADR 0002](0002-method-inventory.md)
|
|
- Date: 2026-10-01
|
|
- PR: #2
|
|
|
|
## Context
|
|
|
|
werkcrappit joins PIT's mutation results with the JaCoCo complexity of the same JVM method.
|
|
The specification demands the results from PIT's listener interface, the complexity from the original class files, and no extra test or PIT run.
|
|
Section 3.4 of the specification warns that `ListenerArguments.getLocator()` yields source readers, not bytecode.
|
|
So the first step had to prove where a result listener gets the original class bytes from.
|
|
|
|
## Decision
|
|
|
|
werkcrappit is a PIT result listener plugin: one JAR on PIT's tool classpath, loaded through the Gradle PIT plugin's `pitest` configuration.
|
|
|
|
- **Registration:** `MutationCrapListenerFactory` is listed in `META-INF/services/org.pitest.mutationtest.MutationResultListenerFactory` and registers the output format `WERKCRAPPIT_MUTATION_CRAP`.
|
|
The specification starts with `MUTATION_CRAP` (section 2.3).
|
|
The name is changed so that an `outputFormats` line shows which plugin provides the format, and so that no other plugin on PIT's tool classpath can claim it by chance.
|
|
The metric id `mutation-crap` and the report file `mutation-crap.json` stay as specified.
|
|
- **Mutation results:** the listener collects every `ClassMutationResults` block and evaluates nothing before `runEnd()`, since a class may arrive in several blocks.
|
|
- **Method identity:** `MutationDetails.getId().getLocation()` carries class, JVM method name and full descriptor.
|
|
The class name is normalized to the dotted binary name in `MethodId`; JaCoCo's slashed name is converted in the JaCoCo adapter.
|
|
- **Original bytecode:** `ListenerArguments.data()` returns PIT's `ReportOptions`, a public type.
|
|
`getMutationClassPaths().code()` names the classes PIT selected for mutation; `getClassPathElements()` gives the classpath in PIT's order.
|
|
werkcrappit reads each class file from the first element that holds it, records that element as origin, and hashes the bytes with SHA-256.
|
|
This is option 2 of section 3.4: data PIT was started with, no private fields, no reflection, no class loading.
|
|
A dedicated Gradle hand-over (option 3) is not needed.
|
|
- **Why the bytes are original:** PIT mutates in memory inside its minion processes; the class files on disk stay as the build compiled them.
|
|
- **Complexity:** JaCoCo Core's `Analyzer` with an empty `ExecutionDataStore` and a `CoverageBuilder`; per method the total of `getComplexityCounter()`.
|
|
- **Report:** written through `ListenerArguments.getOutputStrategy()`, so it lands in PIT's report directory next to the HTML and XML reports.
|
|
- **Dependencies:** `pitest-entry` is `compileOnly`, since PIT provides it at run time.
|
|
`org.jacoco.core` is a regular dependency and reaches PIT's tool classpath through the published metadata.
|
|
PIT 1.30.0 and JaCoCo 0.8.15 both use ASM 9.10.1, so no shading is needed.
|
|
|
|
## Verification
|
|
|
|
`PitWalkingSkeletonTest` (source set `functionalTest`) publishes the JAR into a repository under `build/`, then runs `pitest` with Gradle TestKit on the fixture `fixtures/java-basic`.
|
|
The fixture consumes the published artifact, not the classes of this build.
|
|
The test proves the five points of section 3.3:
|
|
|
|
1. PIT finds the factory and the report exists.
|
|
2. The results arrive with class, method and descriptor and are joined with complexity 3 for `classify(I)Ljava/lang/String;`.
|
|
3. The analysed bytes have the SHA-256 of `build/classes/java/main/demo/Classifier.class`.
|
|
4. No result stays unmatched, no class is missing.
|
|
5. `mutations.xml` and `index.html` exist; the `test` task did not run; no `.exec` file exists.
|
|
|
|
The tested versions are in [docs/compatibility.md](../compatibility.md).
|
|
|
|
## Risks
|
|
|
|
- PIT does not promise its extension interfaces across versions; `ReportOptions` is public but not documented as plugin API.
|
|
An upgrade of PIT needs the functional test to pass before it is pinned.
|
|
- `getClassPathElements()` includes dependency JARs.
|
|
A selected class that also exists in an earlier JAR would be read from that JAR; the recorded origin makes that visible.
|
|
Step 3 decides whether to restrict the lookup to PIT's code paths.
|
|
- Duplicate and contradicting results are not handled yet (step 4).
|
|
- The report has `schemaVersion` 0 and no score; step 2 adds the metric, step 5 the versioned contract.
|