[JVM+IR] New LVT debugging test harness improvements
This is the first step in a push to improve the test coverage of the IR debugging experience. This commit improves on the sketch of local variable table tests built on the new debugger stepping infrastructure. As improvements on the existing checkLocalVariableTableTest they: - don't overspecify codegen strategy: no hard requirements on slots. - test the observed lifespans of locals by stepping through code. Ultimately this should enable us to bring over all existing tests for improved coverage of both old and new JVM backends. WIP list: - Gracefully handle absent LVT: treat as empty Add type of local and - type of value stored there to expectations Print values in local - slots if primitive or java.lang.String Enable specifying - expectations per backend Gracefully handle absent box methods - Gracefully handle null values in slots of reference type - Port a first LVT test
This commit is contained in:
committed by
max-kammerer
parent
4479bf0933
commit
2c9bf95227
@@ -1,16 +0,0 @@
|
||||
class A {
|
||||
fun foo() {
|
||||
try {
|
||||
var a = 1
|
||||
a++
|
||||
}
|
||||
catch(e : Throwable) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// METHOD : A.foo()V
|
||||
// VARIABLE : NAME=a TYPE=I INDEX=1
|
||||
// VARIABLE : NAME=e TYPE=Ljava/lang/Throwable; INDEX=1
|
||||
// VARIABLE : NAME=this TYPE=LA; INDEX=0
|
||||
@@ -0,0 +1,19 @@
|
||||
// FILE: test.kt
|
||||
fun box() {
|
||||
try {
|
||||
var a = 1
|
||||
a --
|
||||
a /= a
|
||||
} catch(e : Throwable) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
// LOCAL VARIABLES
|
||||
// TestKt:3:
|
||||
// TestKt:4:
|
||||
// TestKt:5: a:int=1:int
|
||||
// TestKt:6: a:int=0:int
|
||||
// TestKt:7:
|
||||
// TestKt:8: e:java.lang.Throwable=java.lang.ArithmeticException
|
||||
// TestKt:10:
|
||||
+6
-6
@@ -9,11 +9,11 @@ fun box() {
|
||||
|
||||
// LOCAL VARIABLES
|
||||
// TestKt:6:
|
||||
// someClass:3: a:double, b:double
|
||||
// someClass:3: a:double=1.0:double, b:double=2.0:double
|
||||
// TestKt:6:
|
||||
// TestKt:7: a:someClass
|
||||
// someClass:3: a:double, b:double
|
||||
// someClass.copy(double, double)+9: a:double, b:double
|
||||
// TestKt:7: a:someClass=someClass
|
||||
// someClass:3: a:double=1.0:double, b:double=3.0:double
|
||||
// someClass.copy(double, double)+9: a:double=1.0:double, b:double=3.0:double
|
||||
// someClass.copy$default(someClass, double, double, int, java.lang.Object)+30:
|
||||
// TestKt:7: a:someClass
|
||||
// TestKt:8: a:someClass, b:someClass
|
||||
// TestKt:7: a:someClass=someClass
|
||||
// TestKt:8: a:someClass=someClass, b:someClass=someClass
|
||||
@@ -12,8 +12,8 @@ fun box() {
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// LOCAL VARIABLES
|
||||
// TestKt:6:
|
||||
// TestKt:8: map:java.util.Collections$SingletonMap
|
||||
// TestKt:7: map:java.util.Collections$SingletonMap
|
||||
// TestKt:9: map:java.util.Collections$SingletonMap, a:java.lang.String, b:java.lang.String
|
||||
// TestKt:7: map:java.util.Collections$SingletonMap
|
||||
// TestKt:11: map:java.util.Collections$SingletonMap
|
||||
// TestKt:8: map:java.util.Map=java.util.Collections$SingletonMap
|
||||
// TestKt:7: map:java.util.Map=java.util.Collections$SingletonMap
|
||||
// TestKt:9: map:java.util.Map=java.util.Collections$SingletonMap, a:java.lang.String="1":java.lang.String, b:java.lang.String="23":java.lang.String
|
||||
// TestKt:7: map:java.util.Map=java.util.Collections$SingletonMap
|
||||
// TestKt:11: map:java.util.Map=java.util.Collections$SingletonMap
|
||||
@@ -10,11 +10,11 @@ fun box() {
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// LOCAL VARIABLES
|
||||
// TestKt:8:
|
||||
// A:3: x:java.lang.String, y:int
|
||||
// A:3: x:java.lang.String="O":java.lang.String, y:int=123:int
|
||||
// TestKt:8:
|
||||
// TestKt:5: a:A, block:TestKt$box$1
|
||||
// TestKt$box$1:8: $dstr$x$y:A
|
||||
// TestKt:5: a:A=A, block:kotlin.jvm.functions.Function1=TestKt$box$1
|
||||
// TestKt$box$1:8: $dstr$x$y:A=A
|
||||
// TestKt$box$1.invoke(java.lang.Object)+8:
|
||||
// TestKt:5: a:A, block:TestKt$box$1
|
||||
// TestKt:5: a:A=A, block:kotlin.jvm.functions.Function1=TestKt$box$1
|
||||
// TestKt:8:
|
||||
// TestKt:9:
|
||||
+1
-1
@@ -12,5 +12,5 @@ fun box() {
|
||||
// LOCAL VARIABLES
|
||||
// TestKt:9:
|
||||
// TestKt:4:
|
||||
// TestKt:6: $fun$bar$1:TestKt$foo$1
|
||||
// TestKt:6: $fun$bar$1:TestKt$foo$1=TestKt$foo$1
|
||||
// TestKt:10:
|
||||
+12
-12
@@ -25,25 +25,25 @@ fun box() {
|
||||
// LOCAL VARIABLES
|
||||
// TestKt:11:
|
||||
// A:3:
|
||||
// A:3: x:double, y:java.lang.String, z:char
|
||||
// A:3: x:double=1.0:double, y:java.lang.String="":java.lang.String, z:char=0:char
|
||||
// A:3:
|
||||
// TestKt:11:
|
||||
// TestKt:5: a:A, block:TestKt$box$1
|
||||
// TestKt$box$1:14: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int
|
||||
// TestKt:5: a:A=A, block:kotlin.jvm.functions.Function3=TestKt$box$1
|
||||
// TestKt$box$1:14: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int
|
||||
// A:3:
|
||||
// A:3: x:double, y:java.lang.String, z:char
|
||||
// A:3: x:double=1.0:double, y:java.lang.String="":java.lang.String, z:char=0:char
|
||||
// A:3:
|
||||
// TestKt$box$1:14: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char
|
||||
// TestKt$box$1:15: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char, a:double, c:char
|
||||
// TestKt$box$1:14: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char
|
||||
// TestKt$box$1:15: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char, a:double=1.0:double, c:char=0:char
|
||||
// A:3:
|
||||
// A:3: x:double, y:java.lang.String, z:char
|
||||
// A:3: x:double=1.0:double, y:java.lang.String="":java.lang.String, z:char=0:char
|
||||
// A:3:
|
||||
// TestKt$box$1:15: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char, a:double, c:char
|
||||
// TestKt$box$1:17: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char, a:double, c:char, _:java.lang.String, d:char
|
||||
// TestKt$box$1:15: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char, a:double=1.0:double, c:char=0:char
|
||||
// TestKt$box$1:17: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char, a:double=1.0:double, c:char=0:char, _:java.lang.String="":java.lang.String, d:char=0:char
|
||||
// TestKt:7:
|
||||
// TestKt$box$1:17: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char, a:double, c:char, _:java.lang.String, d:char
|
||||
// TestKt$box$1:21: $dstr$x$_u24__u24$y:A, $noName_1:java.lang.String, w:int, x:double, y:char, a:double, c:char, _:java.lang.String, d:char
|
||||
// TestKt$box$1:17: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char, a:double=1.0:double, c:char=0:char, _:java.lang.String="":java.lang.String, d:char=0:char
|
||||
// TestKt$box$1:21: $dstr$x$_u24__u24$y:A=A, $noName_1:java.lang.String="":java.lang.String, w:int=1:int, x:double=1.0:double, y:char=0:char, a:double=1.0:double, c:char=0:char, _:java.lang.String="":java.lang.String, d:char=0:char
|
||||
// TestKt$box$1.invoke(java.lang.Object, java.lang.Object, java.lang.Object)+19:
|
||||
// TestKt:5: a:A, block:TestKt$box$1
|
||||
// TestKt:5: a:A=A, block:kotlin.jvm.functions.Function3=TestKt$box$1
|
||||
// TestKt:11:
|
||||
// TestKt:23:
|
||||
+3
@@ -171,6 +171,9 @@ abstract class AbstractDebugTest : CodegenTestCase() {
|
||||
|
||||
val classLoader = createGeneratedClassLoader(classesDir)
|
||||
val aClass = getGeneratedClass(classLoader, TEST_CLASS)
|
||||
assert(aClass.declaredMethods.any { it.name == BOX_METHOD }) {
|
||||
"Test method $BOX_METHOD not present on test class $TEST_CLASS"
|
||||
}
|
||||
if (virtualMachine.allThreads().any { it.isSuspended }) {
|
||||
virtualMachine.resume()
|
||||
}
|
||||
|
||||
+83
-19
@@ -1,11 +1,10 @@
|
||||
package org.jetbrains.kotlin.codegen.debugInformation
|
||||
|
||||
import com.sun.jdi.LocalVariable
|
||||
import com.sun.jdi.StackFrame
|
||||
import com.sun.jdi.VirtualMachine
|
||||
import com.sun.jdi.*
|
||||
import com.sun.jdi.event.Event
|
||||
import com.sun.jdi.event.LocatableEvent
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.test.TargetBackend
|
||||
import org.junit.AfterClass
|
||||
import org.junit.BeforeClass
|
||||
import java.io.File
|
||||
@@ -15,7 +14,41 @@ abstract class AbstractLocalVariableTest : AbstractDebugTest() {
|
||||
override val virtualMachine: VirtualMachine = Companion.virtualMachine
|
||||
override val proxyPort: Int = Companion.proxyPort
|
||||
|
||||
interface LocalValue
|
||||
|
||||
class LocalPrimitive(val value: String, val valueType: String) : LocalValue {
|
||||
override fun toString(): String {
|
||||
return "$value:$valueType"
|
||||
}
|
||||
}
|
||||
|
||||
class LocalReference(val id: String, val referenceType: String) : LocalValue {
|
||||
override fun toString(): String {
|
||||
return "$referenceType"
|
||||
}
|
||||
}
|
||||
|
||||
class LocalNullValue : LocalValue {
|
||||
override fun toString(): String {
|
||||
return "null"
|
||||
}
|
||||
}
|
||||
|
||||
class LocalVariableRecord(
|
||||
val variable: String,
|
||||
val variableType: String,
|
||||
val value: LocalValue
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "$variable:$variableType=$value"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LOCAL_VARIABLES_MARKER = "// LOCAL VARIABLES"
|
||||
const val JVM_LOCAL_VARIABLES_MARKER = "$LOCAL_VARIABLES_MARKER JVM"
|
||||
const val JVM_IR_LOCAL_VARIABLES_MARKER = "$LOCAL_VARIABLES_MARKER JVM_IR"
|
||||
|
||||
const val LOCAL_VARIABLES = "// LOCAL VARIABLES"
|
||||
var proxyPort = 0
|
||||
lateinit var process: Process
|
||||
@@ -41,29 +74,60 @@ abstract class AbstractLocalVariableTest : AbstractDebugTest() {
|
||||
|
||||
override fun storeStep(loggedItems: ArrayList<Any>, event: Event) {
|
||||
waitUntil { (event as LocatableEvent).thread().isSuspended }
|
||||
val visibleVars = (event as LocatableEvent)
|
||||
.thread()
|
||||
.frame(0)
|
||||
.visibleVariables()
|
||||
.map { variable -> toRecord(event.thread().frame(0), variable) }
|
||||
.joinToString(", ")
|
||||
loggedItems.add("${event.location()}: $visibleVars".trim())
|
||||
val frame = (event as LocatableEvent).thread().frame(0)
|
||||
try {
|
||||
val visibleVars = frame
|
||||
.visibleVariables()
|
||||
.map { variable -> toRecord(frame, variable) }
|
||||
.joinToString(", ")
|
||||
loggedItems.add("${event.location()}: $visibleVars".trim())
|
||||
} catch (e: AbsentInformationException) {
|
||||
// LVT Completely absent - not distinguished from an empty table
|
||||
loggedItems.add("${event.location()}:".trim())
|
||||
}
|
||||
}
|
||||
|
||||
override fun checkResult(wholeFile: File, loggedItems: List<Any>) {
|
||||
val expectedLocalVariables = wholeFile
|
||||
.readLines()
|
||||
.dropWhile { !it.startsWith(LOCAL_VARIABLES) }
|
||||
.drop(1)
|
||||
.map { it.drop(3) }
|
||||
.joinToString("\n")
|
||||
val lines = wholeFile.readLines()
|
||||
|
||||
val expectedLocalVariables = mutableListOf<String>()
|
||||
val lineIterator = lines.iterator()
|
||||
for (line in lineIterator) {
|
||||
if (line.startsWith(LOCAL_VARIABLES_MARKER)) break
|
||||
}
|
||||
|
||||
var currentBackend = TargetBackend.ANY
|
||||
for (line in lineIterator) {
|
||||
if (line.trim() == "") continue
|
||||
if (line.startsWith(LOCAL_VARIABLES_MARKER)) {
|
||||
currentBackend = when (line) {
|
||||
LOCAL_VARIABLES_MARKER -> TargetBackend.ANY
|
||||
JVM_LOCAL_VARIABLES_MARKER -> TargetBackend.JVM
|
||||
JVM_IR_LOCAL_VARIABLES_MARKER -> TargetBackend.JVM_IR
|
||||
else -> error("Expected JVM backend: $line")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (currentBackend == TargetBackend.ANY || currentBackend == backend) {
|
||||
expectedLocalVariables.add(line.drop(3))
|
||||
}
|
||||
}
|
||||
|
||||
val actualLocalVariables = loggedItems.joinToString("\n")
|
||||
|
||||
TestCase.assertEquals(expectedLocalVariables, actualLocalVariables)
|
||||
TestCase.assertEquals(expectedLocalVariables.joinToString("\n"), actualLocalVariables)
|
||||
}
|
||||
|
||||
private fun toRecord(frame: StackFrame, variable: LocalVariable): String {
|
||||
return "${variable.name()}:${frame.getValue(variable)?.type()?.name() ?: "null"}"
|
||||
private fun toRecord(frame: StackFrame, variable: LocalVariable): LocalVariableRecord {
|
||||
val value = frame.getValue(variable)
|
||||
val valueRecord = if (value == null) {
|
||||
LocalNullValue()
|
||||
} else if (value is ObjectReference && value.referenceType().name() != "java.lang.String") {
|
||||
LocalReference(value.uniqueID().toString(), value.referenceType().name())
|
||||
} else {
|
||||
LocalPrimitive(value.toString(), value.type().name())
|
||||
}
|
||||
return LocalVariableRecord(variable.name(), variable.typeName(), valueRecord)
|
||||
}
|
||||
|
||||
private fun waitUntil(condition: () -> Boolean) {
|
||||
|
||||
-5
@@ -29,11 +29,6 @@ public class CheckLocalVariablesTableTestGenerated extends AbstractCheckLocalVar
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/checkLocalVariablesTable"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
|
||||
}
|
||||
|
||||
@TestMetadata("catchClause.kt")
|
||||
public void testCatchClause() throws Exception {
|
||||
runTest("compiler/testData/checkLocalVariablesTable/catchClause.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("copyFunction.kt")
|
||||
public void testCopyFunction() throws Exception {
|
||||
runTest("compiler/testData/checkLocalVariablesTable/copyFunction.kt");
|
||||
|
||||
Generated
+6
@@ -31,6 +31,12 @@ public class IrLocalVariableTestGenerated extends AbstractIrLocalVariableTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/debug/localVariables"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("catchClause.kt")
|
||||
public void testCatchClause() throws Exception {
|
||||
runTest("compiler/testData/debug/localVariables/catchClause.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("copyFunction.kt")
|
||||
public void testCopyFunction() throws Exception {
|
||||
|
||||
Generated
+6
@@ -31,6 +31,12 @@ public class LocalVariableTestGenerated extends AbstractLocalVariableTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/debug/localVariables"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("catchClause.kt")
|
||||
public void testCatchClause() throws Exception {
|
||||
runTest("compiler/testData/debug/localVariables/catchClause.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("copyFunction.kt")
|
||||
public void testCopyFunction() throws Exception {
|
||||
|
||||
Generated
-5
@@ -29,11 +29,6 @@ public class IrCheckLocalVariablesTableTestGenerated extends AbstractIrCheckLoca
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/checkLocalVariablesTable"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
|
||||
}
|
||||
|
||||
@TestMetadata("catchClause.kt")
|
||||
public void testCatchClause() throws Exception {
|
||||
runTest("compiler/testData/checkLocalVariablesTable/catchClause.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("copyFunction.kt")
|
||||
public void testCopyFunction() throws Exception {
|
||||
runTest("compiler/testData/checkLocalVariablesTable/copyFunction.kt");
|
||||
|
||||
Reference in New Issue
Block a user