diff --git a/.idea/kotlinTestDataPluginTestDataPaths.xml b/.idea/kotlinTestDataPluginTestDataPaths.xml index dfda4788d3c..2cdabc10bb9 100644 --- a/.idea/kotlinTestDataPluginTestDataPaths.xml +++ b/.idea/kotlinTestDataPluginTestDataPaths.xml @@ -18,6 +18,13 @@ + + + + + + diff --git a/buildSrc/src/main/kotlin/common-configuration.gradle.kts b/buildSrc/src/main/kotlin/common-configuration.gradle.kts index ae0bfc79d3d..090d0292fa4 100644 --- a/buildSrc/src/main/kotlin/common-configuration.gradle.kts +++ b/buildSrc/src/main/kotlin/common-configuration.gradle.kts @@ -155,7 +155,9 @@ fun Project.configureKotlinCompilationOptions() { // Requires serialization plugin ":wasm:wasm.ir", // Uses multiplatform - ":kotlin-stdlib-jvm-minimal-for-test" + ":kotlin-stdlib-jvm-minimal-for-test", + // Requires serialization plugin + ":js:js.tests", ) // TODO: fix remaining warnings and remove this property. diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirSteppingTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirSteppingTestGenerated.java index 46aa4840735..16f6af770e7 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirSteppingTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirSteppingTestGenerated.java @@ -319,6 +319,12 @@ public class FirSteppingTestGenerated extends AbstractFirSteppingTest { runTest("compiler/testData/debug/stepping/localProperty.kt"); } + @Test + @TestMetadata("multiModule.kt") + public void testMultiModule() throws Exception { + runTest("compiler/testData/debug/stepping/multiModule.kt"); + } + @Test @TestMetadata("multilineExpression.kt") public void testMultilineExpression() throws Exception { diff --git a/compiler/testData/debug/stepping/assertion.kt b/compiler/testData/debug/stepping/assertion.kt index 9fbca68455c..ed6e2b5914d 100644 --- a/compiler/testData/debug/stepping/assertion.kt +++ b/compiler/testData/debug/stepping/assertion.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // FILE: test.kt public val MASSERTIONS_ENABLED: Boolean = true @@ -30,17 +31,17 @@ fun box(): String { } // EXPECTATIONS -// test.kt:24 box -// test.kt:15 box -// test.kt:16 box -// test.kt:3 getMASSERTIONS_ENABLED +// test.kt:25 box // test.kt:16 box // test.kt:17 box -// test.kt:21 box -// test.kt:25 box -// test.kt:6 box -// test.kt:3 getMASSERTIONS_ENABLED -// test.kt:6 box +// test.kt:4 getMASSERTIONS_ENABLED +// test.kt:17 box +// test.kt:18 box +// test.kt:22 box +// test.kt:26 box // test.kt:7 box -// test.kt:12 box -// test.kt:29 box +// test.kt:4 getMASSERTIONS_ENABLED +// test.kt:7 box +// test.kt:8 box +// test.kt:13 box +// test.kt:30 box diff --git a/compiler/testData/debug/stepping/functionCallWithInlinedLambdaParam.kt b/compiler/testData/debug/stepping/functionCallWithInlinedLambdaParam.kt index b88e09f2b3b..7093040ccc9 100644 --- a/compiler/testData/debug/stepping/functionCallWithInlinedLambdaParam.kt +++ b/compiler/testData/debug/stepping/functionCallWithInlinedLambdaParam.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // FILE: test.kt fun box() { diff --git a/compiler/testData/debug/stepping/if.kt b/compiler/testData/debug/stepping/if.kt index 5673b067ead..b8d2362b9fd 100644 --- a/compiler/testData/debug/stepping/if.kt +++ b/compiler/testData/debug/stepping/if.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // FILE: test.kt fun box(): Int { if ( @@ -14,11 +15,11 @@ inline fun getB(): Int { } // EXPECTATIONS -// test.kt:4 box -// test.kt:13 box // test.kt:5 box -// test.kt:10 getA -// test.kt:5 box -// test.kt:7 box -// test.kt:13 box -// test.kt:7 box +// test.kt:14 box +// test.kt:6 box +// test.kt:11 getA +// test.kt:6 box +// test.kt:8 box +// test.kt:14 box +// test.kt:8 box diff --git a/compiler/testData/debug/stepping/inTheEndOfLambdaArgumentOfInlineCall.kt b/compiler/testData/debug/stepping/inTheEndOfLambdaArgumentOfInlineCall.kt index c482f310249..de3c7af48f1 100644 --- a/compiler/testData/debug/stepping/inTheEndOfLambdaArgumentOfInlineCall.kt +++ b/compiler/testData/debug/stepping/inTheEndOfLambdaArgumentOfInlineCall.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // FILE: test.kt fun box() { diff --git a/compiler/testData/debug/stepping/inlineSimpleCall.kt b/compiler/testData/debug/stepping/inlineSimpleCall.kt index 96d1b46c153..fe3edd0c15b 100644 --- a/compiler/testData/debug/stepping/inlineSimpleCall.kt +++ b/compiler/testData/debug/stepping/inlineSimpleCall.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // FILE: test.kt inline fun inlineFun(s: () -> Unit) { diff --git a/compiler/testData/debug/stepping/lambdaStepInlineWithDefaults.kt b/compiler/testData/debug/stepping/lambdaStepInlineWithDefaults.kt index c94f68281df..2ca8ccb0cce 100644 --- a/compiler/testData/debug/stepping/lambdaStepInlineWithDefaults.kt +++ b/compiler/testData/debug/stepping/lambdaStepInlineWithDefaults.kt @@ -1,4 +1,4 @@ - +// IGNORE_BACKEND: JS_IR // FILE: test.kt inline fun foo(stringMaker: () -> String = { "OK" }): String { return stringMaker() diff --git a/compiler/testData/debug/stepping/linenumberForOneParametersArgumentCall.kt b/compiler/testData/debug/stepping/linenumberForOneParametersArgumentCall.kt index c6ce92c52c0..18db2f7f68f 100644 --- a/compiler/testData/debug/stepping/linenumberForOneParametersArgumentCall.kt +++ b/compiler/testData/debug/stepping/linenumberForOneParametersArgumentCall.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // FILE: test.kt fun box() { @@ -12,11 +13,11 @@ inline fun lookAtMe(f: (String) -> Unit) { } // EXPECTATIONS -// test.kt:4 box -// test.kt:10 box -// test.kt:11 box // test.kt:5 box -// test.kt:6 box // test.kt:11 box // test.kt:12 box -// test.kt:7 box \ No newline at end of file +// test.kt:6 box +// test.kt:7 box +// test.kt:12 box +// test.kt:13 box +// test.kt:8 box diff --git a/compiler/testData/debug/stepping/localFunction.kt b/compiler/testData/debug/stepping/localFunction.kt index 69c17b35a51..2bd1bb937a8 100644 --- a/compiler/testData/debug/stepping/localFunction.kt +++ b/compiler/testData/debug/stepping/localFunction.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // FILE: test.kt fun box() { diff --git a/compiler/testData/debug/stepping/localFunctionWIthOnelineExpressionBody.kt b/compiler/testData/debug/stepping/localFunctionWIthOnelineExpressionBody.kt index 7be976c26ad..b934f39cc06 100644 --- a/compiler/testData/debug/stepping/localFunctionWIthOnelineExpressionBody.kt +++ b/compiler/testData/debug/stepping/localFunctionWIthOnelineExpressionBody.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // FILE: test.kt fun box() { @@ -9,17 +10,17 @@ fun box() { } // EXPECTATIONS -// test.kt:4 box -// EXPECTATIONS JVM // test.kt:5 box -// EXPECTATIONS -// test.kt:6 box -// test.kt:7 box // EXPECTATIONS JVM -// test.kt:5 invoke -// EXPECTATIONS JVM_IR -// test.kt:5 box$bar +// test.kt:6 box // EXPECTATIONS // test.kt:7 box // test.kt:8 box -// test.kt:9 box \ No newline at end of file +// EXPECTATIONS JVM +// test.kt:6 invoke +// EXPECTATIONS JVM_IR +// test.kt:6 box$bar +// EXPECTATIONS +// test.kt:8 box +// test.kt:9 box +// test.kt:10 box diff --git a/compiler/testData/debug/stepping/multiModule.kt b/compiler/testData/debug/stepping/multiModule.kt new file mode 100644 index 00000000000..cd8d6ad14b5 --- /dev/null +++ b/compiler/testData/debug/stepping/multiModule.kt @@ -0,0 +1,25 @@ +// MODULE: lib +// FILE: a.kt + +fun a() = "a" + +// FILE: b.kt + +fun b() = "b" + +// MODULE: main(lib) +// FILE: test.kt + +fun box() { + a() + b() +} + +// EXPECTATIONS +// test.kt:14 box +// a.kt:4 a +// test.kt:14 box +// test.kt:15 box +// b.kt:8 b +// test.kt:15 box +// test.kt:16 box diff --git a/compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt b/compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt index dbe665cdc16..13d2d9939ef 100644 --- a/compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt +++ b/compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // FILE: test.kt fun box() { diff --git a/compiler/testData/debug/stepping/simpleDefaultArgWithInline.kt b/compiler/testData/debug/stepping/simpleDefaultArgWithInline.kt index a599b04f45b..07cf74c1522 100644 --- a/compiler/testData/debug/stepping/simpleDefaultArgWithInline.kt +++ b/compiler/testData/debug/stepping/simpleDefaultArgWithInline.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // FILE: test.kt inline fun ifoo(ok: String = "OK"): String { @@ -15,11 +16,11 @@ fun box(): String { // FORCE_STEP_INTO // EXPECTATIONS -// test.kt:12 box -// test.kt:3 box -// test.kt:4 box // test.kt:13 box -// test.kt:7 ifoo2$default (synthetic) -// test.kt:8 ifoo2 -// test.kt:7 ifoo2$default (synthetic) -// test.kt:13 box \ No newline at end of file +// test.kt:4 box +// test.kt:5 box +// test.kt:14 box +// test.kt:8 ifoo2$default (synthetic) +// test.kt:9 ifoo2 +// test.kt:8 ifoo2$default (synthetic) +// test.kt:14 box diff --git a/compiler/testData/debug/stepping/simpleSmap.kt b/compiler/testData/debug/stepping/simpleSmap.kt index 3f7d151c408..c28d890bc24 100644 --- a/compiler/testData/debug/stepping/simpleSmap.kt +++ b/compiler/testData/debug/stepping/simpleSmap.kt @@ -1,3 +1,5 @@ +// IGNORE_BACKEND: JS_IR + // FILE: test.kt inline fun inlineFun(s: () -> Unit) { @@ -11,10 +13,10 @@ fun box() { } // EXPECTATIONS -// test.kt:8 box -// test.kt:4 box -// test.kt:9 box // test.kt:10 box -// test.kt:4 box -// test.kt:5 box +// test.kt:6 box // test.kt:11 box +// test.kt:12 box +// test.kt:6 box +// test.kt:7 box +// test.kt:13 box diff --git a/compiler/testData/debug/stepping/suspendFunWithSuspendLambdaParameter.kt b/compiler/testData/debug/stepping/suspendFunWithSuspendLambdaParameter.kt index a66e2f80001..2c176c557d8 100644 --- a/compiler/testData/debug/stepping/suspendFunWithSuspendLambdaParameter.kt +++ b/compiler/testData/debug/stepping/suspendFunWithSuspendLambdaParameter.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // WITH_STDLIB // FILE: test.kt suspend fun foo(block: suspend Long.() -> String): String { diff --git a/compiler/testData/debug/stepping/tryCatchFinally.kt b/compiler/testData/debug/stepping/tryCatchFinally.kt index 6d481693b35..5fd8bcf8fad 100644 --- a/compiler/testData/debug/stepping/tryCatchFinally.kt +++ b/compiler/testData/debug/stepping/tryCatchFinally.kt @@ -1,3 +1,4 @@ +// IGNORE_BACKEND: JS_IR // FILE: test.kt fun foo() { @@ -89,4 +90,4 @@ fun box() { // test.kt:18 foo // test.kt:12 foo // test.kt:19 foo -// test.kt:38 box \ No newline at end of file +// test.kt:38 box diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrSteppingTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrSteppingTestGenerated.java index 1774aeb42ce..c9aea3ab2c1 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrSteppingTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrSteppingTestGenerated.java @@ -319,6 +319,12 @@ public class IrSteppingTestGenerated extends AbstractIrSteppingTest { runTest("compiler/testData/debug/stepping/localProperty.kt"); } + @Test + @TestMetadata("multiModule.kt") + public void testMultiModule() throws Exception { + runTest("compiler/testData/debug/stepping/multiModule.kt"); + } + @Test @TestMetadata("multilineExpression.kt") public void testMultilineExpression() throws Exception { diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/SteppingTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/SteppingTestGenerated.java index 976666dc02e..b80c29af270 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/SteppingTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/SteppingTestGenerated.java @@ -319,6 +319,12 @@ public class SteppingTestGenerated extends AbstractSteppingTest { runTest("compiler/testData/debug/stepping/localProperty.kt"); } + @Test + @TestMetadata("multiModule.kt") + public void testMultiModule() throws Exception { + runTest("compiler/testData/debug/stepping/multiModule.kt"); + } + @Test @TestMetadata("multilineExpression.kt") public void testMultilineExpression() throws Exception { diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index b007f2ab5f3..69087c29f6d 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -3298,6 +3298,11 @@ + + + + + @@ -3308,6 +3313,11 @@ + + + + + @@ -3344,6 +3354,11 @@ + + + + + @@ -3376,6 +3391,11 @@ + + + + + @@ -3414,6 +3434,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -3436,6 +3476,11 @@ + + + + + @@ -3458,6 +3503,11 @@ + + + + + @@ -3484,6 +3534,11 @@ + + + + + @@ -3530,6 +3585,11 @@ + + + + + @@ -3556,6 +3616,11 @@ + + + + + @@ -3572,6 +3637,11 @@ + + + + + @@ -3618,6 +3688,11 @@ + + + + + @@ -3634,6 +3709,11 @@ + + + + + @@ -3670,6 +3750,11 @@ + + + + + @@ -3680,6 +3765,11 @@ + + + + + @@ -3694,6 +3784,16 @@ + + + + + + + + + + @@ -3774,6 +3874,11 @@ + + + + + @@ -3816,6 +3921,11 @@ + + + + + @@ -3830,12 +3940,32 @@ + + + + + + + + + + + + + + + + + + + + @@ -7797,6 +7927,11 @@ + + + + + @@ -7841,6 +7976,11 @@ + + + + + @@ -7939,6 +8079,16 @@ + + + + + + + + + + @@ -8093,6 +8243,11 @@ + + + + + @@ -8133,6 +8288,11 @@ + + + + + @@ -8191,6 +8351,11 @@ + + + + + @@ -8217,6 +8382,11 @@ + + + + + diff --git a/gradle/versions.properties b/gradle/versions.properties index eaa26878dbf..ef47994ea3b 100644 --- a/gradle/versions.properties +++ b/gradle/versions.properties @@ -43,14 +43,18 @@ versions.kotlinx-collections-immutable=0.3.1 versions.kotlinx-coroutines-core-jvm=1.5.0 versions.kotlinx-coroutines-core=1.5.0 versions.kotlinx-metadata-jvm=0.5.0 +versions.kotlinx-serialization-json=1.3.3 versions.ktor-network=1.0.1 versions.ktor-server-test-host=1.1.5 versions.ktor-server-core=1.6.7 versions.ktor-server-netty=1.6.7 versions.ktor-client-mock=1.6.7 +versions.ktor-client-core=2.0.2 +versions.ktor-client-cio=2.0.2 +versions.ktor-client-websockets=2.0.2 versions.native-platform=0.14 versions.protobuf=2.6.1 versions.r8=2.2.64 versions.robolectric=4.0 versions.nodejs=16.14.2 -versions.v8=10.2.9 \ No newline at end of file +versions.v8=10.2.9 diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt index 33ac094893c..6ecedbf14da 100644 --- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMap.kt @@ -21,6 +21,9 @@ import java.io.* class SourceMap(val sourceContentResolver: (String) -> Reader?) { val groups = mutableListOf() + @Suppress("UNUSED") // For use in the debugger + fun debugToString() = ByteArrayOutputStream().also { debug(PrintStream(it)) }.toString() + fun debug(writer: PrintStream = System.out) { for ((index, group) in groups.withIndex()) { writer.print("${index + 1}:") diff --git a/js/js.tests/build.gradle.kts b/js/js.tests/build.gradle.kts index f25e5d5fe91..592af9075f4 100644 --- a/js/js.tests/build.gradle.kts +++ b/js/js.tests/build.gradle.kts @@ -8,6 +8,7 @@ import org.jetbrains.kotlin.ideaExt.idea plugins { kotlin("jvm") + kotlin("plugin.serialization") id("jps-compatible") id("com.github.node-gradle.node") version "3.2.1" id("de.undercouch.download") @@ -78,6 +79,11 @@ dependencies { antLauncherJar(toolsJar()) testRuntimeOnly("org.junit.vintage:junit-vintage-engine:${commonDependencyVersion("org.junit", "junit-bom")}") + + testImplementation(commonDependency("org.jetbrains.kotlinx", "kotlinx-serialization-json")) + testImplementation(commonDependency("io.ktor", "ktor-client-core")) + testImplementation(commonDependency("io.ktor", "ktor-client-cio")) + testImplementation(commonDependency("io.ktor", "ktor-client-websockets")) } val generationRoot = projectDir.resolve("tests-gen") @@ -177,6 +183,18 @@ val generateTypeScriptTests = sequential( .map { generateTypeScriptTestFor(it.name) } ) +fun Test.setupNodeJs() { + systemProperty("javascript.engine.path.NodeJs", com.github.gradle.node.variant.VariantComputer() + .let { variantComputer -> + variantComputer + .computeNodeDir(node) + .let { variantComputer.computeNodeBinDir(it) } + .let { variantComputer.computeNodeExec(node, it) } + .get() + } + ) +} + fun Test.setupSpiderMonkey() { dependsOn(unzipJsShell) val jsShellExecutablePath = File(unzipJsShell.get().destinationDir, "js").absolutePath @@ -195,6 +213,8 @@ fun Test.setupV8() { fun Test.setUpJsBoxTests(jsEnabled: Boolean, jsIrEnabled: Boolean) { setupV8() + if (jsIrEnabled) + setupNodeJs() inputs.files(rootDir.resolve("js/js.engines/src/org/jetbrains/kotlin/js/engine/repl.js")) diff --git a/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt b/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt index 5658c9b6f12..eaf6212af17 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt @@ -149,6 +149,10 @@ fun main(args: Array) { model("codegen/boxWasmJsInterop") } + testClass { + model("debug/stepping") + } + testClass( suiteTestClassName = "Fir2IrJsTextTestGenerated" ) { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsBlackBoxCodegenTestBase.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsBlackBoxCodegenTestBase.kt index ea8e851e676..e8a1a802b08 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsBlackBoxCodegenTestBase.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsBlackBoxCodegenTestBase.kt @@ -41,6 +41,18 @@ abstract class AbstractJsBlackBoxCodegenTestBase> override fun TestConfigurationBuilder.configuration() { + commonConfigurationForJsBlackBoxCodegenTest() + jsArtifactsHandlersStep { + useHandlers( + ::NodeJsGeneratorHandler, + ::JsBoxRunner, + ::JsMinifierRunner, + ::JsAstHandler + ) + } + } + + protected fun TestConfigurationBuilder.commonConfigurationForJsBlackBoxCodegenTest() { globalDefaults { frontend = targetFrontend targetPlatform = JsPlatforms.defaultJsPlatform @@ -100,13 +112,7 @@ abstract class AbstractJsBlackBoxCodegenTestBase CDPMethodCallEncodingInfo +): CDPResponse { + val jsonElement = json.parseToJsonElement(message) + return when (val id = jsonElement.jsonObject["id"]?.jsonPrimitive?.int) { + null -> CDPResponse.Event(decodeCDPEvent(jsonElement)) + else -> { + val serializer = when (val encodingInfo = serializerForMessageId(id)) { + is CDPMethodCallEncodingInfoImpl -> encodingInfo.serializer + is CDPMethodCallEncodingInfoPlainText -> null + } + val result = jsonElement.jsonObject["result"] + val error = jsonElement.jsonObject["error"] + if (result != null) { + CDPResponse.MethodInvocationResult( + id, + serializer?.let { json.decodeFromJsonElement(it, result) } ?: CDPMethodInvocationResultPlainText(result.toString()) + ) + } else if (error != null) { + CDPResponse.Error(id, json.decodeFromJsonElement(error)) + } else { + error("Missing 'result' or 'error' properties in JSON") + } + } + } +} + +/** + * An opaque object that contains information about how to decode a method invocation result. + * + * Depending on the method, the type of the result may be different. So, when invoking a method, + * we save the information about how to decode its result, so that when the result is received, + * we could decode a value of the correct type. + */ +sealed interface CDPMethodCallEncodingInfo + +private class CDPMethodCallEncodingInfoImpl( + val serializer: DeserializationStrategy +) : CDPMethodCallEncodingInfo + +/** + * Indicates that the result of the method call does not need any deserialization. + */ +object CDPMethodCallEncodingInfoPlainText : CDPMethodCallEncodingInfo + +/** + * Returns the JSON message for invoking the method, as well as the information about how to decode its result. + */ +private inline fun encodeCDPMethodCall( + messageId: Int, + methodName: String, + params: Params? +): Pair { + val request = CDPRequest(messageId, methodName, params) + return json.encodeToString(request) to + CDPMethodCallEncodingInfoImpl(json.serializersModule.serializer()) +} + +/** + * A superclass for each kind of CDP method invocation result. + */ +@Serializable +sealed class CDPMethodInvocationResult + +/** + * A special kind of method invocation result that indicates that the response contents should be discarded. + */ +@Serializable +object CDPMethodInvocationResultUnit : CDPMethodInvocationResult() + +/** + * A special kind of method invocation result that indicates that the response contents should not be decoded from JSON, + * and instead should be returned as-is. + */ +class CDPMethodInvocationResultPlainText(val string: String) : CDPMethodInvocationResult() + +/** + * A superclass for each kind of CDP method invocation parameters. + */ +@Serializable +sealed class CDPRequestParams + +/** + * A special kind of method invocation parameters that indicates that the method accepts no parameters. + */ +@Serializable +internal object CDPRequestParamsUnit : CDPRequestParams() + +/** + * A representation of a [Chrome DevTools protocol](https://chromedevtools.github.io/devtools-protocol/) request. + */ +@Serializable +class CDPRequest( + val id: Int, + val method: String, + val params: Params? = null +) + +/** + * A representation of a [Chrome DevTools protocol](https://chromedevtools.github.io/devtools-protocol/) error. + */ +@Serializable +class CDPError private constructor(val code: Int, val message: String? = null) + +/** + * A superclass for each kind of CDP event. + */ +@Serializable +sealed class CDPEvent + +/** + * When we encounter a CDP event whose name we don't know, we return an instance of this class. + */ +class UnknownCDPEvent(val name: String) : CDPEvent() + +private fun decodeCDPEvent(element: JsonElement): CDPEvent { + val method = element.jsonObject["method"]!!.jsonPrimitive.content + val params = element.jsonObject["params"] ?: error("missing params") + return when (method) { + "Debugger.breakpointResolved" -> json.decodeFromJsonElement(Debugger.Event.BreakpointResolved.serializer(), params) + "Debugger.paused" -> json.decodeFromJsonElement(Debugger.Event.Paused.serializer(), params) + "Debugger.resumed" -> json.decodeFromJsonElement(Debugger.Event.Resumed.serializer(), params) + "Debugger.scriptFailedToParse" -> json.decodeFromJsonElement(Debugger.Event.ScriptFailedToParse.serializer(), params) + "Debugger.scriptParsed" -> json.decodeFromJsonElement(Debugger.Event.ScriptParsed.serializer(), params) + "Runtime.executionContextCreated" -> json.decodeFromJsonElement(Runtime.Event.ExecutionContextCreated.serializer(), params) + "Runtime.executionContextDestroyed" -> json.decodeFromJsonElement(Runtime.Event.ExecutionContextDestroyed.serializer(), params) + else -> UnknownCDPEvent(method) + } +} + +/** + * Something capable of invoking a [Chrome DevTools protocol](https://chromedevtools.github.io/devtools-protocol/) method and returning + * the result of the invocation. + */ +interface CDPRequestEvaluator { + + /** + * @param encodeMethodCallWithMessageId passed a message id, expected to return the JSON-encoded CDP message + * and some information about how to decode the response. + */ + suspend fun genericEvaluateRequest( + encodeMethodCallWithMessageId: (Int) -> Pair + ): CDPMethodInvocationResult +} + +private suspend inline fun CDPRequestEvaluator.evaluateRequest( + noinline body: (Int) -> Pair +) = genericEvaluateRequest(body) as T + +/** + * The [`Runtime` domain](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/) of Chrome DevTools protocol. + */ +class Runtime(private val requestEvaluator: CDPRequestEvaluator) { + + /** + * Enables reporting of execution contexts creation by means of [Runtime.Event.ExecutionContextCreated] event. + * When the reporting gets enabled the event will be sent immediately for each existing execution context. + * + * See [Runtime.enable](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-enable) + */ + suspend fun enable() { + requestEvaluator.evaluateRequest { messageId -> + encodeCDPMethodCall(messageId, "Runtime.enable", null) + } + } + + /** + * Tells inspected instance to run if it was waiting for debugger to attach. + * + * See [Runtime.runIfWaitingForDebugger](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-runIfWaitingForDebugger) + */ + suspend fun runIfWaitingForDebugger() { + requestEvaluator.evaluateRequest { messageId -> + encodeCDPMethodCall(messageId, "Runtime.runIfWaitingForDebugger", null) + } + } + + @Serializable + class EvaluationResult private constructor( + /** + * Evaluation result. + */ + val result: RemoteObject, + /** + * Exception details. + */ + val exceptionDetails: ExceptionDetails? = null + ) : CDPMethodInvocationResult() + + @Serializable + class EvaluateRequestParams(val expression: String, val contextId: ExecutionContextId? = null) : CDPRequestParams() + + /** + * Evaluates expression on global object. + * + * See [Runtime.evaluate](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate) + * + * @param expression Expression to evaluate. + * @param contextId Specifies in which execution context to perform evaluation. + * If the parameter is omitted the evaluation will be performed in the context of the inspected page. + */ + suspend fun evaluate(expression: String, contextId: ExecutionContextId? = null) = + requestEvaluator.evaluateRequest { messageId -> + encodeCDPMethodCall( + messageId, + "Runtime.evaluate", + EvaluateRequestParams(expression, contextId) + ) + } + + /** + * Unique script identifier. + * + * See [Runtime.ScriptId](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-ScriptId) + */ + @Serializable + @JvmInline + value class ScriptId(val value: String) + + /** + * Id of an execution context. + * + * See [Runtime.ExecutionContextId](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-ExecutionContextId) + */ + @Serializable + @JvmInline + value class ExecutionContextId(val value: Int) + + /** + * Unique object identifier. + * + * See [Runtime.RemoteObjectId](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObjectId) + */ + @Serializable + @JvmInline + value class RemoteObjectId(val value: String) + + /** + * Object type. + */ + @Serializable + enum class ValueType { + @SerialName("object") + OBJECT, + + @SerialName("function") + FUNCTION, + + @SerialName("undefined") + UNDEFINED, + + @SerialName("string") + STRING, + + @SerialName("number") + NUMBER, + + @SerialName("boolean") + BOOLEAN, + + @SerialName("symbol") + SYMBOL, + + @SerialName("bigint") + BIGINT, + } + + /** + * Object subtype hint. + */ + @Serializable + enum class ObjectSubtype { + @SerialName("array") + ARRAY, + + @SerialName("null") + NULL, + + @SerialName("node") + NODE, + + @SerialName("regexp") + REGEXP, + + @SerialName("date") + DATE, + + @SerialName("map") + MAP, + + @SerialName("set") + SET, + + @SerialName("weakmap") + WEAKMAP, + + @SerialName("weakset") + WEAKSET, + + @SerialName("iterator") + ITERATOR, + + @SerialName("generator") + GENERATOR, + + @SerialName("error") + ERROR, + + @SerialName("proxy") + PROXY, + + @SerialName("promise") + PROMISE, + + @SerialName("typedarray") + TYPEDARRAY, + + @SerialName("arraybuffer") + ARRAYBUFFER, + + @SerialName("dataview") + DATAVIEW, + + @SerialName("webassemblymemory") + WEBASSEMBLYMEMORY, + + @SerialName("wasmvalue") + WASMVALUE + } + + /** + * Mirror object referencing original JavaScript object. + * + * See [Runtime.RemoteObject](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject) + */ + @Serializable + class RemoteObject private constructor( + /** + * Object type. + */ + val type: ValueType, + /** + * Object subtype hint. Specified for [ValueType.OBJECT] type values only. + */ + val subtype: ObjectSubtype? = null, + /** + * Object class (constructor) name. Specified for [ValueType.OBJECT] type values only. + */ + val className: String? = null, + /** + * String representation of the object. + */ + val description: String? = null, + /** + * Unique object identifier (for non-primitive values). + */ + val objectId: RemoteObjectId? = null + ) + + /** + * Stack entry for runtime errors and assertions. + * + * See [Runtime.CallFrame](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-CallFrame) + */ + @Serializable + class CallFrame private constructor( + /** + * JavaScript function name. + */ + val functionName: String, + /** + * JavaScript script id. + */ + val scriptId: ScriptId, + /** + * JavaScript script name or url. + */ + val url: String, + /** + * JavaScript script line number (0-based). + */ + val lineNumber: Int, + /** + * JavaScript script column number (0-based). + */ + val columnNumber: Int + ) + + /** + * Call frames for assertions or error messages. + * + * See [Runtime.StackTrace](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-StackTrace) + */ + @Serializable + class StackTrace private constructor( + /** + * String label of this stack trace. For async traces this may be a name of the function that initiated the async call. + */ + val description: String? = null, + /** + * A list of call frames in the stack trace. + */ + val callFrames: List, + + /** + * Asynchronous JavaScript stack trace that preceded this stack, if available. + */ + val parent: StackTrace? = null + ) + + /** + * Detailed information about exception (or error) that was thrown during script compilation or execution. + * + * See [Runtime.ExceptionDetails](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-ExceptionDetails) + */ + @Serializable + class ExceptionDetails private constructor( + /** + * Exception id. + */ + val exceptionId: Int, + /** + * Exception text, which should be used together with exception object when available. + */ + val text: String, + /** + * Line number of the exception location (0-based). + */ + val lineNumber: Int, + /** + * Column number of the exception location (0-based). + */ + val columnNumber: Int, + /** + * Script ID of the exception location. + */ + val scriptId: ScriptId? = null, + /** + * URL of the exception location, to be used when the script was not reported. + */ + val url: String? = null, + /** + * JavaScript stack trace if available. + */ + val stackTrace: StackTrace? = null, + /** + * Exception object if available. + */ + val exception: RemoteObject? = null, + /** + * Identifier of the context where exception happened. + */ + val executionContextId: ExecutionContextId? = null, + ) + + /** + * Description of an isolated world. + * + * See [Runtime.ExecutionContextDescription](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-ExecutionContextDescription) + */ + @Serializable + class ExecutionContextDescription( + /** + * Unique id of the execution context. It can be used to specify in which execution context script evaluation should be performed. + */ + val id: ExecutionContextId, + /** + * Execution context origin. + */ + val origin: String, + /** + * Human readable name describing given context. + */ + val name: String, + ) + + /** + * An event in the `Runtime` CDP domain. + */ + @Serializable + sealed class Event : CDPEvent() { + /** + * Issued when new execution context is created. + * + * See [Runtime.executionContextCreated](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#event-executionContextCreated) + */ + @Serializable + class ExecutionContextCreated private constructor( + /** + * A newly created execution context. + */ + val context: ExecutionContextDescription, + ) : Event() + + /** + * Issued when execution context is destroyed. + * + * See [Runtime.executionContextDestroyed](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#event-executionContextDestroyed) + */ + @Serializable + class ExecutionContextDestroyed private constructor( + /** + * Id of the destroyed context + */ + val executionContextId: ExecutionContextId + ) : Event() + } +} + +/** + * The [`Debugger` domain](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/) of Chrome DevTools protocol. + */ +class Debugger(private val requestEvaluator: CDPRequestEvaluator) { + + /** + * Enables the debugger. + * + * See [Debugger.enable](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#method-enable) + */ + suspend fun enable() { + requestEvaluator.evaluateRequest { messageId -> + encodeCDPMethodCall(messageId, "Debugger.enable", null) + } + } + + @Serializable + class ResumeRequestParams private constructor(val terminateOnResume: Boolean = false) : CDPRequestParams() + + /** + * Resumes JavaScript execution. + * + * See [Debugger.resume](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#method-resume) + */ + suspend fun resume() { + requestEvaluator.evaluateRequest { messageId -> + encodeCDPMethodCall(messageId, "Debugger.resume", null) + } + } + + @Serializable + class SetBreakpointByUrlResult private constructor( + /** + * Id of the created breakpoint for further reference. + */ + val breakpointId: BreakpointId, + + /** + * List of the locations this breakpoint resolved into upon addition. + */ + val locations: List + ) : CDPMethodInvocationResult() + + @Serializable + private class SetBreakpointByUrlRequestParams( + val lineNumber: Int, + val url: String, + val scriptHash: String? = null, + val columnNumber: Int? = null, + val condition: String? = null, + ) : CDPRequestParams() + + /** + * Sets JavaScript breakpoint at given location specified either by URL or URL regex. + * Once this command is issued, all existing parsed scripts will have breakpoints resolved and returned in + * [SetBreakpointByUrlResult.locations] property. + * Further matching script parsing will result in subsequent [Debugger.Event.BreakpointResolved] events issued. + * + * See [Debugger.setBreakpointByUrl](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#method-setBreakpointByUrl) + * + * @param lineNumber Line number to set breakpoint at. + * @param url URL of the resources to set breakpoint on. + * @param scriptHash Script hash of the resources to set breakpoint on. + * @param columnNumber Offset in the line to set breakpoint at. + * @param condition Expression to use as a breakpoint condition. + * When specified, debugger will only stop on the breakpoint if this expression evaluates to true. + */ + suspend fun setBreakpointByUrl( + lineNumber: Int, + url: String, + scriptHash: String? = null, + columnNumber: Int? = null, + condition: String? = null + ) = requestEvaluator.evaluateRequest { messageId -> + encodeCDPMethodCall( + messageId, + "Debugger.setBreakpointByUrl", + SetBreakpointByUrlRequestParams( + lineNumber, + url, + scriptHash, + columnNumber, + condition + ) + ) + } + + @Serializable + class SetBreakpointResult private constructor( + /** + * Id of the created breakpoint for further reference. + */ + val breakpointId: BreakpointId, + + /** + * Location this breakpoint resolved into. + */ + val actualLocation: Location + ) : CDPMethodInvocationResult() + + @Serializable + private class SetBreakpointRequestParams( + val location: Location, + val condition: String? = null, + ) : CDPRequestParams() + + /** + * Sets JavaScript breakpoint at a given location. + * + * See [Debugger.setBreakpoint](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#method-setBreakpoint) + * + * @param scriptId Script identifier as reported in the [Debugger.Event.ScriptParsed]. + * @param lineNumber Line number in the script (0-based). + * @param columnNumber Column number in the script (0-based). + * @param condition Expression to use as a breakpoint condition. + * When specified, debugger will only stop on the breakpoint if this expression evaluates to true. + */ + suspend fun setBreakpoint( + scriptId: Runtime.ScriptId, + lineNumber: Int, + columnNumber: Int? = null, + condition: String? = null + ) = requestEvaluator.evaluateRequest { messageId -> + encodeCDPMethodCall( + messageId, + "Debugger.setBreakpoint", + SetBreakpointRequestParams(Location(scriptId, lineNumber, columnNumber), condition) + ) + } + + @Serializable + private class SetSkipAllPausesRequestParams(val skip: Boolean) : CDPRequestParams() + + /** + * Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc). + * + * See [Debugger.setSkipAllPauses](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#method-setSkipAllPauses) + * + * @param skip New value for skip pauses state. + */ + suspend fun setSkipAllPauses(skip: Boolean) { + requestEvaluator.evaluateRequest { messageId -> + encodeCDPMethodCall( + messageId, + "Debugger.setSkipAllPauses", + SetSkipAllPausesRequestParams(skip) + ) + } + } + + /** + * Steps into the function call. + * + * See [Debugger.stepInto](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#method-stepInto) + */ + suspend fun stepInto() { + requestEvaluator.evaluateRequest { messageId -> + encodeCDPMethodCall(messageId, "Debugger.stepInto", null) + } + } + + @Serializable + private class EvaluateOnCallFrameRequestParams(val callFrameId: CallFrameId, val expression: String) : CDPRequestParams() + + /** + * Evaluates expression on a given call frame. + * + * See [Debugger.evaluateOnCallFrame](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#method-evaluateOnCallFrame) + * + * @param callFrameId Call frame identifier to evaluate on. + * @param expression Expression to evaluate. + */ + suspend fun evaluateOnCallFrame(callFrameId: CallFrameId, expression: String) = + requestEvaluator.evaluateRequest { messageId -> + encodeCDPMethodCall( + messageId, + "Debugger.evaluateOnCallFrame", + EvaluateOnCallFrameRequestParams(callFrameId, expression) + ) + } + + /** + * Breakpoint identifier. + * + * See [Debugger.BreakpointId](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#type-BreakpointId) + */ + @Serializable + @JvmInline + value class BreakpointId(val value: String) + + /** + * Location in the source code. + * + * See [Debugger.Location](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#type-Location) + */ + @Serializable + class Location internal constructor( + /** + * Script identifier as reported in the [Debugger.Event.ScriptParsed]. + */ + val scriptId: Runtime.ScriptId, + /** + * Line number in the script (0-based). + */ + val lineNumber: Int, + /** + * Column number in the script (0-based). + */ + val columnNumber: Int? = null + ) + + /** + * Call frame identifier. + * + * See [Debugger.CallFrameId](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#type-CallFrameId) + */ + @Serializable + @JvmInline + value class CallFrameId(val value: String) + + /** + * JavaScript call frame. Array of call frames form the call stack. + * + * See [Debugger.CallFrame](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#type-CallFrame) + */ + @Serializable + class CallFrame private constructor( + /** + * Call frame identifier. This identifier is only valid while the virtual machine is paused. + */ + val callFrameId: CallFrameId, + + /** + * Name of the JavaScript function called on this call frame. + */ + val functionName: String, + + /** + * Function location in the source code. + */ + val functionLocation: Location? = null, + + /** + * Location in the source code. + */ + val location: Location, + + /** + * `this` object for this call frame. + */ + val `this`: Runtime.RemoteObject, + + /** + * The value being returned, if the function is at return point. + */ + val returnValue: Runtime.RemoteObject? = null, + ) + + @Serializable + enum class PauseReason { + + @SerialName("ambiguous") + AMBIGUOUS, + + @SerialName("assert") + ASSERT, + + @SerialName("CSPViolation") + CSP_VIOLATION, + + @SerialName("debugCommand") + DEBUG_COMMAND, + + @SerialName("DOM") + DOM, + + @SerialName("EventListener") + EVENT_LISTENER, + + @SerialName("exception") + EXCEPTION, + + @SerialName("instrumentation") + INSTRUMENTATION, + + @SerialName("OOM") + OOM, + + @SerialName("other") + OTHER, + + @SerialName("promiseRejection") + PROMISE_REJECTION, + + @SerialName("XHR") + XHR, + + @SerialName("Break on start") + BREAK_ON_START, + } + + /** + * An event in the `Debugger` CDP domain. + */ + @Serializable + sealed class Event : CDPEvent() { + + /** + * Fired when breakpoint is resolved to an actual script and location. + * + * See [Debugger.breakpointResolved](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#event-breakpointResolved) + */ + @Serializable + class BreakpointResolved private constructor( + /** + * Breakpoint unique identifier. + */ + val breakpointId: BreakpointId, + /** + * Actual breakpoint location. + */ + val location: Location + ) : Event() + + /** + * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + * + * See [Debugger.paused](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#event-paused) + */ + @Serializable + class Paused private constructor( + /** + * Call stack the virtual machine stopped on. + */ + val callFrames: List, + /** + * Pause reason. + */ + val reason: PauseReason, + + /** + * Hit breakpoints IDs + */ + val hitBreakpoints: List = emptyList() + ) : Event() + + /** + * Fired when the virtual machine resumed execution. + * + * See [Debugger.resumed](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#event-resumed) + */ + @Serializable + object Resumed : Event() + + /** + * Fired when virtual machine fails to parse the script. + * + * See [Debugger.scriptFailedToParse](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#event-scriptFailedToParse) + */ + @Serializable + class ScriptFailedToParse private constructor( + /** + * Identifier of the script parsed. + */ + val scriptId: Runtime.ScriptId, + /** + * URL or name of the script parsed (if any). + */ + val url: String, + /** + * Line offset of the script within the resource with given URL (for script tags). + */ + val startLine: Int, + /** + * Column offset of the script within the resource with given URL. + */ + val startColumn: Int, + /** + * Last line of the script. + */ + val endLine: Int, + /** + * Length of the last line of the script. + */ + val endColumn: Int, + /** + * URL of source map associated with script (if any). + */ + val sourceMapUrl: String? = null, + ) : Event() + + /** + * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. + * + * See [Debugger.scriptParsed](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/#event-scriptParsed) + */ + @Serializable + class ScriptParsed private constructor( + /** + * Identifier of the script parsed. + */ + val scriptId: Runtime.ScriptId, + /** + * URL or name of the script parsed (if any). + */ + val url: String, + /** + * Line offset of the script within the resource with given URL (for script tags). + */ + val startLine: Int, + /** + * Column offset of the script within the resource with given URL. + */ + val startColumn: Int, + /** + * Last line of the script. + */ + val endLine: Int, + /** + * Length of the last line of the script. + */ + val endColumn: Int, + /** + * URL of source map associated with script (if any). + */ + val sourceMapUrl: String? = null, + ) : Event() + } +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/debugger/NodeJsInspectorClient.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/debugger/NodeJsInspectorClient.kt new file mode 100644 index 00000000000..6df2580a43b --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/debugger/NodeJsInspectorClient.kt @@ -0,0 +1,218 @@ +/* + * Copyright 2010-2022 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.debugger + +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.websocket.* +import io.ktor.websocket.* +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.SerializationException +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.jetbrains.kotlin.utils.addToStdlib.cast +import java.util.logging.Logger +import kotlin.coroutines.* + +/** + * A client that fires up a Node.js instance in the inspector mode, and connects to it via websocket, + * allowing us to communicate with it using [Chrome DevTools protocol](https://chromedevtools.github.io/devtools-protocol/). + * + * @param scriptPath the script for Node to run. + * @param args the command line arguments passed to the script. + */ +class NodeJsInspectorClient(val scriptPath: String, val args: List) { + + private var onDebuggerEventCallback: ((CDPEvent) -> Unit)? = null + + /** + * Creates a Node process and provides a context for communicating with it. + * After [block] returns, the Node process is destroyed. + */ + fun run(block: suspend NodeJsInspectorClientContext.() -> T): T = runBlocking { + val context = NodeJsInspectorClientContextImpl(this@NodeJsInspectorClient) + try { + runWithContext(context, block) + } finally { + context.release() + } + } + + private suspend fun runWithContext( + context: NodeJsInspectorClientContextImpl, + block: suspend NodeJsInspectorClientContext.() -> T + ): T { + context.startWebsocketSession() + + var blockResult: Result? = null + block.startCoroutine(context, object : Continuation { + override val context: CoroutineContext + get() = EmptyCoroutineContext + + override fun resumeWith(result: Result) { + blockResult = result + } + }) + + context.listenForMessages { message -> + when (val response = decodeCDPResponse(message) { context.messageContinuations[it]!!.first }) { + is CDPResponse.Event -> onDebuggerEventCallback?.invoke(response.event) + is CDPResponse.MethodInvocationResult -> context.messageContinuations.remove(response.id)!!.second.resume(response.result) + is CDPResponse.Error -> context.messageContinuations[response.id]!!.second.resumeWithException( + IllegalStateException("error ${response.error.code}" + (response.error.message?.let { ": $it" } ?: "")) + ) + } + context.waitingOnPredicate?.let { (predicate, continuation) -> + if (predicate()) { + context.waitingOnPredicate = null + continuation.resume(Unit) + } + } + blockResult != null + } + + return blockResult!!.getOrThrow() + } + + /** + * Installs a listener for Chrome DevTools Protocol events. + */ + fun onEvent(receiveEvent: (CDPEvent) -> Unit) { + onDebuggerEventCallback = receiveEvent + } +} + +private const val NODE_WS_DEBUG_URL_PREFIX = "Debugger listening on ws://" + +/** + * The actual implementation of the Node.js inspector client. + */ +private class NodeJsInspectorClientContextImpl(engine: NodeJsInspectorClient) : NodeJsInspectorClientContext, CDPRequestEvaluator { + + private val logger = Logger.getLogger(this::class.java.name) + + private val nodeProcess = ProcessBuilder( + System.getProperty("javascript.engine.path.NodeJs"), + "--inspect-brk=0", + engine.scriptPath, + *engine.args.toTypedArray() + ).also { + logger.fine(it::joinedCommand) + }.start() + + /** + * The WebSocket address to connect to. + */ + private val debugUrl: String = run { + val prompt = nodeProcess.errorStream.bufferedReader().readLine() + logger.fine(prompt) + if (prompt.startsWith(NODE_WS_DEBUG_URL_PREFIX)) { + val startIndexInLine = NODE_WS_DEBUG_URL_PREFIX.length - "ws://".length + prompt.substring(startIndexInLine).trim() + } else { + error(prompt) + } + } + + private val webSocketClient = HttpClient(CIO) { + install(WebSockets) + } + + private var webSocketSession: DefaultClientWebSocketSession? = null + + val messageContinuations = mutableMapOf>>() + + /** + * See [waitForConditionToBecomeTrue]. + */ + var waitingOnPredicate: Pair<(() -> Boolean), Continuation>? = null + + private var nextMessageId = 0 + + suspend fun startWebsocketSession() { + webSocketSession = webSocketClient.webSocketSession(debugUrl) + } + + private val loggingJsonPrettyPrinter by lazy { Json { prettyPrint = true } } + + private fun prettyPrintJson(json: String): String { + val jsonElement = try { + Json.parseToJsonElement(json) + } catch (e: SerializationException) { + return json + } + return loggingJsonPrettyPrinter.encodeToString(jsonElement) + } + + /** + * Starts a loop that waits for incoming Chrome DevTools Protocol messages and invokes [receiveMessage] when one is received. + * The loop stops as soon as at least one message is received *and* [receiveMessage] returns `true`. + */ + suspend fun listenForMessages(receiveMessage: (String) -> Boolean) { + val session = webSocketSession ?: error("Session closed") + do { + val message = when (val frame = session.incoming.receive()) { + is Frame.Text -> frame.readText() + else -> error("Unexpected frame kind: $frame") + } + logger.fine { + "Received message:\n${prettyPrintJson(message)}" + } + } while (!receiveMessage(message)) + } + + override val debugger = Debugger(this) + + override val runtime = Runtime(this) + + override suspend fun waitForConditionToBecomeTrue(predicate: () -> Boolean) { + if (predicate()) return + suspendCoroutine { continuation -> + require(waitingOnPredicate == null) { "already waiting!" } + waitingOnPredicate = predicate to continuation + } + } + + private suspend fun sendPlainTextMessage(message: String) { + val session = webSocketSession ?: error("Session closed") + logger.fine { + "Sent message:\n${prettyPrintJson(message)}" + } + session.send(message) + } + + @Deprecated("Only for debugging purposes", level = DeprecationLevel.WARNING) + override suspend fun sendPlainTextMessage(methodName: String, paramsJson: String): String { + val messageId = nextMessageId++ + sendPlainTextMessage("""{"id":$messageId,"method":$methodName,"params":$paramsJson}""") + return suspendCoroutine { continuation -> + messageContinuations[messageId] = CDPMethodCallEncodingInfoPlainText to continuation + }.cast().string + } + + override suspend fun genericEvaluateRequest( + encodeMethodCallWithMessageId: (Int) -> Pair + ): CDPMethodInvocationResult { + val messageId = nextMessageId++ + val (encodedMessage, encodingInfo) = encodeMethodCallWithMessageId(messageId) + sendPlainTextMessage(encodedMessage) + return suspendCoroutine { continuation -> + messageContinuations[messageId] = encodingInfo to continuation + } + } + + /** + * Releases all the resources and destroys the Node.js process. + */ + suspend fun release() { + webSocketSession?.close() + webSocketSession = null + webSocketClient.close() + nodeProcess.destroy() + } +} + +private fun ProcessBuilder.joinedCommand(): String = + command().joinToString(" ") { "\"${it.replace("\"", "\\\"")}\"" } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/debugger/NodeJsInspectorClientContext.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/debugger/NodeJsInspectorClientContext.kt new file mode 100644 index 00000000000..027709d88f4 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/debugger/NodeJsInspectorClientContext.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2022 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.debugger + +/** + * An interface for invoking [Chrome DevTools protocol](https://chromedevtools.github.io/devtools-protocol/) methods. + * + * An instance of this interface is passed to the `block` closure in [NodeJsInspectorClient.run]. + */ +interface NodeJsInspectorClientContext { + + /** + * The [`Debugger` domain](https://chromedevtools.github.io/devtools-protocol/tot/Debugger/) of Chrome DevTools protocol. + */ + val debugger: Debugger + + /** + * The [`Runtime` domain](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/) of Chrome DevTools protocol. + */ + val runtime: Runtime + + @Deprecated("Only for debugging purposes", level = DeprecationLevel.WARNING) + suspend fun sendPlainTextMessage(methodName: String, paramsJson: String): String + + /** + * On each incoming message invokes [predicate], and returns only when [predicate] returns `true`. + */ + suspend fun waitForConditionToBecomeTrue(predicate: () -> Boolean) +} + +/** + * On each incoming message checks whether [test] returns `null`, and returns only when [test] returns non-`null` value. + */ +suspend inline fun NodeJsInspectorClientContext.waitForValueToBecomeNonNull(crossinline test: () -> T?): T { + var value: T? = null + waitForConditionToBecomeTrue { + test()?.also { + value = it + } != null + } + return value!! +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsDebugRunner.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsDebugRunner.kt new file mode 100644 index 00000000000..43c1574b578 --- /dev/null +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsDebugRunner.kt @@ -0,0 +1,259 @@ +/* + * Copyright 2010-2022 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.handlers + +import org.jetbrains.kotlin.KtPsiSourceFileLinesMapping +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode +import org.jetbrains.kotlin.js.parser.sourcemaps.* +import org.jetbrains.kotlin.js.test.debugger.* +import org.jetbrains.kotlin.js.test.utils.getAllFilesForRunner +import org.jetbrains.kotlin.js.test.utils.getBoxFunction +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives +import org.jetbrains.kotlin.test.model.TestFile +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator +import org.jetbrains.kotlin.test.services.moduleStructure +import org.jetbrains.kotlin.test.utils.SteppingTestLoggedData +import org.jetbrains.kotlin.test.utils.checkSteppingTestResult +import org.jetbrains.kotlin.test.utils.formatAsSteppingTestExpectation +import java.io.File +import java.net.URI +import java.net.URISyntaxException + +/** + * This class is an analogue of the [DebugRunner][org.jetbrains.kotlin.test.backend.handlers.DebugRunner] from JVM stepping tests. + * + * It runs a generated JavaScript file under a debugger, sets a breakpoint in the beginning of the `box` function + * and performs the "step into" action until there is nothing more to step into. On each pause it records the source file name, + * the source line and the function name of the current call frame, and compares this data with the expectations written in the test file. + * + * It uses sourcemaps for mapping locations in the generated JS file to the corresponding locations in the source Kotlin file. + * Also, it assumes that the sourcemap contains absolute paths to source files. The relative paths are replaced with + * absolute paths earlier by [JsSourceMapPathRewriter]. + * + * Stepping tests only work with the IR backend. The legacy backend is not supported. + * + * For simplicity, only the [FULL][org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode.FULL] translation mode is + * supported. + * + */ +class JsDebugRunner(testServices: TestServices) : AbstractJsArtifactsCollector(testServices) { + override fun processAfterAllModules(someAssertionWasFailed: Boolean) { + if (someAssertionWasFailed) return + + val globalDirectives = testServices.moduleStructure.allDirectives + val esModules = JsEnvironmentConfigurationDirectives.ES_MODULES in globalDirectives + + if (esModules) return + + // This file generated in the FULL mode should be self-sufficient. + val jsFilePath = getAllFilesForRunner(testServices, modulesToArtifact)[TranslationMode.FULL]?.single() + ?: error("Only FULL translation mode is supported") + + val mainModule = JsEnvironmentConfigurator.getMainModule(testServices) + + val sourceMapFile = File("$jsFilePath.map") + val sourceMap = when (val parseResult = SourceMapParser.parse(sourceMapFile)) { + is SourceMapSuccess -> parseResult.value + is SourceMapError -> error(parseResult.message) + } + + runGeneratedCode(jsFilePath, sourceMap, mainModule) + } + + private fun runGeneratedCode( + jsFilePath: String, + sourceMap: SourceMap, + mainModule: TestModule, + ) { + val (testFileWithBoxFunction, boxFunctionStartLine) = getBoxFunctionStartLocation(mainModule) + val originalFileWithBoxFunction = testFileWithBoxFunction.originalFile + + val boxFunctionLineInGeneratedFile = + sourceMap.breakpointLineInGeneratedFile(originalFileWithBoxFunction, boxFunctionStartLine) + + if (boxFunctionLineInGeneratedFile < 0) + error("Could not find the location of the 'box' function in the generated file") + + val debuggerFacade = NodeJsDebuggerFacade(jsFilePath) + + val jsFileURI = File(jsFilePath).absoluteFile.toURI().withAuthority("") + + val loggedItems = mutableListOf() + debuggerFacade.run { + with(debuggerFacade) { + val boxFunctionBreakpoint = debugger.setBreakpointByUrl(boxFunctionLineInGeneratedFile, jsFileURI.toString()) + debugger.resume() + waitForResumeEvent() + waitForPauseEvent { + it.reason == Debugger.PauseReason.OTHER && it.hitBreakpoints.contains(boxFunctionBreakpoint.breakpointId) + } + while (true) { + val topMostCallFrame = waitForPauseEvent().callFrames[0] + try { + if (URI(scriptUrlByScriptId(topMostCallFrame.location.scriptId)) != jsFileURI) break + } catch (_: URISyntaxException) { + // Probably something like 'evalmachine.' brought us here. Ignore. + } + addCallFrameInfoToLoggedItems(sourceMap, topMostCallFrame, loggedItems) + debugger.stepInto() + waitForResumeEvent() + } + } + } + checkSteppingTestResult( + mainModule.frontendKind, + mainModule.targetBackend ?: TargetBackend.JS_IR, + originalFileWithBoxFunction, + loggedItems + ) + } + + private fun addCallFrameInfoToLoggedItems( + sourceMap: SourceMap, + topMostCallFrame: Debugger.CallFrame, + loggedItems: MutableList + ) { + sourceMap.getSourceLineForGeneratedLocation(topMostCallFrame.location)?.let { (sourceFile, sourceLine) -> + val testFileName = testFileNameFromMappedLocation(sourceFile, sourceLine) ?: return + val expectation = + formatAsSteppingTestExpectation(testFileName, sourceLine + 1, topMostCallFrame.functionName, false) + loggedItems.add(SteppingTestLoggedData(sourceLine + 1, false, expectation)) + } + } + + /** + * Returns the test file and the line number in that file where the body of the `box` function begins. + */ + private fun getBoxFunctionStartLocation(mainModule: TestModule): Pair { + val boxFunction = getBoxFunction(testServices) ?: error("Missing 'box' function") + val file = boxFunction.containingKtFile + val mapping = KtPsiSourceFileLinesMapping(file) + val firstStatementOffset = boxFunction.bodyBlockExpression?.firstStatement?.startOffset + ?: boxFunction.bodyExpression?.startOffset + ?: boxFunction.startOffset + return mainModule.files.single { it.name == file.name } to mapping.getLineByOffset(firstStatementOffset) + } + + /** + * Maps the location in the source file to the location in the generated file. + * + * The Node.js debugger is not sourcemap-aware, so we need to set a breakpoint in the `box` function in the generated JS file. + * + * We don't know where the generated `box` function is located, so we use the source map to figure it out. + * + * This is basically what Intellij IDEA's built-in JavaScript debugger does when you set a breakpoint in a source file: it tries + * to map the location of the breakpoint in the source file to a location in the generated file. Here we use a simplified + * algorithm for that. + */ + private fun SourceMap.breakpointLineInGeneratedFile(sourceFile: File, sourceLine: Int): Int { + val sourceFileAbsolutePath = sourceFile.absoluteFile.normalize() + var candidateSegment: Pair? = null + for ((generatedLineNumber, group) in groups.withIndex()) { + for (segment in group.segments) { + if (segment.sourceFileName?.let { File(it).absoluteFile.normalize() } != sourceFileAbsolutePath || + segment.sourceLineNumber != sourceLine) + continue + if (candidateSegment == null) + candidateSegment = generatedLineNumber to segment + // Find the segment that points to the earliest column in the source file + if (segment.sourceColumnNumber < candidateSegment.second.sourceColumnNumber) + candidateSegment = generatedLineNumber to segment + } + } + return candidateSegment?.first ?: -1 + } + + /** + * Maps [location] in the generated JavaScript file to the corresponding location in a source file. + * @return The source file path (as specified in the source map) and the line number in that source file. + */ + private fun SourceMap.getSourceLineForGeneratedLocation(location: Debugger.Location): Pair? { + + fun SourceMapSegment.sourceFileAndLine() = sourceFileName!! to sourceLineNumber + + val group = groups.getOrNull(location.lineNumber)?.takeIf { it.segments.isNotEmpty() } ?: return null + val columnNumber = location.columnNumber ?: return group.segments[0].sourceFileAndLine() + val segment = if (columnNumber <= group.segments[0].generatedColumnNumber) { + group.segments[0] + } else { + group.segments.find { + columnNumber > it.generatedColumnNumber + } + } + return segment?.sourceFileAndLine() + } + + /** + * An original test file may represent multiple source files (by using the `// FILE: myFile.kt` comments). + * Sourcemaps contain paths to original test files. However, in test expectations we write names as in the `// FILE:` comments. + * This function maps a location in the original test file to the name specified in a `// FILE:` comment. + */ + private fun testFileNameFromMappedLocation(originalFilePath: String, originalFileLineNumber: Int): String? { + val originalFile = File(originalFilePath) + return testServices.moduleStructure.modules.flatMap { it.files }.findLast { + it.originalFile.absolutePath == originalFile.absolutePath && it.startLineNumberInOriginalFile <= originalFileLineNumber + }?.name + } +} + +/** + * A wrapper around [NodeJsInspectorClient] that handles all the ceremony and allows us to only care about executing common debugging + * actions. + * + * @param jsFilePath the test file to execute and debug. + */ +private class NodeJsDebuggerFacade(jsFilePath: String) { + + private val inspector = NodeJsInspectorClient("js/js.translator/testData/runIrTestInNode.js", listOf(jsFilePath)) + + private val scriptUrls = mutableMapOf() + + private var pausedEvent: Debugger.Event.Paused? = null + + init { + inspector.onEvent { event -> + when (event) { + is Debugger.Event.ScriptParsed -> { + scriptUrls[event.scriptId] = event.url + } + is Debugger.Event.Paused -> { + pausedEvent = event + } + is Debugger.Event.Resumed -> { + pausedEvent = null + } + else -> {} + } + } + } + + /** + * By the time [body] is called, the execution is paused, no code is executed yet. + */ + fun run(body: suspend NodeJsInspectorClientContext.() -> T) = inspector.run { + debugger.enable() + debugger.setSkipAllPauses(false) + runtime.runIfWaitingForDebugger() + waitForPauseEvent { it.reason == Debugger.PauseReason.BREAK_ON_START } + + body() + } + + fun scriptUrlByScriptId(scriptId: Runtime.ScriptId) = scriptUrls[scriptId] ?: error("unknown scriptId") + + suspend fun NodeJsInspectorClientContext.waitForPauseEvent(suchThat: (Debugger.Event.Paused) -> Boolean = { true }) = + waitForValueToBecomeNonNull { + pausedEvent?.takeIf(suchThat) + } + + suspend fun NodeJsInspectorClientContext.waitForResumeEvent() = waitForConditionToBecomeTrue { pausedEvent == null } +} + +private fun URI.withAuthority(newAuthority: String?) = + URI(scheme, newAuthority, path, query, fragment) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/AbstractJsIrTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/AbstractJsIrTest.kt index fcf34dad7d5..8579638b7b9 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/AbstractJsIrTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/AbstractJsIrTest.kt @@ -153,6 +153,21 @@ open class AbstractJsIrLineNumberTest : AbstractJsIrTest( } } +open class AbstractIrJsSteppingTest : AbstractJsIrTest( + pathToTestDir = "compiler/testData/debug/stepping/", + testGroupOutputDirPrefix = "debug/stepping/" +) { + override fun TestConfigurationBuilder.configuration() { + commonConfigurationForJsBlackBoxCodegenTest() + defaultDirectives { + +JsEnvironmentConfigurationDirectives.NO_COMMON_FILES + } + jsArtifactsHandlersStep { + useHandlers(::JsDebugRunner) + } + } +} + open class AbstractIrCodegenWasmJsInteropJsTest : AbstractJsIrTest( pathToTestDir = "compiler/testData/codegen/wasmJsInterop", testGroupOutputDirPrefix = "codegen/wasmJsInteropJs" diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt index eb09778ad7c..1dbfcdee00c 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.js.test.JsAdditionalSourceProvider import org.jetbrains.kotlin.js.test.converters.augmentWithModuleName import org.jetbrains.kotlin.js.test.handlers.JsBoxRunner.Companion.TEST_FUNCTION import org.jetbrains.kotlin.js.testOld.* +import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.test.TargetBackend @@ -24,6 +25,7 @@ import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.services.* import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator.Companion.getMainModule +import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd import java.io.File private const val MODULE_EMULATION_FILE = "${JsEnvironmentConfigurator.TEST_DATA_DIR_PATH}/moduleEmulation.js" @@ -192,7 +194,7 @@ fun getTestModuleName(testServices: TestServices): String? { return getMainModule(testServices).name } -fun extractTestPackage(testServices: TestServices): String? { +fun getBoxFunction(testServices: TestServices): KtNamedFunction? { val runPlainBoxFunction = RUN_PLAIN_BOX_FUNCTION in testServices.moduleStructure.allDirectives if (runPlainBoxFunction) return null val ktFiles = testServices.moduleStructure.modules.flatMap { module -> @@ -204,12 +206,14 @@ fun extractTestPackage(testServices: TestServices): String? { } } - return ktFiles.singleOrNull { ktFile -> - val boxFunction = ktFile.declarations.find { it is KtNamedFunction && it.name == TEST_FUNCTION } - boxFunction != null - }?.packageFqName?.asString()?.takeIf { it.isNotEmpty() } + return ktFiles.mapNotNull { ktFile -> + ktFile.declarations.filterIsInstanceAnd { it.name == TEST_FUNCTION }.firstOrNull() + }.singleOrNull() } +fun extractTestPackage(testServices: TestServices): String? = + getBoxFunction(testServices)?.containingKtFile?.packageFqName?.asString()?.takeIf { it.isNotEmpty() } + fun getTestChecker(testServices: TestServices): AbstractJsTestChecker { val runTestInNashorn = java.lang.Boolean.getBoolean("kotlin.js.useNashorn") val targetBackend = testServices.defaultsProvider.defaultTargetBackend ?: TargetBackend.JS diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsSteppingTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsSteppingTestGenerated.java new file mode 100644 index 00000000000..aa444e05c0d --- /dev/null +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrJsSteppingTestGenerated.java @@ -0,0 +1,585 @@ +/* + * 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.ir; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link GenerateNewCompilerTests.kt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("compiler/testData/debug/stepping") +@TestDataPath("$PROJECT_ROOT") +public class IrJsSteppingTestGenerated extends AbstractIrJsSteppingTest { + @Test + public void testAllFilesPresentInStepping() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/debug/stepping"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @Test + @TestMetadata("anonymousFunction.kt") + public void testAnonymousFunction() throws Exception { + runTest("compiler/testData/debug/stepping/anonymousFunction.kt"); + } + + @Test + @TestMetadata("anonymousFunctionDirect.kt") + public void testAnonymousFunctionDirect() throws Exception { + runTest("compiler/testData/debug/stepping/anonymousFunctionDirect.kt"); + } + + @Test + @TestMetadata("assertion.kt") + public void testAssertion() throws Exception { + runTest("compiler/testData/debug/stepping/assertion.kt"); + } + + @Test + @TestMetadata("beforeGotoToWhileStart.kt") + public void testBeforeGotoToWhileStart() throws Exception { + runTest("compiler/testData/debug/stepping/beforeGotoToWhileStart.kt"); + } + + @Test + @TestMetadata("callWithCallInArguments.kt") + public void testCallWithCallInArguments() throws Exception { + runTest("compiler/testData/debug/stepping/callWithCallInArguments.kt"); + } + + @Test + @TestMetadata("callWithReceiver.kt") + public void testCallWithReceiver() throws Exception { + runTest("compiler/testData/debug/stepping/callWithReceiver.kt"); + } + + @Test + @TestMetadata("callableReference.kt") + public void testCallableReference() throws Exception { + runTest("compiler/testData/debug/stepping/callableReference.kt"); + } + + @Test + @TestMetadata("chainCall.kt") + public void testChainCall() throws Exception { + runTest("compiler/testData/debug/stepping/chainCall.kt"); + } + + @Test + @TestMetadata("class.kt") + public void testClass() throws Exception { + runTest("compiler/testData/debug/stepping/class.kt"); + } + + @Test + @TestMetadata("classObject.kt") + public void testClassObject() throws Exception { + runTest("compiler/testData/debug/stepping/classObject.kt"); + } + + @Test + @TestMetadata("compileTimeConstant.kt") + public void testCompileTimeConstant() throws Exception { + runTest("compiler/testData/debug/stepping/compileTimeConstant.kt"); + } + + @Test + @TestMetadata("conjunction.kt") + public void testConjunction() throws Exception { + runTest("compiler/testData/debug/stepping/conjunction.kt"); + } + + @Test + @TestMetadata("constantConditions.kt") + public void testConstantConditions() throws Exception { + runTest("compiler/testData/debug/stepping/constantConditions.kt"); + } + + @Test + @TestMetadata("constructorCall.kt") + public void testConstructorCall() throws Exception { + runTest("compiler/testData/debug/stepping/constructorCall.kt"); + } + + @Test + @TestMetadata("constructors.kt") + public void testConstructors() throws Exception { + runTest("compiler/testData/debug/stepping/constructors.kt"); + } + + @Test + @TestMetadata("dataClass.kt") + public void testDataClass() throws Exception { + runTest("compiler/testData/debug/stepping/dataClass.kt"); + } + + @Test + @TestMetadata("defaultParameter.kt") + public void testDefaultParameter() throws Exception { + runTest("compiler/testData/debug/stepping/defaultParameter.kt"); + } + + @Test + @TestMetadata("enum.kt") + public void testEnum() throws Exception { + runTest("compiler/testData/debug/stepping/enum.kt"); + } + + @Test + @TestMetadata("for.kt") + public void testFor() throws Exception { + runTest("compiler/testData/debug/stepping/for.kt"); + } + + @Test + @TestMetadata("functionCallWithDefault.kt") + public void testFunctionCallWithDefault() throws Exception { + runTest("compiler/testData/debug/stepping/functionCallWithDefault.kt"); + } + + @Test + @TestMetadata("functionCallWithInlinedLambdaParam.kt") + public void testFunctionCallWithInlinedLambdaParam() throws Exception { + runTest("compiler/testData/debug/stepping/functionCallWithInlinedLambdaParam.kt"); + } + + @Test + @TestMetadata("functionCallWithLambdaParam.kt") + public void testFunctionCallWithLambdaParam() throws Exception { + runTest("compiler/testData/debug/stepping/functionCallWithLambdaParam.kt"); + } + + @Test + @TestMetadata("functionInAnotherFile.kt") + public void testFunctionInAnotherFile() throws Exception { + runTest("compiler/testData/debug/stepping/functionInAnotherFile.kt"); + } + + @Test + @TestMetadata("if.kt") + public void testIf() throws Exception { + runTest("compiler/testData/debug/stepping/if.kt"); + } + + @Test + @TestMetadata("if2.kt") + public void testIf2() throws Exception { + runTest("compiler/testData/debug/stepping/if2.kt"); + } + + @Test + @TestMetadata("ifThen.kt") + public void testIfThen() throws Exception { + runTest("compiler/testData/debug/stepping/ifThen.kt"); + } + + @Test + @TestMetadata("ifThenElse.kt") + public void testIfThenElse() throws Exception { + runTest("compiler/testData/debug/stepping/ifThenElse.kt"); + } + + @Test + @TestMetadata("ifThenElseFalse.kt") + public void testIfThenElseFalse() throws Exception { + runTest("compiler/testData/debug/stepping/ifThenElseFalse.kt"); + } + + @Test + @TestMetadata("IfTrueThenFalse.kt") + public void testIfTrueThenFalse() throws Exception { + runTest("compiler/testData/debug/stepping/IfTrueThenFalse.kt"); + } + + @Test + @TestMetadata("ifWithInlineInCondition.kt") + public void testIfWithInlineInCondition() throws Exception { + runTest("compiler/testData/debug/stepping/ifWithInlineInCondition.kt"); + } + + @Test + @TestMetadata("iincStepping.kt") + public void testIincStepping() throws Exception { + runTest("compiler/testData/debug/stepping/iincStepping.kt"); + } + + @Test + @TestMetadata("inTheEndOfLambdaArgumentOfInlineCall.kt") + public void testInTheEndOfLambdaArgumentOfInlineCall() throws Exception { + runTest("compiler/testData/debug/stepping/inTheEndOfLambdaArgumentOfInlineCall.kt"); + } + + @Test + @TestMetadata("initBlocks.kt") + public void testInitBlocks() throws Exception { + runTest("compiler/testData/debug/stepping/initBlocks.kt"); + } + + @Test + @TestMetadata("initBlocksCompanion.kt") + public void testInitBlocksCompanion() throws Exception { + runTest("compiler/testData/debug/stepping/initBlocksCompanion.kt"); + } + + @Test + @TestMetadata("inlineCallableReference.kt") + public void testInlineCallableReference() throws Exception { + runTest("compiler/testData/debug/stepping/inlineCallableReference.kt"); + } + + @Test + @TestMetadata("inlineNamedCallableReference.kt") + public void testInlineNamedCallableReference() throws Exception { + runTest("compiler/testData/debug/stepping/inlineNamedCallableReference.kt"); + } + + @Test + @TestMetadata("inlineSimpleCall.kt") + public void testInlineSimpleCall() throws Exception { + runTest("compiler/testData/debug/stepping/inlineSimpleCall.kt"); + } + + @Test + @TestMetadata("kt15259.kt") + public void testKt15259() throws Exception { + runTest("compiler/testData/debug/stepping/kt15259.kt"); + } + + @Test + @TestMetadata("kt29179.kt") + public void testKt29179() throws Exception { + runTest("compiler/testData/debug/stepping/kt29179.kt"); + } + + @Test + @TestMetadata("kt42208.kt") + public void testKt42208() throws Exception { + runTest("compiler/testData/debug/stepping/kt42208.kt"); + } + + @Test + @TestMetadata("kt42208b.kt") + public void testKt42208b() throws Exception { + runTest("compiler/testData/debug/stepping/kt42208b.kt"); + } + + @Test + @TestMetadata("kt42208c.kt") + public void testKt42208c() throws Exception { + runTest("compiler/testData/debug/stepping/kt42208c.kt"); + } + + @Test + @TestMetadata("lambdaStepInline.kt") + public void testLambdaStepInline() throws Exception { + runTest("compiler/testData/debug/stepping/lambdaStepInline.kt"); + } + + @Test + @TestMetadata("lambdaStepInlineWithDefaults.kt") + public void testLambdaStepInlineWithDefaults() throws Exception { + runTest("compiler/testData/debug/stepping/lambdaStepInlineWithDefaults.kt"); + } + + @Test + @TestMetadata("lineNumberAfterInline.kt") + public void testLineNumberAfterInline() throws Exception { + runTest("compiler/testData/debug/stepping/lineNumberAfterInline.kt"); + } + + @Test + @TestMetadata("linenumberForOneParametersArgumentCall.kt") + public void testLinenumberForOneParametersArgumentCall() throws Exception { + runTest("compiler/testData/debug/stepping/linenumberForOneParametersArgumentCall.kt"); + } + + @Test + @TestMetadata("localFunction.kt") + public void testLocalFunction() throws Exception { + runTest("compiler/testData/debug/stepping/localFunction.kt"); + } + + @Test + @TestMetadata("localFunctionWIthOnelineExpressionBody.kt") + public void testLocalFunctionWIthOnelineExpressionBody() throws Exception { + runTest("compiler/testData/debug/stepping/localFunctionWIthOnelineExpressionBody.kt"); + } + + @Test + @TestMetadata("localProperty.kt") + public void testLocalProperty() throws Exception { + runTest("compiler/testData/debug/stepping/localProperty.kt"); + } + + @Test + @TestMetadata("multiModule.kt") + public void testMultiModule() throws Exception { + runTest("compiler/testData/debug/stepping/multiModule.kt"); + } + + @Test + @TestMetadata("multilineExpression.kt") + public void testMultilineExpression() throws Exception { + runTest("compiler/testData/debug/stepping/multilineExpression.kt"); + } + + @Test + @TestMetadata("multilineFunctionCall.kt") + public void testMultilineFunctionCall() throws Exception { + runTest("compiler/testData/debug/stepping/multilineFunctionCall.kt"); + } + + @Test + @TestMetadata("multilineInfixCall.kt") + public void testMultilineInfixCall() throws Exception { + runTest("compiler/testData/debug/stepping/multilineInfixCall.kt"); + } + + @Test + @TestMetadata("namedCallableReference.kt") + public void testNamedCallableReference() throws Exception { + runTest("compiler/testData/debug/stepping/namedCallableReference.kt"); + } + + @Test + @TestMetadata("nestedInline.kt") + public void testNestedInline() throws Exception { + runTest("compiler/testData/debug/stepping/nestedInline.kt"); + } + + @Test + @TestMetadata("noParametersArgumentCallInExpression.kt") + public void testNoParametersArgumentCallInExpression() throws Exception { + runTest("compiler/testData/debug/stepping/noParametersArgumentCallInExpression.kt"); + } + + @Test + @TestMetadata("nullcheck.kt") + public void testNullcheck() throws Exception { + runTest("compiler/testData/debug/stepping/nullcheck.kt"); + } + + @Test + @TestMetadata("primitiveNullChecks.kt") + public void testPrimitiveNullChecks() throws Exception { + runTest("compiler/testData/debug/stepping/primitiveNullChecks.kt"); + } + + @Test + @TestMetadata("propertyAccessor.kt") + public void testPropertyAccessor() throws Exception { + runTest("compiler/testData/debug/stepping/propertyAccessor.kt"); + } + + @Test + @TestMetadata("psvm.kt") + public void testPsvm() throws Exception { + runTest("compiler/testData/debug/stepping/psvm.kt"); + } + + @Test + @TestMetadata("recursion.kt") + public void testRecursion() throws Exception { + runTest("compiler/testData/debug/stepping/recursion.kt"); + } + + @Test + @TestMetadata("simpleDefaultArg.kt") + public void testSimpleDefaultArg() throws Exception { + runTest("compiler/testData/debug/stepping/simpleDefaultArg.kt"); + } + + @Test + @TestMetadata("simpleDefaultArgWithInline.kt") + public void testSimpleDefaultArgWithInline() throws Exception { + runTest("compiler/testData/debug/stepping/simpleDefaultArgWithInline.kt"); + } + + @Test + @TestMetadata("simpleInlineDefaultArg.kt") + public void testSimpleInlineDefaultArg() throws Exception { + runTest("compiler/testData/debug/stepping/simpleInlineDefaultArg.kt"); + } + + @Test + @TestMetadata("simpleSmap.kt") + public void testSimpleSmap() throws Exception { + runTest("compiler/testData/debug/stepping/simpleSmap.kt"); + } + + @Test + @TestMetadata("smapInlineAsArgument.kt") + public void testSmapInlineAsArgument() throws Exception { + runTest("compiler/testData/debug/stepping/smapInlineAsArgument.kt"); + } + + @Test + @TestMetadata("smapInlineAsInfixArgument.kt") + public void testSmapInlineAsInfixArgument() throws Exception { + runTest("compiler/testData/debug/stepping/smapInlineAsInfixArgument.kt"); + } + + @Test + @TestMetadata("smapInlineAsInlineArgument.kt") + public void testSmapInlineAsInlineArgument() throws Exception { + runTest("compiler/testData/debug/stepping/smapInlineAsInlineArgument.kt"); + } + + @Test + @TestMetadata("smapInlineInIntrinsicArgument.kt") + public void testSmapInlineInIntrinsicArgument() throws Exception { + runTest("compiler/testData/debug/stepping/smapInlineInIntrinsicArgument.kt"); + } + + @Test + @TestMetadata("stringSwitches.kt") + public void testStringSwitches() throws Exception { + runTest("compiler/testData/debug/stepping/stringSwitches.kt"); + } + + @Test + @TestMetadata("stringSwitchesSmall.kt") + public void testStringSwitchesSmall() throws Exception { + runTest("compiler/testData/debug/stepping/stringSwitchesSmall.kt"); + } + + @Test + @TestMetadata("suspendFunWithLambdaParameter.kt") + public void testSuspendFunWithLambdaParameter() throws Exception { + runTest("compiler/testData/debug/stepping/suspendFunWithLambdaParameter.kt"); + } + + @Test + @TestMetadata("suspendFunWithSuspendLambdaParameter.kt") + public void testSuspendFunWithSuspendLambdaParameter() throws Exception { + runTest("compiler/testData/debug/stepping/suspendFunWithSuspendLambdaParameter.kt"); + } + + @Test + @TestMetadata("throwException.kt") + public void testThrowException() throws Exception { + runTest("compiler/testData/debug/stepping/throwException.kt"); + } + + @Test + @TestMetadata("topLevel.kt") + public void testTopLevel() throws Exception { + runTest("compiler/testData/debug/stepping/topLevel.kt"); + } + + @Test + @TestMetadata("trait.kt") + public void testTrait() throws Exception { + runTest("compiler/testData/debug/stepping/trait.kt"); + } + + @Test + @TestMetadata("tryCatch.kt") + public void testTryCatch() throws Exception { + runTest("compiler/testData/debug/stepping/tryCatch.kt"); + } + + @Test + @TestMetadata("tryCatchExpression.kt") + public void testTryCatchExpression() throws Exception { + runTest("compiler/testData/debug/stepping/tryCatchExpression.kt"); + } + + @Test + @TestMetadata("tryCatchFinally.kt") + public void testTryCatchFinally() throws Exception { + runTest("compiler/testData/debug/stepping/tryCatchFinally.kt"); + } + + @Test + @TestMetadata("tryFinally.kt") + public void testTryFinally() throws Exception { + runTest("compiler/testData/debug/stepping/tryFinally.kt"); + } + + @Test + @TestMetadata("variablesWithoutInitializer.kt") + public void testVariablesWithoutInitializer() throws Exception { + runTest("compiler/testData/debug/stepping/variablesWithoutInitializer.kt"); + } + + @Test + @TestMetadata("voidLambdaStepInline.kt") + public void testVoidLambdaStepInline() throws Exception { + runTest("compiler/testData/debug/stepping/voidLambdaStepInline.kt"); + } + + @Test + @TestMetadata("when.kt") + public void testWhen() throws Exception { + runTest("compiler/testData/debug/stepping/when.kt"); + } + + @Test + @TestMetadata("whenComplicatedSubject.kt") + public void testWhenComplicatedSubject() throws Exception { + runTest("compiler/testData/debug/stepping/whenComplicatedSubject.kt"); + } + + @Test + @TestMetadata("whenConstant.kt") + public void testWhenConstant() throws Exception { + runTest("compiler/testData/debug/stepping/whenConstant.kt"); + } + + @Test + @TestMetadata("whenIsChecks.kt") + public void testWhenIsChecks() throws Exception { + runTest("compiler/testData/debug/stepping/whenIsChecks.kt"); + } + + @Test + @TestMetadata("whenMultiLine.kt") + public void testWhenMultiLine() throws Exception { + runTest("compiler/testData/debug/stepping/whenMultiLine.kt"); + } + + @Test + @TestMetadata("whenMultiLineSubject.kt") + public void testWhenMultiLineSubject() throws Exception { + runTest("compiler/testData/debug/stepping/whenMultiLineSubject.kt"); + } + + @Test + @TestMetadata("whenNullalbeSubject.kt") + public void testWhenNullalbeSubject() throws Exception { + runTest("compiler/testData/debug/stepping/whenNullalbeSubject.kt"); + } + + @Test + @TestMetadata("whenSubject.kt") + public void testWhenSubject() throws Exception { + runTest("compiler/testData/debug/stepping/whenSubject.kt"); + } + + @Test + @TestMetadata("whenSubject2.kt") + public void testWhenSubject2() throws Exception { + runTest("compiler/testData/debug/stepping/whenSubject2.kt"); + } + + @Test + @TestMetadata("whenWithInlineInCondition.kt") + public void testWhenWithInlineInCondition() throws Exception { + runTest("compiler/testData/debug/stepping/whenWithInlineInCondition.kt"); + } + + @Test + @TestMetadata("while.kt") + public void testWhile() throws Exception { + runTest("compiler/testData/debug/stepping/while.kt"); + } +} diff --git a/wasm/wasm.ir/build.gradle.kts b/wasm/wasm.ir/build.gradle.kts index aaf870d2c59..5730731b91f 100644 --- a/wasm/wasm.ir/build.gradle.kts +++ b/wasm/wasm.ir/build.gradle.kts @@ -11,7 +11,7 @@ dependencies { testCompileOnly(project(":kotlin-test:kotlin-test-jvm")) testCompileOnly(project(":kotlin-test:kotlin-test-junit")) testImplementation(projectTests(":compiler:tests-common")) - testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0") + testImplementation(commonDependency("org.jetbrains.kotlinx", "kotlinx-serialization-json")) } @@ -74,4 +74,4 @@ projectTest("test", true) { workingDir = projectDir } -testsJar() \ No newline at end of file +testsJar()