[Test] Migrate AbstractBytecodeTextTest to new test infrastructure

This commit is contained in:
Dmitriy Novozhilov
2021-01-25 14:40:14 +03:00
parent e928448e00
commit f61a318c9d
26 changed files with 3556 additions and 1737 deletions
@@ -0,0 +1,144 @@
/*
* 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.text.StringUtil
import org.jetbrains.kotlin.test.Assertions
import org.jetbrains.kotlin.test.TargetBackend
import java.io.File
import java.util.ArrayList
import java.util.regex.Matcher
import java.util.regex.Pattern
private val AT_OUTPUT_FILE_PATTERN = Pattern.compile("^\\s*//\\s*@(.*):$")
private val EXPECTED_OCCURRENCES_PATTERN = Pattern.compile("^\\s*//\\s*(\\d+)\\s*(.*)$")
private const val JVM_TEMPLATES = "// JVM_TEMPLATES"
private const val JVM_IR_TEMPLATES = "// JVM_IR_TEMPLATES"
class OccurrenceInfo constructor(private val numberOfOccurrences: Int, private val needle: String, val backend: TargetBackend) {
private val pattern = Pattern.compile("($needle)")
fun getActualOccurrence(text: String): String {
val actualCount = StringUtil.findMatches(text, pattern).size
return "$actualCount $needle"
}
override fun toString(): String {
return "$numberOfOccurrences $needle"
}
}
fun readExpectedOccurrences(filename: String): List<OccurrenceInfo> {
val lines = File(filename).readLines().dropLastWhile(String::isEmpty)
return readExpectedOccurrences(lines)
}
fun readExpectedOccurrences(lines: List<String>): List<OccurrenceInfo> {
val result = ArrayList<OccurrenceInfo>()
var backend = TargetBackend.ANY
for (line in lines) {
if (line.contains(JVM_TEMPLATES)) backend = TargetBackend.JVM
else if (line.contains(JVM_IR_TEMPLATES)) backend = TargetBackend.JVM_IR
val matcher = EXPECTED_OCCURRENCES_PATTERN.matcher(line)
if (matcher.matches()) {
result.add(parseOccurrenceInfo(matcher, backend))
}
}
return result
}
fun readExpectedOccurrencesForMultiFileTest(
fileName: String,
fileContent: String,
destination: MutableMap<String, List<OccurrenceInfo>>
) {
var currentOccurrenceInfos: MutableList<OccurrenceInfo>? = null
var backend = TargetBackend.ANY
for (line in fileContent.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
if (line.contains(JVM_TEMPLATES)) backend = TargetBackend.JVM
else if (line.contains(JVM_IR_TEMPLATES)) backend = TargetBackend.JVM_IR
val atOutputFileMatcher = AT_OUTPUT_FILE_PATTERN.matcher(line)
if (atOutputFileMatcher.matches()) {
val outputFileName = atOutputFileMatcher.group(1)
if (destination.containsKey(outputFileName)) {
throw AssertionError("${fileName}: Expected occurrences for output file $outputFileName were already provided")
}
currentOccurrenceInfos = ArrayList()
destination[outputFileName] = currentOccurrenceInfos
}
val expectedOccurrencesMatcher = EXPECTED_OCCURRENCES_PATTERN.matcher(line)
if (expectedOccurrencesMatcher.matches()) {
if (currentOccurrenceInfos == null) {
throw AssertionError("${fileName}: Should specify output file with '// @<OUTPUT_FILE_NAME>:' before expectations")
}
val occurrenceInfo = parseOccurrenceInfo(expectedOccurrencesMatcher, backend)
currentOccurrenceInfos.add(occurrenceInfo)
}
}
}
private fun parseOccurrenceInfo(matcher: Matcher, backend: TargetBackend): OccurrenceInfo {
val numberOfOccurrences = Integer.parseInt(matcher.group(1))
val needle = matcher.group(2)
return OccurrenceInfo(numberOfOccurrences, needle, backend)
}
fun checkGeneratedTextAgainstExpectedOccurrences(
text: String,
expectedOccurrences: List<OccurrenceInfo>,
currentBackend: TargetBackend,
reportProblems: Boolean,
assertions: Assertions
) {
val expected = StringBuilder()
val actual = StringBuilder()
var lastBackend = TargetBackend.ANY
for (info in expectedOccurrences) {
if (lastBackend != info.backend) {
when (info.backend) {
TargetBackend.JVM -> JVM_TEMPLATES
TargetBackend.JVM_IR -> JVM_IR_TEMPLATES
else -> error("Common part should be first one: ${expectedOccurrences.joinToString("\n")}")
}.also {
actual.append("\n$it\n")
expected.append("\n$it\n")
}
lastBackend = info.backend
}
expected.append(info).append("\n")
if (info.backend == TargetBackend.ANY || info.backend == currentBackend) {
actual.append(info.getActualOccurrence(text)).append("\n")
} else {
actual.append(info).append("\n")
}
}
try {
assertions.assertEquals(expected.toString(), actual.toString()) { text }
} catch (e: Throwable) {
if (reportProblems) {
println(text)
}
throw e
}
}
fun assertTextWasGenerated(expectedOutputFile: String, generated: Map<String, String>, assertions: Assertions) {
if (!generated.containsKey(expectedOutputFile)) {
val failMessage = StringBuilder()
failMessage.append("Missing output file ").append(expectedOutputFile).append(", got ").append(generated.size).append(": ")
for (generatedFile in generated.keys) {
failMessage.append(generatedFile).append(" ")
}
assertions.fail { failMessage.toString() }
}
}
@@ -7,6 +7,6 @@ class A {
}
}
// 1 public final g\$test_module\(Lkotlin/coroutines/Continuation;\)Ljava/lang/Object;
// 1 private final g\$test_module\$\$forInline\(Lkotlin/coroutines/Continuation;\)Ljava/lang/Object;
// 0 g\$\$forInline
// 1 public final g\$main\(Lkotlin/coroutines/Continuation;\)Ljava/lang/Object;
// 1 private final g\$main\$\$forInline\(Lkotlin/coroutines/Continuation;\)Ljava/lang/Object;
// 0 g\$\$forInline
@@ -1,6 +1,7 @@
// FILE: JClass.java
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JClass {
public final static int PrimitiveInt = 9000;
@@ -8,7 +9,7 @@ public class JClass {
public final static long PrimitiveLong = 100000;
public final static short PrimitiveShort = 901;
public final static boolean PrimitiveBool = false;
public final static float PrimitiveFloat = 36.6;
public final static float PrimitiveFloat = 36.6f;
public final static double PrimitiveDouble = 42.4242;
public final static byte PrimitiveByte = -8;
public final static char PrimitiveChar = 'K';
@@ -10,5 +10,5 @@ fun box(): String {
}
// Check the names of mangled functions
// 1 public final f\$test_module\(\)Ljava/lang/String;
// 1 public final static f\$test_module\(\)Ljava/lang/String;
// 1 public final f\$main\(\)Ljava/lang/String;
// 1 public final static f\$main\(\)Ljava/lang/String;
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM
fun main() {
println("FAIL")
}
@@ -8,4 +7,4 @@ fun Array<String>.main(x: Int = 4, y: String = "Test") {
println("OK")
}
// 0 INVOKESTATIC DontGenerateOnJvmOverloadsKt\.main ()V
// 0 INVOKESTATIC DontGenerateOnJvmOverloadsKt\.main ()V
@@ -1,6 +1,7 @@
// FILE: JClass.java
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JClass {
public final static int PrimitiveInt = 9000;
@@ -8,7 +9,7 @@ public class JClass {
public final static long PrimitiveLong = 100000;
public final static short PrimitiveShort = 901;
public final static boolean PrimitiveBool = false;
public final static float PrimitiveFloat = 36.6;
public final static float PrimitiveFloat = 36.6f;
public final static double PrimitiveDouble = 42.4242;
public final static byte PrimitiveByte = -8;
public final static char PrimitiveChar = 'K';
@@ -2,7 +2,7 @@
public class J {
public interface F {
public int call(String x)
public int call(String x);
}
public static void g(F r) {
@@ -26,4 +26,4 @@ inline fun inlineFun() {
// 0 declaration: void <init>\(kotlin.jvm.functions.Function1<.*, .*>\)
// 0 declaration: function extends kotlin.jvm.functions.Function1<.*, .*>
// 2 private final synthetic Lkotlin/jvm/functions/Function1; function
// 2 <init>\(Lkotlin/jvm/functions/Function1;\)V
// 2 <init>\(Lkotlin/jvm/functions/Function1;\)V
@@ -1,4 +1,5 @@
// KOTLIN_CONFIGURATION_FLAGS: STRING_CONCAT=indy-with-constants
// IGNORE_JAVA_ERRORS
// JVM_TARGET: 9
// FILE: JavaClass.java
@@ -16,4 +17,4 @@ fun box() {
// 0 INVOKEDYNAMIC makeConcat
// 1 JavaClass.toString
// 1 String.valueOf
// 0 append
// 0 append
@@ -0,0 +1,63 @@
/*
* 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.*
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.TREAT_AS_ONE_FILE
import org.jetbrains.kotlin.test.directives.model.DirectivesContainer
import org.jetbrains.kotlin.test.model.BinaryArtifacts
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.isKtFile
import java.util.LinkedHashMap
class BytecodeTextHandler(testServices: TestServices) : JvmBinaryArtifactHandler(testServices) {
companion object {
private const val IGNORED_PREFIX = "helpers/"
}
override val directivesContainers: List<DirectivesContainer>
get() = listOf(CodegenTestDirectives)
override fun processModule(module: TestModule, info: BinaryArtifacts.Jvm) {
val targetBackend = module.targetBackend!!
val isIgnored = targetBackend in module.directives[CodegenTestDirectives.IGNORE_BACKEND]
val files = module.files.filter { it.isKtFile }
if (files.size > 1 && TREAT_AS_ONE_FILE !in module.directives) {
processMultiFileTest(files, info, targetBackend, !isIgnored)
} else {
val file = files.first { !it.isAdditional }
val expected = readExpectedOccurrences(file.originalContent.split("\n"))
val actual = info.classFileFactory.createText(IGNORED_PREFIX)
checkGeneratedTextAgainstExpectedOccurrences(actual, expected, targetBackend, !isIgnored, assertions)
}
}
private fun processMultiFileTest(
files: List<TestFile>,
info: BinaryArtifacts.Jvm,
targetBackend: TargetBackend,
reportProblems: Boolean
) {
val expectedOccurrencesByOutputFile = LinkedHashMap<String, List<OccurrenceInfo>>()
for (file in files) {
readExpectedOccurrencesForMultiFileTest(file.name, file.originalContent, expectedOccurrencesByOutputFile)
}
val generated = info.classFileFactory.createTextForEachFile()
for (expectedOutputFile in expectedOccurrencesByOutputFile.keys) {
assertTextWasGenerated(expectedOutputFile, generated, assertions)
val generatedText = generated[expectedOutputFile]!!
val expectedOccurrences = expectedOccurrencesByOutputFile[expectedOutputFile]!!
checkGeneratedTextAgainstExpectedOccurrences(generatedText, expectedOccurrences, targetBackend, reportProblems, assertions)
}
}
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {}
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.test.directives
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.backend.handlers.BytecodeTextHandler
import org.jetbrains.kotlin.test.backend.handlers.IrPrettyKotlinDumpHandler
import org.jetbrains.kotlin.test.backend.handlers.IrTextDumpHandler
import org.jetbrains.kotlin.test.backend.handlers.NoCompilationErrorsHandler
@@ -85,4 +86,8 @@ object CodegenTestDirectives : SimpleDirectivesContainer() {
val SKIP_KT_DUMP by directive(
description = "Skips check pretty kt IR dump (disables ${IrPrettyKotlinDumpHandler::class})"
)
val TREAT_AS_ONE_FILE by directive(
description = "Treat bytecode from all files as one in ${BytecodeTextHandler::class}"
)
}
@@ -0,0 +1,101 @@
/*
* 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.runners.codegen
import org.jetbrains.kotlin.test.Constructor
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.backend.BlackBoxCodegenSuppressor
import org.jetbrains.kotlin.test.backend.classic.ClassicJvmBackendFacade
import org.jetbrains.kotlin.test.backend.handlers.BytecodeTextHandler
import org.jetbrains.kotlin.test.backend.ir.JvmIrBackendFacade
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.USE_PSI_CLASS_FILES_READING
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.WITH_REFLECT
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.WITH_STDLIB
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontend2ClassicBackendConverter
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontend2IrConverter
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendFacade
import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact
import org.jetbrains.kotlin.test.frontend.fir.Fir2IrResultsConverter
import org.jetbrains.kotlin.test.frontend.fir.FirFrontendFacade
import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact
import org.jetbrains.kotlin.test.model.*
import org.jetbrains.kotlin.test.runners.AbstractKotlinCompilerWithTargetBackendTest
abstract class AbstractBytecodeTextTestBase<R : ResultingArtifact.FrontendOutput<R>>(
targetBackend: TargetBackend,
val targetFrontend: FrontendKind<*>
) : AbstractKotlinCompilerWithTargetBackendTest(targetBackend) {
abstract val frontendFacade: Constructor<FrontendFacade<R>>
abstract val frontendToBackendConverter: Constructor<Frontend2BackendConverter<R, *>>
abstract val backendFacade: Constructor<BackendFacade<*, BinaryArtifacts.Jvm>>
override fun TestConfigurationBuilder.configuration() {
commonConfigurationForCodegenTest(targetFrontend, frontendFacade, frontendToBackendConverter, backendFacade)
defaultDirectives {
+WITH_STDLIB
+WITH_REFLECT
}
useArtifactsHandlers(::BytecodeTextHandler)
useAfterAnalysisCheckers(::BlackBoxCodegenSuppressor)
}
}
open class AbstractBytecodeTextTest : AbstractBytecodeTextTestBase<ClassicFrontendOutputArtifact>(
targetBackend = TargetBackend.JVM,
targetFrontend = FrontendKinds.ClassicFrontend
) {
override val frontendFacade: Constructor<FrontendFacade<ClassicFrontendOutputArtifact>>
get() = ::ClassicFrontendFacade
override val frontendToBackendConverter: Constructor<Frontend2BackendConverter<ClassicFrontendOutputArtifact, *>>
get() = ::ClassicFrontend2ClassicBackendConverter
override val backendFacade: Constructor<BackendFacade<*, BinaryArtifacts.Jvm>>
get() = ::ClassicJvmBackendFacade
}
open class AbstractIrBytecodeTextTest : AbstractBytecodeTextTestBase<ClassicFrontendOutputArtifact>(
targetBackend = TargetBackend.JVM_IR,
targetFrontend = FrontendKinds.ClassicFrontend
) {
override val frontendFacade: Constructor<FrontendFacade<ClassicFrontendOutputArtifact>>
get() = ::ClassicFrontendFacade
override val frontendToBackendConverter: Constructor<Frontend2BackendConverter<ClassicFrontendOutputArtifact, *>>
get() = ::ClassicFrontend2IrConverter
override val backendFacade: Constructor<BackendFacade<*, BinaryArtifacts.Jvm>>
get() = ::JvmIrBackendFacade
}
open class AbstractFirBytecodeTextTest : AbstractBytecodeTextTestBase<FirOutputArtifact>(
targetBackend = TargetBackend.JVM_IR,
targetFrontend = FrontendKinds.FIR
) {
override val frontendFacade: Constructor<FrontendFacade<FirOutputArtifact>>
get() = ::FirFrontendFacade
override val frontendToBackendConverter: Constructor<Frontend2BackendConverter<FirOutputArtifact, *>>
get() = ::Fir2IrResultsConverter
override val backendFacade: Constructor<BackendFacade<*, BinaryArtifacts.Jvm>>
get() = ::JvmIrBackendFacade
override fun configure(builder: TestConfigurationBuilder) {
super.configure(builder)
with(builder) {
defaultDirectives {
// See KT-44152
-USE_PSI_CLASS_FILES_READING
}
}
}
}
@@ -5,21 +5,13 @@
package org.jetbrains.kotlin.test.runners.codegen
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.test.Constructor
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.backend.BlackBoxCodegenSuppressor
import org.jetbrains.kotlin.test.backend.handlers.*
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.RUN_DEX_CHECKER
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.USE_PSI_CLASS_FILES_READING
import org.jetbrains.kotlin.test.model.*
import org.jetbrains.kotlin.test.runners.AbstractKotlinCompilerWithTargetBackendTest
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CodegenHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider
abstract class AbstractJvmBlackBoxCodegenTestBase<R : ResultingArtifact.FrontendOutput<R>>(
val targetFrontend: FrontendKind<R>,
@@ -30,31 +22,7 @@ abstract class AbstractJvmBlackBoxCodegenTestBase<R : ResultingArtifact.Frontend
abstract val backendFacade: Constructor<BackendFacade<*, BinaryArtifacts.Jvm>>
override fun TestConfigurationBuilder.configuration() {
globalDefaults {
frontend = targetFrontend
targetPlatform = JvmPlatforms.defaultJvmPlatform
dependencyKind = DependencyKind.Binary
}
defaultDirectives {
+USE_PSI_CLASS_FILES_READING
+RUN_DEX_CHECKER
}
useConfigurators(
::CommonEnvironmentConfigurator,
::JvmEnvironmentConfigurator
)
useAdditionalSourceProviders(
::AdditionalDiagnosticsSourceFilesProvider,
::CoroutineHelpersSourceFilesProvider,
::CodegenHelpersSourceFilesProvider,
)
useFrontendFacades(frontendFacade)
useFrontend2BackendConverters(frontendToBackendConverter)
useBackendFacades(backendFacade)
commonConfigurationForCodegenTest(targetFrontend, frontendFacade, frontendToBackendConverter, backendFacade)
useFrontendHandlers(
::NoCompilationErrorsHandler,
@@ -0,0 +1,51 @@
/*
* 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.runners.codegen
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.test.Constructor
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.RUN_DEX_CHECKER
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.USE_PSI_CLASS_FILES_READING
import org.jetbrains.kotlin.test.model.*
import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator
import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CodegenHelpersSourceFilesProvider
import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider
fun <R : ResultingArtifact.FrontendOutput<R>> TestConfigurationBuilder.commonConfigurationForCodegenTest(
targetFrontend: FrontendKind<*>,
frontendFacade: Constructor<FrontendFacade<R>>,
frontendToBackendConverter: Constructor<Frontend2BackendConverter<R, *>>,
backendFacade: Constructor<BackendFacade<*, BinaryArtifacts.Jvm>>
) {
globalDefaults {
frontend = targetFrontend
targetPlatform = JvmPlatforms.defaultJvmPlatform
dependencyKind = DependencyKind.Binary
}
defaultDirectives {
+USE_PSI_CLASS_FILES_READING
+RUN_DEX_CHECKER
}
useConfigurators(
::CommonEnvironmentConfigurator,
::JvmEnvironmentConfigurator
)
useAdditionalSourceProviders(
::AdditionalDiagnosticsSourceFilesProvider,
::CoroutineHelpersSourceFilesProvider,
::CodegenHelpersSourceFilesProvider,
)
useFrontendFacades(frontendFacade)
useFrontend2BackendConverters(frontendToBackendConverter)
useBackendFacades(backendFacade)
}
@@ -5,16 +5,14 @@
package org.jetbrains.kotlin.codegen
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.ObsoleteTestInfrastructure
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.TargetBackend
import org.junit.Assert
import org.jetbrains.kotlin.test.util.JUnit4Assertions
import java.io.File
import java.util.*
import java.util.regex.Matcher
import java.util.regex.Pattern
@ObsoleteTestInfrastructure
abstract class AbstractBytecodeTextTest : CodegenTestCase() {
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>) {
val isIgnored = InTextDirectivesUtils.isIgnoredTarget(backend, wholeFile)
@@ -31,57 +29,26 @@ abstract class AbstractBytecodeTextTest : CodegenTestCase() {
} else {
val expected = readExpectedOccurrences(wholeFile.path)
val actual = generateToText("helpers/")
checkGeneratedTextAgainstExpectedOccurrences(actual, expected, backend, !isIgnored)
checkGeneratedTextAgainstExpectedOccurrences(actual, expected, backend, !isIgnored, JUnit4Assertions)
}
}
private fun doTestMultiFile(files: List<TestFile>, reportProblems: Boolean) {
val expectedOccurrencesByOutputFile = LinkedHashMap<String, List<OccurrenceInfo>>()
for (file in files) {
readExpectedOccurrencesForMultiFileTest(file, expectedOccurrencesByOutputFile)
readExpectedOccurrencesForMultiFileTest(file.name, file.content, expectedOccurrencesByOutputFile)
}
val generated = generateEachFileToText()
for (expectedOutputFile in expectedOccurrencesByOutputFile.keys) {
assertTextWasGenerated(expectedOutputFile, generated)
assertTextWasGenerated(expectedOutputFile, generated, JUnit4Assertions)
val generatedText = generated[expectedOutputFile]!!
val expectedOccurrences = expectedOccurrencesByOutputFile[expectedOutputFile]!!
checkGeneratedTextAgainstExpectedOccurrences(generatedText, expectedOccurrences, backend, reportProblems)
}
}
protected fun readExpectedOccurrences(filename: String): List<OccurrenceInfo> {
val result = ArrayList<OccurrenceInfo>()
val lines = File(filename).readLines().dropLastWhile(String::isEmpty)
var backend = TargetBackend.ANY
for (line in lines) {
if (line.contains(JVM_TEMPLATES)) backend = TargetBackend.JVM
else if (line.contains(JVM_IR_TEMPLATES)) backend = TargetBackend.JVM_IR
val matcher = EXPECTED_OCCURRENCES_PATTERN.matcher(line)
if (matcher.matches()) {
result.add(parseOccurrenceInfo(matcher, backend))
}
}
return result
}
class OccurrenceInfo constructor(private val numberOfOccurrences: Int, private val needle: String, val backend: TargetBackend) {
fun getActualOccurrence(text: String): String? {
val actualCount = StringUtil.findMatches(text, Pattern.compile("($needle)")).size
return "$actualCount $needle"
}
override fun toString(): String {
return "$numberOfOccurrences $needle"
checkGeneratedTextAgainstExpectedOccurrences(generatedText, expectedOccurrences, backend, reportProblems, JUnit4Assertions)
}
}
companion object {
private val AT_OUTPUT_FILE_PATTERN = Pattern.compile("^\\s*//\\s*@(.*):$")
private val EXPECTED_OCCURRENCES_PATTERN = Pattern.compile("^\\s*//\\s*(\\d+)\\s*(.*)$")
private fun isMultiFileTest(files: List<TestFile>): Boolean {
var kotlinFiles = 0
for (file in files) {
@@ -91,95 +58,5 @@ abstract class AbstractBytecodeTextTest : CodegenTestCase() {
}
return kotlinFiles > 1
}
fun checkGeneratedTextAgainstExpectedOccurrences(
text: String,
expectedOccurrences: List<OccurrenceInfo>,
currentBackend: TargetBackend,
reportProblems: Boolean
) {
val expected = StringBuilder()
val actual = StringBuilder()
var lastBackend = TargetBackend.ANY
for (info in expectedOccurrences) {
if (lastBackend != info.backend) {
when (info.backend) {
TargetBackend.JVM -> JVM_TEMPLATES
TargetBackend.JVM_IR -> JVM_IR_TEMPLATES
else -> error("Common part should be first one: ${expectedOccurrences.joinToString("\n")}")
}.also {
actual.append("\n$it\n")
expected.append("\n$it\n")
}
lastBackend = info.backend
}
expected.append(info).append("\n")
if (info.backend == TargetBackend.ANY || info.backend == currentBackend) {
actual.append(info.getActualOccurrence(text)).append("\n")
} else {
actual.append(info).append("\n")
}
}
try {
Assert.assertEquals(text, expected.toString(), actual.toString())
} catch (e: Throwable) {
if (reportProblems) {
println(text)
}
throw e
}
}
private fun assertTextWasGenerated(expectedOutputFile: String, generated: Map<String, String>) {
if (!generated.containsKey(expectedOutputFile)) {
val failMessage = StringBuilder()
failMessage.append("Missing output file ").append(expectedOutputFile).append(", got ").append(generated.size).append(": ")
for (generatedFile in generated.keys) {
failMessage.append(generatedFile).append(" ")
}
Assert.fail(failMessage.toString())
}
}
private const val JVM_TEMPLATES = "// JVM_TEMPLATES"
private const val JVM_IR_TEMPLATES = "// JVM_IR_TEMPLATES"
private fun readExpectedOccurrencesForMultiFileTest(file: TestFile, occurrenceMap: MutableMap<String, List<OccurrenceInfo>>) {
var currentOccurrenceInfos: MutableList<OccurrenceInfo>? = null
var backend = TargetBackend.ANY
for (line in file.content.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
if (line.contains(JVM_TEMPLATES)) backend = TargetBackend.JVM
else if (line.contains(JVM_IR_TEMPLATES)) backend = TargetBackend.JVM_IR
val atOutputFileMatcher = AT_OUTPUT_FILE_PATTERN.matcher(line)
if (atOutputFileMatcher.matches()) {
val outputFileName = atOutputFileMatcher.group(1)
if (occurrenceMap.containsKey(outputFileName)) {
throw AssertionError("${file.name}: Expected occurrences for output file $outputFileName were already provided")
}
currentOccurrenceInfos = ArrayList()
occurrenceMap[outputFileName] = currentOccurrenceInfos
}
val expectedOccurrencesMatcher = EXPECTED_OCCURRENCES_PATTERN.matcher(line)
if (expectedOccurrencesMatcher.matches()) {
if (currentOccurrenceInfos == null) {
throw AssertionError("${file.name}: Should specify output file with '// @<OUTPUT_FILE_NAME>:' before expectations")
}
val occurrenceInfo = parseOccurrenceInfo(expectedOccurrencesMatcher, backend)
currentOccurrenceInfos.add(occurrenceInfo)
}
}
}
private fun parseOccurrenceInfo(matcher: Matcher, backend: TargetBackend): OccurrenceInfo {
val numberOfOccurrences = Integer.parseInt(matcher.group(1))
val needle = matcher.group(2)
return OccurrenceInfo(numberOfOccurrences, needle, backend)
}
}
}
@@ -24,12 +24,16 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.test.*;
import org.jetbrains.kotlin.test.util.JUnit4Assertions;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import java.io.File;
import java.util.Collections;
import java.util.List;
import static org.jetbrains.kotlin.codegen.BytecodeTextUtilsKt.checkGeneratedTextAgainstExpectedOccurrences;
import static org.jetbrains.kotlin.codegen.BytecodeTextUtilsKt.readExpectedOccurrences;
public abstract class AbstractTopLevelMembersInvocationTest extends AbstractBytecodeTextTest {
@Override
public void doTest(@NotNull String filename) throws Exception {
@@ -60,6 +64,6 @@ public abstract class AbstractTopLevelMembersInvocationTest extends AbstractByte
List<OccurrenceInfo> expected = readExpectedOccurrences(KtTestUtil.getTestDataPathBase() + "/codegen/" + sourceFiles.get(0));
String actual = generateToText();
Companion.checkGeneratedTextAgainstExpectedOccurrences(actual, expected, TargetBackend.ANY, true);
checkGeneratedTextAgainstExpectedOccurrences(actual, expected, TargetBackend.ANY, true, JUnit4Assertions.INSTANCE);
}
}
@@ -5,10 +5,12 @@
package org.jetbrains.kotlin.codegen.ir
import org.jetbrains.kotlin.ObsoleteTestInfrastructure
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.AbstractBytecodeTextTest
import org.jetbrains.kotlin.test.TargetBackend
@OptIn(ObsoleteTestInfrastructure::class)
abstract class AbstractComposeLikeIrBytecodeTextTest : AbstractBytecodeTextTest() {
override val backend = TargetBackend.JVM_IR
@@ -1,18 +0,0 @@
/*
* Copyright 2010-2020 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.ir
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
abstract class AbstractFirBytecodeTextTest : AbstractIrBytecodeTextTest() {
override fun updateConfiguration(configuration: CompilerConfiguration) {
super.updateConfiguration(configuration)
configuration.put(CommonConfigurationKeys.USE_FIR, true)
configuration.put(JVMConfigurationKeys.IR, true)
}
}
@@ -1,13 +0,0 @@
/*
* Copyright 2010-2019 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.ir
import org.jetbrains.kotlin.codegen.AbstractBytecodeTextTest
import org.jetbrains.kotlin.test.TargetBackend
abstract class AbstractIrBytecodeTextTest : AbstractBytecodeTextTest() {
override val backend = TargetBackend.JVM_IR
}
@@ -32,14 +32,19 @@ fun compileJavaFiles(
val diagnosticCollector = DiagnosticCollector<JavaFileObject>()
javaCompiler.getStandardFileManager(diagnosticCollector, Locale.ENGLISH, Charset.forName("utf-8")).use { fileManager ->
val javaFileObjectsFromFiles = fileManager.getJavaFileObjectsFromFiles(files)
val task = javaCompiler.getTask(
StringWriter(), // do not write to System.err
fileManager,
diagnosticCollector,
options,
null,
javaFileObjectsFromFiles
)
val task = try {
javaCompiler.getTask(
StringWriter(), // do not write to System.err
fileManager,
diagnosticCollector,
options,
null,
javaFileObjectsFromFiles
)
} catch (e: Throwable) {
if (ignoreJavaErrors) return false
else throw e
}
val success = task.call() // do NOT inline this variable, call() should complete before errorsToString()
if (javaErrorFile == null || !javaErrorFile.exists()) {
assertions.assertTrue(success || ignoreJavaErrors) { errorsToString(diagnosticCollector, true) }
@@ -140,10 +140,6 @@ fun generateJUnit3CompilerTests(args: Array<String>) {
model("codegen/customScript", pattern = "^(.*)$")
}
testClass<AbstractBytecodeTextTest> {
model("codegen/bytecodeText", targetBackend = TargetBackend.JVM)
}
testClass<AbstractIrJsTextTestCase> {
model("ir/irJsText", pattern = "^(.+)\\.kt(s)?\$")
}
@@ -484,10 +480,6 @@ fun generateJUnit3CompilerTests(args: Array<String>) {
model("codegen/boxInline", targetBackend = TargetBackend.JVM_IR)
}
testClass<AbstractIrBytecodeTextTest> {
model("codegen/bytecodeText", targetBackend = TargetBackend.JVM_IR, excludeDirs = listOf("oldLanguageVersions"))
}
testClass<AbstractIrAsmLikeInstructionListingTest> {
model("codegen/asmLike", targetBackend = TargetBackend.JVM_IR)
}
@@ -510,10 +502,6 @@ fun generateJUnit3CompilerTests(args: Array<String>) {
model("codegen/boxAgainstJava", targetBackend = TargetBackend.JVM_IR, excludeDirs = listOf("oldLanguageVersions"))
}
testClass<AbstractFirBytecodeTextTest> {
model("codegen/bytecodeText", targetBackend = TargetBackend.JVM_IR, excludeDirs = listOf("oldLanguageVersions"))
}
testClass<AbstractFirCompileKotlinAgainstKotlinTest> {
model("compileKotlinAgainstKotlin", targetBackend = TargetBackend.JVM_IR)
}
@@ -9,9 +9,7 @@ import org.jetbrains.kotlin.generators.model.annotation
import org.jetbrains.kotlin.generators.util.TestGeneratorUtil
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.runners.*
import org.jetbrains.kotlin.test.runners.codegen.AbstractBlackBoxCodegenTest
import org.jetbrains.kotlin.test.runners.codegen.AbstractFirBlackBoxCodegenTest
import org.jetbrains.kotlin.test.runners.codegen.AbstractIrBlackBoxCodegenTest
import org.jetbrains.kotlin.test.runners.codegen.*
import org.jetbrains.kotlin.test.runners.ir.AbstractFir2IrTextTest
import org.jetbrains.kotlin.test.runners.ir.AbstractIrTextTest
import org.junit.jupiter.api.parallel.Execution
@@ -73,6 +71,14 @@ fun generateJUnit5CompilerTests(args: Array<String>) {
testClass<AbstractIrTextTest> {
model("ir/irText")
}
testClass<AbstractBytecodeTextTest> {
model("codegen/bytecodeText")
}
testClass<AbstractIrBytecodeTextTest> {
model("codegen/bytecodeText", excludeDirs = listOf("oldLanguageVersions"))
}
}
// ---------------------------------------------- FIR tests ----------------------------------------------
@@ -105,6 +111,10 @@ fun generateJUnit5CompilerTests(args: Array<String>) {
testClass<AbstractFir2IrTextTest> {
model("ir/irText")
}
testClass<AbstractFirBytecodeTextTest> {
model("codegen/bytecodeText", excludeDirs = listOf("oldLanguageVersions"))
}
}
}
}
@@ -16,9 +16,11 @@
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.ObsoleteTestInfrastructure
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
@OptIn(ObsoleteTestInfrastructure::class)
class CustomBytecodeTextTest : AbstractBytecodeTextTest() {
fun testEnumMapping() {
createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.ALL)
@@ -16,13 +16,18 @@
package org.jetbrains.kotlin.android.synthetic.test
import org.jetbrains.kotlin.ObsoleteTestInfrastructure
import org.jetbrains.kotlin.codegen.AbstractBytecodeTextTest
import org.jetbrains.kotlin.codegen.checkGeneratedTextAgainstExpectedOccurrences
import org.jetbrains.kotlin.codegen.readExpectedOccurrences
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.kotlin.test.util.JUnit4Assertions
@OptIn(ObsoleteTestInfrastructure::class)
abstract class AbstractAndroidBytecodeShapeTest : AbstractBytecodeTextTest() {
private fun createAndroidAPIEnvironment(path: String) {
return createEnvironmentForConfiguration(KotlinTestUtils.newConfiguration(ConfigurationKind.ALL, TestJdkKind.ANDROID_API), path)
@@ -40,6 +45,6 @@ abstract class AbstractAndroidBytecodeShapeTest : AbstractBytecodeTextTest() {
loadFileByFullPath(fileName)
val expected = readExpectedOccurrences(fileName)
val actual = generateToText()
checkGeneratedTextAgainstExpectedOccurrences(actual, expected, TargetBackend.ANY, true)
checkGeneratedTextAgainstExpectedOccurrences(actual, expected, TargetBackend.ANY, true, JUnit4Assertions)
}
}