From 58ea71cb0ef1ce539173b4fb2d139a3a7f3eb495 Mon Sep 17 00:00:00 2001 From: Alexander Gorshenev Date: Thu, 14 Jan 2021 05:23:05 +0300 Subject: [PATCH] Made test runner work with binary compatibility tests (cherry picked from commit e0b599a632b94e4b1303fc94275dcd5b6a7dfb6b) --- .../backend.native/tests/build.gradle | 1 + .../org/jetbrains/kotlin/KonanTest.groovy | 84 ++++++++++++++----- .../org/jetbrains/kotlin/TestDirectives.kt | 48 +++++++++-- 3 files changed, 102 insertions(+), 31 deletions(-) diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index b6103d957d6..12f3fdda098 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -220,6 +220,7 @@ def update_external_tests() { include 'compiler/codegen/box/**' include 'compiler/codegen/boxInline/**' include 'compiler/compileKotlinAgainstKotlin/**' + include 'compiler/binaryCompatibility/klibEvolution/**' exclude 'stdlib/**' } } diff --git a/kotlin-native/build-tools/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy b/kotlin-native/build-tools/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy index 749ee1436b3..87c7690c7da 100644 --- a/kotlin-native/build-tools/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy +++ b/kotlin-native/build-tools/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy @@ -22,6 +22,7 @@ import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecResult import org.jetbrains.kotlin.utils.DFS +import java.io.File import java.nio.file.Paths import java.util.function.Function import java.util.function.UnaryOperator @@ -469,6 +470,59 @@ fun runTest() { } } + class MultiModuleCompilerInvocations { + String executablePath + Map modules + List flags + + MultiModuleCompilerInvocations(String executablePath, Map modules, List flags) { + this.executablePath = executablePath + this.modules = modules + this.flags = flags + } + + def libs = new HashSet() + + String moduleToKlibName(String moduleName, Integer version) { + def module = modules[moduleName] + // TODO: cleanup + def versionSuffix = module.hasVersions ? "_version${version}" : "" + return module.hasVersions ? "${executablePath}${versionSuffix}/${moduleName}.klib" : "${executablePath}.${moduleName}.klib" + } + + void produceLibrary(TestModule module, Integer moduleVersion, Integer dependencyVersion) { + def klibModulePath = moduleToKlibName(module.name, moduleVersion) + libs.addAll(module.dependencies) + def klibs = module.dependencies.collectMany { ["-l", moduleToKlibName(it, dependencyVersion)] }.toList() + def repos = ["-r", "${executablePath}_version${dependencyVersion}", "-r", project.file(executablePath).parentFile.absolutePath ] + + def friends = module.friends ? + module.friends.collectMany { + ["-friend-modules", "${executablePath}.${it}.klib"] + }.toList() : [] + runCompiler(module.versionFiles(moduleVersion).collect { it.path }, + klibModulePath, flags + ["-p", "library"/*, "-module-name", module.name*/] + repos + klibs + friends) + } + + void produceProgram(List compileList, Integer version) { + def compileMain = compileList.findAll { + it.module.isDefaultModule() || it.module == TestModule.support + } + compileMain.forEach { f -> + libs.addAll(f.module.dependencies) + } + def friends = compileMain.collectMany { it.module.friends }.toSet() + def repos = ["-r", "${executablePath}_version${version}", "-r", project.file(executablePath).parentFile.absolutePath] + + if (!compileMain.empty) { + runCompiler(compileMain.collect { it.path }, executablePath, flags + repos + + libs.collectMany { ["-l", moduleToKlibName(it, version)] }.toList() + + friends.collectMany { ["-friend-modules", "${executablePath}.${it}.klib"] }.toList() + ) + } + } + } + @TaskAction void executeTest() { createOutputDirectory() @@ -519,34 +573,18 @@ fun runTest() { List orderedModules = DFS.INSTANCE.topologicalOrder(modules.values()) { module -> module.dependencies.collect { modules[it] }.findAll { it != null } } - Set libs = new HashSet() + def compiler = new MultiModuleCompilerInvocations(executablePath(), modules, flags) + orderedModules.reverse().each { module -> if (!module.isDefaultModule()) { - def klibModulePath = "${executablePath()}.${module.name}.klib" - libs.addAll(module.dependencies) - def klibs = libs.collectMany { ["-l", "${executablePath()}.${it}.klib"] }.toList() - def friends = module.friends ? - module.friends.collectMany { - ["-friend-modules", "${executablePath()}.${it}.klib"] - }.toList() : [] - runCompiler(compileList.findAll { it.module == module }.collect { it.path }, - klibModulePath, flags + ["-p", "library"] + klibs + friends) + compiler.produceLibrary(module, 1, 1) + if (module.hasVersions) { + compiler.produceLibrary(module, 2, 1) + } } } - def compileMain = compileList.findAll { - it.module.isDefaultModule() || it.module == TestModule.support - } - compileMain.forEach { f -> - libs.addAll(f.module.dependencies) - } - def friends = compileMain.collectMany {it.module.friends }.toSet() - if (!compileMain.empty) { - runCompiler(compileMain.collect { it.path }, executablePath(), flags + - libs.collectMany { ["-l", "${executablePath()}.${it}.klib"] }.toList() + - friends.collectMany {["-friend-modules", "${executablePath()}.${it}.klib"]}.toList() - ) - } + compiler.produceProgram(compileList, 2) } } catch (Exception ex) { project.logger.quiet("ERROR: Compilation failed for test suite: $name with exception", ex) diff --git a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/TestDirectives.kt b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/TestDirectives.kt index 6621b68b42e..c604b49b009 100644 --- a/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/TestDirectives.kt +++ b/kotlin-native/build-tools/src/main/kotlin/org/jetbrains/kotlin/TestDirectives.kt @@ -5,15 +5,21 @@ package org.jetbrains.kotlin +import org.jetbrains.kotlin.TestModule.Companion.default +import org.jetbrains.kotlin.TestModule.Companion.support import java.nio.file.Path import java.nio.file.Paths +import java.util.regex.Matcher import java.util.regex.Pattern private const val MODULE_DELIMITER = ",\\s*" -// This pattern is a copy from the kotlin/compiler/tests-common/tests/org/jetbrains/kotlin/test/TestFiles.java +// These patterns are copies from +// kotlin/compiler/tests-common/tests/org/jetbrains/kotlin/test/TestFiles.java +// kotlin/compiler/tests-common/tests/org/jetbrains/kotlin/test/KotlinTestUtils.java private val FILE_OR_MODULE_PATTERN: Pattern = Pattern.compile("(?://\\s*MODULE:\\s*([^()\\n]+)(?:\\(([^()]+(?:" + "$MODULE_DELIMITER[^()]+)*)\\))?\\s*(?:\\(([^()]+(?:$MODULE_DELIMITER[^()]+)*)\\))?\\s*)?//\\s*FILE:\\s*(.*)$", Pattern.MULTILINE) +private val DIRECTIVE_PATTERN = Pattern.compile("^//\\s*[!]?([A-Z_]+)(:[ \\t]*(.*))?$", Pattern.MULTILINE) /** * Creates test files from the given source file that may contain different test directives. @@ -82,12 +88,16 @@ private fun String?.parseModuleList() = this * - [support] for a helper sources like Coroutines support. */ data class TestModule( - val name: String, - val dependencies: List, - val friends: List + val name: String, + val dependencies: List, + val friends: List ) { + val files = mutableListOf() fun isDefaultModule() = this == default || name.endsWith(".main") + val hasVersions get() = this.files.any { it.version != null } + fun versionFiles(version: Int) = this.files.filter { it.version == null || it.version == version } + companion object { val default = TestModule("default", emptyList(), emptyList()) val support = TestModule("support", emptyList(), emptyList()) @@ -97,11 +107,33 @@ data class TestModule( /** * Represent a single test file that belongs to the [module]. */ -data class TestFile(val name: String, - val path: String, - var text: String = "", - val module: TestModule = TestModule.default +data class TestFile( + val name: String, + val path: String, + var text: String = "", + val module: TestModule = TestModule.default ) { + init { + this.module.files.add(this) + } + + val directives: Map by lazy { + parseDirectives() + } + + val version: Int? get() = this.directives["VERSION"]?.toInt() + + fun parseDirectives(): Map { + val newDirectives = mutableMapOf() + val directiveMatcher: Matcher = DIRECTIVE_PATTERN.matcher(text) + while (directiveMatcher.find()) { + val name = directiveMatcher.group(1) + val value = directiveMatcher.group(3) + newDirectives.put(name, value) + } + return newDirectives + } + /** * Writes [text] to the file created from the [path]. */