diff --git a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/CommonSMAPTestUtil.kt b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/CommonSMAPTestUtil.kt new file mode 100644 index 00000000000..4074b02d5dd --- /dev/null +++ b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/CommonSMAPTestUtil.kt @@ -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): List { + 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?, 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(":") + } + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/handlers/SMAPDumpHandler.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/handlers/SMAPDumpHandler.kt new file mode 100644 index 00000000000..6b3a64d8114 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/handlers/SMAPDumpHandler.kt @@ -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 + 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 + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/CodegenTestDirectives.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/CodegenTestDirectives.kt index 22a47501f5d..059ce196a89 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/CodegenTestDirectives.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/CodegenTestDirectives.kt @@ -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() + ) } diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/SMAPTestUtil.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/SMAPTestUtil.kt index 9d84969bb37..96c7594f489 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/SMAPTestUtil.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/SMAPTestUtil.kt @@ -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): List { - 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?) { - 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(":") - } - } - } }