Skip to content

Commit 1cd936d

Browse files
authored
Fix UI test coverage (#2549)
Create a jacoco server for the life of our ui tests that listens to coverage from the IDE
1 parent 584947a commit 1cd936d

File tree

5 files changed

+123
-2
lines changed

5 files changed

+123
-2
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ tasks.createRelease.configure {
4242

4343
dependencies {
4444
aggregateCoverage(project(":intellij"))
45+
aggregateCoverage(project(":ui-tests"))
4546
}
4647

4748
tasks.register("runIde") {

buildSrc/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencies {
3030
testImplementation("com.pinterest.ktlint:ktlint-core:$ktlintVersion")
3131
testImplementation("com.pinterest.ktlint:ktlint-test:$ktlintVersion")
3232

33+
implementation("org.jacoco:org.jacoco.core:${JacocoPlugin.DEFAULT_JACOCO_VERSION}")
3334
implementation("org.gradle:test-retry-gradle-plugin:$gradleRetryPluginVersion")
3435
implementation("com.adarshr:gradle-test-logger-plugin:$gradleTestLoggerPlugin")
3536

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.gradle.jacoco
5+
6+
import org.gradle.api.file.RegularFileProperty
7+
import org.gradle.api.services.BuildService
8+
import org.gradle.api.services.BuildServiceParameters
9+
import org.gradle.api.tasks.testing.Test
10+
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
11+
import org.jacoco.core.data.ExecutionData
12+
import org.jacoco.core.data.ExecutionDataWriter
13+
import org.jacoco.core.data.IExecutionDataVisitor
14+
import org.jacoco.core.data.ISessionInfoVisitor
15+
import org.jacoco.core.data.SessionInfo
16+
import org.jacoco.core.runtime.RemoteControlReader
17+
import org.jacoco.core.runtime.RemoteControlWriter
18+
import java.net.ServerSocket
19+
import java.net.Socket
20+
import java.util.concurrent.atomic.AtomicBoolean
21+
22+
class RemoteCoverage private constructor(task: Test) {
23+
companion object {
24+
fun enableRemoteCoverage(task: Test) = RemoteCoverage(task)
25+
26+
private const val DEFAULT_JACOCO_PORT = 6300
27+
}
28+
29+
init {
30+
task.extensions.findByType(JacocoTaskExtension::class.java)?.let {
31+
val execFile = it.destinationFile ?: return@let
32+
33+
val jacocoServer = task.project.gradle.sharedServices.registerIfAbsent("jacocoServer", JacocoServer::class.java) {
34+
if (!execFile.exists()) {
35+
task.project.mkdir(execFile.parentFile)
36+
execFile.createNewFile()
37+
}
38+
39+
parameters.execFile.set(execFile)
40+
}
41+
42+
task.doFirst {
43+
jacocoServer.get().start()
44+
}
45+
} ?: task.logger.warn("$task does not have Jacoco enabled on it")
46+
}
47+
48+
abstract class JacocoServer : BuildService<JacocoServer.Params>, AutoCloseable {
49+
interface Params : BuildServiceParameters {
50+
val execFile: RegularFileProperty
51+
}
52+
53+
private val serverSocket = ServerSocket(DEFAULT_JACOCO_PORT)
54+
private val signalShutdown = AtomicBoolean(false)
55+
56+
private val outputStream = parameters.execFile.asFile.get().outputStream()
57+
private val fileWriter = ExecutionDataWriter(outputStream)
58+
private val serverThread = Thread {
59+
while (!signalShutdown.get()) {
60+
val clientSocket = serverSocket.accept()
61+
JacocoHandler(clientSocket, fileWriter).start()
62+
}
63+
}
64+
65+
fun start() {
66+
serverThread.start()
67+
}
68+
69+
override fun close() {
70+
signalShutdown.set(true)
71+
serverThread.interrupt()
72+
73+
outputStream.close()
74+
}
75+
}
76+
77+
private class JacocoHandler(private val socket: Socket, private val fileWriter: ExecutionDataWriter) : Thread(), ISessionInfoVisitor,
78+
IExecutionDataVisitor {
79+
80+
override fun run() {
81+
socket.use {
82+
socket.getInputStream().use { input ->
83+
socket.getOutputStream().use { output ->
84+
85+
val reader = RemoteControlReader(input)
86+
reader.setSessionInfoVisitor(this)
87+
reader.setExecutionDataVisitor(this)
88+
89+
RemoteControlWriter(output)
90+
91+
@Suppress("ControlFlowWithEmptyBody")
92+
// Read all the data from jacoco
93+
while (reader.read()) {
94+
}
95+
synchronized(fileWriter) { fileWriter.flush() }
96+
}
97+
}
98+
}
99+
}
100+
101+
override fun visitSessionInfo(info: SessionInfo) {
102+
synchronized(fileWriter) { fileWriter.visitSessionInfo(info) }
103+
}
104+
105+
override fun visitClassExecution(data: ExecutionData) {
106+
synchronized(fileWriter) { fileWriter.visitClassExecution(data) }
107+
}
108+
}
109+
}

buildSrc/src/main/kotlin/toolkit-intellij-subplugin.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension.Output
45
import org.jetbrains.intellij.tasks.DownloadRobotServerPluginTask
56
import org.jetbrains.intellij.tasks.RunIdeForUiTestTask
67
import software.aws.toolkits.gradle.IdeVersions
@@ -9,7 +10,6 @@ import software.aws.toolkits.gradle.findFolders
910
import software.aws.toolkits.gradle.intellij
1011
import software.aws.toolkits.gradle.intellij.ToolkitIntelliJExtension
1112
import software.aws.toolkits.gradle.intellij.ToolkitIntelliJExtension.IdeFlavor
12-
import java.time.Instant
1313

1414
val toolkitIntelliJ = project.extensions.create<ToolkitIntelliJExtension>("intellijToolkit")
1515

@@ -142,7 +142,7 @@ afterEvaluate {
142142

143143
configure<JacocoTaskExtension> {
144144
includes = listOf("software.aws.toolkits.*")
145-
setDestinationFile(file("$buildDir/jacoco/${Instant.now()}-jacocoUiTests.exec"))
145+
output = Output.TCP_CLIENT // Dump to our jacoco server instead of to a file
146146
}
147147
}
148148
}

ui-tests/build.gradle.kts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import software.aws.toolkits.gradle.jacoco.RemoteCoverage.Companion.enableRemoteCoverage
2+
13
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
24
// SPDX-License-Identifier: Apache-2.0
35

@@ -55,4 +57,12 @@ tasks.register<Test>("uiTestCore") {
5557
useJUnitPlatform {
5658
includeTags("core")
5759
}
60+
61+
// We disable coverage for the JVM running our UI tests, we are running a TCP server that the sandbox IDE dumps to when it exits
62+
// This is transparent to coverageReport creation since the coverage gets associated with this tasks jacoco output
63+
configure<JacocoTaskExtension> {
64+
isEnabled = false
65+
}
66+
67+
enableRemoteCoverage(this)
5868
}

0 commit comments

Comments
 (0)