blob: a4321c3fd742247604b61b02ee91a4952ad0d136 [file] [log] [blame]
/*
* Copyright (C) 2017 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.processor
import COMMON
import androidx.room.Dao
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.GuavaUtilConcurrentTypeNames
import androidx.room.ext.RxJava2TypeNames
import androidx.room.ext.RxJava3TypeNames
import androidx.room.compiler.processing.XDeclaredType
import androidx.room.compiler.processing.XMethodElement
import androidx.room.testing.TestInvocation
import androidx.room.testing.TestProcessor
import androidx.room.vo.ShortcutMethod
import com.google.common.truth.Truth
import com.google.testing.compile.CompileTester
import com.google.testing.compile.JavaFileObjects
import com.google.testing.compile.JavaSourcesSubjectFactory
import com.squareup.javapoet.ArrayTypeName
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
import toJFO
import javax.tools.JavaFileObject
import kotlin.reflect.KClass
/**
* Base test class for shortcut methods.
*/
abstract class ShortcutMethodProcessorTest<out T : ShortcutMethod>(
val annotation: KClass<out Annotation>
) {
companion object {
const val DAO_PREFIX = """
package foo.bar;
import androidx.room.*;
import java.util.*;
@Dao
abstract class MyClass {
"""
const val DAO_SUFFIX = "}"
val USER_TYPE_NAME: TypeName = COMMON.USER_TYPE_NAME
val USERNAME_TYPE_NAME: TypeName = ClassName.get("foo.bar", "Username")
val BOOK_TYPE_NAME: TypeName = ClassName.get("foo.bar", "Book")
}
@Test
fun noParams() {
singleShortcutMethod(
"""
@${annotation.java.canonicalName}
abstract public void foo();
""") { shortcut, _ ->
assertThat(shortcut.name, `is`("foo"))
assertThat(shortcut.parameters.size, `is`(0))
}.failsToCompile().withErrorContaining(noParamsError())
}
abstract fun noParamsError(): String
@Test
fun single() {
singleShortcutMethod(
"""
@${annotation.java.canonicalName}
abstract public int foo(User user);
""") { shortcut, _ ->
assertThat(shortcut.name, `is`("foo"))
assertThat(shortcut.parameters.size, `is`(1))
val param = shortcut.parameters.first()
assertThat(param.type.typeName, `is`(USER_TYPE_NAME))
assertThat(param.pojoType?.typeName, `is`(USER_TYPE_NAME))
assertThat(shortcut.entities.size, `is`(1))
assertThat(shortcut.entities["user"]?.isPartialEntity, `is`(false))
assertThat(shortcut.entities["user"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
}.compilesWithoutError()
}
@Test
fun notAnEntity() {
singleShortcutMethod(
"""
@${annotation.java.canonicalName}
abstract public void foo(NotAnEntity notValid);
""") { shortcut, _ ->
assertThat(shortcut.name, `is`("foo"))
assertThat(shortcut.parameters.size, `is`(1))
assertThat(shortcut.entities.size, `is`(0))
}.failsToCompile().withErrorContaining(
ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
)
}
@Test
fun two() {
singleShortcutMethod(
"""
@${annotation.java.canonicalName}
abstract public void foo(User u1, User u2);
""") { shortcut, _ ->
assertThat(shortcut.name, `is`("foo"))
assertThat(shortcut.parameters.size, `is`(2))
shortcut.parameters.forEach {
assertThat(it.type.typeName, `is`(USER_TYPE_NAME))
assertThat(it.pojoType?.typeName, `is`(USER_TYPE_NAME))
}
assertThat(shortcut.entities.size, `is`(2))
assertThat(shortcut.entities["u1"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
assertThat(shortcut.entities["u1"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
assertThat(shortcut.parameters.map { it.name },
`is`(listOf("u1", "u2")))
}.compilesWithoutError()
}
@Test
fun list() {
listOf(
"int",
"Integer",
"${RxJava2TypeNames.SINGLE}<Integer>",
"${RxJava2TypeNames.MAYBE}<Integer>",
RxJava2TypeNames.COMPLETABLE,
"${RxJava3TypeNames.SINGLE}<Integer>",
"${RxJava3TypeNames.MAYBE}<Integer>",
RxJava3TypeNames.COMPLETABLE,
"${GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE}<Integer>"
).forEach { type ->
singleShortcutMethod(
"""
@${annotation.java.canonicalName}
abstract public $type users(List<User> users);
""") { shortcut, _ ->
assertThat(shortcut.name, `is`("users"))
assertThat(shortcut.parameters.size, `is`(1))
val param = shortcut.parameters.first()
assertThat(param.type.typeName, `is`(
ParameterizedTypeName.get(
ClassName.get("java.util", "List"), USER_TYPE_NAME) as TypeName))
assertThat(param.pojoType?.typeName, `is`(USER_TYPE_NAME))
assertThat(shortcut.entities.size, `is`(1))
assertThat(shortcut.entities["users"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
}.compilesWithoutError()
}
}
@Test
fun array() {
singleShortcutMethod(
"""
@${annotation.java.canonicalName}
abstract public void users(User[] users);
""") { shortcut, _ ->
assertThat(shortcut.name, `is`("users"))
assertThat(shortcut.parameters.size, `is`(1))
val param = shortcut.parameters.first()
assertThat(param.type.typeName, `is`(
ArrayTypeName.of(COMMON.USER_TYPE_NAME) as TypeName))
assertThat(shortcut.entities.size, `is`(1))
assertThat(shortcut.entities["users"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
}.compilesWithoutError()
}
@Test
fun set() {
singleShortcutMethod(
"""
@${annotation.java.canonicalName}
abstract public void modifyUsers(Set<User> users);
""") { shortcut, _ ->
assertThat(shortcut.name, `is`("modifyUsers"))
assertThat(shortcut.parameters.size, `is`(1))
val param = shortcut.parameters.first()
assertThat(param.type.typeName, `is`(
ParameterizedTypeName.get(
ClassName.get("java.util", "Set"),
COMMON.USER_TYPE_NAME
) as TypeName))
assertThat(shortcut.entities.size, `is`(1))
assertThat(shortcut.entities["users"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
}.compilesWithoutError()
}
@Test
fun iterable() {
singleShortcutMethod(
"""
@${annotation.java.canonicalName}
abstract public void modifyUsers(Iterable<User> users);
""") { shortcut, _ ->
assertThat(shortcut.name, `is`("modifyUsers"))
assertThat(shortcut.parameters.size, `is`(1))
val param = shortcut.parameters.first()
assertThat(param.type.typeName, `is`(
ParameterizedTypeName.get(ClassName.get("java.lang", "Iterable"),
COMMON.USER_TYPE_NAME) as TypeName))
assertThat(shortcut.entities.size, `is`(1))
assertThat(shortcut.entities["users"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
}.compilesWithoutError()
}
@Test
fun customCollection() {
singleShortcutMethod(
"""
static class MyList<Irrelevant, Item> extends ArrayList<Item> {}
@${annotation.java.canonicalName}
abstract public void modifyUsers(MyList<String, User> users);
""") { shortcut, _ ->
assertThat(shortcut.name, `is`("modifyUsers"))
assertThat(shortcut.parameters.size, `is`(1))
val param = shortcut.parameters.first()
assertThat(param.type.typeName, `is`(
ParameterizedTypeName.get(ClassName.get("foo.bar", "MyClass.MyList"),
CommonTypeNames.STRING, COMMON.USER_TYPE_NAME) as TypeName))
assertThat(shortcut.entities.size, `is`(1))
assertThat(shortcut.entities["users"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
}.compilesWithoutError()
}
@Test
fun differentTypes() {
listOf(
"void",
"int",
"Integer",
"${RxJava2TypeNames.SINGLE}<Integer>",
"${RxJava2TypeNames.MAYBE}<Integer>",
RxJava2TypeNames.COMPLETABLE,
"${RxJava3TypeNames.SINGLE}<Integer>",
"${RxJava3TypeNames.MAYBE}<Integer>",
RxJava3TypeNames.COMPLETABLE,
"${GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE}<Integer>"
).forEach { type ->
singleShortcutMethod(
"""
@${annotation.java.canonicalName}
abstract public $type foo(User u1, Book b1);
""") { shortcut, _ ->
assertThat(shortcut.parameters.size, `is`(2))
assertThat(shortcut.parameters[0].type.typeName.toString(),
`is`("foo.bar.User"))
assertThat(shortcut.parameters[1].type.typeName.toString(),
`is`("foo.bar.Book"))
assertThat(shortcut.parameters.map { it.name }, `is`(listOf("u1", "b1")))
assertThat(shortcut.entities.size, `is`(2))
assertThat(shortcut.entities["u1"]?.pojo?.typeName, `is`(USER_TYPE_NAME))
assertThat(shortcut.entities["b1"]?.pojo?.typeName, `is`(BOOK_TYPE_NAME))
}.compilesWithoutError()
}
}
@Test
fun invalidReturnType() {
listOf(
"long",
"String",
"User",
"${RxJava2TypeNames.SINGLE}<Int>",
"${RxJava2TypeNames.MAYBE}<Int>",
"${RxJava2TypeNames.SINGLE}<String>",
"${RxJava2TypeNames.MAYBE}<String>",
"${RxJava2TypeNames.SINGLE}<User>",
"${RxJava2TypeNames.MAYBE}<User>",
"${GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE}<Int>",
"${GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE}<String>",
"${GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE}<User>"
).forEach { type ->
singleShortcutMethod(
"""
@${annotation.java.canonicalName}
abstract public $type foo(User user);
""") { _, _ ->
}.failsToCompile().withErrorContaining(invalidReturnTypeError())
}
}
@Test
fun targetEntity() {
val usernameJfo = """
package foo.bar;
import androidx.room.*;
public class Username {
int uid;
String name;
}
""".toJFO("foo.bar.Username")
singleShortcutMethod(
"""
@${annotation.java.canonicalName}(entity = User.class)
abstract public int foo(Username username);
""",
additionalJFOs = listOf(usernameJfo)) { shortcut, _ ->
assertThat(shortcut.name, `is`("foo"))
assertThat(shortcut.parameters.size, `is`(1))
val param = shortcut.parameters.first()
assertThat(param.type.typeName, `is`(USERNAME_TYPE_NAME))
assertThat(param.pojoType?.typeName, `is`(USERNAME_TYPE_NAME))
assertThat(shortcut.entities.size, `is`(1))
assertThat(shortcut.entities["username"]?.isPartialEntity, `is`(true))
assertThat(shortcut.entities["username"]?.entityTypeName, `is`(USER_TYPE_NAME))
assertThat(shortcut.entities["username"]?.pojo?.typeName, `is`(USERNAME_TYPE_NAME))
}.compilesWithoutError()
}
@Test
fun targetEntitySameAsPojo() {
singleShortcutMethod(
"""
@${annotation.java.canonicalName}(entity = User.class)
abstract public int foo(User user);
""") { _, _ ->
}.compilesWithoutError()
}
@Test
fun targetEntityExtraColumn() {
val usernameJfo = """
package foo.bar;
import androidx.room.*;
public class Username {
int uid;
String name;
long extraField;
}
""".toJFO("foo.bar.Username")
singleShortcutMethod(
"""
@${annotation.java.canonicalName}(entity = User.class)
abstract public int foo(Username username);
""",
additionalJFOs = listOf(usernameJfo)) { _, _ ->
}.failsToCompile().withErrorContaining(
ProcessorErrors.cannotFindAsEntityField("foo.bar.User"))
}
@Test
fun targetEntityExtraColumnIgnored() {
val usernameJfo = """
package foo.bar;
import androidx.room.*;
public class Username {
int uid;
String name;
@Ignore
long extraField;
}
""".toJFO("foo.bar.Username")
singleShortcutMethod(
"""
@${annotation.java.canonicalName}(entity = User.class)
abstract public int foo(Username username);
""",
additionalJFOs = listOf(usernameJfo)) { _, _ ->
}.compilesWithoutError()
}
@Test
fun targetEntityWithEmbedded() {
val usernameJfo = """
package foo.bar;
import androidx.room.*;
public class Username {
int uid;
@Embedded
Fullname name;
}
""".toJFO("foo.bar.Username")
val fullnameJfo = """
package foo.bar;
import androidx.room.*;
public class Fullname {
@ColumnInfo(name = "name")
String firstName;
String lastName;
}
""".toJFO("foo.bar.Fullname")
singleShortcutMethod(
"""
@${annotation.java.canonicalName}(entity = User.class)
abstract public int foo(Username username);
""",
additionalJFOs = listOf(usernameJfo, fullnameJfo)) { _, _ ->
}.compilesWithoutError()
}
@Test
fun targetEntityWithRelation() {
val userPetsJfo = """
package foo.bar;
import androidx.room.*;
import java.util.List;
public class UserPets {
int uid;
@Relation(parentColumn = "uid", entityColumn = "ownerId")
List<Pet> pets;
}
""".toJFO("foo.bar.UserPets")
val petJfo = """
package foo.bar;
import androidx.room.*;
@Entity
public class Pet {
@PrimaryKey
int petId;
int ownerId;
}
""".toJFO("foo.bar.Pet")
singleShortcutMethod(
"""
@${annotation.java.canonicalName}(entity = User.class)
abstract public int foo(UserPets userPets);
""",
additionalJFOs = listOf(userPetsJfo, petJfo)) { _, _ ->
}.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_RELATION_IN_PARTIAL_ENTITY)
}
abstract fun invalidReturnTypeError(): String
abstract fun process(
baseContext: Context,
containing: XDeclaredType,
executableElement: XMethodElement
): T
fun singleShortcutMethod(
vararg input: String,
additionalJFOs: List<JavaFileObject> = emptyList(),
handler: (T, TestInvocation) -> Unit
):
CompileTester {
return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
.that(listOf(JavaFileObjects.forSourceString("foo.bar.MyClass",
DAO_PREFIX + input.joinToString("\n") + DAO_SUFFIX
), COMMON.USER, COMMON.BOOK, COMMON.NOT_AN_ENTITY, COMMON.RX2_COMPLETABLE,
COMMON.RX2_MAYBE, COMMON.RX2_SINGLE, COMMON.RX3_COMPLETABLE,
COMMON.RX3_MAYBE, COMMON.RX3_SINGLE, COMMON.LISTENABLE_FUTURE,
COMMON.GUAVA_ROOM) + additionalJFOs)
.processedWith(TestProcessor.builder()
.forAnnotations(annotation, Dao::class)
.nextRunHandler { invocation ->
val (owner, methods) = invocation.roundEnv
.getElementsAnnotatedWith(Dao::class.java)
.map {
Pair(it,
it.asTypeElement().getAllMethods().filter {
it.hasAnnotation(annotation)
}
)
}.first { it.second.isNotEmpty() }
val processed = process(
baseContext = invocation.context,
containing = owner.asDeclaredType(),
executableElement = methods.first())
handler(processed, invocation)
true
}
.build())
}
}