Creating IrLineNumberTestGenerated, adding line numbers for few common expressions
This PR enables LineNumberTestGenerated test on IR backend. The testing of hardcoded sequence of line numbers is replaced with mere checks for set-like checks for expected line numbers.
This commit is contained in:
committed by
Mikhael Bogdanov
parent
36936d252c
commit
ddf92ef187
+103
-91
@@ -20,11 +20,11 @@ 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.kotlin.utils.rethrow
|
||||
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() {
|
||||
|
||||
@@ -41,10 +41,7 @@ abstract class AbstractLineNumberTest : CodegenTestCase() {
|
||||
|
||||
try {
|
||||
if (isCustomTest) {
|
||||
val actualLineNumbers = extractActualLineNumbersFromBytecode(classFileFactory, false)
|
||||
val text = psiFile.text
|
||||
val newFileText = text.substringBefore("// ") + getActualLineNumbersAsString(actualLineNumbers)
|
||||
KotlinTestUtils.assertEqualsToFile(wholeFile, newFileText)
|
||||
compareCustom(psiFile, wholeFile)
|
||||
} else {
|
||||
val expectedLineNumbers = extractSelectedLineNumbersFromSource(psiFile)
|
||||
val actualLineNumbers = extractActualLineNumbersFromBytecode(classFileFactory, true)
|
||||
@@ -57,8 +54,108 @@ abstract class AbstractLineNumberTest : CodegenTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
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.ASM5) {
|
||||
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.ASM5) {
|
||||
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] = Integer.toString(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun readAllLineNumbers(reader: ClassReader): List<String> {
|
||||
val result = ArrayList<String>()
|
||||
val visitedLabels = HashSet<String>()
|
||||
|
||||
reader.accept(object : ClassVisitor(Opcodes.ASM5) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String,
|
||||
desc: String,
|
||||
signature: String?,
|
||||
exceptions: Array<String>?
|
||||
): MethodVisitor {
|
||||
return object : MethodVisitor(Opcodes.ASM5) {
|
||||
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(Integer.toString(i + 1))
|
||||
}
|
||||
}
|
||||
|
||||
return lineNumbers
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val LINE_NUMBER_FUN = "lineNumber"
|
||||
const val LINE_NUMBER_FUN = "lineNumber"
|
||||
private val TEST_LINE_NUMBER_PATTERN = Pattern.compile("^.*test.$LINE_NUMBER_FUN\\(\\).*$")
|
||||
|
||||
private fun createLineNumberDeclaration() =
|
||||
@@ -69,90 +166,5 @@ abstract class AbstractLineNumberTest : CodegenTestCase() {
|
||||
|
||||
private fun getActualLineNumbersAsString(lines: List<String>) =
|
||||
lines.joinToString(" ", "// ")
|
||||
|
||||
private fun extractActualLineNumbersFromBytecode(factory: ClassFileFactory, testFunInvoke: Boolean) =
|
||||
factory.getClassFiles().flatMap { outputFile ->
|
||||
val cr = ClassReader(outputFile.asByteArray())
|
||||
if (testFunInvoke) readTestFunLineNumbers(cr) else readAllLineNumbers(cr)
|
||||
}
|
||||
|
||||
private 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(Integer.toString(i + 1))
|
||||
}
|
||||
}
|
||||
|
||||
return lineNumbers
|
||||
}
|
||||
|
||||
private fun readTestFunLineNumbers(cr: ClassReader): List<String> {
|
||||
val labels = arrayListOf<Label>()
|
||||
val labels2LineNumbers = HashMap<Label, String>()
|
||||
|
||||
val visitor = object : ClassVisitor(Opcodes.ASM5) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String,
|
||||
desc: String,
|
||||
signature: String?,
|
||||
exceptions: Array<String>?
|
||||
): MethodVisitor {
|
||||
return object : MethodVisitor(Opcodes.ASM5) {
|
||||
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] = Integer.toString(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cr.accept(visitor, ClassReader.SKIP_FRAMES)
|
||||
|
||||
return labels.map { label ->
|
||||
labels2LineNumbers[label] ?: error("No line number found for a label")
|
||||
}
|
||||
}
|
||||
|
||||
private fun readAllLineNumbers(reader: ClassReader): List<String> {
|
||||
val result = ArrayList<String>()
|
||||
val visitedLabels = HashSet<String>()
|
||||
|
||||
reader.accept(object : ClassVisitor(Opcodes.ASM5) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String,
|
||||
desc: String,
|
||||
signature: String?,
|
||||
exceptions: Array<String>?
|
||||
): MethodVisitor {
|
||||
return object : MethodVisitor(Opcodes.ASM5) {
|
||||
override fun visitLineNumber(line: Int, label: Label) {
|
||||
val overrides = !visitedLabels.add(label.toString())
|
||||
|
||||
result.add((if (overrides) "+" else "") + line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ClassReader.SKIP_FRAMES)
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||
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 extractConfigurationKind(files: MutableList<TestFile>): ConfigurationKind {
|
||||
return ConfigurationKind.ALL
|
||||
}
|
||||
|
||||
override fun updateConfiguration(configuration: CompilerConfiguration) {
|
||||
super.updateConfiguration(configuration)
|
||||
configuration.put(JVMConfigurationKeys.IR, true)
|
||||
}
|
||||
|
||||
override fun compareCustom(psiFile: KtFile, wholeFile: File) {
|
||||
val fileText = psiFile.text
|
||||
val expectedLineNumbers = normalize(
|
||||
fileText.substring(fileText.indexOf("//") + 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.ASM5) {
|
||||
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!!] = Integer.toString(lastLine) // Inherited line number
|
||||
}
|
||||
lastLabel = label
|
||||
}
|
||||
|
||||
override fun visitLineNumber(line: Int, start: Label) {
|
||||
labels2LineNumbers[start] = Integer.toString(line)
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user