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 index 3d4294eb8ef..346262913b3 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt @@ -18,7 +18,6 @@ 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.CharsetToolkit import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.PsiManager @@ -56,7 +55,6 @@ import org.jetbrains.kotlin.test.KotlinTestUtils.TestFileFactory import org.jetbrains.kotlin.test.KotlinTestWithEnvironment import org.jetbrains.kotlin.test.TargetBackend import org.jetbrains.kotlin.utils.DFS -import org.mozilla.javascript.Context import java.io.ByteArrayOutputStream import java.io.Closeable import java.io.File @@ -76,6 +74,8 @@ abstract class BasicBoxTest( protected open fun getOutputPrefixFile(testFilePath: String): File? = null protected open fun getOutputPostfixFile(testFilePath: String): File? = null + protected open val runMinifierByDefault: Boolean = false + fun doTest(filePath: String) { doTest(filePath, "OK", MainCallParameters.noCall()) } @@ -159,8 +159,46 @@ abstract class BasicBoxTest( performAdditionalChecks(generatedJsFiles.map { it.first }, outputPrefixFile, outputPostfixFile) - minifyAndRun(File(File(outputDir, "min"), file.nameWithoutExtension), allJsFiles, generatedJsFiles, expectedResult, - mainModuleName, testFactory.testPackage, TEST_FUNCTION, withModuleSystem) + val expectedReachableNodesMatcher = EXPECTED_REACHABLE_NODES.matcher(fileContent) + val expectedReachableNodesFound = expectedReachableNodesMatcher.find() + val skipMinification = System.getProperty("kotlin.js.skipMinificationTest", "false").toBoolean() + if (!skipMinification && + (runMinifierByDefault || expectedReachableNodesFound) && + !SKIP_MINIFICATION.matcher(fileContent).find() + ) { + val thresholdChecker: (Int) -> Unit = { reachableNodesCount -> + val replacement = "// $EXPECTED_REACHABLE_NODES_DIRECTIVE: $reachableNodesCount" + if (!expectedReachableNodesFound) { + file.writeText("$replacement\n$fileContent") + fail("The number of expected reachable nodes was not set. Actual reachable nodes: $reachableNodesCount") + } + else { + val expectedReachableNodes = expectedReachableNodesMatcher.group(1).toInt() + val minThreshold = expectedReachableNodes * 9 / 10 + val maxThreshold = expectedReachableNodes * 11 / 10 + if (reachableNodesCount < minThreshold || reachableNodesCount > maxThreshold) { + + val newText = fileContent.substring(0, expectedReachableNodesMatcher.start()) + + replacement + + fileContent.substring(expectedReachableNodesMatcher.end()) + file.writeText(newText) + fail("Number of reachable nodes ($reachableNodesCount) does not fit into expected range " + + "[$minThreshold; $maxThreshold]") + } + } + } + + minifyAndRun( + workDir = File(File(outputDir, "min"), file.nameWithoutExtension), + allJsFiles = allJsFiles, + generatedJsFiles = generatedJsFiles, + expectedResult = expectedResult, + testModuleName = mainModuleName, + testPackage = testFactory.testPackage, + testFunction = TEST_FUNCTION, + withModuleSystem = withModuleSystem, + minificationThresholdChecker = thresholdChecker) + } } } @@ -407,7 +445,8 @@ abstract class BasicBoxTest( private fun minifyAndRun( workDir: File, allJsFiles: List, generatedJsFiles: List>, - expectedResult: String, testModuleName: String, testPackage: String?, testFunction: String, withModuleSystem: Boolean + expectedResult: String, testModuleName: String, testPackage: String?, testFunction: String, withModuleSystem: Boolean, + minificationThresholdChecker: (Int) -> Unit ) { val kotlinJsLib = DIST_DIR_JS_PATH + "kotlin.js" val kotlinTestJsLib = DIST_DIR_JS_PATH + "kotlin-test.js" @@ -425,37 +464,28 @@ abstract class BasicBoxTest( 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.kotlin.io.output.buffer", "kotlin-test.kotlin.test.overrideAsserter_wbnzx$" ) val allFilesToMinify = filesToMinify.values + kotlinJsInputFile + kotlinTestJsInputFile - val reachableNodes = DeadCodeElimination.run(allFilesToMinify, additionalReachableNodes) { } - if (reachableNodes.size > 1500) println("!!!") - println(reachableNodes.size.toString() + ": " + workDir.path) + val dceResult = DeadCodeElimination.run(allFilesToMinify, additionalReachableNodes) { } + + val moduleDeclarations = dceResult.globalScope.member("module").member("exports") + val reachableNodes = dceResult.reachableNodes.filter { moduleDeclarations !in generateSequence(it) { it.qualifier?.parent } } + minificationThresholdChecker(reachableNodes.size) val runList = mutableListOf() runList += kotlinJsLibOutput runList += kotlinTestJsLibOutput + runList += TEST_DATA_DIR_PATH + "nashorn-polyfills.js" + runList += allJsFiles.map { filesToMinify[it]?.outputPath ?: it } - val context = Context.enter() - context.languageVersion = Context.VERSION_1_8 - context.optimizationLevel = -1 - val scope = context.initStandardObjects() - - fun applyFile(fileToRun: String) { - val code = FileUtil.loadFile(File(fileToRun), CharsetToolkit.UTF8, true) - context.evaluateString(scope, code, fileToRun, 1, null) + val result = engineForMinifier.runAndRestoreContext { + runList.forEach(this::loadFile) + overrideAsserter() + eval(NashornJsTestChecker.SETUP_KOTLIN_OUTPUT) + runTestFunction(testModuleName, testPackage, testFunction, withModuleSystem) } - - applyFile(DIST_DIR_JS_PATH + RhinoUtils.RHINO_POLYFILLS_RELATIVE_PATH) - applyFile(kotlinJsLibOutput) - applyFile(kotlinTestJsLibOutput) - context.evaluateString(scope, "function ok() {}", "setup assertions", 0, null) - context.evaluateString(scope, RhinoUtils.SETUP_KOTLIN_OUTPUT, "setup kotlin output", 0, null) - - allJsFiles.map { filesToMinify[it]?.outputName ?: it }.forEach(::applyFile) - - val checker = RhinoFunctionResultChecker(testModuleName, testPackage, testFunction, expectedResult, withModuleSystem) - checker.runChecks(context, scope) + TestCase.assertEquals(expectedResult, result) } private inner class TestFileFactoryImpl : TestFileFactory, Closeable { @@ -543,6 +573,9 @@ abstract class BasicBoxTest( private val NO_MODULE_SYSTEM_PATTERN = Pattern.compile("^// *NO_JS_MODULE_SYSTEM", 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 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 AST_EXTENSION = "jsast" private val METADATA_EXTENSION = "jsmeta" @@ -554,5 +587,7 @@ abstract class BasicBoxTest( private val OLD_MODULE_SUFFIX = "-old" const val KOTLIN_TEST_INTERNAL = "\$kotlin_test_internal\$" + + private val engineForMinifier = createScriptEngine() } } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/NashornJsTestChecker.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/NashornJsTestChecker.kt index f0793b38ef3..e145bd58513 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/NashornJsTestChecker.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/NashornJsTestChecker.kt @@ -27,14 +27,63 @@ fun createScriptEngine(): ScriptEngine = // TODO use "-strict" NashornScriptEngineFactory().getScriptEngine("--language=es5", "--no-java", "--no-syntax-extensions") -private fun ScriptEngine.loadFile(path: String) { +fun ScriptEngine.overrideAsserter() { + eval("this['kotlin-test'].kotlin.test.overrideAsserter_wbnzx$(new this['kotlin-test'].kotlin.test.DefaultAsserter());") +} + +fun ScriptEngine.runTestFunction( + testModuleName: String, testPackageName: String?, testFunctionName: String, + withModuleSystem: Boolean +): Any? { + val testModule = + when { + withModuleSystem -> + eval(BasicBoxTest.Companion.KOTLIN_TEST_INTERNAL + ".require('" + testModuleName + "')") + else -> + get(testModuleName) + } + testModule as ScriptObjectMirror + + val testPackage = + when { + testPackageName === null -> + testModule + testPackageName.contains(".") -> + testPackageName.split(".").fold(testModule) { p, part -> p[part] as ScriptObjectMirror } + else -> + testModule[testPackageName]!! + } + + return (this as Invocable).invokeMethod(testPackage, testFunctionName) +} + +fun ScriptEngine.loadFile(path: String) { eval("load('${path.replace('\\', '/')}');") } +fun ScriptEngine.runAndRestoreContext( + f: ScriptEngine.() -> Any? +): Any? { + val globalObject = eval("this") as ScriptObjectMirror + val before = globalObject.toMapWithAllMembers() + + return try { + this.f() + } + finally { + val after = globalObject.toMapWithAllMembers() + val diff = after.entries - before.entries + + diff.forEach { + globalObject.put(it.key, before[it.key] ?: ScriptRuntime.UNDEFINED) + } + } +} + private fun ScriptObjectMirror.toMapWithAllMembers(): Map = getOwnKeys(true).associate { it to this[it] } object NashornJsTestChecker { - private val SETUP_KOTLIN_OUTPUT = "kotlin.kotlin.io.output = new kotlin.kotlin.io.BufferedOutput();" + val SETUP_KOTLIN_OUTPUT = "kotlin.kotlin.io.output = new kotlin.kotlin.io.BufferedOutput();" private val GET_KOTLIN_OUTPUT = "kotlin.kotlin.io.output.buffer;" private val engine = createScriptEngineForTest() @@ -68,47 +117,17 @@ object NashornJsTestChecker { testFunctionName: String, withModuleSystem: Boolean ) = run(files) { - val testModule = - when { - withModuleSystem -> - engine.eval(BasicBoxTest.Companion.KOTLIN_TEST_INTERNAL + ".require('" + testModuleName + "')") as ScriptObjectMirror - else -> - engine.get(testModuleName) as ScriptObjectMirror - } - - val testPackage = - when { - testPackageName === null -> - testModule - testPackageName.contains(".") -> - testPackageName.split(".").fold(testModule) { p, part -> p[part] as ScriptObjectMirror } - else -> - testModule[testPackageName]!! - } - - (engine as Invocable).invokeMethod(testPackage, testFunctionName) + runTestFunction(testModuleName, testPackageName, testFunctionName, withModuleSystem) } private fun run( files: List, f: ScriptEngine.() -> Any? ): Any? { - val globalObject = engine.eval("this") as ScriptObjectMirror - val before = globalObject.toMapWithAllMembers() - engine.eval(SETUP_KOTLIN_OUTPUT) - - try { + return engine.runAndRestoreContext { files.forEach(engine::loadFile) - return engine.f() - } - finally { - val after = globalObject.toMapWithAllMembers() - val diff = after.entries - before.entries - - diff.forEach { - globalObject.put(it.key, before[it.key] ?: ScriptRuntime.UNDEFINED) - } + engine.f() } } @@ -121,7 +140,7 @@ object NashornJsTestChecker { BasicBoxTest.DIST_DIR_JS_PATH + "kotlin-test.js" ).forEach(engine::loadFile) - engine.eval("this['kotlin-test'].kotlin.test.overrideAsserter_wbnzx$(new this['kotlin-test'].kotlin.test.DefaultAsserter());") + engine.overrideAsserter() return engine } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt index 44dee39f2e3..a30d3992c57 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/abstractClassesForGeneratedTests.kt @@ -42,7 +42,9 @@ abstract class AbstractInlineDefaultValuesTests : BorrowedInlineTest("defaultVal abstract class AbstractBoxJsTest : BasicBoxTest( BasicBoxTest.TEST_DATA_DIR_PATH + "box/", BasicBoxTest.TEST_DATA_DIR_PATH + "out/box/" -) +) { + override val runMinifierByDefault: Boolean = true +} abstract class AbstractJsCodegenBoxTest : BasicBoxTest( "compiler/testData/codegen/box/",