[IR, JS] KLIB ABI tests: separate testing logic so that it can be reused in non-JS tests

^KT-50775
This commit is contained in:
Dmitriy Dolovov
2022-01-19 11:30:08 +03:00
parent d64a720749
commit 9096725d1d
2 changed files with 161 additions and 179 deletions
@@ -5,19 +5,14 @@
package org.jetbrains.kotlin.klib
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.local.CoreLocalFileSystem
import com.intellij.psi.PsiManager
import com.intellij.psi.SingleRootFileViewProvider
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.klib.AbstractKlibABITestCase.Companion.ModuleBuildDirs.Companion.OUTPUT_DIR_NAME
import org.jetbrains.kotlin.klib.AbstractKlibABITestCase.Companion.ModuleBuildDirs.Companion.SOURCE_DIR_NAME
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.junit.jupiter.api.fail
import java.io.File
import kotlin.io.path.createTempDirectory
@@ -40,157 +35,127 @@ abstract class AbstractKlibABITestCase : KtUsefulTestCase() {
buildDir.deleteRecursively()
}
private fun parseProjectInfo(testName: String, infoFile: File): ProjectInfo {
return ProjectInfoParser(infoFile).parse(testName)
}
protected abstract fun stdlibFile(): File
private fun parseModuleInfo(moduleName: String, infoFile: File): ModuleInfo {
return ModuleInfoParser(infoFile).parse(moduleName)
}
abstract fun compileBinaryAndRun(project: Project, configuration: CompilerConfiguration, libraries: Collection<String>, mainModulePath: String, buildDir: File)
abstract fun stdlibPath(): String
fun doTest(testPath: String) {
val testDir = File(testPath)
val testName = testDir.name
val projectInfoFile = File(testDir, PROJECT_INFO_FILE)
val projectInfo = parseProjectInfo(testName, projectInfoFile)
val modulesMap = mutableMapOf<String, ModuleInfo>()
for (module in projectInfo.modules) {
val moduleDir = File(testDir, module)
assert(moduleDir.exists())
val moduleInfoFile = File(moduleDir, MODULE_INFO_FILE)
modulesMap[module] = parseModuleInfo(module, moduleInfoFile)
}
val modulesBuildDirs = prepareBuildDirs(testDir, buildDir, projectInfo)
for (projectStep in projectInfo.steps) {
for (module in projectStep.order) {
val moduleTestDir = File(testDir, module)
val moduleBuildDir = modulesBuildDirs[module] ?: error("No module dir found for $module")
val moduleInfo = modulesMap[module] ?: error("No module $module found on step ${projectStep.id}")
val moduleStep = moduleInfo.steps[projectStep.id]
val moduleSourceDir = File(moduleBuildDir, SOURCE_DIR_NAME)
for (modification in moduleStep.modifications) {
modification.execute(moduleTestDir, moduleSourceDir)
}
val klibFile = moduleBuildDir.toKlibFile(module)
if (klibFile.exists()) klibFile.delete()
val dependencies = collectDependenciesKlib(buildDir, moduleStep.dependencies)
buildKlib(module, moduleSourceDir, dependencies, klibFile)
}
}
val mainModuleDir = modulesBuildDirs[MAIN_MODULE_NAME] ?: error("No main module $MAIN_MODULE_NAME found")
val moduleKlibs = collectAllModulesKlibs(modulesBuildDirs)
compileBinaryAndRun(
environment.project,
environment.configuration,
moduleKlibs,
mainModuleDir.toKlibFile(MAIN_MODULE_NAME).canonicalPath,
buildDir
)
}
private fun makeDirectoriesForModule(moduleName: String, buildDir: File): File {
val moduleDir = File(buildDir, moduleName)
File(moduleDir, SOURCE_DIR_NAME).mkdirs()
File(moduleDir, KLIB_DIR_NAME).mkdirs()
moduleDir.deleteOnExit()
return moduleDir
}
private fun prepareBuildDirs(testDir: File, buildDir: File, projectInfo: ProjectInfo): Map<String, File> {
val modulesBuildDirs = projectInfo.modules.associateWith { makeDirectoriesForModule(it, buildDir) }
for (module in projectInfo.modules) {
val moduleTestDir = File(testDir, module).also { assert(it.exists()) }
val moduleBuildDir = modulesBuildDirs[module] ?: error("No dir found for module $module")
val moduleSourceDir = File(moduleBuildDir, SOURCE_DIR_NAME)
moduleTestDir.listFiles { _, name -> name.endsWith(".kt") }!!.forEach {
val sourceFile = File(moduleSourceDir, it.name)
it.copyTo(sourceFile)
}
}
return modulesBuildDirs
}
private fun collectDependenciesKlib(buildDir: File, dependencies: Collection<String>): List<String> {
val result = ArrayList<String>(dependencies.size)
return dependencies.mapTo(result) { dep ->
if (dep == "stdlib") {
stdlibPath()
} else {
val depBuildDir = File(buildDir, dep)
depBuildDir.toKlibFile(dep).canonicalPath
}
}
}
private fun File.toKlibFile(name: String): File {
return File(File(this, KLIB_DIR_NAME), "$name.klib")
}
private fun collectAllModulesKlibs(modulesBuildDirs: Map<String, File>): Collection<String> {
val result = ArrayList<String>(modulesBuildDirs.size + 1)
result.add(stdlibPath())
for ((name, dir) in modulesBuildDirs) {
result.add(dir.toKlibFile(name).canonicalPath)
}
return result
}
private fun KotlinCoreEnvironment.createPsiFiles(sourceDir: File): Collection<KtFile> {
val psiManager = PsiManager.getInstance(project)
val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) as CoreLocalFileSystem
val sourceFile = sourceDir.listFiles()!!.first()
val vFile = fileSystem.findFileByIoFile(sourceFile) ?: error("Virtual File for $sourceFile not found")
val provider = SingleRootFileViewProvider(psiManager, vFile)
val allfiles = provider.allFiles
return allfiles.mapNotNull { it as? KtFile }
}
abstract fun buildKlibImpl(
project: Project,
configuration: CompilerConfiguration,
protected abstract fun buildKlib(
moduleName: String,
sources: Collection<KtFile>,
dependencies: Collection<String>,
outputFile: File
moduleSourceDir: File,
moduleDependencies: Collection<File>,
klibFile: File
)
private fun buildKlib(
moduleName: String,
sourceDir: File,
dependencies: List<String>,
outputFile: File
) {
val ktFiles = environment.createPsiFiles(sourceDir)
val config = environment.configuration.copy()
config.put(CommonConfigurationKeys.MODULE_NAME, moduleName)
buildKlibImpl(environment.project, config, moduleName, ktFiles, dependencies, outputFile)
}
protected abstract fun buildBinaryAndRun(
mainModuleKlibFile: File,
libraries: Collection<File>
)
fun doTest(testPath: String) = Companion.doTest(
testDir = File(testPath).absoluteFile,
buildDir = buildDir,
stdlibFile = stdlibFile(),
buildKlib = ::buildKlib,
buildBinaryAndRun = ::buildBinaryAndRun
)
companion object {
private const val SOURCE_DIR_NAME = "sources"
private const val KLIB_DIR_NAME = "klibs"
fun doTest(
testDir: File,
buildDir: File,
stdlibFile: File,
buildKlib: (moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File) -> Unit,
buildBinaryAndRun: (mainModuleKlibFile: File, allDependencies: Collection<File>) -> Unit
) {
val projectName = testDir.name
val projectInfoFile = File(testDir, PROJECT_INFO_FILE)
val projectInfo: ProjectInfo = ProjectInfoParser(projectInfoFile).parse(projectName)
val modulesMap: Map<String, ModuleUnderTest> = buildMap {
projectInfo.modules.forEach { moduleName ->
val moduleTestDir = File(testDir, moduleName)
assertExists(moduleTestDir)
val moduleInfoFile = File(moduleTestDir, MODULE_INFO_FILE)
val moduleInfo = ModuleInfoParser(moduleInfoFile).parse(moduleName)
val moduleBuildDirs = createModuleDirs(buildDir, moduleName)
// Populate the source dir with *.kt files.
val moduleSourceDir = moduleBuildDirs.sourceDir.apply { mkdirs() }
moduleTestDir.walk().filter { it.isFile && it.extension == "kt" }.forEach { sourceFile ->
val destFile = moduleSourceDir.resolve(sourceFile.relativeTo(moduleTestDir))
destFile.parentFile.mkdirs()
sourceFile.copyTo(destFile)
}
val moduleOutputDir = moduleBuildDirs.outputDir.apply { mkdirs() }
val klibFile = moduleOutputDir.resolve("$moduleName.klib")
this[moduleName] = ModuleUnderTest(
info = moduleInfo,
testDir = moduleTestDir,
buildDirs = moduleBuildDirs,
klibFile = klibFile
)
}
}
projectInfo.steps.forEach { projectStep ->
projectStep.order.forEach { moduleName ->
val (moduleInfo, moduleTestDir, moduleBuildDirs, klibFile) = modulesMap[moduleName]
?: fail { "No module $moduleName found on step ${projectStep.id}" }
val moduleStep = moduleInfo.steps[projectStep.id]
moduleStep.modifications.forEach { modification ->
modification.execute(moduleTestDir, moduleBuildDirs.sourceDir)
}
if (klibFile.exists()) klibFile.delete() // TODO: maybe backup?
val moduleDependencies = moduleStep.dependencies.map { dependencyName ->
if (dependencyName == "stdlib")
stdlibFile
else
modulesMap[dependencyName]?.klibFile ?: fail { "No module $dependencyName found on step ${projectStep.id}" }
}
buildKlib(moduleInfo.moduleName, moduleBuildDirs.sourceDir, moduleDependencies, 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 }
}
buildBinaryAndRun(mainModuleKlibFile, allKlibs)
}
fun createModuleDirs(buildDir: File, moduleName: String): ModuleBuildDirs {
val moduleBuildDir = buildDir.resolve(moduleName)
val moduleSourceDir = moduleBuildDir.resolve(SOURCE_DIR_NAME).apply { mkdirs() }
val moduleOutputDir = moduleBuildDir.resolve(OUTPUT_DIR_NAME).apply { mkdirs() }
return ModuleBuildDirs(moduleSourceDir, moduleOutputDir)
}
data class ModuleBuildDirs(val sourceDir: File, val outputDir: File) {
internal companion object {
const val SOURCE_DIR_NAME = "sources"
const val OUTPUT_DIR_NAME = "outputs"
}
}
private data class ModuleUnderTest(
val info: ModuleInfo,
val testDir: File,
val buildDirs: ModuleBuildDirs,
val klibFile: File
)
const val MAIN_MODULE_NAME = "main"
}
}
@@ -5,11 +5,15 @@
package org.jetbrains.kotlin.js.test
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.local.CoreLocalFileSystem
import com.intellij.psi.PsiManager
import com.intellij.psi.SingleRootFileViewProvider
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformerTmp
@@ -25,14 +29,37 @@ import org.jetbrains.kotlin.serialization.js.ModuleKind
import java.io.File
abstract class AbstractJsKLibABITestCase : AbstractKlibABITestCase() {
override fun stdlibFile(): File = File("libraries/stdlib/js-ir/build/classes/kotlin/js/main").absoluteFile
override fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File) {
val ktFiles = environment.createPsiFiles(moduleSourceDir)
val config = environment.configuration.copy()
config.put(CommonConfigurationKeys.MODULE_NAME, moduleName)
val sourceModule = prepareAnalyzedSourceModule(
environment.project,
ktFiles,
config,
moduleDependencies.map { it.path },
emptyList(), // TODO
AnalyzerWithCompilerReport(config)
)
generateKLib(sourceModule, IrFactoryImpl, klibFile.path, nopack = false, jsOutputName = moduleName)
}
override fun buildBinaryAndRun(mainModuleKlibFile: File, libraries: Collection<File>) {
val project = environment.project
val configuration = environment.configuration
override fun compileBinaryAndRun(project: Project, configuration: CompilerConfiguration, libraries: Collection<String>, mainModulePath: String, buildDir: File) {
configuration.put(JSConfigurationKeys.PARTIAL_LINKAGE, true)
configuration.put(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN)
configuration.put(JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION, true)
configuration.put(CommonConfigurationKeys.MODULE_NAME, MAIN_MODULE_NAME)
val kLib = MainModule.Klib(mainModulePath)
val moduleStructure = ModulesStructure(project, kLib, configuration, libraries, emptyList())
val kLib = MainModule.Klib(mainModuleKlibFile.path)
val moduleStructure = ModulesStructure(project, kLib, configuration, libraries.map { it.path }, emptyList())
val ir = compile(
moduleStructure,
@@ -71,6 +98,18 @@ abstract class AbstractJsKLibABITestCase : AbstractKlibABITestCase() {
executeAndCheckBinaries(MAIN_MODULE_NAME, binaries)
}
private fun KotlinCoreEnvironment.createPsiFiles(sourceDir: File): List<KtFile> {
val psiManager = PsiManager.getInstance(project)
val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) as CoreLocalFileSystem
val sourceFile = sourceDir.listFiles()!!.first()
val vFile = fileSystem.findFileByIoFile(sourceFile) ?: error("Virtual File for $sourceFile not found")
val provider = SingleRootFileViewProvider(psiManager, vFile)
val allfiles = provider.allFiles
return allfiles.mapNotNull { it as? KtFile }
}
private fun File.binJsFile(name: String): File = File(this, "$name.js")
private fun executeAndCheckBinaries(mainModuleName: String, dependencies: Collection<File>) {
@@ -80,28 +119,6 @@ abstract class AbstractJsKLibABITestCase : AbstractKlibABITestCase() {
checker.check(filePaths, mainModuleName, null, "box", "OK", withModuleSystem = false)
}
override fun buildKlibImpl(
project: Project,
configuration: CompilerConfiguration,
moduleName: String,
sources: Collection<KtFile>,
dependencies: Collection<String>,
outputFile: File
) {
val sourceModule = prepareAnalyzedSourceModule(
project,
sources.toList(),
configuration,
dependencies.toList(),
emptyList(), // TODO
AnalyzerWithCompilerReport(configuration)
)
generateKLib(sourceModule, IrFactoryImpl, outputFile.canonicalPath, nopack = false, jsOutputName = moduleName)
}
override fun stdlibPath(): String = "libraries/stdlib/js-ir/build/classes/kotlin/js/main"
companion object {
private const val BIN_DIR_NAME = "_bins_js"
}