[JVM] Port remaining line number tests to stepping infrastructure.

These line number tests only tested that a set of line numbers where
present in the java bytecode. Not that they would be hit in the
right order by the debugger. Moving them to stepping tests fixes that.

This exposes a couple of issues (in particular around try-catch-finally)
that should be fixed.

A number of tests are marked as failing now. Will investigate and
work on fixes next.
This commit is contained in:
Mads Ager
2020-05-26 14:33:36 +02:00
committed by max-kammerer
parent 5db6a0b563
commit 7d7b9262e7
60 changed files with 1462 additions and 1039 deletions
@@ -1,165 +0,0 @@
/*
* 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.codegen
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.jetbrains.org.objectweb.asm.*
import java.io.File
import java.util.*
import java.util.regex.Pattern
import kotlin.collections.ArrayList
abstract class AbstractLineNumberTest : CodegenTestCase() {
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>) {
val isCustomTest = wholeFile.parentFile.name.equals("custom", ignoreCase = true)
compile(if (!isCustomTest) files + createLineNumberDeclaration() else files)
val psiFile = myFiles.psiFiles.single { file -> file.name == wholeFile.name }
try {
if (isCustomTest) {
compareCustom(psiFile, wholeFile)
} else {
val expectedLineNumbers = extractSelectedLineNumbersFromSource(psiFile)
val actualLineNumbers = extractActualLineNumbersFromBytecode(classFileFactory, true)
assertFalse("Missed 'lineNumbers' calls in test data", expectedLineNumbers.isEmpty())
KtUsefulTestCase.assertSameElements(actualLineNumbers, expectedLineNumbers)
}
} catch (e: Throwable) {
printReport(wholeFile)
throw e
}
}
protected open fun compareCustom(psiFile: KtFile, wholeFile: File) {
val actualLineNumbers = extractActualLineNumbersFromBytecode(classFileFactory, false)
val text = psiFile.text
val newFileText = text.substring(0 until Regex("// \\d+").find(text)!!.range.first) +
getActualLineNumbersAsString(actualLineNumbers)
KotlinTestUtils.assertEqualsToFile(wholeFile, newFileText)
}
protected fun extractActualLineNumbersFromBytecode(factory: ClassFileFactory, testFunInvoke: Boolean) =
factory.getClassFiles().flatMap { outputFile ->
val cr = ClassReader(outputFile.asByteArray())
if (testFunInvoke) readTestFunLineNumbers(cr) else readAllLineNumbers(cr)
}
protected open fun readTestFunLineNumbers(cr: ClassReader): List<String> {
val labels = arrayListOf<Label>()
val labels2LineNumbers = HashMap<Label, String>()
val visitor = object : ClassVisitor(Opcodes.API_VERSION) {
override fun visitMethod(
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<String>?
): MethodVisitor {
return getTestFunLineNumbersMethodVisitor(labels, labels2LineNumbers)
}
}
cr.accept(visitor, ClassReader.SKIP_FRAMES)
return labels.map { label ->
labels2LineNumbers[label] ?: error("No line number found for a label")
}
}
protected open fun getTestFunLineNumbersMethodVisitor(
labels: ArrayList<Label>,
labels2LineNumbers: HashMap<Label, String>
): MethodVisitor {
return object : MethodVisitor(Opcodes.API_VERSION) {
private var lastLabel: Label? = null
override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
if (LINE_NUMBER_FUN == name) {
labels.add(lastLabel ?: error("A function call with no preceding label"))
}
lastLabel = null
}
override fun visitLabel(label: Label) {
lastLabel = label
}
override fun visitLineNumber(line: Int, start: Label) {
labels2LineNumbers[start] = line.toString()
}
}
}
protected open fun readAllLineNumbers(reader: ClassReader): List<String> {
val result = ArrayList<String>()
val visitedLabels = HashSet<String>()
reader.accept(object : ClassVisitor(Opcodes.API_VERSION) {
override fun visitMethod(
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<String>?
): MethodVisitor {
return object : MethodVisitor(Opcodes.API_VERSION) {
override fun visitLineNumber(line: Int, label: Label) {
val overrides = !visitedLabels.add(label.toString())
result.add((if (overrides) "+" else "") + line)
}
}
}
}, ClassReader.SKIP_FRAMES)
return result
}
protected open fun extractSelectedLineNumbersFromSource(file: KtFile): List<String> {
val fileContent = file.text
val lineNumbers = arrayListOf<String>()
val lines = StringUtil.convertLineSeparators(fileContent).split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
for (i in lines.indices) {
val matcher = TEST_LINE_NUMBER_PATTERN.matcher(lines[i])
if (matcher.matches()) {
lineNumbers.add((i + 1).toString())
}
}
return lineNumbers
}
companion object {
const val LINE_NUMBER_FUN = "lineNumber"
private val TEST_LINE_NUMBER_PATTERN = Pattern.compile("^.*test.$LINE_NUMBER_FUN\\(\\).*$")
private fun createLineNumberDeclaration() =
TestFile(
"$LINE_NUMBER_FUN.kt",
"package test;\n\npublic fun $LINE_NUMBER_FUN(): Int = 0\n"
)
private fun getActualLineNumbersAsString(lines: List<String>) =
lines.joinToString(" ", "// ")
}
}
@@ -9,6 +9,7 @@ import com.sun.jdi.VirtualMachine
import com.sun.jdi.event.Event
import com.sun.jdi.event.LocatableEvent
import junit.framework.TestCase
import org.jetbrains.kotlin.test.KotlinTestUtils.assertEqualsToFile
import org.jetbrains.kotlin.test.TargetBackend
import org.junit.AfterClass
import org.junit.BeforeClass
@@ -51,38 +52,11 @@ abstract class AbstractSteppingTest : AbstractDebugTest() {
loggedItems.add(event)
}
data class SteppingExpectations(val forceStepInto: Boolean, val lineNumbers: String)
private fun readExpectations(wholeFile: File): SteppingExpectations {
val expected = mutableListOf<String>()
val lines = wholeFile.readLines().dropWhile {
!it.startsWith(LINENUMBERS_MARKER) && !it.startsWith(FORCE_STEP_INTO_MARKER)
}
var forceStepInto = false
var currentBackend = TargetBackend.ANY
for (line in lines) {
if (line.trim() == FORCE_STEP_INTO_MARKER) {
forceStepInto = true
continue
}
if (line.startsWith(LINENUMBERS_MARKER)) {
currentBackend = when (line) {
LINENUMBERS_MARKER -> TargetBackend.ANY
JVM_LINENUMBER_MARKER -> TargetBackend.JVM
JVM_IR_LINENUMBER_MARKER -> TargetBackend.JVM_IR
else -> error("Expected JVM backend")
}
continue
}
if (currentBackend == TargetBackend.ANY || currentBackend == backend) {
expected.add(line.drop(3).trim())
}
}
return SteppingExpectations(forceStepInto, expected.joinToString("\n"))
}
override fun checkResult(wholeFile: File, loggedItems: List<Any>) {
val (forceStepInto, expectedLineNumbers) = readExpectations(wholeFile)
val actual = mutableListOf<String>()
val lines = wholeFile.readLines()
val forceStepInto = lines.any { it.startsWith(FORCE_STEP_INTO_MARKER) }
val actualLineNumbers = loggedItems
.filter {
val location = (it as LocatableEvent).location()
@@ -93,9 +67,40 @@ abstract class AbstractSteppingTest : AbstractDebugTest() {
.map { event ->
val location = (event as LocatableEvent).location()
val synthetic = if (location.method().isSynthetic) " (synthetic)" else ""
"${location.sourceName()}:${location.lineNumber()} ${location.method().name()}$synthetic"
"// ${location.sourceName()}:${location.lineNumber()} ${location.method().name()}$synthetic"
}
TestCase.assertEquals(expectedLineNumbers, actualLineNumbers.joinToString("\n"))
val actualLineNumbersIterator = actualLineNumbers.iterator()
val lineIterator = lines.iterator()
for (line in lineIterator) {
actual.add(line)
if (line.startsWith(LINENUMBERS_MARKER) || line.startsWith(FORCE_STEP_INTO_MARKER)) break
}
var currentBackend = TargetBackend.ANY
for (line in lineIterator) {
if (line.startsWith(LINENUMBERS_MARKER)) {
actual.add(line)
currentBackend = when (line) {
LINENUMBERS_MARKER -> TargetBackend.ANY
JVM_LINENUMBER_MARKER -> TargetBackend.JVM
JVM_IR_LINENUMBER_MARKER -> TargetBackend.JVM_IR
else -> error("Expected JVM backend")
}
continue
}
if (currentBackend == TargetBackend.ANY || currentBackend == backend) {
if (actualLineNumbersIterator.hasNext()) {
actual.add(actualLineNumbersIterator.next())
}
} else {
actual.add(line)
}
}
actualLineNumbersIterator.forEach { actual.add(it) }
assertEqualsToFile(wholeFile, actual.joinToString("\n"))
}
}
@@ -1,77 +0,0 @@
/*
* Copyright 2010-2018 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.codegen.ir
import org.jetbrains.kotlin.codegen.AbstractLineNumberTest
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import java.io.File
abstract class AbstractIrLineNumberTest : AbstractLineNumberTest() {
override fun compareCustom(psiFile: KtFile, wholeFile: File) {
val fileText = psiFile.text
val expectedLineNumbers = normalize(
fileText.substring(Regex("// \\d+").find(fileText)!!.range.start + 2)
.trim().split(" ").map { it.trim() }.toMutableList()
)
val actualLineNumbers = normalize(extractActualLineNumbersFromBytecode(classFileFactory, false))
KtUsefulTestCase.assertSameElements(actualLineNumbers, expectedLineNumbers)
}
override fun readAllLineNumbers(reader: ClassReader) =
normalize(super.readAllLineNumbers(reader))
override fun extractSelectedLineNumbersFromSource(file: KtFile) =
normalize(super.extractSelectedLineNumbersFromSource(file))
override fun getTestFunLineNumbersMethodVisitor(
labels: ArrayList<Label>,
labels2LineNumbers: java.util.HashMap<Label, String>
): MethodVisitor {
return object : MethodVisitor(Opcodes.API_VERSION) {
private var lastLabel: Label? = null
private var lastLine = -1
override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
if (LINE_NUMBER_FUN == name) {
labels.add(lastLabel ?: error("A function call with no preceding label"))
}
}
override fun visitLabel(label: Label) {
if (lastLabel != null && !labels2LineNumbers.containsKey(lastLabel!!) && lastLine >= 0) {
labels2LineNumbers[lastLabel!!] = lastLine.toString() // Inherited line number
}
lastLabel = label
}
override fun visitLineNumber(line: Int, start: Label) {
labels2LineNumbers[start] = line.toString()
lastLine = line
}
}
}
override fun readTestFunLineNumbers(cr: ClassReader) =
normalize(super.readTestFunLineNumbers(cr))
private fun normalize(numbers: List<String>) =
numbers
.map { if (it.startsWith('+')) it.substring(1) else it }
.toSet()
.toMutableList()
.sortedBy { it.toInt() }
.toList()
override fun getBackend(): TargetBackend {
return TargetBackend.JVM_IR
}
}