diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsLineNumberTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsLineNumberTest.kt index d2c93ed8c98..21f98667e50 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsLineNumberTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsLineNumberTest.kt @@ -35,6 +35,7 @@ import org.jetbrains.kotlin.test.Directives import org.jetbrains.kotlin.test.KotlinBaseTest import org.jetbrains.kotlin.test.KotlinTestWithEnvironment import org.jetbrains.kotlin.test.TestFiles +import org.jetbrains.kotlin.test.services.ModuleStructureExtractor import org.jetbrains.kotlin.test.util.KtTestUtil import org.jetbrains.kotlin.utils.DFS import java.io.ByteArrayOutputStream @@ -140,7 +141,7 @@ abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() { private inner class TestFileFactoryImpl : TestFiles.TestFileFactory, Closeable { private val tmpDir = KtTestUtil.tmpDir("js-tests") - private val defaultModule = TestModule(BasicBoxTest.TEST_MODULE, emptyList(), emptyList()) + private val defaultModule = TestModule(ModuleStructureExtractor.DEFAULT_MODULE_NAME, emptyList(), emptyList()) override fun createFile(module: TestModule?, fileName: String, text: String, directives: Directives): TestFile? { val currentModule = module ?: defaultModule @@ -177,7 +178,7 @@ abstract class AbstractJsLineNumberTest : KotlinTestWithEnvironment() { companion object { private val DIR_NAME = "lineNumbers" private val LINES_PATTERN = Regex("^ *// *LINES: *(.*)$", RegexOption.MULTILINE) - private val BASE_PATH = "${BasicBoxTest.TEST_DATA_DIR_PATH}/$DIR_NAME" - private val OUT_PATH = "${BasicBoxTest.TEST_DATA_DIR_PATH}/out/$DIR_NAME" + private val BASE_PATH = "${BasicWasmBoxTest.TEST_DATA_DIR_PATH}/$DIR_NAME" + private val OUT_PATH = "${BasicWasmBoxTest.TEST_DATA_DIR_PATH}/out/$DIR_NAME" } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt deleted file mode 100644 index 4dffebdc9f7..00000000000 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ /dev/null @@ -1,1072 +0,0 @@ -/* - * 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.util.io.FileUtil -import com.intellij.openapi.util.text.StringUtil -import com.intellij.openapi.vfs.StandardFileSystems -import com.intellij.openapi.vfs.VirtualFileManager -import com.intellij.psi.PsiManager -import junit.framework.TestCase -import org.jetbrains.kotlin.checkers.CompilerTestLanguageVersionSettings -import org.jetbrains.kotlin.checkers.parseLanguageVersionSettings -import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants -import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport -import org.jetbrains.kotlin.cli.common.messages.MessageRenderer -import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector -import org.jetbrains.kotlin.cli.common.output.writeAllTo -import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles -import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment -import org.jetbrains.kotlin.config.* -import org.jetbrains.kotlin.idea.KotlinFileType -import org.jetbrains.kotlin.incremental.js.IncrementalDataProviderImpl -import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumerImpl -import org.jetbrains.kotlin.incremental.js.TranslationResultValue -import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity -import org.jetbrains.kotlin.ir.backend.js.ic.ICCache -import org.jetbrains.kotlin.js.JavaScript -import org.jetbrains.kotlin.js.config.* -import org.jetbrains.kotlin.js.facade.K2JSTranslator -import org.jetbrains.kotlin.js.facade.MainCallParameters -import org.jetbrains.kotlin.js.facade.TranslationResult -import org.jetbrains.kotlin.js.facade.TranslationUnit -import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapParser -import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapSuccess -import org.jetbrains.kotlin.js.test.utils.ExceptionThrowingReporter -import org.jetbrains.kotlin.js.testNew.* -import org.jetbrains.kotlin.js.testNew.handlers.JsAstHandler -import org.jetbrains.kotlin.js.testNew.handlers.JsMinifierRunner -import org.jetbrains.kotlin.js.testNew.handlers.JsSourceMapHandler -import org.jetbrains.kotlin.js.testNew.handlers.NodeJsGeneratorHandler -import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver -import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder -import org.jetbrains.kotlin.js.test.engines.ExternalTool -import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilderConsumer -import org.jetbrains.kotlin.js.test.utils.* -import org.jetbrains.kotlin.js.util.TextOutputImpl -import org.jetbrains.kotlin.library.KotlinAbiVersion -import org.jetbrains.kotlin.metadata.DebugProtoBuf -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.psi.KtPsiFactory -import org.jetbrains.kotlin.resolve.CompilerEnvironment -import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor -import org.jetbrains.kotlin.serialization.js.JsSerializerProtocol -import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil -import org.jetbrains.kotlin.serialization.js.ModuleKind -import org.jetbrains.kotlin.test.* -import org.jetbrains.kotlin.test.util.KtTestUtil -import org.jetbrains.kotlin.utils.DFS -import org.jetbrains.kotlin.utils.JsMetadataVersion -import org.jetbrains.kotlin.utils.KotlinJavascriptMetadata -import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils -import org.junit.Assert -import java.io.ByteArrayOutputStream -import java.io.Closeable -import java.io.File -import java.io.PrintStream -import java.lang.Boolean.getBoolean -import java.nio.charset.Charset -import java.util.regex.Pattern - -abstract class BasicBoxTest( - protected val pathToTestDir: String, - testGroupOutputDirPrefix: String, - private val typedArraysEnabled: Boolean = true, - private val generateSourceMap: Boolean = false, - private val generateNodeJsRunner: Boolean = true, - private val targetBackend: TargetBackend = TargetBackend.JS -) : KotlinTestWithEnvironment() { - private val pathToRootOutputDir = System.getProperty("kotlin.js.test.root.out.dir") ?: error("'kotlin.js.test.root.out.dir' is not set") - private val testGroupOutputDirForCompilation = File(pathToRootOutputDir + "out/" + testGroupOutputDirPrefix) - private val testGroupOutputDirForMinification = File(pathToRootOutputDir + "out-min/" + testGroupOutputDirPrefix) - - protected open fun getOutputPrefixFile(testFilePath: String): File? = null - protected open fun getOutputPostfixFile(testFilePath: String): File? = null - - protected open val runMinifierByDefault: Boolean = false - protected open val skipMinification = getBoolean("kotlin.js.skipMinificationTest") - - protected open val skipRegularMode: Boolean = false - protected open val runIrDce: Boolean = false - - protected open val incrementalCompilationChecksEnabled = true - - protected open val testChecker get() = if (runTestInNashorn) NashornJsTestChecker else V8JsTestChecker - - protected val logger = KotlinJsTestLogger() - - - fun doTest(filePath: String) { - doTestWithIgnoringByFailFile(filePath) - } - - fun doTestWithIgnoringByFailFile(filePath: String) { - val failFile = File("$filePath.fail") - try { - doTest(filePath, "OK", MainCallParameters.noCall()) - } catch (e: Throwable) { - if (failFile.exists()) { - KotlinTestUtils.assertEqualsToFile(failFile, e.message ?: "") - } else { - throw e - } - } - } - - open fun doTest(filePath: String, expectedResult: String, mainCallParameters: MainCallParameters) { - val file = File(filePath) - - @Suppress("NAME_SHADOWING") - val filePath = file.absolutePath - - logger.logFile("Test file", file) - - val outputDir = getOutputDir(file) - val dceOutputDir = getOutputDir(file, testGroupOutputDirForMinification) - val fileContent = KtTestUtil.doLoadFile(file) - - val needsFullIrRuntime = KJS_WITH_FULL_RUNTIME.matcher(fileContent).find() || WITH_RUNTIME.matcher(fileContent).find() || WITH_STDLIB.matcher(fileContent).find() - - - val actualMainCallParameters = if (CALL_MAIN_PATTERN.matcher(fileContent).find()) - MainCallParameters.mainWithArguments(listOf()) - else - mainCallParameters - - val outputPrefixFile = getOutputPrefixFile(filePath) - val outputPostfixFile = getOutputPostfixFile(filePath) - - val runPlainBoxFunction = RUN_PLAIN_BOX_FUNCTION.matcher(fileContent).find() - val inferMainModule = INFER_MAIN_MODULE.matcher(fileContent).find() - val expectActualLinker = EXPECT_ACTUAL_LINKER.matcher(fileContent).find() - val errorPolicyMatcher = ERROR_POLICY_PATTERN.matcher(fileContent) - val errorPolicy = - if (errorPolicyMatcher.find()) ErrorTolerancePolicy.resolvePolicy(errorPolicyMatcher.group(1)) else ErrorTolerancePolicy.DEFAULT - - val skipDceDriven = SKIP_DCE_DRIVEN.matcher(fileContent).find() - val esModules = ES_MODULES.matcher(fileContent).find() - - val splitPerModule = SPLIT_PER_MODULE.matcher(fileContent).find() - val splitPerFile = SPLIT_PER_FILE.matcher(fileContent).find() - - val granularity = when { - splitPerModule -> JsGenerationGranularity.PER_MODULE - splitPerFile -> JsGenerationGranularity.PER_FILE - else -> JsGenerationGranularity.WHOLE_PROGRAM - } - - val skipMangleVerification = SKIP_MANGLE_VERIFICATION.matcher(fileContent).find() - - val safeExternalBoolean = SAFE_EXTERNAL_BOOLEAN.matcher(fileContent).find() - val safeExternalBooleanDiagnosticMatcher = SAFE_EXTERNAL_BOOLEAN_DIAGNOSTIC.matcher(fileContent) - - val safeExternalBooleanDiagnostic = - if (safeExternalBooleanDiagnosticMatcher.find()) - RuntimeDiagnostic.resolve(safeExternalBooleanDiagnosticMatcher.group(1)) - else null - - val skipIcChecks = targetBackend == TargetBackend.JS_IR && SKIP_IR_INCREMENTAL_CHECKS.matcher(fileContent).find() - - TestFileFactoryImpl().use { testFactory -> - val inputFiles = TestFiles.createTestFiles( - file.name, - fileContent, - testFactory, - true - ) - val modules = inputFiles - .map { it.module }.distinct() - .map { it.name to it }.toMap() - - fun TestModule.allTransitiveDependencies(): Set { - return dependenciesSymbols.toSet() + dependenciesSymbols.flatMap { modules[it]!!.allTransitiveDependencies() } - } - - val checkIC = !skipIcChecks && modules.any { it.value.hasFilesToRecompile } - - val orderedModules = DFS.topologicalOrder(modules.values) { module -> module.dependenciesSymbols.mapNotNull { modules[it] } } - - val testPackage = if (runPlainBoxFunction) null else testFactory.testPackage - - val testFunction = TEST_FUNCTION - - val mainModuleName = when { - inferMainModule -> orderedModules.last().name - TEST_MODULE in modules -> TEST_MODULE - else -> DEFAULT_MODULE - } - val testModuleName = if (runPlainBoxFunction) null else mainModuleName - val mainModule = modules[mainModuleName] ?: error("No module with name \"$mainModuleName\"") - val icCache = mutableMapOf() - - if (esModules) { - modules.forEach { _, m -> m.moduleKind = ModuleKind.ES } - } - - val customTestModule: String? = inputFiles.find { it.testEntryEsModule }?.let { File(it.fileName).readText() } - - val generatedJsFiles = orderedModules.asReversed().mapNotNull { module -> - val dependencies = module.dependenciesSymbols.map { modules[it]?.outputFileName(outputDir) + ".meta.js" } - val allDependencies = module.allTransitiveDependencies().map { modules[it]?.outputFileName(outputDir) + ".meta.js" } - val friends = module.friendsSymbols.map { modules[it]?.outputFileName(outputDir) + ".meta.js" } - - val outputFileName = module.outputFileName(outputDir) + ".js" - val dceOutputFileName = module.outputFileName(dceOutputDir) + ".js" - val abiVersion = module.abiVersion - val isMainModule = mainModuleName == module.name - - logger.logFile("Output JS", File(outputFileName)) - generateJavaScriptFile( - testFactory.tmpDir, - file.parent, - module, - outputFileName, - dceOutputFileName, - dependencies, - allDependencies, - friends, - modules.size > 1, - !SKIP_SOURCEMAP_REMAPPING.matcher(fileContent).find(), - outputPrefixFile, - outputPostfixFile, - actualMainCallParameters, - testPackage, - testFunction, - needsFullIrRuntime, - isMainModule, - expectActualLinker, - skipDceDriven, - esModules, - granularity, - errorPolicy, - propertyLazyInitialization = true, - safeExternalBoolean, - safeExternalBooleanDiagnostic, - skipMangleVerification, - abiVersion, - checkIC, - icCache, - customTestModule, - ) - - when { - module.name.endsWith(OLD_MODULE_SUFFIX) -> null - // JS_IR generates single js file for all modules (apart from runtime). - // TODO: Split and refactor test runner for JS_IR - targetBackend == TargetBackend.JS_IR && !isMainModule -> null - else -> Pair(outputFileName, module) - } - } - - val commonFiles = JsAdditionalSourceProvider.getAdditionalJsFiles(file.parent).map { it.absolutePath } - val inputJsFilesBefore = mutableListOf() - val inputJsFilesAfter = mutableListOf() - - fun copyInputJsFile(inputJsFile: TestFile): String { - val sourceFile = File(inputJsFile.fileName) - val targetFile = File(outputDir, inputJsFile.module.outputFileSimpleName() + "-js-" + sourceFile.name) - FileUtil.copy(File(inputJsFile.fileName), targetFile) - return targetFile.absolutePath - } - - inputFiles.forEach { - val name = it.fileName - when { - name.endsWith("__after.js") -> - inputJsFilesAfter += copyInputJsFile(it) - - name.endsWith(".js") -> - inputJsFilesBefore += copyInputJsFile(it) - } - } - - val additionalFiles = mutableListOf() - val moduleEmulationFiles = mutableListOf() - - val moduleKindMatcher = MODULE_KIND_PATTERN.matcher(fileContent) - val moduleKind = if (moduleKindMatcher.find()) ModuleKind.valueOf(moduleKindMatcher.group(1)) else ModuleKind.PLAIN - - val withModuleSystem = moduleKind != ModuleKind.PLAIN && !NO_MODULE_SYSTEM_PATTERN.matcher(fileContent).find() - - if (withModuleSystem) { - moduleEmulationFiles += File(MODULE_EMULATION_FILE).absolutePath - } - - val additionalJsFile = filePath.removeSuffix("." + KotlinFileType.EXTENSION) + JavaScript.DOT_EXTENSION - if (File(additionalJsFile).exists()) { - additionalFiles += additionalJsFile - } - - if (esModules) { - val additionalMainJsFile = - (filePath.removeSuffix("." + KotlinFileType.EXTENSION) + "__main.js").takeIf { File(it).exists() } - - val maybeAdditionalMjsFile: String? = - (filePath.removeSuffix("." + KotlinFileType.EXTENSION) + ".mjs") - .takeIf { File(it).exists() } - - val allMjsFiles: List = - inputFiles.filter { it.fileName.endsWith(".mjs") }.map { it.fileName } + listOfNotNull(maybeAdditionalMjsFile) - - val allNonEsModuleFiles: List = - additionalFiles + inputJsFilesBefore + commonFiles - - fun runIrEsmTests(testOutputDir: File) { - val esmOutputDir = testOutputDir.esModulesSubDir - - // Copy __main file if present - if (additionalMainJsFile != null) { - val newFileName = File(esmOutputDir, "test.mjs") - newFileName.delete() - File(additionalMainJsFile).copyTo(newFileName) - } - - // Copy all .mjs files into generated directory - allMjsFiles.forEach { mjsFile -> - val outFile = File(esmOutputDir, File(mjsFile).name) - File(mjsFile).copyTo(outFile) - } - - val perFileEsModuleFile = "$esmOutputDir/test.mjs" - v8tool.run(*allNonEsModuleFiles.toTypedArray(), perFileEsModuleFile, *inputJsFilesAfter.toTypedArray()) - } - - fun File.getTestDir(): File = - File(generatedJsFiles.single().first.replace(outputDir.absolutePath, this.absolutePath)) - - if (!skipRegularMode) { - runIrEsmTests(outputDir.getTestDir()) - - if (runIrDce) { - runIrEsmTests(dceOutputDir.getTestDir()) - } - } - } - - if (esModules) - return - - // Old systems tests - - additionalFiles.addAll(0, moduleEmulationFiles) - - val additionalMainFiles = mutableListOf() - val additionalMainJsFile = filePath.removeSuffix("." + KotlinFileType.EXTENSION) + "__main.js" - if (File(additionalMainJsFile).exists()) { - additionalMainFiles += additionalMainJsFile - } - - val allJsFiles = additionalFiles + inputJsFilesBefore + generatedJsFiles.map { it.first } + commonFiles + - additionalMainFiles + inputJsFilesAfter - - val dceAllJsFiles = additionalFiles + inputJsFilesBefore + generatedJsFiles.map { - it.first.replace( - outputDir.absolutePath, - dceOutputDir.absolutePath - ) - } + commonFiles + additionalMainFiles + inputJsFilesAfter - - val dontRunGeneratedCode = InTextDirectivesUtils.dontRunGeneratedCode(targetBackend, file) - if (!dontRunGeneratedCode && generateNodeJsRunner && !SKIP_NODE_JS.matcher(fileContent).find()) { - val nodeRunnerName = mainModule.outputFileName(outputDir) + ".node.js" - val ignored = InTextDirectivesUtils.isIgnoredTarget(TargetBackend.JS, file) - val nodeRunnerText = NodeJsGeneratorHandler.generateNodeRunner(allJsFiles, outputDir, mainModuleName, ignored, testPackage) - FileUtil.writeToFile(File(nodeRunnerName), nodeRunnerText) - } - - if (!dontRunGeneratedCode) { - if (!skipRegularMode) { - runGeneratedCode(allJsFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem) - - if (runIrDce && !checkIC) { - runGeneratedCode(dceAllJsFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem) - } - } - } - - performAdditionalChecks(generatedJsFiles.map { it.first }, outputPrefixFile, outputPostfixFile) - performAdditionalChecks(file, File(mainModule.outputFileName(outputDir))) - val expectedReachableNodesMatcher = EXPECTED_REACHABLE_NODES.matcher(fileContent) - val expectedReachableNodesFound = expectedReachableNodesMatcher.find() - - if (!dontRunGeneratedCode && !skipMinification && - (runMinifierByDefault || expectedReachableNodesFound) && - !SKIP_MINIFICATION.matcher(fileContent).find() - ) { - val outputDirForMinification = getOutputDir(file, testGroupOutputDirForMinification) - - JsMinifierRunner.minifyAndRun( - file = file, - expectedReachableNodes = expectedReachableNodesMatcher.group(1).toInt(), - workDir = File(outputDirForMinification, file.nameWithoutExtension), - allJsFiles = allJsFiles, - generatedJsFiles = generatedJsFiles.map { it.first to it.second.name }, - expectedResult = expectedResult, - testModuleName = testModuleName, - testPackage = testPackage, - testFunction = testFunction, - withModuleSystem = withModuleSystem, - ) { expect, actual -> TestCase.assertEquals(expect, actual) } - } - } - } - - protected open fun runGeneratedCode( - jsFiles: List, - testModuleName: String?, - testPackage: String?, - testFunction: String, - expectedResult: String, - withModuleSystem: Boolean - ) { - testChecker.check(jsFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem) - } - - protected open fun performAdditionalChecks(generatedJsFiles: List, outputPrefixFile: File?, outputPostfixFile: File?) {} - protected open fun performAdditionalChecks(inputFile: File, outputFile: File) {} - - protected fun getOutputDir(file: File, testGroupOutputDir: File = testGroupOutputDirForCompilation): File { - val stopFile = File(pathToTestDir) - return generateSequence(file.parentFile) { it.parentFile } - .takeWhile { it != stopFile } - .map { it.name } - .toList().asReversed() - .fold(testGroupOutputDir, ::File) - } - - private fun TestModule.outputFileSimpleName(): String { - val outputFileSuffix = if (this.name == TEST_MODULE) "" else "-$name" - return getTestName(true) + outputFileSuffix - } - - private fun TestModule.outputFileName(directory: File) = directory.absolutePath + "/" + outputFileSimpleName() + "_v5" - - private fun generateJavaScriptFile( - tmpDir: File, - directory: String, - module: TestModule, - outputFileName: String, - dceOutputFileName: String, - dependencies: List, - allDependencies: List, - friends: List, - multiModule: Boolean, - remap: Boolean, - outputPrefixFile: File?, - outputPostfixFile: File?, - mainCallParameters: MainCallParameters, - testPackage: String?, - testFunction: String, - needsFullIrRuntime: Boolean, - isMainModule: Boolean, - expectActualLinker: Boolean, - skipDceDriven: Boolean, - esModules: Boolean, - granularity: JsGenerationGranularity, - errorIgnorancePolicy: ErrorTolerancePolicy, - propertyLazyInitialization: Boolean, - safeExternalBoolean: Boolean, - safeExternalBooleanDiagnostic: RuntimeDiagnostic?, - skipMangleVerification: Boolean, - abiVersion: KotlinAbiVersion, - checkIC: Boolean, - icCache: MutableMap, - customTestModule: String?, - ) { - val kotlinFiles = module.files.filter { it.fileName.endsWith(".kt") } - val testFiles = kotlinFiles.map { it.fileName } - val additionalFiles = JsAdditionalSourceProvider.getAdditionalKotlinFiles(directory).map { it.absolutePath } - val allSourceFiles = (testFiles + additionalFiles).map(::File) - val psiFiles = createPsiFiles(allSourceFiles.sortedBy { it.canonicalPath }.map { it.canonicalPath }) - - val sourceDirs = (testFiles + additionalFiles).map { File(it).parent }.distinct() - val config = createConfig( - sourceDirs, - module, - dependencies, - allDependencies, - friends, - multiModule, - tmpDir, - incrementalData = null, - expectActualLinker = expectActualLinker, - errorIgnorancePolicy - ) - val outputFile = File(outputFileName) - val dceOutputFile = File(dceOutputFileName) - - val incrementalData = IncrementalData() - translateFiles( - psiFiles.map(TranslationUnit::SourceFile), - outputFile, - dceOutputFile, - config, - outputPrefixFile, - outputPostfixFile, - mainCallParameters, - incrementalData, - remap, - testPackage, - testFunction, - needsFullIrRuntime, - isMainModule, - skipDceDriven, - esModules, - granularity, - propertyLazyInitialization, - safeExternalBoolean, - safeExternalBooleanDiagnostic, - skipMangleVerification, - abiVersion, - checkIC, - recompile = false, - icCache, - customTestModule, - ) - - if (checkIC && incrementalCompilationChecksEnabled && module.hasFilesToRecompile) { - checkIncrementalCompilation( - sourceDirs, - module, - kotlinFiles, - dependencies, - allDependencies, - friends, - multiModule, - tmpDir, - remap, - outputFile, - outputPrefixFile, - outputPostfixFile, - mainCallParameters, - incrementalData, - testPackage, - testFunction, - needsFullIrRuntime, - expectActualLinker, - icCache - ) - } - } - - protected open fun checkIncrementalCompilation( - sourceDirs: List, - module: TestModule, - kotlinFiles: List, - dependencies: List, - allDependencies: List, - friends: List, - multiModule: Boolean, - tmpDir: File, - remap: Boolean, - outputFile: File, - outputPrefixFile: File?, - outputPostfixFile: File?, - mainCallParameters: MainCallParameters, - incrementalData: IncrementalData, - testPackage: String?, - testFunction: String, - needsFullIrRuntime: Boolean, - expectActualLinker: Boolean, - icCaches: MutableMap - ) { - val sourceToTranslationUnit = hashMapOf() - for (testFile in kotlinFiles) { - if (testFile.recompile) { - val sourceFile = File(testFile.fileName) - incrementalData.translatedFiles.remove(sourceFile) - sourceToTranslationUnit[sourceFile] = TranslationUnit.SourceFile(createPsiFile(testFile.fileName)) - incrementalData.packageMetadata.remove(testFile.packageName) - } - } - for ((sourceFile, data) in incrementalData.translatedFiles) { - sourceToTranslationUnit[sourceFile] = TranslationUnit.BinaryAst(data.binaryAst, data.inlineData) - } - val translationUnits = sourceToTranslationUnit.keys - .sortedBy { it.canonicalPath } - .map { sourceToTranslationUnit[it]!! } - - val recompiledConfig = createConfig( - sourceDirs, - module, - dependencies, - allDependencies, - friends, - multiModule, - tmpDir, - incrementalData, - expectActualLinker, - ErrorTolerancePolicy.DEFAULT - ) - val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js") - - translateFiles( - translationUnits, - recompiledOutputFile, - recompiledOutputFile, - recompiledConfig, - outputPrefixFile, - outputPostfixFile, - mainCallParameters, - incrementalData, - remap, - testPackage, - testFunction, - needsFullIrRuntime, - isMainModule = false, - skipDceDriven = true, - esModules = false, - granularity = JsGenerationGranularity.WHOLE_PROGRAM, - propertyLazyInitialization = true, - safeExternalBoolean = false, - safeExternalBooleanDiagnostic = null, - skipMangleVerification = false, - abiVersion = KotlinAbiVersion.CURRENT, - incrementalCompilation = true, - recompile = true, - mutableMapOf(), - customTestModule = null, - ) - - val originalOutput = FileUtil.loadFile(outputFile) - val recompiledOutput = removeRecompiledSuffix(FileUtil.loadFile(recompiledOutputFile)) - assertEquals("Output file changed after recompilation", originalOutput, recompiledOutput) - - val originalSourceMap = FileUtil.loadFile(File(outputFile.parentFile, outputFile.name + ".map")) - val recompiledSourceMap = - removeRecompiledSuffix(FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.name + ".map")) - ) - if (originalSourceMap != recompiledSourceMap) { - val originalSourceMapParse = SourceMapParser.parse(originalSourceMap) - val recompiledSourceMapParse = SourceMapParser.parse(recompiledSourceMap) - if (originalSourceMapParse is SourceMapSuccess && recompiledSourceMapParse is SourceMapSuccess) { - assertEquals( - "Source map file changed after recompilation", - originalSourceMapParse.toDebugString(), - recompiledSourceMapParse.toDebugString() - ) - } - assertEquals("Source map file changed after recompilation", originalSourceMap, recompiledSourceMap) - } - - if (multiModule) { - val originalMetadata = FileUtil.loadFile(File(outputFile.parentFile, outputFile.nameWithoutExtension + ".meta.js")) - val recompiledMetadata = removeRecompiledSuffix( - FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.nameWithoutExtension + ".meta.js")) - ) - assertEquals( - "Metadata file changed after recompilation", - metadataAsString(originalMetadata), - metadataAsString(recompiledMetadata) - ) - } - } - - private fun SourceMapSuccess.toDebugString(): String { - val out = ByteArrayOutputStream() - PrintStream(out).use { value.debug(it) } - return String(out.toByteArray(), Charset.forName("UTF-8")) - } - - private fun metadataAsString(metadataText: String): String { - val metadata = mutableListOf().apply { - KotlinJavascriptMetadataUtils.parseMetadata(metadataText, this) - }.single() - val metadataParts = KotlinJavascriptSerializationUtil.readModuleAsProto(metadata.body, metadata.version).body - return metadataParts.joinToString("-----\n") { - val binary = it.toByteArray() - DebugProtoBuf.PackageFragment.parseFrom(binary, JsSerializerProtocol.extensionRegistry).toString() - } - } - - private fun removeRecompiledSuffix(text: String): String = text.replace("-recompiled.js", ".js") - - class IncrementalData( - var header: ByteArray? = null, - val translatedFiles: MutableMap = hashMapOf(), - val packageMetadata: MutableMap = hashMapOf() - ) - - protected open fun translateFiles( - units: List, - outputFile: File, - dceOutputFile: File, - config: JsConfig, - outputPrefixFile: File?, - outputPostfixFile: File?, - mainCallParameters: MainCallParameters, - incrementalData: IncrementalData, - remap: Boolean, - testPackage: String?, - testFunction: String, - needsFullIrRuntime: Boolean, - isMainModule: Boolean, - skipDceDriven: Boolean, - esModules: Boolean, - granularity: JsGenerationGranularity, - propertyLazyInitialization: Boolean, - safeExternalBoolean: Boolean, - safeExternalBooleanDiagnostic: RuntimeDiagnostic?, - skipMangleVerification: Boolean, - abiVersion: KotlinAbiVersion, - incrementalCompilation: Boolean, - recompile: Boolean, - icCache: MutableMap, - customTestModule: String?, - ) { - val translator = K2JSTranslator(config, false) - val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters) - - if (translationResult !is TranslationResult.Success) { - val outputStream = ByteArrayOutputStream() - val collector = PrintingMessageCollector(PrintStream(outputStream), MessageRenderer.PLAIN_FULL_PATHS, true) - AnalyzerWithCompilerReport.reportDiagnostics(translationResult.diagnostics, collector) - val messages = outputStream.toByteArray().toString(Charset.forName("UTF-8")) - throw AssertionError("The following errors occurred compiling test:\n" + messages) - } - - val outputFiles = translationResult.getOutputFiles(outputFile, outputPrefixFile, outputPostfixFile) - val outputDir = outputFile.parentFile ?: error("Parent file for output file should not be null, outputFilePath: " + outputFile.path) - outputFiles.writeAllTo(outputDir) - - if (config.moduleKind != ModuleKind.PLAIN) { - val content = FileUtil.loadFile(outputFile, true) - val wrappedContent = wrapWithModuleEmulationMarkers(content, moduleId = config.moduleId, moduleKind = config.moduleKind) - FileUtil.writeToFile(outputFile, wrappedContent) - } - - config.configuration[JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER]?.let { - val incrementalService = it as IncrementalResultsConsumerImpl - - for ((srcFile, data) in incrementalService.packageParts) { - incrementalData.translatedFiles[srcFile] = data - } - - incrementalData.packageMetadata += incrementalService.packageMetadata - - incrementalData.header = incrementalService.headerMetadata - } - - JsAstHandler.processUnitsOfJsProgram(translationResult.program, units, targetBackend) { Assert.fail(it) } - JsSourceMapHandler.checkSourceMap(outputFile, translationResult.program, remap) { expected, actual -> - TestCase.assertEquals(expected, actual) - } - } - - protected fun wrapWithModuleEmulationMarkers( - content: String, - moduleKind: ModuleKind, - moduleId: String - ): String { - val escapedModuleId = StringUtil.escapeStringCharacters(moduleId) - - return when (moduleKind) { - - ModuleKind.COMMON_JS -> "$KOTLIN_TEST_INTERNAL.beginModule();\n" + - "$content\n" + - "$KOTLIN_TEST_INTERNAL.endModule(\"$escapedModuleId\");" - - ModuleKind.AMD, ModuleKind.UMD -> - "if (typeof $KOTLIN_TEST_INTERNAL !== \"undefined\") { " + - "$KOTLIN_TEST_INTERNAL.setModuleId(\"$escapedModuleId\"); }\n" + - "$content\n" - - ModuleKind.PLAIN -> content - - ModuleKind.ES -> error("Module emulation markers are not supported for ES modules") - } - } - - private fun createPsiFile(fileName: String): KtFile { - val psiManager = PsiManager.getInstance(project) - val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) - - val file = fileSystem.findFileByPath(fileName) ?: error("File not found: ${fileName}") - - return psiManager.findFile(file) as KtFile - } - - private fun createPsiFiles(fileNames: List): List = fileNames.map(this::createPsiFile) - - protected fun createConfig( - sourceDirs: List, - module: TestModule, - dependencies: List, - allDependencies: List, - friends: List, - multiModule: Boolean, - tmpDir: File, - incrementalData: IncrementalData?, - expectActualLinker: Boolean, - errorIgnorancePolicy: ErrorTolerancePolicy, - ): JsConfig { - val configuration = environment.configuration.copy() - - configuration.put(CommonConfigurationKeys.DISABLE_INLINE, module.inliningDisabled) - module.languageVersionSettings?.let { languageVersionSettings -> - configuration.languageVersionSettings = languageVersionSettings - } - - val libraries = when (targetBackend) { - TargetBackend.JS_IR -> dependencies - TargetBackend.JS -> JsConfig.JS_STDLIB + JsConfig.JS_KOTLIN_TEST + dependencies - else -> error("Unsupported target backend: $targetBackend") - } - - configuration.put(JSConfigurationKeys.LIBRARIES, libraries) - configuration.put(JSConfigurationKeys.TRANSITIVE_LIBRARIES, allDependencies) - configuration.put(JSConfigurationKeys.FRIEND_PATHS, friends) - - configuration.put(CommonConfigurationKeys.MODULE_NAME, module.name.removeSuffix(OLD_MODULE_SUFFIX)) - configuration.put(JSConfigurationKeys.MODULE_KIND, module.moduleKind) - configuration.put(JSConfigurationKeys.TARGET, EcmaVersion.v5) - configuration.put(JSConfigurationKeys.ERROR_TOLERANCE_POLICY, errorIgnorancePolicy) - - if (errorIgnorancePolicy.allowErrors) { - configuration.put(JSConfigurationKeys.DEVELOPER_MODE, true) - } - - val hasFilesToRecompile = module.hasFilesToRecompile - configuration.put(JSConfigurationKeys.META_INFO, multiModule) - if (hasFilesToRecompile) { - val header = incrementalData?.header - if (header != null) { - configuration.put( - JSConfigurationKeys.INCREMENTAL_DATA_PROVIDER, - IncrementalDataProviderImpl( - header, - incrementalData.translatedFiles, - JsMetadataVersion.INSTANCE.toArray(), - incrementalData.packageMetadata, - emptyMap() - ) - ) - } - - configuration.put(JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER, IncrementalResultsConsumerImpl()) - } - configuration.put(JSConfigurationKeys.SOURCE_MAP, true) - configuration.put(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, sourceDirs) - configuration.put(JSConfigurationKeys.SOURCE_MAP_EMBED_SOURCES, module.sourceMapSourceEmbedding) - - configuration.put(JSConfigurationKeys.TYPED_ARRAYS_ENABLED, typedArraysEnabled) - - configuration.put(JSConfigurationKeys.GENERATE_REGION_COMMENTS, true) - - configuration.put( - JSConfigurationKeys.FILE_PATHS_PREFIX_MAP, - mapOf( - tmpDir.absolutePath to "", - File(".").absolutePath.removeSuffix(".") to "" - ) - ) - - configuration.put(CommonConfigurationKeys.EXPECT_ACTUAL_LINKER, expectActualLinker) - - return JsConfig(project, configuration, CompilerEnvironment, METADATA_CACHE, (JsConfig.JS_STDLIB + JsConfig.JS_KOTLIN_TEST).toSet()) - } - - private inner class TestFileFactoryImpl() : TestFiles.TestFileFactory, Closeable { - var testPackage: String? = null - val tmpDir = KtTestUtil.tmpDir("js-tests") - val defaultModule = TestModule(TEST_MODULE, emptyList(), emptyList(), KotlinAbiVersion.CURRENT) - var languageVersionSettings: LanguageVersionSettings? = null - - override fun createFile(module: TestModule?, fileName: String, text: String, directives: Directives): TestFile? { - val currentModule = module ?: defaultModule - - val ktFile = KtPsiFactory(project).createFile(text) - val boxFunction = ktFile.declarations.find { it is KtNamedFunction && it.name == TEST_FUNCTION } - if (boxFunction != null) { - testPackage = ktFile.packageFqName.asString() - if (testPackage?.isEmpty() == true) { - testPackage = null - } - } - - val moduleKindMatcher = MODULE_KIND_PATTERN.matcher(text) - if (moduleKindMatcher.find()) { - currentModule.moduleKind = ModuleKind.valueOf(moduleKindMatcher.group(1)) - } - - if (NO_INLINE_PATTERN.matcher(text).find()) { - currentModule.inliningDisabled = true - } - - val temporaryFile = File(tmpDir, "${currentModule.name}/$fileName") - KtTestUtil.mkdirs(temporaryFile.parentFile) - temporaryFile.writeText(text, Charsets.UTF_8) - - // TODO Deduplicate logic copied from CodegenTestCase.updateConfigurationByDirectivesInTestFiles - fun LanguageVersion.toSettings() = CompilerTestLanguageVersionSettings( - emptyMap(), - ApiVersion.createByLanguageVersion(this), - this, - emptyMap() - ) - - fun LanguageVersionSettings.trySet() { - val old = languageVersionSettings - assert(old == null || old == this) { "Should not specify language version settings twice:\n$old\n$this" } - languageVersionSettings = this - } - InTextDirectivesUtils.findStringWithPrefixes(text, "// LANGUAGE_VERSION:")?.let { - LanguageVersion.fromVersionString(it)?.toSettings()?.trySet() - } - - parseLanguageVersionSettings(directives)?.trySet() - - // Relies on the order of module creation - // TODO is that ok? - currentModule.languageVersionSettings = languageVersionSettings - - SOURCE_MAP_SOURCE_EMBEDDING.find(text)?.let { match -> - currentModule.sourceMapSourceEmbedding = SourceMapSourceEmbedding.valueOf(match.groupValues[1]) - } - - return TestFile( - temporaryFile.absolutePath, - currentModule, - recompile = RECOMPILE_PATTERN.matcher(text).find(), - packageName = ktFile.packageFqName.asString(), - testEntryEsModule = ENTRY_ES_MODULE.matcher(text).find(), - ) - } - - override fun createModule(name: String, dependencies: List, friends: List, abiVersions: List): TestModule { - val abiVersion = if (abiVersions.isEmpty()) KotlinAbiVersion.CURRENT else { - assert(abiVersions.size == 3) - KotlinAbiVersion(abiVersions[0], abiVersions[1], abiVersions[2]) - } - return TestModule(name, dependencies, friends, abiVersion) - } - - override fun close() { - FileUtil.delete(tmpDir) - } - } - - protected class TestFile(val fileName: String, val module: TestModule, val recompile: Boolean, val packageName: String, val testEntryEsModule: Boolean) { - init { - module.files += this - } - } - - protected class TestModule( - name: String, - dependencies: List, - friends: List, - val abiVersion: KotlinAbiVersion - ) : KotlinBaseTest.TestModule(name, dependencies, friends) { - var moduleKind = ModuleKind.PLAIN - var inliningDisabled = false - val files = mutableListOf() - var languageVersionSettings: LanguageVersionSettings? = null - var sourceMapSourceEmbedding = SourceMapSourceEmbedding.NEVER - - val hasFilesToRecompile get() = files.any { it.recompile } - } - - override fun createEnvironment() = - KotlinCoreEnvironment.createForTests(testRootDisposable, CompilerConfiguration(), EnvironmentConfigFiles.JS_CONFIG_FILES) - - companion object { - val METADATA_CACHE = (JsConfig.JS_STDLIB + JsConfig.JS_KOTLIN_TEST).flatMap { path -> - KotlinJavascriptMetadataUtils.loadMetadata(path).map { metadata -> - val parts = KotlinJavascriptSerializationUtil.readModuleAsProto(metadata.body, metadata.version) - JsModuleDescriptor(metadata.moduleName, parts.kind, parts.importedModules, parts) - } - } - - const val TEST_DATA_DIR_PATH = "js/js.translator/testData/" - const val DIST_DIR_JS_PATH = "dist/js/" - - private const val MODULE_EMULATION_FILE = TEST_DATA_DIR_PATH + "/moduleEmulation.js" - - private val MODULE_KIND_PATTERN = Pattern.compile("^// *MODULE_KIND: *(.+)$", Pattern.MULTILINE) - private val NO_MODULE_SYSTEM_PATTERN = Pattern.compile("^// *NO_JS_MODULE_SYSTEM", Pattern.MULTILINE) - - // Infer main module using dependency graph - private val INFER_MAIN_MODULE = Pattern.compile("^// *INFER_MAIN_MODULE", Pattern.MULTILINE) - - // Run top level box function - private val RUN_PLAIN_BOX_FUNCTION = Pattern.compile("^// *RUN_PLAIN_BOX_FUNCTION", Pattern.MULTILINE) - - private val ENTRY_ES_MODULE = Pattern.compile("^// *ENTRY_ES_MODULE", Pattern.MULTILINE) - - private val NO_INLINE_PATTERN = Pattern.compile("^// *NO_INLINE *$", Pattern.MULTILINE) - private val SKIP_NODE_JS = Pattern.compile("^// *SKIP_NODE_JS *$", Pattern.MULTILINE) - private val SKIP_MINIFICATION = Pattern.compile("^// *SKIP_MINIFICATION *$", Pattern.MULTILINE) - private val SKIP_SOURCEMAP_REMAPPING = Pattern.compile("^// *SKIP_SOURCEMAP_REMAPPING *$", Pattern.MULTILINE) - private val EXPECTED_REACHABLE_NODES_DIRECTIVE = "EXPECTED_REACHABLE_NODES" - private val EXPECTED_REACHABLE_NODES = Pattern.compile("^// *$EXPECTED_REACHABLE_NODES_DIRECTIVE: *([0-9]+) *$", Pattern.MULTILINE) - private val RECOMPILE_PATTERN = Pattern.compile("^// *RECOMPILE *$", Pattern.MULTILINE) - private val SOURCE_MAP_SOURCE_EMBEDDING = Regex("^// *SOURCE_MAP_EMBED_SOURCES: ([A-Z]+)*\$", RegexOption.MULTILINE) - private val CALL_MAIN_PATTERN = Pattern.compile("^// *CALL_MAIN *$", Pattern.MULTILINE) - private val KJS_WITH_FULL_RUNTIME = Pattern.compile("^// *KJS_WITH_FULL_RUNTIME *\$", Pattern.MULTILINE) - private val WITH_RUNTIME = Pattern.compile("^// *WITH_RUNTIME *\$", Pattern.MULTILINE) - private val WITH_STDLIB = Pattern.compile("^// *WITH_STDLIB *\$", Pattern.MULTILINE) - private val EXPECT_ACTUAL_LINKER = Pattern.compile("^// EXPECT_ACTUAL_LINKER *$", Pattern.MULTILINE) - private val SKIP_DCE_DRIVEN = Pattern.compile("^// *SKIP_DCE_DRIVEN *$", Pattern.MULTILINE) - private val ES_MODULES = Pattern.compile("^// *ES_MODULES *$", Pattern.MULTILINE) - - private val SPLIT_PER_MODULE = Pattern.compile("^// *SPLIT_PER_MODULE *$", Pattern.MULTILINE) - private val SPLIT_PER_FILE = Pattern.compile("^// *SPLIT_PER_FILE *$", Pattern.MULTILINE) - private val SKIP_MANGLE_VERIFICATION = Pattern.compile("^// *SKIP_MANGLE_VERIFICATION *$", Pattern.MULTILINE) - - private val ERROR_POLICY_PATTERN = Pattern.compile("^// *ERROR_POLICY: *(.+)$", Pattern.MULTILINE) - - private val SAFE_EXTERNAL_BOOLEAN = Pattern.compile("^// *SAFE_EXTERNAL_BOOLEAN *$", Pattern.MULTILINE) - private val SAFE_EXTERNAL_BOOLEAN_DIAGNOSTIC = Pattern.compile("^// *SAFE_EXTERNAL_BOOLEAN_DIAGNOSTIC: *(.+)$", Pattern.MULTILINE) - - private val SKIP_IR_INCREMENTAL_CHECKS = Pattern.compile("^// *SKIP_IR_INCREMENTAL_CHECKS *$", Pattern.MULTILINE) - - @JvmStatic - protected val runTestInNashorn = getBoolean("kotlin.js.useNashorn") - - const val TEST_MODULE = "main" - const val DEFAULT_MODULE = "main" - private const val TEST_FUNCTION = "box" - private const val OLD_MODULE_SUFFIX = "_old" - - const val KOTLIN_TEST_INTERNAL = "\$kotlin_test_internal\$" - } -} - -fun KotlinTestWithEnvironment.createPsiFile(fileName: String): KtFile { - val psiManager = PsiManager.getInstance(project) - val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) - - val file = fileSystem.findFileByPath(fileName) ?: error("File not found: $fileName") - - return psiManager.findFile(file) as KtFile -} - -fun KotlinTestWithEnvironment.createPsiFiles(fileNames: List): List = fileNames.map(this::createPsiFile) - -class KotlinJsTestLogger { - val verbose = getBoolean("kotlin.js.test.verbose") - - fun logFile(description: String, file: File) { - if (verbose) { - println("TEST_LOG: $description file://${file.absolutePath}") - } - } -} - -fun RuntimeDiagnostic.Companion.resolve( - value: String?, -): RuntimeDiagnostic? = when (value?.lowercase()) { - K2JsArgumentConstants.RUNTIME_DIAGNOSTIC_LOG -> RuntimeDiagnostic.LOG - K2JsArgumentConstants.RUNTIME_DIAGNOSTIC_EXCEPTION -> RuntimeDiagnostic.EXCEPTION - null -> null - else -> { - null - } -} - -val v8tool by lazy { ExternalTool(System.getProperty("javascript.engine.path.V8")) } \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt deleted file mode 100644 index 717fe6a6a3a..00000000000 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt +++ /dev/null @@ -1,672 +0,0 @@ -/* - * 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.util.io.FileUtil -import org.jetbrains.kotlin.backend.common.phaser.AnyNamedPhase -import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig -import org.jetbrains.kotlin.backend.common.phaser.toPhaseMap -import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport -import org.jetbrains.kotlin.config.CommonConfigurationKeys -import org.jetbrains.kotlin.ir.backend.js.* -import org.jetbrains.kotlin.ir.backend.js.codegen.CompilerOutputSink -import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity -import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity.* -import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationOptions -import org.jetbrains.kotlin.ir.backend.js.codegen.generateEsModules -import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer -import org.jetbrains.kotlin.ir.backend.js.ic.* -import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl -import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory -import org.jetbrains.kotlin.ir.util.IdSignature -import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy -import org.jetbrains.kotlin.js.config.JSConfigurationKeys -import org.jetbrains.kotlin.js.config.JsConfig -import org.jetbrains.kotlin.js.config.RuntimeDiagnostic -import org.jetbrains.kotlin.js.facade.MainCallParameters -import org.jetbrains.kotlin.js.facade.TranslationUnit -import org.jetbrains.kotlin.js.testNew.handlers.JsAstHandler -import org.jetbrains.kotlin.konan.properties.propertyList -import org.jetbrains.kotlin.library.KLIB_PROPERTY_DEPENDS -import org.jetbrains.kotlin.library.KotlinAbiVersion -import org.jetbrains.kotlin.library.KotlinLibrary -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.parsing.parseBoolean -import org.jetbrains.kotlin.test.TargetBackend -import org.jetbrains.kotlin.util.DummyLogger -import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull -import org.junit.Assert -import java.io.File -import java.lang.Boolean.getBoolean - -private val fullRuntimeKlib: String = System.getProperty("kotlin.js.full.stdlib.path") -private val defaultRuntimeKlib = System.getProperty("kotlin.js.reduced.stdlib.path") -private val kotlinTestKLib = System.getProperty("kotlin.js.kotlin.test.path") - -// TODO Cache on FS (requires bootstrap) -private val predefinedKlibHasIcCache = mutableMapOf( - File(fullRuntimeKlib).absolutePath to null, - File(kotlinTestKLib).absolutePath to null, - File(defaultRuntimeKlib).absolutePath to null -) - -class TestModuleCache(val moduleName: String, val files: MutableMap) { - - constructor(moduleName: String) : this(moduleName, mutableMapOf()) - - fun cacheProvider(): PersistentCacheProvider { - return object : PersistentCacheProvider { - override fun fileFingerPrint(path: String): Hash { - return 0L - } - - override fun serializedParts(path: String): SerializedIcDataForFile { - error("Is not supported") - } - - override fun inlineGraphForFile(path: String, sigResolver: (Int) -> IdSignature): Collection> { - error("Is not supported") - } - - override fun inlineHashes(path: String, sigResolver: (Int) -> IdSignature): Map { - error("Is not supported") - } - - override fun allInlineHashes(sigResolver: (String, Int) -> IdSignature): Map { - error("Is not supported") - } - - override fun binaryAst(path: String): ByteArray? { - return files[path]?.ast ?: ByteArray(0) - } - - override fun dts(path: String): ByteArray? { - return files[path]?.dts - } - - override fun sourceMap(path: String): ByteArray? { - return files[path]?.sourceMap - } - } - } - - fun cacheConsumer(): PersistentCacheConsumer { - return object : PersistentCacheConsumer { - override fun commitInlineFunctions( - path: String, - hashes: Collection>, - sigResolver: (IdSignature) -> Int - ) { - - } - - override fun commitFileFingerPrint(path: String, fingerprint: Hash) { - - } - - override fun commitInlineGraph( - path: String, - hashes: Collection>, - sigResolver: (IdSignature) -> Int - ) { - - } - - override fun commitICCacheData(path: String, icData: SerializedIcDataForFile) { - - } - - override fun commitBinaryAst(path: String, astData: ByteArray) { - val storage = files.getOrPut(path) { FileCache(path, null, null, null) } - storage.ast = astData - } - - override fun commitBinaryDts(path: String, dstData: ByteArray) { - val storage = files.getOrPut(path) { FileCache(path, null, null, null) } - storage.dts = dstData - } - - override fun commitSourceMap(path: String, mapData: ByteArray) { - val storage = files.getOrPut(path) { FileCache(path, null, null, null) } - storage.sourceMap = mapData - } - - override fun invalidateForFile(path: String) { - files.remove(path) - } - - override fun commitLibraryPath(libraryPath: String) { - - } - } - } - - fun createModuleCache(): ModuleCache = ModuleCache(moduleName, files) -} - -abstract class BasicIrBoxTest( - pathToTestDir: String, - testGroupOutputDirPrefix: String, - generateSourceMap: Boolean = false, - generateNodeJsRunner: Boolean = false, - targetBackend: TargetBackend = TargetBackend.JS_IR -) : BasicBoxTest( - pathToTestDir, - testGroupOutputDirPrefix, - typedArraysEnabled = true, - generateSourceMap = generateSourceMap, - generateNodeJsRunner = generateNodeJsRunner, - targetBackend = targetBackend -) { - open val generateDts = false - - override val skipMinification = true - - private fun getBoolean(s: String, default: Boolean) = System.getProperty(s)?.let { parseBoolean(it) } ?: default - - private val runIcMode: Boolean = getBoolean("kotlin.js.ir.icMode") - - private val lowerPerModule: Boolean = runIcMode || getBoolean("kotlin.js.ir.lowerPerModule") - - private val klibMainModule: Boolean = getBoolean("kotlin.js.ir.klibMainModule") - - override val skipRegularMode: Boolean = getBoolean("kotlin.js.ir.skipRegularMode") - - override val runIrDce: Boolean = getBoolean("kotlin.js.ir.dce", true) - - val perModule: Boolean = getBoolean("kotlin.js.ir.perModule") - - // TODO Design incremental compilation for IR and add test support - override val incrementalCompilationChecksEnabled = true - - private val compilationCache = mutableMapOf() - - private val cachedDependencies = mutableMapOf>() - - override fun doTest(filePath: String, expectedResult: String, mainCallParameters: MainCallParameters) { - compilationCache.clear() - cachedDependencies.clear() - super.doTest(filePath, expectedResult, mainCallParameters) - } - - override val testChecker get() = if (runTestInNashorn) NashornIrJsTestChecker else V8IrJsTestChecker - - override fun translateFiles( - units: List, - outputFile: File, - dceOutputFile: File, - config: JsConfig, - outputPrefixFile: File?, - outputPostfixFile: File?, - mainCallParameters: MainCallParameters, - incrementalData: IncrementalData, - remap: Boolean, - testPackage: String?, - testFunction: String, - needsFullIrRuntime: Boolean, - isMainModule: Boolean, - skipDceDriven: Boolean, - esModules: Boolean, - granularity: JsGenerationGranularity, - propertyLazyInitialization: Boolean, - safeExternalBoolean: Boolean, - safeExternalBooleanDiagnostic: RuntimeDiagnostic?, - skipMangleVerification: Boolean, - abiVersion: KotlinAbiVersion, - incrementalCompilation: Boolean, - recompile: Boolean, - icCache: MutableMap, - customTestModule: String?, - ) { - val filesToCompile = units.mapNotNull { (it as? TranslationUnit.SourceFile)?.file } - - val runtimeKlibs = if (needsFullIrRuntime) listOf(fullRuntimeKlib, kotlinTestKLib) else listOf(defaultRuntimeKlib) - - val transitiveLibraries = config.configuration[JSConfigurationKeys.TRANSITIVE_LIBRARIES]!!.map { File(it).name } - val friendsLibraries = (config.configuration[JSConfigurationKeys.FRIEND_PATHS] ?: emptyList()).map { File(it).name } - - val allKlibPaths = (runtimeKlibs + transitiveLibraries.map { - compilationCache[it] ?: error("Can't find compiled module for dependency $it") - }).map { File(it).absolutePath }.toMutableList() - - val friendPaths = friendsLibraries.map { compilationCache[it] ?: error("Can't find compiled module for friend dep $it") }.map { - File(it).canonicalPath - } - - val klibPath = outputFile.canonicalPath.replace("_v5.js", "") - - val actualOutputFile = outputFile.canonicalPath.let { - if (!isMainModule) klibPath else it - } - - if (incrementalCompilationChecksEnabled && incrementalCompilation) { - runtimeKlibs.forEach { createIcCache(it, null, runtimeKlibs, config, icCache) } - } - - if (!recompile) { // In case of incremental recompilation we only rebuild caches, not klib itself - val module = prepareAnalyzedSourceModule( - config.project, - filesToCompile, - config.configuration, - allKlibPaths, - friendPaths, - AnalyzerWithCompilerReport(config.configuration), - ) - generateKLib( - module, - irFactory = IrFactoryImpl, - outputKlibPath = klibPath, - nopack = true, - jsOutputName = null, - abiVersion = abiVersion - ) - } - - val klibCannonPath = File(klibPath).canonicalPath - - if (incrementalCompilation) { - icCache[klibCannonPath] = - createIcCache(klibCannonPath, filesToCompile.map { it.virtualFilePath }, allKlibPaths + klibCannonPath, config, icCache) - } - - compilationCache[outputFile.name.replace(".js", ".meta.js")] = actualOutputFile - - if (isMainModule) { - logger.logFile("Output JS", outputFile) - val mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null } - - @Suppress("NAME_SHADOWING") - val granularity = if (perModule) PER_MODULE else granularity - - if (!skipRegularMode) { - val dirtyFilesToRecompile = if (recompile) { - units.map { (it as TranslationUnit.SourceFile).file.virtualFilePath }.toSet() - } else null - - if (incrementalCompilation) { - val jsOutputFile = if (recompile) File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js") - else outputFile - val compiledModule = generateJsFromAst(klibPath, icCache.map { it.key to it.value.createModuleCache() }.toMap()) - generateOldModuleSystems(compiledModule, jsOutputFile, dceOutputFile, config, units, dirtyFilesToRecompile) - } else { - val ir = compileToLoweredIr( - config, - outputFile, - klibPath, - allKlibPaths + klibCannonPath, - friendPaths, - testPackage, - testFunction, - propertyLazyInitialization = propertyLazyInitialization, - skipMangleVerification = skipMangleVerification, - safeExternalBoolean = safeExternalBoolean, - safeExternalBooleanDiagnostic = safeExternalBooleanDiagnostic, - dceDriven = false, - granularity, - dirtyFilesToRecompile - ) - if (esModules) { - generateEsModules(ir, outputFile.esModulesSubDir, granularity, config, customTestModule, mainArguments) - if (runIrDce) { - eliminateDeadDeclarations(ir.allModules, ir.context) - generateEsModules(ir, dceOutputFile.esModulesSubDir, granularity, config, customTestModule, mainArguments) - } - } else { - generateOldModuleSystems( - ir.oldIr2Js(granularity, runIrDce, mainArguments), - outputFile, - dceOutputFile, - config, - units, - dirtyFilesToRecompile - ) - } - } - } - } - } - - private fun compileToLoweredIr( - config: JsConfig, - outputFile: File, - klibPath: String, - dependencies: List, - friendPaths: List, - testPackage: String?, - testFunction: String, - propertyLazyInitialization: Boolean, - skipMangleVerification: Boolean, - safeExternalBoolean: Boolean, - safeExternalBooleanDiagnostic: RuntimeDiagnostic?, - dceDriven: Boolean, - granularity: JsGenerationGranularity, - dirtyFilesToRecompile: Set? = null - ): LoweredIr { - - val debugMode = getBoolean("kotlin.js.debugMode") - - val phaseConfig = if (debugMode) { - val allPhasesSet = jsPhases.toPhaseMap().values.toSet() - val dumpOutputDir = File(outputFile.parent, outputFile.nameWithoutExtension + "-irdump") - logger.logFile("Dumping phasesTo", dumpOutputDir) - PhaseConfig( - jsPhases, - dumpToDirectory = dumpOutputDir.path, - toDumpStateAfter = fromSysPropertyOrAll("kotlin.js.test.phasesToDumpAfter", allPhasesSet), - toValidateStateAfter = fromSysPropertyOrAll("kotlin.js.test.phasesToValidateAfter", allPhasesSet), - dumpOnlyFqName = null - ) - } else { - PhaseConfig(jsPhases) - } - - val module = ModulesStructure( - config.project, - MainModule.Klib(klibPath), - config.configuration, - dependencies, - friendPaths, - icUseGlobalSignatures = runIcMode, - icUseStdlibCache = runIcMode, - icCache = emptyMap() - ) - - return compile( - module, - phaseConfig = phaseConfig, - irFactory = IrFactoryImpl, - exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), - dceDriven = dceDriven, - propertyLazyInitialization = propertyLazyInitialization, - verifySignatures = !skipMangleVerification, - lowerPerModule = lowerPerModule, - safeExternalBoolean = safeExternalBoolean, - safeExternalBooleanDiagnostic = safeExternalBooleanDiagnostic, - granularity = granularity, - filesToLower = dirtyFilesToRecompile - ) - } - - private fun LoweredIr.oldIr2Js( - granularity: JsGenerationGranularity, - runDce: Boolean, - mainArguments: List?, - ): CompilerResult { - check(granularity != PER_FILE) { "Per file granularity is not supported for old module systems" } - val transformer = IrModuleToJsTransformer( - context, - mainArguments, - fullJs = true, - dceJs = runDce, - multiModule = granularity == PER_MODULE, - relativeRequirePath = false - ) - - return transformer.generateModule(allModules) - } - - private fun generateOldModuleSystems( - compiledModule: CompilerResult, - outputFile: File, - outputDceFile: File, - config: JsConfig, - units: List, - dirtyFilesToRecompile: Set? = null - ) { - outputFile.deleteRecursively() - - val compiledOutput = compiledModule.outputs!! - val compiledDCEOutput = if (dirtyFilesToRecompile != null) null else compiledModule.outputsAfterDce - - compiledOutput.writeTo(outputFile, config) - - compiledDCEOutput?.writeTo(outputDceFile, config) - - if (generateDts) { - val dtsFile = outputFile.withReplacedExtensionOrNull("_v5.js", ".d.ts")!! - logger.logFile("Output d.ts", dtsFile) - dtsFile.write(compiledModule.tsDefinitions ?: error("No ts definitions")) - } - - compiledOutput.jsProgram?.let { - JsAstHandler.processUnitsOfJsProgram(it, units, targetBackend = TargetBackend.JS_IR) { Assert.fail(it) } - } - } - - private fun generateTestFile(outputDir: File, config: JsConfig, customTestModule: String?) { - val moduleName = config.configuration[CommonConfigurationKeys.MODULE_NAME] - val esmTestFile = File(outputDir, "test.mjs") - logger.logFile("ES module test file", esmTestFile) - val defaultTestModule = - """ - import { box } from './${moduleName}/index.js'; - let res = box(); - if (res !== "OK") { - throw "Wrong result: " + String(res); - } - """.trimIndent() - - esmTestFile.writeText(customTestModule ?: defaultTestModule) - } - - private fun generateEsModules( - ir: LoweredIr, - outputDir: File, - granularity: JsGenerationGranularity, - config: JsConfig, - customTestModule: String?, - mainArguments: List? - ) { - val options = JsGenerationOptions(generatePackageJson = true, generateTypeScriptDefinitions = generateDts) - outputDir.deleteRecursively() - generateEsModules(ir, jsOutputSink(outputDir), mainArguments = mainArguments, granularity = granularity, options = options) - generateTestFile(outputDir, config, customTestModule) - } - - override fun checkIncrementalCompilation( - sourceDirs: List, - module: TestModule, - kotlinFiles: List, - dependencies: List, - allDependencies: List, - friends: List, - multiModule: Boolean, - tmpDir: File, - remap: Boolean, - outputFile: File, - outputPrefixFile: File?, - outputPostfixFile: File?, - mainCallParameters: MainCallParameters, - incrementalData: IncrementalData, - testPackage: String?, - testFunction: String, - needsFullIrRuntime: Boolean, - expectActualLinker: Boolean, - icCaches: MutableMap - ) { - val isMainModule = module.name == DEFAULT_MODULE || module.name == TEST_MODULE - val cacheKey = outputFile.canonicalPath.replace("_v5.js", "") - - val icCache = icCaches[cacheKey] ?: error("No IC data found for module ${module.name}") - - val dirtyFiles = mutableListOf() - val oldBinaryAsts = mutableMapOf() - - val dataProvider = icCache.cacheProvider() - val dataConsumer = icCache.cacheConsumer() - - for (testFile in kotlinFiles) { - if (testFile.recompile) { - val fileName = testFile.fileName - oldBinaryAsts[fileName] = dataProvider.binaryAst(fileName) ?: error("No AST found for $fileName") - dataConsumer.invalidateForFile(fileName) - dirtyFiles.add(fileName) - } - } - - val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js") - val recompiledMinOutputFile = File(recompiledOutputFile.parentFile, recompiledOutputFile.nameWithoutExtension + "-min.js") - val recompiledConfig = createConfig( - sourceDirs, - module, - dependencies, - allDependencies, - friends, - multiModule, - tmpDir, - null, - expectActualLinker, - ErrorTolerancePolicy.DEFAULT - ) - - translateFiles( - dirtyFiles.map { TranslationUnit.SourceFile(createPsiFile(it)) }, - outputFile, - recompiledMinOutputFile, - recompiledConfig, - outputPrefixFile, - outputPostfixFile, - mainCallParameters, - incrementalData, - remap, - testPackage, - testFunction, - needsFullIrRuntime, - isMainModule, - skipDceDriven = true, - granularity = WHOLE_PROGRAM, // TODO?? - propertyLazyInitialization = true, - safeExternalBoolean = false, - safeExternalBooleanDiagnostic = null, - skipMangleVerification = false, - abiVersion = KotlinAbiVersion.CURRENT, - incrementalCompilation = true, - recompile = true, - icCache = icCaches, - - // TODO?? - customTestModule = null, - esModules = false, - ) - - // TODO: enable asserts when binary stability is achieved -// val cacheProvider = icCache.cacheProvider() -// val newBinaryAsts = dirtyFiles.associateWith { cacheProvider.binaryAst(it) } -// -// for (file in dirtyFiles) { -// val oldBinaryAst = oldBinaryAsts[file] -// val newBinaryAst = newBinaryAsts[file] -// -// assert(oldBinaryAst.contentEquals(newBinaryAst)) { "Binary AST changed after recompilation for file $file" } -// } -// -// if (isMainModule) { -// val originalOutput = FileUtil.loadFile(outputFile) -// val recompiledOutput = FileUtil.loadFile(recompiledOutputFile) -// assertEquals("Output file changed after recompilation", originalOutput, recompiledOutput) -// } - } - - private fun jsOutputSink(perFileOutputDir: File): CompilerOutputSink { - perFileOutputDir.deleteRecursively() - perFileOutputDir.mkdirs() - - return object : CompilerOutputSink { - override fun write(module: String, path: String, content: String) { - val file = File(File(perFileOutputDir, module), path) - file.parentFile.mkdirs() - file.writeText(content) - } - } - } - - private fun createIcCache( - path: String, - dirtyFiles: Collection?, - allKlibPaths: Collection, - config: JsConfig, - icCache: MutableMap - ): TestModuleCache { - - val cannonicalPath = File(path).canonicalPath - var moduleCache = predefinedKlibHasIcCache[cannonicalPath] - - if (moduleCache == null) { - moduleCache = icCache[cannonicalPath] ?: TestModuleCache(cannonicalPath) - - val allResolvedDependencies = jsResolveLibraries(allKlibPaths, emptyList(), DummyLogger) - - val libs = allResolvedDependencies.getFullList().associateBy { File(it.libraryFile.path).canonicalPath } - - val nameToKotlinLibrary: Map = libs.values.associateBy { it.moduleName } - - val dependencyGraph = libs.values.associateWith { - it.manifestProperties.propertyList(KLIB_PROPERTY_DEPENDS, escapeInQuotes = true).map { depName -> - nameToKotlinLibrary[depName] ?: error("No Library found for $depName") - } - } - - val currentLib = libs[File(cannonicalPath).canonicalPath] ?: error("Expected library at $cannonicalPath") - - rebuildCacheForDirtyFiles(currentLib, config.configuration, dependencyGraph, dirtyFiles, moduleCache.cacheConsumer(), IrFactoryImpl) - - if (cannonicalPath in predefinedKlibHasIcCache) { - predefinedKlibHasIcCache[cannonicalPath] = moduleCache - } - } - - icCache[cannonicalPath] = moduleCache - - return moduleCache - } - - private fun fromSysPropertyOrAll(key: String, all: Set): Set { - val phases = System.getProperty(key)?.split(',')?.toSet() ?: emptySet() - if (phases.isEmpty()) return all - - return all.filter { it.name in phases }.toSet() - } - - private fun CompilationOutputs.writeTo(outputFile: File, config: JsConfig) { - val wrappedCode = - wrapWithModuleEmulationMarkers(jsCode, moduleId = config.moduleId, moduleKind = config.moduleKind) - outputFile.write(wrappedCode) - - val dependencyPaths = mutableListOf() - - dependencies.forEach { (moduleId, outputs) -> - val moduleWrappedCode = wrapWithModuleEmulationMarkers(outputs.jsCode, config.moduleKind, moduleId) - val dependencyPath = outputFile.absolutePath.replace("_v5.js", "-${moduleId}_v5.js") - dependencyPaths += dependencyPath - File(dependencyPath).write(moduleWrappedCode) - } - - cachedDependencies[outputFile.absolutePath] = dependencyPaths - } - - override fun runGeneratedCode( - jsFiles: List, - testModuleName: String?, - testPackage: String?, - testFunction: String, - expectedResult: String, - withModuleSystem: Boolean - ) { - // TODO: should we do anything special for module systems? - // TODO: return list of js from translateFiles and provide then to this function with other js files - - val allFiles = jsFiles.flatMap { file -> cachedDependencies[File(file).absolutePath]?.let { deps -> deps + file } ?: listOf(file) } - testChecker.check(allFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem) - } -} - -private fun File.write(text: String) { - parentFile.mkdirs() - writeText(text) -} - -val File.esModulesSubDir: File - get() = File(absolutePath + "_esm") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt index 1a641209c19..6070e2b2aa3 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicWasmBoxTest.kt @@ -6,6 +6,9 @@ package org.jetbrains.kotlin.js.test import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.StandardFileSystems +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.psi.PsiManager import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig import org.jetbrains.kotlin.backend.common.phaser.toPhaseMap import org.jetbrains.kotlin.backend.wasm.WasmCompilerResult @@ -26,6 +29,7 @@ import org.jetbrains.kotlin.js.facade.TranslationUnit import org.jetbrains.kotlin.js.test.engines.ExternalTool import org.jetbrains.kotlin.js.test.engines.SpiderMonkeyEngine import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.resolve.CompilerEnvironment @@ -288,6 +292,19 @@ abstract class BasicWasmBoxTest( override fun createEnvironment() = KotlinCoreEnvironment.createForTests(testRootDisposable, CompilerConfiguration(), EnvironmentConfigFiles.JS_CONFIG_FILES) + private fun KotlinTestWithEnvironment.createPsiFile(fileName: String): KtFile { + val psiManager = PsiManager.getInstance(project) + val fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) + + val file = fileSystem.findFileByPath(fileName) ?: error("File not found: $fileName") + + return psiManager.findFile(file) as KtFile + } + + private fun KotlinTestWithEnvironment.createPsiFiles(fileNames: List): List { + return fileNames.map { this@createPsiFiles.createPsiFile(it) } + } + companion object { const val TEST_DATA_DIR_PATH = "js/js.translator/testData/" const val TEST_MODULE = "main" diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt index cced28cf68a..3170edb7699 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt @@ -12,6 +12,8 @@ import org.jetbrains.kotlin.js.engine.ScriptEngineV8 import org.jetbrains.kotlin.js.engine.loadFiles import org.junit.Assert +private const val DIST_DIR_JS_PATH = "dist/js/" + fun createScriptEngine(): ScriptEngine { return if (java.lang.Boolean.getBoolean("kotlin.js.useNashorn")) ScriptEngineNashorn() else ScriptEngineV8() } @@ -27,7 +29,7 @@ fun ScriptEngine.runTestFunction( withModuleSystem: Boolean ): String { var script = when { - withModuleSystem -> BasicBoxTest.KOTLIN_TEST_INTERNAL + ".require('" + testModuleName!! + "')" + withModuleSystem -> "\$kotlin_test_internal\$.require('" + testModuleName!! + "')" testModuleName === null -> "this" else -> testModuleName } @@ -140,9 +142,9 @@ object NashornJsTestChecker : AbstractNashornJsTestChecker() { } override val preloadedScripts = listOf( - BasicBoxTest.TEST_DATA_DIR_PATH + "nashorn-polyfills.js", - BasicBoxTest.DIST_DIR_JS_PATH + "kotlin.js", - BasicBoxTest.DIST_DIR_JS_PATH + "kotlin-test.js" + BasicWasmBoxTest.TEST_DATA_DIR_PATH + "nashorn-polyfills.js", + DIST_DIR_JS_PATH + "kotlin.js", + DIST_DIR_JS_PATH + "kotlin-test.js" ) override fun createScriptEngineForTest(): ScriptEngineNashorn { @@ -156,7 +158,7 @@ object NashornJsTestChecker : AbstractNashornJsTestChecker() { object NashornIrJsTestChecker : AbstractNashornJsTestChecker() { override val preloadedScripts = listOf( - BasicBoxTest.TEST_DATA_DIR_PATH + "nashorn-polyfills.js", + BasicWasmBoxTest.TEST_DATA_DIR_PATH + "nashorn-polyfills.js", "libraries/stdlib/js-v1/src/js/polyfills.js" ) } @@ -166,8 +168,8 @@ object V8JsTestChecker : AbstractJsTestChecker() { override fun initialValue() = ScriptEngineV8().apply { val preloadedScripts = listOf( - BasicBoxTest.DIST_DIR_JS_PATH + "kotlin.js", - BasicBoxTest.DIST_DIR_JS_PATH + "kotlin-test.js" + DIST_DIR_JS_PATH + "kotlin.js", + DIST_DIR_JS_PATH + "kotlin-test.js" ) loadFiles(preloadedScripts) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ast/NameResolutionTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/ast/NameResolutionTest.kt index e20541fb310..b45bbefca80 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ast/NameResolutionTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ast/NameResolutionTest.kt @@ -23,7 +23,7 @@ import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.inline.clean.renameLabels import org.jetbrains.kotlin.js.inline.clean.resolveTemporaryNames import org.jetbrains.kotlin.js.parser.parse -import org.jetbrains.kotlin.js.test.BasicBoxTest +import org.jetbrains.kotlin.js.test.BasicWasmBoxTest import org.junit.Assert.assertEquals import org.junit.Assert.fail import org.junit.Rule @@ -50,7 +50,7 @@ class NameResolutionTest { private fun doTest() { val methodName = testName.methodName - val baseName = "${BasicBoxTest.TEST_DATA_DIR_PATH}/js-name-resolution/" + val baseName = "${BasicWasmBoxTest.TEST_DATA_DIR_PATH}/js-name-resolution/" val originalName = "$baseName/$methodName.original.js" val expectedName = "$baseName/$methodName.expected.js" diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/BasicOptimizerTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/BasicOptimizerTest.kt index 90429adbfe6..fc203437f6f 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/BasicOptimizerTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/optimizer/BasicOptimizerTest.kt @@ -13,7 +13,7 @@ import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor import org.jetbrains.kotlin.js.parser.parse -import org.jetbrains.kotlin.js.test.BasicBoxTest +import org.jetbrains.kotlin.js.test.BasicWasmBoxTest import org.jetbrains.kotlin.js.test.createScriptEngine import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.util.TextOutputImpl @@ -29,7 +29,7 @@ abstract class BasicOptimizerTest(private var basePath: String) { protected fun box() { val methodName = testName.methodName - val baseName = "${BasicBoxTest.TEST_DATA_DIR_PATH}/js-optimizer/$basePath" + val baseName = "${BasicWasmBoxTest.TEST_DATA_DIR_PATH}/js-optimizer/$basePath" val unoptimizedName = "$baseName/$methodName.original.js" val optimizedName = "$baseName/$methodName.optimized.js" diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/converters/JsIrBackendFacade.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/converters/JsIrBackendFacade.kt index 7b583ff2dd4..e71b15c86c7 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/converters/JsIrBackendFacade.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/converters/JsIrBackendFacade.kt @@ -22,8 +22,8 @@ import org.jetbrains.kotlin.ir.util.IrMessageLogger import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy import org.jetbrains.kotlin.js.config.JSConfigurationKeys -import org.jetbrains.kotlin.js.test.esModulesSubDir import org.jetbrains.kotlin.js.testNew.handlers.JsBoxRunner.Companion.TEST_FUNCTION +import org.jetbrains.kotlin.js.testNew.utils.esModulesSubDir import org.jetbrains.kotlin.js.testNew.utils.extractTestPackage import org.jetbrains.kotlin.js.testNew.utils.jsIrIncrementalDataProvider import org.jetbrains.kotlin.library.uniqueName diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsAstHandler.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsAstHandler.kt index 53232cf214a..29090af2657 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsAstHandler.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsAstHandler.kt @@ -10,7 +10,6 @@ import org.jetbrains.kotlin.js.backend.ast.JsNullLiteral import org.jetbrains.kotlin.js.backend.ast.JsProgram import org.jetbrains.kotlin.js.backend.ast.RecursiveJsVisitor import org.jetbrains.kotlin.js.facade.TranslationResult -import org.jetbrains.kotlin.js.facade.TranslationUnit import org.jetbrains.kotlin.js.test.utils.DirectiveTestUtils import org.jetbrains.kotlin.test.TargetBackend import org.jetbrains.kotlin.test.backend.handlers.JsBinaryArtifactHandler @@ -30,36 +29,26 @@ class JsAstHandler(testServices: TestServices) : JsBinaryArtifactHandler(testSer is BinaryArtifacts.Js.JsIrArtifact -> artifact.compilerResult.outputs?.jsProgram ?: return else -> return } - processJsProgram(jsProgram, ktFiles, module.targetBackend!!) { - testServices.assertions.fail { it } + processJsProgram(jsProgram, ktFiles, module.targetBackend!!) + } + + private fun processJsProgram(program: JsProgram, psiFiles: List, targetBackend: TargetBackend) { + // TODO: For now the IR backend generates JS code that doesn't pass verification, + // TODO: so we temporarily disabled AST verification. + if (targetBackend == TargetBackend.JS) { + psiFiles.forEach { DirectiveTestUtils.processDirectives(program, it, targetBackend) } + program.verifyAst() } } - companion object { - fun processUnitsOfJsProgram( - program: JsProgram, units: List, targetBackend: TargetBackend, onFail: (String) -> Unit - ) { - processJsProgram(program, units.filterIsInstance().map { it.file.text }, targetBackend, onFail) - } - - fun processJsProgram(program: JsProgram, psiFiles: List, targetBackend: TargetBackend, onFail: (String) -> Unit) { - // TODO: For now the IR backend generates JS code that doesn't pass verification, - // TODO: so we temporarily disabled AST verification. - if (targetBackend == TargetBackend.JS) { - psiFiles.forEach { DirectiveTestUtils.processDirectives(program, it, targetBackend) } - program.verifyAst(onFail) - } - } - - private fun JsProgram.verifyAst(onFail: (String) -> Unit) { - accept(object : RecursiveJsVisitor() { - override fun visitExpressionStatement(x: JsExpressionStatement) { - when (x.expression) { - is JsNullLiteral -> onFail("Expression statement contains `null` literal") - else -> super.visitExpressionStatement(x) - } + private fun JsProgram.verifyAst() { + accept(object : RecursiveJsVisitor() { + override fun visitExpressionStatement(x: JsExpressionStatement) { + when (x.expression) { + is JsNullLiteral -> testServices.assertions.fail { "Expression statement contains `null` literal" } + else -> super.visitExpressionStatement(x) } - }) - } + } + }) } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsBoxRunner.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsBoxRunner.kt index ad54d2fddea..242136ed49d 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsBoxRunner.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsBoxRunner.kt @@ -5,8 +5,7 @@ package org.jetbrains.kotlin.js.testNew.handlers -import org.jetbrains.kotlin.js.test.esModulesSubDir -import org.jetbrains.kotlin.js.test.v8tool +import org.jetbrains.kotlin.js.test.engines.ExternalTool import org.jetbrains.kotlin.js.testNew.utils.* import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives import org.jetbrains.kotlin.test.services.TestServices @@ -15,6 +14,8 @@ import org.jetbrains.kotlin.test.services.defaultsProvider import org.jetbrains.kotlin.test.services.moduleStructure import java.io.File +private val v8tool by lazy { ExternalTool(System.getProperty("javascript.engine.path.V8")) } + class JsBoxRunner(testServices: TestServices) : AbstractJsArtifactsCollector(testServices) { override fun processAfterAllModules(someAssertionWasFailed: Boolean) { if (someAssertionWasFailed) return diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsMinifierRunner.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsMinifierRunner.kt index 836aa5c95a6..1b241f0af34 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsMinifierRunner.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsMinifierRunner.kt @@ -22,6 +22,10 @@ import org.jetbrains.kotlin.test.services.moduleStructure import java.io.File class JsMinifierRunner(testServices: TestServices) : AbstractJsArtifactsCollector(testServices) { + private val distDirJsPath = "dist/js/" + private val overwriteReachableNodesProperty = "kotlin.js.overwriteReachableNodes" + private val overwriteReachableNodes = java.lang.Boolean.getBoolean(overwriteReachableNodesProperty) + override fun processAfterAllModules(someAssertionWasFailed: Boolean) { if (someAssertionWasFailed) return @@ -56,104 +60,96 @@ class JsMinifierRunner(testServices: TestServices) : AbstractJsArtifactsCollecto testPackage = testPackage, testFunction = testFunction, withModuleSystem = withModuleSystem - ) { expect, actual -> assertions.assertEquals(expect, actual) } + ) } } - companion object { - private const val DIST_DIR_JS_PATH = "dist/js/" - - private const val overwriteReachableNodesProperty = "kotlin.js.overwriteReachableNodes" - private val overwriteReachableNodes = java.lang.Boolean.getBoolean(overwriteReachableNodesProperty) - - private fun minificationThresholdChecker(expectedReachableNodes: Int?, actualReachableNodes: Int, file: File) { - val fileContent = file.readText() - val replacement = "// ${JsEnvironmentConfigurationDirectives.EXPECTED_REACHABLE_NODES.name}: $actualReachableNodes" - val enablingMessage = "To set expected reachable nodes use '$replacement'\n" + - "To enable automatic overwriting reachable nodes use property '-Pfd.${overwriteReachableNodesProperty}=true'" - if (expectedReachableNodes == null) { - val baseMessage = "The number of expected reachable nodes was not set. Actual reachable nodes: $actualReachableNodes." - return when { - overwriteReachableNodes -> { - file.writeText("$replacement\n$fileContent") - throw AssertionError(baseMessage) - } - else -> println("$baseMessage\n$enablingMessage") + private fun minificationThresholdChecker(expectedReachableNodes: Int?, actualReachableNodes: Int, file: File) { + val fileContent = file.readText() + val replacement = "// ${JsEnvironmentConfigurationDirectives.EXPECTED_REACHABLE_NODES.name}: $actualReachableNodes" + val enablingMessage = "To set expected reachable nodes use '$replacement'\n" + + "To enable automatic overwriting reachable nodes use property '-Pfd.${overwriteReachableNodesProperty}=true'" + if (expectedReachableNodes == null) { + val baseMessage = "The number of expected reachable nodes was not set. Actual reachable nodes: $actualReachableNodes." + return when { + overwriteReachableNodes -> { + file.writeText("$replacement\n$fileContent") + throw AssertionError(baseMessage) } - } - - val minThreshold = expectedReachableNodes * 9 / 10 - val maxThreshold = expectedReachableNodes * 11 / 10 - if (actualReachableNodes < minThreshold || actualReachableNodes > maxThreshold) { - val message = "Number of reachable nodes ($actualReachableNodes) does not fit into expected range " + - "[$minThreshold; $maxThreshold]" - val additionalMessage: String = - if (overwriteReachableNodes) { - val oldValue = "// ${JsEnvironmentConfigurationDirectives.EXPECTED_REACHABLE_NODES.name}: $expectedReachableNodes" - val newText = fileContent.replaceFirst(oldValue, replacement) - file.writeText(newText) - "" - } else { - "\n$enablingMessage" - } - - throw AssertionError("$message$additionalMessage") + else -> println("$baseMessage\n$enablingMessage") } } - fun minifyAndRun( - file: File, - expectedReachableNodes: Int?, - workDir: File, - allJsFiles: List, - generatedJsFiles: List>, - expectedResult: String, - testModuleName: String?, - testPackage: String?, - testFunction: String, - withModuleSystem: Boolean, - assertEquals: (String, String) -> Unit - ) { - val kotlinJsLib = DIST_DIR_JS_PATH + "kotlin.js" - val kotlinTestJsLib = DIST_DIR_JS_PATH + "kotlin-test.js" - val kotlinJsLibOutput = File(workDir, "kotlin.min.js").path - val kotlinTestJsLibOutput = File(workDir, "kotlin-test.min.js").path + val minThreshold = expectedReachableNodes * 9 / 10 + val maxThreshold = expectedReachableNodes * 11 / 10 + if (actualReachableNodes < minThreshold || actualReachableNodes > maxThreshold) { + val message = "Number of reachable nodes ($actualReachableNodes) does not fit into expected range " + + "[$minThreshold; $maxThreshold]" + val additionalMessage: String = + if (overwriteReachableNodes) { + val oldValue = "// ${JsEnvironmentConfigurationDirectives.EXPECTED_REACHABLE_NODES.name}: $expectedReachableNodes" + val newText = fileContent.replaceFirst(oldValue, replacement) + file.writeText(newText) + "" + } else { + "\n$enablingMessage" + } - val kotlinJsInputFile = InputFile(InputResource.file(kotlinJsLib), null, kotlinJsLibOutput, "kotlin") - val kotlinTestJsInputFile = InputFile(InputResource.file(kotlinTestJsLib), null, kotlinTestJsLibOutput, "kotlin-test") - - val filesToMinify = generatedJsFiles.associate { (fileName, moduleName) -> - val inputFileName = File(fileName).nameWithoutExtension - fileName to InputFile(InputResource.file(fileName), null, File(workDir, inputFileName + ".min.js").absolutePath, moduleName) - } - - val testFunctionFqn = testModuleName + (if (testPackage.isNullOrEmpty()) "" else ".$testPackage") + ".$testFunction" - val additionalReachableNodes = setOf( - testFunctionFqn, "kotlin.kotlin.io.BufferedOutput", "kotlin.kotlin.io.output.flush", - "kotlin.kotlin.io.output.buffer", "kotlin-test.kotlin.test.overrideAsserter_wbnzx$", - "kotlin-test.kotlin.test.DefaultAsserter" - ) - val allFilesToMinify = filesToMinify.values + kotlinJsInputFile + kotlinTestJsInputFile - val dceResult = DeadCodeElimination.run(allFilesToMinify, additionalReachableNodes, true) { _, _ -> } - - val reachableNodes = dceResult.reachableNodes - minificationThresholdChecker(expectedReachableNodes, reachableNodes.count { it.reachable }, file) - - val runList = mutableListOf() - runList += kotlinJsLibOutput - runList += kotlinTestJsLibOutput - runList += "${JsEnvironmentConfigurator.TEST_DATA_DIR_PATH}/nashorn-polyfills.js" - runList += allJsFiles.map { filesToMinify[it]?.outputPath ?: it } - - val engineForMinifier = createScriptEngine() - val result = engineForMinifier.runAndRestoreContext { - loadFiles(runList) - overrideAsserter() - eval(SETUP_KOTLIN_OUTPUT) - runTestFunction(testModuleName, testPackage, testFunction, withModuleSystem) - } - engineForMinifier.release() - assertEquals(expectedResult, result) + throw AssertionError("$message$additionalMessage") } } + + fun minifyAndRun( + file: File, + expectedReachableNodes: Int?, + workDir: File, + allJsFiles: List, + generatedJsFiles: List>, + expectedResult: String, + testModuleName: String?, + testPackage: String?, + testFunction: String, + withModuleSystem: Boolean + ) { + val kotlinJsLib = distDirJsPath + "kotlin.js" + val kotlinTestJsLib = distDirJsPath + "kotlin-test.js" + val kotlinJsLibOutput = File(workDir, "kotlin.min.js").path + val kotlinTestJsLibOutput = File(workDir, "kotlin-test.min.js").path + + val kotlinJsInputFile = InputFile(InputResource.file(kotlinJsLib), null, kotlinJsLibOutput, "kotlin") + val kotlinTestJsInputFile = InputFile(InputResource.file(kotlinTestJsLib), null, kotlinTestJsLibOutput, "kotlin-test") + + val filesToMinify = generatedJsFiles.associate { (fileName, moduleName) -> + val inputFileName = File(fileName).nameWithoutExtension + fileName to InputFile(InputResource.file(fileName), null, File(workDir, inputFileName + ".min.js").absolutePath, moduleName) + } + + val testFunctionFqn = testModuleName + (if (testPackage.isNullOrEmpty()) "" else ".$testPackage") + ".$testFunction" + val additionalReachableNodes = setOf( + testFunctionFqn, "kotlin.kotlin.io.BufferedOutput", "kotlin.kotlin.io.output.flush", + "kotlin.kotlin.io.output.buffer", "kotlin-test.kotlin.test.overrideAsserter_wbnzx$", + "kotlin-test.kotlin.test.DefaultAsserter" + ) + val allFilesToMinify = filesToMinify.values + kotlinJsInputFile + kotlinTestJsInputFile + val dceResult = DeadCodeElimination.run(allFilesToMinify, additionalReachableNodes, true) { _, _ -> } + + val reachableNodes = dceResult.reachableNodes + minificationThresholdChecker(expectedReachableNodes, reachableNodes.count { it.reachable }, file) + + val runList = mutableListOf() + runList += kotlinJsLibOutput + runList += kotlinTestJsLibOutput + runList += "${JsEnvironmentConfigurator.TEST_DATA_DIR_PATH}/nashorn-polyfills.js" + runList += allJsFiles.map { filesToMinify[it]?.outputPath ?: it } + + val engineForMinifier = createScriptEngine() + val result = engineForMinifier.runAndRestoreContext { + loadFiles(runList) + overrideAsserter() + eval(SETUP_KOTLIN_OUTPUT) + runTestFunction(testModuleName, testPackage, testFunction, withModuleSystem) + } + engineForMinifier.release() + assertions.assertEquals(expectedResult, result) + } } \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsSourceMapHandler.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsSourceMapHandler.kt index 0cabf64234a..3131925d624 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsSourceMapHandler.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/JsSourceMapHandler.kt @@ -37,65 +37,61 @@ class JsSourceMapHandler(testServices: TestServices) : JsBinaryArtifactHandler(t val result = (info.unwrap() as? BinaryArtifacts.Js.OldJsArtifact)?.translationResult ?: throw IllegalArgumentException("JsSourceMapHandler suppose to work only with old js backend") val remap = JsEnvironmentConfigurationDirectives.SKIP_SOURCEMAP_REMAPPING !in module.directives - checkSourceMap(outputFile, (result as TranslationResult.Success).program, remap) { expected, actual -> - testServices.assertions.assertEquals(expected, actual) + checkSourceMap(outputFile, (result as TranslationResult.Success).program, remap) + } + + private fun checkSourceMap(outputFile: File, program: JsProgram, remap: Boolean) { + val generatedProgram = JsProgram() + generatedProgram.globalBlock.statements += program.globalBlock.statements.map { it.deepCopy() } + generatedProgram.accept(object : RecursiveJsVisitor() { + override fun visitObjectLiteral(x: JsObjectLiteral) { + super.visitObjectLiteral(x) + x.isMultiline = false + } + + override fun visitVars(x: JsVars) { + x.isMultiline = false + super.visitVars(x) + } + }) + removeLocationFromBlocks(generatedProgram) + generatedProgram.accept(AmbiguousAstSourcePropagation()) + + val output = TextOutputImpl() + val pathResolver = SourceFilePathResolver(mutableListOf(File(".")), null) + val sourceMapBuilder = SourceMap3Builder(outputFile, output, "") + generatedProgram.accept( + JsToStringGenerationVisitor( + output, SourceMapBuilderConsumer(File("."), sourceMapBuilder, pathResolver, false, false) + ) + ) + val code = output.toString() + val generatedSourceMap = sourceMapBuilder.build() + + val codeWithLines = generatedProgram.toStringWithLineNumbers() + + val parsedProgram = JsProgram() + parsedProgram.globalBlock.statements += parse(code, ThrowExceptionOnErrorReporter, parsedProgram.scope, outputFile.path).orEmpty() + removeLocationFromBlocks(parsedProgram) + val sourceMapParseResult = SourceMapParser.parse(generatedSourceMap) + val sourceMap = when (sourceMapParseResult) { + is SourceMapSuccess -> sourceMapParseResult.value + is SourceMapError -> error("Could not parse source map: ${sourceMapParseResult.message}") + } + + if (remap) { + SourceMapLocationRemapper(sourceMap).remap(parsedProgram) + val codeWithRemappedLines = parsedProgram.toStringWithLineNumbers() + testServices.assertions.assertEquals(codeWithLines, codeWithRemappedLines) } } - companion object { - fun checkSourceMap(outputFile: File, program: JsProgram, remap: Boolean, assertEquals: (String, String) -> Unit) { - val generatedProgram = JsProgram() - generatedProgram.globalBlock.statements += program.globalBlock.statements.map { it.deepCopy() } - generatedProgram.accept(object : RecursiveJsVisitor() { - override fun visitObjectLiteral(x: JsObjectLiteral) { - super.visitObjectLiteral(x) - x.isMultiline = false - } - - override fun visitVars(x: JsVars) { - x.isMultiline = false - super.visitVars(x) - } - }) - removeLocationFromBlocks(generatedProgram) - generatedProgram.accept(AmbiguousAstSourcePropagation()) - - val output = TextOutputImpl() - val pathResolver = SourceFilePathResolver(mutableListOf(File(".")), null) - val sourceMapBuilder = SourceMap3Builder(outputFile, output, "") - generatedProgram.accept( - JsToStringGenerationVisitor( - output, SourceMapBuilderConsumer(File("."), sourceMapBuilder, pathResolver, false, false) - ) - ) - val code = output.toString() - val generatedSourceMap = sourceMapBuilder.build() - - val codeWithLines = generatedProgram.toStringWithLineNumbers() - - val parsedProgram = JsProgram() - parsedProgram.globalBlock.statements += parse(code, ThrowExceptionOnErrorReporter, parsedProgram.scope, outputFile.path).orEmpty() - removeLocationFromBlocks(parsedProgram) - val sourceMapParseResult = SourceMapParser.parse(generatedSourceMap) - val sourceMap = when (sourceMapParseResult) { - is SourceMapSuccess -> sourceMapParseResult.value - is SourceMapError -> error("Could not parse source map: ${sourceMapParseResult.message}") + private fun removeLocationFromBlocks(program: JsProgram) { + program.globalBlock.accept(object : RecursiveJsVisitor() { + override fun visitBlock(x: JsBlock) { + super.visitBlock(x) + x.source = null } - - if (remap) { - SourceMapLocationRemapper(sourceMap).remap(parsedProgram) - val codeWithRemappedLines = parsedProgram.toStringWithLineNumbers() - assertEquals(codeWithLines, codeWithRemappedLines) - } - } - - private fun removeLocationFromBlocks(program: JsProgram) { - program.globalBlock.accept(object : RecursiveJsVisitor() { - override fun visitBlock(x: JsBlock) { - super.visitBlock(x) - x.source = null - } - }) - } + }) } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/NodeJsGeneratorHandler.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/NodeJsGeneratorHandler.kt index e2ad50bfdba..87b1d739b25 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/NodeJsGeneratorHandler.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/handlers/NodeJsGeneratorHandler.kt @@ -44,35 +44,33 @@ class NodeJsGeneratorHandler(testServices: TestServices) : AbstractJsArtifactsCo FileUtil.writeToFile(File(nodeRunnerName), nodeRunnerText) } - companion object { - fun generateNodeRunner(files: Collection, dir: File, moduleName: String, ignored: Boolean, testPackage: String?): String { - val filesToLoad = files.map { - val relativePath = when { - it.startsWith(dir.absolutePath) -> FileUtil.getRelativePath(dir, File(it))!! - else -> it - } - "\"${relativePath.replace(File.separatorChar, '/')}\"" + private fun generateNodeRunner(files: Collection, dir: File, moduleName: String, ignored: Boolean, testPackage: String?): String { + val filesToLoad = files.map { + val relativePath = when { + it.startsWith(dir.absolutePath) -> FileUtil.getRelativePath(dir, File(it))!! + else -> it } - val fqn = testPackage?.let { ".$it" } ?: "" - val loadAndRun = "load([${filesToLoad.joinToString(",")}], '$moduleName')$fqn.box()" - - val sb = StringBuilder() - sb.append("module.exports = function(load) {\n") - if (ignored) { - sb.append(" try {\n") - sb.append(" var result = $loadAndRun;\n") - sb.append(" if (result != 'OK') return 'OK';") - sb.append(" return 'fail: expected test failure';\n") - sb.append(" }\n") - sb.append(" catch (e) {\n") - sb.append(" return 'OK';\n") - sb.append("}\n") - } else { - sb.append(" return $loadAndRun;\n") - } - sb.append("};\n") - - return sb.toString() + "\"${relativePath.replace(File.separatorChar, '/')}\"" } + val fqn = testPackage?.let { ".$it" } ?: "" + val loadAndRun = "load([${filesToLoad.joinToString(",")}], '$moduleName')$fqn.box()" + + val sb = StringBuilder() + sb.append("module.exports = function(load) {\n") + if (ignored) { + sb.append(" try {\n") + sb.append(" var result = $loadAndRun;\n") + sb.append(" if (result != 'OK') return 'OK';") + sb.append(" return 'fail: expected test failure';\n") + sb.append(" }\n") + sb.append(" catch (e) {\n") + sb.append(" return 'OK';\n") + sb.append("}\n") + } else { + sb.append(" return $loadAndRun;\n") + } + sb.append("};\n") + + return sb.toString() } } \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/utils/JsIrIncrementalDataProvider.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/utils/JsIrIncrementalDataProvider.kt index cb8b25c6232..a20f27e5e2f 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/utils/JsIrIncrementalDataProvider.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/utils/JsIrIncrementalDataProvider.kt @@ -6,12 +6,10 @@ package org.jetbrains.kotlin.js.testNew.utils import org.jetbrains.kotlin.config.CompilerConfiguration -import org.jetbrains.kotlin.ir.backend.js.ic.ModuleCache -import org.jetbrains.kotlin.ir.backend.js.ic.ModuleName -import org.jetbrains.kotlin.ir.backend.js.ic.rebuildCacheForDirtyFiles +import org.jetbrains.kotlin.ir.backend.js.ic.* import org.jetbrains.kotlin.ir.backend.js.moduleName import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl -import org.jetbrains.kotlin.js.test.TestModuleCache +import org.jetbrains.kotlin.ir.util.IdSignature import org.jetbrains.kotlin.konan.properties.propertyList import org.jetbrains.kotlin.library.KLIB_PROPERTY_DEPENDS import org.jetbrains.kotlin.library.KotlinLibrary @@ -24,6 +22,100 @@ import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurato import org.jetbrains.kotlin.test.services.jsLibraryProvider import java.io.File +class TestModuleCache(val moduleName: String, val files: MutableMap) { + + constructor(moduleName: String) : this(moduleName, mutableMapOf()) + + fun cacheProvider(): PersistentCacheProvider { + return object : PersistentCacheProvider { + override fun fileFingerPrint(path: String): Hash { + return 0L + } + + override fun serializedParts(path: String): SerializedIcDataForFile { + error("Is not supported") + } + + override fun inlineGraphForFile(path: String, sigResolver: (Int) -> IdSignature): Collection> { + error("Is not supported") + } + + override fun inlineHashes(path: String, sigResolver: (Int) -> IdSignature): Map { + error("Is not supported") + } + + override fun allInlineHashes(sigResolver: (String, Int) -> IdSignature): Map { + error("Is not supported") + } + + override fun binaryAst(path: String): ByteArray? { + return files[path]?.ast ?: ByteArray(0) + } + + override fun dts(path: String): ByteArray? { + return files[path]?.dts + } + + override fun sourceMap(path: String): ByteArray? { + return files[path]?.sourceMap + } + } + } + + fun cacheConsumer(): PersistentCacheConsumer { + return object : PersistentCacheConsumer { + override fun commitInlineFunctions( + path: String, + hashes: Collection>, + sigResolver: (IdSignature) -> Int + ) { + + } + + override fun commitFileFingerPrint(path: String, fingerprint: Hash) { + + } + + override fun commitInlineGraph( + path: String, + hashes: Collection>, + sigResolver: (IdSignature) -> Int + ) { + + } + + override fun commitICCacheData(path: String, icData: SerializedIcDataForFile) { + + } + + override fun commitBinaryAst(path: String, astData: ByteArray) { + val storage = files.getOrPut(path) { FileCache(path, null, null, null) } + storage.ast = astData + } + + override fun commitBinaryDts(path: String, dstData: ByteArray) { + val storage = files.getOrPut(path) { FileCache(path, null, null, null) } + storage.dts = dstData + } + + override fun commitSourceMap(path: String, mapData: ByteArray) { + val storage = files.getOrPut(path) { FileCache(path, null, null, null) } + storage.sourceMap = mapData + } + + override fun invalidateForFile(path: String) { + files.remove(path) + } + + override fun commitLibraryPath(libraryPath: String) { + + } + } + } + + fun createModuleCache(): ModuleCache = ModuleCache(moduleName, files) +} + class JsIrIncrementalDataProvider(private val testServices: TestServices) : TestService { private val fullRuntimeKlib: String = System.getProperty("kotlin.js.full.stdlib.path") private val defaultRuntimeKlib = System.getProperty("kotlin.js.reduced.stdlib.path") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/utils/RunnerUtils.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/utils/RunnerUtils.kt index f0d100d312e..1b7587e51a0 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testNew/utils/RunnerUtils.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testNew/utils/RunnerUtils.kt @@ -25,6 +25,9 @@ import java.io.File private const val MODULE_EMULATION_FILE = "${JsEnvironmentConfigurator.TEST_DATA_DIR_PATH}/moduleEmulation.js" +val File.esModulesSubDir: File + get() = File(absolutePath + "_esm") + private fun extractJsFiles(testServices: TestServices, modules: List): Pair, List> { val outputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices)