[IR][tests] Support friend-dependencies in KLIB ABI compatibility tests

This commit is contained in:
Dmitriy Dolovov
2022-09-26 14:39:16 +02:00
parent bd4304c6e2
commit 181809e2af
5 changed files with 118 additions and 70 deletions
@@ -18,8 +18,8 @@ object KlibABITestUtils {
val buildDir: File
val stdlibFile: File
fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File)
fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: Collection<File>)
fun buildKlib(moduleName: String, moduleSourceDir: File, dependencies: Dependencies, klibFile: File)
fun buildBinaryAndRun(mainModuleKlibFile: File, dependencies: Dependencies)
fun onNonEmptyBuildDirectory(directory: File)
@@ -27,6 +27,15 @@ object KlibABITestUtils {
fun onIgnoredTest()
}
class Dependencies(val regularDependencies: Set<File>, val friendDependencies: Set<File>) {
fun mergeWith(other: Dependencies): Dependencies =
Dependencies(regularDependencies + other.regularDependencies, friendDependencies + other.friendDependencies)
companion object {
val EMPTY = Dependencies(emptySet(), emptySet())
}
}
fun runTest(testConfiguration: TestConfiguration) = with(testConfiguration) {
val projectName = testDir.name
@@ -70,6 +79,9 @@ object KlibABITestUtils {
}
}
// Collect all dependencies for building the final binary file.
var binaryDependencies = Dependencies.EMPTY
projectInfo.steps.forEach { projectStep ->
projectStep.order.forEach { moduleName ->
val (moduleInfo, moduleTestDir, moduleBuildDirs, klibFile) = modulesMap[moduleName]
@@ -84,24 +96,31 @@ object KlibABITestUtils {
if (!moduleBuildDirs.outputDir.list().isNullOrEmpty())
onNonEmptyBuildDirectory(moduleBuildDirs.outputDir)
val moduleDependencies = moduleStep.dependencies.map { dependencyName ->
if (dependencyName == "stdlib")
stdlibFile
else
modulesMap[dependencyName]?.klibFile ?: fail { "No module $dependencyName found on step ${projectStep.id}" }
val regularDependencies = hashSetOf<File>()
val friendDependencies = hashSetOf<File>()
moduleStep.dependencies.forEach { dependency ->
if (dependency.moduleName == "stdlib")
regularDependencies += stdlibFile
else {
val moduleFile = modulesMap[dependency.moduleName]?.klibFile
?: fail { "No module ${dependency.moduleName} found on step ${projectStep.id}" }
regularDependencies += moduleFile
if (dependency.isFriend) friendDependencies += moduleFile
}
}
buildKlib(moduleInfo.moduleName, moduleBuildDirs.sourceDir, moduleDependencies, klibFile)
val dependencies = Dependencies(regularDependencies, friendDependencies)
binaryDependencies = binaryDependencies.mergeWith(dependencies)
buildKlib(moduleInfo.moduleName, moduleBuildDirs.sourceDir, dependencies, klibFile)
}
}
val mainModuleKlibFile = modulesMap[MAIN_MODULE_NAME]?.klibFile ?: fail { "No main module $MAIN_MODULE_NAME found" }
val allKlibs = buildSet {
this += stdlibFile
modulesMap.mapTo(this) { (_, module) -> module.klibFile }
}
binaryDependencies = binaryDependencies.mergeWith(Dependencies(setOf(mainModuleKlibFile), emptySet()))
buildBinaryAndRun(mainModuleKlibFile, allKlibs)
buildBinaryAndRun(mainModuleKlibFile, binaryDependencies)
}
private fun copySources(from: File, to: File) {
@@ -41,9 +41,11 @@ class ModuleInfo(val moduleName: String) {
abstract fun execute(testDirectory: File, sourceDirectory: File, deletedFilesCollector: (File) -> Unit = {})
}
class Dependency(val moduleName: String, val isFriend: Boolean)
class ModuleStep(
val id: Int,
val dependencies: Collection<String>,
val dependencies: Collection<Dependency>,
val modifications: List<Modification>,
val expectedFileStats: Map<String, Set<String>>
)
@@ -51,9 +53,18 @@ class ModuleInfo(val moduleName: String) {
val steps = mutableListOf<ModuleStep>()
}
const val MODULES_LIST = "MODULES"
const val PROJECT_INFO_FILE = "project.info"
private const val MODULES_LIST = "MODULES"
private const val LIBS_LIST = "libs"
private const val DIRTY_JS_MODULES_LIST = "dirty js"
private const val LANGUAGE = "language"
const val MODULE_INFO_FILE = "module.info"
private const val DEPENDENCIES = "dependencies"
private const val FRIENDS = "friends"
private const val MODIFICATIONS = "modifications"
private const val MODIFICATION_UPDATE = "U"
private const val MODIFICATION_DELETE = "D"
private val STEP_PATTERN = Pattern.compile("^\\s*STEP\\s+(\\d+)\\.*(\\d+)?\\s*:?$")
@@ -104,8 +115,8 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
val splitIndex = line.indexOf(':')
if (splitIndex < 0) throwSyntaxError(line)
val splitted = line.split(":")
val op = splitted[0]
val split = line.split(":")
val op = split[0]
if (op.matches(STEP_PATTERN.toRegex())) {
return@loop true // break the loop
@@ -115,15 +126,9 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
when (op) {
"libs" -> {
order += splitted[1].splitAndTrim()
}
"dirty js" -> {
dirtyJS += splitted[1].splitAndTrim()
}
"language" -> {
language += splitted[1].splitAndTrim()
}
LIBS_LIST -> order += split[1].splitAndTrim()
DIRTY_JS_MODULES_LIST -> dirtyJS += split[1].splitAndTrim()
LANGUAGE -> language += split[1].splitAndTrim()
else -> println(diagnosticMessage("Unknown op $op", line))
}
@@ -149,13 +154,11 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
val splitIndex = line.indexOf(':')
if (splitIndex < 0) throwSyntaxError(line)
val splitted = line.split(":")
val op = splitted[0]
val split = line.split(":")
val op = split[0]
when {
op == MODULES_LIST -> {
libraries += splitted[1].splitAndTrim()
}
op == MODULES_LIST -> libraries += split[1].splitAndTrim()
op.matches(STEP_PATTERN.toRegex()) -> {
val m = STEP_PATTERN.matcher(op)
if (!m.matches()) throwSyntaxError(line)
@@ -164,7 +167,7 @@ class ProjectInfoParser(infoFile: File) : InfoParser<ProjectInfo>(infoFile) {
val lastId = m.group(2)?.let { Integer.parseInt(it) } ?: firstId
steps += parseSteps(firstId, lastId)
}
else -> println(diagnosticMessage("Unknown op $op", line))
else -> error(diagnosticMessage("Unknown op $op", line))
}
false
@@ -186,11 +189,11 @@ class ModuleInfoParser(infoFile: File) : InfoParser<ModuleInfo>(infoFile) {
val mop = matcher3.group(1)
val cmd = matcher3.group(2)
when (mop) {
"U" -> {
MODIFICATION_UPDATE -> {
val (from, to) = cmd.split("->")
modifications.add(ModuleInfo.Modification.Update(from.trim(), to.trim()))
}
"D" -> modifications.add(ModuleInfo.Modification.Delete(cmd.trim()))
MODIFICATION_DELETE -> modifications.add(ModuleInfo.Modification.Delete(cmd.trim()))
else -> error("Unknown modification $line")
}
false
@@ -204,7 +207,8 @@ class ModuleInfoParser(infoFile: File) : InfoParser<ModuleInfo>(infoFile) {
private fun parseSteps(firstId: Int, lastId: Int): List<ModuleInfo.ModuleStep> {
val expectedFileStats = mutableMapOf<String, Set<String>>()
val dependencies = mutableSetOf<String>()
val regularDependencies = mutableSetOf<String>()
val friendDependencies = mutableSetOf<String>()
val modifications = mutableListOf<ModuleInfo.Modification>()
loop { line ->
@@ -223,15 +227,24 @@ class ModuleInfoParser(infoFile: File) : InfoParser<ModuleInfo>(infoFile) {
expectedFileStats[expectedState.str] = getOpArgs().toSet()
} else {
when (op) {
"dependencies" -> getOpArgs().forEach { dependencies.add(it) }
"modifications" -> modifications.addAll(parseModifications())
else -> println(diagnosticMessage("Unknown op $op", line))
DEPENDENCIES -> getOpArgs().forEach { regularDependencies += it }
FRIENDS -> getOpArgs().forEach { friendDependencies += it }
MODIFICATIONS -> modifications += parseModifications()
else -> error(diagnosticMessage("Unknown op $op", line))
}
}
false
}
(friendDependencies - regularDependencies)
.takeIf(Set<String>::isNotEmpty)
?.let { error("Misconfiguration: There are friend modules that are not listed as regular dependencies: $it") }
val dependencies = regularDependencies.map { regularDependency ->
ModuleInfo.Dependency(regularDependency, regularDependency in friendDependencies)
}
return (firstId..lastId).map {
ModuleInfo.ModuleStep(
id = it,
@@ -43,7 +43,7 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
private const val BOX_FUNCTION_NAME = "box"
private const val STDLIB_ALIAS = "stdlib"
private val STDLIB_MODULE_NAME = "kotlin-kotlin-stdlib-js-ir"
private const val STDLIB_MODULE_NAME = "kotlin-kotlin-stdlib-js-ir"
private val STDLIB_KLIB = File(System.getProperty("kotlin.js.stdlib.klib.path") ?: error("Please set stdlib path")).canonicalPath
private val KT_FILE_IGNORE_PATTERN = Regex("^.*\\..+\\.kt$")
@@ -139,7 +139,9 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
modification.execute(moduleTestDir, moduleSourceDir) { deletedFiles.add(it.name) }
}
val dependencies = moduleStep.dependencies.mapTo(mutableListOf(File(STDLIB_KLIB))) { resolveModuleArtifact(it, buildDir) }
val dependencies = moduleStep.dependencies.mapTo(mutableListOf(File(STDLIB_KLIB))) {
resolveModuleArtifact(it.moduleName, buildDir)
}
val outputKlibFile = resolveModuleArtifact(module, buildDir)
val configuration = createConfiguration(module, projStep.language)
buildArtifact(configuration, module, moduleSourceDir, dependencies, outputKlibFile)
@@ -71,11 +71,11 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() {
override val buildDir: File get() = this@AbstractJsKLibABITestCase.buildDir
override val stdlibFile: File get() = File("libraries/stdlib/js-ir/build/classes/kotlin/js/main").absoluteFile
override fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File) =
this@AbstractJsKLibABITestCase.buildKlib(moduleName, moduleSourceDir, moduleDependencies, klibFile)
override fun buildKlib(moduleName: String, moduleSourceDir: File, dependencies: KlibABITestUtils.Dependencies, klibFile: File) =
this@AbstractJsKLibABITestCase.buildKlib(moduleName, moduleSourceDir, dependencies, klibFile)
override fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: Collection<File>) =
this@AbstractJsKLibABITestCase.buildBinaryAndRun(mainModuleKlibFile, allDependencies)
override fun buildBinaryAndRun(mainModuleKlibFile: File, dependencies: KlibABITestUtils.Dependencies) =
this@AbstractJsKLibABITestCase.buildBinaryAndRun(mainModuleKlibFile, dependencies)
override fun onNonEmptyBuildDirectory(directory: File) {
directory.listFiles()?.forEach(File::deleteRecursively)
@@ -103,7 +103,7 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() {
// The entry point to generated test classes.
fun doTest(testPath: String) = KlibABITestUtils.runTest(JsTestConfiguration(testPath))
private fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File) {
private fun buildKlib(moduleName: String, moduleSourceDir: File, dependencies: KlibABITestUtils.Dependencies, klibFile: File) {
val ktFiles = environment.createPsiFiles(moduleSourceDir)
val config = environment.configuration.copy()
@@ -113,15 +113,15 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() {
environment.project,
ktFiles,
config,
moduleDependencies.map { it.path },
emptyList(), // TODO
dependencies.regularDependencies.map { it.path },
dependencies.friendDependencies.map { it.path },
AnalyzerWithCompilerReport(config)
)
generateKLib(sourceModule, IrFactoryImpl, klibFile.path, nopack = false, jsOutputName = moduleName)
}
private fun buildBinaryAndRun(mainModuleKlibFile: File, libraries: Collection<File>) {
private fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: KlibABITestUtils.Dependencies) {
val configuration = environment.configuration.copy()
configuration.put(JSConfigurationKeys.PARTIAL_LINKAGE, true)
@@ -130,12 +130,12 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() {
configuration.put(CommonConfigurationKeys.MODULE_NAME, MAIN_MODULE_NAME)
val compilationOutputs = if (useIncrementalCompiler)
buildBinaryWithIC(configuration, mainModuleKlibFile, libraries)
buildBinaryWithIC(configuration, mainModuleKlibFile, allDependencies)
else
buildBinaryNoIC(configuration, mainModuleKlibFile, libraries)
buildBinaryNoIC(configuration, mainModuleKlibFile, allDependencies)
val binariesDir = File(buildDir, BIN_DIR_NAME).also { it.mkdirs() }
val binaries = ArrayList<File>(libraries.size)
val binaries = ArrayList<File>(allDependencies.regularDependencies.size)
for ((name, code) in compilationOutputs.dependencies) {
val depBinary = binariesDir.binJsFile(name)
@@ -154,14 +154,15 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() {
private fun buildBinaryWithIC(
configuration: CompilerConfiguration,
mainModuleKlibFile: File,
libraries: Collection<File>
allDependencies: KlibABITestUtils.Dependencies
): CompilationOutputs {
fun cacheDir(library: File): File = buildDir.resolve("libs-cache").resolve(library.name).apply { mkdirs() }
// TODO: what about friend dependencies?
val cacheUpdater = CacheUpdater(
mainModule = mainModuleKlibFile.absolutePath,
allModules = libraries.map { it.absolutePath },
icCachePaths = libraries.map { cacheDir(it).absolutePath },
allModules = allDependencies.regularDependencies.map { it.path },
icCachePaths = allDependencies.regularDependencies.map { cacheDir(it).path },
compilerConfiguration = configuration,
irFactory = { IrFactoryImplForJsIC(WholeWorldStageController()) },
mainArguments = null,
@@ -186,10 +187,16 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() {
private fun buildBinaryNoIC(
configuration: CompilerConfiguration,
mainModuleKlibFile: File,
libraries: Collection<File>
allDependencies: KlibABITestUtils.Dependencies
): CompilationOutputs {
val klib = MainModule.Klib(mainModuleKlibFile.path)
val moduleStructure = ModulesStructure(environment.project, klib, configuration, libraries.map { it.path }, emptyList())
val moduleStructure = ModulesStructure(
environment.project,
klib,
configuration,
allDependencies.regularDependencies.map { it.path },
allDependencies.friendDependencies.map { it.path }
)
val ir = compile(
moduleStructure,
@@ -23,18 +23,18 @@ import java.io.File
@Tag("klib-abi")
abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
private val producedKlibs = linkedMapOf<KLIB, Collection<File>>() // IMPORTANT: The order makes sense!
private val producedKlibs = linkedMapOf<KLIB, KlibABITestUtils.Dependencies>() // IMPORTANT: The order makes sense!
private inner class NativeTestConfiguration(testPath: String) : KlibABITestUtils.TestConfiguration {
override val testDir = getAbsoluteFile(testPath)
override val buildDir get() = this@AbstractNativeKlibABITest.buildDir
override val stdlibFile get() = this@AbstractNativeKlibABITest.stdlibFile
override fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File) =
this@AbstractNativeKlibABITest.buildKlib(moduleName, moduleSourceDir, moduleDependencies, klibFile)
override fun buildKlib(moduleName: String, moduleSourceDir: File, dependencies: KlibABITestUtils.Dependencies, klibFile: File) =
this@AbstractNativeKlibABITest.buildKlib(moduleName, moduleSourceDir, dependencies, klibFile)
override fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: Collection<File>) =
this@AbstractNativeKlibABITest.buildBinaryAndRun(allDependencies)
override fun buildBinaryAndRun(mainModuleKlibFile: File, dependencies: KlibABITestUtils.Dependencies) =
this@AbstractNativeKlibABITest.buildBinaryAndRun(dependencies)
override fun onNonEmptyBuildDirectory(directory: File) = backupDirectoryContents(directory)
@@ -44,7 +44,7 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
// The entry point to generated test classes.
protected fun runTest(@TestDataFile testPath: String) = KlibABITestUtils.runTest(NativeTestConfiguration(testPath))
private fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File) {
private fun buildKlib(moduleName: String, moduleSourceDir: File, dependencies: KlibABITestUtils.Dependencies, klibFile: File) {
val module = createModule(moduleName)
moduleSourceDir.walk()
.filter { file -> file.isFile && file.extension == "kt" }
@@ -57,16 +57,16 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
settings = testRunSettings,
freeCompilerArgs = testCase.freeCompilerArgs,
sourceModules = testCase.modules,
dependencies = createLibraryDependencies(moduleDependencies),
dependencies = createLibraryDependencies(dependencies),
expectedArtifact = klibArtifact
)
compilation.result.assertSuccess() // <-- trigger compilation
producedKlibs[klibArtifact] = moduleDependencies // Remember the artifact with its dependencies.
producedKlibs[klibArtifact] = dependencies // Remember the artifact with its dependencies.
}
private fun buildBinaryAndRun(allDependencies: Collection<File>) {
private fun buildBinaryAndRun(allDependencies: KlibABITestUtils.Dependencies) {
val cacheDependencies = if (staticCacheRequiredForEveryLibrary) {
producedKlibs.map { (klibArtifact, moduleDependencies) ->
buildCacheForKlib(moduleDependencies, klibArtifact)
@@ -102,7 +102,7 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
runExecutableAndVerify(testCase, executable) // <-- run executable and verify
}
private fun buildCacheForKlib(moduleDependencies: Collection<File>, klibArtifact: KLIB) {
private fun buildCacheForKlib(moduleDependencies: KlibABITestUtils.Dependencies, klibArtifact: KLIB) {
val compilation = StaticCacheCompilation(
settings = testRunSettings,
freeCompilerArgs = COMPILER_ARGS_FOR_STATIC_CACHE_AND_EXECUTABLE,
@@ -133,13 +133,20 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
initialize(null)
}
private fun createLibraryDependencies(klibFiles: Iterable<File>): Iterable<TestCompilationDependency<KLIB>> =
klibFiles.map { klibFile -> KLIB(klibFile).toDependency() }
private fun createLibraryDependencies(dependencies: KlibABITestUtils.Dependencies): Iterable<TestCompilationDependency<KLIB>> =
with(dependencies) {
regularDependencies.map { KLIB(it).toDependency() } + friendDependencies.map { KLIB(it).toFriendDependency() }
}
private fun createLibraryCacheDependencies(klibFiles: Iterable<File>): Iterable<TestCompilationDependency<KLIBStaticCache>> =
klibFiles.mapNotNull { klibFile -> if (klibFile != stdlibFile) KLIB(klibFile).toStaticCacheArtifact().toDependency() else null }
private fun createLibraryCacheDependencies(dependencies: KlibABITestUtils.Dependencies): Iterable<TestCompilationDependency<KLIBStaticCache>> =
with(dependencies) {
regularDependencies.mapNotNull { klibFile ->
if (klibFile != stdlibFile) KLIB(klibFile).toStaticCacheArtifact().toDependency() else null
}
}
private fun KLIB.toDependency() = ExistingDependency(this, Library)
private fun KLIB.toFriendDependency() = ExistingDependency(this, TestCompilationDependencyType.FriendLibrary)
private fun KLIBStaticCache.toDependency() = ExistingDependency(this, TestCompilationDependencyType.LibraryStaticCache)
private fun KLIB.toStaticCacheArtifact() = KLIBStaticCache(