blob: f025ebe6ef5efd596abfbbcf9b9dff63bf0afe6f [file] [log] [blame]
/*
* Copyright (C) 2015 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 com.android.build.gradle.internal.test
import com.android.SdkConstants
import com.android.build.gradle.internal.component.InstrumentedTestCreationConfig
import com.android.build.gradle.internal.tasks.databinding.DATA_BINDING_TRIGGER_CLASS
import com.android.build.gradle.internal.tasks.extractApkFilesBypassingBundleTool
import com.android.build.gradle.internal.testing.StaticTestData
import com.android.build.gradle.internal.testing.TestData
import com.android.builder.testing.api.DeviceConfigProvider
import com.android.ide.common.util.toPathString
import com.google.common.collect.ImmutableList
import com.google.common.io.Files
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.Directory
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
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 java.io.File
import java.nio.file.Path
import java.util.zip.ZipFile
/**
* Common implementation of [TestData] for embedded test projects (in androidTest folder)
* and separate module test projects.
*/
abstract class AbstractTestDataImpl(
@get:Input
val namespace: Provider<String>,
creationConfig: InstrumentedTestCreationConfig,
override val testApkDir: Provider<Directory>,
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:Optional
val testedApksDir: Provider<Directory>?,
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
@get:Optional
val privacySandboxSdkApks: FileCollection?,
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:Optional
val privacySandboxCompatSdkApks: Provider<Directory>?,
@get:InputFiles
@get:PathSensitive(PathSensitivity.NONE)
@get:Optional
val additionalSdkSupportedSplitApks: Provider<Directory>?,
extraInstrumentationTestRunnerArgs: Map<String, String>
) : TestData {
@get:Internal
val privacyInstallBundlesFinder: ApkBundlesFinder
get() = _privacyInstallBundlesFinder ?:
object: ApkBundlesFinder {
val privacySandboxApks: Set<File>? = privacySandboxSdkApks?.files
override fun findBundles(
deviceConfigProvider: DeviceConfigProvider
): List<List<Path>> {
privacySandboxApks ?: return emptyList()
val privacySandboxInstallBundles = ImmutableList.builder<List<Path>>()
privacySandboxApks.forEach { apk ->
privacySandboxInstallBundles.add(
extractApkFilesBypassingBundleTool(apk.toPath()))
}
return privacySandboxInstallBundles.build()
}
}.also { _privacyInstallBundlesFinder = it }
private var _privacyInstallBundlesFinder: ApkBundlesFinder? = null
@get:Internal
abstract val testedApksFinder: ApksFinder
override val applicationId = creationConfig.applicationId
// Note: creationConfig.testedApplicationId returns the instrumentation target application ID.
// testedApplicationId and instrumentationTargetPackageID are usually the same value
// except for the one case where there are test.apk and app.apk and the self-instrumeting
// flag is enabled. See TestApplicationTestData class.
override val testedApplicationId = creationConfig.testedApplicationId
override val instrumentationTargetPackageId = creationConfig.testedApplicationId
override val instrumentationRunner = creationConfig.instrumentationRunner
final override val instrumentationRunnerArguments =
creationConfig.services.mapProperty(String::class.java, String::class.java)
override var animationsDisabled = creationConfig.services.provider { false }
override val testCoverageEnabled =
creationConfig.services.provider { creationConfig.codeCoverageEnabled }
override val minSdkVersion = creationConfig.services.provider { creationConfig.minSdk }
override val flavorName = creationConfig.services.provider { creationConfig.flavorName ?: "" }
override val testDirectories: ConfigurableFileCollection =
creationConfig.services.fileCollection().also { fileCollection ->
// For now we check if there are any test sources. We could inspect the test classes and
// apply JUnit logic to see if there's something to run, but that would not catch the case
// where user makes a typo in a test name or forgets to inherit from a JUnit class
creationConfig.sources.java { javaSources -> fileCollection.from(javaSources.all) }
creationConfig.sources.kotlin { kotlinSources -> fileCollection.from(kotlinSources.all) }
}
init {
// lazily set the instrumentationRunnerArguments
instrumentationRunnerArguments.set(creationConfig.instrumentationRunnerArguments)
instrumentationRunnerArguments.putAll(extraInstrumentationTestRunnerArgs)
// memoize the value which makes it similar to `by lazy`
instrumentationRunnerArguments.finalizeValueOnRead()
}
override fun getAsStaticData(): StaticTestData {
return StaticTestData(
applicationId.get(),
testedApplicationId.orNull,
instrumentationTargetPackageId.get(),
instrumentationRunner.get(),
instrumentationRunnerArguments.get(),
animationsDisabled.get(),
testCoverageEnabled.get(),
minSdkVersion.get(),
libraryType.get(),
flavorName.get(),
getTestApk().get(),
testDirectories.files.toList(),
testedApksFinder,
privacyInstallBundlesFinder
)
}
override fun hasTests(
allClasses: FileCollection,
rClasses: FileCollection,
buildConfig: FileCollection
): Provider<Boolean> =
allClasses
.minus(rClasses)
.minus(buildConfig).elements.map { testClasses ->
val namespaceDir = namespace.get().replace('.', '/')
val DATA_BINDER_MAPPER_IMPL = "DataBinderMapperImpl"
val ignoredPaths = setOf(
"${namespaceDir}/${SdkConstants.FN_BUILD_CONFIG_BASE}${SdkConstants.DOT_CLASS}",
"${namespaceDir}/${SdkConstants.FN_MANIFEST_BASE}${SdkConstants.DOT_CLASS}",
"${namespaceDir}/${DATA_BINDING_TRIGGER_CLASS}${SdkConstants.DOT_CLASS}",
"${namespaceDir}/$DATA_BINDER_MAPPER_IMPL${SdkConstants.DOT_CLASS}",
)
val regexIgnoredPaths = setOf(
"androidx/databinding/.*\\${SdkConstants.DOT_CLASS}".toRegex(), // Classes in androidx/databinding
"${namespaceDir}/$DATA_BINDER_MAPPER_IMPL\\\$.*\\${SdkConstants.DOT_CLASS}".toRegex(), // DataBinderMapplerImpl inner classes
".*/BR${SdkConstants.DOT_CLASS}".toRegex(), // BR.class files
)
val isNotIgnoredClass = { relativePath: String ->
Files.getFileExtension(relativePath)==SdkConstants.EXT_CLASS &&
relativePath !in ignoredPaths &&
!regexIgnoredPaths.any { it.matches(relativePath) }
}
for (fileSystemLocation in testClasses) {
val jarOrDirectory = fileSystemLocation.asFile
if (!jarOrDirectory.exists()) {
continue
}
if (jarOrDirectory.isDirectory) {
for (file in jarOrDirectory.walk()) {
if (isNotIgnoredClass(jarOrDirectory.toPath()
.relativize(file.toPath())
.toPathString().portablePath)) {
return@map true
}
}
} else {
ZipFile(jarOrDirectory).use {
for (entry in it.entries()) {
if (isNotIgnoredClass(entry.name)) {
return@map true
}
}
}
}
}
false
}
override fun findTestedApks(deviceConfigProvider: DeviceConfigProvider): List<File> =
testedApksFinder.findApks(deviceConfigProvider)
override fun privacySandboxInstallBundlesFinder(
deviceConfigProvider: DeviceConfigProvider): List<List<Path>> =
privacyInstallBundlesFinder.findBundles(deviceConfigProvider)
}