[debugger] Add possibility to use suspend-thread mode in Kotlin stepping tests

IDEA-381878 IDEA-365578

GitOrigin-RevId: 84125a840c6d105a087eb34131f3d7da899d6438
This commit is contained in:
Alexey.Merkulov
2025-11-08 00:55:18 +00:00
committed by intellij-monorepo-bot
parent b4e940ebe4
commit 23fe1a8e21
6 changed files with 73 additions and 6 deletions
@@ -3,8 +3,12 @@ package com.intellij.debugger
import com.intellij.execution.process.ProcessEvent
import com.intellij.execution.process.ProcessListener
import com.intellij.openapi.util.KeyWithDefaultValue
import com.intellij.util.ConcurrencyUtil
@JvmField
val USE_XSESSION_PAUSE_LISTENER_KEY: KeyWithDefaultValue<Boolean?> = KeyWithDefaultValue.create("USE_XSESSION_PAUSE_LISTENER_KEY", false)
/**
* It allows to avoid the hell with listeners by pretty linear program.
* It allows to write:
@@ -7,6 +7,7 @@ import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl;
import com.intellij.debugger.engine.events.DebuggerCommandImpl;
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.impl.PositionUtil;
import com.intellij.debugger.impl.PrioritizedTask;
import com.intellij.debugger.jdi.StackFrameProxyImpl;
@@ -31,6 +32,11 @@ import com.intellij.util.TimeoutUtil;
import com.intellij.util.lang.CompoundRuntimeException;
import com.intellij.util.ui.EdtInvocationManager;
import com.intellij.util.ui.UIUtil;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.XDebugSessionListener;
import com.intellij.xdebugger.frame.XSuspendContext;
import com.intellij.xdebugger.impl.XSteppingSuspendContext;
import com.intellij.xdebugger.impl.frame.XDebugSessionProxyKeeperKt;
import com.sun.jdi.Method;
import com.sun.jdi.request.StepRequest;
import org.jetbrains.annotations.NotNull;
@@ -43,6 +49,8 @@ import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.intellij.debugger.DebuggerBreakpointTestUtilsKt.USE_XSESSION_PAUSE_LISTENER_KEY;
/**
* Runs the IDE with the debugger.
* <p>
@@ -151,11 +159,35 @@ public abstract class ExecutionWithDebuggerToolsTestCase extends ExecutionTestCa
assertNotNull("Debug process was not started", debugProcess);
myBreakpointProvider = new BreakpointProvider(myDebugProcess);
debugProcess.addDebugProcessListener(myBreakpointProvider, getTestRootDisposable());
if (USE_XSESSION_PAUSE_LISTENER_KEY.get(myDebugProcess.getProject(), false)) {
useXSessionPauseListener(debugProcess);
} else {
debugProcess.addDebugProcessListener(myBreakpointProvider, getTestRootDisposable());
}
}
return myBreakpointProvider;
}
private void useXSessionPauseListener(DebugProcessImpl debugProcess) {
DebuggerSession jvmSession = debugProcess.getSession();
JavaDebugProcess process = debugProcess.getXdebugProcess();
assert (process != null);
XDebugSession xSession = process.getSession();
XDebugSessionProxyKeeperKt.asProxy(xSession).addSessionListener(new XDebugSessionListener() {
@Override
public void sessionPaused() {
SuspendContextImpl suspendContext = jvmSession.getContextManager().getContext().getSuspendContext();
if (suspendContext == null) {
XSuspendContext xSessionSuspendContext = xSession.getSuspendContext();
assert xSessionSuspendContext instanceof XSteppingSuspendContext : "Suspension context is null and XSuspendContext is " + xSessionSuspendContext;
} else {
myBreakpointProvider.pausedWrapper(suspendContext);
}
}
}, xSession.getProject());
}
/**
* Queues an action to be run a single time.
* <p>
@@ -573,6 +605,10 @@ public abstract class ExecutionWithDebuggerToolsTestCase extends ExecutionTestCa
@Override
public void paused(SuspendContextImpl suspendContext) {
// Need to add SuspendContextCommandImpl because the stepping pause is not now in SuspendContextCommandImpl
pausedWrapper(suspendContext);
}
void pausedWrapper(SuspendContextImpl suspendContext) {
if (DebugProcessImpl.isInSuspendCommand(suspendContext)) {
pausedImpl(suspendContext);
}
@@ -40,9 +40,9 @@ import com.intellij.util.containers.addIfNotNull
import com.intellij.xdebugger.XDebugSession
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
import org.jetbrains.kotlin.idea.artifacts.TestKotlinArtifacts
import org.jetbrains.kotlin.idea.base.codeInsight.KotlinMainFunctionDetector
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
import org.jetbrains.kotlin.idea.artifacts.TestKotlinArtifacts
import org.jetbrains.kotlin.idea.base.psi.classIdIfNonLocal
import org.jetbrains.kotlin.idea.base.test.IgnoreTests
import org.jetbrains.kotlin.idea.base.test.InTextDirectivesUtils
@@ -633,6 +633,7 @@ abstract class KotlinDescriptorTestCase : DescriptorTestCase(),
if (file != null) {
val breakpointCreator = this.breakpointCreator ?: error(BreakpointCreator::class.java.simpleName + " should be set")
breakpointCreator.createBreakpoints(file)
myWasUsedOnlyDefaultSuspendPolicy = breakpointCreator.wasUsedOnlyDefaultSuspendPolicy
}
}
@@ -49,6 +49,8 @@ internal object DebuggerPreferenceKeys {
val JVM_DEFAULT_MODE by debuggerPreferenceKey("")
val USE_XSESSION_PAUSE_LISTENER by debuggerPreferenceKey(false)
val values: List<DebuggerPreferenceKey<*>> by lazy {
DebuggerPreferenceKeys::class.declaredMemberProperties
.filter { (it.returnType.javaType as? ParameterizedType)?.rawType == DebuggerPreferenceKey::class.java }
@@ -2,13 +2,14 @@
package org.jetbrains.kotlin.idea.debugger.test.preference
import com.intellij.debugger.USE_XSESSION_PAUSE_LISTENER_KEY
import com.intellij.debugger.settings.DebuggerSettings
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.project.Project
import com.intellij.xdebugger.impl.settings.XDebuggerSettingManagerImpl
import org.jetbrains.kotlin.idea.compiler.configuration.Kotlin2JvmCompilerArgumentsHolder
import org.jetbrains.kotlin.idea.debugger.core.DebuggerUtils
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
import org.jetbrains.kotlin.idea.debugger.core.DebuggerUtils
import org.jetbrains.kotlin.idea.debugger.core.ToggleKotlinVariablesState
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.ReflectionCallClassPatcher
import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferenceKeys.DISABLE_KOTLIN_INTERNAL_CLASSES
@@ -33,6 +34,7 @@ internal val SettingsMutators: List<SettingsMutator<*>> = listOf(
ForceRankingSettingsMutator,
ReflectionPatchingMutator,
ShowLibraryStackFramesMutator,
UseXSessionPauseListenerMutator,
)
private class DebuggerSettingsMutator<T : Any>(
@@ -104,3 +106,11 @@ private object ShowLibraryStackFramesMutator : SettingsMutator<Boolean>(Debugger
return oldValue
}
}
private object UseXSessionPauseListenerMutator : SettingsMutator<Boolean>(DebuggerPreferenceKeys.USE_XSESSION_PAUSE_LISTENER) {
override fun setValue(value: Boolean, project: Project): Boolean {
val oldValue = USE_XSESSION_PAUSE_LISTENER_KEY.get(project, false)!!
USE_XSESSION_PAUSE_LISTENER_KEY.set(project, value)
return oldValue
}
}
@@ -4,6 +4,7 @@ package org.jetbrains.kotlin.idea.debugger.test.util
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl
import com.intellij.debugger.settings.DebuggerSettings
import com.intellij.debugger.ui.breakpoints.Breakpoint
import com.intellij.debugger.ui.breakpoints.BreakpointManager
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType
@@ -40,6 +41,8 @@ internal class BreakpointCreator(
private val logger: (String) -> Unit,
private val preferences: DebuggerPreferences
) {
var wasUsedOnlyDefaultSuspendPolicy = true
fun createBreakpoints(file: PsiFile) {
val document = runReadAction { PsiDocumentManager.getInstance(project).getDocument(file) } ?: return
val breakpointManager = XDebuggerManager.getInstance(project).breakpointManager
@@ -85,7 +88,8 @@ internal class BreakpointCreator(
val lambdaOrdinal = getPropertyFromComment(comment, "lambdaOrdinal")?.toInt()
val conditionalReturn = getPropertyFromComment(comment, "conditionalReturn").toBoolean()
val condition = getPropertyFromComment(comment, "condition")
createLineBreakpoint(breakpointManager, file, lineIndex, lambdaOrdinal, conditionalReturn, condition)
val suspendPolicy = getPropertyFromComment(comment, "suspendPolicy")
createLineBreakpoint(breakpointManager, file, lineIndex, lambdaOrdinal, conditionalReturn, condition, suspendPolicy)
}
comment.startsWith("//FunctionBreakpoint!") -> {
createFunctionBreakpoint(breakpointManager, file, lineIndex, false)
@@ -109,7 +113,7 @@ internal class BreakpointCreator(
when (kind) {
"line" -> createBreakpoint(fileName, lineMarker) { psiFile, lineNumber ->
createLineBreakpoint(breakpointManager, psiFile, lineNumber + 1, ordinal, false, null)
createLineBreakpoint(breakpointManager, psiFile, lineNumber + 1, ordinal, false, null, null)
}
"fun" -> createBreakpoint(fileName, lineMarker) { psiFile, lineNumber ->
createFunctionBreakpoint(breakpointManager, psiFile, lineNumber, true)
@@ -173,7 +177,8 @@ internal class BreakpointCreator(
lineIndex: Int,
lambdaOrdinal: Int?,
conditionalReturn: Boolean,
condition: String?
condition: String?,
suspendPolicy: String?
) {
val kotlinLineBreakpointType = findBreakpointType(KotlinLineBreakpointType::class.java)
val updatedLambdaOrdinal = lambdaOrdinal?.let { if (it != JavaLineBreakpointProperties.NO_LAMBDA) it - 1 else it }
@@ -205,6 +210,15 @@ internal class BreakpointCreator(
suffix += " condition = $condition"
}
if (suspendPolicy != null) {
wasUsedOnlyDefaultSuspendPolicy = false
val okVariants = listOf(DebuggerSettings.SUSPEND_ALL, DebuggerSettings.SUSPEND_THREAD)
if (!okVariants.contains(suspendPolicy)) {
error("Unknown suspend policy: $suspendPolicy")
}
javaBreakpoint.suspendPolicy = suspendPolicy
}
BreakpointManager.addBreakpoint(javaBreakpoint)
logger("LineBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1}$suffix")
}