blob: 0b840021443ae6b8c070feb85d5ffe34900892d1 [file] [log] [blame]
/*
* Copyright (C) 2020 The Dagger Authors.
*
* 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.
*/
import java.io.DataInputStream
import java.io.FileInputStream
import javassist.bytecode.ByteArray
import javassist.bytecode.ClassFile
import junit.framework.Assert.assertEquals
import org.gradle.testkit.runner.TaskOutcome
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
class TransformTest {
@get:Rule
val testProjectDir = TemporaryFolder()
lateinit var gradleRunner: GradleTestRunner
@Before
fun setup() {
gradleRunner = GradleTestRunner(testProjectDir)
gradleRunner.addSrc(
srcPath = "minimal/MainActivity.java",
srcContent =
"""
package minimal;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
@dagger.hilt.android.AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
""".trimIndent()
)
}
// Simple functional test to verify transformation.
@Test
fun testAssemble() {
gradleRunner.addDependencies(
"implementation 'androidx.appcompat:appcompat:1.1.0'",
"implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
"annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
)
gradleRunner.addActivities(
"<activity android:name=\".MainActivity\"/>"
)
val result = gradleRunner.build()
val assembleTask = result.getTask(":assembleDebug")
Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
val transformedClass = result.getTransformedFile("minimal/MainActivity.class")
FileInputStream(transformedClass).use { fileInput ->
ClassFile(DataInputStream(fileInput)).let { classFile ->
// Verify superclass is updated
Assert.assertEquals("minimal.Hilt_MainActivity", classFile.superclass)
// Verify super call is also updated
val constPool = classFile.constPool
classFile.methods.first { it.name == "onCreate" }.let { methodInfo ->
// bytecode of MainActivity.onCreate() is:
// 0 - aload_0
// 1 - aload_1
// 2 - invokespecial
// 5 - return
val invokeIndex = 2
val methodRef = ByteArray.readU16bit(methodInfo.codeAttribute.code, invokeIndex + 1)
val classRef = constPool.getMethodrefClassName(methodRef)
Assert.assertEquals("minimal.Hilt_MainActivity", classRef)
}
}
}
}
// Verify correct transformation is done on nested classes.
@Test
fun testAssemble_nestedClass() {
gradleRunner.addDependencies(
"implementation 'androidx.appcompat:appcompat:1.1.0'",
"implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
"annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
)
gradleRunner.addSrc(
srcPath = "minimal/TopClass.java",
srcContent =
"""
package minimal;
import androidx.appcompat.app.AppCompatActivity;
public class TopClass {
@dagger.hilt.android.AndroidEntryPoint
public static class NestedActivity extends AppCompatActivity { }
}
""".trimIndent()
)
val result = gradleRunner.build()
val assembleTask = result.getTask(":assembleDebug")
Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
val transformedClass = result.getTransformedFile("minimal/TopClass\$NestedActivity.class")
FileInputStream(transformedClass).use { fileInput ->
ClassFile(DataInputStream(fileInput)).let { classFile ->
Assert.assertEquals("minimal.Hilt_TopClass_NestedActivity", classFile.superclass)
}
}
}
// Verify transformation ignores abstract methods.
@Test
fun testAssemble_abstractMethod() {
gradleRunner.addDependencies(
"implementation 'androidx.appcompat:appcompat:1.1.0'",
"implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
"annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
)
gradleRunner.addSrc(
srcPath = "minimal/AbstractActivity.java",
srcContent =
"""
package minimal;
import androidx.appcompat.app.AppCompatActivity;
@dagger.hilt.android.AndroidEntryPoint
public abstract class AbstractActivity extends AppCompatActivity {
public abstract void method();
}
""".trimIndent()
)
val result = gradleRunner.build()
val assembleTask = result.getTask(":assembleDebug")
Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
val transformedClass = result.getTransformedFile("minimal/AbstractActivity.class")
FileInputStream(transformedClass).use { fileInput ->
ClassFile(DataInputStream(fileInput)).let { classFile ->
Assert.assertEquals("minimal.Hilt_AbstractActivity", classFile.superclass)
}
}
}
// Verify transformation ignores native methods.
@Test
fun testAssemble_nativeMethod() {
gradleRunner.addDependencies(
"implementation 'androidx.appcompat:appcompat:1.1.0'",
"implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
"annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
)
gradleRunner.addSrc(
srcPath = "minimal/SimpleActivity.java",
srcContent =
"""
package minimal;
import androidx.appcompat.app.AppCompatActivity;
@dagger.hilt.android.AndroidEntryPoint
public class SimpleActivity extends AppCompatActivity {
public native void method();
}
""".trimIndent()
)
val result = gradleRunner.build()
val assembleTask = result.getTask(":assembleDebug")
Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
val transformedClass = result.getTransformedFile("minimal/SimpleActivity.class")
FileInputStream(transformedClass).use { fileInput ->
ClassFile(DataInputStream(fileInput)).let { classFile ->
Assert.assertEquals("minimal.Hilt_SimpleActivity", classFile.superclass)
}
}
}
// Verifies the transformation is applied incrementally when a class to be transformed is updated.
@Test
fun testTransform_incrementalClass() {
gradleRunner.addDependencies(
"implementation 'androidx.appcompat:appcompat:1.1.0'",
"implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
"annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
)
val srcFile = gradleRunner.addSrc(
srcPath = "minimal/OtherActivity.java",
srcContent =
"""
package minimal;
import androidx.appcompat.app.AppCompatActivity;
@dagger.hilt.android.AndroidEntryPoint
public class OtherActivity extends AppCompatActivity {
}
""".trimIndent()
)
gradleRunner.build().let {
val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
}
gradleRunner.build().let {
val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
Assert.assertEquals(TaskOutcome.UP_TO_DATE, assembleTask.outcome)
}
srcFile.delete()
gradleRunner.addSrc(
srcPath = "minimal/OtherActivity.java",
srcContent =
"""
package minimal;
import androidx.fragment.app.FragmentActivity;
@dagger.hilt.android.AndroidEntryPoint
public class OtherActivity extends FragmentActivity {
}
""".trimIndent()
)
val result = gradleRunner.build()
val assembleTask = result.getTask(TRANSFORM_TASK_NAME)
Assert.assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
val transformedClass = result.getTransformedFile("minimal/OtherActivity.class")
FileInputStream(transformedClass).use { fileInput ->
ClassFile(DataInputStream(fileInput)).let { classFile ->
Assert.assertEquals("minimal.Hilt_OtherActivity", classFile.superclass)
}
}
}
// Verifies the transformation is applied incrementally when a new class is added to an existing
// directory.
@Test
fun testTransform_incrementalDir() {
gradleRunner.addDependencies(
"implementation 'androidx.appcompat:appcompat:1.1.0'",
"implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
"annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'"
)
gradleRunner.addSrcPackage("ui/")
gradleRunner.build().let {
val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
}
gradleRunner.build().let {
val assembleTask = it.getTask(TRANSFORM_TASK_NAME)
assertEquals(TaskOutcome.UP_TO_DATE, assembleTask.outcome)
}
gradleRunner.addSrc(
srcPath = "ui/OtherActivity.java",
srcContent =
"""
package ui;
import androidx.appcompat.app.AppCompatActivity;
@dagger.hilt.android.AndroidEntryPoint
public class OtherActivity extends AppCompatActivity {
}
""".trimIndent()
)
val result = gradleRunner.build()
val assembleTask = result.getTask(TRANSFORM_TASK_NAME)
assertEquals(TaskOutcome.SUCCESS, assembleTask.outcome)
}
companion object {
const val TRANSFORM_TASK_NAME =
":transformDebugClassesWithAsm"
}
}