blob: 554dab6dee879bc4eee143cebc1c9d5631aa8114 [file] [log] [blame]
/*
* Copyright (C) 2021 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 com.android.build.gradle.internal.dsl.decorator
import com.android.build.gradle.internal.dsl.AgpDslLockedException
import com.android.build.gradle.internal.dsl.Lockable
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.gradle.api.provider.Property
import org.junit.Test
import java.lang.reflect.Modifier
import javax.inject.Inject
import kotlin.test.assertFailsWith
/** Unit tests for the code generated by the AgpDslDecorator */
class DslDecoratorUnitTest {
interface Empty
@Test
fun `check generated constructor `() {
val decorator = DslDecorator(listOf())
val decorated = decorator.decorate(Empty::class)
val constructor = decorated.getConstructor()
assertWithMessage("Expected to not have @Injected on constructor")
.that(constructor.declaredAnnotations.map { it.annotationClass })
.isEmpty()
// Check does not throw
constructor.newInstance()
}
abstract class EmptyWithInjectAnnotation @Inject constructor()
@Test
fun `check generated constructor keeps the inject annotation from the constructor it delegates to`() {
val decorator = DslDecorator(listOf())
val decorated = decorator.decorate(EmptyWithInjectAnnotation::class)
val constructor = decorated.getConstructor()
assertWithMessage("Expected to be copied to generated constructor")
.that(constructor.declaredAnnotations.map { it.annotationClass })
.containsExactly(Inject::class)
// Check does not throw
constructor.newInstance()
}
abstract class WithPrimitiveInConstructor @Inject constructor(val integer: Int, val boxed: Int?)
@Test
fun `check constructors with arguments work correctly`() {
val decorator = DslDecorator(listOf())
val decorated = decorator.decorate(WithPrimitiveInConstructor::class)
val o = decorated.getDeclaredConstructor(Int::class.java, Integer::class.java).newInstance(1, 2)
assertWithMessage("Constructor values should be passed through")
.that(o.integer).isEqualTo(1)
assertWithMessage("Constructor values should be passed through")
.that(o.boxed).isEqualTo(2)
}
@Suppress("unused")
abstract class ConstructorWithArgsWithoutInject constructor(val integer: Int) {
constructor(): this(0)
@Inject constructor(string: String): this(Integer.getInteger(string)!!)
}
@Test
fun `check only annotated and zero-arg constructors are generated`() {
val decorator = DslDecorator(listOf())
val decorated = decorator.decorate(ConstructorWithArgsWithoutInject::class)
val zeroArgumentConstructor = decorated.getDeclaredConstructor()
assertWithMessage("Expected to not have @Injected")
.that(zeroArgumentConstructor.declaredAnnotations.map { it.annotationClass })
.isEmpty()
val injectAnnotatedConstructor = decorated.getDeclaredConstructor(String::class.java)
assertWithMessage("Expected to be copied to generated constructor")
.that(injectAnnotatedConstructor.declaredAnnotations.map { it.annotationClass })
.containsExactly(Inject::class)
assertFailsWith(NoSuchMethodException::class) {
decorated.getDeclaredConstructor(Int::class.java)
}
}
interface WithManagedString {
var managedString: String
}
@Test
fun `check managed var properties`() {
val decorator = DslDecorator(listOf(SupportedPropertyType.Var.String))
val o = decorator.decorate(WithManagedString::class.java).getConstructor().newInstance()
assertThat(o).isNotNull()
assertThat(o.managedString).isNull()
o.managedString = "a"
assertThat(o.managedString).isEqualTo("a")
}
interface WithManagedStringLockable : Lockable {
abstract var managedString: String
}
@Test
fun `check locking works for managed var properties`() {
val decorator = DslDecorator(listOf(SupportedPropertyType.Var.String))
val o = decorator.decorate(WithManagedStringLockable::class).getConstructor().newInstance()
o.managedString = "a"
o.lock()
val failure = assertFailsWith<AgpDslLockedException> {
o.managedString = "b"
}
assertThat(failure).hasMessageThat().contains("It is too late to set managedString")
assertThat(o.managedString).isEqualTo("a")
}
abstract class WithExtraSetter {
abstract var managedString: String
fun setManagedString(value: Int) {
managedString = "$value"
}
}
@Test
fun `check extra setters`() {
val decorator = DslDecorator(listOf(SupportedPropertyType.Var.String))
val o = decorator.decorate(WithExtraSetter::class.java).getConstructor().newInstance()
assertThat(o).isNotNull()
assertThat(o.managedString).isNull()
o.managedString = "a"
assertThat(o.managedString).isEqualTo("a")
o.setManagedString(4)
assertThat(o.managedString).isEqualTo("4")
}
abstract class LeaveAbstract {
@Suppress("unused")
abstract val forGradle: Property<String>
}
@Test
fun `check unknown types are left as abstract`() {
val decorator = DslDecorator(listOf(SupportedPropertyType.Var.String))
val decorated = decorator.decorate(LeaveAbstract::class)
val method = decorated.getMethod("getForGradle")
assertThat(Modifier.toString(method.modifiers)).isEqualTo("public abstract")
}
abstract class WithProtectedField {
protected abstract var compileSdkVersion: String?
var compileSdk: String?
get() = compileSdkVersion
set(value) { compileSdkVersion = value }
}
@Test
fun `check protected field is implemented`() {
val decorated = DslDecorator(listOf(SupportedPropertyType.Var.String))
.decorate(WithProtectedField::class)
val o = decorated.getConstructor().newInstance()
assertThat(o).isNotNull()
assertThat(o.compileSdk).isNull()
o.compileSdk = "a"
assertThat(o.compileSdk).isEqualTo("a")
val method = decorated.getDeclaredMethod("getCompileSdkVersion")
assertThat(Modifier.toString(method.modifiers)).isEqualTo("protected")
}
abstract class Subclass: WithProtectedField()
@Test
fun `check protected field in superclass is implemented`() {
val decorated = DslDecorator(listOf(SupportedPropertyType.Var.String))
.decorate(Subclass::class)
val o = decorated.getConstructor().newInstance()
assertThat(o).isNotNull()
assertThat(o.compileSdk).isNull()
o.compileSdk = "a"
assertThat(o.compileSdk).isEqualTo("a")
val method = decorated.getDeclaredMethod("getCompileSdkVersion")
assertThat(Modifier.toString(method.modifiers)).isEqualTo("protected")
}
abstract class PublicFieldOverridesProtectedField: WithProtectedField() {
public abstract override var compileSdkVersion: String?
}
@Test
fun `check handling of public override of a protected field`() {
val decorated = DslDecorator(listOf(SupportedPropertyType.Var.String))
.decorate(PublicFieldOverridesProtectedField::class)
val o = decorated.getConstructor().newInstance()
assertThat(o).isNotNull()
assertThat(o.compileSdkVersion).isNull()
o.compileSdkVersion = "a"
assertThat(o.compileSdkVersion).isEqualTo("a")
val method = decorated.getDeclaredMethod("getCompileSdkVersion")
assertThat(Modifier.toString(method.modifiers)).isEqualTo("public")
}
interface LockableWithList : Lockable {
val foo: MutableList<String>
}
@Test
fun `check locking propagation`() {
val decorated = DslDecorator(listOf(SupportedPropertyType.Val.List))
.decorate(LockableWithList::class)
val o = decorated.getDeclaredConstructor().newInstance()
o.foo += "one"
o.lock()
assertThat(o.foo).containsExactly("one")
val exception = assertFailsWith(AgpDslLockedException::class) {
o.foo += "two"
}
assertThat(exception).hasMessageThat().isEqualTo(
"""
It is too late to modify foo
It has already been read to configure this project.
Consider either moving this call to be during evaluation,
or using the variant API.""".trimIndent()
)
assertThat(o.foo).containsExactly("one")
}
interface A {
val foo: MutableList<String>
}
abstract class B {
abstract val foo: MutableCollection<String>
}
abstract class C: B(), A
@Test
fun `check synthetic methods where interface has more specific type`() {
val decorated = DslDecorator(listOf(SupportedPropertyType.Val.List)).decorate(C::class)
val c = decorated.getDeclaredConstructor().newInstance()
val asA: A = c
asA.foo.add("a")
assertThat(c.foo).containsExactly("a")
val asB: B = c
asB.foo.add("b")
assertThat(c.foo).containsExactly("a", "b")
}
interface X {
val foo: MutableCollection<String>
}
abstract class Y {
abstract val foo: MutableList<String>
}
abstract class Z: Y(), X
@Test
fun `check synthetic methods where supertype has more specific type`() {
val decorated = DslDecorator(listOf(SupportedPropertyType.Val.List)).decorate(Z::class)
val z = decorated.getDeclaredConstructor().newInstance()
val asX: X = z
asX.foo.add("a")
assertThat(asX.foo).containsExactly("a")
val asY: Y = z
asY.foo.add("b")
assertThat(asY.foo).containsExactly("a", "b")
}
interface InterfaceWithVar {
var foo: String
}
abstract class WithoutConcreteImplementation: InterfaceWithVar {
abstract override var foo: String
}
abstract class ConcreteImplementation : WithoutConcreteImplementation() {
override var foo: String = "hello"
}
@Test
fun `check doesn't override concrete implementations`() {
val dslDecorator = DslDecorator(listOf(SupportedPropertyType.Var.String))
val decoratedInterface = dslDecorator.decorate(WithoutConcreteImplementation::class)
.getDeclaredConstructor().newInstance()
assertThat(decoratedInterface.foo).isNull()
val decorated = dslDecorator.decorate(ConcreteImplementation::class)
.getDeclaredConstructor().newInstance()
assertThat(decorated.foo).isEqualTo("hello")
}
}