From f378bc7b8585b6c2061a78a6f66b7d294db258ac Mon Sep 17 00:00:00 2001 From: Anastasia Katsman Date: Thu, 12 Feb 2026 14:30:25 +0100 Subject: [PATCH] testFramework [lambda]: more straightforward way to set scope for processes without major refactoring GitOrigin-RevId: 3ddb37f11c178767a5121c4f1b0922cf642b2ed4 --- .../resources/junit-platform.properties | 5 ----- .../starter/LambdaTestExecutionListener.kt | 6 +++++- .../driver/remoteDev/IDEFrontendHandler.kt | 4 ++-- .../starter/driver/engine/LocalDriverRunner.kt | 4 ++-- .../junit5/config/KillOutdatedProcesses.kt | 7 +++++++ .../ide/starter/coroutine/CommonScope.kt | 16 +++++++++++++++- .../ide/starter/process/exec/ProcessExecutor.kt | 4 ++-- 7 files changed, 33 insertions(+), 13 deletions(-) delete mode 100644 tools/intellij.lambda.testFramework/resources/junit-platform.properties diff --git a/tools/intellij.lambda.testFramework/resources/junit-platform.properties b/tools/intellij.lambda.testFramework/resources/junit-platform.properties deleted file mode 100644 index df650cff1dc3..000000000000 --- a/tools/intellij.lambda.testFramework/resources/junit-platform.properties +++ /dev/null @@ -1,5 +0,0 @@ -junit.jupiter.extensions.autodetection.enabled = true -junit.jupiter.extensions.autodetection.exclude = com.intellij.ide.starter.junit5.config.KillOutdatedProcessesAfterEach,com.intellij.ide.starter.junit5.config.KillOutdatedProcesses - -# Will be handled by com.intellij.lambda.testFramework.starter.LambdaTestExecutionListener -junit.platform.execution.listeners.deactivate = com.intellij.ide.starter.junit5.TestCleanupListener \ No newline at end of file diff --git a/tools/intellij.lambda.testFramework/src/com/intellij/lambda/testFramework/starter/LambdaTestExecutionListener.kt b/tools/intellij.lambda.testFramework/src/com/intellij/lambda/testFramework/starter/LambdaTestExecutionListener.kt index 53463e884b2a..76252c7cc9f9 100644 --- a/tools/intellij.lambda.testFramework/src/com/intellij/lambda/testFramework/starter/LambdaTestExecutionListener.kt +++ b/tools/intellij.lambda.testFramework/src/com/intellij/lambda/testFramework/starter/LambdaTestExecutionListener.kt @@ -2,10 +2,13 @@ package com.intellij.lambda.testFramework.starter import com.intellij.ide.starter.ci.CIServer import com.intellij.ide.starter.ci.teamcity.TeamCityCIServer +import com.intellij.ide.starter.coroutine.CommonScope +import com.intellij.ide.starter.coroutine.CommonScope.testSuiteSupervisorScope import com.intellij.ide.starter.di.di import com.intellij.ide.starter.junit5.TestCleanupListener import com.intellij.ide.starter.runner.CurrentTestMethod import com.intellij.lambda.testFramework.utils.IdeWithLambda +import kotlinx.coroutines.job import org.junit.platform.engine.TestExecutionResult import org.junit.platform.launcher.TestIdentifier import org.kodein.di.DI @@ -16,6 +19,8 @@ import java.nio.file.Path class LambdaTestExecutionListener : TestCleanupListener() { companion object { init { + CommonScope.perSuiteScopeForIdeActivities() + di = DI { extend(di) @@ -45,6 +50,5 @@ class LambdaTestExecutionListener : TestCleanupListener() { if (!testIdentifier.isTest) return IdeInstance.publishArtifacts() - cancelPerTestSupervisorScope(testIdentifier) } } \ No newline at end of file diff --git a/tools/intellij.tools.ide.starter.driver/src/com/intellij/ide/starter/driver/driver/remoteDev/IDEFrontendHandler.kt b/tools/intellij.tools.ide.starter.driver/src/com/intellij/ide/starter/driver/driver/remoteDev/IDEFrontendHandler.kt index 5799f280cb72..3e1ba89806e3 100644 --- a/tools/intellij.tools.ide.starter.driver/src/com/intellij/ide/starter/driver/driver/remoteDev/IDEFrontendHandler.kt +++ b/tools/intellij.tools.ide.starter.driver/src/com/intellij/ide/starter/driver/driver/remoteDev/IDEFrontendHandler.kt @@ -1,6 +1,6 @@ package com.intellij.ide.starter.driver.driver.remoteDev -import com.intellij.ide.starter.coroutine.CommonScope.perClassSupervisorScope +import com.intellij.ide.starter.coroutine.CommonScope.scopeForProcesses import com.intellij.ide.starter.driver.engine.DriverOptions import com.intellij.ide.starter.driver.engine.remoteDev.XorgWindowManagerHandler import com.intellij.ide.starter.ide.IDETestContext @@ -65,7 +65,7 @@ internal class IDEFrontendHandler( EventsBus.subscribeOnce(process) { event: IdeLaunchEvent -> process.complete(event.ideProcess) } - val result = perClassSupervisorScope.async { + val result = scopeForProcesses.async { try { val thinClientCommand = if (frontendContext.ide.vmOptions.data().contains("-Djava.awt.headless=true")) "thinClient-headless" else "thinClient" diff --git a/tools/intellij.tools.ide.starter.driver/src/com/intellij/ide/starter/driver/engine/LocalDriverRunner.kt b/tools/intellij.tools.ide.starter.driver/src/com/intellij/ide/starter/driver/engine/LocalDriverRunner.kt index 5202ed7e6242..d3efeec313a5 100644 --- a/tools/intellij.tools.ide.starter.driver/src/com/intellij/ide/starter/driver/engine/LocalDriverRunner.kt +++ b/tools/intellij.tools.ide.starter.driver/src/com/intellij/ide/starter/driver/engine/LocalDriverRunner.kt @@ -2,7 +2,7 @@ package com.intellij.ide.starter.driver.engine import com.intellij.driver.client.Driver import com.intellij.driver.client.impl.JmxHost -import com.intellij.ide.starter.coroutine.CommonScope.perClassSupervisorScope +import com.intellij.ide.starter.coroutine.CommonScope.scopeForProcesses import com.intellij.ide.starter.ide.IDETestContext import com.intellij.ide.starter.ide.isRemDevContext import com.intellij.ide.starter.runner.IDECommandLine @@ -28,7 +28,7 @@ class LocalDriverRunner : DriverRunner { EventsBus.subscribeOnce(process) { event: IdeLaunchEvent -> process.complete(event.ideProcess) } - val runResult = perClassSupervisorScope.async { + val runResult = scopeForProcesses.async { Allure.getLifecycle().setCurrentTestCase(currentStep.orElse(UUID.randomUUID().toString())) try { context.runIdeSuspending(commandLine, diff --git a/tools/intellij.tools.ide.starter.junit5/src/com/intellij/ide/starter/junit5/config/KillOutdatedProcesses.kt b/tools/intellij.tools.ide.starter.junit5/src/com/intellij/ide/starter/junit5/config/KillOutdatedProcesses.kt index 1a0459fbf8e5..791408fe970b 100644 --- a/tools/intellij.tools.ide.starter.junit5/src/com/intellij/ide/starter/junit5/config/KillOutdatedProcesses.kt +++ b/tools/intellij.tools.ide.starter.junit5/src/com/intellij/ide/starter/junit5/config/KillOutdatedProcesses.kt @@ -1,5 +1,6 @@ package com.intellij.ide.starter.junit5.config +import com.intellij.ide.starter.coroutine.CommonScope import com.intellij.ide.starter.process.findAndKillLeftoverProcessesFromTestRuns import org.junit.jupiter.api.extension.AfterAllCallback import org.junit.jupiter.api.extension.BeforeAllCallback @@ -7,10 +8,16 @@ import org.junit.jupiter.api.extension.ExtensionContext open class KillOutdatedProcesses : BeforeAllCallback, AfterAllCallback { override fun beforeAll(context: ExtensionContext) { + if (!CommonScope.shouldKillOutdatedProcessesBetweenContainers()) { + return + } findAndKillLeftoverProcessesFromTestRuns() } override fun afterAll(context: ExtensionContext) { + if (!CommonScope.shouldKillOutdatedProcessesBetweenContainers()) { + return + } findAndKillLeftoverProcessesFromTestRuns(reportErrors = true) } } diff --git a/tools/intellij.tools.ide.starter/src/com/intellij/ide/starter/coroutine/CommonScope.kt b/tools/intellij.tools.ide.starter/src/com/intellij/ide/starter/coroutine/CommonScope.kt index a871fb48a71f..e3eb53dabaf3 100644 --- a/tools/intellij.tools.ide.starter/src/com/intellij/ide/starter/coroutine/CommonScope.kt +++ b/tools/intellij.tools.ide.starter/src/com/intellij/ide/starter/coroutine/CommonScope.kt @@ -47,7 +47,8 @@ object CommonScope { CoroutineScope(SupervisorJob() + Dispatchers.IO + CoroutineName("Test's supervisor scope")) /** - * Lifespan is limited to the duration of the test class. By the end of the test class whole coroutines tree will be cancelled. + * Lifespan is limited to the duration of the test class. By the end of the test container, a whole coroutines tree will be canceled. + * In a simple case a test container is the same as a class, in case when we have a base class and several classes extend it, the test container is over when all tests extending the base class are over. */ val perClassSupervisorScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + CoroutineName("Test class's supervisor scope")) @@ -56,4 +57,17 @@ object CommonScope { * In most scenarios you don't need that behavior. */ val simpleScope = CoroutineScope(Job() + Dispatchers.IO + CoroutineName("Simple scope")) + + var scopeForProcesses: CoroutineScope = perClassSupervisorScope + private set + + /* + Processes started in ProcessExecutor and by various runWithDriver will be launched on per suite scope. + It means that they will be stopped when the whole test suite is finished and not after each test container. + */ + fun perSuiteScopeForIdeActivities() { + scopeForProcesses = testSuiteSupervisorScope + } + + fun shouldKillOutdatedProcessesBetweenContainers(): Boolean = scopeForProcesses == perClassSupervisorScope } \ No newline at end of file diff --git a/tools/intellij.tools.ide.starter/src/com/intellij/ide/starter/process/exec/ProcessExecutor.kt b/tools/intellij.tools.ide.starter/src/com/intellij/ide/starter/process/exec/ProcessExecutor.kt index ae6073477629..3c935d686ab6 100644 --- a/tools/intellij.tools.ide.starter/src/com/intellij/ide/starter/process/exec/ProcessExecutor.kt +++ b/tools/intellij.tools.ide.starter/src/com/intellij/ide/starter/process/exec/ProcessExecutor.kt @@ -2,7 +2,7 @@ package com.intellij.ide.starter.process.exec import com.intellij.ide.starter.config.ConfigurationStorage import com.intellij.ide.starter.config.logEnvVariables -import com.intellij.ide.starter.coroutine.CommonScope.perClassSupervisorScope +import com.intellij.ide.starter.coroutine.CommonScope.scopeForProcesses import com.intellij.ide.starter.utils.catchAll import com.intellij.ide.starter.utils.getThrowableText import com.intellij.tools.ide.util.common.logError @@ -202,7 +202,7 @@ class ProcessExecutor( @Suppress("BlockingMethodInNonBlockingContext") val process = processBuilder.start() val processId = process.pid() - val onProcessCreatedJob: Job = perClassSupervisorScope.launch(Dispatchers.IO + CoroutineName("On process $presentableName created job")) { + val onProcessCreatedJob: Job = scopeForProcesses.launch(Dispatchers.IO + CoroutineName("On process $presentableName created job")) { if (!silent) logOutput(" ... started external process `$presentableName` with process ID = $processId") onProcessCreated(process, processId) }