1 /*
2 * Copyright 2018 The Android Open Source Project
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 package androidx.navigation.safeargs.gradle
18
19 import org.gradle.testkit.runner.BuildResult
20 import org.gradle.testkit.runner.GradleRunner
21 import org.gradle.testkit.runner.TaskOutcome
22 import org.hamcrest.CoreMatchers.`is`
23 import org.hamcrest.CoreMatchers.not
24 import org.hamcrest.MatcherAssert.assertThat
25 import org.junit.Before
26 import org.junit.Rule
27 import org.junit.Test
28 import org.junit.rules.TemporaryFolder
29 import org.junit.runner.RunWith
30 import org.junit.runners.JUnit4
31 import java.io.File
32 import java.util.Properties
33
34 private const val MAIN_DIR = "androidx/navigation/testapp"
35
36 private const val NEXT_DIRECTIONS = "$MAIN_DIR/NextFragmentDirections.java"
37 private const val MAIN_DIRECTIONS = "$MAIN_DIR/MainFragmentDirections.java"
38 private const val MODIFIED_NEXT_DIRECTIONS = "$MAIN_DIR/ModifiedNextFragmentDirections.java"
39 private const val ADDITIONAL_DIRECTIONS = "$MAIN_DIR/AdditionalFragmentDirections.java"
40 private const val FOO_DIRECTIONS = "safe/gradle/test/app/foo/FooFragmentDirections.java"
41
42 private const val NAV_RESOURCES = "src/main/res/navigation"
43 private const val SEC = 1000L
44
45 // Does not work in the Android Studio
46 @RunWith(JUnit4::class)
47 class PluginTest {
48
49 @Suppress("MemberVisibilityCanBePrivate")
50 @get:Rule
51 val testProjectDir = TemporaryFolder()
52
53 private var buildFile: File = File("")
54 private var compileSdkVersion = ""
55 private var buildToolsVersion = ""
56
projectRootnull57 private fun projectRoot(): File = testProjectDir.root
58
59 private fun assertGenerated(name: String) = assertExists(name, true)
60
61 private fun assertNotGenerated(name: String) = assertExists(name, false)
62
63 private fun assertExists(name: String, ex: Boolean): File {
64 val generatedFile = File(projectRoot(), "build/$GENERATED_PATH/$name")
65 assertThat(generatedFile.exists(), `is`(ex))
66 return generatedFile
67 }
68
navResourcenull69 private fun navResource(name: String) = File(projectRoot(), "$NAV_RESOURCES/$name")
70
71 private fun gradleBuilder(vararg args: String) = GradleRunner.create()
72 .withProjectDir(projectRoot()).withPluginClasspath().withArguments(*args)
73
74 private fun runGradle(vararg args: String) = gradleBuilder(*args).build()
75 private fun runAndFailGradle(vararg args: String) = gradleBuilder(*args).buildAndFail()
76
77 @Before
78 fun setup() {
79 projectRoot().mkdirs()
80 buildFile = File(projectRoot(), "build.gradle")
81 buildFile.createNewFile()
82 // copy local.properties
83 val appToolkitProperties = File("../../app-toolkit/local.properties")
84 if (appToolkitProperties.exists()) {
85 appToolkitProperties.copyTo(File(projectRoot(), "local.properties"), overwrite = true)
86 } else {
87 File("../../local.properties").copyTo(
88 File(projectRoot(), "local.properties"), overwrite = true)
89 }
90 val stream = PluginTest::class.java.classLoader.getResourceAsStream("sdk.prop")
91 val properties = Properties()
92 properties.load(stream)
93 compileSdkVersion = properties.getProperty("compileSdkVersion")
94 buildToolsVersion = properties.getProperty("buildToolsVersion")
95 testData("app-project").copyRecursively(projectRoot())
96 }
97
setupSimpleBuildGradlenull98 private fun setupSimpleBuildGradle() {
99 buildFile.writeText("""
100 plugins {
101 id('com.android.application')
102 id('androidx.navigation.safeargs')
103 }
104
105 android {
106 compileSdkVersion $compileSdkVersion
107 buildToolsVersion "$buildToolsVersion"
108 }
109 """.trimIndent())
110 }
111
112 @Test
runGenerateTasknull113 fun runGenerateTask() {
114 buildFile.writeText("""
115 plugins {
116 id('com.android.application')
117 id('androidx.navigation.safeargs')
118 }
119
120 android {
121 compileSdkVersion $compileSdkVersion
122 buildToolsVersion "$buildToolsVersion"
123 flavorDimensions "mode"
124 productFlavors {
125 foo {
126 dimension "mode"
127 applicationIdSuffix ".foo"
128 }
129 notfoo {
130 dimension "mode"
131 }
132
133 }
134 }
135 """.trimIndent())
136
137 runGradle("generateSafeArgsNotfooDebug", "generateSafeArgsFooDebug")
138 .assertSuccessfulTask("generateSafeArgsNotfooDebug")
139 .assertSuccessfulTask("generateSafeArgsFooDebug")
140
141 assertGenerated("notfoo/debug/$NEXT_DIRECTIONS")
142 assertNotGenerated("foo/debug/$NEXT_DIRECTIONS")
143 assertGenerated("foo/debug/$FOO_DIRECTIONS")
144 }
145
146 @Test
incrementalAddnull147 fun incrementalAdd() {
148 setupSimpleBuildGradle()
149 runGradle("generateSafeArgsDebug").assertSuccessfulTask("generateSafeArgsDebug")
150 val nextLastMod = assertGenerated("debug/$NEXT_DIRECTIONS").lastModified()
151
152 testData("incremental-test-data/add_nav.xml").copyTo(navResource("add_nav.xml"))
153
154 // lastModified has one second precision on certain platforms and jdk versions
155 // so sleep for a second
156 Thread.sleep(SEC)
157 runGradle("generateSafeArgsDebug").assertSuccessfulTask("generateSafeArgsDebug")
158 assertGenerated("debug/$ADDITIONAL_DIRECTIONS")
159 val newNextLastMod = assertGenerated("debug/$NEXT_DIRECTIONS").lastModified()
160 assertThat(newNextLastMod, `is`(nextLastMod))
161 }
162
163 @Test
incrementalModifynull164 fun incrementalModify() {
165 setupSimpleBuildGradle()
166 testData("incremental-test-data/add_nav.xml").copyTo(navResource("add_nav.xml"))
167
168 runGradle("generateSafeArgsDebug").assertSuccessfulTask("generateSafeArgsDebug")
169 val mainLastMod = assertGenerated("debug/$MAIN_DIRECTIONS").lastModified()
170 val additionalLastMod = assertGenerated("debug/$ADDITIONAL_DIRECTIONS").lastModified()
171 assertGenerated("debug/$NEXT_DIRECTIONS")
172
173 testData("incremental-test-data/modified_nav.xml").copyTo(navResource("nav_test.xml"), true)
174
175 // lastModified has one second precision on certain platforms and jdk versions
176 // so sleep for a second
177 Thread.sleep(SEC)
178 runGradle("generateSafeArgsDebug").assertSuccessfulTask("generateSafeArgsDebug")
179 val newMainLastMod = assertGenerated("debug/$MAIN_DIRECTIONS").lastModified()
180 // main directions were regenerated
181 assertThat(newMainLastMod, not(mainLastMod))
182
183 // but additional directions weren't touched
184 val newAdditionalLastMod = assertGenerated("debug/$ADDITIONAL_DIRECTIONS").lastModified()
185 assertThat(newAdditionalLastMod, `is`(additionalLastMod))
186
187 assertGenerated("debug/$MODIFIED_NEXT_DIRECTIONS")
188 assertNotGenerated("debug/$NEXT_DIRECTIONS")
189 }
190
191 @Test
incrementalRemovenull192 fun incrementalRemove() {
193 setupSimpleBuildGradle()
194 testData("incremental-test-data/add_nav.xml").copyTo(navResource("add_nav.xml"))
195
196 runGradle("generateSafeArgsDebug").assertSuccessfulTask("generateSafeArgsDebug")
197 val mainLastMod = assertGenerated("debug/$MAIN_DIRECTIONS").lastModified()
198 assertGenerated("debug/$ADDITIONAL_DIRECTIONS")
199
200 val wasRemoved = navResource("add_nav.xml").delete()
201 assertThat(wasRemoved, `is`(true))
202
203 // lastModified has one second precision on certain platforms and jdk versions
204 // so sleep for a second
205 Thread.sleep(SEC)
206 runGradle("generateSafeArgsDebug").assertSuccessfulTask("generateSafeArgsDebug")
207 val newMainLastMod = assertGenerated("debug/$MAIN_DIRECTIONS").lastModified()
208 // main directions weren't touched
209 assertThat(newMainLastMod, `is`(mainLastMod))
210
211 // but additional directions are removed
212 assertNotGenerated("debug/$ADDITIONAL_DIRECTIONS")
213 }
214
215 @Test
invalidModifynull216 fun invalidModify() {
217 setupSimpleBuildGradle()
218 testData("incremental-test-data/add_nav.xml").copyTo(navResource("add_nav.xml"))
219 runGradle("generateSafeArgsDebug").assertSuccessfulTask("generateSafeArgsDebug")
220 val step1MainLastMod = assertGenerated("debug/$MAIN_DIRECTIONS").lastModified()
221 val step1AdditionalLastMod = assertGenerated("debug/$ADDITIONAL_DIRECTIONS").lastModified()
222 assertGenerated("debug/$NEXT_DIRECTIONS")
223
224 testData("invalid/failing_nav.xml").copyTo(navResource("nav_test.xml"), true)
225 Thread.sleep(SEC)
226 runAndFailGradle("generateSafeArgsDebug").assertFailingTask("generateSafeArgsDebug")
227 val step2MainLastMod = assertGenerated("debug/$MAIN_DIRECTIONS").lastModified()
228 // main directions were regenerated
229 assertThat(step2MainLastMod, not(step1MainLastMod))
230
231 // but additional directions weren't touched
232 val step2AdditionalLastMod = assertGenerated("debug/$ADDITIONAL_DIRECTIONS").lastModified()
233 assertThat(step2AdditionalLastMod, `is`(step1AdditionalLastMod))
234
235 val step2ModifiedTime = assertGenerated("debug/$MODIFIED_NEXT_DIRECTIONS").lastModified()
236 assertNotGenerated("debug/$NEXT_DIRECTIONS")
237
238 testData("incremental-test-data/modified_nav.xml").copyTo(navResource("nav_test.xml"), true)
239 Thread.sleep(SEC)
240 runGradle("generateSafeArgsDebug").assertSuccessfulTask("generateSafeArgsDebug")
241
242 // additional directions are touched because once task failed,
243 // gradle next time makes full run
244 val step3AdditionalLastMod = assertGenerated("debug/$ADDITIONAL_DIRECTIONS").lastModified()
245 assertThat(step3AdditionalLastMod, not(step2AdditionalLastMod))
246
247 val step3ModifiedTime = assertGenerated("debug/$MODIFIED_NEXT_DIRECTIONS").lastModified()
248 assertThat(step2ModifiedTime, not(step3ModifiedTime))
249 }
250 }
251
testDatanull252 private fun testData(name: String) = File("src/test/test-data", name)
253
254 private fun BuildResult.assertSuccessfulTask(name: String): BuildResult {
255 assertThat(task(":$name")!!.outcome, `is`(TaskOutcome.SUCCESS))
256 return this
257 }
258
BuildResultnull259 private fun BuildResult.assertFailingTask(name: String): BuildResult {
260 assertThat(task(":$name")!!.outcome, `is`(TaskOutcome.FAILED))
261 return this
262 }