Skip to content

Commit

Permalink
Merge pull request #53 from Banno/configuration-cache
Browse files Browse the repository at this point in the history
Initial work toward supporting configuration caching
  • Loading branch information
joshschriever authored Aug 13, 2020
2 parents f71d5f5 + 4bf9ba7 commit 37d5eb2
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 39 deletions.
3 changes: 3 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion gordon-plugin/src/main/kotlin/com/banno/gordon/GordonPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class GordonPlugin : Plugin<Project> {
val androidPluginType = project.androidPluginType()
?: error("Gordon plugin must be applied after applying the application, library, or dynamic-feature Android plugin")

project.extensions.create<GordonExtension>("gordon")
val gordonExtension = project.extensions.create<GordonExtension>("gordon")

val androidExtension = project.extensions.getByType<TestedExtension>()

Expand Down Expand Up @@ -76,8 +76,22 @@ class GordonPlugin : Plugin<Project> {
)
}.finalizeValue()

if (androidPluginType == AndroidPluginType.DYNAMIC_FEATURE) {
this.dynamicFeatureModuleName.apply { set(project.name) }.finalizeValue()
}

this.instrumentationApk.apply { set(testVariant.apkOutputFile()) }.finalizeValue()
this.instrumentationPackage.apply { set(testVariant.applicationId) }.finalizeValue()

this.poolingStrategy.apply { set(gordonExtension.poolingStrategy) }.finalizeValue()
this.tabletShortestWidthDp.apply { set(gordonExtension.tabletShortestWidthDp) }.finalizeValue()
this.retryQuota.apply { set(gordonExtension.retryQuota) }.finalizeValue()
this.installTimeoutMillis.apply { set(gordonExtension.installTimeoutMillis) }.finalizeValue()
this.testTimeoutMillis.apply { set(gordonExtension.testTimeoutMillis) }.finalizeValue()
this.extensionTestFilter.apply { set(gordonExtension.testFilter) }.finalizeValue()
this.extensionTestInstrumentationRunner.apply { set(gordonExtension.testInstrumentationRunner) }
.finalizeValue()

this.androidInstrumentationRunnerOptions.apply { set(instrumentationRunnerOptions) }.finalizeValue()
}
}
Expand Down
83 changes: 45 additions & 38 deletions gordon-plugin/src/main/kotlin/com/banno/gordon/GordonTestTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import arrow.fx.extensions.fx
import kotlinx.coroutines.Dispatchers
import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.LogLevel
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.CacheableTask
Expand All @@ -17,82 +19,85 @@ 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.kotlin.dsl.property
import se.vidstige.jadb.JadbConnection
import java.io.File
import javax.inject.Inject

@CacheableTask
internal abstract class GordonTestTask : DefaultTask() {
internal abstract class GordonTestTask @Inject constructor(
objects: ObjectFactory,
projectLayout: ProjectLayout
) : DefaultTask() {

@get:InputFile
@get:PathSensitive(PathSensitivity.NAME_ONLY)
internal val instrumentationApk: RegularFileProperty = project.objects.fileProperty()
internal val instrumentationApk: RegularFileProperty = objects.fileProperty()

@get:InputFile
@get:PathSensitive(PathSensitivity.NAME_ONLY)
internal val applicationAab: RegularFileProperty = project.objects.fileProperty()
internal val applicationAab: RegularFileProperty = objects.fileProperty()

@get:InputFile
@get:PathSensitive(PathSensitivity.NAME_ONLY)
internal val signingKeystoreFile: RegularFileProperty = project.objects.fileProperty()
internal val signingKeystoreFile: RegularFileProperty = objects.fileProperty()

@get:Input
internal val signingConfigCredentials: Property<SigningConfigCredentials> = project.objects.property()
internal val signingConfigCredentials: Property<SigningConfigCredentials> = objects.property()

@get:Input
internal val applicationPackage: Property<String> = project.objects.property()
internal val dynamicFeatureModuleName: Property<String> = objects.property()

@get:Input
internal val instrumentationPackage: Property<String> = project.objects.property()
internal val applicationPackage: Property<String> = objects.property()

@get:Input
internal val instrumentationRunnerOptions: InstrumentationRunnerOptions
get() {
val options = androidInstrumentationRunnerOptions.get()
val extensionRunner = extensionTestInstrumentationRunner.get()

return if (extensionRunner.isNotBlank()) options.copy(testInstrumentationRunner = extensionRunner)
else options
}
internal val instrumentationPackage: Property<String> = objects.property()

@get:Input
internal val testFilters: List<String>
get() = (commandlineTestFilter.get().takeIf { it.isNotBlank() } ?: extensionTestFilter.get())
.split(',')
.filter { it.isNotBlank() }
.map { it.replace('#', '.') }
internal val poolingStrategy: Property<PoolingStrategy> = objects.property()

@get:Input
internal val poolingStrategy = project.extensions.getByType<GordonExtension>().poolingStrategy
internal val tabletShortestWidthDp: Property<Int> = objects.property()

@get:Input
internal val tabletShortestWidthDp = project.extensions.getByType<GordonExtension>().tabletShortestWidthDp
internal val retryQuota: Property<Int> = objects.property()
internal val installTimeoutMillis: Property<Long> = objects.property()
internal val testTimeoutMillis: Property<Long> = objects.property()

private val retryQuota = project.extensions.getByType<GordonExtension>().retryQuota
private val installTimeoutMillis = project.extensions.getByType<GordonExtension>().installTimeoutMillis
private val testTimeoutMillis = project.extensions.getByType<GordonExtension>().testTimeoutMillis
internal val extensionTestFilter: Property<String> = objects.property()
internal val extensionTestInstrumentationRunner: Property<String> = objects.property()

@Option(option = "tests", description = "Comma-separated packages, classes, methods, or annotations.")
val commandlineTestFilter: Property<String> = project.objects.property()
val commandlineTestFilter: Property<String> = objects.property()

private val extensionTestFilter = project.extensions.getByType<GordonExtension>().testFilter
internal val androidInstrumentationRunnerOptions: Property<InstrumentationRunnerOptions> = objects.property()

internal val androidInstrumentationRunnerOptions: Property<InstrumentationRunnerOptions> =
project.objects.property()
@get:Input
internal val testFilters: Provider<List<String>> =
commandlineTestFilter.zip(extensionTestFilter) { commandlineTestFilter, extensionTestFilter ->
(commandlineTestFilter.takeIf { it.isNotBlank() } ?: extensionTestFilter)
.split(',')
.filter { it.isNotBlank() }
.map { it.replace('#', '.') }
}

private val extensionTestInstrumentationRunner =
project.extensions.getByType<GordonExtension>().testInstrumentationRunner
@get:Input
internal val instrumentationRunnerOptions: Provider<InstrumentationRunnerOptions> =
androidInstrumentationRunnerOptions.zip(extensionTestInstrumentationRunner) { options, extensionRunner ->
if (extensionRunner.isNotBlank()) options.copy(testInstrumentationRunner = extensionRunner)
else options
}

@OutputDirectory
val testResultsDirectory: Provider<Directory> = project.layout.buildDirectory.dir("test-results/$name")
val testResultsDirectory: Provider<Directory> = projectLayout.buildDirectory.dir("test-results/$name")

@OutputDirectory
val reportDirectory: Provider<Directory> = project.layout.buildDirectory.dir("reports/$name")
val reportDirectory: Provider<Directory> = projectLayout.buildDirectory.dir("reports/$name")

init {
applicationAab.convention { PLACEHOLDER_APPLICATION_AAB }
signingKeystoreFile.convention { PLACEHOLDER_SIGNING_KEYSTORE }
dynamicFeatureModuleName.convention(PLACEHOLDER_DYNAMIC_MODULE_NAME)
applicationPackage.convention(PLACEHOLDER_APPLICATION_PACKAGE)
commandlineTestFilter.convention("")
}
Expand All @@ -109,7 +114,7 @@ internal abstract class GordonTestTask : DefaultTask() {
tabletShortestWidthDp.get().takeIf { it > -1 }
).bind()
val testCases = loadTestSuite(instrumentationApk.get().asFile).bind()
.filter { it.matchesFilter(testFilters) }
.filter { it.matchesFilter(testFilters.get()) }

when {
testCases.isEmpty() -> raiseError<Unit>(IllegalStateException("No test cases found")).bind()
Expand All @@ -122,6 +127,7 @@ internal abstract class GordonTestTask : DefaultTask() {

val applicationAab = applicationAab.get().asFile.takeUnless { it == PLACEHOLDER_APPLICATION_AAB }
val applicationPackage = applicationPackage.get().takeUnless { it == PLACEHOLDER_APPLICATION_PACKAGE }
val dynamicModuleName = dynamicFeatureModuleName.get().takeUnless { it == PLACEHOLDER_DYNAMIC_MODULE_NAME }

val signingConfig = SigningConfig(
storeFile = signingKeystoreFile.get().asFile.takeUnless { it == PLACEHOLDER_SIGNING_KEYSTORE },
Expand All @@ -135,7 +141,7 @@ internal abstract class GordonTestTask : DefaultTask() {
logger = logger,
applicationPackage = applicationPackage,
instrumentationPackage = instrumentationPackage.get(),
dynamicModule = project.name.takeIf { project.androidPluginType() == AndroidPluginType.DYNAMIC_FEATURE },
dynamicModule = dynamicModuleName,
applicationAab = applicationAab,
signingConfig = signingConfig,
instrumentationApk = instrumentationApk.get().asFile,
Expand All @@ -146,7 +152,7 @@ internal abstract class GordonTestTask : DefaultTask() {
dispatcher = Dispatchers.Default,
logger = logger,
instrumentationPackage = instrumentationPackage.get(),
instrumentationRunnerOptions = instrumentationRunnerOptions,
instrumentationRunnerOptions = instrumentationRunnerOptions.get(),
allTestCases = testCases,
allPools = pools,
retryQuota = retryQuota.get(),
Expand Down Expand Up @@ -197,4 +203,5 @@ internal fun TestCase.matchesFilter(filters: List<String>): Boolean {

private val PLACEHOLDER_APPLICATION_AAB = File.createTempFile("PLACEHOLDER_APPLICATION_AAB", null)
private val PLACEHOLDER_SIGNING_KEYSTORE = File.createTempFile("PLACEHOLDER_SIGNING_KEYSTORE", null)
private const val PLACEHOLDER_DYNAMIC_MODULE_NAME = "PLACEHOLDER_DYNAMIC_MODULE_NAME"
private const val PLACEHOLDER_APPLICATION_PACKAGE = "PLACEHOLDER_APPLICATION_PACKAGE"

0 comments on commit 37d5eb2

Please sign in to comment.