| /* |
| * 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") |
| } |
| } |