[build] Remove obsolete intellij.build.test.batchTest.includes (aka batch mode) in tests (IJI-3451)

GitOrigin-RevId: 0b6d98a14800c02ae357ae448335dae04ac396ba
This commit is contained in:
Ilia Kirianovskii
2026-02-02 16:47:20 +00:00
committed by intellij-monorepo-bot
parent f11494e117
commit 6fdf0d02ea
2 changed files with 247 additions and 428 deletions
@@ -153,14 +153,6 @@ open class TestingOptions {
*/
var isEnableCausalProfiling: Boolean = getBooleanProperty("intellij.build.test.enable.causal.profiling", false)
/**
* Pattern to match tests in [mainModule] or default main module tests compilation outputs.
* Tests from each matched class will be executed in a forked Runtime.
*
* E.g. "com/intellij/util/ui/standalone/ **Test.class"
*/
var batchTestIncludes: String? = System.getProperty("intellij.build.test.batchTest.includes")
/**
* Run only whole classes/packages in forked Runtime
* Allowed values:
@@ -10,7 +10,6 @@ import com.intellij.idea.IJIgnore
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.util.Pair
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.io.NioFiles
import com.intellij.openapi.util.text.StringUtilRt
@@ -19,7 +18,6 @@ import com.intellij.platform.ijent.community.buildConstants.MULTI_ROUTING_FILE_S
import com.intellij.testFramework.SkipInHeadlessEnvironment
import com.intellij.util.io.awaitExit
import com.intellij.util.lang.UrlClassLoader
import io.opentelemetry.api.common.AttributeKey
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@@ -39,8 +37,6 @@ import org.jetbrains.intellij.build.causal.CausalProfilingOptions
import org.jetbrains.intellij.build.dependencies.TeamCityHelper
import org.jetbrains.intellij.build.impl.coverage.Coverage
import org.jetbrains.intellij.build.impl.coverage.CoverageImpl
import org.jetbrains.intellij.build.io.ZipEntryProcessorResult
import org.jetbrains.intellij.build.io.readZipFile
import org.jetbrains.intellij.build.io.runProcess
import org.jetbrains.intellij.build.telemetry.TraceManager.spanBuilder
import org.jetbrains.intellij.build.telemetry.block
@@ -59,13 +55,11 @@ import java.nio.charset.Charset
import java.nio.file.AccessDeniedException
import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.absolutePathString
import kotlin.io.path.copyTo
import kotlin.io.path.deleteRecursively
import kotlin.io.path.exists
import kotlin.io.path.extension
import kotlin.io.path.isRegularFile
import kotlin.io.path.outputStream
import kotlin.io.path.pathString
@@ -261,11 +255,6 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
else if (options.testPatterns != null && options.testGroups != TestingOptions.ALL_EXCLUDE_DEFINED_GROUP) {
warnOptionIgnored("intellij.build.test.patterns", "intellij.build.test.groups")
}
if (options.batchTestIncludes != null && !isRunningInBatchMode) {
context.messages.warning(
"'intellij.build.test.batchTest.includes' option will be ignored as other tests matching options are specified."
)
}
if (options.validateMainModule && mainModule.isNullOrEmpty()) {
context.messages.logErrorAndThrow("'intellij.build.test.main.module.validate' option requires 'intellij.build.test.main.module' to be set")
@@ -459,7 +448,7 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
}
}
if (isBootstrapSuiteDefault && !isRunningInBatchMode) {
if (isBootstrapSuiteDefault) {
//module with "com.intellij.TestAll" which output should be found in `testClasspath + modulePath`
val testFrameworkCoreModule = outputProvider.findRequiredModule("intellij.platform.testFramework.core")
val testFrameworkCoreModuleOutputRoots = outputProvider.getModuleOutputRoots(testFrameworkCoreModule)
@@ -508,10 +497,7 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
val allJvmArgs = ArrayList(jvmArgs)
prepareEnvForTestRun(jvmArgs = allJvmArgs, systemProperties = systemProperties, classPath = bootstrapClasspath, remoteDebugging = remoteDebugging)
val messages = context.messages
if (isRunningInBatchMode) {
messages.info("Running tests from $mainModule matched by '${options.batchTestIncludes}' pattern.")
}
else if (!testPatterns.isNullOrEmpty()) {
if (!testPatterns.isNullOrEmpty()) {
messages.info("Starting tests from patterns '${testPatterns}' from classpath of module '${mainModule}'")
}
else {
@@ -790,140 +776,6 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
}.flatMap { it.getCompleted() }
}
private fun getTestClassesForModule(mainModule: String, filteringPattern: Pattern = Pattern.compile(".*\\.class")): List<String> {
val testClasses: List<String> = context.outputProvider.getModuleOutputRoots(context.outputProvider.findRequiredModule(mainModule), forTests = true).flatMap { root ->
if (root.isRegularFile() && root.extension == "jar") {
val classes = ArrayList<String>()
val regex = filteringPattern.toRegex()
readZipFile(root) { name, _ ->
if (FileUtilRt.toSystemIndependentName(name).matches(regex)) {
classes.add(name)
}
ZipEntryProcessorResult.CONTINUE
}
classes
}
else {
Files.walk(root).use { stream ->
stream.map { FileUtilRt.toSystemIndependentName(root.relativize(it).toString()) }.filter {
filteringPattern.matcher(it).matches()
}.toList()
} ?: listOf()
}
}
if (testClasses.isEmpty()) {
throw RuntimeException("No tests were found in module '$mainModule' with $filteringPattern")
}
return testClasses
}
private suspend fun runInBatchMode(
mainModule: String,
systemProperties: Map<String, String>,
jvmArgs: List<String>,
envVariables: Map<String, String>,
bootstrapClasspath: List<String>,
testClasspath: List<String>,
devBuildServerSettings: DevBuildServerSettings?,
) {
val pattern = Pattern.compile(FileUtil.convertAntToRegexp(options.batchTestIncludes!!))
val testClasses = getTestClassesForModule(mainModule = mainModule, filteringPattern = pattern)
val files = testClasspath.map { Path.of(it) }
val loader = UrlClassLoader.build().files(files).get()
@Suppress("UNCHECKED_CAST")
val testAnnotation4 = loader.loadClass("org.junit.Test") as Class<Annotation>
@Suppress("UNCHECKED_CAST")
val testAnnotation5 = loader.loadClass("org.junit.jupiter.api.Test") as Class<Annotation>
@Suppress("UNCHECKED_CAST")
val testFactoryAnnotation5 = loader.loadClass("org.junit.jupiter.api.TestFactory") as Class<Annotation>
var noTestsInAllClasses = true
for (testClass in testClasses) {
val qName = FileUtilRt.getNameWithoutExtension(testClass).replace('/', '.')
try {
var noTests = true
val aClass = loader.loadClass(qName)
val jUnit4And5TestMethods = getAnnotatedTestMethods(aClass, testAnnotation4, testAnnotation5, testFactoryAnnotation5)
// Run JUnit 4 and 5 whole test classes separately
if (options.isDedicatedTestRuntime != "false" && jUnit4And5TestMethods.isNotEmpty()) {
val exitCode = runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties,
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = null,
testClasspath = testClasspath,
suiteName = qName,
methodName = null,
devBuildSettings = devBuildServerSettings,
)
noTests = exitCode == NO_TESTS_ERROR
}
// Run JUnit 4 and 5 test methods separately if any
else if (jUnit4And5TestMethods.isNotEmpty()) {
for (method in jUnit4And5TestMethods) {
val exitCode = runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties,
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = null,
testClasspath = testClasspath,
suiteName = qName,
methodName = method,
devBuildSettings = devBuildServerSettings,
)
noTests = noTests && exitCode == NO_TESTS_ERROR
}
}
// Fallback to running whole class (JUnit 3+4)
if (noTests) {
val exitCode = runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties,
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = null,
testClasspath = testClasspath,
suiteName = qName,
methodName = null,
devBuildSettings = devBuildServerSettings,
)
noTests = exitCode == NO_TESTS_ERROR
}
noTestsInAllClasses = noTestsInAllClasses && noTests
}
catch (e: Throwable) {
throw RuntimeException("Failed to process $qName", e)
}
}
if (noTestsInAllClasses) {
throw RuntimeException("No tests were found in $mainModule with $pattern")
}
}
private fun getAnnotatedTestMethods(aClass: Class<*>, vararg annotations: Class<Annotation>): List<String> {
return aClass.methods
.asSequence()
.filter { m -> Modifier.isPublic(m.modifiers) }
.filter { m -> annotations.any { a -> m.isAnnotationPresent(a) } }
.map { m -> m.name }
.toList()
}
private suspend fun runJUnit5Engine(
mainModule: String,
systemProperties: Map<String, String>,
@@ -934,291 +786,274 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
testClasspath: List<String>,
devBuildServerSettings: DevBuildServerSettings?,
) {
if (isRunningInBatchMode) {
spanBuilder("run tests in batch mode")
.setAttribute(AttributeKey.stringKey("pattern"), options.batchTestIncludes ?: "")
.use {
runInBatchMode(
mainModule = mainModule,
systemProperties = systemProperties,
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
testClasspath = testClasspath,
devBuildServerSettings = devBuildServerSettings,
)
val messages = context.messages
if (options.isDedicatedTestRuntime != "false") {
if (options.isDedicatedTestRuntime != "class" && options.isDedicatedTestRuntime != "package") {
messages.logErrorAndThrow("Unsupported 'intellij.build.test.dedicated.runtime' value: ${options.isDedicatedTestRuntime}. Expected 'class', 'package' or 'false'")
}
messages.info("Will run tests in dedicated runtimes ('${options.isDedicatedTestRuntime}')")
// First, collect all tests for both JUnit5 and JUnit3+4
val testClassesJUnit5 = spanBuilder("collect junit 5 tests").use {
if (options.shouldSkipJUnit5Tests) {
messages.warning("JUnit 5 tests collections is skipped")
return@use emptyList()
}
val testClassesListFile = Files.createTempFile("tests-to-run-", ".list").apply { Files.delete(this) }
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + ("intellij.build.test.list.classes" to testClassesListFile.absolutePathString()),
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = null,
methodName = null,
devBuildSettings = null,
)
testClassesListFile.let { if (Files.exists(it)) it.readLines() else emptyList() }
}
val testClassesJUnit34 = block("collect junit 3+4 tests") {
if (options.shouldSkipJUnit34Tests) {
messages.warning("JUnit 3+4 tests collections is skipped")
return@block emptyList()
}
val testClassesListFile = Files.createTempFile("tests-to-run-", ".list").apply { Files.delete(this) }
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + ("intellij.build.test.list.classes" to testClassesListFile.absolutePathString()),
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = options.bootstrapSuite,
methodName = null,
devBuildSettings = null,
)
return@block testClassesListFile.let { if (Files.exists(it)) it.readLines() else emptyList() }
}
if (testClassesJUnit5.isEmpty() && testClassesJUnit34.isEmpty() &&
// a bucket might be empty for run configurations with too few tests due to imperfect tests balancing
options.bucketsCount < 2) {
throw NoTestsFound()
}
if (options.isDedicatedTestRuntime == "class") {
var hasFailures = false
suspend fun runOneClass(testClassName: String) {
val exitCode = block("running test class '$testClassName'") {
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + ("idea.performance.tests.discovery.filter" to "true"),
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = testClassName,
methodName = null,
devBuildSettings = devBuildServerSettings,
)
}
if (exitCode == 1) hasFailures = true // reported as test failure or assertNoUnhandledExceptions if exception
else if (exitCode == NO_TESTS_ERROR) throw NoTestsFound()
else if (exitCode != 0) throw RuntimeException("Unexpected exit code $exitCode when running tests in dedicated runtime (class mode)")
}
if (testClassesJUnit5.isNotEmpty()) {
messages.info("Will run JUnit 5 tests:\n${testClassesJUnit5.joinToString("\n")}")
for (s in testClassesJUnit5) {
runOneClass(s)
}
}
if (testClassesJUnit34.isNotEmpty()) {
messages.info("Will run JUnit 3+4 tests:\n${testClassesJUnit34.joinToString("\n")}")
for (s in testClassesJUnit34) {
runOneClass(s)
}
}
// On TeamCity test failures themselves control the build status, no need to report them as additional errors
if (hasFailures && !TeamCityHelper.isUnderTeamCity) {
throw RuntimeException("Tests failed in dedicated runtime (class mode)")
}
}
else if (options.isDedicatedTestRuntime == "package") {
var hasFailures = false
fun groupByPackages(tests: List<String>): Map<String, List<String>> {
return tests.groupBy {
val i = it.lastIndexOf('.')
if (i != -1) it.substring(0, i) else ""
}
}
suspend fun runOnePackage(entry: Map.Entry<String, List<String>>) {
val packageName = entry.key
val classes = entry.value
val exitCode = block("running tests in package '$packageName'") {
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + ("idea.performance.tests.discovery.filter" to "true"),
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = "__classes__",
methodName = classes.joinToString(";"),
devBuildSettings = devBuildServerSettings,
)
}
if (exitCode == 1) hasFailures = true // reported as test failure or assertNoUnhandledExceptions if exception
else if (exitCode == NO_TESTS_ERROR) throw NoTestsFound()
else if (exitCode != 0) throw RuntimeException("Unexpected exit code $exitCode when running tests in dedicated runtime (package mode)")
}
if (testClassesJUnit5.isNotEmpty()) {
val packages = groupByPackages(testClassesJUnit5)
messages.info(packages.entries.joinToString(prefix = "Will run JUnit 5 packages:\n", separator = "\n") { e ->
e.value.joinToString(prefix = "${e.key}\n ", separator = "\n ")
})
for (entry in packages) {
runOnePackage(entry)
}
}
if (testClassesJUnit34.isNotEmpty()) {
val packages = groupByPackages(testClassesJUnit34)
messages.info(packages.entries.joinToString(prefix = "Will run JUnit 3+4 packages:\n", separator = "\n") { e ->
e.value.joinToString(prefix = "${e.key}\n ", separator = "\n ")
})
for (entry in packages) {
runOnePackage(entry)
}
}
// On TeamCity test failures themselves control the build status, no need to report them as additional errors
if (hasFailures && !TeamCityHelper.isUnderTeamCity) {
throw RuntimeException("Tests failed in dedicated runtime (package mode)")
}
}
}
else {
val messages = context.messages
if (options.isDedicatedTestRuntime != "false") {
if (options.isDedicatedTestRuntime != "class" && options.isDedicatedTestRuntime != "package") {
messages.logErrorAndThrow("Unsupported 'intellij.build.test.dedicated.runtime' value: ${options.isDedicatedTestRuntime}. Expected 'class', 'package' or 'false'")
val failedClassesJUnit5List = Files.createTempFile("failed-classes-junit5-", ".list").apply { Files.delete(this) }
val failedClassesJUnit34List = Files.createTempFile("failed-classes-junit34-", ".list").apply { Files.delete(this) }
val additionalPropertiesJUnit5: Map<String, String> = failedClassesJUnit5List.let {
if (options.attemptCount > 1) mapOf("intellij.build.test.retries.failedClasses.file" to "$it", "intellij.build.test.list.file" to "$it")
else emptyMap()
}
val additionalPropertiesJUnit34: Map<String, String> = failedClassesJUnit34List.let {
if (options.attemptCount > 1) mapOf("intellij.build.test.retries.failedClasses.file" to "$it", "intellij.build.test.list.file" to "$it")
else emptyMap()
}
var runJUnit5 = !options.shouldSkipJUnit5Tests
var runJUnit34 = !options.shouldSkipJUnit34Tests
var lastExitCode5 = 0
var lastExitCode34 = 0
for (attempt in 1..options.attemptCount) {
if (!runJUnit5 && !runJUnit34) break
val spanNameSuffix = if (options.attemptCount > 1) " (attempt $attempt)" else ""
val systemPath = systemProperties[PathManager.PROPERTY_SYSTEM_PATH]
val additionalProperties = mutableMapOf(PathManager.PROPERTY_LOG_PATH to "$systemPath${File.separator}log${File.separator}junit5")
if (attempt > 1) {
additionalProperties["intellij.build.test.ignoreFirstAndLastTests"] = "true"
}
messages.info("Will run tests in dedicated runtimes ('${options.isDedicatedTestRuntime}')")
// First, collect all tests for both JUnit5 and JUnit3+4
val testClassesJUnit5 = spanBuilder("collect junit 5 tests").use {
if (options.shouldSkipJUnit5Tests) {
messages.warning("JUnit 5 tests collections is skipped")
return@use emptyList()
val exitCode5: Int = if (runJUnit5) {
block("run junit 5 tests${spanNameSuffix}") {
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + additionalProperties + additionalPropertiesJUnit5,
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = null,
methodName = null,
devBuildSettings = devBuildServerSettings,
)
}
val testClassesListFile = Files.createTempFile("tests-to-run-", ".list").apply { Files.delete(this) }
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + ("intellij.build.test.list.classes" to testClassesListFile.absolutePathString()),
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = null,
methodName = null,
devBuildSettings = null,
)
testClassesListFile.let { if (Files.exists(it)) it.readLines() else emptyList() }
}
else {
0
}
if (runJUnit5) lastExitCode5 = exitCode5
val testClassesJUnit34 = block("collect junit 3+4 tests") {
if (options.shouldSkipJUnit34Tests) {
messages.warning("JUnit 3+4 tests collections is skipped")
return@block emptyList()
additionalProperties[PathManager.PROPERTY_LOG_PATH] = "$systemPath${File.separator}log${File.separator}junit3and4"
val exitCode34: Int = if (runJUnit34) {
block("run junit 3+4 tests${spanNameSuffix}") {
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + additionalProperties + additionalPropertiesJUnit34,
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = options.bootstrapSuite,
methodName = null,
devBuildSettings = devBuildServerSettings,
)
}
val testClassesListFile = Files.createTempFile("tests-to-run-", ".list").apply { Files.delete(this) }
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + ("intellij.build.test.list.classes" to testClassesListFile.absolutePathString()),
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = options.bootstrapSuite,
methodName = null,
devBuildSettings = null,
)
return@block testClassesListFile.let { if (Files.exists(it)) it.readLines() else emptyList() }
}
else {
0
}
if (runJUnit34) lastExitCode34 = exitCode34
if (testClassesJUnit5.isEmpty() && testClassesJUnit34.isEmpty() &&
if (exitCode5 == NO_TESTS_ERROR && exitCode34 == NO_TESTS_ERROR &&
// only check on the first (full) attempt
attempt == 1 &&
// a bucket might be empty for run configurations with too few tests due to imperfect tests balancing
options.bucketsCount < 2) {
throw NoTestsFound()
}
if (exitCode5 != 0 && exitCode5 != 1 && exitCode5 != NO_TESTS_ERROR) {
throw RuntimeException("Unexpected exit code $exitCode5 when running JUnit 5 tests")
}
if (exitCode34 != 0 && exitCode34 != 1 && exitCode34 != NO_TESTS_ERROR) {
throw RuntimeException("Unexpected exit code $exitCode34 when running JUnit 3+4 tests")
}
if (options.isDedicatedTestRuntime == "class") {
var hasFailures = false
suspend fun runOneClass(testClassName: String) {
val exitCode = block("running test class '$testClassName'") {
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + ("idea.performance.tests.discovery.filter" to "true"),
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = testClassName,
methodName = null,
devBuildSettings = devBuildServerSettings,
)
}
if (exitCode == 1) hasFailures = true // reported as test failure or assertNoUnhandledExceptions if exception
else if (exitCode == NO_TESTS_ERROR) throw NoTestsFound()
else if (exitCode != 0) throw RuntimeException("Unexpected exit code $exitCode when running tests in dedicated runtime (class mode)")
if (runJUnit5) {
val failedClassesJUnit5 = failedClassesJUnit5List.let { if (Files.exists(it)) it.readLines() else emptyList() }
if (failedClassesJUnit5.isNotEmpty()) {
messages.warning("Will rerun JUnit 5 tests: $failedClassesJUnit5")
}
if (testClassesJUnit5.isNotEmpty()) {
messages.info("Will run JUnit 5 tests:\n${testClassesJUnit5.joinToString("\n")}")
for (s in testClassesJUnit5) {
runOneClass(s)
}
}
if (testClassesJUnit34.isNotEmpty()) {
messages.info("Will run JUnit 3+4 tests:\n${testClassesJUnit34.joinToString("\n")}")
for (s in testClassesJUnit34) {
runOneClass(s)
}
}
// On TeamCity test failures themselves control the build status, no need to report them as additional errors
if (hasFailures && !TeamCityHelper.isUnderTeamCity) {
throw RuntimeException("Tests failed in dedicated runtime (class mode)")
else {
runJUnit5 = false
}
}
else if (options.isDedicatedTestRuntime == "package") {
var hasFailures = false
fun groupByPackages(tests: List<String>): Map<String, List<String>> {
return tests.groupBy {
val i = it.lastIndexOf('.')
if (i != -1) it.substring(0, i) else ""
}
if (runJUnit34) {
val failedClassesJUnit34 = failedClassesJUnit34List.let { if (Files.exists(it)) it.readLines() else emptyList() }
if (failedClassesJUnit34.isNotEmpty()) {
messages.warning("Will rerun JUnit 3+4 tests: $failedClassesJUnit34")
}
suspend fun runOnePackage(entry: Map.Entry<String, List<String>>) {
val packageName = entry.key
val classes = entry.value
val exitCode = block("running tests in package '$packageName'") {
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + ("idea.performance.tests.discovery.filter" to "true"),
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = "__classes__",
methodName = classes.joinToString(";"),
devBuildSettings = devBuildServerSettings,
)
}
if (exitCode == 1) hasFailures = true // reported as test failure or assertNoUnhandledExceptions if exception
else if (exitCode == NO_TESTS_ERROR) throw NoTestsFound()
else if (exitCode != 0) throw RuntimeException("Unexpected exit code $exitCode when running tests in dedicated runtime (package mode)")
}
if (testClassesJUnit5.isNotEmpty()) {
val packages = groupByPackages(testClassesJUnit5)
messages.info(packages.entries.joinToString(prefix = "Will run JUnit 5 packages:\n", separator = "\n") { e ->
e.value.joinToString(prefix = "${e.key}\n ", separator = "\n ")
})
for (entry in packages) {
runOnePackage(entry)
}
}
if (testClassesJUnit34.isNotEmpty()) {
val packages = groupByPackages(testClassesJUnit34)
messages.info(packages.entries.joinToString(prefix = "Will run JUnit 3+4 packages:\n", separator = "\n") { e ->
e.value.joinToString(prefix = "${e.key}\n ", separator = "\n ")
})
for (entry in packages) {
runOnePackage(entry)
}
}
// On TeamCity test failures themselves control the build status, no need to report them as additional errors
if (hasFailures && !TeamCityHelper.isUnderTeamCity) {
throw RuntimeException("Tests failed in dedicated runtime (package mode)")
else {
runJUnit34 = false
}
}
}
else {
val failedClassesJUnit5List = Files.createTempFile("failed-classes-junit5-", ".list").apply { Files.delete(this) }
val failedClassesJUnit34List = Files.createTempFile("failed-classes-junit34-", ".list").apply { Files.delete(this) }
val additionalPropertiesJUnit5: Map<String, String> = failedClassesJUnit5List.let {
if (options.attemptCount > 1) mapOf("intellij.build.test.retries.failedClasses.file" to "$it", "intellij.build.test.list.file" to "$it")
else emptyMap()
// Check if tests failed after all retry attempts are exhausted
val hadTestFailures = lastExitCode5 == 1 || lastExitCode34 == 1
// On TeamCity test failures themselves control the build status, no need to report them as additional errors
if (!TeamCityHelper.isUnderTeamCity) {
if (hadTestFailures) {
throw RuntimeException("Tests failed (JUnit5 exit code: $lastExitCode5, JUnit3+4 exit code: $lastExitCode34, $NO_TESTS_ERROR means no test for this test framework)")
}
val additionalPropertiesJUnit34: Map<String, String> = failedClassesJUnit34List.let {
if (options.attemptCount > 1) mapOf("intellij.build.test.retries.failedClasses.file" to "$it", "intellij.build.test.list.file" to "$it")
else emptyMap()
}
var runJUnit5 = !options.shouldSkipJUnit5Tests
var runJUnit34 = !options.shouldSkipJUnit34Tests
var lastExitCode5 = 0
var lastExitCode34 = 0
for (attempt in 1..options.attemptCount) {
if (!runJUnit5 && !runJUnit34) break
val spanNameSuffix = if (options.attemptCount > 1) " (attempt $attempt)" else ""
val systemPath = systemProperties[PathManager.PROPERTY_SYSTEM_PATH]
val additionalProperties = mutableMapOf(PathManager.PROPERTY_LOG_PATH to "$systemPath${File.separator}log${File.separator}junit5")
if (attempt > 1) {
additionalProperties["intellij.build.test.ignoreFirstAndLastTests"] = "true"
}
val exitCode5: Int = if (runJUnit5) {
block("run junit 5 tests${spanNameSuffix}") {
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + additionalProperties + additionalPropertiesJUnit5,
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = null,
methodName = null,
devBuildSettings = devBuildServerSettings,
)
}
}
else {
0
}
if (runJUnit5) lastExitCode5 = exitCode5
additionalProperties[PathManager.PROPERTY_LOG_PATH] = "$systemPath${File.separator}log${File.separator}junit3and4"
val exitCode34: Int = if (runJUnit34) {
block("run junit 3+4 tests${spanNameSuffix}") {
runJUnit5Engine(
mainModule = mainModule,
systemProperties = systemProperties + additionalProperties + additionalPropertiesJUnit34,
jvmArgs = jvmArgs,
envVariables = envVariables,
bootstrapClasspath = bootstrapClasspath,
modulePath = modulePath,
testClasspath = testClasspath,
suiteName = options.bootstrapSuite,
methodName = null,
devBuildSettings = devBuildServerSettings,
)
}
}
else {
0
}
if (runJUnit34) lastExitCode34 = exitCode34
if (exitCode5 == NO_TESTS_ERROR && exitCode34 == NO_TESTS_ERROR &&
// only check on the first (full) attempt
attempt == 1 &&
// a bucket might be empty for run configurations with too few tests due to imperfect tests balancing
options.bucketsCount < 2) {
throw NoTestsFound()
}
if (exitCode5 != 0 && exitCode5 != 1 && exitCode5 != NO_TESTS_ERROR) {
throw RuntimeException("Unexpected exit code $exitCode5 when running JUnit 5 tests")
}
if (exitCode34 != 0 && exitCode34 != 1 && exitCode34 != NO_TESTS_ERROR) {
throw RuntimeException("Unexpected exit code $exitCode34 when running JUnit 3+4 tests")
}
if (runJUnit5) {
val failedClassesJUnit5 = failedClassesJUnit5List.let { if (Files.exists(it)) it.readLines() else emptyList() }
if (failedClassesJUnit5.isNotEmpty()) {
messages.warning("Will rerun JUnit 5 tests: $failedClassesJUnit5")
}
else {
runJUnit5 = false
}
}
if (runJUnit34) {
val failedClassesJUnit34 = failedClassesJUnit34List.let { if (Files.exists(it)) it.readLines() else emptyList() }
if (failedClassesJUnit34.isNotEmpty()) {
messages.warning("Will rerun JUnit 3+4 tests: $failedClassesJUnit34")
}
else {
runJUnit34 = false
}
}
}
// Check if tests failed after all retry attempts are exhausted
val hadTestFailures = lastExitCode5 == 1 || lastExitCode34 == 1
// On TeamCity test failures themselves control the build status, no need to report them as additional errors
if (!TeamCityHelper.isUnderTeamCity) {
if (hadTestFailures) {
throw RuntimeException("Tests failed (JUnit5 exit code: $lastExitCode5, JUnit3+4 exit code: $lastExitCode34, $NO_TESTS_ERROR means no test for this test framework)")
}
else {
println("*** All tests passed ***")
}
else {
println("*** All tests passed ***")
}
}
}
@@ -1303,7 +1138,7 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
appendJUnitStarter(classpath, context)
}
if (!isBootstrapSuiteDefault || isRunningInBatchMode || options.isDedicatedTestRuntime != "false" || suiteName == null) {
if (!isBootstrapSuiteDefault || options.isDedicatedTestRuntime != "false" || suiteName == null) {
classpath.addAll(testClasspath)
}
@@ -1401,14 +1236,6 @@ internal class TestingTasksImpl(context: CompilationContext, private val options
private val isBootstrapSuiteDefault: Boolean
get() = options.bootstrapSuite == TestingOptions.BOOTSTRAP_SUITE_DEFAULT
private val isRunningInBatchMode: Boolean
get() {
return options.batchTestIncludes != null &&
options.testPatterns == null &&
options.testConfigurations == null &&
options.testGroups == TestingOptions.ALL_EXCLUDE_DEFINED_GROUP
}
}
private fun appendJUnitStarter(classPath: MutableList<String>, context: CompilationContext) {