[TEST] Add Klib ABI test runner logic

- common and js-specific classes
This commit is contained in:
Roman Artemev
2021-12-06 19:20:39 +03:00
committed by TeamCityServer
parent 4ef316a590
commit c3c2bb5884
2 changed files with 312 additions and 0 deletions
@@ -0,0 +1,202 @@
/*
* 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.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.test.testFramework.KtUsefulTestCase
import java.io.File
import kotlin.io.path.createTempDirectory
abstract class AbstractKlibABITestCase : KtUsefulTestCase() {
private fun parseProjectInfo(testName: String, infoFile: File): ProjectInfo {
return ProjectInfoParser(infoFile).parse(testName)
}
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 disposable = TestDisposable()
val environment = createEnvironment(disposable)
val buildDir = createTempDirectory().toFile().also { it.mkdirs() }
try {
doTestImpl(environment, testDir, buildDir, projectInfo, modulesMap)
} finally {
buildDir.deleteRecursively()
disposable.dispose()
}
}
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 createEnvironment(disposable: TestDisposable): KotlinCoreEnvironment {
return KotlinCoreEnvironment.createForTests(disposable, CompilerConfiguration(), EnvironmentConfigFiles.JS_CONFIG_FILES)
}
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 doTestImpl(
environment: KotlinCoreEnvironment,
testDir: File,
buildDir: File,
projectInfo: ProjectInfo,
modulesInfo: Map<String, ModuleInfo>
) {
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 = modulesInfo[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(environment, 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 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,
moduleName: String,
sources: Collection<KtFile>,
dependencies: Collection<String>,
outputFile: File
)
private fun buildKlib(
environment: KotlinCoreEnvironment,
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)
}
companion object {
private const val SOURCE_DIR_NAME = "sources"
private const val KLIB_DIR_NAME = "klibs"
const val MAIN_MODULE_NAME = "main"
}
}
@@ -0,0 +1,110 @@
/*
* 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.js.test
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
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
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.testOld.V8IrJsTestChecker
import org.jetbrains.kotlin.klib.AbstractKlibABITestCase
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.serialization.js.ModuleKind
import java.io.File
abstract class AbstractJsKLibABITestCase : AbstractKlibABITestCase() {
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(CommonConfigurationKeys.MODULE_NAME, MAIN_MODULE_NAME)
val kLib = MainModule.Klib(mainModulePath)
val moduleStructure = ModulesStructure(project, kLib, configuration, libraries, emptyList(), false, false, emptyMap())
val ir = compile(
moduleStructure,
PhaseConfig(jsPhases),
IrFactoryImplForJsIC(WholeWorldStageController()),
propertyLazyInitialization = true,
granularity = JsGenerationGranularity.PER_MODULE,
exportedDeclarations = setOf(FqName("box")),
icCompatibleIr2Js = true
)
val transformer = IrModuleToJsTransformerTmp(
ir.context,
emptyList(),
fullJs = true,
dceJs = true,
multiModule = true,
relativeRequirePath = false
)
val compiledResult = transformer.generateModule(ir.allModules)
val dceOutput = compiledResult.outputsAfterDce ?: error("No DCE output")
val binariesDir = File(buildDir, BIN_DIR_NAME).also { it.mkdirs() }
val binaries = ArrayList<File>(libraries.size)
for ((name, code) in dceOutput.dependencies) {
val depBinary = binariesDir.binJsFile(name)
depBinary.parentFile?.let { if (!it.exists()) it.mkdirs() }
depBinary.writeText(code.jsCode)
binaries.add(depBinary)
}
val mainBinary = binariesDir.binJsFile(MAIN_MODULE_NAME)
mainBinary.writeText(dceOutput.jsCode)
binaries.add(mainBinary)
executeAndCheckBinaries(MAIN_MODULE_NAME, binaries)
}
private fun File.binJsFile(name: String): File = File(this, "$name.js")
private fun executeAndCheckBinaries(mainModuleName: String, dependencies: Collection<File>) {
val checker = V8IrJsTestChecker
val filePaths = dependencies.map { it.canonicalPath }
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"
}
}