blob: f82adad559491095a1b7c69e3d72061af5810a06 [file] [log] [blame]
/*
* 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 androidx.build.dependencyTracker.ProjectSubset
import androidx.build.renameApkForTesting
import com.android.build.api.variant.BuiltArtifactsLoader
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File
/**
* Writes a configuration file in
* <a href=https://source.android.com/devices/tech/test_infra/tradefed/testing/through-suite/android-test-structure>AndroidTest.xml</a>
* format that gets zipped alongside the APKs to be tested.
* This config gets ingested by Tradefed.
*/
abstract class GenerateTestConfigurationTask : DefaultTask() {
@get:InputFiles
@get:Optional
abstract val appFolder: DirectoryProperty
@get:Internal
abstract val appLoader: Property<BuiltArtifactsLoader>
@get:Input
@get:Optional
abstract val appProjectPath: Property<String>
@get:InputFiles
abstract val testFolder: DirectoryProperty
@get:Internal
abstract val testLoader: Property<BuiltArtifactsLoader>
@get:Input
abstract val testProjectPath: Property<String>
@get:Input
abstract val minSdk: Property<Int>
@get:Input
abstract val hasBenchmarkPlugin: Property<Boolean>
@get:Input
abstract val testRunner: Property<String>
@get:Input
abstract val affectedModuleDetectorSubset: Property<ProjectSubset>
@get:OutputFile
abstract val outputXml: RegularFileProperty
@get:OutputFile
abstract val constrainedOutputXml: RegularFileProperty
@TaskAction
fun generateAndroidTestZip() {
writeConfigFileContent(constrainedOutputXml, true)
writeConfigFileContent(outputXml)
}
private fun writeConfigFileContent(
outputFile: RegularFileProperty,
isConstrained: Boolean = true
) {
/*
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()
if (appLoader.isPresent) {
val appApk = appLoader.get().load(appFolder.get())
?: throw RuntimeException("Cannot load required APK for task: $name")
// We don't need to check hasBenchmarkPlugin because benchmarks shouldn't have test apps
val appName = appApk.elements.single().outputFile.substringAfterLast("/")
.renameApkForTesting(appProjectPath.get(), hasBenchmarkPlugin = false)
// TODO(b/178776319): Clean up this hardcoded hack
if (appProjectPath.get().contains("macrobenchmark-target")) {
configBuilder.appApkName(appName.replace("debug-androidTest", "release"))
} else {
configBuilder.appApkName(appName)
}
}
when (affectedModuleDetectorSubset.get()) {
ProjectSubset.CHANGED_PROJECTS -> {
configBuilder.isPostsubmit(false)
configBuilder.runAllTests(true)
}
ProjectSubset.ALL_AFFECTED_PROJECTS -> {
configBuilder.isPostsubmit(true)
configBuilder.runAllTests(true)
}
ProjectSubset.DEPENDENT_PROJECTS -> {
configBuilder.isPostsubmit(false)
// Don't ever run full tests of RV if it is dependent, since they take > 45 minutes
if (isConstrained || testProjectPath.get().contains("recyclerview")) {
configBuilder.runAllTests(false)
} else {
configBuilder.runAllTests(true)
}
}
else -> {
throw IllegalStateException(
"$name should not be running if the AffectedModuleDetector is returning " +
"${affectedModuleDetectorSubset.get()} for this project."
)
}
}
// This section adds metadata tags that will help filter runners to specific modules.
if (hasBenchmarkPlugin.get()) {
configBuilder.isBenchmark(true)
if (configBuilder.isPostsubmit) {
configBuilder.tag("microbenchmarks")
}
} else if (testProjectPath.get().endsWith("macrobenchmark")) {
configBuilder.tag("macrobenchmarks")
} else {
configBuilder.tag("androidx_unit_tests")
if (project.path.startsWith(":compose:")) {
configBuilder.tag("compose")
} else if (project.path.startsWith(":wear:")) {
configBuilder.tag("wear")
}
}
val testApk = testLoader.get().load(testFolder.get())
?: throw RuntimeException("Cannot load required APK for task: $name")
val testName = testApk.elements.single().outputFile
.substringAfterLast("/")
.renameApkForTesting(testProjectPath.get(), hasBenchmarkPlugin.get())
configBuilder.testApkName(testName)
.applicationId(testApk.applicationId)
.minSdk(minSdk.get().toString())
.testRunner(testRunner.get())
val resolvedOutputFile: File = outputFile.asFile.get()
if (!resolvedOutputFile.exists()) {
if (!resolvedOutputFile.createNewFile()) {
throw RuntimeException(
"Failed to create test configuration file: $resolvedOutputFile"
)
}
}
resolvedOutputFile.writeText(configBuilder.build())
}
}