From 4a4cfa8b0385f8f5a9078c983f8d2dce7debcf27 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Tue, 1 Aug 2023 22:23:08 -0800 Subject: [PATCH 1/4] Use fun interface --- .../AbstractTestSymbolProcessor.kt | 13 ------------- .../com/tschuchort/compiletesting/KspTest.kt | 18 +++++++++--------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/ksp/src/test/kotlin/com/tschuchort/compiletesting/AbstractTestSymbolProcessor.kt b/ksp/src/test/kotlin/com/tschuchort/compiletesting/AbstractTestSymbolProcessor.kt index 73c7a8b4..348957c7 100644 --- a/ksp/src/test/kotlin/com/tschuchort/compiletesting/AbstractTestSymbolProcessor.kt +++ b/ksp/src/test/kotlin/com/tschuchort/compiletesting/AbstractTestSymbolProcessor.kt @@ -12,17 +12,4 @@ internal open class AbstractTestSymbolProcessor( override fun process(resolver: Resolver): List { return emptyList() } -} - -// Would be nice if SymbolProcessorProvider was a fun interface -internal fun processorProviderOf( - body: (environment: SymbolProcessorEnvironment) -> SymbolProcessor -): SymbolProcessorProvider { - return object : SymbolProcessorProvider { - override fun create( - environment: SymbolProcessorEnvironment - ): SymbolProcessor { - return body(environment) - } - } } \ No newline at end of file diff --git a/ksp/src/test/kotlin/com/tschuchort/compiletesting/KspTest.kt b/ksp/src/test/kotlin/com/tschuchort/compiletesting/KspTest.kt index 8b1cbd3a..78a0be95 100644 --- a/ksp/src/test/kotlin/com/tschuchort/compiletesting/KspTest.kt +++ b/ksp/src/test/kotlin/com/tschuchort/compiletesting/KspTest.kt @@ -95,7 +95,7 @@ class KspTest { ) val result = KotlinCompilation().apply { sources = listOf(annotation, targetClass) - symbolProcessorProviders = listOf(processorProviderOf { env -> + symbolProcessorProviders = listOf(SymbolProcessorProvider { env -> object : AbstractTestSymbolProcessor(env.codeGenerator) { override fun process(resolver: Resolver): List { val symbols = resolver.getSymbolsWithAnnotation("foo.bar.TestAnnotation").toList() @@ -140,10 +140,10 @@ class KspTest { val result = KotlinCompilation().apply { sources = listOf(source) symbolProcessorProviders = listOf( - processorProviderOf { env -> ClassGeneratingProcessor(env.codeGenerator, "generated", "A") }, - processorProviderOf { env -> ClassGeneratingProcessor(env.codeGenerator, "generated", "B") }) + SymbolProcessorProvider { env -> ClassGeneratingProcessor(env.codeGenerator, "generated", "A") }, + SymbolProcessorProvider { env -> ClassGeneratingProcessor(env.codeGenerator, "generated", "B") }) symbolProcessorProviders = symbolProcessorProviders + - processorProviderOf { env -> ClassGeneratingProcessor(env.codeGenerator, "generated", "C") } + SymbolProcessorProvider { env -> ClassGeneratingProcessor(env.codeGenerator, "generated", "C") } }.compile() assertThat(result.exitCode).isEqualTo(ExitCode.OK) } @@ -179,7 +179,7 @@ class KspTest { fun outputDirectoryContents() { val compilation = KotlinCompilation().apply { sources = listOf(DUMMY_KOTLIN_SRC) - symbolProcessorProviders = listOf(processorProviderOf { env -> + symbolProcessorProviders = listOf(SymbolProcessorProvider { env -> ClassGeneratingProcessor(env.codeGenerator, "generated", "Gen") }) } @@ -212,7 +212,7 @@ class KspTest { val result = mutableListOf() val compilation = KotlinCompilation().apply { sources = listOf(javaSource, kotlinSource) - symbolProcessorProviders += processorProviderOf { env -> + symbolProcessorProviders += SymbolProcessorProvider { env -> object : AbstractTestSymbolProcessor(env.codeGenerator) { override fun process(resolver: Resolver): List { resolver.getSymbolsWithAnnotation( @@ -274,7 +274,7 @@ class KspTest { ) val result = KotlinCompilation().apply { sources = listOf(annotation, targetClass) - symbolProcessorProviders = listOf(processorProviderOf { env -> + symbolProcessorProviders = listOf(SymbolProcessorProvider { env -> object : AbstractTestSymbolProcessor(env.codeGenerator) { override fun process(resolver: Resolver): List { env.logger.logging("This is a log message") @@ -308,7 +308,7 @@ class KspTest { ) val result = KotlinCompilation().apply { sources = listOf(annotation, targetClass) - symbolProcessorProviders = listOf(processorProviderOf { env -> + symbolProcessorProviders = listOf(SymbolProcessorProvider { env -> object : AbstractTestSymbolProcessor(env.codeGenerator) { override fun process(resolver: Resolver): List { env.logger.error("This is an error message") @@ -340,7 +340,7 @@ class KspTest { ) val result = KotlinCompilation().apply { sources = listOf(annotation, targetClass) - symbolProcessorProviders = listOf(processorProviderOf { env -> + symbolProcessorProviders = listOf(SymbolProcessorProvider { env -> object : AbstractTestSymbolProcessor(env.codeGenerator) { override fun process(resolver: Resolver): List { env.logger.logging("This is a log message with ellipsis $ellipsis") From e34122f17caf9ea7d46f1e3c083a67910b0d0220 Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Tue, 1 Aug 2023 22:23:52 -0800 Subject: [PATCH 2/4] Add API for extra generated sources --- .../tschuchort/compiletesting/KotlinCompilation.kt | 14 ++++++++++++-- .../kotlin/com/tschuchort/compiletesting/Ksp.kt | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt b/core/src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt index aa203e45..3529d7ff 100644 --- a/core/src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt +++ b/core/src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt @@ -256,6 +256,13 @@ class KotlinCompilation : AbstractKotlinCompilation() { val kaptStubsDir get() = kaptBaseDir.resolve("stubs") val kaptIncrementalDataDir get() = kaptBaseDir.resolve("incrementalData") + private val extraGeneratedSources = mutableListOf() + + /** Registers extra directories with generated sources, such as sources generated by KSP. */ + fun registerGeneratedSourcesDir(dir: File) { + extraGeneratedSources.add(dir) + } + /** ExitCode of the entire Kotlin compilation process */ enum class ExitCode { OK, INTERNAL_ERROR, COMPILATION_ERROR, SCRIPT_EXECUTION_ERROR @@ -473,8 +480,11 @@ class KotlinCompilation : AbstractKotlinCompilation() { /** Performs the 4th compilation step to compile Java source files */ private fun compileJava(sourceFiles: List): ExitCode { - val javaSources = (sourceFiles + kaptSourceDir.listFilesRecursively()) - .filterNot(File::hasKotlinFileExtension) + val javaSources = sourceFiles + .plus(kaptSourceDir.listFilesRecursively()) + .plus(extraGeneratedSources.flatMap(File::listFilesRecursively)) + .distinct() + .filterNot(File::hasKotlinFileExtension) if(javaSources.isEmpty()) return ExitCode.OK diff --git a/ksp/src/main/kotlin/com/tschuchort/compiletesting/Ksp.kt b/ksp/src/main/kotlin/com/tschuchort/compiletesting/Ksp.kt index e225d8a4..05f60838 100644 --- a/ksp/src/main/kotlin/com/tschuchort/compiletesting/Ksp.kt +++ b/ksp/src/main/kotlin/com/tschuchort/compiletesting/Ksp.kt @@ -198,6 +198,7 @@ private class KspCompileTestingComponentRegistrar( this.javaOutputDir = compilation.kspJavaSourceDir.also { it.deleteRecursively() it.mkdirs() + compilation.registerGeneratedSourcesDir(it) } this.kotlinOutputDir = compilation.kspKotlinSourceDir.also { it.deleteRecursively() @@ -244,6 +245,6 @@ private fun KotlinCompilation.getKspRegistrar(): KspCompileTestingComponentRegis return it } val kspRegistrar = KspCompileTestingComponentRegistrar(this) - componentRegistrars = componentRegistrars + kspRegistrar + componentRegistrars += kspRegistrar return kspRegistrar } From 58939d7bcd50dc21d6be8fd3b603adf88eaccb1b Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Tue, 1 Aug 2023 23:30:42 -0800 Subject: [PATCH 3/4] Fix missing info in diagnostic printing --- .../com/tschuchort/compiletesting/KotlinCompilation.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt b/core/src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt index 3529d7ff..d6faab27 100644 --- a/core/src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt +++ b/core/src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt @@ -542,11 +542,12 @@ class KotlinCompilation : AbstractKotlinCompilation() { val diagnosticCollector = DiagnosticCollector() fun printDiagnostics() = diagnosticCollector.diagnostics.forEach { diag -> + // Print toString() for these to get the full error message when(diag.kind) { - Diagnostic.Kind.ERROR -> error(diag.getMessage(null)) + Diagnostic.Kind.ERROR -> error(diag.toString()) Diagnostic.Kind.WARNING, - Diagnostic.Kind.MANDATORY_WARNING -> warn(diag.getMessage(null)) - else -> log(diag.getMessage(null)) + Diagnostic.Kind.MANDATORY_WARNING -> warn(diag.toString()) + else -> log(diag.toString()) } } @@ -567,7 +568,7 @@ class KotlinCompilation : AbstractKotlinCompilation() { ExitCode.COMPILATION_ERROR } catch (e: Exception) { - if(e is RuntimeException || e is IllegalArgumentException) { + if (e is RuntimeException) { printDiagnostics() error(e.toString()) return ExitCode.INTERNAL_ERROR From cbd4ea067ed8a34d111235e6f3962470eee8ae5a Mon Sep 17 00:00:00 2001 From: Zac Sweers Date: Tue, 1 Aug 2023 23:34:21 -0800 Subject: [PATCH 4/4] Add a test --- .../com/tschuchort/compiletesting/KspTest.kt | 104 ++++++++++++++++-- 1 file changed, 93 insertions(+), 11 deletions(-) diff --git a/ksp/src/test/kotlin/com/tschuchort/compiletesting/KspTest.kt b/ksp/src/test/kotlin/com/tschuchort/compiletesting/KspTest.kt index 78a0be95..43a5dbe1 100644 --- a/ksp/src/test/kotlin/com/tschuchort/compiletesting/KspTest.kt +++ b/ksp/src/test/kotlin/com/tschuchort/compiletesting/KspTest.kt @@ -1,20 +1,24 @@ package com.tschuchort.compiletesting -import com.google.devtools.ksp.processing.* +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorProvider import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.inOrder import com.nhaarman.mockitokotlin2.mock import com.tschuchort.compiletesting.KotlinCompilation.ExitCode +import java.util.Locale +import java.util.concurrent.atomic.AtomicInteger +import kotlin.text.Typography.ellipsis import org.assertj.core.api.Assertions.assertThat -import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Mockito.`when` -import java.util.concurrent.atomic.AtomicInteger -import kotlin.text.Typography.ellipsis @RunWith(JUnit4::class) class KspTest { @@ -99,7 +103,7 @@ class KspTest { object : AbstractTestSymbolProcessor(env.codeGenerator) { override fun process(resolver: Resolver): List { val symbols = resolver.getSymbolsWithAnnotation("foo.bar.TestAnnotation").toList() - if (symbols.isNotEmpty()) { + if (symbols.isNotEmpty()) { assertThat(symbols.size).isEqualTo(1) val klass = symbols.first() check(klass is KSClassDeclaration) @@ -110,11 +114,13 @@ class KspTest { dependencies = Dependencies.ALL_FILES, packageName = genPackage, fileName = genClassName - ).bufferedWriter(Charsets.UTF_8).use { - it.write(""" + ).bufferedWriter().use { + it.write( + """ package $genPackage class $genClassName() {} - """.trimIndent()) + """.trimIndent() + ) } } return emptyList() @@ -246,11 +252,13 @@ class KspTest { dependencies = Dependencies.ALL_FILES, packageName = packageName, fileName = className - ).bufferedWriter(Charsets.UTF_8).use { - it.write(""" + ).bufferedWriter().use { + it.write( + """ package $packageName class $className() {} - """.trimIndent()) + """.trimIndent() + ) } } return emptyList() @@ -357,6 +365,80 @@ class KspTest { assertThat(result.messages).contains("This is an warn message with emoji 🔥") } + // This test exercises both using withCompilation (for in-process compilation of generated sources) + // and generating Java sources (to ensure generated java files are compiled too) + @Test + fun withCompilationAndJavaTest() { + val annotation = SourceFile.kotlin( + "TestAnnotation.kt", """ + package foo.bar + annotation class TestAnnotation + """.trimIndent() + ) + val targetClass = SourceFile.kotlin( + "AppCode.kt", """ + package foo.bar + @TestAnnotation + class AppCode + """.trimIndent() + ) + val compilation = KotlinCompilation() + val result = compilation.apply { + sources = listOf(annotation, targetClass) + symbolProcessorProviders = listOf(SymbolProcessorProvider { env -> + object : AbstractTestSymbolProcessor(env.codeGenerator) { + override fun process(resolver: Resolver): List { + resolver.getSymbolsWithAnnotation("foo.bar.TestAnnotation") + .forEach { symbol -> + check(symbol is KSClassDeclaration) { "Expected class declaration" } + @Suppress("DEPRECATION") + val simpleName = "${symbol.simpleName.asString().capitalize(Locale.US)}Dummy" + env.codeGenerator.createNewFile( + dependencies = Dependencies.ALL_FILES, + packageName = "foo.bar", + fileName = simpleName, + extensionName = "java" + ).bufferedWriter().use { + //language=JAVA + it.write( + """ + package foo.bar; + + class ${simpleName}Java { + + } + """.trimIndent() + ) + } + env.codeGenerator.createNewFile( + dependencies = Dependencies.ALL_FILES, + packageName = "foo.bar", + fileName = "${simpleName}Kt", + extensionName = "kt" + ).bufferedWriter().use { + //language=KOTLIN + it.write( + """ + package foo.bar + + class ${simpleName}Kt { + + } + """.trimIndent() + ) + } + } + return emptyList() + } + } + }) + kspWithCompilation = true + }.compile() + assertThat(result.exitCode).isEqualTo(ExitCode.OK) + assertThat(result.classLoader.loadClass("foo.bar.AppCodeDummyJava")).isNotNull() + assertThat(result.classLoader.loadClass("foo.bar.AppCodeDummyKt")).isNotNull() + } + companion object { private val DUMMY_KOTLIN_SRC = SourceFile.kotlin( "foo.bar.Dummy.kt", """