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