1
1
package com.jaredsburrows.spoon
2
2
3
- import com.android.build.gradle.api.ApkVariantOutput
4
- import com.android.build.gradle.api.TestVariant
5
3
import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner.TestSize
6
4
import com.squareup.spoon.SpoonRunner
7
5
import org.gradle.api.DefaultTask
8
6
import org.gradle.api.GradleException
9
7
import org.gradle.api.Task
10
- import org.gradle.api.reporting.ReportingExtension
11
8
import org.gradle.api.tasks.Internal
12
9
import org.gradle.api.tasks.OutputDirectory
13
10
import org.gradle.api.tasks.TaskAction
@@ -18,96 +15,86 @@ import java.time.Duration
18
15
open class SpoonTask : DefaultTask () { // tasks can't be final
19
16
20
17
/* * Results baseOutputDir. */
21
- @get:OutputDirectory var outputDir: File
18
+ @get:OutputDirectory lateinit var outputDir: File
19
+ @Internal lateinit var buildDir: File
22
20
23
- /* * Variant of the test */
24
- @Internal lateinit var variant : TestVariant
21
+ /* * For testing only. */
22
+ @get: Internal internal var testing : Boolean = false
25
23
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
28
29
29
30
/* * 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
31
38
32
39
init {
33
- // From DefaultTask
34
40
description = " Run instrumentation tests for '$name ' variant."
35
41
group = " Verification"
36
-
37
- // Customizing internal task options
38
- outputDir = project.extensions.getByType(ReportingExtension ::class .java)
39
- .file(SpoonExtension .DEFAULT_OUTPUT_DIRECTORY )
40
42
}
41
43
42
44
@TaskAction
43
45
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()) {
46
47
throw IllegalStateException (
47
- " '${extension .methodName} ' must have a fully qualified class name."
48
+ " '${spoonExtension .methodName} ' must have a fully qualified class name."
48
49
)
49
50
}
50
51
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
62
53
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
64
55
}
65
- outputDir = File (outputBase, variant.testedVariant.name )
56
+ outputDir = File (outputBase, variantName )
66
57
67
58
val builder = SpoonRunner .Builder ()
68
- .setTitle(extension .title)
59
+ .setTitle(spoonExtension .title)
69
60
.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)
80
71
.setTerminateAdb(false )
81
- .setSingleInstrumentationCall(extension .singleInstrumentationCall)
82
- .setClearAppDataBeforeEachTest(extension .clearAppDataBeforeEachTest)
72
+ .setSingleInstrumentationCall(spoonExtension .singleInstrumentationCall)
73
+ .setClearAppDataBeforeEachTest(spoonExtension .clearAppDataBeforeEachTest)
83
74
84
75
// APKs
85
- if (project.isNotTest() ) {
76
+ if (testing ) {
86
77
builder.setTestApk(instrumentationApk)
87
78
builder.addOtherApk(applicationApk)
88
79
}
89
80
90
81
// 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)
96
83
97
84
// 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) {
100
87
throw UnsupportedOperationException (" 'shardIndex' needs to be less than 'numShards'." )
101
88
}
102
89
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} " )
105
92
}
106
93
107
94
// If we have args apply them else let them be null
108
- if (extension .instrumentationArgs.isNotEmpty()) {
95
+ if (spoonExtension .instrumentationArgs.isNotEmpty()) {
109
96
val instrumentationArgs = hashMapOf<String , String >()
110
- extension .instrumentationArgs.forEach { instrumentation ->
97
+ spoonExtension .instrumentationArgs.forEach { instrumentation ->
111
98
if (! (instrumentation.contains(' :' ) or instrumentation.contains(' =' ))) {
112
99
throw UnsupportedOperationException (" Please use '=' or ':' to separate arguments." )
113
100
}
@@ -123,30 +110,25 @@ open class SpoonTask : DefaultTask() { // tasks can't be final
123
110
}
124
111
125
112
// 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))
128
115
}
129
116
130
117
// Add all skipped devices
131
- extension .skipDevices.forEach {
118
+ spoonExtension .skipDevices.forEach {
132
119
builder.skipDevice(it)
133
120
}
134
121
135
122
// Add all devices
136
- extension .devices.forEach {
123
+ spoonExtension .devices.forEach {
137
124
builder.addDevice(it)
138
125
}
139
126
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) {
142
129
throw GradleException (
143
130
" Tests failed! See ${ConsoleRenderer .asClickableFileUrl(File (outputDir, " index.html" ))} "
144
131
)
145
132
}
146
133
}
147
-
148
- companion object {
149
- private const val ANDROID_EXTENSION_NAME = " android"
150
- private const val SDK_DIRECTORY_METHOD = " getSdkDirectory"
151
- }
152
134
}
0 commit comments