Troubleshooting¶
The failures people actually hit, what each one means, and what to do about it. Ordered by how often they come up rather than by severity.
"no mutants found"¶
jzap analysed the classes it was given and none of them yielded a mutant. Usually one of:
- The code was not compiled.
mutableCodePathspoints at a directory that does not exist or is empty.jzap run --dry-runprints the resolved paths; check them before anything else. - Everything is out of scope. On a diff-scoped run this is the normal, healthy case: the changed lines carry no mutants. The scope line in the report says how many lines were in scope.
- The include/exclude globs exclude everything.
--include com.example.*matches binary class names, so a typo in a package name silently matches nothing. - The classes carry no line numbers. Compiled without
-gor with-g:none, a class has no line information, and a mutant that cannot be pointed at any source line is not reported. Compile with debug information, which is the default for both Gradle and Maven.
Every mutant says NO_COVERAGE¶
No test executed the mutated code. Distinguish two cases from the report's tests discovered line:
- Zero tests discovered. Test discovery found nothing. The usual causes are a missing
junit-platform-launcheron the test runtime classpath, ortestClassPathspointing at the wrong directory. Both show up in--dry-run. - Tests discovered, but nothing covered. The tests run but do not reach the mutated classes,
which for a library module means its tests live in another module. Analyse the modules together:
mutationTestAllin Gradle, or one project model listing every module.
"test already fails before any mutant is applied"¶
Exactly what it says, and it matters more than it looks. A mutant covered by an already-failing test would be reported as killed by a failure that has nothing to do with it, so those tests are excluded from selection. Fix the test suite first; until then the score understates coverage, because mutants only those tests reach come back as uncovered.
The analysis JVM does not start, or dies immediately¶
The message includes the exact command line. Run it by hand: it is an ordinary java invocation and
fails the same way outside jzap. Common causes are a classpath entry that no longer exists after a
clean, and a javaHome pointing at a JRE without the tools jzap needs.
Scores differ between runs¶
They should not. If they do, in order of likelihood:
- A test in the project is not deterministic. Run the suite twice on its own before blaming jzap; a test whose result varies makes every verdict that depends on it vary too.
- Wall-clock timeouts. A mutant that blocks rather than loops is caught by a wall-clock
backstop, and that is not reproducible. Loop-based hangs are detected by counting iterations and
are deterministic.
TIMED_OUTis never cached for exactly this reason. - A stale cache.
--cache-dirreuse is keyed on bytecode, mutators, filters and toolchain, so a mismatch discards it — but a cache shared between machines with different JDKs is refused rather than trusted, and the reason is printed.
The cache is not being reused¶
The run prints why it was discarded. The usual answers:
- The toolchain changed. Bytecode differs between
javacversions and platforms, so a cache records the toolchain that wrote it and refuses to be read under another. - The filter or mutator set changed. Both change the inventory, so verdicts recorded under one set say nothing about another.
- The engine version changed. Cached verdicts are only meaningful for the engine that produced them.
Kotlin: mutants in code nobody wrote¶
Should not happen: property accessors, data class members, null-check intrinsics and for-each loop
scaffolding are filtered by default. If you see one, it is a gap in those rules and worth reporting
with the mutant key. --mutate-kotlin-internals turns the filters off, which is occasionally useful
for seeing what is being suppressed.
Kotest: nothing is discovered¶
Kotest builds its test tree when a spec runs rather than when the platform asks what it contains, so
jzap takes its runnable unit from whatever the engine exposes — a spec, not a leaf test. If nothing
at all is found, check that kotest-runner-junit5 is on the test runtime classpath, since without it
the engine is not registered with the JUnit Platform.
The Gradle task is UP-TO-DATE when you expected it to run¶
It is doing what it was asked. The task's inputs are the compiled classes, the test classpath and the
configuration; if none changed, the previous report still stands. --rerun-tasks forces it.
threshold = 80 fails to configure in a Groovy build script¶
Use threshold = 80 and not threshold = "80". If you see a BigDecimal type error, you are on a
version before the Number setter; upgrade, or write threshold = 80 as double.
Getting more out of a failure¶
jzap run --dry-runprints the resolved scope, classpaths and module list without analysing anything. Most configuration problems are visible there.jzap list-mutants --format tableprints the inventory without running a single test, which separates "no mutants" from "no coverage".- The project model is a plain JSON file. Attach it to a bug report and the problem reproduces without your build tool in the loop.