Support "Force Step Over" action over suspended calls (KT-18453)

#KT-18453 Fixed
This commit is contained in:
Nikolay Krasko
2017-06-13 18:16:18 +03:00
parent 0e8e8ef546
commit 850568b8e9
8 changed files with 83 additions and 8 deletions
@@ -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/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/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/custom", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doCustomTest")
}
@@ -96,7 +96,7 @@ class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
val location = suspendContext.debugProcess.invokeInManagerThread { suspendContext.frameProxy?.location() } ?: return null
if (isInSuspendMethod(location) && !isOnSuspendReturnOrReenter(location) && !isLastLineLocationInMethod(location)) {
return DebugProcessImplHelper.createStepOverCommandWithCustomFilter(
suspendContext, ignoreBreakpoints, KotlinSuspendCallStepOverFilter(sourcePosition.line, file))
suspendContext, ignoreBreakpoints, KotlinSuspendCallStepOverFilter(sourcePosition.line, file, ignoreBreakpoints))
}
return null
@@ -32,7 +32,10 @@ import org.jetbrains.kotlin.idea.debugger.isOnSuspendReturnOrReenter
import org.jetbrains.kotlin.idea.debugger.suspendFunctionFirstLineLocation
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 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
breakpointManager.applyThreadFilter(debugProcess, null)
createRunToCursorBreakpoint(context, suspendStartLineNumber - 1, file)
createRunToCursorBreakpoint(context, suspendStartLineNumber - 1, file, ignoreBreakpoints)
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 process = context.debugProcess
process.showStatusText(DebuggerBundle.message("status.run.to.cursor"))
process.cancelRunToCursorBreakpoint()
if (ignoreBreakpoints) {
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.disableBreakpoints(process)
}
val runToCursorBreakpoint =
runReadAction {
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.addRunToCursorBreakpoint(position, false)
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.addRunToCursorBreakpoint(position, ignoreBreakpoints)
} ?:
return
@@ -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
@@ -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")
}
protected fun doStepOverForceTest(path: String) {
doTest(path, "STEP_OVER_FORCE")
}
protected fun doSmartStepIntoTest(path: String) {
doTest(path, "SMART_STEP_INTO")
}
@@ -177,9 +177,10 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
dp.managerThread.schedule(stepOutCommand)
}
protected fun SuspendContextImpl.doStepOver() {
val stepOverCommand = runReadAction { commandProvider.getStepOverCommand(this, false, debuggerContext) }
?: dp.createStepOverCommand(this, false)
protected fun SuspendContextImpl.doStepOver(ignoreBreakpoints: Boolean = false) {
val stepOverCommand =
runReadAction { commandProvider.getStepOverCommand(this, ignoreBreakpoints, debuggerContext) } ?:
dp.createStepOverCommand(this, ignoreBreakpoints)
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_OUT: ") -> repeat("// STEP_OUT: ") { doStepOut() }
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: ") -> repeat("// SMART_STEP_INTO: ") { doSmartStepInto() }
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")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)