33 *
44 * https://mcdev.io/
55 *
6- * Copyright (C) 2025 minecraft-dev
6+ * Copyright (C) 2026 minecraft-dev
77 *
88 * This program is free software: you can redistribute it and/or modify
99 * it under the terms of the GNU Lesser General Public License as published
1919 */
2020
2121import io.sentry.android.gradle.extensions.SentryPluginExtension
22+ import java.util.concurrent.atomic.AtomicInteger
23+ import kotlin.concurrent.atomics.AtomicInt
2224import org.gradle.kotlin.dsl.configure
2325import org.jetbrains.changelog.Changelog
2426import org.jetbrains.gradle.ext.settings
2527import org.jetbrains.gradle.ext.taskTriggers
2628import org.jetbrains.intellij.platform.gradle.TestFrameworkType
29+ import org.jetbrains.intellij.platform.gradle.tasks.CleanSandboxTask
2730import org.jetbrains.intellij.platform.gradle.tasks.PrepareSandboxTask
2831
2932plugins {
@@ -102,6 +105,8 @@ dependencies {
102105 exclude(group = " org.slf4j" )
103106 }
104107
108+ implementation(libs.jspecify)
109+
105110 intellijPlatform {
106111 intellijIdea(libs.versions.intellij.ide) {
107112 useInstaller = false
@@ -123,7 +128,6 @@ dependencies {
123128 bundledPlugin(" org.toml.lang" )
124129 bundledPlugin(" org.jetbrains.plugins.yaml" )
125130
126-
127131 testFramework(TestFrameworkType .JUnit5 )
128132 testFramework(TestFrameworkType .Platform )
129133 testFramework(TestFrameworkType .Plugin .Java )
@@ -200,8 +204,39 @@ tasks.processResources {
200204 }
201205}
202206
207+ // Run unit tests in paralllel. Unfortunately, to accomplish this, we also need separate sandboxes for each test fork.
208+ // All of this is still worth doing since the IntelliJ test fixtures themselves are rather slow.
209+ val testForks = 6
210+ val sandboxTestTasks = mutableListOf<TaskProvider <PrepareSandboxTask >>()
211+ repeat(testForks) {
212+ sandboxTestTasks + = tasks.register<PrepareSandboxTask >(" prepareTestSandboxFork$it " ) {
213+ sandboxSuffix.set(" -fork-$it " )
214+ doFirst {
215+ sandboxDirectory.get().asFile.listFiles()
216+ ?.filter { f -> f.name.endsWith(" -fork-$it " ) }
217+ ?.forEach { f -> f.deleteRecursively() }
218+ }
219+ }
220+ }
221+ tasks.prepareTestSandbox {
222+ doFirst {
223+ sandboxDirectory.get().asFile.listFiles()
224+ ?.filter { f -> f.name.endsWith(" -test" ) }
225+ ?.forEach { f -> f.deleteRecursively() }
226+ }
227+ }
228+
229+ val cleanTestSandboxForks by tasks.registering(Delete ::class ) {
230+ doFirst {
231+ tasks.prepareTestSandbox.flatMap { it.sandboxDirectory }
232+ .get().asFile.listFiles()
233+ ?.filter { it.name.matches(Regex (" .*-(?:fork-\\ d+|test)" )) }
234+ ?.let { delete(it) }
235+ }
236+ }
203237tasks.test {
204- dependsOn(tasks.jar, testLibs)
238+ dependsOn(tasks.jar, testLibs, sandboxTestTasks)
239+ finalizedBy(cleanTestSandboxForks)
205240
206241 testLibs.resolvedConfiguration.resolvedArtifacts.forEach {
207242 systemProperty(" testLibs.${it.name} " , it.file.absolutePath)
@@ -213,6 +248,12 @@ tasks.test {
213248 " -Dsun.io.useCanonCaches=false" ,
214249 " -Dsun.io.useCanonPrefixCache=false" ,
215250 )
251+
252+ val sandboxDir = tasks.prepareTestSandbox.flatMap { it.sandboxDirectory }.get().asFile
253+
254+ maxParallelForks = testForks
255+ systemProperty(" sandboxDir" , sandboxDir.absolutePath)
256+ systemProperty(" forks" , testForks.toString())
216257}
217258
218259idea {
0 commit comments