[Test] Implement SMAP dump handler
This commit is contained in:
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.backend.common.output.OutputFile
|
||||
import org.jetbrains.kotlin.codegen.inline.RangeMapping
|
||||
import org.jetbrains.kotlin.codegen.inline.SMAPParser
|
||||
import org.jetbrains.kotlin.codegen.inline.toRange
|
||||
import org.jetbrains.kotlin.test.Assertions
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import java.io.File
|
||||
|
||||
object CommonSMAPTestUtil {
|
||||
fun extractSMAPFromClasses(outputFiles: Iterable<OutputFile>): List<SMAPAndFile> {
|
||||
return outputFiles.map { outputFile ->
|
||||
var debugInfo: String? = null
|
||||
ClassReader(outputFile.asByteArray()).accept(object : ClassVisitor(Opcodes.API_VERSION) {
|
||||
override fun visitSource(source: String?, debug: String?) {
|
||||
debugInfo = debug
|
||||
}
|
||||
}, 0)
|
||||
|
||||
SMAPAndFile(debugInfo, outputFile.sourceFiles.single(), outputFile.relativePath)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkNoConflictMappings(compiledSmap: List<SMAPAndFile>?, assertions: Assertions) {
|
||||
if (compiledSmap == null) return
|
||||
|
||||
compiledSmap.mapNotNull(SMAPAndFile::smap).forEach { smapString ->
|
||||
val smap = SMAPParser.parseOrNull(smapString) ?: throw AssertionError("bad SMAP: $smapString")
|
||||
val conflictingLines = smap.fileMappings.flatMap { fileMapping ->
|
||||
fileMapping.lineMappings.flatMap { lineMapping: RangeMapping ->
|
||||
lineMapping.toRange.keysToMap { lineMapping }.entries
|
||||
}
|
||||
}.groupBy { it.key }.entries.filter { it.value.size != 1 }
|
||||
|
||||
assertions.assertTrue(conflictingLines.isEmpty()) {
|
||||
conflictingLines.joinToString(separator = "\n") {
|
||||
"Conflicting mapping for line ${it.key} in ${it.value.joinToString(transform = Any::toString)}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SMAPAndFile(val smap: String?, val sourceFile: String, val outputFile: String) {
|
||||
constructor(smap: String?, sourceFile: File, outputFile: String) : this(smap, getPath(sourceFile), outputFile)
|
||||
|
||||
companion object {
|
||||
fun getPath(file: File): String =
|
||||
getPath(file.canonicalPath)
|
||||
|
||||
fun getPath(canonicalPath: String): String {
|
||||
//There are some problems with disk name on windows cause LightVirtualFile return it without disk name
|
||||
return FileUtil.toSystemIndependentName(canonicalPath).substringAfter(":")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.test.backend.handlers
|
||||
|
||||
import org.jetbrains.kotlin.codegen.CommonSMAPTestUtil
|
||||
import org.jetbrains.kotlin.codegen.inline.GENERATE_SMAP
|
||||
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
|
||||
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.DUMP_SMAP
|
||||
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.SEPARATE_SMAP_DUMPS
|
||||
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
|
||||
import org.jetbrains.kotlin.test.model.BinaryArtifacts
|
||||
import org.jetbrains.kotlin.test.model.TestModule
|
||||
import org.jetbrains.kotlin.test.services.TestServices
|
||||
import org.jetbrains.kotlin.test.services.moduleStructure
|
||||
import org.jetbrains.kotlin.test.utils.MultiModuleInfoDumperImpl
|
||||
import org.jetbrains.kotlin.test.utils.withExtension
|
||||
|
||||
class SMAPDumpHandler(testServices: TestServices) : JvmBinaryArtifactHandler(testServices) {
|
||||
companion object {
|
||||
const val SMAP_EXT = "smap"
|
||||
const val SMAP_SEP_EXT = "smap-separate-compilation"
|
||||
const val SMAP_NON_SEP_EXT = "smap-nonseparate-compilation"
|
||||
}
|
||||
|
||||
override val directivesContainers: List<DirectivesContainer>
|
||||
get() = listOf(CodegenTestDirectives)
|
||||
|
||||
private val dumper = MultiModuleInfoDumperImpl()
|
||||
|
||||
override fun processModule(module: TestModule, info: BinaryArtifacts.Jvm) {
|
||||
if (!GENERATE_SMAP) return
|
||||
if (DUMP_SMAP !in module.directives) return
|
||||
|
||||
val compiledSmaps = CommonSMAPTestUtil.extractSMAPFromClasses(info.classFileFactory.currentOutput)
|
||||
|
||||
CommonSMAPTestUtil.checkNoConflictMappings(compiledSmaps, assertions)
|
||||
|
||||
val compiledData = compiledSmaps.groupBy {
|
||||
it.sourceFile
|
||||
}.map {
|
||||
val smap = it.value.sortedByDescending(CommonSMAPTestUtil.SMAPAndFile::outputFile).mapNotNull(CommonSMAPTestUtil.SMAPAndFile::smap).joinToString("\n")
|
||||
CommonSMAPTestUtil.SMAPAndFile(if (smap.isNotEmpty()) smap else null, it.key, "NOT_SORTED")
|
||||
}.associateBy { it.sourceFile }
|
||||
|
||||
dumper.builderForModule(module).apply {
|
||||
for (source in compiledData.values) {
|
||||
appendLine("// FILE: ${source.sourceFile}")
|
||||
appendLine(source.smap ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {
|
||||
val separateDumpEnabled = separateDumpsEnabled()
|
||||
val isSeparateCompilation = isSeparateCompilation()
|
||||
|
||||
val extension = when {
|
||||
!separateDumpEnabled -> SMAP_EXT
|
||||
isSeparateCompilation -> SMAP_SEP_EXT
|
||||
else -> SMAP_NON_SEP_EXT
|
||||
}
|
||||
|
||||
val testDataFile = testServices.moduleStructure.originalTestDataFiles.first()
|
||||
val expectedFile = testDataFile.withExtension(extension)
|
||||
assertions.assertEqualsToFile(expectedFile, dumper.generateResultingDump())
|
||||
|
||||
if (separateDumpEnabled && isSeparateCompilation) {
|
||||
val otherExtension = if (isSeparateCompilation) SMAP_NON_SEP_EXT else SMAP_SEP_EXT
|
||||
val otherFile = expectedFile.withExtension(otherExtension)
|
||||
if (!otherFile.exists()) return
|
||||
val expectedText = expectedFile.readText()
|
||||
if (expectedText == otherFile.readText()) {
|
||||
val smapFile = expectedFile.withExtension(SMAP_EXT)
|
||||
smapFile.writeText(expectedText)
|
||||
expectedFile.delete()
|
||||
otherFile.delete()
|
||||
assertions.fail {
|
||||
"""
|
||||
Contents of ${expectedFile.name} and ${otherFile.name} are equals, so they are deleted
|
||||
and joined to ${smapFile.name}. Please remove $SEPARATE_SMAP_DUMPS directive from
|
||||
${testDataFile.name} and rerun test
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSeparateCompilation(): Boolean {
|
||||
return testServices.moduleStructure.modules.size > 1
|
||||
}
|
||||
|
||||
private fun separateDumpsEnabled(): Boolean {
|
||||
return SEPARATE_SMAP_DUMPS in testServices.moduleStructure.allDirectives
|
||||
}
|
||||
}
|
||||
+13
@@ -95,4 +95,17 @@ object CodegenTestDirectives : SimpleDirectivesContainer() {
|
||||
val SKIP_INLINE_CHECK_IN by stringDirective(
|
||||
description = "Skip checking of specific methods in ${BytecodeInliningHandler::class.java}"
|
||||
)
|
||||
|
||||
val DUMP_SMAP by directive(
|
||||
description = """Enables ${SMAPDumpHandler::class}"""
|
||||
)
|
||||
|
||||
val SEPARATE_SMAP_DUMPS by directive(
|
||||
description = """
|
||||
If enabled then ${SMAPDumpHandler::class} will dump smap dumps
|
||||
into ${SMAPDumpHandler.SMAP_SEP_EXT} and ${SMAPDumpHandler.SMAP_EXT}
|
||||
files instead of ${SMAPDumpHandler.SMAP_EXT} depending of module
|
||||
structure of test
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,36 +16,18 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import org.jetbrains.kotlin.backend.common.output.OutputFile
|
||||
import org.jetbrains.kotlin.codegen.CommonSMAPTestUtil.SMAPAndFile
|
||||
import org.jetbrains.kotlin.codegen.CommonSMAPTestUtil.checkNoConflictMappings
|
||||
import org.jetbrains.kotlin.codegen.CommonSMAPTestUtil.extractSMAPFromClasses
|
||||
import org.jetbrains.kotlin.codegen.inline.GENERATE_SMAP
|
||||
import org.jetbrains.kotlin.codegen.inline.RangeMapping
|
||||
import org.jetbrains.kotlin.codegen.inline.SMAPParser
|
||||
import org.jetbrains.kotlin.codegen.inline.toRange
|
||||
import org.jetbrains.kotlin.test.KotlinBaseTest
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.kotlin.test.util.JUnit4Assertions
|
||||
import org.junit.Assert
|
||||
import java.io.File
|
||||
import java.io.StringReader
|
||||
|
||||
object SMAPTestUtil {
|
||||
private fun extractSMAPFromClasses(outputFiles: Iterable<OutputFile>): List<SMAPAndFile> {
|
||||
return outputFiles.mapNotNull { outputFile ->
|
||||
var debugInfo: String? = null
|
||||
ClassReader(outputFile.asByteArray()).accept(object : ClassVisitor(Opcodes.API_VERSION) {
|
||||
override fun visitSource(source: String?, debug: String?) {
|
||||
debugInfo = debug
|
||||
}
|
||||
}, 0)
|
||||
|
||||
SMAPAndFile(debugInfo, outputFile.sourceFiles.single(), outputFile.relativePath)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractSmapFromTestDataFile(file: KotlinBaseTest.TestFile, separateCompilation: Boolean): SMAPAndFile? {
|
||||
if (!checkExtension(file, separateCompilation)) return null
|
||||
|
||||
@@ -88,43 +70,9 @@ object SMAPTestUtil {
|
||||
Assert.assertEquals("Smap data differs for $ktFileName", normalize(source.smap), normalize(data?.smap))
|
||||
}
|
||||
|
||||
checkNoConflictMappings(compiledSmaps)
|
||||
}
|
||||
|
||||
private fun checkNoConflictMappings(compiledSmap: List<SMAPAndFile>?) {
|
||||
if (compiledSmap == null) return
|
||||
|
||||
compiledSmap.mapNotNull(SMAPAndFile::smap).forEach { smapString ->
|
||||
val smap = SMAPParser.parseOrNull(smapString) ?: throw AssertionError("bad SMAP: $smapString")
|
||||
val conflictingLines = smap.fileMappings.flatMap { fileMapping ->
|
||||
fileMapping.lineMappings.flatMap { lineMapping: RangeMapping ->
|
||||
lineMapping.toRange.keysToMap { lineMapping }.entries
|
||||
}
|
||||
}.groupBy { it.key }.entries.filter { it.value.size != 1 }
|
||||
|
||||
Assert.assertTrue(
|
||||
conflictingLines.joinToString(separator = "\n") {
|
||||
"Conflicting mapping for line ${it.key} in ${it.value.joinToString(transform = Any::toString)}"
|
||||
},
|
||||
conflictingLines.isEmpty()
|
||||
)
|
||||
}
|
||||
checkNoConflictMappings(compiledSmaps, JUnit4Assertions)
|
||||
}
|
||||
|
||||
private fun normalize(text: String?) =
|
||||
text?.let { StringUtil.convertLineSeparators(it.trim()) }
|
||||
|
||||
private class SMAPAndFile(val smap: String?, val sourceFile: String, val outputFile: String) {
|
||||
constructor(smap: String?, sourceFile: File, outputFile: String) : this(smap, getPath(sourceFile), outputFile)
|
||||
|
||||
companion object {
|
||||
fun getPath(file: File): String =
|
||||
getPath(file.canonicalPath)
|
||||
|
||||
fun getPath(canonicalPath: String): String {
|
||||
//There are some problems with disk name on windows cause LightVirtualFile return it without disk name
|
||||
return FileUtil.toSystemIndependentName(canonicalPath).substringAfter(":")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user