1 /*
<lambda>null2 * Copyright 2020 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.build.testConfiguration
18
19 import java.io.File
20 import org.gradle.api.DefaultTask
21 import org.gradle.api.GradleException
22 import org.gradle.api.file.ConfigurableFileCollection
23 import org.gradle.api.file.RegularFileProperty
24 import org.gradle.api.provider.ListProperty
25 import org.gradle.api.provider.MapProperty
26 import org.gradle.api.provider.Property
27 import org.gradle.api.tasks.Input
28 import org.gradle.api.tasks.InputFile
29 import org.gradle.api.tasks.InputFiles
30 import org.gradle.api.tasks.Optional
31 import org.gradle.api.tasks.OutputFile
32 import org.gradle.api.tasks.PathSensitive
33 import org.gradle.api.tasks.PathSensitivity
34 import org.gradle.api.tasks.SkipWhenEmpty
35 import org.gradle.api.tasks.TaskAction
36 import org.gradle.work.DisableCachingByDefault
37
38 /**
39 * Writes a configuration file in <a
40 * href=https://source.android.com/devices/tech/test_infra/tradefed/testing/through-suite/android-test-structure>AndroidTest.xml</a>
41 * format that gets zipped alongside the APKs to be tested.
42 *
43 * Generates XML for Tradefed test infrastructure and JSON for FTL test infrastructure.
44 */
45 @DisableCachingByDefault(because = "Doesn't benefit from caching")
46 abstract class GenerateTestConfigurationTask : DefaultTask() {
47
48 @get:Input abstract val testConfigType: Property<TestConfigType>
49
50 /** File containing [AppApksModel] with list of App APKs to install */
51 @get:InputFile
52 @get:Optional
53 @get:PathSensitive(PathSensitivity.NONE)
54 abstract val appApksModel: RegularFileProperty
55
56 /** File existence check to determine whether to run this task. */
57 @get:InputFiles
58 @get:SkipWhenEmpty
59 @get:PathSensitive(PathSensitivity.NONE)
60 abstract val androidTestSourceCodeCollection: ConfigurableFileCollection
61
62 @get:InputFile
63 @get:PathSensitive(PathSensitivity.NAME_ONLY)
64 abstract val testApk: RegularFileProperty
65
66 @get:Input abstract val applicationId: Property<String>
67
68 @get:Input abstract val minSdk: Property<Int>
69
70 @get:Input abstract val macrobenchmark: Property<Boolean>
71
72 @get:Input abstract val hasBenchmarkPlugin: Property<Boolean>
73
74 @get:Input abstract val testRunner: Property<String>
75
76 @get:Input abstract val presubmit: Property<Boolean>
77
78 @get:Input abstract val additionalApkKeys: ListProperty<String>
79
80 @get:Input abstract val additionalTags: ListProperty<String>
81
82 @get:Input abstract val instrumentationArgs: MapProperty<String, String>
83
84 @get:OutputFile abstract val outputXml: RegularFileProperty
85
86 /**
87 * Optional as privacy sandbox not yet supported in JSON configs.
88 *
89 * TODO (b/347315428): Support privacy sandbox on FTL.
90 */
91 @get:[OutputFile Optional]
92 abstract val outputJson: RegularFileProperty
93
94 @TaskAction
95 fun generateAndroidTestZip() {
96 /*
97 Testing an Android Application project involves 2 APKS: an application to be instrumented,
98 and a test APK. Testing an Android Library project involves only 1 APK, since the library
99 is bundled inside the test APK, meaning it is self instrumenting. We add extra data to
100 configurations testing Android Application projects, so that both APKs get installed.
101 */
102 val configBuilder = ConfigBuilder()
103 configBuilder.configName(outputXml.asFile.get().name)
104 configBuilder.configType(testConfigType.get())
105 if (appApksModel.isPresent) {
106 val modelJson = appApksModel.get().asFile.readText()
107 val model = AppApksModel.fromJson(modelJson)
108 configBuilder.appApksModel(model)
109 }
110
111 configBuilder.additionalApkKeys(additionalApkKeys.get())
112 val isPresubmit = presubmit.get()
113 configBuilder.isPostsubmit(!isPresubmit)
114 // This section adds metadata tags that will help filter runners to specific modules.
115 if (hasBenchmarkPlugin.get()) {
116 configBuilder.isMicrobenchmark(true)
117
118 // tag microbenchmarks as "microbenchmarks" in either build config, so that benchmark
119 // test configs will always have something to run, regardless of build (though presubmit
120 // builds will still set dry run, and not output metrics)
121 configBuilder.tag("microbenchmarks")
122
123 if (isPresubmit) {
124 // in presubmit, we treat micro benchmarks as regular correctness tests as
125 // they run with dryRunMode to check crashes don't happen, without measurement
126 configBuilder.tag("androidx_unit_tests")
127 }
128 } else if (macrobenchmark.get()) {
129 // macro benchmarks do not have a dryRunMode, so we don't run them in presubmit
130 configBuilder.isMacrobenchmark(true)
131 configBuilder.tag("macrobenchmarks")
132 if (additionalTags.get().contains("wear")) {
133 // Wear macrobenchmarks are tagged separately to enable running on wear in CI
134 // standard macrobenchmarks don't currently run well on wear (b/189952249)
135 configBuilder.tag("wear-macrobenchmarks")
136 }
137 } else {
138 configBuilder.tag("androidx_unit_tests")
139 if (additionalTags.get().contains("compose")) {
140 configBuilder.tag("compose_tests")
141 }
142 }
143 additionalTags.get().forEach { configBuilder.tag(it) }
144 instrumentationArgs.get().forEach { (key, value) ->
145 configBuilder.instrumentationArgsMap[key] = value
146 }
147 val testApkFile = testApk.get().asFile
148 configBuilder
149 .testApkName(testApkFile.name)
150 .applicationId(applicationId.get())
151 .minSdk(minSdk.get().toString())
152 .testRunner(testRunner.get())
153 .testApkSha256(sha256(testApkFile))
154 createOrFail(outputXml).writeText(configBuilder.buildXml())
155 if (outputJson.isPresent) {
156 if (!outputJson.asFile.get().name.startsWith("_")) {
157 // Prefixing json file names with _ allows us to collocate these files
158 // inside of the androidTest.zip to make fetching them less expensive.
159 throw GradleException(
160 "json output file names are expected to use _ prefix to, " +
161 "currently set to ${outputJson.asFile.get().name}"
162 )
163 }
164 createOrFail(outputJson).writeText(configBuilder.buildJson())
165 }
166 }
167 }
168
createOrFailnull169 internal fun createOrFail(fileProperty: RegularFileProperty): File {
170 val resolvedFile: File = fileProperty.asFile.get()
171 if (!resolvedFile.exists()) {
172 if (!resolvedFile.createNewFile()) {
173 throw RuntimeException("Failed to create test configuration file: $resolvedFile")
174 }
175 }
176 return resolvedFile
177 }
178