Kapt3: Support processor arguments. Add integration tests.

This commit is contained in:
Yan Zhulanow
2016-11-02 22:08:31 +03:00
committed by Yan Zhulanow
parent b54df7a945
commit 0914a65234
12 changed files with 287 additions and 111 deletions
@@ -137,7 +137,7 @@ import org.jetbrains.kotlin.jvm.compiler.*
import org.jetbrains.kotlin.jvm.runtime.AbstractJvm8RuntimeDescriptorLoaderTest
import org.jetbrains.kotlin.jvm.runtime.AbstractJvmRuntimeDescriptorLoaderTest
import org.jetbrains.kotlin.kapt3.test.AbstractClassFileToSourceStubConverterTest
import org.jetbrains.kotlin.kapt3.test.AbstractKotlinKaptRunnerTest
import org.jetbrains.kotlin.kapt3.test.AbstractKotlinKaptContextTest
import org.jetbrains.kotlin.kdoc.AbstractKDocLexerTest
import org.jetbrains.kotlin.lang.resolve.android.test.AbstractAndroidBoxTest
import org.jetbrains.kotlin.lang.resolve.android.test.AbstractAndroidBytecodeShapeTest
@@ -1144,7 +1144,7 @@ fun main(args: Array<String>) {
model("converter")
}
testClass<AbstractKotlinKaptRunnerTest> {
testClass<AbstractKotlinKaptContextTest> {
model("kotlinRunner")
}
}
@@ -44,13 +44,13 @@ class ClasspathBasedKapt3Extension(
javaSourceRoots: List<File>,
sourcesOutputDir: File,
classFilesOutputDir: File,
stubsOutputDir: File?,
val stubsOutputDir: File?,
options: Map<String, String>,
aptOnly: Boolean,
pluginInitializedTime: Long,
logger: KaptLogger
) : AbstractKapt3Extension(annotationProcessingClasspath, javaSourceRoots, sourcesOutputDir,
classFilesOutputDir, stubsOutputDir, options, aptOnly, pluginInitializedTime, logger) {
classFilesOutputDir, options, aptOnly, pluginInitializedTime, logger) {
override fun loadProcessors(): List<Processor> {
val classLoader = URLClassLoader(annotationProcessingClasspath.map { it.toURI().toURL() }.toTypedArray())
val processors = ServiceLoader.load(Processor::class.java, classLoader).toList()
@@ -63,6 +63,18 @@ class ClasspathBasedKapt3Extension(
return processors
}
override fun saveStubs(stubs: JavacList<JCCompilationUnit>) {
val outputDir = stubsOutputDir ?: return
for (stub in stubs) {
val className = (stub.defs.first { it is JCTree.JCClassDecl } as JCTree.JCClassDecl).simpleName.toString()
val packageName = stub.packageName?.toString() ?: ""
val packageDir = if (packageName.isEmpty()) outputDir else File(outputDir, packageName.replace('.', '/'))
packageDir.mkdirs()
File(packageDir, className + ".java").writeText(stub.toString())
}
}
}
abstract class AbstractKapt3Extension(
@@ -70,8 +82,7 @@ abstract class AbstractKapt3Extension(
val javaSourceRoots: List<File>,
val sourcesOutputDir: File,
val classFilesOutputDir: File,
val stubsOutputDir: File?,
val options: Map<String, String>, //TODO
val options: Map<String, String>,
val aptOnly: Boolean,
val pluginInitializedTime: Long,
val logger: KaptLogger
@@ -167,7 +178,7 @@ abstract class AbstractKapt3Extension(
logger.info { "Stubs compilation took $classFilesCompilationTime ms" }
logger.info { "Compiled classes: " + compiledClasses.joinToString { it.name } }
return Pair(KaptContext(logger, compiledClasses, origins), generationState)
return Pair(KaptContext(logger, compiledClasses, origins, options), generationState)
}
private fun generateKotlinSourceStubs(kaptContext: KaptContext, typeMapper: KotlinTypeMapper): JavacList<JCCompilationUnit> {
@@ -178,24 +189,10 @@ abstract class AbstractKapt3Extension(
logger.info { "Java stub generation took $stubGenerationTime ms" }
logger.info { "Stubs for Kotlin classes: " + kotlinSourceStubs.joinToString { it.sourcefile.name } }
if (stubsOutputDir != null) {
saveStubs(stubsOutputDir, kotlinSourceStubs)
}
saveStubs(kotlinSourceStubs)
return kotlinSourceStubs
}
private fun saveStubs(outputDir: File, stubs: JavacList<JCTree.JCCompilationUnit>) {
for (stub in stubs) {
val className = (stub.defs.first { it is JCTree.JCClassDecl } as JCTree.JCClassDecl).simpleName.toString()
val packageName = stub.packageName?.toString() ?: ""
val packageDir = if (packageName.isEmpty()) outputDir else File(outputDir, packageName.replace('.', '/'))
packageDir.mkdirs()
File(packageDir, className + ".java").writeText(stub.toString())
}
}
private fun collectJavaSourceFiles(): List<File> {
val javaFilesFromJavaSourceRoots = javaSourceRoots.flatMap {
root -> root.walk().filter { it.isFile && it.extension == "java" }.toList()
@@ -205,6 +202,8 @@ abstract class AbstractKapt3Extension(
return javaFilesFromJavaSourceRoots
}
protected abstract fun saveStubs(stubs: JavacList<JCTree.JCCompilationUnit>)
protected abstract fun loadProcessors(): List<Processor>
}
@@ -122,8 +122,8 @@ class Kapt3ComponentRegistrar : ComponentRegistrar {
val apOptions = (configuration.get(APT_OPTIONS) ?: listOf())
.map { it.split(':') }
.filter { it.size == 2 }
.map { it[0] to it[1] }
.filter { it.isNotEmpty() && it.size <= 2 }
.map { it[0] to it.getOrElse(1) { "" } }
.toMap()
sourcesOutputDir.mkdirs()
@@ -30,7 +30,8 @@ import javax.tools.JavaFileManager
class KaptContext(
val logger: KaptLogger,
val compiledClasses: List<ClassNode>,
val origins: Map<Any, JvmDeclarationOrigin>
val origins: Map<Any, JvmDeclarationOrigin>,
processorOptions: Map<String, String>
) {
val context = Context()
val compiler: KaptJavaCompiler
@@ -46,6 +47,10 @@ class KaptContext(
compiler = JavaCompiler.instance(context) as KaptJavaCompiler
options = Options.instance(context)
for ((key, value) in processorOptions) {
val option = if (value.isEmpty()) "-A$key" else "-A$key=$value"
options.put(option, option) // key == value: it's intentional
}
}
fun close() {
@@ -0,0 +1,160 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.kapt3.test
import com.intellij.openapi.util.text.StringUtil
import com.sun.tools.javac.tree.JCTree
import org.jetbrains.kotlin.codegen.CodegenTestCase
import org.jetbrains.kotlin.codegen.CodegenTestUtil
import org.jetbrains.kotlin.kapt3.AbstractKapt3Extension
import org.jetbrains.kotlin.kapt3.Kapt3BuilderFactory
import org.jetbrains.kotlin.kapt3.util.KaptLogger
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.util.trimTrailingWhitespacesAndAddNewlineAtEOF
import java.io.File
import java.nio.file.Files
import javax.annotation.processing.Completion
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.Processor
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.AnnotationMirror
import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement
import com.sun.tools.javac.util.List as JavacList
abstract class AbstractKotlinKapt3IntegrationTest : CodegenTestCase() {
private companion object {
val TEST_DATA_DIR = File("plugins/kapt3/testData/kotlinRunner")
}
private lateinit var processors: List<Processor>
private lateinit var options: Map<String, String>
protected fun test(
name: String,
vararg supportedAnnotations: String,
options: Map<String, String> = emptyMap(),
process: (Set<TypeElement>, RoundEnvironment, ProcessingEnvironment) -> Unit
) = testAP(true, name, options, process, *supportedAnnotations)
protected fun testShouldNotRun(
name: String,
vararg supportedAnnotations: String,
options: Map<String, String> = emptyMap()
) = testAP(false, name, options, { set, roundEnv, env -> fail("Should not run") }, *supportedAnnotations)
protected fun testAP(
shouldRun: Boolean,
name: String,
options: Map<String, String>,
process: (Set<TypeElement>, RoundEnvironment, ProcessingEnvironment) -> Unit,
vararg supportedAnnotations: String
) {
this.options = options
val ktFileName = File(TEST_DATA_DIR, name + ".kt")
var started = false
val processor = object : Processor {
lateinit var processingEnv: ProcessingEnvironment
override fun getSupportedOptions() = options.keys
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
if (!roundEnv.processingOver()) {
started = true
process(annotations, roundEnv, processingEnv)
}
return true
}
override fun init(env: ProcessingEnvironment) {
processingEnv = env
}
override fun getCompletions(
element: Element?,
annotation: AnnotationMirror?,
member: ExecutableElement?,
userText: String?
): Iterable<Completion>? {
return emptyList()
}
override fun getSupportedSourceVersion() = SourceVersion.RELEASE_6
override fun getSupportedAnnotationTypes() = supportedAnnotations.toSet()
}
processors = listOf(processor)
doTest(ktFileName.canonicalPath)
if (started != shouldRun) {
fail("Annotation processor " + (if (shouldRun) "was not started" else "was started"))
}
}
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
val javaSources = javaFilesDir?.let { arrayOf(it) } ?: emptyArray()
val txtFile = File(wholeFile.parentFile, wholeFile.nameWithoutExtension + ".it.txt")
val sourceOutputDir = Files.createTempDirectory("kaptRunner").toFile()
createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.ALL, *javaSources)
val kapt3Extension = Kapt3ExtensionForTests(processors, javaSources.toList(), sourceOutputDir, this.options)
AnalysisHandlerExtension.registerExtension(myEnvironment.project, kapt3Extension)
try {
loadMultiFiles(files)
val classBuilderFactory = Kapt3BuilderFactory()
CodegenTestUtil.generateFiles(myEnvironment, myFiles, classBuilderFactory)
val actualRaw = kapt3Extension.savedStubs ?: error("Stubs were not saved")
val actual = StringUtil.convertLineSeparators(actualRaw.trim({ it <= ' ' })).trimTrailingWhitespacesAndAddNewlineAtEOF()
KotlinTestUtils.assertEqualsToFile(txtFile, actual)
} finally {
sourceOutputDir.deleteRecursively()
}
}
private class Kapt3ExtensionForTests(
private val processors: List<Processor>,
javaSourceRoots: List<File>,
outputDir: File,
options: Map<String, String>
) : AbstractKapt3Extension(emptyList(), javaSourceRoots, outputDir, outputDir,
options, true, System.currentTimeMillis(), KaptLogger(true)
) {
internal var savedStubs: String? = null
override fun loadProcessors() = processors
override fun saveStubs(stubs: JavacList<JCTree.JCCompilationUnit>) {
if (this.savedStubs != null) {
error("Stubs are already saved")
}
this.savedStubs = stubs
.map { it.toString() }
.sortedBy(String::hashCode)
.joinToString(AbstractKotlinKapt3Test.FILE_SEPARATOR)
}
}
}
@@ -56,7 +56,8 @@ abstract class AbstractKotlinKapt3Test : CodegenTestCase() {
val typeMapper = factory.generationState.typeMapper
val logger = KaptLogger(isVerbose = true)
val kaptContext = KaptContext(logger, classBuilderFactory.compiledClasses, classBuilderFactory.origins)
val kaptContext = KaptContext(logger, classBuilderFactory.compiledClasses,
classBuilderFactory.origins, processorOptions = emptyMap())
try {
check(kaptContext, typeMapper, txtFile)
} finally {
@@ -91,12 +92,12 @@ abstract class AbstractClassFileToSourceStubConverterTest : AbstractKotlinKapt3T
}
}
abstract class AbstractKotlinKaptRunnerTest : AbstractKotlinKapt3Test() {
abstract class AbstractKotlinKaptContextTest : AbstractKotlinKapt3Test() {
override fun check(kaptRunner: KaptContext, typeMapper: KotlinTypeMapper, txtFile: File) {
val compilationUnits = convert(kaptRunner, typeMapper)
val sourceOutputDir = Files.createTempDirectory("kaptRunner").toFile()
try {
kaptRunner.doAnnotationProcessing(emptyList(), listOf(KaptRunnerTest.simpleProcessor()),
kaptRunner.doAnnotationProcessing(emptyList(), listOf(JavaKaptContextTest.simpleProcessor()),
classpath = listOf(), sourcesOutputDir = sourceOutputDir, classesOutputDir = sourceOutputDir,
additionalSources = compilationUnits)
@@ -1,76 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.kapt3.test
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.codegen.CodegenTestCase
import org.jetbrains.kotlin.codegen.CodegenTestUtil
import org.jetbrains.kotlin.kapt3.AbstractKapt3Extension
import org.jetbrains.kotlin.kapt3.Kapt3BuilderFactory
import org.jetbrains.kotlin.kapt3.util.KaptLogger
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.util.trimTrailingWhitespacesAndAddNewlineAtEOF
import org.junit.Test
import java.io.File
import java.nio.file.Files
import javax.annotation.processing.Processor
class KotlinKapt3TestWithExtension : CodegenTestCase() {
class Kapt3ExtensionForTests(
private val processors: List<Processor>,
javaSourceRoots: List<File>,
outputDir: File
) : AbstractKapt3Extension(emptyList(), javaSourceRoots, outputDir, outputDir,
null, emptyMap(), true, System.currentTimeMillis(), KaptLogger(true)) {
override fun loadProcessors() = processors
}
private fun getProcessor(): Processor = KaptRunnerTest.simpleProcessor()
@Test
fun testSimple() {
doTest("plugins/kapt3/testData/kotlinRunner/Simple.kt")
}
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
val javaSources = javaFilesDir?.let { arrayOf(it) } ?: emptyArray()
val txtFile = File(wholeFile.parentFile, wholeFile.nameWithoutExtension + ".ext.txt")
val sourceOutputDir = Files.createTempDirectory("kaptRunner").toFile()
createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.ALL, *javaSources)
val kapt3Extension = Kapt3ExtensionForTests(listOf(getProcessor()), javaSources.toList(), sourceOutputDir)
AnalysisHandlerExtension.registerExtension(myEnvironment.project, kapt3Extension)
try {
loadMultiFiles(files)
val classBuilderFactory = Kapt3BuilderFactory()
CodegenTestUtil.generateFiles(myEnvironment, myFiles, classBuilderFactory)
val javaFiles = sourceOutputDir.walkTopDown().filter { it.isFile && it.extension == "java" }
val actualRaw = javaFiles.sortedBy { it.name }.joinToString(AbstractKotlinKapt3Test.FILE_SEPARATOR) { it.name + ":\n\n" + it.readText() }
val actual = StringUtil.convertLineSeparators(actualRaw.trim({ it <= ' ' })).trimTrailingWhitespacesAndAddNewlineAtEOF()
KotlinTestUtils.assertEqualsToFile(txtFile, actual)
} finally {
sourceOutputDir.deleteRecursively()
}
}
}
@@ -45,7 +45,7 @@ import javax.lang.model.element.TypeElement
* limitations under the License.
*/
class KaptRunnerTest {
class JavaKaptContextTest {
companion object {
private val TEST_DATA_DIR = File("plugins/kapt3/testData/runner")
@@ -75,7 +75,11 @@ class KaptRunnerTest {
}
private fun doAnnotationProcessing(javaSourceFile: File, processor: Processor, outputDir: File) {
KaptContext(KaptLogger(isVerbose = true), emptyList(), emptyMap()).doAnnotationProcessing(
KaptContext(KaptLogger(isVerbose = true),
compiledClasses = emptyList(),
origins = emptyMap(),
processorOptions = emptyMap()
).doAnnotationProcessing(
listOf(javaSourceFile),
listOf(processor),
emptyList(), // classpath
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.kapt3.test
import org.junit.Test
class KotlinKapt3IntegrationTests : AbstractKotlinKapt3IntegrationTest() {
@Test
fun testSimple() = test("Simple", "test.MyAnnotation") { set, roundEnv, env ->
assertEquals(1, set.size)
val annotatedElements = roundEnv.getElementsAnnotatedWith(set.single())
assertEquals(1, annotatedElements.size)
assertEquals("myMethod", annotatedElements.single().simpleName.toString())
}
@Test
fun testOptions() = test(
"Simple", "test.MyAnnotation",
options = mapOf("firstKey" to "firstValue", "secondKey" to "")
) { set, roundEnv, env ->
val options = env.options
assertEquals("firstValue", options["firstKey"])
assertTrue("secondKey" in options)
}
}
@@ -30,7 +30,7 @@ import java.util.regex.Pattern;
@TestMetadata("plugins/kapt3/testData/kotlinRunner")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class KotlinKaptRunnerTestGenerated extends AbstractKotlinKaptRunnerTest {
public class KotlinKaptContextTestGenerated extends AbstractKotlinKaptContextTest {
public void testAllFilesPresentInKotlinRunner() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("plugins/kapt3/testData/kotlinRunner"), Pattern.compile("^(.+)\\.kt$"), true);
}
-4
View File
@@ -1,4 +0,0 @@
MyMethodMyAnnotation.java:
package generated;
class MyMethodMyAnnotation {}
+48
View File
@@ -0,0 +1,48 @@
package test;
public enum EnumClass2 {
/*public static final*/ WHITE /* = null */,
/*public static final*/ RED /* = null */;
private final java.lang.String blah = null;
EnumClass2(java.lang.String blah) {
}
}
////////////////////
package test;
public abstract @interface MyAnnotation {
}
////////////////////
package test;
public enum EnumClass {
/*public static final*/ BLACK /* = null */,
/*public static final*/ WHITE /* = null */;
EnumClass() {
}
}
////////////////////
package test;
public final class Simple {
@MyAnnotation()
public final void myMethod() {
}
public final int heavyMethod() {
return 0;
}
public Simple() {
super();
}
}