Skip to content

Commit 688c21c

Browse files
Merge pull request #108 from jaredsburrows/pr/jaredsburrows/configuration-cache
configuration cache
2 parents 0777f22 + d47a879 commit 688c21c

File tree

4 files changed

+124
-71
lines changed

4 files changed

+124
-71
lines changed

gradle-spoon-plugin/src/main/kotlin/com/jaredsburrows/spoon/SpoonExtension.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ open class SpoonExtension { // extensions cannot be final
6060
/** Test method name to run (must also use className) */
6161
var methodName: String = ""
6262

63-
/** Code coverage flag. For Spoon to calculate coverage file your app must have the
63+
/**
64+
* Code coverage flag. For Spoon to calculate coverage file your app must have the
6465
* `WRITE_EXTERNAL_STORAGE` permission. (false by default)
6566
* (This option pulls the coverage file from all devices and merge them into a single file
6667
* `merged-coverage.ec`.)
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
package com.jaredsburrows.spoon
22

3-
import com.android.build.gradle.api.ApkVariantOutput
4-
import com.android.build.gradle.api.TestVariant
53
import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner.TestSize
64
import com.squareup.spoon.SpoonRunner
75
import org.gradle.api.DefaultTask
86
import org.gradle.api.GradleException
97
import org.gradle.api.Task
10-
import org.gradle.api.reporting.ReportingExtension
118
import org.gradle.api.tasks.Internal
129
import org.gradle.api.tasks.OutputDirectory
1310
import org.gradle.api.tasks.TaskAction
@@ -18,96 +15,86 @@ import java.time.Duration
1815
open class SpoonTask : DefaultTask() { // tasks can't be final
1916

2017
/** Results baseOutputDir. */
21-
@get:OutputDirectory var outputDir: File
18+
@get:OutputDirectory lateinit var outputDir: File
19+
@Internal lateinit var buildDir: File
2220

23-
/** Variant of the test */
24-
@Internal lateinit var variant: TestVariant
21+
/** For testing only. */
22+
@get:Internal internal var testing: Boolean = false
2523

26-
/** Application APK (eg. app-debug.apk). */
27-
private lateinit var applicationApk: File
24+
/** Spoon Extension. */
25+
@get:Internal internal lateinit var spoonExtension: SpoonExtension
26+
27+
/** Android SDK directory */
28+
@get:Internal internal lateinit var sdkDirectory: File
2829

2930
/** Instrumentation APK (eg. app-debug-androidTest.apk). */
30-
private lateinit var instrumentationApk: File
31+
@get:Internal internal lateinit var instrumentationApk: File
32+
33+
/** Application APK (eg. app-debug.apk). */
34+
@get:Internal internal lateinit var applicationApk: File
35+
36+
/** The variant ran by Spoon. */
37+
@get:Internal internal lateinit var variantName: String
3138

3239
init {
33-
// From DefaultTask
3440
description = "Run instrumentation tests for '$name' variant."
3541
group = "Verification"
36-
37-
// Customizing internal task options
38-
outputDir = project.extensions.getByType(ReportingExtension::class.java)
39-
.file(SpoonExtension.DEFAULT_OUTPUT_DIRECTORY)
4042
}
4143

4244
@TaskAction
4345
fun spoonTask() {
44-
val extension = project.extensions.getByType(SpoonExtension::class.java)
45-
if (extension.className.isEmpty() && extension.methodName.isNotEmpty()) {
46+
if (spoonExtension.className.isEmpty() && spoonExtension.methodName.isNotEmpty()) {
4647
throw IllegalStateException(
47-
"'${extension.methodName}' must have a fully qualified class name."
48+
"'${spoonExtension.methodName}' must have a fully qualified class name."
4849
)
4950
}
5051

51-
instrumentationApk = variant.outputs.first().outputFile
52-
val testedOutput = variant.testedVariant.outputs.first()
53-
// This is a hack for library projects.
54-
// We supply the same apk as an application and instrumentation to the soon runner.
55-
applicationApk = if (testedOutput is ApkVariantOutput) {
56-
testedOutput.outputFile
57-
} else {
58-
instrumentationApk
59-
}
60-
61-
var outputBase = extension.baseOutputDir
52+
var outputBase = spoonExtension.baseOutputDir
6253
if (SpoonExtension.DEFAULT_OUTPUT_DIRECTORY == outputBase) {
63-
outputBase = File(project.buildDir, SpoonExtension.DEFAULT_OUTPUT_DIRECTORY).path
54+
outputBase = File(buildDir, SpoonExtension.DEFAULT_OUTPUT_DIRECTORY).path
6455
}
65-
outputDir = File(outputBase, variant.testedVariant.name)
56+
outputDir = File(outputBase, variantName)
6657

6758
val builder = SpoonRunner.Builder()
68-
.setTitle(extension.title)
59+
.setTitle(spoonExtension.title)
6960
.setOutputDirectory(outputDir)
70-
.setDebug(extension.debug)
71-
.setNoAnimations(extension.noAnimations)
72-
.setAdbTimeout(Duration.ofSeconds(extension.adbTimeout.toLong()))
73-
.setClassName(extension.className)
74-
.setAllowNoDevices(extension.allowNoDevices)
75-
.setSequential(extension.sequential)
76-
.setGrantAll(extension.grantAll)
77-
.setMethodName(extension.methodName)
78-
.setCodeCoverage(extension.codeCoverage)
79-
.setShard(extension.shard)
61+
.setDebug(spoonExtension.debug)
62+
.setNoAnimations(spoonExtension.noAnimations)
63+
.setAdbTimeout(Duration.ofSeconds(spoonExtension.adbTimeout.toLong()))
64+
.setClassName(spoonExtension.className)
65+
.setAllowNoDevices(spoonExtension.allowNoDevices)
66+
.setSequential(spoonExtension.sequential)
67+
.setGrantAll(spoonExtension.grantAll)
68+
.setMethodName(spoonExtension.methodName)
69+
.setCodeCoverage(spoonExtension.codeCoverage)
70+
.setShard(spoonExtension.shard)
8071
.setTerminateAdb(false)
81-
.setSingleInstrumentationCall(extension.singleInstrumentationCall)
82-
.setClearAppDataBeforeEachTest(extension.clearAppDataBeforeEachTest)
72+
.setSingleInstrumentationCall(spoonExtension.singleInstrumentationCall)
73+
.setClearAppDataBeforeEachTest(spoonExtension.clearAppDataBeforeEachTest)
8374

8475
// APKs
85-
if (project.isNotTest()) {
76+
if (testing) {
8677
builder.setTestApk(instrumentationApk)
8778
builder.addOtherApk(applicationApk)
8879
}
8980

9081
// File and add the SDK
91-
val android = project.extensions.findByName(ANDROID_EXTENSION_NAME)
92-
val sdkFolder = android?.javaClass?.getMethod(SDK_DIRECTORY_METHOD)?.invoke(android) as File?
93-
sdkFolder?.let {
94-
builder.setAndroidSdk(sdkFolder)
95-
}
82+
builder.setAndroidSdk(sdkDirectory)
9683

9784
// Add shard information to instrumentation args if there are any
98-
if (extension.numShards > 0) {
99-
if (extension.shardIndex >= extension.numShards) {
85+
if (spoonExtension.numShards > 0) {
86+
if (spoonExtension.shardIndex >= spoonExtension.numShards) {
10087
throw UnsupportedOperationException("'shardIndex' needs to be less than 'numShards'.")
10188
}
10289

103-
extension.instrumentationArgs.add("numShards:${extension.numShards}")
104-
extension.instrumentationArgs.add("shardIndex:${extension.shardIndex}")
90+
spoonExtension.instrumentationArgs.add("numShards:${spoonExtension.numShards}")
91+
spoonExtension.instrumentationArgs.add("shardIndex:${spoonExtension.shardIndex}")
10592
}
10693

10794
// If we have args apply them else let them be null
108-
if (extension.instrumentationArgs.isNotEmpty()) {
95+
if (spoonExtension.instrumentationArgs.isNotEmpty()) {
10996
val instrumentationArgs = hashMapOf<String, String>()
110-
extension.instrumentationArgs.forEach { instrumentation ->
97+
spoonExtension.instrumentationArgs.forEach { instrumentation ->
11198
if (!(instrumentation.contains(':') or instrumentation.contains('='))) {
11299
throw UnsupportedOperationException("Please use '=' or ':' to separate arguments.")
113100
}
@@ -123,30 +110,25 @@ open class SpoonTask : DefaultTask() { // tasks can't be final
123110
}
124111

125112
// Only apply test size if given, no default
126-
if (extension.testSize.isNotEmpty()) {
127-
builder.setTestSize(TestSize.getTestSize(extension.testSize))
113+
if (spoonExtension.testSize.isNotEmpty()) {
114+
builder.setTestSize(TestSize.getTestSize(spoonExtension.testSize))
128115
}
129116

130117
// Add all skipped devices
131-
extension.skipDevices.forEach {
118+
spoonExtension.skipDevices.forEach {
132119
builder.skipDevice(it)
133120
}
134121

135122
// Add all devices
136-
extension.devices.forEach {
123+
spoonExtension.devices.forEach {
137124
builder.addDevice(it)
138125
}
139126

140-
val success = if (project.isNotTest()) builder.build().run() else true
141-
if (!success && !extension.ignoreFailures) {
127+
val success = if (testing) builder.build().run() else true
128+
if (!success && !spoonExtension.ignoreFailures) {
142129
throw GradleException(
143130
"Tests failed! See ${ConsoleRenderer.asClickableFileUrl(File(outputDir, "index.html"))}"
144131
)
145132
}
146133
}
147-
148-
companion object {
149-
private const val ANDROID_EXTENSION_NAME = "android"
150-
private const val SDK_DIRECTORY_METHOD = "getSdkDirectory"
151-
}
152134
}

gradle-spoon-plugin/src/main/kotlin/com/jaredsburrows/spoon/projectAndroid.kt

+32-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package com.jaredsburrows.spoon
22

33
import com.android.build.gradle.AppExtension
44
import com.android.build.gradle.AppPlugin
5+
import com.android.build.gradle.BaseExtension
56
import com.android.build.gradle.LibraryExtension
67
import com.android.build.gradle.LibraryPlugin
8+
import com.android.build.gradle.api.ApkVariantOutput
79
import com.android.build.gradle.api.TestVariant
810
import org.gradle.api.DomainObjectSet
911
import org.gradle.api.Project
12+
import org.gradle.api.reporting.ReportingExtension
1013
import java.util.Locale
1114

1215
/** Returns true if Android Gradle project */
@@ -34,19 +37,22 @@ internal fun Project.configureAndroidProject() {
3437
when (it) {
3538
is AppPlugin -> {
3639
project.extensions.getByType(AppExtension::class.java).run {
37-
configureVariant(testVariants)
40+
configureVariant(this, testVariants)
3841
}
3942
}
4043
is LibraryPlugin -> {
4144
project.extensions.getByType(LibraryExtension::class.java).run {
42-
configureVariant(testVariants)
45+
configureVariant(this, testVariants)
4346
}
4447
}
4548
}
4649
}
4750
}
4851

49-
private fun Project.configureVariant(variants: DomainObjectSet<TestVariant>? = null) {
52+
private fun Project.configureVariant(
53+
baseExtension: BaseExtension,
54+
variants: DomainObjectSet<TestVariant>? = null
55+
) {
5056
// Configure tasks for all variants
5157
variants?.all { variant ->
5258
val name = variant.name.replaceFirstChar {
@@ -62,7 +68,29 @@ private fun Project.configureVariant(variants: DomainObjectSet<TestVariant>? = n
6268
if (project.isNotTest()) {
6369
it.dependsOn(variant.testedVariant.assembleProvider, variant.assembleProvider)
6470
}
65-
it.variant = variant
71+
72+
it.spoonExtension = project.extensions.getByType(SpoonExtension::class.java)
73+
it.outputDir = project.extensions.getByType(ReportingExtension::class.java)
74+
.file(SpoonExtension.DEFAULT_OUTPUT_DIRECTORY)
75+
it.buildDir = project.buildDir
76+
77+
it.testing = project.isNotTest()
78+
79+
val instrumentationApk = variant.outputs.first().outputFile
80+
it.instrumentationApk = instrumentationApk
81+
82+
val testedOutput = variant.testedVariant.outputs.first()
83+
// This is a hack for library projects.
84+
// We supply the same apk as an application and instrumentation to the soon runner.
85+
it.applicationApk = if (testedOutput is ApkVariantOutput) {
86+
testedOutput.outputFile
87+
} else {
88+
instrumentationApk
89+
}
90+
91+
it.sdkDirectory = baseExtension.sdkDirectory
92+
93+
it.variantName = variant.testedVariant.name
6694
}
6795
}
6896
}

gradle-spoon-plugin/src/test/groovy/com/jaredsburrows/spoon/SpoonPluginSpec.groovy

+42
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.jaredsburrows.spoon
22

33
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
4+
import static org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE
45
import static test.TestUtils.gradleWithCommand
56
import static test.TestUtils.gradleWithCommandWithFail
67

@@ -230,4 +231,45 @@ final class SpoonPluginSpec extends Specification {
230231
'com.android.library',
231232
]
232233
}
234+
235+
def 'apply with plugin with configuration cache'() {
236+
given:
237+
buildFile <<
238+
"""
239+
buildscript {
240+
repositories {
241+
mavenCentral()
242+
google()
243+
}
244+
245+
dependencies {
246+
classpath files($classpathString)
247+
}
248+
}
249+
250+
apply plugin: 'com.android.application'
251+
apply plugin: 'com.jaredsburrows.spoon'
252+
253+
android {
254+
compileSdkVersion $compileSdkVersion
255+
256+
defaultConfig {
257+
applicationId 'com.example'
258+
}
259+
}
260+
"""
261+
262+
when:
263+
def firstResult = gradleWithCommand(testProjectDir.root, '--configuration-cache', 'spoonDebugAndroidTest', '-s', '-Ptesting')
264+
265+
then:
266+
firstResult.task(':spoonDebugAndroidTest').outcome == SUCCESS
267+
268+
and:
269+
def secondResult = gradleWithCommand(testProjectDir.root, '--configuration-cache', 'spoonDebugAndroidTest', '-s', '-Ptesting')
270+
271+
then:
272+
secondResult.task(':spoonDebugAndroidTest').outcome == UP_TO_DATE
273+
secondResult.output.contains('Reusing configuration cache.')
274+
}
233275
}

0 commit comments

Comments
 (0)