Made test runner work with binary compatibility tests

(cherry picked from commit e0b599a632b94e4b1303fc94275dcd5b6a7dfb6b)
This commit is contained in:
Alexander Gorshenev
2021-01-14 05:23:05 +03:00
committed by Vasily Levchenko
parent 64445bb793
commit 58ea71cb0e
3 changed files with 102 additions and 31 deletions
@@ -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/**'
}
}
@@ -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<String, TestModule> modules
List<String> flags
MultiModuleCompilerInvocations(String executablePath, Map<String, TestModule> modules, List<String> flags) {
this.executablePath = executablePath
this.modules = modules
this.flags = flags
}
def libs = new HashSet<String>()
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<TestFile> 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<TestModule> orderedModules = DFS.INSTANCE.topologicalOrder(modules.values()) { module ->
module.dependencies.collect { modules[it] }.findAll { it != null }
}
Set<String> libs = new HashSet<String>()
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)
@@ -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<String>,
val friends: List<String>
val name: String,
val dependencies: List<String>,
val friends: List<String>
) {
val files = mutableListOf<TestFile>()
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<String, String> by lazy {
parseDirectives()
}
val version: Int? get() = this.directives["VERSION"]?.toInt()
fun parseDirectives(): Map<String, String> {
val newDirectives = mutableMapOf<String, String>()
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].
*/