• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 import java.io.File
18 import org.gradle.testkit.runner.BuildResult
19 import org.gradle.testkit.runner.GradleRunner
20 import org.junit.rules.TemporaryFolder
21 
22 /**
23  * Testing utility class that sets up a simple Android project that applies the Hilt plugin.
24  */
25 class GradleTestRunner(val tempFolder: TemporaryFolder) {
26   private val dependencies = mutableListOf<String>()
27   private val activities = mutableListOf<String>()
28   private val additionalAndroidOptions = mutableListOf<String>()
29   private val hiltOptions = mutableListOf<String>()
30   private var appClassName: String? = null
31   private var buildFile: File? = null
32   private var gradlePropertiesFile: File? = null
33   private var manifestFile: File? = null
34 
35   init {
36     tempFolder.newFolder("src", "main", "java", "minimal")
37     tempFolder.newFolder("src", "main", "res")
38   }
39 
40   // Adds project dependencies, e.g. "implementation <group>:<id>:<version>"
addDependenciesnull41   fun addDependencies(vararg deps: String) {
42     dependencies.addAll(deps)
43   }
44 
45   // Adds an <activity> tag in the project's Android Manifest, e.g. "<activity name=".Foo"/>
addActivitiesnull46   fun addActivities(vararg activityElements: String) {
47     activities.addAll(activityElements)
48   }
49 
50   // Adds 'android' options to the project's build.gradle, e.g. "lintOptions.checkReleaseBuilds = false"
addAndroidOptionnull51   fun addAndroidOption(vararg options: String) {
52     additionalAndroidOptions.addAll(options)
53   }
54 
55   // Adds 'hilt' options to the project's build.gradle, e.g. "enableExperimentalClasspathAggregation = true"
addHiltOptionnull56   fun addHiltOption(vararg options: String) {
57     hiltOptions.addAll(options)
58   }
59 
60   // Adds a source package to the project. The package path is relative to 'src/main/java'.
addSrcPackagenull61   fun addSrcPackage(packagePath: String) {
62     File(tempFolder.root, "src/main/java/$packagePath").mkdirs()
63   }
64 
65   // Adds a source file to the project. The source path is relative to 'src/main/java'.
addSrcnull66   fun addSrc(srcPath: String, srcContent: String): File {
67     File(tempFolder.root, "src/main/java/${srcPath.substringBeforeLast(File.separator)}").mkdirs()
68     return tempFolder.newFile("/src/main/java/$srcPath").apply { writeText(srcContent) }
69   }
70 
71   // Adds a resource file to the project. The source path is relative to 'src/main/res'.
addResnull72   fun addRes(resPath: String, resContent: String): File {
73     File(tempFolder.root, "src/main/res/${resPath.substringBeforeLast(File.separator)}").mkdirs()
74     return tempFolder.newFile("/src/main/res/$resPath").apply { writeText(resContent) }
75   }
76 
setAppClassNamenull77   fun setAppClassName(name: String) {
78     appClassName = name
79   }
80 
81   // Executes a Gradle builds and expects it to succeed.
buildnull82   fun build(): Result {
83     setupFiles()
84     return Result(tempFolder.root, createRunner().build())
85   }
86 
87   // Executes a Gradle build and expects it to fail.
buildAndFailnull88   fun buildAndFail(): Result {
89     setupFiles()
90     return Result(tempFolder.root, createRunner().buildAndFail())
91   }
92 
setupFilesnull93   private fun setupFiles() {
94     writeBuildFile()
95     writeGradleProperties()
96     writeAndroidManifest()
97   }
98 
writeBuildFilenull99   private fun writeBuildFile() {
100     buildFile?.delete()
101     buildFile = tempFolder.newFile("build.gradle").apply {
102       writeText(
103         """
104         buildscript {
105           repositories {
106             google()
107             jcenter()
108           }
109           dependencies {
110             classpath 'com.android.tools.build:gradle:4.2.0-beta04'
111           }
112         }
113 
114         plugins {
115           id 'com.android.application'
116           id 'dagger.hilt.android.plugin'
117         }
118 
119         android {
120           compileSdkVersion 30
121           buildToolsVersion "30.0.2"
122 
123           defaultConfig {
124             applicationId "plugin.test"
125             minSdkVersion 21
126             targetSdkVersion 30
127           }
128 
129           compileOptions {
130               sourceCompatibility 1.8
131               targetCompatibility 1.8
132           }
133           ${additionalAndroidOptions.joinToString(separator = "\n")}
134         }
135 
136         allprojects {
137           repositories {
138             mavenLocal()
139             google()
140             jcenter()
141           }
142         }
143 
144         dependencies {
145           ${dependencies.joinToString(separator = "\n")}
146         }
147 
148         hilt {
149           ${hiltOptions.joinToString(separator = "\n")}
150         }
151         """.trimIndent()
152       )
153     }
154   }
155 
writeGradlePropertiesnull156   private fun writeGradleProperties() {
157     gradlePropertiesFile?.delete()
158     gradlePropertiesFile = tempFolder.newFile("gradle.properties").apply {
159       writeText(
160         """
161         android.useAndroidX=true
162         """.trimIndent()
163       )
164     }
165   }
166 
writeAndroidManifestnull167   private fun writeAndroidManifest() {
168     manifestFile?.delete()
169     manifestFile = tempFolder.newFile("/src/main/AndroidManifest.xml").apply {
170       writeText(
171         """
172         <?xml version="1.0" encoding="utf-8"?>
173         <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="minimal">
174             <application
175                 android:name="${appClassName ?: "android.app.Application"}"
176                 android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
177                 ${activities.joinToString(separator = "\n")}
178             </application>
179         </manifest>
180         """.trimIndent()
181       )
182     }
183   }
184 
createRunnernull185   private fun createRunner() = GradleRunner.create()
186     .withProjectDir(tempFolder.root)
187     .withArguments("assembleDebug", "--stacktrace")
188     .withPluginClasspath()
189 //    .withDebug(true) // Add this line to enable attaching a debugger to the gradle test invocation
190     .forwardOutput()
191 
192   // Data class representing a Gradle Test run result.
193   data class Result(
194     private val projectRoot: File,
195     private val buildResult: BuildResult
196   ) {
197     // Finds a task by name.
198     fun getTask(name: String) = buildResult.task(name) ?: error("Task '$name' not found.")
199 
200     // Gets the full build output.
201     fun getOutput() = buildResult.output
202 
203     // Finds a transformed file. The srcFilePath is relative to the app's package.
204     fun getTransformedFile(srcFilePath: String): File {
205       val parentDir =
206         File(projectRoot, "build/intermediates/asm_instrumented_project_classes/debug")
207       return File(parentDir, srcFilePath).also {
208         if (!it.exists()) {
209           error("Unable to find transformed class ${it.path}")
210         }
211       }
212     }
213   }
214 }
215