Kotlin KSP annotation Processor generated class not found during jupiter test

3.7k Views Asked by At

I'm developing an KSP annotation processor for Kotlin. The code is generated correctly during the compile time and I can see the generated classes in the output directory. Now I want to test my annotation processor via JUnit and "com.github.tschuchortdev.KotlinCompilation". If I call the compile method the code will be generated and I can see the generated class in Temp-Directory but if I try to load the class then I get a "java.lang.ClassNotFoundException: test.pack.TestClassDslBuilder" exception. I hope the code is self-explanatory. My question is: Why isn't the classes compiled and not loadable? Maybe there's a missing configuration of the kompiler.

    @BeforeEach
    fun setup() {
        val kotlinSource = SourceFile.kotlin(
            "TestClass.kt", """
                package test.pack

                import yy.xxx.dsl.builder.annotation.DslBuilder
                @DslBuilder        
                class TestClass {

                }
            """
        )

        val compilation = KotlinCompilation().apply {
            sources = listOf(kotlinSource)
            symbolProcessorProviders = listOf(DslBuilderProcessorProvider())
            //workingDir =
            inheritClassPath = true
            verbose = false
            //messageOutputStream = System.out
            kspIncremental = true
        }
        compilationResult = compilation.compile()
        assertEquals(KotlinCompilation.ExitCode.OK, compilationResult.exitCode)

        // The next line leads to java.lang.ClassNotFoundException 
        compilationResult.classLoader.loadClass("test.pack.TestClassDslBuilder")
    }
2

There are 2 best solutions below

1
On

I have the same issue. Didn't fix it, but it seems to me, that problem is in path of generated file (which you wrote like string), like in google compile testing it was important to use StandardLocation.SOURCE_OUTPUT, when you try to find generatedFile(). But in my case, it doesn't generate anything - I mean generatedFiles.size() == 0 in KotlinCompilation.Result

0
On

This lib is not compatible with KSP fully yet. There is a hack to make it work with double compilation: https://github.com/tschuchortdev/kotlin-compile-testing/issues/72#issuecomment-744475289

fun compile(tempDir: File, compilation: KotlinCompilation): KotlinCompilation.Result {
    val pass1 = compilation.compile()
    require(pass1.exitCode == KotlinCompilation.ExitCode.OK) {
        "Cannot do the 1st pass \n ${pass1.messages}"
    }
    val pass2 = KotlinCompilation().apply {
        sources = compilation.kspGeneratedSourceFiles(tempDir) + compilation.sources
        inheritClassPath = true
    }.compile()
    require(pass2.exitCode == KotlinCompilation.ExitCode.OK) {
        "Cannot do the 2nd pass \n ${pass2.messages}"
    }
    return pass2
}

private fun KotlinCompilation.kspGeneratedSourceFiles(tempDir: File): List<SourceFile> =
    kspGeneratedSources(tempDir)
        .filter { it.isFile }
        .map { SourceFile.fromPath(it.absoluteFile) }
        .toList()
}

private fun kspGeneratedSources(tempDir: File): List<File> {
    val kspWorkingDir = tempDir.resolve("ksp")
    val kspGeneratedDir = kspWorkingDir.resolve("sources")
    val kotlinGeneratedDir = kspGeneratedDir.resolve("kotlin")
    val javaGeneratedDir = kspGeneratedDir.resolve("java")
    return kotlinGeneratedDir.walkTopDown().toList() +
            javaGeneratedDir.walkTopDown()
}

And I can confirm that it works. This is just an example. Depending on your output directory, you might want to tinker with the file paths a bit, of course. If it still doesn't work, make sure that the second KotlinCompilation() is right for your case.