mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 04:51:24 +07:00
IJPL-158: Deprecate DumbService.setDumbMode and suggest alternative API (runInDumbMode)
New API is currently `@TestOnly`. The plan is that `DumbService.runInDumbMode` will become a recommended production API.
`{run/compute}InDumbModeSynchronously` will remain as `@TestOnly`, and (likely) will be moved to DumbModeTestUtils later.
GitOrigin-RevId: c9f35ed8162b5ea53caa0abb859193d2918376fe
This commit is contained in:
committed by
intellij-monorepo-bot
parent
dd39f1be13
commit
a0cf94f7e6
@@ -276,7 +276,7 @@ public class ClsPsiTest extends LightIdeaTestCase {
|
||||
assertEquals("o", parameters[1].getName());
|
||||
};
|
||||
|
||||
DumbServiceImpl.getInstance(getProject()).runInDumbModeSynchronously(checkNames);
|
||||
DumbServiceImpl.getInstance(getProject()).runInDumbModeSynchronously(checkNames::run);
|
||||
checkNames.run();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@ import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.IdeBundle
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.AccessToken
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.WriteAction
|
||||
import com.intellij.openapi.application.*
|
||||
import com.intellij.openapi.application.impl.ApplicationImpl
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
@@ -24,19 +21,23 @@ import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.ModificationTracker
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.util.ThrowableComputable
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.wm.WindowManager
|
||||
import com.intellij.openapi.wm.ex.StatusBarEx
|
||||
import com.intellij.serviceContainer.NonInjectable
|
||||
import com.intellij.util.ConcurrencyUtil
|
||||
import com.intellij.util.SystemProperties
|
||||
import com.intellij.util.ThrowableRunnable
|
||||
import com.intellij.util.application
|
||||
import com.intellij.util.indexing.IndexingBundle
|
||||
import com.intellij.util.ui.DeprecationStripePanel
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Async
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
@@ -173,7 +174,7 @@ open class DumbServiceImpl @NonInjectable @VisibleForTesting constructor(private
|
||||
}
|
||||
return myState.value.isDumb
|
||||
}
|
||||
@TestOnly set(dumb) {
|
||||
@TestOnly @Deprecated("Use runInDumbMode instead (or {run/compute}InDumbModeSynchronously)") set(dumb) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread()
|
||||
if (dumb) {
|
||||
myState.update { it.makeDumb() }
|
||||
@@ -184,14 +185,70 @@ open class DumbServiceImpl @NonInjectable @VisibleForTesting constructor(private
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts dumb mode (if not started), then runs the runnable, then ends dumb mode (if no other dumb tasks are running).
|
||||
*
|
||||
* This method can be invoked from any thread. It will switch to EDT to start/stop dumb mode. Runnable itself will be invoked from
|
||||
* method's invocation thread.
|
||||
*/
|
||||
@TestOnly
|
||||
fun runInDumbModeSynchronously(runnable: Runnable) {
|
||||
isDumb = true
|
||||
try {
|
||||
fun runInDumbModeSynchronously(runnable: ThrowableRunnable<in Throwable>) {
|
||||
computeInDumbModeSynchronously {
|
||||
runnable.run()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts dumb mode (if not started), then runs the computable, then ends dumb mode (if no other dumb tasks are running).
|
||||
*
|
||||
* This method can be invoked from any thread. It will switch to EDT to start/stop dumb mode. Runnable itself will be invoked from
|
||||
* method's invocation thread.
|
||||
*/
|
||||
@TestOnly
|
||||
fun <T> computeInDumbModeSynchronously(computable: ThrowableComputable<T, in Throwable>): T {
|
||||
application.invokeAndWait {
|
||||
isDumb = true
|
||||
}
|
||||
try {
|
||||
return computable.compute()
|
||||
}
|
||||
finally {
|
||||
isDumb = false
|
||||
application.invokeAndWait {
|
||||
isDumb = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts dumb mode (if not started), then runs suspend lambda, then ends dumb mode (if no other dumb tasks are running).
|
||||
*
|
||||
* This method can be invoked from any thread. It will switch to EDT to start/stop dumb mode. Runnable itself will be invoked from
|
||||
* method's invocation thread.
|
||||
*/
|
||||
@TestOnly
|
||||
suspend fun <T> runInDumbMode(block: suspend () -> T): T {
|
||||
executeImmediatelyOrScheduleOnEDT {
|
||||
isDumb = true
|
||||
}
|
||||
try {
|
||||
return block()
|
||||
}
|
||||
finally {
|
||||
executeImmediatelyOrScheduleOnEDT {
|
||||
isDumb = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun executeImmediatelyOrScheduleOnEDT(block: suspend () -> Unit) {
|
||||
//Dispatchers.EDT, Dispatchers.Main, and even Dispatchers.Main.immediate may never execute if already on EDT. See SwiftAttributeCompletionTest
|
||||
if (application.isDispatchThread) {
|
||||
block()
|
||||
}
|
||||
else {
|
||||
withContext(Dispatchers.EDT) {
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,7 @@ import com.intellij.util.indexing.diagnostic.ProjectDumbIndexingHistoryImpl
|
||||
import com.intellij.util.indexing.diagnostic.ProjectIndexingHistoryImpl
|
||||
import com.intellij.util.indexing.diagnostic.ScanningType
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.*
|
||||
import org.junit.*
|
||||
import org.junit.Assert.*
|
||||
import org.junit.runner.RunWith
|
||||
@@ -550,6 +547,13 @@ class DumbServiceImplTest {
|
||||
assertNull(exception.get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test startEternalDumbModeTask and endEternalDumbModeTaskAndWaitForSmartMode do not hang when invoked from EDT`() {
|
||||
runInEdtAndWait {
|
||||
val dumbTask = DumbModeTestUtils.startEternalDumbModeTask(project)
|
||||
DumbModeTestUtils.endEternalDumbModeTaskAndWaitForSmartMode(project, dumbTask)
|
||||
}
|
||||
}
|
||||
|
||||
private fun waitForSmartModeFiveSecondsOrThrow() {
|
||||
if (!dumbService.waitForSmartMode(5_000)) {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.testFramework
|
||||
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.DumbServiceImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.application
|
||||
import kotlinx.coroutines.*
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.jupiter.api.fail
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.DurationUnit.SECONDS
|
||||
import kotlin.time.toDuration
|
||||
|
||||
object DumbModeTestUtils {
|
||||
/**
|
||||
* "Eternal" means that test framework will not terminate the task. Please stop dumb mode in the end of test. Use wisely.
|
||||
*
|
||||
* Always invoke [Job.cancel] or [endEternalDumbModeTaskAndWaitForSmartMode] in test's `tearDown` in `finally` block.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun startEternalDumbModeTask(project: Project): Job {
|
||||
var dumbModeJob: Job? = null
|
||||
@Suppress("RAW_RUN_BLOCKING")
|
||||
runBlocking {
|
||||
val dumbModeStarted = CompletableDeferred<Boolean>()
|
||||
withTimeout(10.toDuration(SECONDS)) {
|
||||
dumbModeJob = CoroutineScope(Dispatchers.Main.immediate + Job()).launch {
|
||||
DumbServiceImpl.getInstance(project).runInDumbMode {
|
||||
dumbModeStarted.complete(true)
|
||||
delay(Duration.INFINITE)
|
||||
}
|
||||
}
|
||||
dumbModeStarted.await()
|
||||
}
|
||||
}
|
||||
assertTrue("Dumb mode didn't start", DumbService.isDumb(project))
|
||||
return dumbModeJob ?: fail("Could not start dumb mode task")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun endEternalDumbModeTaskAndWaitForSmartMode(project: Project, job: Job) {
|
||||
job.cancel()
|
||||
waitForSmartMode(project)
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for smart mode at most 10 seconds and throws AssertionError if smart mode didn't start.
|
||||
*
|
||||
* Can be invoked from any thread (even from EDT).
|
||||
*/
|
||||
@JvmStatic
|
||||
fun waitForSmartMode(project: Project) {
|
||||
if (application.isDispatchThread) {
|
||||
PlatformTestUtil.waitWithEventsDispatching("Dumb mode didn't finish", { !DumbService.isDumb(project) }, 10)
|
||||
}
|
||||
else {
|
||||
DumbServiceImpl.getInstance(project).waitForSmartMode(10_000)
|
||||
}
|
||||
assertFalse("Dumb mode didn't finish", DumbService.isDumb(project))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user