Skip to content

Commit b3a72b8

Browse files
authored
Increase test parallelism (#493)
Enables parallel test method execution to improve build performance. Tweak maxParallelForks to avoid the overhead for the faster test suites. ~40% build time reduction in one test class of `cleanIntTest intTest` and in one of `cleanExTest examplesTest`. ~20% reduction on `check --rerun-tasks`. `cleanIntTest intTest`, test filter for `ExampleGradleTaskTest` <img width="1624" height="1056" alt="Screenshot 2025-09-13 at 17 17 35" src="https://github.com/user-attachments/assets/318e01e5-f9bf-4ad7-8e48-8e6b6e61509c" /> `cleanExTest examplesTest`, test filter for `NotebooksTest` <img width="1624" height="1056" alt="Screenshot 2025-09-13 at 17 17 42" src="https://github.com/user-attachments/assets/31b3ba53-5e47-48dd-bf87-571a6864a407" /> `check --rerun-tasks` <img width="1624" height="1056" alt="Screenshot 2025-09-13 at 19 05 05" src="https://github.com/user-attachments/assets/8e85b32c-f4ee-4386-aadc-92a41328ebf6" />
1 parent 035d272 commit b3a72b8

File tree

5 files changed

+71
-79
lines changed

5 files changed

+71
-79
lines changed

library/build.gradle.kts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,24 +116,26 @@ tasks.named("compileKotlin", KotlinCompile::class) {
116116
}
117117

118118
tasks.withType<Test>().configureEach {
119-
maxParallelForks = 4
120-
systemProperty(
121-
"junit.jupiter.tempdir.cleanup.mode.default",
122-
System.getProperty("junit.jupiter.tempdir.cleanup.mode.default") ?: "always",
123-
)
119+
systemProperty("junit.jupiter.execution.parallel.enabled", "true")
120+
systemProperty("junit.jupiter.execution.parallel.mode.default", "same_thread")
121+
val cleanupMode = System.getProperty("junit.jupiter.tempdir.cleanup.mode.default")
122+
?: "always"
123+
systemProperty("junit.jupiter.tempdir.cleanup.mode.default", cleanupMode)
124124
}
125125

126126
tasks.named<Test>("test") {
127127
environment = emptyMap()
128128
}
129129

130130
tasks.named<Test>("integrationTest") {
131+
maxParallelForks = 2
131132
environment = emptyMap()
132133
}
133134

134135
val publishUnsignedSnapshotDevelocityApiKotlinPublicationToMavenLocal by tasks.getting
135136

136137
tasks.named<Test>("examplesTest") {
138+
maxParallelForks = 4
137139
inputs.files(files(publishUnsignedSnapshotDevelocityApiKotlinPublicationToMavenLocal))
138140
.withPropertyName("snapshotPublicationArtifacts")
139141
.withNormalizer(ClasspathNormalizer::class)

library/src/examplesTest/kotlin/com/gabrielfeo/develocity/api/example/gradle/ExampleGradleTaskTest.kt

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,26 @@ package com.gabrielfeo.develocity.api.example.gradle
22

33
import org.junit.jupiter.api.Assertions.assertFalse
44
import org.junit.jupiter.api.Assertions.assertTrue
5-
import org.junit.jupiter.api.BeforeEach
6-
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation
7-
import org.junit.jupiter.api.Order
8-
import org.junit.jupiter.api.TestMethodOrder
95
import org.junit.jupiter.api.Test
106
import org.junit.jupiter.api.io.TempDir
117
import java.nio.file.Path
128
import com.gabrielfeo.develocity.api.copyFromResources
139
import com.gabrielfeo.develocity.api.example.BuildStartTime
1410
import com.gabrielfeo.develocity.api.example.runInShell
11+
import org.junit.jupiter.api.parallel.Execution
12+
import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
1513
import kotlin.io.path.div
1614

17-
@TestMethodOrder(OrderAnnotation::class)
15+
@Execution(CONCURRENT)
1816
class ExampleGradleTaskTest {
1917

20-
@TempDir
21-
lateinit var tempDir: Path
22-
23-
private val projectDir
24-
get() = tempDir / "examples/example-gradle-task"
25-
26-
@BeforeEach
27-
fun setup() {
28-
copyFromResources("/examples", tempDir)
29-
copyFromResources("/${ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY}", tempDir)
30-
copyFromResources("/${ResourceInitScripts.REQUIRE_JAVA_11_COMPATIBILITY}", tempDir)
18+
class TestPaths(val rootDir: Path) {
19+
val initScriptsDir = rootDir
20+
val projectDir = rootDir / "examples/example-gradle-task"
3121
}
3222

3323
@Test
34-
@Order(1)
35-
fun smokeTest() {
24+
fun ensureRunBuildUsesSnapshotDependency(@TempDir tempDir: Path) = with(setup(tempDir)) {
3625
val dependencies = runBuild(":buildSrc:dependencies --configuration runtimeClasspath").stdout
3726
val libraryMatches = dependencies.lines().filter { "develocity-api-kotlin" in it }
3827
assertTrue(libraryMatches.isNotEmpty())
@@ -41,28 +30,36 @@ class ExampleGradleTaskTest {
4130
}
4231
}
4332

33+
private fun setup(tempDir: Path): TestPaths {
34+
copyFromResources("/examples", tempDir)
35+
copyFromResources("/${ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY}", tempDir)
36+
copyFromResources("/${ResourceInitScripts.REQUIRE_JAVA_11_COMPATIBILITY}", tempDir)
37+
return TestPaths(tempDir)
38+
}
39+
4440
@Test
45-
fun testBuildPerformanceMetricsTask() {
41+
fun testBuildPerformanceMetricsTask(@TempDir tempDir: Path) = with(setup(tempDir)) {
4642
val args = "--user runner --period=${BuildStartTime.RECENT}"
4743
val output = runBuild("userBuildPerformanceMetrics $args").stdout
4844
assertPerformanceMetricsOutput(output, user = "runner", period = BuildStartTime.RECENT)
4945
}
5046

5147
@Test
52-
fun testJavaVersionCompatibility() {
53-
val initScript = tempDir / ResourceInitScripts.REQUIRE_JAVA_11_COMPATIBILITY
48+
fun testJavaVersionCompatibility(@TempDir tempDir: Path) = with(setup(tempDir)) {
49+
val initScript = initScriptsDir / ResourceInitScripts.REQUIRE_JAVA_11_COMPATIBILITY
5450
val output = runBuild("-p buildSrc :generateExternalPluginSpecBuilders -I '$initScript'").stdout
5551
assertFalse(Regex("""FAILED|Could not resolve|No matching variant""").containsMatchIn(output))
5652
}
5753

58-
private fun runBuild(gradleArgs: String) =
54+
private fun TestPaths.runBuild(gradleArgs: String) =
5955
runInShell(
6056
projectDir,
6157
"./gradlew --stacktrace --no-daemon",
62-
"-I ${tempDir / ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY}",
58+
"-I ${initScriptsDir / ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY}",
6359
gradleArgs,
6460
)
6561

62+
@Suppress("SameParameterValue")
6663
private fun assertPerformanceMetricsOutput(
6764
output: String,
6865
user: String,

library/src/examplesTest/kotlin/com/gabrielfeo/develocity/api/example/gradle/ExampleProjectTest.kt

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,25 @@ package com.gabrielfeo.develocity.api.example.gradle
22

33
import com.gabrielfeo.develocity.api.example.Queries
44
import org.junit.jupiter.api.Assertions.assertTrue
5-
import org.junit.jupiter.api.BeforeEach
6-
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation
7-
import org.junit.jupiter.api.Order
8-
import org.junit.jupiter.api.TestMethodOrder
95
import org.junit.jupiter.api.Test
106
import org.junit.jupiter.api.io.TempDir
117
import java.nio.file.Path
128
import kotlin.io.path.div
139
import com.gabrielfeo.develocity.api.copyFromResources
1410
import com.gabrielfeo.develocity.api.example.runInShell
11+
import org.junit.jupiter.api.parallel.Execution
12+
import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
1513

16-
@TestMethodOrder(OrderAnnotation::class)
14+
@Execution(CONCURRENT)
1715
class ExampleProjectTest {
1816

19-
@TempDir
20-
lateinit var tempDir: Path
21-
22-
private val projectDir
23-
get() = tempDir / "examples/example-project"
24-
25-
private val initScriptPath
26-
get() = tempDir / ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY
27-
28-
@BeforeEach
29-
fun setup() {
30-
copyFromResources("/examples", tempDir)
31-
copyFromResources("/${ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY}", tempDir)
17+
class TestPaths(val rootDir: Path) {
18+
val projectDir = rootDir / "examples/example-project"
19+
val initScriptPath = rootDir / ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY
3220
}
3321

3422
@Test
35-
@Order(1)
36-
fun smokeTest() {
23+
fun ensureRunBuildUsesSnapshotDependencies(@TempDir tempDir: Path) = with(setup(tempDir)) {
3724
val dependencies = runBuild("dependencies --configuration runtimeClasspath").stdout
3825
val libraryMatches = dependencies.lines().filter { "develocity-api-kotlin" in it }
3926
assertTrue(libraryMatches.isNotEmpty())
@@ -42,16 +29,22 @@ class ExampleProjectTest {
4229
}
4330
}
4431

32+
private fun setup(tempDir: Path): TestPaths {
33+
copyFromResources("/examples", tempDir)
34+
copyFromResources("/${ResourceInitScripts.FORCE_SNAPSHOT_LIBRARY}", tempDir)
35+
return TestPaths(tempDir)
36+
}
37+
4538
@Test
46-
fun testExampleProject() {
39+
fun testExampleProject(@TempDir tempDir: Path) = with(setup(tempDir)) {
4740
val output = runBuild("""run --args '"${Queries.FAST}"'""").stdout
4841
val tableRegex = Regex("""(?ms)^[-]+\nMost frequent builds:\n\s*\n(.+\|\s*\d+\s*\n?)+""")
4942
assertTrue(tableRegex.containsMatchIn(output)) {
5043
"Expected match for pattern '$tableRegex' in output '$output'"
5144
}
5245
}
5346

54-
private fun runBuild(gradleArgs: String) =
47+
private fun TestPaths.runBuild(gradleArgs: String) =
5548
runInShell(
5649
projectDir,
5750
"./gradlew --stacktrace --no-daemon",

library/src/examplesTest/kotlin/com/gabrielfeo/develocity/api/example/notebook/NotebooksTest.kt

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,27 @@ import com.gabrielfeo.develocity.api.copyFromResources
44
import com.gabrielfeo.develocity.api.example.JsonAdapter
55
import com.gabrielfeo.develocity.api.example.Queries
66
import org.junit.jupiter.api.Assertions.assertTrue
7-
import org.junit.jupiter.api.BeforeEach
87
import org.junit.jupiter.api.Test
98
import org.junit.jupiter.api.assertDoesNotThrow
109
import org.junit.jupiter.api.io.TempDir
10+
import org.junit.jupiter.api.parallel.Execution
11+
import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
1112
import java.net.URI
1213
import java.nio.file.Path
1314
import kotlin.io.path.Path
1415
import kotlin.io.path.absolute
1516
import kotlin.io.path.div
1617
import kotlin.io.path.writeText
1718

19+
@Execution(CONCURRENT)
1820
class NotebooksTest {
1921

20-
@TempDir
21-
lateinit var tempDir: Path
22-
23-
lateinit var venv: PythonVenv
24-
25-
lateinit var jupyter: Jupyter
26-
27-
@BeforeEach
28-
fun setup() {
29-
copyFromResources("/examples", tempDir)
30-
venv = PythonVenv(tempDir / ".venv").also {
31-
it.installRequirements(
32-
requirementsFile = tempDir / "examples/example-notebooks/requirements.txt",
33-
linksDir = Path(System.getProperty("downloaded-requirements-path")).absolute(),
34-
)
35-
}
36-
jupyter = Jupyter(tempDir, venv.dir)
37-
}
38-
3922
@Test
40-
fun testMostFrequentBuildsNotebook() {
23+
fun testMostFrequentBuildsNotebook(@TempDir tempDir: Path) {
24+
val jupyter = setup(tempDir)
4125
val sourceNotebook = tempDir / "examples/example-notebooks/MostFrequentBuilds.ipynb"
42-
val fasterNotebook = forceUseOfFasterQuery(sourceNotebook)
43-
val snapshotNotebook = forceUseOfMavenLocalSnapshotArtifact(fasterNotebook)
26+
val fasterNotebook = jupyter.forceUseOfFasterQuery(sourceNotebook)
27+
val snapshotNotebook = jupyter.forceUseOfMavenLocalSnapshotArtifact(fasterNotebook)
4428
val executedNotebook = assertDoesNotThrow { jupyter.executeNotebook(snapshotNotebook) }
4529
with(JsonAdapter.fromJson(executedNotebook.outputNotebook).asNotebookJson()) {
4630
assertTrue(textOutputLines.any { Regex("""Collected \d+ builds from the API""").containsMatchIn(it) }) {
@@ -55,27 +39,39 @@ class NotebooksTest {
5539
}
5640
}
5741

58-
private fun forceUseOfFasterQuery(sourceNotebook: Path): Path = jupyter.replacePattern(
42+
private fun setup(tempDir: Path): Jupyter {
43+
copyFromResources("/examples", tempDir)
44+
val venv = PythonVenv(tempDir / ".venv").also {
45+
it.installRequirements(
46+
requirementsFile = tempDir / "examples/example-notebooks/requirements.txt",
47+
linksDir = Path(System.getProperty("downloaded-requirements-path")).absolute(),
48+
)
49+
}
50+
return Jupyter(tempDir, venv.dir)
51+
}
52+
53+
private fun Jupyter.forceUseOfFasterQuery(sourceNotebook: Path): Path = replacePattern(
5954
path = sourceNotebook,
6055
pattern = Regex("""query\s*=.+,"""),
6156
replacement = """query = "${Queries.FAST}",""",
6257
)
6358

6459
@Test
65-
fun testLoggingNotebook() {
60+
fun testLoggingNotebook(@TempDir tempDir: Path) {
61+
val jupyter = setup(tempDir)
6662
val sourceNotebook = tempDir / "examples/example-notebooks/Logging.ipynb"
67-
val snapshotNotebook = forceUseOfMavenLocalSnapshotArtifact(sourceNotebook)
63+
val snapshotNotebook = jupyter.forceUseOfMavenLocalSnapshotArtifact(sourceNotebook)
6864
val executedNotebook = assertDoesNotThrow { jupyter.executeNotebook(snapshotNotebook) }
6965
val kernelLogs = executedNotebook.outputStreams.stderr
7066
assertTrue(kernelLogs.contains("gabrielfeo.develocity.api.Cache - HTTP cache", ignoreCase = true))
7167
}
7268

73-
private fun forceUseOfMavenLocalSnapshotArtifact(sourceNotebook: Path): Path {
69+
private fun Jupyter.forceUseOfMavenLocalSnapshotArtifact(sourceNotebook: Path): Path {
7470
val mavenLocal = Path(System.getProperty("user.home"), ".m2/repository").toUri()
75-
val libraryDescriptor = (tempDir / "develocity-api-kotlin.json").apply {
71+
val libraryDescriptor = (workDir / "develocity-api-kotlin.json").apply {
7672
writeText(buildLibraryDescriptor(version = "SNAPSHOT", repository = mavenLocal))
7773
}
78-
return jupyter.replacePattern(
74+
return replacePattern(
7975
path = sourceNotebook,
8076
pattern = Regex("(?:DependsOn|%use).*develocity-api-kotlin.*"),
8177
replacement = """
@@ -86,6 +82,7 @@ class NotebooksTest {
8682
)
8783
}
8884

85+
@Suppress("SameParameterValue")
8986
private fun buildLibraryDescriptor(version: String, repository: URI) = """
9087
{
9188
"dependencies": ["com.gabrielfeo:develocity-api-kotlin:$version"],

library/src/integrationTest/kotlin/com/gabrielfeo/develocity/api/internal/jupyter/DevelocityApiJupyterIntegrationTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ import com.google.common.reflect.ClassPath.ClassInfo
55
import org.intellij.lang.annotations.Language
66
import org.jetbrains.kotlinx.jupyter.api.Code
77
import org.jetbrains.kotlinx.jupyter.testkit.JupyterReplTestCase
8+
import org.junit.jupiter.api.parallel.Execution
9+
import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
810
import kotlin.reflect.KVisibility
911
import kotlin.test.Test
1012
import kotlin.test.assertEquals
1113
import kotlin.test.assertNotEquals
1214

1315
@ExperimentalStdlibApi
16+
@Execution(CONCURRENT)
1417
class DevelocityApiJupyterIntegrationTest : JupyterReplTestCase() {
1518

1619
@Test

0 commit comments

Comments
 (0)