[JVM IR] Support target templates in

AbstractCheckLocalVariablesTableTest and fix
`checkLocalVariablesTable/localFun.kt` for JVM IR.

Also improved diagnosability of CheckLocalVariablesTableTest failures.
This commit is contained in:
Mark Punzalan
2020-01-07 22:50:51 -08:00
committed by Alexander Udalov
parent 152d88e747
commit 70b304e6e4
3 changed files with 85 additions and 58 deletions
+11 -1
View File
@@ -1,8 +1,18 @@
// IGNORE_BACKEND: JVM_IR
fun foo() {
val x = 1
fun bar() {
val y = x
}
}
// Local function bodies are in a separate class (implementing FunctionN) for non-IR, and are static methods in the enclosing class for IR.
// JVM_TEMPLATES
// METHOD : LocalFunKt$foo$1.invoke()V
// VARIABLE : NAME=y TYPE=I INDEX=1
// VARIABLE : NAME=this TYPE=LLocalFunKt$foo$1; INDEX=0
// JVM_IR_TEMPLATES
// METHOD : LocalFunKt.foo$bar(I)V
// VARIABLE : NAME=y TYPE=I INDEX=1
// VARIABLE : NAME=$x TYPE=I INDEX=0
@@ -16,11 +16,13 @@
package org.jetbrains.kotlin.codegen
import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.text.StringUtil
import junit.framework.TestCase
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.org.objectweb.asm.*
import org.junit.ComparisonFailure
import java.io.File
import java.util.*
import java.util.regex.Pattern
@@ -33,43 +35,85 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
compile(files)
try {
val classAndMethod = parseClassAndMethodSignature(wholeFile)
val filteredLines = filterTestFileForTargetBackend(wholeFile)
val classAndMethod = parseClassAndMethodSignature(filteredLines)
val split = classAndMethod.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
assert(split.size == 2) { "Exactly one dot is expected: $classAndMethod" }
val classFileRegex = StringUtil.escapeToRegexp(split[0] + ".class").replace("\\*", ".+")
val methodName = split[1]
val outputFiles = (classFileFactory as OutputFileCollection).asList()
val outputFile = outputFiles.first { file -> file.relativePath.matches(classFileRegex.toRegex()) }
val pathsString = outputFiles.joinToString { it.relativePath }
assertNotNull("Couldn't find class file for pattern $classFileRegex in: $pathsString", outputFile)
val outputFile = outputFiles.firstOrNull { file -> file.relativePath.matches(classFileRegex.toRegex()) }
checkNotNull(outputFile) {
val pathsString = outputFiles.joinToString { it.relativePath }
"Couldn't find class file for pattern $classFileRegex in: $pathsString"
}
val classReader = ClassReader(outputFile.asByteArray())
val actualLocalVariables = readLocalVariable(classReader, methodName)
checkLocalVariableTypes(classReader, methodName, actualLocalVariables)
doCompare(wholeFile, files.single().content, actualLocalVariables)
doCompare(filteredLines, actualLocalVariables)
} catch (e: Throwable) {
printReport(wholeFile)
throw e
}
}
protected open fun doCompare(
testFile: File,
text: String,
actualLocalVariables: List<LocalVariable>
) {
KotlinTestUtils.assertEqualsToFile(
testFile,
text.substringBefore("// VARIABLE : ") + getActualVariablesAsString(
actualLocalVariables
)
)
// TODO: Refactor test infrastructure to share filtering with AbstractBytecodeTextTest
private fun filterTestFileForTargetBackend(testFile: File): List<String> {
val filteredLines = mutableListOf<String>()
var currentBackend = TargetBackend.ANY
for (line in testFile.readLines()) {
if (line.contains(JVM_TEMPLATES)) {
currentBackend = TargetBackend.JVM
} else if (line.contains(JVM_IR_TEMPLATES)) {
currentBackend = TargetBackend.JVM_IR
} else if (currentBackend == TargetBackend.ANY || currentBackend == backend) {
filteredLines.add(line)
}
}
return filteredLines
}
protected class LocalVariable internal constructor(
private fun doCompare(
testFileLines: List<String>,
actualLocalVariables: List<LocalVariable>
) {
val actual = getActualVariablesAsString(actualLocalVariables)
val expected = getExpectedVariablesAsString(testFileLines)
if (!Comparing.equal(expected, actual)) {
throw ComparisonFailure(
"Variables differ from expected",
expected,
actual
)
}
}
private fun getActualVariablesAsString(list: List<LocalVariable>) = if (backend.isIR) {
// Ignore local index.
list.map { it.toString().replaceFirst("INDEX=\\d+".toRegex(), "INDEX=*") }
.sorted()
.joinToString("\n")
} else {
list.joinToString("\n")
}
private fun getExpectedVariablesAsString(testFileLines: List<String>): String {
val variableLines = testFileLines.asSequence().filter { line -> line.startsWith("// VARIABLE ") }
return if (backend.isIR) {
// Ignore local index.
variableLines
.map { it.replaceFirst("INDEX=\\d+".toRegex(), "INDEX=*") }
.sorted()
.joinToString("\n")
} else {
variableLines.joinToString("\n")
}
}
private class LocalVariable internal constructor(
val name: String,
val type: String,
val index: Int,
@@ -82,8 +126,8 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
}
}
private fun parseClassAndMethodSignature(testFile: File): String {
for (line in testFile.readLines()) {
private fun parseClassAndMethodSignature(testFileLines: List<String>): String {
for (line in testFileLines) {
val methodMatcher = methodPattern.matcher(line)
if (methodMatcher.matches()) {
return methodMatcher.group(1)
@@ -95,7 +139,9 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
companion object {
private fun getActualVariablesAsString(list: List<LocalVariable>) = list.joinToString("\n")
private const val JVM_TEMPLATES = "// JVM_TEMPLATES"
private const val JVM_IR_TEMPLATES = "// JVM_IR_TEMPLATES"
private val methodPattern = Pattern.compile("^// METHOD : *(.*)")
@@ -104,6 +150,7 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
class Visitor : ClassVisitor(Opcodes.API_VERSION) {
var readVariables: MutableList<LocalVariable> = ArrayList()
var methodFound = false
var methodsFound = mutableListOf<String>()
override fun visitMethod(
access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?
@@ -128,6 +175,7 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
}
}
} else {
methodsFound.add(name + desc)
super.visitMethod(access, name, desc, signature, exceptions)
}
}
@@ -135,7 +183,10 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
val visitor = Visitor()
cr.accept(visitor, ClassReader.SKIP_FRAMES)
TestCase.assertTrue("method not found: $methodName", visitor.methodFound)
TestCase.assertTrue(
"Method not found: $methodName. Methods found were: ${visitor.methodsFound.joinToString()}",
visitor.methodFound
)
return visitor.readVariables
}
@@ -5,43 +5,9 @@
package org.jetbrains.kotlin.codegen.ir
import com.intellij.openapi.util.Comparing
import org.jetbrains.kotlin.codegen.AbstractCheckLocalVariablesTableTest
import org.jetbrains.kotlin.test.TargetBackend
import org.junit.ComparisonFailure
import java.io.File
abstract class AbstractIrCheckLocalVariablesTableTest : AbstractCheckLocalVariablesTableTest() {
override fun doCompare(
testFile: File,
text: String,
actualLocalVariables: List<LocalVariable>
) {
val actual = getActualVariablesAsList(actualLocalVariables)
val expected = getExpectedVariablesAsList(testFile)
if (!Comparing.equal(expected, actual)) {
throw ComparisonFailure(
"Variables differ from expected",
expected.joinToString("\n"),
actual.joinToString("\n")
)
}
}
private fun getActualVariablesAsList(list: List<LocalVariable>): List<String> {
return list.map { it.toString() }
// Ignore local index.
.map { line -> line.replaceFirst("INDEX=\\d+".toRegex(), "INDEX=*") }
.sorted()
}
private fun getExpectedVariablesAsList(testFile: File): List<String> {
return testFile.readLines()
.filter { line -> line.startsWith("// VARIABLE ") }
// Ignore local index.
.map { line -> line.replaceFirst("INDEX=\\d+".toRegex(), "INDEX=*") }
.sorted()
}
override fun getBackend() = TargetBackend.JVM_IR
}