Re-enable evaluation tests in 201 platform
Revert `[DEBUGGER] Temporary mute AbstractKotlinEvaluateExpressionTest`
(85c59328c7) in 201 bunch.
This commit is contained in:
committed by
Nikolay Krasko
parent
1bc369c63c
commit
1d14926444
+299
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.test
|
||||
|
||||
import com.intellij.debugger.engine.ContextUtil
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl
|
||||
import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilderImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl.createDebuggerContext
|
||||
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.ui.treeStructure.Tree
|
||||
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
|
||||
import com.sun.jdi.ObjectReference
|
||||
import org.jetbrains.eval4j.ObjectValue
|
||||
import org.jetbrains.eval4j.Value
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.test.preference.DebuggerPreferences
|
||||
import org.jetbrains.kotlin.idea.debugger.test.util.FramePrinter
|
||||
import org.jetbrains.kotlin.idea.debugger.test.util.FramePrinterDelegate
|
||||
import org.jetbrains.kotlin.idea.debugger.test.util.KotlinOutputChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.test.util.SteppingInstruction
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils.findLinesWithPrefixesRemoved
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes
|
||||
import org.jetbrains.kotlin.test.KotlinBaseTest
|
||||
import org.jetbrains.kotlin.test.TargetBackend
|
||||
import java.io.File
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.swing.tree.TreeNode
|
||||
|
||||
private data class CodeFragment(val text: String, val result: String, val kind: CodeFragmentKind)
|
||||
|
||||
private data class DebugLabel(val name: String, val localName: String)
|
||||
|
||||
private class EvaluationTestData(
|
||||
val instructions: List<SteppingInstruction>,
|
||||
val fragments: List<CodeFragment>,
|
||||
val debugLabels: List<DebugLabel>
|
||||
)
|
||||
|
||||
abstract class AbstractKotlinEvaluateExpressionTest : KotlinDescriptorTestCaseWithStepping(), FramePrinterDelegate {
|
||||
private companion object {
|
||||
private val ID_PART_REGEX = "id=[0-9]*".toRegex()
|
||||
}
|
||||
|
||||
override val debuggerContext: DebuggerContextImpl
|
||||
get() = super.debuggerContext
|
||||
|
||||
private var isMultipleBreakpointsTest = false
|
||||
|
||||
private var framePrinter: FramePrinter? = null
|
||||
|
||||
private val exceptions = ConcurrentHashMap<String, Throwable>()
|
||||
|
||||
fun doSingleBreakpointTest(path: String) {
|
||||
isMultipleBreakpointsTest = false
|
||||
doTest(path)
|
||||
}
|
||||
|
||||
fun doMultipleBreakpointsTest(path: String) {
|
||||
isMultipleBreakpointsTest = true
|
||||
doTest(path)
|
||||
}
|
||||
|
||||
override fun doMultiFileTest(files: TestFiles, preferences: DebuggerPreferences) {
|
||||
val wholeFile = files.wholeFile
|
||||
|
||||
val instructions = SteppingInstruction.parse(wholeFile)
|
||||
val expressions = loadExpressions(wholeFile)
|
||||
val blocks = loadCodeBlocks(files.originalFile)
|
||||
val debugLabels = loadDebugLabels(wholeFile)
|
||||
|
||||
val data = EvaluationTestData(instructions, expressions + blocks, debugLabels)
|
||||
|
||||
framePrinter = FramePrinter(myDebuggerSession, this, preferences, testRootDisposable)
|
||||
|
||||
if (isMultipleBreakpointsTest) {
|
||||
performMultipleBreakpointTest(data)
|
||||
} else {
|
||||
performSingleBreakpointTest(data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
framePrinter?.close()
|
||||
framePrinter = null
|
||||
exceptions.clear()
|
||||
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
private fun performSingleBreakpointTest(data: EvaluationTestData) {
|
||||
process(data.instructions)
|
||||
|
||||
doOnBreakpoint {
|
||||
createDebugLabels(data.debugLabels)
|
||||
|
||||
val exceptions = linkedMapOf<String, Throwable>()
|
||||
|
||||
for ((expression, expected, kind) in data.fragments) {
|
||||
mayThrow(expression) {
|
||||
evaluate(this, expression, kind, expected)
|
||||
}
|
||||
}
|
||||
|
||||
val completion = { resume(this) }
|
||||
framePrinter?.printFrame(completion) ?: completion()
|
||||
}
|
||||
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun performMultipleBreakpointTest(data: EvaluationTestData) {
|
||||
for ((expression, expected) in data.fragments) {
|
||||
doOnBreakpoint {
|
||||
mayThrow(expression) {
|
||||
try {
|
||||
evaluate(this, expression, CodeFragmentKind.EXPRESSION, expected)
|
||||
} finally {
|
||||
val completion = { resume(this) }
|
||||
framePrinter?.printFrame(completion) ?: completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun evaluate(suspendContext: SuspendContextImpl, textWithImports: TextWithImportsImpl) {
|
||||
evaluate(suspendContext, textWithImports, null)
|
||||
}
|
||||
|
||||
private fun evaluate(suspendContext: SuspendContextImpl, text: String, codeFragmentKind: CodeFragmentKind, expectedResult: String?) {
|
||||
val textWithImports = TextWithImportsImpl(codeFragmentKind, text, "", KotlinFileType.INSTANCE)
|
||||
return evaluate(suspendContext, textWithImports, expectedResult)
|
||||
}
|
||||
|
||||
private fun evaluate(suspendContext: SuspendContextImpl, item: TextWithImportsImpl, expectedResult: String?) {
|
||||
val evaluationContext = this.evaluationContext
|
||||
val sourcePosition = ContextUtil.getSourcePosition(suspendContext)
|
||||
|
||||
// Default test debuggerContext doesn't provide a valid stackFrame so we have to create one more for evaluation purposes.
|
||||
val frameProxy = suspendContext.frameProxy
|
||||
val threadProxy = frameProxy?.threadProxy()
|
||||
val debuggerContext = createDebuggerContext(myDebuggerSession, suspendContext, threadProxy, frameProxy)
|
||||
debuggerContext.initCaches()
|
||||
|
||||
val contextElement = ContextUtil.getContextElement(debuggerContext)!!
|
||||
|
||||
assert(KotlinCodeFragmentFactory().isContextAccepted(contextElement)) {
|
||||
val text = runReadAction { contextElement.text }
|
||||
"KotlinCodeFragmentFactory should be accepted for context element otherwise default evaluator will be called. " +
|
||||
"ContextElement = $text"
|
||||
}
|
||||
|
||||
contextElement.putCopyableUserData(KotlinCodeFragmentFactory.DEBUG_CONTEXT_FOR_TESTS, debuggerContext)
|
||||
|
||||
suspendContext.runActionInSuspendCommand {
|
||||
try {
|
||||
val evaluator = runReadAction {
|
||||
EvaluatorBuilderImpl.build(
|
||||
item,
|
||||
contextElement,
|
||||
sourcePosition,
|
||||
this@AbstractKotlinEvaluateExpressionTest.project
|
||||
)
|
||||
}
|
||||
?: throw AssertionError("Cannot create an Evaluator for Evaluate Expression")
|
||||
|
||||
val value = evaluator.evaluate(evaluationContext)
|
||||
val actualResult = value.asValue().asString()
|
||||
if (expectedResult != null) {
|
||||
assertEquals(
|
||||
"Evaluate expression returns wrong result for ${item.text}:\n" +
|
||||
"expected = $expectedResult\n" +
|
||||
"actual = $actualResult\n",
|
||||
expectedResult, actualResult
|
||||
)
|
||||
}
|
||||
} catch (e: EvaluateException) {
|
||||
val expectedMessage = e.message?.replaceFirst(
|
||||
ID_PART_REGEX,
|
||||
"id=ID"
|
||||
)
|
||||
assertEquals(
|
||||
"Evaluate expression throws wrong exception for ${item.text}:\n" +
|
||||
"expected = $expectedResult\n" +
|
||||
"actual = $expectedMessage\n",
|
||||
expectedResult,
|
||||
expectedMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun logDescriptor(descriptor: NodeDescriptorImpl, text: String) {
|
||||
super.logDescriptor(descriptor, text)
|
||||
}
|
||||
|
||||
override fun expandAll(tree: Tree, runnable: () -> Unit, filter: (TreeNode) -> Boolean, suspendContext: SuspendContextImpl) {
|
||||
super.expandAll(tree, runnable, HashSet(), filter, suspendContext)
|
||||
}
|
||||
|
||||
private fun mayThrow(expression: String, f: () -> Unit) {
|
||||
try {
|
||||
f()
|
||||
} catch (e: Throwable) {
|
||||
exceptions[expression] = e
|
||||
}
|
||||
}
|
||||
|
||||
override fun throwExceptionsIfAny() {
|
||||
if (exceptions.isNotEmpty()) {
|
||||
val isIgnored = InTextDirectivesUtils.isIgnoredTarget(
|
||||
if (useIrBackend()) TargetBackend.JVM_IR else TargetBackend.JVM,
|
||||
getExpectedOutputFile()
|
||||
)
|
||||
|
||||
if (!isIgnored) {
|
||||
for (exc in exceptions.values) {
|
||||
exc.printStackTrace()
|
||||
}
|
||||
val expressionsText = exceptions.entries.joinToString("\n") { (k, v) -> "expression: $k, exception: ${v.message}" }
|
||||
throw AssertionError("Test failed:\n$expressionsText")
|
||||
} else {
|
||||
(checker as KotlinOutputChecker).threwException = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Value.asString(): String {
|
||||
if (this is ObjectValue && this.value is ObjectReference) {
|
||||
return this.toString().replaceFirst(ID_PART_REGEX, "id=ID")
|
||||
}
|
||||
return this.toString()
|
||||
}
|
||||
|
||||
private fun loadExpressions(testFile: KotlinBaseTest.TestFile): List<CodeFragment> {
|
||||
val directives = findLinesWithPrefixesRemoved(testFile.content, "// EXPRESSION: ")
|
||||
val expected = findLinesWithPrefixesRemoved(testFile.content, "// RESULT: ")
|
||||
assert(directives.size == expected.size) { "Sizes of test directives are different" }
|
||||
return directives.zip(expected).map { (text, result) -> CodeFragment(text, result, CodeFragmentKind.EXPRESSION) }
|
||||
}
|
||||
|
||||
private fun loadCodeBlocks(wholeFile: File): List<CodeFragment> {
|
||||
val regexp = (Regex.escape(wholeFile.name) + ".fragment\\d*").toRegex()
|
||||
val fragmentFiles = wholeFile.parentFile.listFiles { _, name -> regexp.matches(name) } ?: emptyArray()
|
||||
|
||||
val codeFragments = mutableListOf<CodeFragment>()
|
||||
|
||||
for (fragmentFile in fragmentFiles) {
|
||||
val contents = FileUtil.loadFile(fragmentFile, true)
|
||||
val value = findStringWithPrefixes(contents, "// RESULT: ") ?: error("'RESULT' directive is missing in $fragmentFile")
|
||||
codeFragments += CodeFragment(contents, value, CodeFragmentKind.CODE_BLOCK)
|
||||
}
|
||||
|
||||
return codeFragments
|
||||
}
|
||||
|
||||
private fun loadDebugLabels(testFile: KotlinBaseTest.TestFile): List<DebugLabel> {
|
||||
return findLinesWithPrefixesRemoved(testFile.content, "// DEBUG_LABEL: ")
|
||||
.map { text ->
|
||||
val labelParts = text.split("=")
|
||||
assert(labelParts.size == 2) { "Wrong format for DEBUG_LABEL directive: // DEBUG_LABEL: {localVariableName} = {labelText}" }
|
||||
|
||||
val localName = labelParts[0].trim()
|
||||
val name = labelParts[1].trim()
|
||||
DebugLabel(name, localName)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDebugLabels(labels: List<DebugLabel>) {
|
||||
if (labels.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val markupMap = NodeDescriptorImpl.getMarkupMap(debugProcess) ?: return
|
||||
|
||||
for ((name, localName) in labels) {
|
||||
val localVariable = evaluationContext.frameProxy!!.visibleVariableByName(localName)
|
||||
assert(localVariable != null) { "Cannot find localVariable for label: name = $localName" }
|
||||
|
||||
val localVariableValue = evaluationContext.frameProxy!!.getValue(localVariable) as? ObjectReference
|
||||
assert(localVariableValue != null) { "Local variable $localName should be an ObjectReference" }
|
||||
|
||||
markupMap[localVariableValue] = ValueMarkup(name, null, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user