Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .agents/tasks/kmp-jvmtest-junit-platform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Enable JUnit Platform for KMP `jvmTest` in the `kmp-module` convention

## Problem

`kmp-module.gradle.kts` never applies `module-testing` (it cannot: that plugin
applies `java-library`, which conflicts with `kotlin("multiplatform")`), so the
KMP `jvmTest` task runs without the JUnit Platform and silently discovers zero
JUnit 5/Kotest tests. Verified: `./gradlew :logging:jvmTest --rerun` passed in
~1 s with no test reports while 13 `*Spec.kt` files exist under
`logging/src/jvmTest/`. `backends/otel-backend/build.gradle.kts` carries a
module-local workaround.

Recorded as the "Collateral finding" in
`.agents/tasks/otel-backend-validation.md` (main checkout).

## Plan

1. In the sibling `config` checkout, extend `kmp-module.gradle.kts`:
configure `tasks.named<Test>("jvmTest")` with `useJUnitPlatform()` and
`configureLogging()` — mirroring `module-testing.setupTests()` but without
`includeEngines("junit-jupiter")`, because `kmp-module` itself adds the
Kotest runner (engine `kotest`) to `jvmTest` dependencies.
2. Apply the identical change to this repo's `buildSrc` copy (anticipating the
config float; `./config/pull` will overwrite with the same content once the
config change lands).
3. Remove the module-local workaround from
`backends/otel-backend/build.gradle.kts` (the `tasks.named<Test>("jvmTest")`
block). Keep its `registerTestTasks()` call — the convention does not
register `fastTest`/`slowTest`.
4. Verify with JDK 17: `:logging:jvmTest --rerun` executes the 13 jvm specs
(plus commonTest specs and Java tests), and `:backends:otel-backend:jvmTest`
still executes its tests without the workaround.

## Status

- [x] Config repo edited — committed by the user as `512c1068` on the
`address-logging-audit-finding` branch of the `config` checkout
- [x] Local `buildSrc` copy updated
- [x] otel-backend workaround removed
- [x] `:logging:jvmTest` executes the 13 jvm specs — 232 tests ran
(previously zero); all 13 spec classes have result files
- [x] `:otel-backend:jvmTest` still executes its 22 tests (all pass);
`:logging-testlib:jvmTest` now runs 3 tests (pass);
`:tests:fixtures` has no test sources (NO-SOURCE)

## Latent-failure triage (resolved)

Enabling the platform surfaced **25 latent test failures** (232 run,
207 pass) — behavior drift accumulated while the task silently ran
nothing. They were triaged in a dedicated session; its fixes were
adopted onto this branch. `:logging:jvmTest` now passes 232/232.

Production bugs found and fixed by the triage (tests were right):

- `AbstractLogger.atConfig()` delegated to `Level.INFO` instead of
`Level.CONFIG`.
- `MetadataHandler.Builder.addRepeatedHandler` had inverted validation
(rejected repeatable keys instead of requiring them; drift from the
`custom-metadata` PR #144).
- `MetadataKey.cast()` returned `null` instead of throwing the
documented `ClassCastException`; `checkCannotRepeat` threw
`IllegalStateException` where callers expect `IllegalArgumentException`.
- `SimpleProcessor` "wrapped" repeated-value lists with a no-op cast;
handlers could mutate them. Replaced with an unmodifiable iterator.
- `ScopedLoggingContext.Builder` lacked a member `run(Runnable)`, so
Kotlin's stdlib `run` extension executed blocks *without installing
the context*.
- Lazy log messages were evaluated outside the recursion guard of
`AbstractLogger.write`, so throwing/reentrant `toString()` escaped
error handling; timestamps lacked millis and UTC offset.

Test-side fixes (production was right): logger-name expectations
(`kotlin.String` vs `java.lang.String`), synthetic lambda method names,
eager invocation counting for rate-limiter specs, Kotlin spread
operator for `logVarargs`, updated message-text expectations.
10 changes: 0 additions & 10 deletions backends/otel-backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import io.spine.dependency.lib.OpenTelemetryKotlin
import io.spine.gradle.publish.SpinePublishing
import io.spine.gradle.publish.spinePublishing
import io.spine.gradle.testing.registerTestTasks
import org.gradle.api.tasks.testing.Test

plugins {
`kmp-module`
Expand Down Expand Up @@ -95,12 +94,3 @@ dependencies {
// Registers the `fastTest`/`slowTest` tasks and the `*Spec`/`*Test` filter,
// matching the core `logging` module.
tasks.registerTestTasks()

// The `kmp-module` convention does not put the JUnit Platform on the JVM test
// task (it configures only the `jvm-module` `test` task), so enable it here.
tasks.named<Test>("jvmTest") {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
}
12 changes: 12 additions & 0 deletions buildSrc/src/main/kotlin/kmp-module.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import io.spine.gradle.javac.configureJavac
import io.spine.gradle.kotlin.setFreeCompilerArgs
import io.spine.gradle.publish.IncrementGuard
import io.spine.gradle.report.license.LicenseReporter
import io.spine.gradle.testing.configureLogging

/**
* Configures this [Project] as a Kotlin Multiplatform module.
Expand Down Expand Up @@ -162,11 +163,22 @@ java {
*
* Also, Kotlin and Java share the same test executor (JUnit), so tests
* configuration is for both.
*
* The `jvmTest` task mirrors the setup made by `module-testing` for
* the `test` task of a `jvm-module` (`module-testing` itself cannot be
* applied here because it brings `java-library`, which conflicts with
* the Kotlin Multiplatform plugin). Unlike `module-testing`, no engine
* filter is imposed: `jvmTest` dependencies include the Kotest runner,
* which is a JUnit Platform engine of its own.
*/
tasks {
withType<JavaCompile>().configureEach {
configureJavac()
}
named<Test>("jvmTest") {
useJUnitPlatform()
configureLogging()
}
}

/**
Expand Down
Loading
Loading