[JS TESTS] Drop old basic tests

This commit is contained in:
Ivan Kylchik
2021-11-09 16:33:27 +03:00
committed by teamcity
parent 33c799b6d9
commit 8c2e9fdc89
15 changed files with 318 additions and 1967 deletions
@@ -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<TestModule, TestFile>, 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"
}
}
File diff suppressed because it is too large Load Diff
@@ -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<String, TestModuleCache?>(
File(fullRuntimeKlib).absolutePath to null,
File(kotlinTestKLib).absolutePath to null,
File(defaultRuntimeKlib).absolutePath to null
)
class TestModuleCache(val moduleName: String, val files: MutableMap<String, FileCache>) {
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<Pair<IdSignature, TransHash>> {
error("Is not supported")
}
override fun inlineHashes(path: String, sigResolver: (Int) -> IdSignature): Map<IdSignature, TransHash> {
error("Is not supported")
}
override fun allInlineHashes(sigResolver: (String, Int) -> IdSignature): Map<IdSignature, TransHash> {
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<Pair<IdSignature, TransHash>>,
sigResolver: (IdSignature) -> Int
) {
}
override fun commitFileFingerPrint(path: String, fingerprint: Hash) {
}
override fun commitInlineGraph(
path: String,
hashes: Collection<Pair<IdSignature, TransHash>>,
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<String, String>()
private val cachedDependencies = mutableMapOf<String, Collection<String>>()
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<TranslationUnit>,
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<String, TestModuleCache>,
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<String>,
friendPaths: List<String>,
testPackage: String?,
testFunction: String,
propertyLazyInitialization: Boolean,
skipMangleVerification: Boolean,
safeExternalBoolean: Boolean,
safeExternalBooleanDiagnostic: RuntimeDiagnostic?,
dceDriven: Boolean,
granularity: JsGenerationGranularity,
dirtyFilesToRecompile: Set<String>? = 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<String>?,
): 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<TranslationUnit>,
dirtyFilesToRecompile: Set<String>? = 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<String>?
) {
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<String>,
module: TestModule,
kotlinFiles: List<TestFile>,
dependencies: List<String>,
allDependencies: List<String>,
friends: List<String>,
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<String, TestModuleCache>
) {
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<String>()
val oldBinaryAsts = mutableMapOf<String, ByteArray>()
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<String>?,
allKlibPaths: Collection<String>,
config: JsConfig,
icCache: MutableMap<String, TestModuleCache>
): 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<ModuleName, KotlinLibrary> = 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<AnyNamedPhase>): Set<AnyNamedPhase> {
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<String>()
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<String>,
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")
@@ -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<String>): List<KtFile> {
return fileNames.map { this@createPsiFiles.createPsiFile(it) }
}
companion object {
const val TEST_DATA_DIR_PATH = "js/js.translator/testData/"
const val TEST_MODULE = "main"
@@ -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)
@@ -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"
@@ -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"
@@ -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
@@ -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<String>, 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<TranslationUnit>, targetBackend: TargetBackend, onFail: (String) -> Unit
) {
processJsProgram(program, units.filterIsInstance<TranslationUnit.SourceFile>().map { it.file.text }, targetBackend, onFail)
}
fun processJsProgram(program: JsProgram, psiFiles: List<String>, 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)
}
})
}
}
})
}
}
@@ -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
@@ -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<String>,
generatedJsFiles: List<Pair<String, String>>,
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<String>()
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<String>,
generatedJsFiles: List<Pair<String, String>>,
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<String>()
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)
}
}
@@ -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
}
})
}
})
}
}
@@ -44,35 +44,33 @@ class NodeJsGeneratorHandler(testServices: TestServices) : AbstractJsArtifactsCo
FileUtil.writeToFile(File(nodeRunnerName), nodeRunnerText)
}
companion object {
fun generateNodeRunner(files: Collection<String>, 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<String>, 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()
}
}
@@ -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<String, FileCache>) {
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<Pair<IdSignature, TransHash>> {
error("Is not supported")
}
override fun inlineHashes(path: String, sigResolver: (Int) -> IdSignature): Map<IdSignature, TransHash> {
error("Is not supported")
}
override fun allInlineHashes(sigResolver: (String, Int) -> IdSignature): Map<IdSignature, TransHash> {
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<Pair<IdSignature, TransHash>>,
sigResolver: (IdSignature) -> Int
) {
}
override fun commitFileFingerPrint(path: String, fingerprint: Hash) {
}
override fun commitInlineGraph(
path: String,
hashes: Collection<Pair<IdSignature, TransHash>>,
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")
@@ -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<TestModule>): Pair<List<String>, List<String>> {
val outputDir = JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices)