IJPL-578 wait for project activities on project start in tests

Otherwise, some tests that expect project activities to complete, will fail. E.g. CMakeWorkspaceOutputConsoleTest

GitOrigin-RevId: 851c334a64d11ebbbfe579e61d9dbe794f719f20
This commit is contained in:
Andrei.Kuznetsov
2024-03-18 22:08:39 +01:00
committed by intellij-monorepo-bot
parent ecacc8bbd2
commit b7a4fa5972
2 changed files with 14 additions and 24 deletions

View File

@@ -36,6 +36,7 @@ import com.intellij.platform.diagnostic.telemetry.Scope
import com.intellij.platform.diagnostic.telemetry.TelemetryManager
import com.intellij.serviceContainer.ComponentManagerImpl
import com.intellij.util.ModalityUiUtil
import com.intellij.util.application
import com.intellij.util.concurrency.ThreadingAssertions
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
@@ -48,6 +49,7 @@ import org.jetbrains.annotations.VisibleForTesting
import java.awt.event.InvocationEvent
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.time.Duration.Companion.minutes
@@ -175,14 +177,10 @@ open class StartupManagerImpl(private val project: Project, private val coroutin
}
else {
val activities = runPostStartupActivities(async = true)
if (coroutineContext.contextModality() == null || coroutineContext.contextModality() == ModalityState.nonModal()) {
withTimeout(2.minutes) {
activities.joinAll()
}
withTimeout(2.minutes) {
activities.joinAll()
}
// don't wait, because waiting under modal progress may result in a deadlock
// (for example, if activities are waiting for smart mode which will only start in non-modal context)
CompletableDeferred(activities)
}
}
@@ -258,7 +256,9 @@ open class StartupManagerImpl(private val project: Project, private val coroutin
if (activity is ProjectActivity) {
if (async) {
val job = launchActivity(activity = activity, project = project, pluginId = pluginDescriptor.pluginId)
val job = blockingContext {
launchActivity(activity = activity, project = project, pluginId = pluginDescriptor.pluginId)
}
launchedActivities.add(job)
}
else {
@@ -466,8 +466,13 @@ private fun launchBackgroundPostStartupActivity(activity: Any, pluginId: PluginI
}
private fun launchActivity(activity: ProjectActivity, project: Project, pluginId: PluginId): Job {
// we propagate modality only in unit tests, because in unit tests activities are often started in modal context (see TestProjectManager),
// so any "invokeAndWait" will hang without modality propagation. In the future we want to avoid .joinAll in runPostStartupActivities at all.
val launchTaskModality = if (application.isUnitTestMode) currentThreadContextModality() else null
val launchTaskModalityContext = launchTaskModality?.asContextElement() ?: EmptyCoroutineContext
return (project as ComponentManagerImpl).pluginCoroutineScope(activity.javaClass.classLoader).launch(
tracer.rootSpan(name = "run activity", arrayOf("class", activity.javaClass.name, "plugin", pluginId.idString))
launchTaskModalityContext + tracer.rootSpan(name = "run activity", arrayOf("class", activity.javaClass.name, "plugin", pluginId.idString)),
) {
activity.execute(project)
}

View File

@@ -27,16 +27,12 @@ import com.intellij.openapi.project.impl.runInitProjectActivities
import com.intellij.openapi.startup.StartupManager
import com.intellij.openapi.util.Disposer
import com.intellij.testFramework.LeakHunter
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.TestApplicationManager.Companion.publishHeapDump
import com.intellij.testFramework.common.LEAKED_PROJECTS
import com.intellij.util.ModalityUiUtil
import com.intellij.util.application
import com.intellij.util.containers.UnsafeWeakList
import com.intellij.util.ref.GCUtil
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.runBlocking
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.TestOnly
@@ -118,7 +114,7 @@ open class TestProjectManager : ProjectManagerImpl() {
val app = ApplicationManager.getApplication()
try {
val launchedActivities: List<Job> = runUnderModalProgressIfIsEdt {
runUnderModalProgressIfIsEdt {
coroutineScope {
runInitProjectActivities(project = project)
}
@@ -129,17 +125,6 @@ open class TestProjectManager : ProjectManagerImpl() {
emptyList()
}
}
// wait outside the modal progress, because some activities will be waiting for smart mode
// which on start will only start in non-modal context
launchedActivities.forEach {
if (application.isDispatchThread) {
PlatformTestUtil.waitForFuture(it.asCompletableFuture())
}
else {
it.asCompletableFuture().get(2, TimeUnit.MINUTES)
}
}
}
catch (e: ProcessCanceledException) {
app.invokeAndWait { closeProject(project, saveProject = false, checkCanClose = false) }