/*
* Copyright 2020 The Android Open Source Project
*
* 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.
*/
package androidx.build.testConfiguration
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.TaskAction
import org.gradle.work.DisableCachingByDefault
/**
* Writes a configuration file in AndroidTest.xml
* format that gets zipped alongside the APKs to be tested.
*
* Generates XML for Tradefed test infrastructure and JSON for FTL test infrastructure.
*/
@DisableCachingByDefault(because = "Doesn't benefit from caching")
abstract class GenerateTestConfigurationTask : DefaultTask() {
@get:Input abstract val testConfigType: Property
/** File containing [AppApksModel] with list of App APKs to install */
@get:InputFile
@get:Optional
@get:PathSensitive(PathSensitivity.NONE)
abstract val appApksModel: RegularFileProperty
/** File existence check to determine whether to run this task. */
@get:InputFiles
@get:SkipWhenEmpty
@get:PathSensitive(PathSensitivity.NONE)
abstract val androidTestSourceCodeCollection: ConfigurableFileCollection
@get:InputFile
@get:PathSensitive(PathSensitivity.NAME_ONLY)
abstract val testApk: RegularFileProperty
@get:Input abstract val applicationId: Property
@get:Input abstract val minSdk: Property
@get:Input abstract val macrobenchmark: Property
@get:Input abstract val hasBenchmarkPlugin: Property
@get:Input abstract val testRunner: Property
@get:Input abstract val presubmit: Property
@get:Input abstract val additionalApkKeys: ListProperty
@get:Input abstract val additionalTags: ListProperty
@get:Input abstract val instrumentationArgs: MapProperty
@get:OutputFile abstract val outputXml: RegularFileProperty
/**
* Optional as privacy sandbox not yet supported in JSON configs.
*
* TODO (b/347315428): Support privacy sandbox on FTL.
*/
@get:[OutputFile Optional]
abstract val outputJson: RegularFileProperty
@TaskAction
fun generateAndroidTestZip() {
/*
Testing an Android Application project involves 2 APKS: an application to be instrumented,
and a test APK. Testing an Android Library project involves only 1 APK, since the library
is bundled inside the test APK, meaning it is self instrumenting. We add extra data to
configurations testing Android Application projects, so that both APKs get installed.
*/
val configBuilder = ConfigBuilder()
configBuilder.configName(outputXml.asFile.get().name)
configBuilder.configType(testConfigType.get())
if (appApksModel.isPresent) {
val modelJson = appApksModel.get().asFile.readText()
val model = AppApksModel.fromJson(modelJson)
configBuilder.appApksModel(model)
}
configBuilder.additionalApkKeys(additionalApkKeys.get())
val isPresubmit = presubmit.get()
configBuilder.isPostsubmit(!isPresubmit)
// This section adds metadata tags that will help filter runners to specific modules.
if (hasBenchmarkPlugin.get()) {
configBuilder.isMicrobenchmark(true)
// tag microbenchmarks as "microbenchmarks" in either build config, so that benchmark
// test configs will always have something to run, regardless of build (though presubmit
// builds will still set dry run, and not output metrics)
configBuilder.tag("microbenchmarks")
if (isPresubmit) {
// in presubmit, we treat micro benchmarks as regular correctness tests as
// they run with dryRunMode to check crashes don't happen, without measurement
configBuilder.tag("androidx_unit_tests")
}
} else if (macrobenchmark.get()) {
// macro benchmarks do not have a dryRunMode, so we don't run them in presubmit
configBuilder.isMacrobenchmark(true)
configBuilder.tag("macrobenchmarks")
if (additionalTags.get().contains("wear")) {
// Wear macrobenchmarks are tagged separately to enable running on wear in CI
// standard macrobenchmarks don't currently run well on wear (b/189952249)
configBuilder.tag("wear-macrobenchmarks")
}
} else {
configBuilder.tag("androidx_unit_tests")
if (additionalTags.get().contains("compose")) {
configBuilder.tag("compose_tests")
}
}
additionalTags.get().forEach { configBuilder.tag(it) }
instrumentationArgs.get().forEach { (key, value) ->
configBuilder.instrumentationArgsMap[key] = value
}
val testApkFile = testApk.get().asFile
configBuilder
.testApkName(testApkFile.name)
.applicationId(applicationId.get())
.minSdk(minSdk.get().toString())
.testRunner(testRunner.get())
.testApkSha256(sha256(testApkFile))
createOrFail(outputXml).writeText(configBuilder.buildXml())
if (outputJson.isPresent) {
if (!outputJson.asFile.get().name.startsWith("_")) {
// Prefixing json file names with _ allows us to collocate these files
// inside of the androidTest.zip to make fetching them less expensive.
throw GradleException(
"json output file names are expected to use _ prefix to, " +
"currently set to ${outputJson.asFile.get().name}"
)
}
createOrFail(outputJson).writeText(configBuilder.buildJson())
}
}
}
internal fun createOrFail(fileProperty: RegularFileProperty): File {
val resolvedFile: File = fileProperty.asFile.get()
if (!resolvedFile.exists()) {
if (!resolvedFile.createNewFile()) {
throw RuntimeException("Failed to create test configuration file: $resolvedFile")
}
}
return resolvedFile
}