| /* |
| * Copyright 2020 The Android Open Source Project |
| * |
| * 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 androidx.room.compiler.processing |
| |
| import androidx.room.compiler.processing.util.Source |
| import androidx.room.compiler.processing.util.XTestInvocation |
| import androidx.room.compiler.processing.util.compileFiles |
| import androidx.room.compiler.processing.util.getAllFieldNames |
| import androidx.room.compiler.processing.util.getField |
| import androidx.room.compiler.processing.util.getMethod |
| import androidx.room.compiler.processing.util.runKspTest |
| import androidx.room.compiler.processing.util.runProcessorTest |
| import com.google.common.truth.Truth.assertThat |
| import com.google.common.truth.Truth.assertWithMessage |
| import com.squareup.javapoet.ClassName |
| import com.squareup.javapoet.ParameterizedTypeName |
| import com.squareup.javapoet.TypeName |
| import com.squareup.javapoet.TypeVariableName |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| import org.junit.runners.JUnit4 |
| |
| @RunWith(JUnit4::class) |
| class XTypeElementTest { |
| @Test |
| fun qualifiedNames() { |
| val src1 = Source.kotlin( |
| "Foo.kt", |
| """ |
| class TopLevel |
| """.trimIndent() |
| ) |
| val src2 = Source.kotlin( |
| "Bar.kt", |
| """ |
| package foo.bar |
| class InFooBar |
| """.trimIndent() |
| ) |
| val src3 = Source.java( |
| "foo.bar.Outer", |
| """ |
| package foo.bar; |
| public class Outer { |
| public static class Nested { |
| } |
| } |
| """ |
| ) |
| runProcessorTest( |
| sources = listOf(src1, src2, src3) |
| ) { invocation -> |
| invocation.processingEnv.requireTypeElement("TopLevel").let { |
| assertThat(it.packageName).isEqualTo("") |
| assertThat(it.name).isEqualTo("TopLevel") |
| assertThat(it.qualifiedName).isEqualTo("TopLevel") |
| assertThat(it.className).isEqualTo(ClassName.get("", "TopLevel")) |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.InFooBar").let { |
| assertThat(it.packageName).isEqualTo("foo.bar") |
| assertThat(it.name).isEqualTo("InFooBar") |
| assertThat(it.qualifiedName).isEqualTo("foo.bar.InFooBar") |
| assertThat(it.className).isEqualTo(ClassName.get("foo.bar", "InFooBar")) |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.Outer").let { |
| assertThat(it.packageName).isEqualTo("foo.bar") |
| assertThat(it.name).isEqualTo("Outer") |
| assertThat(it.qualifiedName).isEqualTo("foo.bar.Outer") |
| assertThat(it.className).isEqualTo( |
| ClassName.get("foo.bar", "Outer") |
| ) |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.Outer.Nested").let { |
| assertThat(it.packageName).isEqualTo("foo.bar") |
| assertThat(it.name).isEqualTo("Nested") |
| assertThat(it.qualifiedName).isEqualTo("foo.bar.Outer.Nested") |
| assertThat(it.className).isEqualTo( |
| ClassName.get("foo.bar", "Outer", "Nested") |
| ) |
| } |
| if (invocation.isKsp) { |
| // these are KSP specific tests, typenames are tested elsewhere |
| invocation.processingEnv.requireTypeElement("java.lang.Integer").let { |
| // always return kotlin types, this is what compiler does |
| assertThat(it.packageName).isEqualTo("kotlin") |
| assertThat(it.name).isEqualTo("Int") |
| assertThat(it.qualifiedName).isEqualTo("kotlin.Int") |
| } |
| invocation.processingEnv.requireTypeElement("kotlin.Int").let { |
| assertThat(it.packageName).isEqualTo("kotlin") |
| assertThat(it.name).isEqualTo("Int") |
| assertThat(it.qualifiedName).isEqualTo("kotlin.Int") |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun typeAndSuperType() { |
| val src = Source.kotlin( |
| "foo.kt", |
| """ |
| package foo.bar; |
| class Baz : MyInterface, AbstractClass() { |
| } |
| abstract class AbstractClass {} |
| interface MyInterface {} |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| invocation.processingEnv.requireTypeElement("foo.bar.Baz").let { |
| assertThat(it.superType).isEqualTo( |
| invocation.processingEnv.requireType("foo.bar.AbstractClass") |
| ) |
| assertThat(it.type).isEqualTo( |
| invocation.processingEnv.requireType("foo.bar.Baz") |
| ) |
| assertThat(it.isInterface()).isFalse() |
| assertThat(it.isKotlinObject()).isFalse() |
| assertThat(it.isAbstract()).isFalse() |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.AbstractClass").let { |
| assertThat(it.superType).let { |
| // KSP does not return Object / Any as super class |
| if (invocation.isKsp) { |
| it.isNull() |
| } else { |
| it.isEqualTo( |
| invocation.processingEnv.requireType(TypeName.OBJECT) |
| ) |
| } |
| } |
| assertThat(it.isAbstract()).isTrue() |
| assertThat(it.isInterface()).isFalse() |
| assertThat(it.type).isEqualTo( |
| invocation.processingEnv.requireType("foo.bar.AbstractClass") |
| ) |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.MyInterface").let { |
| assertThat(it.superType).isNull() |
| assertThat(it.isInterface()).isTrue() |
| assertThat(it.type).isEqualTo( |
| invocation.processingEnv.requireType("foo.bar.MyInterface") |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun nestedClassName() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| package foo.bar; |
| class Outer { |
| class Inner |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| invocation.processingEnv.requireTypeElement("foo.bar.Outer").let { |
| assertThat(it.className).isEqualTo(ClassName.get("foo.bar", "Outer")) |
| assertThat(it.enclosingTypeElement).isNull() |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.Outer.Inner").let { |
| assertThat(it.className).isEqualTo(ClassName.get("foo.bar", "Outer", "Inner")) |
| assertThat(it.packageName).isEqualTo("foo.bar") |
| assertThat(it.name).isEqualTo("Inner") |
| assertThat(it.enclosingTypeElement).isEqualTo( |
| invocation.processingEnv.requireTypeElement("foo.bar.Outer") |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun modifiers() { |
| val kotlinSrc = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class OpenClass |
| abstract class AbstractClass |
| object MyObject |
| interface MyInterface |
| class Final |
| private class PrivateClass |
| class OuterKotlinClass { |
| inner class InnerKotlinClass |
| class NestedKotlinClass |
| } |
| annotation class KotlinAnnotation |
| data class DataClass(val foo: Int) |
| inline class InlineClass(val foo: Int) |
| fun interface FunInterface { |
| fun foo() |
| } |
| """.trimIndent() |
| ) |
| val javaSrc = Source.java( |
| "OuterJavaClass", |
| """ |
| public class OuterJavaClass { |
| public class InnerJavaClass {} |
| public static class NestedJavaClass {} |
| } |
| """.trimIndent() |
| ) |
| val javaAnnotationSrc = Source.java( |
| "JavaAnnotation", |
| """ |
| public @interface JavaAnnotation { |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(kotlinSrc, javaSrc, javaAnnotationSrc) |
| ) { invocation -> |
| fun getModifiers(element: XTypeElement): Set<String> { |
| val result = mutableSetOf<String>() |
| if (element.isAbstract()) result.add("abstract") |
| if (element.isFinal()) result.add("final") |
| if (element.isPrivate()) result.add("private") |
| if (element.isProtected()) result.add("protected") |
| if (element.isPublic()) result.add("public") |
| if (element.isKotlinObject()) result.add("object") |
| if (element.isCompanionObject()) result.add("companion") |
| if (element.isFunctionalInterface()) result.add("fun") |
| if (element.isClass()) result.add("class") |
| if (element.isDataClass()) result.add("data") |
| if (element.isValueClass()) result.add("value") |
| if (element.isExpect()) result.add("expect") |
| if (element.isInterface()) result.add("interface") |
| if (element.isStatic()) result.add("static") |
| if (element.isAnnotationClass()) result.add("annotation") |
| return result |
| } |
| |
| fun getModifiers(qName: String): Set<String> = getModifiers( |
| invocation.processingEnv |
| .requireTypeElement(qName) |
| ) |
| |
| assertThat(getModifiers("OpenClass")) |
| .containsExactly("public", "class") |
| assertThat(getModifiers("AbstractClass")) |
| .containsExactly("abstract", "public", "class") |
| assertThat(getModifiers("MyObject")) |
| .containsExactly("final", "public", "object") |
| assertThat(getModifiers("MyInterface")) |
| .containsExactly("abstract", "interface", "public") |
| assertThat(getModifiers("Final")) |
| .containsExactly("final", "public", "class") |
| assertThat(getModifiers("PrivateClass")) |
| .containsExactlyElementsIn( |
| if (invocation.isKsp) { |
| listOf("private", "final", "class") |
| } else { |
| // java does not support top level private classes. |
| listOf("final", "class") |
| } |
| ) |
| assertThat(getModifiers("OuterKotlinClass.InnerKotlinClass")) |
| .containsExactly("final", "public", "class") |
| assertThat(getModifiers("OuterKotlinClass.NestedKotlinClass")) |
| .containsExactly("final", "public", "static", "class") |
| assertThat(getModifiers("OuterJavaClass.InnerJavaClass")) |
| .containsExactly("public", "class") |
| assertThat(getModifiers("OuterJavaClass.NestedJavaClass")) |
| .containsExactly("public", "static", "class") |
| assertThat(getModifiers("JavaAnnotation")) |
| .containsExactly("abstract", "public", "annotation") |
| assertThat(getModifiers("KotlinAnnotation")).apply { |
| // KSP vs KAPT metadata have a difference in final vs abstract modifiers |
| // for annotation types. |
| if (invocation.isKsp) { |
| containsExactly("final", "public", "annotation") |
| } else { |
| containsExactly("abstract", "public", "annotation") |
| } |
| } |
| assertThat(getModifiers("DataClass")) |
| .containsExactly("public", "final", "class", "data") |
| assertThat(getModifiers("InlineClass")) |
| .containsExactly("public", "final", "class", "value") |
| assertThat(getModifiers("FunInterface")) |
| .containsExactly("public", "abstract", "interface", "fun") |
| } |
| } |
| |
| @Test |
| fun kindName() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| class MyClass |
| interface MyInterface |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| invocation.processingEnv.requireTypeElement("MyClass").let { |
| assertThat(it.kindName()).isEqualTo("class") |
| } |
| invocation.processingEnv.requireTypeElement("MyInterface").let { |
| assertThat(it.kindName()).isEqualTo("interface") |
| } |
| } |
| } |
| |
| @Test |
| fun fieldBasic() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class BaseClass<T>(val genericProp : T) { |
| fun baseMethod(input: T) {} |
| } |
| class SubClass(x : Int) : BaseClass<Int>(x) { |
| val subClassProp : String = "abc" |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val baseClass = invocation.processingEnv.requireTypeElement("BaseClass") |
| assertThat(baseClass.getAllFieldNames()).containsExactly("genericProp") |
| assertThat(baseClass.getDeclaredFields().map { it.name }) |
| .containsExactly("genericProp") |
| val subClass = invocation.processingEnv.requireTypeElement("SubClass") |
| assertThat(subClass.getAllFieldNames()).containsExactly("genericProp", "subClassProp") |
| assertThat(subClass.getDeclaredFields().map { it.name }) |
| .containsExactly("subClassProp") |
| |
| val baseMethod = baseClass.getMethod("baseMethod") |
| baseMethod.asMemberOf(subClass.type).let { methodType -> |
| val genericArg = methodType.parameterTypes.first() |
| assertThat(genericArg.typeName).isEqualTo(TypeName.INT.box()) |
| } |
| |
| baseClass.getField("genericProp").let { field -> |
| if (invocation.isKsp) { |
| // ksp replaces these with Any? |
| assertThat(field.type.typeName).isEqualTo(TypeName.OBJECT) |
| } else { |
| assertThat(field.type.typeName).isEqualTo(TypeVariableName.get("T")) |
| } |
| } |
| |
| subClass.getField("genericProp").let { field -> |
| // this is tricky because even though it is non-null it, it should still be boxed |
| assertThat(field.type.typeName).isEqualTo(TypeName.INT.box()) |
| } |
| } |
| } |
| |
| @Test |
| fun fieldsOverride() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class BaseClass( |
| open val value : List<Int> |
| ) |
| class SubClass( |
| override val value : MutableList<Int> |
| ) : BaseClass(value) |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val baseClass = invocation.processingEnv.requireTypeElement("BaseClass") |
| assertThat(baseClass.getAllFieldNames()).containsExactly("value") |
| val subClass = invocation.processingEnv.requireTypeElement("SubClass") |
| assertThat(subClass.getAllFieldNames()).containsExactly("value") |
| assertThat( |
| baseClass.getField("value").type.typeName |
| ).isEqualTo( |
| ParameterizedTypeName.get(List::class.java, Integer::class.java) |
| ) |
| assertThat( |
| subClass.getField("value").type.typeName |
| ).isEqualTo( |
| ParameterizedTypeName.get(List::class.java, Integer::class.java) |
| ) |
| } |
| } |
| |
| @Test |
| fun fieldsInInterfaces() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| interface MyInterface { |
| var x:Int |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val element = invocation.processingEnv.requireTypeElement("MyInterface") |
| assertThat(element.getAllFieldsIncludingPrivateSupers()).isEmpty() |
| element.getMethod("getX").let { |
| assertThat(it.isAbstract()).isTrue() |
| } |
| element.getMethod("setX").let { |
| assertThat(it.isAbstract()).isTrue() |
| } |
| } |
| } |
| |
| @Test |
| fun fieldsInAbstractClass() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| abstract class MyAbstractClass { |
| @JvmField |
| var jvmVar: Int = 0 |
| abstract var abstractVar: Int |
| var nonAbstractVar: Int = 0 |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val element = invocation.processingEnv.requireTypeElement("MyAbstractClass") |
| assertThat( |
| element.getAllFieldNames() |
| ).containsExactly( |
| "nonAbstractVar", "jvmVar" |
| ) |
| assertThat( |
| element.getDeclaredMethods().map { it.name } |
| ).containsExactly( |
| "getAbstractVar", "setAbstractVar", |
| "getNonAbstractVar", "setNonAbstractVar" |
| ) |
| element.getMethod("getAbstractVar").let { |
| assertThat(it.isAbstract()).isTrue() |
| } |
| element.getMethod("setAbstractVar").let { |
| assertThat(it.isAbstract()).isTrue() |
| } |
| |
| element.getMethod("getNonAbstractVar").let { |
| assertThat(it.isAbstract()).isFalse() |
| } |
| element.getMethod("setNonAbstractVar").let { |
| assertThat(it.isAbstract()).isFalse() |
| } |
| } |
| } |
| |
| @Test |
| fun propertyGettersSetters() { |
| val dependencyJavaSource = Source.java( |
| "DependencyJavaSubject.java", |
| """ |
| class DependencyJavaSubject { |
| int myField; |
| private int mutable; |
| int immutable; |
| int getMutable() {return 3;} |
| void setMutable(int x) {} |
| int getImmutable() {return 3;} |
| } |
| """.trimIndent() |
| ) |
| val dependencyKotlinSource = Source.kotlin( |
| "DependencyKotlinSubject.kt", |
| """ |
| class DependencyKotlinSubject { |
| private val myField = 0 |
| var mutable: Int = 0 |
| val immutable:Int = 0 |
| } |
| """.trimIndent() |
| ) |
| val dependency = compileFiles(listOf(dependencyJavaSource, dependencyKotlinSource)) |
| val javaSource = Source.java( |
| "JavaSubject.java", |
| """ |
| class JavaSubject { |
| int myField; |
| private int mutable; |
| int immutable; |
| int getMutable() {return 3;} |
| void setMutable(int x) {} |
| int getImmutable() {return 3;} |
| } |
| """.trimIndent() |
| ) |
| val kotlinSource = Source.kotlin( |
| "KotlinSubject.kt", |
| """ |
| class KotlinSubject { |
| private val myField = 0 |
| var mutable: Int = 0 |
| val immutable:Int = 0 |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| listOf(javaSource, kotlinSource), |
| classpath = dependency |
| ) { invocation -> |
| listOf( |
| "JavaSubject", "DependencyJavaSubject", |
| "KotlinSubject", "DependencyKotlinSubject" |
| ).map { |
| invocation.processingEnv.requireTypeElement(it) |
| }.forEach { subject -> |
| assertWithMessage(subject.qualifiedName) |
| .that( |
| subject.getDeclaredMethods().map { |
| it.name |
| } |
| ).containsExactly( |
| "getMutable", "setMutable", "getImmutable" |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun declaredAndInstanceMethods() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class Base(x:Int) { |
| open fun baseFun(): Int = TODO() |
| suspend fun suspendFun(): Int = TODO() |
| private fun privateBaseFun(): Int = TODO() |
| companion object { |
| @JvmStatic |
| fun staticBaseFun(): Int = TODO() |
| fun companionMethod(): Int = TODO() |
| } |
| } |
| open class SubClass : Base { |
| constructor(y:Int): super(y) { |
| } |
| constructor(x:Int, y:Int): super(y) { |
| } |
| override fun baseFun(): Int = TODO() |
| fun subFun(): Int = TODO() |
| private fun privateSubFun(): Int = TODO() |
| companion object { |
| @JvmStatic |
| fun staticFun(): Int = TODO() |
| } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val base = invocation.processingEnv.requireTypeElement("Base") |
| val objectMethodNames = invocation.objectMethodNames() |
| assertThat(base.getDeclaredMethods().names()).containsExactly( |
| "baseFun", "suspendFun", "privateBaseFun", "staticBaseFun" |
| ) |
| |
| val sub = invocation.processingEnv.requireTypeElement("SubClass") |
| assertThat(sub.getDeclaredMethods().names()).containsExactly( |
| "baseFun", "subFun", "privateSubFun", "staticFun" |
| ) |
| assertThat( |
| sub.getAllNonPrivateInstanceMethods().names() - objectMethodNames |
| ).containsExactly( |
| "baseFun", "suspendFun", "subFun" |
| ) |
| } |
| } |
| |
| @Test |
| fun allMethods() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class Base(x:Int) { |
| constructor(x:Int, y:Int): this(x) { |
| } |
| fun baseMethod(): Int = TODO() |
| open fun overriddenMethod(): Int = TODO() |
| private fun privateBaseMethod(): Int = TODO() |
| companion object { |
| @JvmStatic |
| private fun privateBaseCompanionMethod(): Int = TODO() |
| @JvmStatic |
| fun baseCompanionMethod(): Int = TODO() |
| } |
| } |
| interface MyInterface { |
| fun interfaceMethod(): Int = TODO() |
| } |
| class SubClass : Base, MyInterface { |
| constructor(x:Int): super(x) { |
| } |
| constructor(x:Int, y:Int): super(y) { |
| } |
| fun subMethod(): Int = TODO() |
| fun privateSubMethod(): Int = TODO() |
| override fun overriddenMethod(): Int = TODO() |
| override fun interfaceMethod(): Int = TODO() |
| companion object { |
| fun dontSeeThisOne(): Int = TODO() |
| @JvmStatic |
| fun subCompanionMethod(): Int = TODO() |
| } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val objectMethodNames = invocation.objectMethodNames() |
| val klass = invocation.processingEnv.requireTypeElement("SubClass") |
| assertThat( |
| klass.getAllMethods().names() - objectMethodNames |
| ).containsExactly( |
| "baseMethod", "overriddenMethod", "baseCompanionMethod", |
| "interfaceMethod", "subMethod", "privateSubMethod", "subCompanionMethod" |
| ) |
| } |
| } |
| |
| @Test |
| fun gettersSetters() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class JustGetter(val x:Int) { |
| private val invisible:Int = TODO() |
| private var invisibleMutable:Int = TODO() |
| } |
| class GetterSetter(var y:Int) : JustGetter(y) { |
| private val subInvisible:Int = TODO() |
| private var subInvisibleMutable:Int = TODO() |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val objectMethodNames = invocation.objectMethodNames() |
| invocation.processingEnv.requireTypeElement("JustGetter").let { base -> |
| assertThat(base.getDeclaredMethods().names()).containsExactly( |
| "getX" |
| ) |
| assertThat(base.getAllMethods().names() - objectMethodNames).containsExactly( |
| "getX" |
| ) |
| assertThat( |
| base.getAllNonPrivateInstanceMethods().names() - objectMethodNames |
| ).containsExactly( |
| "getX" |
| ) |
| } |
| invocation.processingEnv.requireTypeElement("GetterSetter").let { sub -> |
| assertThat(sub.getDeclaredMethods().names()).containsExactly( |
| "getY", "setY" |
| ) |
| assertThat(sub.getAllMethods().names() - objectMethodNames).containsExactly( |
| "getX", "getY", "setY" |
| ) |
| assertThat( |
| sub.getAllNonPrivateInstanceMethods().names() - objectMethodNames |
| ).containsExactly( |
| "getX", "getY", "setY" |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun gettersSetters_companion() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class CompanionSubject { |
| companion object { |
| @JvmStatic |
| var mutableStatic: String = "a" |
| @JvmStatic |
| val immutableStatic: String = "bar" |
| val companionProp: Int = 3 |
| } |
| } |
| class SubClass : CompanionSubject() |
| """.trimIndent() |
| ) |
| // KAPT is a bit aggressive in adding fields, specifically, it adds companionProp and |
| // Companion as static fields which are not really fields from room's perspective. |
| runKspTest(sources = listOf(src)) { invocation -> |
| val subject = invocation.processingEnv.requireTypeElement("CompanionSubject") |
| assertThat(subject.getAllFieldNames()).containsExactly( |
| "mutableStatic", "immutableStatic" |
| ) |
| assertThat(subject.getDeclaredMethods().names()).containsExactly( |
| "getMutableStatic", "setMutableStatic", "getImmutableStatic" |
| ) |
| assertThat(subject.getAllMethods().names()).containsExactly( |
| "getMutableStatic", "setMutableStatic", "getImmutableStatic" |
| ) |
| assertThat(subject.getAllNonPrivateInstanceMethods().names()).isEmpty() |
| val subClass = invocation.processingEnv.requireTypeElement("SubClass") |
| assertThat(subClass.getDeclaredMethods()).isEmpty() |
| assertThat(subClass.getAllMethods().names()).containsExactly( |
| "getMutableStatic", "setMutableStatic", "getImmutableStatic" |
| ) |
| } |
| } |
| |
| @Test |
| fun gettersSetters_interface() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| interface JustGetter { |
| val x:Int |
| } |
| interface GetterSetter : JustGetter { |
| var y:Int |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| invocation.processingEnv.requireTypeElement("JustGetter").let { base -> |
| assertThat(base.getDeclaredMethods().names()).containsExactly( |
| "getX" |
| ) |
| assertThat(base.getAllMethods().names()).containsExactly( |
| "getX" |
| ) |
| assertThat(base.getAllNonPrivateInstanceMethods().names()).containsExactly( |
| "getX" |
| ) |
| } |
| invocation.processingEnv.requireTypeElement("GetterSetter").let { sub -> |
| assertThat(sub.getDeclaredMethods().names()).containsExactly( |
| "getY", "setY" |
| ) |
| assertThat(sub.getAllMethods().names()).containsExactly( |
| "getX", "getY", "setY" |
| ) |
| assertThat(sub.getAllNonPrivateInstanceMethods().names()).containsExactly( |
| "getX", "getY", "setY" |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun constructors() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| interface MyInterface |
| class NoExplicitConstructor |
| open class Base(x:Int) |
| open class ExplicitConstructor { |
| constructor(x:Int) |
| } |
| open class BaseWithSecondary(x:Int) { |
| constructor(y:String):this(3) |
| } |
| class Sub(x:Int) : Base(x) |
| class SubWith3Constructors() : BaseWithSecondary("abc") { |
| constructor(list:List<String>): this() |
| constructor(list:List<String>, x:Int): this() |
| } |
| abstract class AbstractNoExplicit |
| abstract class AbstractExplicit(x:Int) |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val subjects = listOf( |
| "MyInterface", "NoExplicitConstructor", "Base", "ExplicitConstructor", |
| "BaseWithSecondary", "Sub", "SubWith3Constructors", |
| "AbstractNoExplicit", "AbstractExplicit" |
| ) |
| val constructorCounts = subjects.map { |
| it to invocation.processingEnv.requireTypeElement(it).getConstructors().size |
| } |
| assertThat(constructorCounts) |
| .containsExactly( |
| "MyInterface" to 0, |
| "NoExplicitConstructor" to 1, |
| "Base" to 1, |
| "ExplicitConstructor" to 1, |
| "BaseWithSecondary" to 2, |
| "Sub" to 1, |
| "SubWith3Constructors" to 3, |
| "AbstractNoExplicit" to 1, |
| "AbstractExplicit" to 1 |
| ) |
| |
| val primaryConstructorParameterNames = subjects.map { |
| it to invocation.processingEnv.requireTypeElement(it) |
| .findPrimaryConstructor() |
| ?.parameters?.map { |
| it.name |
| } |
| } |
| assertThat(primaryConstructorParameterNames) |
| .containsExactly( |
| "MyInterface" to null, |
| "NoExplicitConstructor" to emptyList<String>(), |
| "Base" to listOf("x"), |
| "ExplicitConstructor" to null, |
| "BaseWithSecondary" to listOf("x"), |
| "Sub" to listOf("x"), |
| "SubWith3Constructors" to emptyList<String>(), |
| "AbstractNoExplicit" to emptyList<String>(), |
| "AbstractExplicit" to listOf("x") |
| ) |
| } |
| } |
| |
| @Test |
| fun jvmDefault() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| interface MyInterface { |
| fun notJvmDefault() |
| @JvmDefault |
| fun jvmDefault() {} |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val subject = invocation.processingEnv.requireTypeElement("MyInterface") |
| assertThat(subject.getMethod("notJvmDefault").isJavaDefault()).isFalse() |
| assertThat(subject.getMethod("jvmDefault").isJavaDefault()).isTrue() |
| } |
| } |
| |
| @Test |
| fun constructors_java() { |
| val src = Source.java( |
| "Source", |
| """ |
| import java.util.List; |
| interface MyInterface {} |
| class NoExplicitConstructor{} |
| class Base { |
| Base(int x){} |
| } |
| class ExplicitConstructor { |
| ExplicitConstructor(int x){} |
| } |
| class BaseWithSecondary { |
| BaseWithSecondary(int x){} |
| BaseWithSecondary(String y){} |
| } |
| class Sub extends Base { |
| Sub(int x) { |
| super(x); |
| } |
| } |
| class SubWith3Constructors extends BaseWithSecondary { |
| SubWith3Constructors() { |
| super(3); |
| } |
| SubWith3Constructors(List<String> list) { |
| super(3); |
| } |
| SubWith3Constructors(List<String> list, int x) { |
| super(3); |
| } |
| } |
| abstract class AbstractNoExplicit {} |
| abstract class AbstractExplicit { |
| AbstractExplicit(int x) {} |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val subjects = listOf( |
| "MyInterface", "NoExplicitConstructor", "Base", "ExplicitConstructor", |
| "BaseWithSecondary", "Sub", "SubWith3Constructors", |
| "AbstractNoExplicit", "AbstractExplicit" |
| ) |
| val constructorCounts = subjects.map { |
| it to invocation.processingEnv.requireTypeElement(it).getConstructors().size |
| } |
| assertThat(constructorCounts) |
| .containsExactly( |
| "MyInterface" to 0, |
| "NoExplicitConstructor" to 1, |
| "Base" to 1, |
| "ExplicitConstructor" to 1, |
| "BaseWithSecondary" to 2, |
| "Sub" to 1, |
| "SubWith3Constructors" to 3, |
| "AbstractNoExplicit" to 1, |
| "AbstractExplicit" to 1 |
| ) |
| |
| subjects.forEach { |
| assertWithMessage(it) |
| .that(invocation.processingEnv.requireTypeElement(it).findPrimaryConstructor()) |
| .isNull() |
| } |
| } |
| } |
| |
| @Test |
| fun enumTypeElement() { |
| fun createSources(packageName: String) = listOf( |
| Source.kotlin( |
| "$packageName/KotlinEnum.kt", |
| """ |
| package $packageName |
| enum class KotlinEnum(private val x:Int) { |
| VAL1(1), |
| VAL2(2); |
| |
| fun enumMethod():Unit {} |
| } |
| """.trimIndent() |
| ), |
| Source.java( |
| "$packageName.JavaEnum", |
| """ |
| package $packageName; |
| public enum JavaEnum { |
| VAL1(1), |
| VAL2(2); |
| |
| private int x; |
| |
| JavaEnum(int x) { |
| this.x = x; |
| } |
| void enumMethod() {} |
| } |
| """.trimIndent() |
| ) |
| ) |
| |
| val classpath = compileFiles( |
| createSources("lib") |
| ) |
| runProcessorTest( |
| sources = createSources("app"), |
| classpath = classpath |
| ) { invocation -> |
| listOf( |
| "lib.KotlinEnum", "lib.JavaEnum", |
| "app.KotlinEnum", "app.JavaEnum" |
| ).forEach { qName -> |
| val typeElement = invocation.processingEnv.requireTypeElement(qName) |
| assertWithMessage("$qName is enum") |
| .that(typeElement.isEnum()) |
| .isTrue() |
| assertWithMessage("$qName does not report enum constants in methods") |
| .that(typeElement.getDeclaredMethods().map { it.name }) |
| .run { |
| contains("enumMethod") |
| containsNoneOf("VAL1", "VAL2") |
| } |
| assertWithMessage("$qName can return enum constants") |
| .that((typeElement as XEnumTypeElement).entries.map { it.name }) |
| .containsExactly("VAL1", "VAL2") |
| assertWithMessage("$qName does not report enum constants in fields") |
| .that(typeElement.getAllFieldNames()) |
| .run { |
| contains("x") |
| containsNoneOf("VAL1", "VAL2") |
| } |
| assertWithMessage("$qName does not report enum constants in declared fields") |
| .that(typeElement.getDeclaredFields().map { it.name }) |
| .containsExactly("x") |
| } |
| } |
| } |
| |
| @Test |
| fun enclosedTypes() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| class TopLevelClass { |
| class NestedClass |
| object NestedObject |
| interface NestedInterface |
| enum class NestedEnum { |
| A, B |
| } |
| companion object { |
| val foo = 1 |
| } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val topLevelClass = invocation.processingEnv.requireTypeElement("TopLevelClass") |
| val enclosedTypeElements = topLevelClass.getEnclosedTypeElements() |
| |
| assertThat(enclosedTypeElements) |
| .containsExactly( |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedClass"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedObject"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedInterface"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedEnum"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.Companion"), |
| ) |
| } |
| } |
| |
| @Test |
| fun enclosedTypes_java() { |
| val src = Source.java( |
| "Source", |
| """ |
| class TopLevelClass { |
| class InnerClass { } |
| static class NestedClass { } |
| interface NestedInterface { } |
| enum NestedEnum { |
| A, B |
| } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val topLevelClass = invocation.processingEnv.requireTypeElement("TopLevelClass") |
| val enclosedTypeElements = topLevelClass.getEnclosedTypeElements() |
| |
| assertThat(enclosedTypeElements) |
| .containsExactly( |
| invocation.processingEnv.requireTypeElement("TopLevelClass.InnerClass"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedClass"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedInterface"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedEnum"), |
| ) |
| } |
| } |
| |
| /** |
| * it is good to exclude methods coming from Object when testing as they differ between KSP |
| * and KAPT but irrelevant for Room. |
| */ |
| private fun XTestInvocation.objectMethodNames() = processingEnv |
| .requireTypeElement("java.lang.Object") |
| .getAllMethods() |
| .names() |
| |
| private fun List<XMethodElement>.names() = map { |
| it.name |
| } |
| } |