Testing the Processor¶
Traps that bite when authoring TestKspRunner-based tests in kraft-ksp/src/jvmTest. Both were hit in PR #74's CI run; check here before writing a new fixture.
1. Generated-file order is filesystem-dependent — never assert on generated.first()¶
Symptom: a test asserting on compileAndReturnGenerated(...).first().readText() passes locally on macOS but fails on Linux CI with output like:
expected to contain: fun Status.toStatusDto()
but was: // Generated by Kraft. Do not edit.
package kraft.generated.registry
...
Cause: any fixture that produces a converter — a top-level @KraftConverter function or a @MapEnum mapping — generates two files: the mapper and the @KraftConverterDelegate registry (kraft/generated/registry/Converters_<hash>.kt). sourcesGeneratedBySymbolProcessor returns them in filesystem walk order, which differs between macOS and Linux (ext4 readdir order). On CI the registry can come first.
Fix: select generated files by name, never positionally:
val content = TestKspRunner.compileAndReturnGenerated(source)
.first { "EnumMapper" in it.name }
.readText()
or assert against all files joined:
Plain @MapConfig data-class fixtures generate a single file, so first() happens to be safe there — but the by-name form costs nothing and survives fixture growth.
Minimal reproduction: any of the three @MapEnum content tests (MapEnumAutoMappingTest etc.) with the name filter removed, run on a Linux runner.
2. Fixtures using converters need a package declaration¶
Symptom: generated code fails to compile inside the test (with kspWithCompilation = true):
e: .../EventDtoToEventMapper.kt.kt:12:30 Unresolved reference 'toIso'.
e: .../Converters_<hash>.kt:9:61 Unresolved reference 'toIso'.
Cause: the fixture declares a top-level @KraftConverter function without a package line, so it lands in the root package. Kotlin cannot import top-level declarations from the root package, so the generated mapper and delegate registry (which live in their own packages) cannot reference the converter.
Fix: give every fixture that declares a top-level @KraftConverter a package declaration:
val source = SourceFile.kotlin("Models.kt", """
package fixtures
@com.blu3berry.kraft.config.KraftConverter
fun Stamp.toIso(): String = epochMillis.toString()
...
""")
Fixtures without converters can stay package-less — nothing generated needs to import from them across packages.
Minimal reproduction: TypeAliasMappingTest.'converter declared with alias receiver...' with its package aliased line removed.