diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/klib/AbstractKlibABITestCase.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/klib/AbstractKlibABITestCase.kt new file mode 100644 index 00000000000..5e7b4bf3f36 --- /dev/null +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/klib/AbstractKlibABITestCase.kt @@ -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, 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() + + 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 { + 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): List { + val result = ArrayList(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 + ) { + 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): Collection { + val result = ArrayList(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 { + 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, + dependencies: Collection, + outputFile: File + ) + + private fun buildKlib( + environment: KotlinCoreEnvironment, + moduleName: String, + sourceDir: File, + dependencies: List, + 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" + } + + +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsKLibABITestCase.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsKLibABITestCase.kt new file mode 100644 index 00000000000..03d7f7ca3e2 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsKLibABITestCase.kt @@ -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, 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(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) { + 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, + dependencies: Collection, + 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" + } +} \ No newline at end of file