[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() { fun foo() {
val x = 1
fun bar() { 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 // METHOD : LocalFunKt$foo$1.invoke()V
// VARIABLE : NAME=y TYPE=I INDEX=1
// VARIABLE : NAME=this TYPE=LLocalFunKt$foo$1; INDEX=0 // 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 package org.jetbrains.kotlin.codegen
import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import junit.framework.TestCase import junit.framework.TestCase
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection 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.jetbrains.org.objectweb.asm.*
import org.junit.ComparisonFailure
import java.io.File import java.io.File
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
@@ -33,43 +35,85 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
compile(files) compile(files)
try { try {
val classAndMethod = parseClassAndMethodSignature(wholeFile) val filteredLines = filterTestFileForTargetBackend(wholeFile)
val classAndMethod = parseClassAndMethodSignature(filteredLines)
val split = classAndMethod.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() val split = classAndMethod.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
assert(split.size == 2) { "Exactly one dot is expected: $classAndMethod" } assert(split.size == 2) { "Exactly one dot is expected: $classAndMethod" }
val classFileRegex = StringUtil.escapeToRegexp(split[0] + ".class").replace("\\*", ".+") val classFileRegex = StringUtil.escapeToRegexp(split[0] + ".class").replace("\\*", ".+")
val methodName = split[1] val methodName = split[1]
val outputFiles = (classFileFactory as OutputFileCollection).asList() val outputFiles = (classFileFactory as OutputFileCollection).asList()
val outputFile = outputFiles.first { file -> file.relativePath.matches(classFileRegex.toRegex()) } val outputFile = outputFiles.firstOrNull { file -> file.relativePath.matches(classFileRegex.toRegex()) }
checkNotNull(outputFile) {
val pathsString = outputFiles.joinToString { it.relativePath } val pathsString = outputFiles.joinToString { it.relativePath }
assertNotNull("Couldn't find class file for pattern $classFileRegex in: $pathsString", outputFile) "Couldn't find class file for pattern $classFileRegex in: $pathsString"
}
val classReader = ClassReader(outputFile.asByteArray()) val classReader = ClassReader(outputFile.asByteArray())
val actualLocalVariables = readLocalVariable(classReader, methodName) val actualLocalVariables = readLocalVariable(classReader, methodName)
checkLocalVariableTypes(classReader, methodName, actualLocalVariables) checkLocalVariableTypes(classReader, methodName, actualLocalVariables)
doCompare(wholeFile, files.single().content, actualLocalVariables) doCompare(filteredLines, actualLocalVariables)
} catch (e: Throwable) { } catch (e: Throwable) {
printReport(wholeFile) printReport(wholeFile)
throw e throw e
} }
} }
protected open fun doCompare( // TODO: Refactor test infrastructure to share filtering with AbstractBytecodeTextTest
testFile: File, private fun filterTestFileForTargetBackend(testFile: File): List<String> {
text: String, val filteredLines = mutableListOf<String>()
actualLocalVariables: List<LocalVariable> var currentBackend = TargetBackend.ANY
) { for (line in testFile.readLines()) {
KotlinTestUtils.assertEqualsToFile( if (line.contains(JVM_TEMPLATES)) {
testFile, currentBackend = TargetBackend.JVM
text.substringBefore("// VARIABLE : ") + getActualVariablesAsString( } else if (line.contains(JVM_IR_TEMPLATES)) {
actualLocalVariables 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 name: String,
val type: String, val type: String,
val index: Int, val index: Int,
@@ -82,8 +126,8 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
} }
} }
private fun parseClassAndMethodSignature(testFile: File): String { private fun parseClassAndMethodSignature(testFileLines: List<String>): String {
for (line in testFile.readLines()) { for (line in testFileLines) {
val methodMatcher = methodPattern.matcher(line) val methodMatcher = methodPattern.matcher(line)
if (methodMatcher.matches()) { if (methodMatcher.matches()) {
return methodMatcher.group(1) return methodMatcher.group(1)
@@ -95,7 +139,9 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
companion object { 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 : *(.*)") private val methodPattern = Pattern.compile("^// METHOD : *(.*)")
@@ -104,6 +150,7 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
class Visitor : ClassVisitor(Opcodes.API_VERSION) { class Visitor : ClassVisitor(Opcodes.API_VERSION) {
var readVariables: MutableList<LocalVariable> = ArrayList() var readVariables: MutableList<LocalVariable> = ArrayList()
var methodFound = false var methodFound = false
var methodsFound = mutableListOf<String>()
override fun visitMethod( override fun visitMethod(
access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>? access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?
@@ -128,6 +175,7 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
} }
} }
} else { } else {
methodsFound.add(name + desc)
super.visitMethod(access, name, desc, signature, exceptions) super.visitMethod(access, name, desc, signature, exceptions)
} }
} }
@@ -135,7 +183,10 @@ abstract class AbstractCheckLocalVariablesTableTest : CodegenTestCase() {
val visitor = Visitor() val visitor = Visitor()
cr.accept(visitor, ClassReader.SKIP_FRAMES) 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 return visitor.readVariables
} }
@@ -5,43 +5,9 @@
package org.jetbrains.kotlin.codegen.ir package org.jetbrains.kotlin.codegen.ir
import com.intellij.openapi.util.Comparing
import org.jetbrains.kotlin.codegen.AbstractCheckLocalVariablesTableTest import org.jetbrains.kotlin.codegen.AbstractCheckLocalVariablesTableTest
import org.jetbrains.kotlin.test.TargetBackend import org.jetbrains.kotlin.test.TargetBackend
import org.junit.ComparisonFailure
import java.io.File
abstract class AbstractIrCheckLocalVariablesTableTest : AbstractCheckLocalVariablesTableTest() { 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 override fun getBackend() = TargetBackend.JVM_IR
} }