blob: b805725a09860112dc2acc634b65108f143c5c45 [file] [log] [blame]
/*
* Copyright 2023 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
import com.android.build.api.artifact.Artifacts
import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.BuiltArtifactsLoader
import com.android.build.api.variant.HasAndroidTest
import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
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.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import org.gradle.kotlin.dsl.getByType
import org.gradle.process.ExecOperations
import org.gradle.work.DisableCachingByDefault
@DisableCachingByDefault(because = "Expected to rerun every time")
abstract class FtlRunner : DefaultTask() {
init {
group = "Verification"
description = "Runs devices tests in Firebase Test Lab filtered by --className"
}
@get:Inject
abstract val execOperations: ExecOperations
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val testFolder: DirectoryProperty
@get:Internal
abstract val testLoader: Property<BuiltArtifactsLoader>
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:Optional
abstract val appFolder: DirectoryProperty
@get:Internal
abstract val appLoader: Property<BuiltArtifactsLoader>
@get:Input
abstract val apkPackageName: Property<String>
@get:Optional
@get:Input
@get:Option(option = "className", description = "Fully qualified class name of a class to run")
abstract val className: Property<String>
@get:Optional
@get:Input
@get:Option(option = "packageName", description = "Package name test classes to run")
abstract val packageName: Property<String>
@get:Optional
@get:Input
@get:Option(option = "pullScreenshots", description = "true if screenshots should be pulled")
abstract val pullScreenshots: Property<String>
@get:Input
abstract val device: Property<String>
@TaskAction
fun execThings() {
val testApk = testLoader.get().load(testFolder.get())
?: throw RuntimeException("Cannot load required APK for task: $name")
val testApkPath = testApk.elements.single().outputFile
val appApkPath = if (appLoader.isPresent) {
val appApk = appLoader.get().load(appFolder.get())
?: throw RuntimeException("Cannot load required APK for task: $name")
appApk.elements.single().outputFile
} else {
"gs://androidx-ftl-test-results/github-ci-action/placeholderApp/" +
"aadb5e0219ce132e73236ef1e06bb50dd60217e20e803ea00d57a1cf1cea902c.apk"
}
try {
execOperations.exec {
it.commandLine("gcloud", "--version")
}
} catch (exception: Exception) {
throw Exception(
"Missing gcloud, please follow go/androidx-dev#remote-build-cache to set it up"
)
}
val hasFilters = className.isPresent || packageName.isPresent
val filters = listOfNotNull(
if (className.isPresent) "class ${className.get()}" else null,
if (packageName.isPresent) "package ${packageName.get()}" else null,
).joinToString(separator = ",")
val shouldPull = pullScreenshots.isPresent && pullScreenshots.get() == "true"
execOperations.exec {
it.commandLine(
listOfNotNull(
"gcloud",
"--project",
"androidx-dev-prod",
"firebase",
"test",
"android",
"run",
"--type",
"instrumentation",
"--no-performance-metrics",
"--no-auto-google-login",
"--device",
"model=${device.get()},locale=en_US,orientation=portrait",
"--app",
appApkPath,
"--test",
testApkPath,
if (hasFilters) "--test-targets" else null,
if (hasFilters) filters else null,
if (shouldPull) "--directories-to-pull" else null,
if (shouldPull) {
"/sdcard/Android/data/${apkPackageName.get()}/cache/androidx_screenshots"
} else null,
)
)
}
}
}
private val devicesToRunOn = listOf(
"ftlpixel2api33" to "Pixel2.arm,version=33",
"ftlpixel2api30" to "Pixel2.arm,version=30",
"ftlpixel2api28" to "Pixel2.arm,version=28",
"ftlpixel2api26" to "Pixel2.arm,version=26",
"ftlnexus4api21" to "Nexus4,version=21",
)
fun Project.configureFtlRunner() {
extensions.getByType(AndroidComponentsExtension::class.java).apply {
onVariants { variant ->
var name: String? = null
var artifacts: Artifacts? = null
var apkPackageName: Provider<String>? = null
when {
variant is HasAndroidTest -> {
name = variant.androidTest?.name
artifacts = variant.androidTest?.artifacts
apkPackageName = variant.androidTest?.namespace
}
project.plugins.hasPlugin("com.android.test") -> {
name = variant.name
artifacts = variant.artifacts
apkPackageName = variant.namespace
}
}
if (name == null || artifacts == null || apkPackageName == null) {
return@onVariants
}
devicesToRunOn.forEach { (taskPrefix, model) ->
tasks.register("$taskPrefix$name", FtlRunner::class.java) { task ->
task.device.set(model)
task.apkPackageName.set(apkPackageName)
task.testFolder.set(artifacts.get(SingleArtifact.APK))
task.testLoader.set(artifacts.getBuiltArtifactsLoader())
}
}
}
}
}
fun Project.addAppApkToFtlRunner() {
extensions.getByType<ApplicationAndroidComponentsExtension>().apply {
onVariants(selector().withBuildType("debug")) { appVariant ->
devicesToRunOn.forEach { (taskPrefix, _) ->
tasks.named("$taskPrefix${appVariant.name}AndroidTest") { configTask ->
configTask as FtlRunner
configTask.appFolder.set(appVariant.artifacts.get(SingleArtifact.APK))
configTask.appLoader.set(appVariant.artifacts.getBuiltArtifactsLoader())
}
}
}
}
}