Support "Force Step Over" action over suspended calls (KT-18453)
#KT-18453 Fixed
This commit is contained in:
@@ -921,6 +921,7 @@ fun main(args: Array<String>) {
|
|||||||
model("debugger/tinyApp/src/stepping/stepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepIntoOnly")
|
model("debugger/tinyApp/src/stepping/stepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepIntoOnly")
|
||||||
model("debugger/tinyApp/src/stepping/stepOut", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepOutTest")
|
model("debugger/tinyApp/src/stepping/stepOut", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepOutTest")
|
||||||
model("debugger/tinyApp/src/stepping/stepOver", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepOverTest")
|
model("debugger/tinyApp/src/stepping/stepOver", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepOverTest")
|
||||||
|
model("debugger/tinyApp/src/stepping/stepOverForce", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepOverForceTest")
|
||||||
model("debugger/tinyApp/src/stepping/filters", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest")
|
model("debugger/tinyApp/src/stepping/filters", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest")
|
||||||
model("debugger/tinyApp/src/stepping/custom", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doCustomTest")
|
model("debugger/tinyApp/src/stepping/custom", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doCustomTest")
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -96,7 +96,7 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
|
|||||||
val location = suspendContext.debugProcess.invokeInManagerThread { suspendContext.frameProxy?.location() } ?: return null
|
val location = suspendContext.debugProcess.invokeInManagerThread { suspendContext.frameProxy?.location() } ?: return null
|
||||||
if (isInSuspendMethod(location) && !isOnSuspendReturnOrReenter(location) && !isLastLineLocationInMethod(location)) {
|
if (isInSuspendMethod(location) && !isOnSuspendReturnOrReenter(location) && !isLastLineLocationInMethod(location)) {
|
||||||
return DebugProcessImplHelper.createStepOverCommandWithCustomFilter(
|
return DebugProcessImplHelper.createStepOverCommandWithCustomFilter(
|
||||||
suspendContext, ignoreBreakpoints, KotlinSuspendCallStepOverFilter(sourcePosition.line, file))
|
suspendContext, ignoreBreakpoints, KotlinSuspendCallStepOverFilter(sourcePosition.line, file, ignoreBreakpoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|||||||
+11
-4
@@ -32,7 +32,10 @@ import org.jetbrains.kotlin.idea.debugger.isOnSuspendReturnOrReenter
|
|||||||
import org.jetbrains.kotlin.idea.debugger.suspendFunctionFirstLineLocation
|
import org.jetbrains.kotlin.idea.debugger.suspendFunctionFirstLineLocation
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
|
|
||||||
class KotlinSuspendCallStepOverFilter(private val line: Int, private val file: PsiFile) : MethodFilter {
|
class KotlinSuspendCallStepOverFilter(
|
||||||
|
private val line: Int,
|
||||||
|
private val file: PsiFile,
|
||||||
|
private val ignoreBreakpoints: Boolean) : MethodFilter {
|
||||||
override fun getCallingExpressionLines(): Range<Int>? = Range(line, line)
|
override fun getCallingExpressionLines(): Range<Int>? = Range(line, line)
|
||||||
|
|
||||||
override fun locationMatches(process: DebugProcessImpl, location: Location?): Boolean {
|
override fun locationMatches(process: DebugProcessImpl, location: Location?): Boolean {
|
||||||
@@ -47,20 +50,24 @@ class KotlinSuspendCallStepOverFilter(private val line: Int, private val file: P
|
|||||||
val breakpointManager = DebuggerManagerEx.getInstanceEx(debugProcess.project).breakpointManager
|
val breakpointManager = DebuggerManagerEx.getInstanceEx(debugProcess.project).breakpointManager
|
||||||
breakpointManager.applyThreadFilter(debugProcess, null)
|
breakpointManager.applyThreadFilter(debugProcess, null)
|
||||||
|
|
||||||
createRunToCursorBreakpoint(context, suspendStartLineNumber - 1, file)
|
createRunToCursorBreakpoint(context, suspendStartLineNumber - 1, file, ignoreBreakpoints)
|
||||||
return RequestHint.RESUME
|
return RequestHint.RESUME
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRunToCursorBreakpoint(context: SuspendContextImpl, line: Int, file: PsiFile) {
|
private fun createRunToCursorBreakpoint(context: SuspendContextImpl, line: Int, file: PsiFile, ignoreBreakpoints: Boolean) {
|
||||||
val position = XSourcePositionImpl.create(file.virtualFile, line) ?: return
|
val position = XSourcePositionImpl.create(file.virtualFile, line) ?: return
|
||||||
val process = context.debugProcess
|
val process = context.debugProcess
|
||||||
process.showStatusText(DebuggerBundle.message("status.run.to.cursor"))
|
process.showStatusText(DebuggerBundle.message("status.run.to.cursor"))
|
||||||
process.cancelRunToCursorBreakpoint()
|
process.cancelRunToCursorBreakpoint()
|
||||||
|
|
||||||
|
if (ignoreBreakpoints) {
|
||||||
|
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.disableBreakpoints(process)
|
||||||
|
}
|
||||||
|
|
||||||
val runToCursorBreakpoint =
|
val runToCursorBreakpoint =
|
||||||
runReadAction {
|
runReadAction {
|
||||||
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.addRunToCursorBreakpoint(position, false)
|
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.addRunToCursorBreakpoint(position, ignoreBreakpoints)
|
||||||
} ?:
|
} ?:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package sofSuspendableCallInFun
|
||||||
|
|
||||||
|
import forTests.builder
|
||||||
|
import kotlin.coroutines.experimental.Continuation
|
||||||
|
import kotlin.coroutines.experimental.suspendCoroutine
|
||||||
|
|
||||||
|
private fun foo(a: Any) {}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
builder {
|
||||||
|
inFun()
|
||||||
|
}
|
||||||
|
|
||||||
|
foo("Main end")
|
||||||
|
Thread.sleep(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun inFun() {
|
||||||
|
//Breakpoint!
|
||||||
|
run()
|
||||||
|
foo("End")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun run() {
|
||||||
|
//Breakpoint!
|
||||||
|
foo("This breakpoint should be skipped")
|
||||||
|
|
||||||
|
suspendCoroutine { cont: Continuation<Unit> ->
|
||||||
|
Thread {
|
||||||
|
cont.resume(Unit)
|
||||||
|
Thread.sleep(10)
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP_OVER_FORCE: 2
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
LineBreakpoint created at sofSuspendableCallInFun.kt:20
|
||||||
|
LineBreakpoint created at sofSuspendableCallInFun.kt:26
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
sofSuspendableCallInFun.kt:20
|
||||||
|
sofSuspendableCallInFun.kt:18
|
||||||
|
sofSuspendableCallInFun.kt:21
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
@@ -33,6 +33,10 @@ abstract class AbstractKotlinSteppingTest : KotlinDebuggerTestBase() {
|
|||||||
doTest(path, "STEP_OVER")
|
doTest(path, "STEP_OVER")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun doStepOverForceTest(path: String) {
|
||||||
|
doTest(path, "STEP_OVER_FORCE")
|
||||||
|
}
|
||||||
|
|
||||||
protected fun doSmartStepIntoTest(path: String) {
|
protected fun doSmartStepIntoTest(path: String) {
|
||||||
doTest(path, "SMART_STEP_INTO")
|
doTest(path, "SMART_STEP_INTO")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,9 +177,10 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
|
|||||||
dp.managerThread.schedule(stepOutCommand)
|
dp.managerThread.schedule(stepOutCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun SuspendContextImpl.doStepOver() {
|
protected fun SuspendContextImpl.doStepOver(ignoreBreakpoints: Boolean = false) {
|
||||||
val stepOverCommand = runReadAction { commandProvider.getStepOverCommand(this, false, debuggerContext) }
|
val stepOverCommand =
|
||||||
?: dp.createStepOverCommand(this, false)
|
runReadAction { commandProvider.getStepOverCommand(this, ignoreBreakpoints, debuggerContext) } ?:
|
||||||
|
dp.createStepOverCommand(this, ignoreBreakpoints)
|
||||||
dp.managerThread.schedule(stepOverCommand)
|
dp.managerThread.schedule(stepOverCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +204,7 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
|
|||||||
line.startsWith("// STEP_INTO: ") -> repeat("// STEP_INTO: ") { doStepInto(false, null) }
|
line.startsWith("// STEP_INTO: ") -> repeat("// STEP_INTO: ") { doStepInto(false, null) }
|
||||||
line.startsWith("// STEP_OUT: ") -> repeat("// STEP_OUT: ") { doStepOut() }
|
line.startsWith("// STEP_OUT: ") -> repeat("// STEP_OUT: ") { doStepOut() }
|
||||||
line.startsWith("// STEP_OVER: ") -> repeat("// STEP_OVER: ") { doStepOver() }
|
line.startsWith("// STEP_OVER: ") -> repeat("// STEP_OVER: ") { doStepOver() }
|
||||||
|
line.startsWith("// STEP_OVER_FORCE: ") -> repeat("// STEP_OVER_FORCE: ") { doStepOver(true) }
|
||||||
line.startsWith("// SMART_STEP_INTO_BY_INDEX: ") -> doOnBreakpoint { doSmartStepInto(InTextDirectivesUtils.getPrefixedInt(line, "// SMART_STEP_INTO_BY_INDEX: ")!!) }
|
line.startsWith("// SMART_STEP_INTO_BY_INDEX: ") -> doOnBreakpoint { doSmartStepInto(InTextDirectivesUtils.getPrefixedInt(line, "// SMART_STEP_INTO_BY_INDEX: ")!!) }
|
||||||
line.startsWith("// SMART_STEP_INTO: ") -> repeat("// SMART_STEP_INTO: ") { doSmartStepInto() }
|
line.startsWith("// SMART_STEP_INTO: ") -> repeat("// SMART_STEP_INTO: ") { doSmartStepInto() }
|
||||||
line.startsWith("// RESUME: ") -> repeat("// RESUME: ") { resume(this) }
|
line.startsWith("// RESUME: ") -> repeat("// RESUME: ") { resume(this) }
|
||||||
|
|||||||
@@ -963,6 +963,21 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOverForce")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class StepOverForce extends AbstractKotlinSteppingTest {
|
||||||
|
public void testAllFilesPresentInStepOverForce() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/stepping/stepOverForce"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("sofSuspendableCallInFun.kt")
|
||||||
|
public void testSofSuspendableCallInFun() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOverForce/sofSuspendableCallInFun.kt");
|
||||||
|
doStepOverForceTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("idea/testData/debugger/tinyApp/src/stepping/filters")
|
@TestMetadata("idea/testData/debugger/tinyApp/src/stepping/filters")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user