Skip to content

Fix 25 latent test failures surfaced by enabling JUnit Platform on KMP jvmTest - #147

Closed
alexander-yevsyukov wants to merge 8 commits into
claude/angry-hugle-d62cfefrom
claude/inspiring-morse-a006d4
Closed

Fix 25 latent test failures surfaced by enabling JUnit Platform on KMP jvmTest#147
alexander-yevsyukov wants to merge 8 commits into
claude/angry-hugle-d62cfefrom
claude/inspiring-morse-a006d4

Conversation

@alexander-yevsyukov

Copy link
Copy Markdown
Contributor

What changed

The kmp-module convention never enabled the JUnit Platform on the KMP jvmTest task, so :logging:jvmTest has been silently discovering zero tests while behavior drifted from the Flogger contract during the Java-to-Kotlin migration. This PR enables the platform in the convention (mirroring module-testing, without an engine filter since kmp-module adds the Kotest runner — a JUnit Platform engine of its own), removes the now-superseded module-local workaround in otel-backend, and fixes the 25 latent failures this surfaced: :logging:jvmTest now runs 232/232 green.

Production fixes (Flogger-contract drift)

  • MetadataKey.cast() throws ClassCastException on a type mismatch again instead of silently returning null (the message reports the value class, never invoking a user toString()).
  • checkCannotRepeat() throws IllegalArgumentException per the documented contract of findValue()/getSingleValue(); its message was also inverted.
  • MetadataHandler.Builder.addRepeatedHandler() required a key that cannot repeat — the exact opposite of Flogger's checkArgument(key.canRepeat()).
  • SimpleProcessor lost Collections.unmodifiableList in translation (e.setValue(e.value as List<*>) was a no-op cast); repeated values now dispatch through an iterator whose remove() throws.
  • LogContext evaluated the lazy message lambda before AbstractLogger.write(), outside the recursion guard and robust error handling — a throwing toString() propagated to callers and reentrant logging hit StackOverflowError. Evaluation now happens inside write(data, prepare) (@JvmOverloads preserves the old JVM signature). A null message also reaches the backend as null instead of the string "null".
  • AbstractLogger.atConfig() mapped to Level.INFO (copy-paste); now Level.CONFIG.
  • Logging-error timestamps now include milliseconds and the UTC offset (yyyy-MM-dd'T'HH:mm:ss.SSSZ equivalent).
  • LogPerBucketingStrategy.byClass()/byClassName() used key::class/qualifiedName!! — no identity guarantee and an NPE for local/anonymous classes; now key.javaClass/.name.
  • ScopedLoggingContext.Builder.run(Runnable) was missing, so .run { } silently bound to Kotlin's stdlib run and never installed the context; the member is restored.

Test fixes (stale or Java-specific expectations)

Invocation counters moved out of lazy log lambdas (they only run for statements that actually log), the * spread operator for vararg arrays, Kotlin qualified names in LogLevelMapSpec, and message-format alignments. Full per-failure triage rationale: .agents/tasks/logging-jvmtest-triage.md.

Reviewer notes

  • Must-not-forget follow-up: buildSrc/src/main/kotlin/kmp-module.gradle.kts is config-distributed — the same jvmTest wiring must land in SpineEventEngine/config, or the next ./config/pull reverts it and jvmTest silently drops back to zero tests (a follow-up task is queued for this).
  • The version bump also restores the canonical val versionToPublish: String by extra("…") shape; the previous bump commit introduced extra.set(…), which the version-bumped guard script cannot parse.
  • One deliberate non-fix: AbstractLogger.write() catches RuntimeException around the message evaluation, which would also contain a CancellationException raised from user code reachable via toString(). This matches Flogger's "log statements never throw" contract and the lambdas are non-suspending, so it is left as-is — flagging for awareness.
  • Copyright headers of Flogger-ported files keep the The Flogger Authors; attribution (also restored in MetadataKey.kt, where it had been lost on master).

Pre-PR checklist: ./gradlew build dokkaGenerate green (all modules, detekt, Kover); reviewers kotlin-engineer, spine-code-review, review-docs all APPROVE WITH CHANGES, applied or tracked as above.

🤖 Generated with Claude Code

alexander-yevsyukov and others added 4 commits July 2, 2026 12:52
…P jvmTest

The kmp-module convention never enabled the JUnit Platform on the KMP
jvmTest task, so :logging:jvmTest silently discovered zero tests while
behavior drifted from the Flogger contract during the Kotlin migration.
Enable the platform (mirroring module-testing, without an engine filter
since Kotest provides its own engine) and fix the failures it surfaced.

Production fixes:
- MetadataKey.cast() throws ClassCastException on type mismatch again
  instead of silently returning null.
- checkCannotRepeat() throws IllegalArgumentException per the documented
  contract of findValue()/getSingleValue(), with a corrected message.
- MetadataHandler.Builder.addRepeatedHandler() requires a repeatable key
  (the check was inverted).
- SimpleProcessor guards repeated values with an unmodifiable iterator
  (the unmodifiable-list wrapping was lost in the Java-to-Kotlin port).
- LogContext evaluates the lazy message lambda inside
  AbstractLogger.write(), so a throwing or reentrant toString() is
  handled by the recursion guard and robust error handling instead of
  propagating (or overflowing the stack). A null message now reaches
  the backend unmodified.
- AbstractLogger.atConfig() maps to Level.CONFIG, not Level.INFO.
- Logging-error timestamps include milliseconds and the UTC offset.
- LogPerBucketingStrategy.byClass()/byClassName() use the Java class,
  restoring identity semantics and fixing an NPE for local and
  anonymous classes.
- ScopedLoggingContext.Builder.run(Runnable) is restored; without it,
  `.run { }` silently bound to the Kotlin stdlib extension and never
  installed the context.

Test fixes: eager invocation counters outside lazy log lambdas, the
spread operator for vararg arrays, Kotlin qualified names in
LogLevelMapSpec, and message-format alignments.

The module-local jvmTest workaround in otel-backend is superseded by
the convention change and removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Do not invoke a user-supplied toString() when composing the
  ClassCastException message in MetadataKey.cast(); report the value
  class instead.
- Document the IllegalArgumentException thrown by addRepeatedHandler().
- Document the null pass-through of MetadataKey.cast().
- Clarify comments around the deferred message evaluation in LogContext.
- Restore the Flogger attribution in the MetadataKey.kt header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 2, 2026 13:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restores effective JVM test execution for the logging KMP module by enabling JUnit Platform for jvmTest, then fixes the production/test regressions that were previously masked by “0 tests discovered”. It also realigns several logging behaviors with the Flogger contract and updates documentation/version artifacts accordingly.

Changes:

  • Enable JUnit Platform on KMP jvmTest (and remove the module-local otel-backend workaround).
  • Fix Flogger-contract drift in core logging (lazy message evaluation inside write(), correct atConfig() level, repeated-metadata immutability, stricter MetadataKey.cast(), safer bucketing strategy keys, restored ScopedLoggingContext.Builder.run(Runnable)).
  • Update tests and generated docs/version references to match the corrected behavior.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated no comments.

Show a summary per file
File Description
version.gradle.kts Bumps versionToPublish to 2.0.0-SNAPSHOT.422 using the delegated extra property shape.
logging/src/jvmTest/kotlin/io/spine/logging/test/LoggingCompatibilityTest.kt Fixes Kotlin vararg array passing via spread operator in compatibility test.
logging/src/jvmTest/kotlin/io/spine/logging/LogContextSpec.kt Updates expectations for null literal message handling and formatting behavior.
logging/src/jvmTest/kotlin/io/spine/logging/JvmLoggerSpec.kt Aligns log-site/method-name expectation and moves counters outside lazy message lambdas.
logging/src/jvmTest/kotlin/io/spine/logging/backend/AnyExtsJvmSpec.kt Updates expectation to match backtick diagnostic style for toString() returning null.
logging/src/commonTest/kotlin/io/spine/logging/context/LogLevelMapSpec.kt Adjusts mapped logger-name expectations to Kotlin qualified names (e.g., kotlin.String).
logging/src/commonTest/kotlin/io/spine/logging/AbstractLoggerSpec.kt Makes recursion error assertion case-insensitive.
logging/src/commonMain/kotlin/io/spine/logging/MetadataKey.kt Restores Flogger-like cast() behavior (throw on mismatch) and corrects checkCannotRepeat() exception type/message.
logging/src/commonMain/kotlin/io/spine/logging/LogPerBucketingStrategy.kt Uses JVM class identity/name for bucketing to avoid KClass identity/NPE issues.
logging/src/commonMain/kotlin/io/spine/logging/LogContext.kt Defers lazy message evaluation into AbstractLogger.write() recursion guard and preserves null literal behavior to backend.
logging/src/commonMain/kotlin/io/spine/logging/context/ScopedLoggingContext.kt Restores Builder.run(Runnable) to avoid stdlib run shadowing and ensure context installation.
logging/src/commonMain/kotlin/io/spine/logging/backend/MetadataProcessor.kt Ensures repeated metadata iteration is non-modifiable via an iterator wrapper that throws on remove().
logging/src/commonMain/kotlin/io/spine/logging/backend/MetadataHandler.kt Fixes repeated-handler registration to require key.canRepeat() (and documents the thrown IllegalArgumentException).
logging/src/commonMain/kotlin/io/spine/logging/AbstractLogger.kt Fixes atConfig() level, moves message evaluation into write(data, prepare), and improves logging-error timestamp formatting (millis + UTC offset).
docs/dependencies/pom.xml Updates docs dependency POM version to 2.0.0-SNAPSHOT.422.
docs/dependencies/dependencies.md Updates generated dependency report version headers/timestamps to match the bumped snapshot.
buildSrc/src/main/kotlin/kmp-module.gradle.kts Configures KMP jvmTest to use JUnit Platform and test logging (config-distributed file).
backends/otel-backend/build.gradle.kts Removes now-superseded local jvmTest JUnit Platform/test logging workaround.
.agents/tasks/logging-jvmtest-triage.md Adds a triage note capturing failure analysis and rationale (config-managed path).

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.87%. Comparing base (cf43e4b) to head (13222bd).

Additional details and impacted files
@@                       Coverage Diff                       @@
##             claude/angry-hugle-d62cfe     #147      +/-   ##
===============================================================
+ Coverage                        67.80%   67.87%   +0.06%     
  Complexity                         464      464              
===============================================================
  Files                              109      109              
  Lines                             2578     2574       -4     
  Branches                           403      402       -1     
===============================================================
- Hits                              1748     1747       -1     
+ Misses                             696      694       -2     
+ Partials                           134      133       -1     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 210c1e869d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread logging/src/commonTest/kotlin/io/spine/logging/context/LogLevelMapSpec.kt Outdated
On the JVM, `LoggingFactory.loggerFor()` names loggers by the Java class
name (`Platform.getBackend(cls.java.name)`), while `LogLevelMap.Builder`
registered classes under their Kotlin qualified names. For classes whose
Kotlin and Java names differ (mapped types such as `String`, and nested
classes), a map entry created via `add(level, SomeClass::class)` could
never affect the logger created for that very class.

`KClass.toLoggerName()` now returns the Java class name, and the builder
uses it, so map entries always match the loggers they target. This also
removes the `IllegalArgumentException` for local and anonymous classes,
which have no Kotlin qualified name but always have a Java name.

Reported by Codex review on PR #147.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alexander-yevsyukov alexander-yevsyukov self-assigned this Jul 2, 2026
@alexander-yevsyukov alexander-yevsyukov moved this to 🏗 In progress in v2.0 Jul 2, 2026
@alexander-yevsyukov
alexander-yevsyukov changed the base branch from master to claude/angry-hugle-d62cfe July 2, 2026 14:45
@alexander-yevsyukov
alexander-yevsyukov changed the base branch from claude/angry-hugle-d62cfe to master July 2, 2026 14:46
@alexander-yevsyukov
alexander-yevsyukov changed the base branch from master to claude/angry-hugle-d62cfe July 2, 2026 15:28
alexander-yevsyukov and others added 2 commits July 2, 2026 19:17
Both branches fixed the same 25 latent jvmTest failures independently.
The merge deduplicates them, generally preferring the base branch and
keeping from this branch:

- The `LogLevelMap`/`toLoggerName` alignment with JVM logger names
  (Codex review finding on PR #147).
- The canonical `val versionToPublish: String by extra(...)` shape,
  advanced to `2.0.0-SNAPSHOT.423` above the base version.
- The `The Flogger Authors; ` copyright attribution in ported files.

New tests from the base branch (level-mapping coverage, repeated-handler
precondition) are retained: 234 tests, all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alexander-yevsyukov
alexander-yevsyukov marked this pull request as draft July 2, 2026 18:18
@alexander-yevsyukov

Copy link
Copy Markdown
Contributor Author

Closing as incorrect. We want to use fully qualified Kotlin class names in LogLevelMap, not Java class names.

@github-project-automation github-project-automation Bot moved this from 🏗 In progress to ✅ Done in v2.0 Jul 2, 2026
@alexander-yevsyukov
alexander-yevsyukov deleted the claude/inspiring-morse-a006d4 branch July 2, 2026 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants