55 lines
3.4 KiB
Markdown
55 lines
3.4 KiB
Markdown
# ADR 0009: Kotlin/JVM Basis Support
|
|
|
|
- Status: accepted
|
|
- Date: 2026-10-11
|
|
- PR: #61
|
|
|
|
## Context
|
|
|
|
The specification makes Kotlin/JVM a limited, optional goal (section 7).
|
|
werkcrappit joins PIT's mutants and JaCoCo's complexity by JVM class, method name and descriptor, never by source lines.
|
|
The Kotlin compiler turns one source function into several JVM methods, or into none of its own,
|
|
so the question is what each construct gives in the report.
|
|
Werkator, written in Kotlin, has run werkcrappit every night since PR#30 of werkcrappit, with a limit of 15.
|
|
|
|
## Decision
|
|
|
|
werkcrappit supports Kotlin/JVM as it is, without code of its own for Kotlin.
|
|
`KotlinBasicTest` runs PIT with the packaged plugin on the fixture `kotlin-basic` and pins what each construct gives;
|
|
the versions are those in [compatibility.md](../compatibility.md).
|
|
|
|
| Kotlin construct | JVM methods | In the report |
|
|
| --- | --- | --- |
|
|
| ordinary method, `when`, overloads | one per function, overloads by descriptor | `SCORED`, each overload apart |
|
|
| top-level function | a static method of `<File>Kt` | `SCORED` |
|
|
| companion object | methods of `<Class>$Companion` | `SCORED`; its constructors `EXCLUDED` by JaCoCo |
|
|
| property with a getter | `get…`/`is…`, `set…` | `SCORED` like any method; a setter without mutants has no score |
|
|
| default arguments | the function and a static `…$default` | the function `SCORED`; `…$default` `NO_MUTATION_RESULTS` |
|
|
| data class | `equals`, `hashCode`, `toString`, `copy`, `componentN` | `EXCLUDED`, `jacoco-filtered:other` or `:synthetic` |
|
|
| bridge of a generic override | a synthetic bridge method | `EXCLUDED`, `jacoco-filtered:bridge` |
|
|
| lambda passed to an inline function | none: inlined into the caller | counts for the caller |
|
|
| other lambda | a static `…$lambda$0` | `SCORED` apart; the function around it keeps only its own code |
|
|
| inline function | the function, and a copy in every caller | its own body never runs: its mutants stay `NO_COVERAGE` |
|
|
| suspend function | the function and a continuation class | `SCORED`; the continuation `EXCLUDED`; see the limits |
|
|
|
|
## Limits
|
|
|
|
- **Inline functions:** PIT mutates the inline function itself, which no test runs, as every caller runs its own copy.
|
|
Its score is then C² + C, whatever the tests do.
|
|
The copies count for the callers, which carry those mutants.
|
|
- **Suspend functions with a suspension point:** JaCoCo leaves the compiler's state machine out of the complexity,
|
|
but PIT mutates it; some of these mutants are never covered or survive, which lowers the mutation score.
|
|
A suspend function can so score above its complexity although its tests check its logic.
|
|
- **Kotlin's extra branches:** JaCoCo counts branches the compiler adds,
|
|
e.g. the `else` of an exhaustive `when` or an inlined `let`.
|
|
This raises the complexity, so a Kotlin project may set a higher limit than a Java one:
|
|
Werkator uses 15, against 10 in hsadmin-ng.
|
|
- Arcmutate's Kotlin support for PIT is not part of the tested setup.
|
|
|
|
## Consequences
|
|
|
|
- The report can be read for Kotlin as for Java, with the limits above in mind; no Kotlin method gets an invented score.
|
|
- A new Kotlin, PIT or JaCoCo version can change the table: their filters change; `KotlinBasicTest` shows it.
|
|
- The specification's IDs `@MC-032` and `@MC-033` for Kotlin are taken by the progress scenarios
|
|
of `04-pit-lifecycle.feature`; the Kotlin scenarios use `@MC-KT-…` in `11-kotlin-jvm.feature`.
|