Skip to content

Commit 92e1318

Browse files
k4k7us23pavel.krylovRolf-Smit
authored
Fix: Coverage ignored when executeAndroidTests is false and running on Gradle Managed Devices (#104)
When `executeAndroidTests` is set to false and the Android integration tests have run manually using Gradle Managed Devices the coverage results are not picked-up by the `rootCoverageReport` and/or `coverageReport` tasks, this is now fixed. --------- Co-authored-by: pavel.krylov <pavel.krylov@bitrix.ru> Co-authored-by: Rolf Smit <rolf@neotech.nl>
1 parent 01bed93 commit 92e1318

File tree

4 files changed

+124
-42
lines changed

4 files changed

+124
-42
lines changed

plugin/src/main/kotlin/org/neotech/plugin/rootcoverage/RootCoveragePlugin.kt

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.gradle.api.GradleException
1111
import org.gradle.api.NamedDomainObjectContainer
1212
import org.gradle.api.Plugin
1313
import org.gradle.api.Project
14+
import org.gradle.api.Task
1415
import org.gradle.api.tasks.TaskProvider
1516
import org.gradle.api.tasks.testing.Test
1617
import org.gradle.testing.jacoco.plugins.JacocoPlugin
@@ -64,7 +65,7 @@ class RootCoveragePlugin : Plugin<Project> {
6465
}
6566

6667
private fun createCoverageTaskForRoot(project: Project) {
67-
val task = project.createJacocoReportTask(
68+
val rootCoverageTask = project.createJacocoReportTask(
6869
taskName = "rootCoverageReport",
6970
taskGroup = "reporting",
7071
taskDescription = "Generates a Jacoco report with combined results from all the subprojects.",
@@ -76,11 +77,12 @@ class RootCoveragePlugin : Plugin<Project> {
7677
}
7778

7879
// Configure the root task with sub-tasks for the sub-projects.
79-
task.project.subprojects.forEach {
80+
rootCoverageTask.project.subprojects.forEach {
8081
it.afterEvaluate { subProject ->
8182
subProject.applyConfiguration()
8283
}
83-
task.addSubProject(it)
84+
rootCoverageTask.addSubProject(it)
85+
8486
createSubProjectCoverageTask(it)
8587
}
8688

@@ -136,47 +138,28 @@ class RootCoveragePlugin : Plugin<Project> {
136138
}
137139

138140
private fun JacocoReport.addSubProjectVariant(subProject: Project, variant: Variant, buildType: BuildType) {
139-
val name = variant.name.replaceFirstChar(Char::titlecase)
141+
val variantName = variant.name.replaceFirstChar(Char::titlecase)
140142

141143
// Gets the relative path from this task to the subProject
142144
val path = project.relativeProjectPath(subProject.path)
143145

144146
// Add dependencies to the test tasks of the subProject
145147
if (rootProjectExtension.shouldExecuteUnitTests() && (buildType.enableUnitTestCoverage || buildType.isTestCoverageEnabled)) {
146-
dependsOn("$path:test${name}UnitTest")
148+
dependsOn("$path:test${variantName}UnitTest")
147149
}
148150

149-
var runsOnGradleManagedDevices = false
151+
val androidTestTask = getAndroidTestTask(subProject, variant)
152+
val runsOnGradleManagedDevices = androidTestTask.runsOnGradleManagedDevices
150153

151154
if (rootProjectExtension.shouldExecuteAndroidTests() && (buildType.enableAndroidTestCoverage || buildType.isTestCoverageEnabled)) {
152-
153-
// Attempt to run on instrumented tests, giving priority to the following devices in this order:
154-
// - A user provided Gradle Managed Device.
155-
// - All Gradle Managed Devices if any is available.
156-
// - All through ADB connected devices.
157-
val gradleManagedDevices = subProject.extensions.getByType(BaseExtension::class.java).testOptions.managedDevices.devices
158-
if (rootProjectExtension.runOnGradleManagedDevices && !rootProjectExtension.gradleManagedDeviceName.isNullOrEmpty()) {
159-
runsOnGradleManagedDevices = true
160-
dependsOn("$path:${rootProjectExtension.gradleManagedDeviceName}${name}AndroidTest")
161-
} else if (rootProjectExtension.runOnGradleManagedDevices && gradleManagedDevices.isNotEmpty()) {
162-
runsOnGradleManagedDevices = true
163-
dependsOn("$path:allDevices${name}AndroidTest")
164-
} else {
165-
dependsOn("$path:connected${name}AndroidTest")
166-
}
155+
dependsOn(androidTestTask.taskPath)
167156
} else {
168157
// If this plugin should not run instrumented tests on it's own, at least make sure it runs after those tasks (if they are
169158
// selected to run as well and exists).
170159
//
171160
// In theory we don't need to do this if `rootProjectExtension.includeAndroidTestResults` is false, so we could check that, but
172161
// it also does not hurt.
173-
174-
val executeAndroidTestsOnGradleManagedDevicesTask = project.tasks.findByPath("$path:allDevices${name}AndroidTest")
175-
if(executeAndroidTestsOnGradleManagedDevicesTask != null) {
176-
// This task only exists if a Gradle Managed Device is configured, which may not be the case.
177-
mustRunAfter("$path:allDevices${name}AndroidTest")
178-
}
179-
mustRunAfter("$path:connected${name}AndroidTest")
162+
mustRunAfter(androidTestTask.taskPath)
180163
}
181164

182165
sourceDirectories.from(variant.sources.java?.all)
@@ -200,6 +183,41 @@ class RootCoveragePlugin : Plugin<Project> {
200183
)
201184
}
202185

186+
private class AndroidTestTask(
187+
val taskPath: String,
188+
val runsOnGradleManagedDevices: Boolean
189+
)
190+
191+
private fun JacocoReport.getAndroidTestTask(subProject: Project, variant: Variant): AndroidTestTask {
192+
// Gets the relative path from this task to the subProject
193+
val path = project.relativeProjectPath(subProject.path)
194+
val variantName = variant.name.replaceFirstChar(Char::titlecase)
195+
196+
197+
// Attempt to run on instrumented tests, giving priority to the following devices in this order:
198+
// - A user provided Gradle Managed Device.
199+
// - All Gradle Managed Devices if any is available.
200+
// - All through ADB connected devices.
201+
val gradleManagedDevices = subProject.extensions.getByType(BaseExtension::class.java).testOptions.managedDevices.devices
202+
203+
if (rootProjectExtension.runOnGradleManagedDevices && !rootProjectExtension.gradleManagedDeviceName.isNullOrEmpty()) {
204+
return AndroidTestTask(
205+
taskPath = "$path:${rootProjectExtension.gradleManagedDeviceName}${variantName}AndroidTest",
206+
runsOnGradleManagedDevices = true
207+
)
208+
} else if (rootProjectExtension.runOnGradleManagedDevices && gradleManagedDevices.isNotEmpty()) {
209+
return AndroidTestTask(
210+
taskPath = "$path:allDevices${variantName}AndroidTest",
211+
runsOnGradleManagedDevices = true
212+
)
213+
} else {
214+
return AndroidTestTask(
215+
taskPath = "$path:connected${variantName}AndroidTest",
216+
runsOnGradleManagedDevices = false
217+
)
218+
}
219+
}
220+
203221
/**
204222
* Apply configuration from [RootCoveragePluginExtension] to the project.
205223
*/

plugin/src/test/kotlin/org/neotech/plugin/rootcoverage/IntegrationTest.kt

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.junit.runners.Parameterized
1111
import org.neotech.plugin.rootcoverage.util.SimpleTemplate
1212
import org.neotech.plugin.rootcoverage.util.SystemOutputWriter
1313
import org.neotech.plugin.rootcoverage.util.assertSuccessful
14+
import org.neotech.plugin.rootcoverage.util.assertTaskNotExecuted
1415
import org.neotech.plugin.rootcoverage.util.assertTaskSuccess
1516
import org.neotech.plugin.rootcoverage.util.createGradlePropertiesFile
1617
import org.neotech.plugin.rootcoverage.util.createLocalPropertiesFile
@@ -86,28 +87,36 @@ class IntegrationTest(
8687
})
8788
})
8889

90+
val executeAndroidTests = configuration.pluginConfiguration.getPropertyValue("executeAndroidTests", "true").toBoolean()
91+
8992
// Note: rootCodeCoverageReport is the old and deprecated name of the rootCoverageReport task, it is
9093
// used to check whether the old name properly aliases to the new task name.
91-
val gradleCommands = if (configuration.pluginConfiguration.getPropertyValue("executeAndroidTests") == "false") {
92-
listOf("clean", "connectedDebugAndroidTest", "coverageReport", "rootCodeCoverageReport", "--stacktrace")
94+
val gradleCommands = if (!executeAndroidTests) {
95+
val runOnGradleManagedDevices = configuration.pluginConfiguration.getPropertyValue("runOnGradleManagedDevices") ?: "false"
96+
97+
// Execute Android tests completely separately (as if run on some external service,
98+
// after which the resulting files have been imported)
99+
if (runOnGradleManagedDevices == "false") {
100+
executeGradleTasks(listOf("clean", "connectedDebugAndroidTest"))
101+
} else {
102+
executeGradleTasks(listOf("clean", "nexusoneapi30DebugAndroidTest"))
103+
}
104+
105+
listOf("coverageReport", "rootCodeCoverageReport", "--stacktrace")
93106
} else {
94107
listOf("clean", "coverageReport", "rootCodeCoverageReport", "--stacktrace")
95108
}
96109

97-
val runner = GradleRunner.create()
98-
.withProjectDir(projectRoot)
99-
.withGradleVersion(gradleVersion)
100-
.withPluginClasspath()
101-
.forwardStdOutput(SystemOutputWriter.out())
102-
.forwardStdError(SystemOutputWriter.err())
103-
.withArguments(gradleCommands)
104-
105-
val result = runner.build()
110+
val result = executeGradleTasks(gradleCommands)
106111

107112
result.assertSuccessful()
108113

109114
// Assert whether the correct Android Test tasks are executed
110-
result.assertCorrectAndroidTestTasksAreExecuted()
115+
if(executeAndroidTests) {
116+
result.assertCorrectAndroidTestTasksAreExecuted()
117+
} else {
118+
result.assertCorrectAndroidTestTasksAreNotExecuted()
119+
}
111120

112121
// Assert whether the combined coverage report is what we expected
113122
result.assertRootCoverageReport()
@@ -121,18 +130,28 @@ class IntegrationTest(
121130

122131
private fun BuildResult.assertCorrectAndroidTestTasksAreExecuted() {
123132
if (configuration.pluginConfiguration.getPropertyValue("runOnGradleManagedDevices", "false").toBoolean()) {
124-
// Assert that the tests have been run on Gradle Managed Devices
125133
val device = configuration.pluginConfiguration.getPropertyValue("gradleManagedDeviceName", "allDevices")
126134
assertTaskSuccess(":app:${device}DebugAndroidTest")
127135
assertTaskSuccess(":library_android:${device}DebugAndroidTest")
128136

129137
} else {
130-
// Assert that the tests have been run on connected devices
131138
assertTaskSuccess(":app:connectedDebugAndroidTest")
132139
assertTaskSuccess(":library_android:connectedDebugAndroidTest")
133140
}
134141
}
135142

143+
private fun BuildResult.assertCorrectAndroidTestTasksAreNotExecuted() {
144+
if (configuration.pluginConfiguration.getPropertyValue("runOnGradleManagedDevices", "false").toBoolean()) {
145+
val device = configuration.pluginConfiguration.getPropertyValue("gradleManagedDeviceName", "allDevices")
146+
assertTaskNotExecuted(":app:${device}DebugAndroidTest")
147+
assertTaskNotExecuted(":library_android:${device}DebugAndroidTest")
148+
149+
} else {
150+
assertTaskNotExecuted(":app:connectedDebugAndroidTest")
151+
assertTaskNotExecuted(":library_android:connectedDebugAndroidTest")
152+
}
153+
}
154+
136155
private fun BuildResult.assertRootCoverageReport() {
137156
assertTaskSuccess(":rootCoverageReport")
138157

@@ -191,6 +210,17 @@ class IntegrationTest(
191210
}
192211
}
193212

213+
private fun executeGradleTasks(tasks: List<String>): BuildResult {
214+
return GradleRunner.create()
215+
.withProjectDir(projectRoot)
216+
.withGradleVersion(gradleVersion)
217+
.withPluginClasspath()
218+
.forwardStdOutput(SystemOutputWriter.out())
219+
.forwardStdError(SystemOutputWriter.err())
220+
.withArguments(tasks)
221+
.build()
222+
}
223+
194224
companion object {
195225

196226
@Suppress("unused") // This method is used by the JVM (Parameterized JUnit Runner)

plugin/src/test/kotlin/org/neotech/plugin/rootcoverage/util/GradleBuildExtensions.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,8 @@ fun BuildResult.assertSuccessful() {
1010

1111
fun BuildResult.assertTaskSuccess(taskPath: String) {
1212
assertThat(task(taskPath)!!.outcome).isEqualTo(TaskOutcome.SUCCESS)
13+
}
14+
15+
fun BuildResult.assertTaskNotExecuted(taskPath: String) {
16+
assertThat(task(taskPath)).isNull()
1317
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
projectConfiguration:
2+
addGradleManagedDevice: true
3+
pluginConfiguration:
4+
properties:
5+
- name: generateHtml
6+
value: true
7+
- name: generateXml
8+
value: false
9+
- name: generateCsv
10+
value: true
11+
12+
- name: buildVariant
13+
value: debug
14+
15+
- name: executeUnitTests
16+
value: true
17+
- name: executeAndroidTests
18+
value: false
19+
20+
- name: includeUnitTestResults
21+
value: true
22+
- name: includeAndroidTestResults
23+
value: true
24+
- name: includeNoLocationClasses
25+
value: true
26+
27+
- name: runOnGradleManagedDevices
28+
value: true
29+
- name: gradleManagedDeviceName
30+
value: nexusoneapi30

0 commit comments

Comments
 (0)