[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:
@@ -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(" ", "// ")
|
||||
}
|
||||
}
|
||||
+38
-33
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-77
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user