From 73946050c1898cab20426bf683e7e802827d311c Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Wed, 26 Aug 2015 21:18:17 +0300 Subject: [PATCH] Debugger: support stepping over inline function --- .../kotlin/generators/tests/GenerateTests.kt | 1 + idea/src/META-INF/plugin.xml | 5 +- .../KotlinBreakpointHandlerFactory.kt | 11 +- .../breakpoints/KotlinLineBreakpointType.java | 73 +++++++++++ .../breakpoints/breakpointTypeUtils.kt | 30 +++-- .../stepping/KotlinSteppingCommandProvider.kt | 120 ++++++++++++++++++ .../idea/refactoring/jetRefactoringUtil.kt | 15 ++- .../outs/allFilesPresentInStepOver.out | 0 .../tinyApp/outs/stepOverInlinedLambda.out | 17 +++ .../outs/stepOverInlinedLambdaStdlib.out | 11 ++ .../stepOver/stepOverInlinedLambda.kt | 50 ++++++++ .../stepOver/stepOverInlinedLambdaStdlib.kt | 18 +++ .../debugger/AbstractKotlinSteppingTest.kt | 17 ++- .../idea/debugger/KotlinDebuggerTestBase.kt | 33 +++-- .../idea/debugger/KotlinDebuggerTestCase.java | 1 + .../debugger/KotlinSteppingTestGenerated.java | 21 +++ 16 files changed, 384 insertions(+), 39 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java create mode 100644 idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt create mode 100644 idea/testData/debugger/tinyApp/outs/allFilesPresentInStepOver.out create mode 100644 idea/testData/debugger/tinyApp/outs/stepOverInlinedLambda.out create mode 100644 idea/testData/debugger/tinyApp/outs/stepOverInlinedLambdaStdlib.out create mode 100644 idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlinedLambda.kt create mode 100644 idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlinedLambdaStdlib.kt diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 88c07a61eaf..47b54f67e0a 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -637,6 +637,7 @@ fun main(args: Array) { model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto") model("debugger/tinyApp/src/stepping/stepInto", testMethod = "doStepIntoTest", testClassName = "StepIntoOnly") model("debugger/tinyApp/src/stepping/stepOut", testMethod = "doStepOutTest") + model("debugger/tinyApp/src/stepping/stepOver", testMethod = "doStepOverTest") model("debugger/tinyApp/src/stepping/filters", testMethod = "doStepIntoTest") model("debugger/tinyApp/src/stepping/custom", testMethod = "doCustomTest") } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 641e5f72780..fe69425e7ee 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -447,8 +447,11 @@ + - + + + diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinBreakpointHandlerFactory.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinBreakpointHandlerFactory.kt index 7645f437e85..9e202ceb459 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinBreakpointHandlerFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinBreakpointHandlerFactory.kt @@ -21,10 +21,17 @@ import com.intellij.debugger.engine.JavaBreakpointHandler import com.intellij.debugger.engine.JavaBreakpointHandlerFactory import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType -public class KotlinBreakpointHandlerFactory: JavaBreakpointHandlerFactory { +public class KotlinFieldBreakpointHandlerFactory : JavaBreakpointHandlerFactory { override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? { return KotlinFieldBreakpointHandler(process) } } -public class KotlinFieldBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(javaClass(), process) \ No newline at end of file +public class KotlinLineBreakpointHandlerFactory: JavaBreakpointHandlerFactory { + override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? { + return KotlinLineBreakpointHandler(process) + } +} + +public class KotlinFieldBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(javaClass(), process) +public class KotlinLineBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(javaClass(), process) \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java new file mode 100644 index 00000000000..cbff8e4ea9b --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.debugger.breakpoints; + +import com.intellij.debugger.DebuggerBundle; +import com.intellij.debugger.SourcePosition; +import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType; +import com.intellij.debugger.ui.breakpoints.LineBreakpoint; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.psi.JetFunction; + +public class KotlinLineBreakpointType extends JavaLineBreakpointType { + public KotlinLineBreakpointType() { + super("kotlin-line", "Kotlin Line Breakpoints"); + } + + @Override + public boolean matchesPosition(@NotNull LineBreakpoint breakpoint, @NotNull SourcePosition position) { + if (super.matchesPosition(breakpoint, position)) return true; + + PsiElement containingMethod = getContainingMethod(breakpoint); + if (containingMethod == null) return false; + return inTheMethod(position, containingMethod); + } + + @Override + @Nullable + public PsiElement getContainingMethod(@NotNull LineBreakpoint breakpoint) { + SourcePosition position = breakpoint.getSourcePosition(); + if (position == null) return null; + + return getContainingMethod(position.getElementAt()); + } + + @Nullable + public static PsiElement getContainingMethod(@Nullable PsiElement elem) { + //noinspection unchecked + return PsiTreeUtil.getParentOfType(elem, JetFunction.class); + } + + public static boolean inTheMethod(@NotNull SourcePosition pos, @NotNull PsiElement method) { + PsiElement elem = pos.getElementAt(); + if (elem == null) return false; + return Comparing.equal(getContainingMethod(elem), method); + } + + @Override + public boolean canPutAt(@NotNull VirtualFile file, int line, @NotNull Project project) { + return BreakpointsPackage.canPutAt(file, line, project, getClass()); + } + + +} diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt index f63c1e1c3cc..dcc705a5ad3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt @@ -16,34 +16,30 @@ package org.jetbrains.kotlin.idea.debugger.breakpoints +import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiComment -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiManager -import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.* import com.intellij.psi.util.PsiTreeUtil import com.intellij.xdebugger.XDebuggerUtil import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.idea.JetFileType import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.psi.JetElement -import org.jetbrains.kotlin.psi.JetParameter -import org.jetbrains.kotlin.psi.JetProperty +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass: Class<*>): Boolean { val psiFile = PsiManager.getInstance(project).findFile(file) - if (psiFile == null || psiFile.getVirtualFile().getFileType() != JetFileType.INSTANCE) { + if (psiFile == null || psiFile.virtualFile.fileType != JetFileType.INSTANCE) { return false } val document = FileDocumentManager.getInstance().getDocument(file) ?: return false - var canPutAt = false + var result: Class<*>? = null XDebuggerUtil.getInstance().iterateLine(project, document, line, fun (el: PsiElement): Boolean { // avoid comments if (el is PsiWhiteSpace || PsiTreeUtil.getParentOfType(el, javaClass(), false) != null) { @@ -51,13 +47,13 @@ fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass } var element = el - var parent = element.getParent() + var parent = element.parent while (parent != null) { - val offset = parent.getTextOffset() + val offset = parent.textOffset if (offset >= 0 && document.getLineNumber(offset) != line) break element = parent - parent = element.getParent() + parent = element.parent } if (element is JetProperty || element is JetParameter) { @@ -67,15 +63,21 @@ fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass descriptor = bindingContext.get(BindingContext.VALUE_PARAMETER_AS_PROPERTY, descriptor) } if (descriptor is PropertyDescriptor) { - canPutAt = true + result = KotlinFieldBreakpointType::class.java + } + else { + result = KotlinLineBreakpointType::class.java } return false } + else { + result = KotlinLineBreakpointType::class.java + } return true }) - return canPutAt + return result == breakpointTypeClass } diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt new file mode 100644 index 00000000000..f875c6ba0b2 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinSteppingCommandProvider.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.debugger.stepping + +import com.intellij.debugger.SourcePosition +import com.intellij.debugger.engine.ContextUtil +import com.intellij.debugger.engine.DebugProcessImpl +import com.intellij.debugger.engine.SuspendContextImpl +import com.intellij.debugger.engine.events.DebuggerCommandImpl +import com.intellij.debugger.impl.JvmSteppingCommandProvider +import com.intellij.util.concurrency.Semaphore +import com.intellij.xdebugger.impl.XSourcePositionImpl +import com.sun.jdi.ReferenceType +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils +import org.jetbrains.kotlin.idea.core.refactoring.getLineCount +import org.jetbrains.kotlin.idea.core.refactoring.getLineStartOffset +import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.inline.InlineUtil +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() { + override fun getStepOverCommand( + suspendContext: SuspendContextImpl?, + ignoreBreakpoints: Boolean, + stepSize: Int + ): DebugProcessImpl.ResumeCommand? { + if (suspendContext == null) return null + + val semaphore = Semaphore() + semaphore.down() + + var sourcePosition : SourcePosition? = null + var allClasses: List? = null + val worker = object : DebuggerCommandImpl() { + override fun action() { + try { + sourcePosition = runReadAction { ContextUtil.getSourcePosition(suspendContext) } + if (sourcePosition != null) { + allClasses = suspendContext.debugProcess.positionManager.getAllClasses(sourcePosition!!) + } + } + finally { + semaphore.up() + } + } + } + + suspendContext.debugProcess.managerThread?.invoke(worker) + + for (i in 0..25) { + if (semaphore.waitFor(20)) break + } + + val computedSourcePosition = sourcePosition ?: return null + val computedReferenceType = allClasses?.firstOrNull() ?: return null + + val file = computedSourcePosition.file as? JetFile ?: return null + + val inlinedArguments = getInlinedArgumentsIfAny(computedSourcePosition) ?: return null + + val locations = computedReferenceType.allLineLocations() + val countOfLinesInFile = file.getLineCount() + + val nextLine = computedSourcePosition.line + 2 /* +1 - because of locations are counted from 1 and +1 - because we want next line */ + + for (lineNumber in nextLine..countOfLinesInFile) { + val location = locations.firstOrNull { it.lineNumber() == lineNumber } + if (location != null) { + val lineStartOffset = file.getLineStartOffset(lineNumber - 1) ?: continue + if (inlinedArguments.any { it.textRange.contains(lineStartOffset) }) continue + + val elementAt = file.findElementAt(lineStartOffset) + val xPosition = XSourcePositionImpl.createByElement(elementAt) ?: return null + + return suspendContext.debugProcess.createRunToCursorCommand(suspendContext, xPosition, ignoreBreakpoints) + } + } + + return null + } + + private fun getInlinedArgumentsIfAny(sourcePosition: SourcePosition): List? { + val file = sourcePosition.file as? JetFile ?: return null + val lineNumber = sourcePosition.line + val elementAt = CodeInsightUtils.getTopmostElementAtOffset( + sourcePosition.elementAt, + file.getLineStartOffset(lineNumber) ?: sourcePosition.elementAt.startOffset + ) ?: return null + + val start = elementAt.startOffset + val end = elementAt.endOffset + + return CodeInsightUtils. + findElementsOfClassInRange(file, start, end, JetFunctionLiteral::class.java, JetNamedFunction::class.java) + .filter { JetPsiUtil.getParentCallIfPresent(it as JetExpression) != null } + .filterIsInstance() + .filter { + val context = it.analyze(BodyResolveMode.PARTIAL) + InlineUtil.isInlinedArgument(it, context, false) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt index e0061d53ab7..5df35ce0ffe 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt @@ -82,8 +82,7 @@ import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.BindingContext import java.io.File import java.lang.annotation.Retention -import java.util.ArrayList -import java.util.Collections +import java.util.* import javax.swing.Icon fun PsiElement.getAndRemoveCopyableUserData(key: Key): T? { @@ -280,6 +279,18 @@ public class SelectionAwareScopeHighlighter(val editor: Editor) { } } +fun PsiFile.getLineStartOffset(line: Int): Int? { + val doc = this.let { file -> PsiDocumentManager.getInstance(getProject()).getDocument(file) } + if (doc != null) { + val startOffset = doc.getLineStartOffset(line) + val element = findElementAt(startOffset) ?: return startOffset + + return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace::class.java, PsiComment::class.java)?.startOffset ?: startOffset + } + + return null +} + fun PsiElement.getLineCount(): Int { val doc = getContainingFile()?.let { file -> PsiDocumentManager.getInstance(getProject()).getDocument(file) } if (doc != null) { diff --git a/idea/testData/debugger/tinyApp/outs/allFilesPresentInStepOver.out b/idea/testData/debugger/tinyApp/outs/allFilesPresentInStepOver.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/debugger/tinyApp/outs/stepOverInlinedLambda.out b/idea/testData/debugger/tinyApp/outs/stepOverInlinedLambda.out new file mode 100644 index 00000000000..87f04ec36de --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/stepOverInlinedLambda.out @@ -0,0 +1,17 @@ +LineBreakpoint created at stepOverInlinedLambda.kt:6 +!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! stepOverInlinedLambda.StepOverInlinedLambdaPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +stepOverInlinedLambda.kt:6 +stepOverInlinedLambda.kt:7 +stepOverInlinedLambda.kt:10 +stepOverInlinedLambda.kt:11 +stepOverInlinedLambda.kt:16 +stepOverInlinedLambda.kt:17 +stepOverInlinedLambda.kt:19 +stepOverInlinedLambda.kt:20 +stepOverInlinedLambda.kt:23 +stepOverInlinedLambda.kt:29 +stepOverInlinedLambda.kt:30 +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/outs/stepOverInlinedLambdaStdlib.out b/idea/testData/debugger/tinyApp/outs/stepOverInlinedLambdaStdlib.out new file mode 100644 index 00000000000..ff54d8c6c56 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/stepOverInlinedLambdaStdlib.out @@ -0,0 +1,11 @@ +LineBreakpoint created at stepOverInlinedLambdaStdlib.kt:5 +!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! stepOverInlinedLambdaStdlib.StepOverInlinedLambdaStdlibPackage +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +stepOverInlinedLambdaStdlib.kt:5 +stepOverInlinedLambdaStdlib.kt:6 +stepOverInlinedLambdaStdlib.kt:8 +stepOverInlinedLambdaStdlib.kt:10 +stepOverInlinedLambdaStdlib.kt:15 +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlinedLambda.kt b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlinedLambda.kt new file mode 100644 index 00000000000..6c20cbb5dde --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlinedLambda.kt @@ -0,0 +1,50 @@ +package stepOverInlinedLambda + +fun main(args: Array) { + val a = A() + //Breakpoint! + foo { test(1) } + foo { + test(2) + } + a.foo { test(3) }.foo { test(4) } + a.foo { + test(5) + }.foo { + test(6) + } + a.foo { test(7) } + .foo { test(8) } + + foo({ test(9) }) { test(10) } + foo({ test(11) }) { + test(12) + } + foo({ + test(13) + }, { + test(14) + }) + + val b = foo { test(1) } +} + +inline fun foo(f: () -> Unit) { + f() +} + +inline fun foo(f1: () -> Unit, f2: () -> Unit) { + f1() + f2() +} + +class A { + inline fun foo(f: () -> Unit): A { + f() + return this + } +} + +fun test(i: Int) = 1 + +// STEP_OVER: 11 \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlinedLambdaStdlib.kt b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlinedLambdaStdlib.kt new file mode 100644 index 00000000000..de83ee6fad7 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlinedLambdaStdlib.kt @@ -0,0 +1,18 @@ +package stepOverInlinedLambdaStdlib + +fun main(args: Array) { + //Breakpoint! + val a = listOf(1, 2, 3) + a.filter { it > 1 } + + a.filter { it > 1 }.map { it * 2 } + + a.filter { + it > 1 + }.map { + it * 2 + } +} + +// TRACING_FILTERS_ENABLED: false +// STEP_OVER: 4 \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/AbstractKotlinSteppingTest.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/AbstractKotlinSteppingTest.kt index 0a2d943ae9a..6513bed291d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/AbstractKotlinSteppingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/AbstractKotlinSteppingTest.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.debugger import com.intellij.openapi.util.io.FileUtil +import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.InTextDirectivesUtils.getPrefixedInt import java.io.File @@ -29,6 +30,10 @@ public abstract class AbstractKotlinSteppingTest : KotlinDebuggerTestBase() { doTest(path, "STEP_OUT") } + protected fun doStepOverTest(path: String) { + doTest(path, "STEP_OVER") + } + protected fun doSmartStepIntoTest(path: String) { doTest(path, "SMART_STEP_INTO") } @@ -51,15 +56,9 @@ public abstract class AbstractKotlinSteppingTest : KotlinDebuggerTestBase() { createDebugProcess(path) - for (i in 1..(getPrefixedInt(fileText, "// $command: ") ?: 1)) { - doOnBreakpoint { - when(command) { - "STEP_INTO" -> stepInto(this) - "STEP_OUT" -> stepOut() - "SMART_STEP_INTO" -> smartStepInto() - } - } - } + val prefix = "// $command: " + val count = InTextDirectivesUtils.getPrefixedInt(fileText, prefix) ?: "1" + processSteppingInstruction("$prefix$count") finish() } diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt index 9dfd2bb8dbd..062ea8565d1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt @@ -43,6 +43,7 @@ import com.intellij.xdebugger.breakpoints.XBreakpoint import com.intellij.xdebugger.breakpoints.XBreakpointProperties import com.intellij.xdebugger.breakpoints.XBreakpointType import com.intellij.xdebugger.breakpoints.XLineBreakpointType +import com.sun.jdi.request.StepRequest import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpoint import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType import org.jetbrains.kotlin.idea.debugger.stepping.* @@ -138,24 +139,34 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { dp.getManagerThread()!!.schedule(dp.createStepOutCommand(this)) } + protected fun SuspendContextImpl.stepOver() { + val stepOverCommand = runReadAction { KotlinSteppingCommandProvider().getStepOverCommand(this, false, StepRequest.STEP_LINE) } + ?: dp.createStepOverCommand(this, false) + dp.getManagerThread()!!.schedule(stepOverCommand) + } + protected fun doStepping(path: String) { val file = File(path) - val fileText = file.readText() + file.readLines().forEach { + val line = it.trim() + processSteppingInstruction(line) + } + } + + protected fun processSteppingInstruction(line: String) { fun repeat(indexPrefix: String, f: SuspendContextImpl.() -> Unit) { - for (i in 1..(InTextDirectivesUtils.getPrefixedInt(fileText, indexPrefix) ?: 1)) { + for (i in 1..(InTextDirectivesUtils.getPrefixedInt(line, indexPrefix) ?: 1)) { doOnBreakpoint(f) } } - file.readLines().forEach { - val line = it.trim() - when { - line.startsWith("// STEP_INTO: ") -> repeat("// STEP_INTO: ") { stepInto(this) } - line.startsWith("// STEP_OUT: ") -> repeat("// STEP_OUT: ") { stepOut() } - line.startsWith("// SMART_STEP_INTO_BY_INDEX: ") -> doOnBreakpoint { smartStepInto(InTextDirectivesUtils.getPrefixedInt(it, "// SMART_STEP_INTO_BY_INDEX: ")!!) } - line.startsWith("// SMART_STEP_INTO: ") -> repeat("// SMART_STEP_INTO: ") { smartStepInto() } - line.startsWith("// RESUME: ") -> repeat("// RESUME: ") { resume(this) } - } + when { + line.startsWith("// STEP_INTO: ") -> repeat("// STEP_INTO: ") { stepInto(this) } + line.startsWith("// STEP_OUT: ") -> repeat("// STEP_OUT: ") { stepOut() } + line.startsWith("// STEP_OVER: ") -> repeat("// STEP_OVER: ") { stepOver() } + line.startsWith("// SMART_STEP_INTO_BY_INDEX: ") -> doOnBreakpoint { smartStepInto(InTextDirectivesUtils.getPrefixedInt(line, "// SMART_STEP_INTO_BY_INDEX: ")!!) } + line.startsWith("// SMART_STEP_INTO: ") -> repeat("// SMART_STEP_INTO: ") { smartStepInto() } + line.startsWith("// RESUME: ") -> repeat("// RESUME: ") { resume(this) } } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java index c1d18a49c93..7f48acd520c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java @@ -216,6 +216,7 @@ public abstract class KotlinDebuggerTestCase extends DescriptorTestCase { @SuppressWarnings("MethodMayBeStatic") protected void createDebugProcess(@NotNull String path) throws Exception { + VfsUtil.markDirty(true, true, VfsUtil.findFileByIoFile(new File(TINY_APP), true)); File file = new File(path); String packageName = file.getName().replace(".kt", ""); createLocalProcess(PackageClassUtils.getPackageClassFqName(new FqName(packageName)).asString()); diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java index c8fecebd8f0..6322f0355e3 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java @@ -299,6 +299,27 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest { } } + @TestMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class StepOver extends AbstractKotlinSteppingTest { + public void testAllFilesPresentInStepOver() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/stepping/stepOver"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("stepOverInlinedLambda.kt") + public void testStepOverInlinedLambda() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlinedLambda.kt"); + doStepOverTest(fileName); + } + + @TestMetadata("stepOverInlinedLambdaStdlib.kt") + public void testStepOverInlinedLambdaStdlib() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stepOverInlinedLambdaStdlib.kt"); + doStepOverTest(fileName); + } + } + @TestMetadata("idea/testData/debugger/tinyApp/src/stepping/filters") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)