diff --git a/native/native.tests/build.gradle.kts b/native/native.tests/build.gradle.kts index 9384084e636..f07c73ef9ec 100644 --- a/native/native.tests/build.gradle.kts +++ b/native/native.tests/build.gradle.kts @@ -49,6 +49,7 @@ val kotlinTestLibraryTest = nativeTest("kotlinTestLibraryTest", "kotlin-test") val klibAbiTest = nativeTest("klibAbiTest", "klib-abi") val klibBinaryCompatibilityTest = nativeTest("klibBinaryCompatibilityTest", "klib-binary-compatibility") val cinteropTest = nativeTest("cinteropTest", "cinterop") +val debuggerTest = nativeTest("debuggerTest", "debugger") // "test" task is created by convention. We can't just remove it. Let's enable it in developer's environment, so it can be used // to run any test from IDE or from console, but disable it at TeamCity where it is not supposed to be ever used. diff --git a/native/native.tests/testData/lldb/canInspectArrayChildren.kt b/native/native.tests/testData/lldb/canInspectArrayChildren.kt new file mode 100644 index 00000000000..0a5b8ed8d09 --- /dev/null +++ b/native/native.tests/testData/lldb/canInspectArrayChildren.kt @@ -0,0 +1,8 @@ +// KIND: STANDALONE_LLDB +// LLDB_TRACE: canInspectArrayChildren.txt +fun main(args: Array) { + val xs = intArrayOf(3, 5, 8) + return +} + +data class Point(val x: Int, val y: Int) diff --git a/native/native.tests/testData/lldb/canInspectArrayChildren.txt b/native/native.tests/testData/lldb/canInspectArrayChildren.txt new file mode 100644 index 00000000000..80c496220a8 --- /dev/null +++ b/native/native.tests/testData/lldb/canInspectArrayChildren.txt @@ -0,0 +1,5 @@ +> b main.kt:5 +> r +> fr var xs +(ObjHeader *) xs = [..., ..., ...] +> q diff --git a/native/native.tests/testData/lldb/canInspectArrays.kt b/native/native.tests/testData/lldb/canInspectArrays.kt new file mode 100644 index 00000000000..1165129ccaf --- /dev/null +++ b/native/native.tests/testData/lldb/canInspectArrays.kt @@ -0,0 +1,13 @@ +// KIND: STANDALONE_LLDB +// LLDB_TRACE: canInspectArrays.txt +fun main(args: Array) { + val xs = IntArray(3) + xs[0] = 1 + xs[1] = 2 + xs[2] = 3 + val ys: Array = arrayOfNulls(2) + ys[0] = Point(1, 2) + return +} + +data class Point(val x: Int, val y: Int) diff --git a/native/native.tests/testData/lldb/canInspectArrays.txt b/native/native.tests/testData/lldb/canInspectArrays.txt new file mode 100644 index 00000000000..3c3308f9e4a --- /dev/null +++ b/native/native.tests/testData/lldb/canInspectArrays.txt @@ -0,0 +1,7 @@ +> b main.kt:10 +> r +> fr var +(ObjHeader *) args = [] +(ObjHeader *) xs = [..., ..., ...] +(ObjHeader *) ys = [..., ...] +> q diff --git a/native/native.tests/testData/lldb/canInspectCatchParameter.kt b/native/native.tests/testData/lldb/canInspectCatchParameter.kt new file mode 100644 index 00000000000..b6fc66bbcdf --- /dev/null +++ b/native/native.tests/testData/lldb/canInspectCatchParameter.kt @@ -0,0 +1,19 @@ +// KIND: STANDALONE_LLDB +// LLDB_TRACE: canInspectCatchParameter.txt +fun main() { + try { + throw Exception("message 1") + } catch (e1: Throwable) { + println(e1.message) + } + + try { + throwError() + } catch (e2: Throwable) { + println(e2.message) + } +} + +fun throwError() { + throw Error("message 2") +} diff --git a/native/native.tests/testData/lldb/canInspectCatchParameter.txt b/native/native.tests/testData/lldb/canInspectCatchParameter.txt new file mode 100644 index 00000000000..dd559d03327 --- /dev/null +++ b/native/native.tests/testData/lldb/canInspectCatchParameter.txt @@ -0,0 +1,9 @@ +> b main.kt:7 +> r +> fr var +(ObjHeader *) e1 = [..] +> b main.kt:11 +> c +> fr var +(ObjHeader *) e2 = [..] +> q diff --git a/native/native.tests/testData/lldb/canInspectClasses.kt b/native/native.tests/testData/lldb/canInspectClasses.kt new file mode 100644 index 00000000000..d1f3b8cb21b --- /dev/null +++ b/native/native.tests/testData/lldb/canInspectClasses.kt @@ -0,0 +1,12 @@ +// KIND: STANDALONE_LLDB +// LLDB_TRACE: canInspectClasses.txt +fun main(args: Array) { + val point = Point(1, 2) + val person = Person() + return +} + +data class Point(val x: Int, val y: Int) +class Person { + override fun toString() = "John Doe" +} diff --git a/native/native.tests/testData/lldb/canInspectClasses.txt b/native/native.tests/testData/lldb/canInspectClasses.txt new file mode 100644 index 00000000000..2e5058b7411 --- /dev/null +++ b/native/native.tests/testData/lldb/canInspectClasses.txt @@ -0,0 +1,7 @@ +> b main.kt:6 +> r +> fr var +(ObjHeader *) args = [] +(ObjHeader *) point = [x: ..., y: ...] +(ObjHeader *) person = [] +> q diff --git a/native/native.tests/testData/lldb/canInspectValuesOfPrimitiveTypes.kt b/native/native.tests/testData/lldb/canInspectValuesOfPrimitiveTypes.kt new file mode 100644 index 00000000000..38ede90beda --- /dev/null +++ b/native/native.tests/testData/lldb/canInspectValuesOfPrimitiveTypes.kt @@ -0,0 +1,10 @@ +// KIND: STANDALONE_LLDB +// LLDB_TRACE: canInspectValuesOfPrimitiveTypes.txt +fun main(args: Array) { + var a: Byte = 1 + var b: Int = 2 + var c: Long = -3 + var d: Char = 'c' + var e: Boolean = true + return +} diff --git a/native/native.tests/testData/lldb/canInspectValuesOfPrimitiveTypes.txt b/native/native.tests/testData/lldb/canInspectValuesOfPrimitiveTypes.txt new file mode 100644 index 00000000000..f3f1e6a392c --- /dev/null +++ b/native/native.tests/testData/lldb/canInspectValuesOfPrimitiveTypes.txt @@ -0,0 +1,9 @@ +> b main.kt:9 +> r +> fr var +(char) a = '\x01' +(int) b = 2 +(long) c = -3 +(short) d = 99 +(bool) e = true +> q diff --git a/native/native.tests/testData/lldb/canStepThroughCode.kt b/native/native.tests/testData/lldb/canStepThroughCode.kt new file mode 100644 index 00000000000..8bfc61d4928 --- /dev/null +++ b/native/native.tests/testData/lldb/canStepThroughCode.kt @@ -0,0 +1,8 @@ +// KIND: STANDALONE_LLDB +// LLDB_TRACE: canStepThroughCode.txt +fun main(args: Array) { + var x = 1 + var y = 2 + var z = x + y + println(z) +} diff --git a/native/native.tests/testData/lldb/canStepThroughCode.txt b/native/native.tests/testData/lldb/canStepThroughCode.txt new file mode 100644 index 00000000000..bc5db17f8dd --- /dev/null +++ b/native/native.tests/testData/lldb/canStepThroughCode.txt @@ -0,0 +1,23 @@ +> b main.kt:4 +Breakpoint 1: [..] + +> r +Process [..] stopped +[..] stop reason = breakpoint 1.1 +[..] at main.kt:4[..] + +> n +Process [..] stopped +[..] stop reason = step over +[..] at main.kt:5[..] + +> n +Process [..] stopped +[..] stop reason = step over +[..] at main.kt:6[..] + +> n +Process [..] stopped +[..] stop reason = step over +[..] at main.kt:7[..] +> q diff --git a/native/native.tests/testData/lldb/kt33055.kt b/native/native.tests/testData/lldb/kt33055.kt new file mode 100644 index 00000000000..55f449ef11e --- /dev/null +++ b/native/native.tests/testData/lldb/kt33055.kt @@ -0,0 +1,15 @@ +// KIND: STANDALONE_LLDB +// FREE_COMPILER_ARGS: -Xg-generate-debug-trampoline=enable +// LLDB_TRACE: kt33055.txt +// FILE: kt33055.kt +fun question(subject: String, names: Array = emptyArray()): String { + return buildString { // breakpoint here + append("$subject?") // actual stop + for (name in names) + append(" $name?") + } +} + +fun main(args: Array) { + println(question("Subject", args)) +} diff --git a/native/native.tests/testData/lldb/kt33055.txt b/native/native.tests/testData/lldb/kt33055.txt new file mode 100644 index 00000000000..3406fd89b76 --- /dev/null +++ b/native/native.tests/testData/lldb/kt33055.txt @@ -0,0 +1,3 @@ +> b 6 +Breakpoint 1: where = [..]`kfun:#question(kotlin.String;kotlin.Array){}kotlin.String [..] at kt33055.kt:6:12, [..] +> q diff --git a/native/native.tests/testData/lldb/kt33364.kt b/native/native.tests/testData/lldb/kt33364.kt new file mode 100644 index 00000000000..27281d627fa --- /dev/null +++ b/native/native.tests/testData/lldb/kt33364.kt @@ -0,0 +1,21 @@ +// KIND: STANDALONE_LLDB +// FREE_COMPILER_ARGS: -Xg-generate-debug-trampoline=enable +// LLDB_TRACE: kt33364.txt +// FILE: kt33364.kt +fun main() { + val param = 3 + + //breakpoint here (line: 8, breakpoint is set to 9th line) + when(param) { + 1 -> print("A") + 2 -> print("B") + else -> print("C") + } + + // breakpoint here (line: 15, breakpoint is set to 16th line) + when { + param == 1 -> print("A") + param == 2 -> print("B") + else -> print("C") + } +} diff --git a/native/native.tests/testData/lldb/kt33364.txt b/native/native.tests/testData/lldb/kt33364.txt new file mode 100644 index 00000000000..cad8f2b31cd --- /dev/null +++ b/native/native.tests/testData/lldb/kt33364.txt @@ -0,0 +1,5 @@ +> b 8 +Breakpoint 1: where = [..]kfun:#main(){} [..] at kt33364.kt:9:[..] +> b 15 +Breakpoint 2: where = [..]kfun:#main(){} [..] at kt33364.kt:16:[..] +> q diff --git a/native/native.tests/testData/lldb/kt42208.kt b/native/native.tests/testData/lldb/kt42208.kt new file mode 100644 index 00000000000..5aed3457307 --- /dev/null +++ b/native/native.tests/testData/lldb/kt42208.kt @@ -0,0 +1,11 @@ +// KIND: STANDALONE_LLDB +// LLDB_TRACE: kt42208.txt +// FILE: kt42208-1.kt +fun main() { + foo()() +} +// FILE: kt42208-2.kt +// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +inline fun foo() = { + throw Error() +} diff --git a/native/native.tests/testData/lldb/kt42208.txt b/native/native.tests/testData/lldb/kt42208.txt new file mode 100644 index 00000000000..48af71e595a --- /dev/null +++ b/native/native.tests/testData/lldb/kt42208.txt @@ -0,0 +1,14 @@ +> b ThrowException +> r +> bt +* thread #1, [..] stop reason = breakpoint 1.1 + * frame #0: [..]`ThrowException + frame #1: [..]`kfun:main$lambda$0#internal at kt42208-2.kt:10:18 + frame #2: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.invoke#internal(_this=[..]) at kt42208-1.kt:5:5 + frame #3: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.$invoke(_this=[..]){}kotlin.Nothing#internal at kt42208-1.kt:5:5 + frame #4: [..]`kfun:#main(){} at kt42208-1.kt:5:5 + frame #5: [..]`Konan_start(args=[..]) at kt42208-1.kt:4:1 + frame #6: [..] + frame #7: [..] +> q + diff --git a/native/native.tests/testData/lldb/kt42208WithPassingLambdaToAnotherFunction.kt b/native/native.tests/testData/lldb/kt42208WithPassingLambdaToAnotherFunction.kt new file mode 100644 index 00000000000..cf7019e76ac --- /dev/null +++ b/native/native.tests/testData/lldb/kt42208WithPassingLambdaToAnotherFunction.kt @@ -0,0 +1,19 @@ +// KIND: STANDALONE_LLDB +// FREE_COMPILER_ARGS: -XXLanguage:+UnitConversionsOnArbitraryExpressions +// LLDB_TRACE: kt42208WithPassingLambdaToAnotherFunction.txt +// FILE: kt42208-1.kt +fun main() { + val a = foo() + bar(a) +} +// FILE: kt42208-2.kt +// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +class A +val list = mutableListOf() +inline fun foo() = { -> + list.add(A()) +} +// FILE: kt42208-3.kt +fun bar(v:(()->Unit)) { + v() +} diff --git a/native/native.tests/testData/lldb/kt42208WithPassingLambdaToAnotherFunction.txt b/native/native.tests/testData/lldb/kt42208WithPassingLambdaToAnotherFunction.txt new file mode 100644 index 00000000000..bfbaeb68650 --- /dev/null +++ b/native/native.tests/testData/lldb/kt42208WithPassingLambdaToAnotherFunction.txt @@ -0,0 +1,12 @@ +> b kt42208-2.kt:14 +> r +> bt +* thread #1, [..] stop reason = breakpoint 1.1 + * frame #0: [..]`kfun:main$lambda$0#internal at kt42208-2.kt:14:5 + frame #1: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.invoke#internal(_this=[..]) at kt42208-1.kt:6:5 + frame #2: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.$invoke(_this=[..]){}kotlin.Boolean#internal at kt42208-1.kt:6:5 + frame #3: [..]`kfun:#bar(v=[..]){} at kt42208-3.kt:18:5 + frame #4: [..]`kfun:#main(){} at kt42208-1.kt:7:5 + frame #5: [..]`Konan_start(args=[..]) at kt42208-1.kt:5:1 +> c +> q diff --git a/native/native.tests/testData/lldb/kt42208WithVariable.kt b/native/native.tests/testData/lldb/kt42208WithVariable.kt new file mode 100644 index 00000000000..2383d5b149b --- /dev/null +++ b/native/native.tests/testData/lldb/kt42208WithVariable.kt @@ -0,0 +1,16 @@ +// KIND: STANDALONE_LLDB +// LLDB_TRACE: kt42208WithVariable.txt +// FILE: kt42208-1.kt +fun main() { + val a = foo() + a() + a() + a() +} +// FILE: kt42208-2.kt +// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +class A +val list = mutableListOf() +inline fun foo() = { -> + list.add(A()) +} diff --git a/native/native.tests/testData/lldb/kt42208WithVariable.txt b/native/native.tests/testData/lldb/kt42208WithVariable.txt new file mode 100644 index 00000000000..3664043b337 --- /dev/null +++ b/native/native.tests/testData/lldb/kt42208WithVariable.txt @@ -0,0 +1,27 @@ +> b kt42208-2.kt:15 +> r +> bt + * thread #1, [..] stop reason = breakpoint 1.1 + * frame #0: [..]`kfun:main$lambda$0#internal at kt42208-2.kt:15:5 + frame #1: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.invoke#internal(_this=[..]) at kt42208-1.kt:5:5 + frame #2: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.$invoke(_this=[..]){}kotlin.Boolean#internal at kt42208-1.kt:5:5 + frame #3: [..]`kfun:#main(){} at kt42208-1.kt:6:5 + frame #4: [..]`Konan_start(args=[..]) at kt42208-1.kt:4:1 + frame #5: [..] +> c +> bt + * thread #1, [..] stop reason = breakpoint 1.1 + * frame #0: [..]`kfun:main$lambda$0#internal at kt42208-2.kt:15:5 + frame #1: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.invoke#internal(_this=[..]) at kt42208-1.kt:5:5 + frame #2: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.$invoke(_this=[..]){}kotlin.Boolean#internal at kt42208-1.kt:5:5 + frame #3: [..]`kfun:#main(){} at kt42208-1.kt:7:5 + frame #4: [..]`Konan_start(args=[..]) at kt42208-1.kt:4:1 +> c +> bt + * thread #1, [..] stop reason = breakpoint 1.1 + * frame #0: [..]`kfun:main$lambda$0#internal at kt42208-2.kt:15:5 + frame #1: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.invoke#internal(_this=[..]) at kt42208-1.kt:5:5 + frame #2: [..]`kfun:$main$lambda$0$FUNCTION_REFERENCE$0.$invoke(_this=[..]){}kotlin.Boolean#internal at kt42208-1.kt:5:5 + frame #3: [..]`kfun:#main(){} at kt42208-1.kt:8:5 + frame #4: [..]`Konan_start(args=[..]) at kt42208-1.kt:4:1 +> q diff --git a/native/native.tests/testData/lldb/kt47198.kt b/native/native.tests/testData/lldb/kt47198.kt new file mode 100644 index 00000000000..4a171057076 --- /dev/null +++ b/native/native.tests/testData/lldb/kt47198.kt @@ -0,0 +1,8 @@ +// KIND: STANDALONE_LLDB +// LLDB_TRACE: kt47198.txt +// FILE: kt47198.kt +fun foo(a:Int) = print("a: $a") + +fun main() { + foo(33) +} diff --git a/native/native.tests/testData/lldb/kt47198.txt b/native/native.tests/testData/lldb/kt47198.txt new file mode 100644 index 00000000000..4c2bd6857e2 --- /dev/null +++ b/native/native.tests/testData/lldb/kt47198.txt @@ -0,0 +1,6 @@ +> b 4 +Breakpoint 1: where = [..]`kfun:#foo(kotlin.Int){} [..] at kt47198.kt:4:1, [..] +> r +> fr v +(int) a = 33 +> q diff --git a/native/native.tests/testData/lldb/kt47198WithBody.kt b/native/native.tests/testData/lldb/kt47198WithBody.kt new file mode 100644 index 00000000000..022d0a6b5b5 --- /dev/null +++ b/native/native.tests/testData/lldb/kt47198WithBody.kt @@ -0,0 +1,10 @@ +// KIND: STANDALONE_LLDB +// LLDB_TRACE: kt47198WithBody.txt +// FILE: kt47198.kt +fun foo(a:Int){ + print("a: ${'$'}a") +} + +fun main() { + foo(33) +} diff --git a/native/native.tests/testData/lldb/kt47198WithBody.txt b/native/native.tests/testData/lldb/kt47198WithBody.txt new file mode 100644 index 00000000000..e583fecec29 --- /dev/null +++ b/native/native.tests/testData/lldb/kt47198WithBody.txt @@ -0,0 +1,6 @@ +> b 4 +Breakpoint 1: where = [..]`kfun:#foo(kotlin.Int){} [..] at kt47198.kt:4:[..] +> r +> fr v +(int) a = 33 +> q diff --git a/native/native.tests/testData/samples/standalone_lldb_stepping.kt b/native/native.tests/testData/samples/standalone_lldb_stepping.kt new file mode 100644 index 00000000000..e90318d5621 --- /dev/null +++ b/native/native.tests/testData/samples/standalone_lldb_stepping.kt @@ -0,0 +1,10 @@ +// KIND: STANDALONE_LLDB +// LLDB_TRACE: standalone_lldb_stepping.txt +import kotlin.test.* + +fun main(args: Array) { + var x = 1 + var y = 2 + var z = x + y + println(z) +} diff --git a/native/native.tests/testData/samples/standalone_lldb_stepping.txt b/native/native.tests/testData/samples/standalone_lldb_stepping.txt new file mode 100644 index 00000000000..a96741ff6ea --- /dev/null +++ b/native/native.tests/testData/samples/standalone_lldb_stepping.txt @@ -0,0 +1,28 @@ +> b main.kt:6 +Breakpoint 1: [..] +> r +Process [..] stopped +[..] stop reason = breakpoint 1.1 +[..] at main.kt:6[..] + 3 import kotlin.test.* + 4 + 5 fun main(args: Array) { +-> 6 var x = 1 + 7 var y = 2 + 8 var z = x + y + 9 println(z) +> n +Process [..] stopped +[..] stop reason = step over +[..] at main.kt:7[..] +> n +Process [..] stopped +[..] stop reason = step over +[..] at main.kt:8[..] +> n +Process [..] stopped +[..] stop reason = step over +[..] at main.kt:9[..] +> q + + diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/InfrastructureTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/InfrastructureTestGenerated.java index 0fae466e77e..c447aba3cac 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/InfrastructureTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/InfrastructureTestGenerated.java @@ -120,6 +120,12 @@ public class InfrastructureTestGenerated extends AbstractNativeBlackBoxTest { runTest("native/native.tests/testData/samples/regular_simple_worker_tr.kt"); } + @Test + @TestMetadata("standalone_lldb_stepping.kt") + public void testStandalone_lldb_stepping() throws Exception { + runTest("native/native.tests/testData/samples/standalone_lldb_stepping.kt"); + } + @Test @TestMetadata("standalone_multifile.kt") public void testStandalone_multifile() throws Exception { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/LldbTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/LldbTestGenerated.java new file mode 100644 index 00000000000..3e9fedbea58 --- /dev/null +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/LldbTestGenerated.java @@ -0,0 +1,111 @@ +/* + * 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.konan.blackboxtest; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.junit.jupiter.api.Tag; +import org.jetbrains.kotlin.konan.blackboxtest.support.group.UseStandardTestCaseGroupProvider; +import org.jetbrains.kotlin.konan.blackboxtest.support.EnforcedProperty; +import org.jetbrains.kotlin.konan.blackboxtest.support.ClassLevelProperty; +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 org.jetbrains.kotlin.generators.tests.GenerateNativeTestsKt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("native/native.tests/testData/lldb") +@TestDataPath("$PROJECT_ROOT") +@Tag("debugger") +@UseStandardTestCaseGroupProvider() +@EnforcedProperty(property = ClassLevelProperty.OPTIMIZATION_MODE, propertyValue = "DEBUG") +public class LldbTestGenerated extends AbstractNativeBlackBoxTest { + @Test + public void testAllFilesPresentInLldb() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/native.tests/testData/lldb"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @Test + @TestMetadata("canInspectArrayChildren.kt") + public void testCanInspectArrayChildren() throws Exception { + runTest("native/native.tests/testData/lldb/canInspectArrayChildren.kt"); + } + + @Test + @TestMetadata("canInspectArrays.kt") + public void testCanInspectArrays() throws Exception { + runTest("native/native.tests/testData/lldb/canInspectArrays.kt"); + } + + @Test + @TestMetadata("canInspectCatchParameter.kt") + public void testCanInspectCatchParameter() throws Exception { + runTest("native/native.tests/testData/lldb/canInspectCatchParameter.kt"); + } + + @Test + @TestMetadata("canInspectClasses.kt") + public void testCanInspectClasses() throws Exception { + runTest("native/native.tests/testData/lldb/canInspectClasses.kt"); + } + + @Test + @TestMetadata("canInspectValuesOfPrimitiveTypes.kt") + public void testCanInspectValuesOfPrimitiveTypes() throws Exception { + runTest("native/native.tests/testData/lldb/canInspectValuesOfPrimitiveTypes.kt"); + } + + @Test + @TestMetadata("canStepThroughCode.kt") + public void testCanStepThroughCode() throws Exception { + runTest("native/native.tests/testData/lldb/canStepThroughCode.kt"); + } + + @Test + @TestMetadata("kt33055.kt") + public void testKt33055() throws Exception { + runTest("native/native.tests/testData/lldb/kt33055.kt"); + } + + @Test + @TestMetadata("kt33364.kt") + public void testKt33364() throws Exception { + runTest("native/native.tests/testData/lldb/kt33364.kt"); + } + + @Test + @TestMetadata("kt42208.kt") + public void testKt42208() throws Exception { + runTest("native/native.tests/testData/lldb/kt42208.kt"); + } + + @Test + @TestMetadata("kt42208WithPassingLambdaToAnotherFunction.kt") + public void testKt42208WithPassingLambdaToAnotherFunction() throws Exception { + runTest("native/native.tests/testData/lldb/kt42208WithPassingLambdaToAnotherFunction.kt"); + } + + @Test + @TestMetadata("kt42208WithVariable.kt") + public void testKt42208WithVariable() throws Exception { + runTest("native/native.tests/testData/lldb/kt42208WithVariable.kt"); + } + + @Test + @TestMetadata("kt47198.kt") + public void testKt47198() throws Exception { + runTest("native/native.tests/testData/lldb/kt47198.kt"); + } + + @Test + @TestMetadata("kt47198WithBody.kt") + public void testKt47198WithBody() throws Exception { + runTest("native/native.tests/testData/lldb/kt47198WithBody.kt"); + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt index 825fed95d7a..c17fe746c5f 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt @@ -8,6 +8,8 @@ package org.jetbrains.kotlin.generators.tests import org.jetbrains.kotlin.generators.generateTestGroupSuiteWithJUnit5 import org.jetbrains.kotlin.generators.model.annotation import org.jetbrains.kotlin.konan.blackboxtest.* +import org.jetbrains.kotlin.konan.blackboxtest.support.ClassLevelProperty +import org.jetbrains.kotlin.konan.blackboxtest.support.EnforcedProperty import org.jetbrains.kotlin.konan.blackboxtest.support.group.UseExtTestCaseGroupProvider import org.jetbrains.kotlin.konan.blackboxtest.support.group.UseStandardTestCaseGroupProvider import org.jetbrains.kotlin.test.TargetBackend @@ -81,10 +83,22 @@ fun main() { model("CInterop/KT-39120/defs", pattern = "^([^_](.+))$", recursive = false) } } + + // LLDB integration tests. + testGroup("native/native.tests/tests-gen", "native/native.tests/testData") { + testClass( + suiteTestClassName = "LldbTestGenerated", + annotations = listOf(debugger(), provider(), debugOnly()) + ) { + model("lldb") + } + } } } private inline fun provider() = annotation(T::class.java) +private fun debugOnly() = annotation(EnforcedProperty::class.java, Pair("property", ClassLevelProperty.OPTIMIZATION_MODE), Pair("propertyValue", "DEBUG")) private fun codegen() = annotation(Tag::class.java, "codegen") +private fun debugger() = annotation(Tag::class.java, "debugger") private fun infrastructure() = annotation(Tag::class.java, "infrastructure") diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt index c68c14a0eae..00b9cd21a8a 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt @@ -79,7 +79,8 @@ private object NativeTestSupport { TestProcessSettings( nativeHome, computeNativeClassLoader(), - computeBaseDirs() + computeBaseDirs(), + DebugUtils() ) } as TestProcessSettings diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestCase.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestCase.kt index 6b2c4c679b6..2e55b728141 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestCase.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestCase.kt @@ -179,12 +179,12 @@ internal class TestCase( val extras: Extras ) { sealed interface Extras - class NoTestRunnerExtras(val entryPoint: String, val inputDataFile: File?) : Extras + class NoTestRunnerExtras(val entryPoint: String, val inputDataFile: File? = null, val arguments: List = emptyList()) : Extras class WithTestRunnerExtras(val runnerType: TestRunnerType, val ignoredTests: Set = emptySet()) : Extras init { when (kind) { - TestKind.STANDALONE_NO_TR -> assertTrue(extras is NoTestRunnerExtras) + TestKind.STANDALONE_NO_TR, TestKind.STANDALONE_LLDB -> assertTrue(extras is NoTestRunnerExtras) TestKind.REGULAR, TestKind.STANDALONE -> assertTrue(extras is WithTestRunnerExtras) } } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestDirectives.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestDirectives.kt index 2e259ca8bde..30aaf4b7492 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestDirectives.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestDirectives.kt @@ -12,9 +12,11 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.INPUT_DATA import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.KIND import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.OUTPUT_DATA_FILE import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.EXPECTED_TIMEOUT_FAILURE +import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.LLDB_TRACE import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.TEST_RUNNER import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunCheck import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunCheck.OutputDataFile +import org.jetbrains.kotlin.konan.blackboxtest.support.util.LldbSessionSpecification import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer import org.jetbrains.kotlin.test.directives.model.StringDirective @@ -26,7 +28,7 @@ import java.io.File internal object TestDirectives : SimpleDirectivesContainer() { val KIND by enumDirective( description = """ - Usage: // KIND: [REGULAR, STANDALONE, STANDALONE_NO_TR] + Usage: // KIND: [REGULAR, STANDALONE, STANDALONE_NO_TR, STANDALONE_LLDB] Declares the kind of the test: - REGULAR (the default) - include this test into the shared test binary. @@ -37,6 +39,8 @@ internal object TestDirectives : SimpleDirectivesContainer() { - STANDALONE_NO_TR - compile the test to a separate binary that is supposed to have main entry point. The entry point can be customized Note that @kotlin.Test annotations are ignored. + + - STANDALONE_LLDB - compile the test to a separate binary and debug with LLDB. """.trimIndent() ) @@ -51,7 +55,7 @@ internal object TestDirectives : SimpleDirectivesContainer() { val ENTRY_POINT by stringDirective( description = """ Specify custom program entry point. The default entry point is `main` function in the root package. - Note that this directive makes sense only in combination with // KIND: STANDALONE_NO_TR + Note that this directive makes sense only in combination with // KIND: STANDALONE_NO_TR or // KIND: STANDALONE_LLDB """.trimIndent() ) @@ -122,12 +126,19 @@ internal object TestDirectives : SimpleDirectivesContainer() { val FREE_COMPILER_ARGS by stringDirective( description = "Specify free compiler arguments for Kotlin/Native compiler" ) + + val LLDB_TRACE by stringDirective( + description = """ + Specify a filename containing the LLDB commands and the patterns that + the output should match""".trimIndent(), + ) } internal enum class TestKind { REGULAR, STANDALONE, - STANDALONE_NO_TR; + STANDALONE_NO_TR, + STANDALONE_LLDB; } internal enum class TestRunnerType { @@ -203,6 +214,12 @@ internal fun parseEntryPoint(registeredDirectives: RegisteredDirectives, locatio return entryPoint } +internal fun parseLLDBSpec(baseDir:File, registeredDirectives: RegisteredDirectives, location: Location): LldbSessionSpecification { + val specFile = parseFileBasedDirective(baseDir, LLDB_TRACE, registeredDirectives, location) + ?: fail { "$location: A lldb session specification must be provided" } + return LldbSessionSpecification.parse(specFile.readText()) +} + internal fun parseModule(parsedDirective: RegisteredDirectivesParser.ParsedDirective, location: Location): TestModule.Exclusive { val module = parsedDirective.values.singleOrNull()?.toString()?.let(TEST_MODULE_REGEX::matchEntire)?.let { match -> val name = match.groupValues[1] diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/StandardTestCaseGroupProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/StandardTestCaseGroupProvider.kt index 10055719561..74b4d8b1237 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/StandardTestCaseGroupProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/StandardTestCaseGroupProvider.kt @@ -48,6 +48,11 @@ internal class StandardTestCaseGroupProvider : TestCaseGroupProvider { val testCases = includedTestDataFiles.map { testDataFile -> createTestCase(testDataFile, settings) } + if (!settings.get().lldbIsAvailable) { + val lldbTests = testCases.filter { it.kind == TestKind.STANDALONE_LLDB } + lldbTests.mapTo(disabledTestCaseIds) { it.id } + } + TestCaseGroup.Default(disabledTestCaseIds, testCases) } } @@ -200,6 +205,12 @@ internal class StandardTestCaseGroupProvider : TestCaseGroupProvider { fixPackageNames(testModules.values, nominalPackageName, testDataFile) } + val lldbSpec = if (testKind == TestKind.STANDALONE_LLDB) parseLLDBSpec( + baseDir = testDataFile.parentFile, + registeredDirectives, + location + ) else null + val testCase = TestCase( id = TestCaseId.TestDataFile(testDataFile), kind = testKind, @@ -209,15 +220,26 @@ internal class StandardTestCaseGroupProvider : TestCaseGroupProvider { checks = TestRunChecks( computeExecutionTimeoutCheck(settings, expectedTimeoutFailure), computeExitCodeCheck(testKind, registeredDirectives, location), - computeOutputDataFileCheck(testDataFile, registeredDirectives, location) + computeOutputDataFileCheck(testDataFile, registeredDirectives, location), + lldbSpec?.let { OutputMatcher(it::matchOutput) } ), - extras = if (testKind == TestKind.STANDALONE_NO_TR) - NoTestRunnerExtras( - entryPoint = parseEntryPoint(registeredDirectives, location), - inputDataFile = parseInputDataFile(baseDir = testDataFile.parentFile, registeredDirectives, location) - ) - else - WithTestRunnerExtras(runnerType = parseTestRunner(registeredDirectives, location)) + extras = when (testKind) { + TestKind.STANDALONE_NO_TR -> { + NoTestRunnerExtras( + entryPoint = parseEntryPoint(registeredDirectives, location), + inputDataFile = parseInputDataFile(baseDir = testDataFile.parentFile, registeredDirectives, location) + ) + } + TestKind.REGULAR, TestKind.STANDALONE -> { + WithTestRunnerExtras(runnerType = parseTestRunner(registeredDirectives, location)) + } + TestKind.STANDALONE_LLDB -> { + NoTestRunnerExtras( + entryPoint = parseEntryPoint(registeredDirectives, location), + arguments = lldbSpec!!.generateCLIArguments(settings.get().lldbPrettyPrinters) + ) + } + } ) testCase.initialize( givenModules = settings.get().klibs.mapToSet(TestModule::Given), diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/AbstractLocalProcessRunner.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/AbstractLocalProcessRunner.kt index a611041c88b..c91b365faef 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/AbstractLocalProcessRunner.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/AbstractLocalProcessRunner.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunCheck.Execu import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunCheck.ExitCode import org.jetbrains.kotlin.konan.blackboxtest.support.runner.UnfilteredProcessOutput.Companion.launchReader import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter +import org.junit.jupiter.api.Assertions.fail import java.io.ByteArrayOutputStream import kotlin.time.* @@ -105,6 +106,22 @@ internal abstract class AbstractLocalProcessRunner(private val checks: TestRu "Tested process output mismatch. See \"TEST STDOUT\" and \"EXPECTED OUTPUT DATA FILE\" below." } } + is TestRunCheck.OutputMatcher -> { + try { + verifyExpectation(check.match(runResult.processOutput.stdOut.filteredOutput)) { + "Tested process output has not passed validation." + } + } catch (t: Throwable) { + if (t is Exception || t is AssertionError) { + fail( + getLoggedRun().withErrorMessage("Tested process output has not passed validation:\n\n" + t.message), + t + ) + } else { + throw t + } + } + } } } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/BaseTestRunProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/BaseTestRunProvider.kt index dcffc199e97..ef40f20a510 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/BaseTestRunProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/BaseTestRunProvider.kt @@ -29,6 +29,13 @@ internal open class BaseTestRunProvider { addIfNotNull(testCase.checks.outputDataFile?.file?.let(TestRunParameter::WithExpectedOutputData)) when (testCase.kind) { + TestKind.STANDALONE_LLDB -> { + assertTrue(testName == null) + // Note: TestRunParameter.WithLLDB adds program arguments and would therefore conflict + // with other TestRunParameters that do the same (such as WithTCTestLogger). + add(TestRunParameter.WithLLDB(testCase.extras().arguments)) + addIfNotNull(testCase.extras().inputDataFile?.let(TestRunParameter::WithInputData)) + } TestKind.STANDALONE_NO_TR -> { assertTrue(testName == null) addIfNotNull(testCase.extras().inputDataFile?.let(TestRunParameter::WithInputData)) diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRun.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRun.kt index 9e2d732ee7f..0776825b527 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRun.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRun.kt @@ -8,7 +8,10 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.runner import org.jetbrains.kotlin.konan.blackboxtest.support.* import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact.Executable import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Success +import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeHome +import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings import org.jetbrains.kotlin.konan.blackboxtest.support.util.DumpedTestListing +import org.jetbrains.kotlin.konan.blackboxtest.support.util.LldbSessionSpecification import org.jetbrains.kotlin.konan.blackboxtest.support.util.startsWith import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertFalse import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail @@ -96,6 +99,13 @@ internal sealed interface TestRunParameter { override fun applyTo(programArgs: MutableList) = Unit } + class WithLLDB(val commands: List) : TestRunParameter { + override fun applyTo(programArgs: MutableList) { + programArgs.add(0, "lldb") + programArgs.addAll(commands) + } + } + // Currently, used only for logging the data. class WithExpectedOutputData(val expectedOutputDataFile: File) : TestRunParameter { override fun applyTo(programArgs: MutableList) = Unit diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunChecks.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunChecks.kt index 324d6886e2e..7411ea018b4 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunChecks.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunChecks.kt @@ -22,18 +22,22 @@ internal sealed interface TestRunCheck { } class OutputDataFile(val file: File) : TestRunCheck + + class OutputMatcher(val match: (String) -> Boolean): TestRunCheck } internal class TestRunChecks( val executionTimeoutCheck: ExecutionTimeout, private val exitCodeCheck: ExitCode, - val outputDataFile: OutputDataFile? + val outputDataFile: OutputDataFile?, + val outputMatcher: OutputMatcher? ) : Iterable { override fun iterator() = iterator { yield(executionTimeoutCheck) yield(exitCodeCheck) yieldIfNotNull(outputDataFile) + yieldIfNotNull(outputMatcher) } companion object { @@ -42,7 +46,8 @@ internal class TestRunChecks( fun Default(timeout: Duration) = TestRunChecks( executionTimeoutCheck = ExecutionTimeout.ShouldNotExceed(timeout), exitCodeCheck = ExitCode.Expected(0), - outputDataFile = null + outputDataFile = null, + outputMatcher = null ) } } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunProvider.kt index 53400c55935..e4d0147d010 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunProvider.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilati import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationFactory import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider +import org.jetbrains.kotlin.konan.blackboxtest.support.settings.DebugUtils import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings import org.jetbrains.kotlin.konan.blackboxtest.support.util.ThreadSafeCache import org.jetbrains.kotlin.konan.blackboxtest.support.util.TreeNode @@ -103,6 +104,12 @@ internal class TestRunProvider( fun createTestRun(testRunName: String, testName: TestName?) = createTestRun(testCase, executable, testRunName, testName) when (testCase.kind) { + TestKind.STANDALONE_LLDB -> { + if (!settings.get().lldbIsAvailable) TreeNode.oneLevel() + val testRunName = testCase.extras().entryPoint.substringAfterLast('.') + val testRun = createTestRun(testRunName, testName = null) + TreeNode.oneLevel(testRun) + } TestKind.STANDALONE_NO_TR -> { val testRunName = testCase.extras().entryPoint.substringAfterLast('.') val testRun = createTestRun(testRunName, testName = null) @@ -130,7 +137,7 @@ internal class TestRunProvider( val testCase = testCaseGroup.getByName(testCaseId) ?: fail { "No test case for $testCaseId" } val testCompilation = when (testCase.kind) { - TestKind.STANDALONE, TestKind.STANDALONE_NO_TR -> { + TestKind.STANDALONE, TestKind.STANDALONE_NO_TR, TestKind.STANDALONE_LLDB -> { // Create a separate compilation for each standalone test case. cachedCompilations.computeIfAbsent( TestCompilationCacheKey.Standalone(testCaseId) diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestProcessSettings.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestProcessSettings.kt index bd2df3d0bda..3f5d49e642b 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestProcessSettings.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestProcessSettings.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.konan.target.Distribution import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue import java.io.File +import java.io.IOException import java.util.* import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -29,12 +30,23 @@ internal class KotlinNativeTargets(val testTarget: KonanTarget, val hostTarget: internal class KotlinNativeHome(val dir: File) { val librariesDir: File = dir.resolve("klib") val stdlibFile: File = librariesDir.resolve("common/stdlib") - + val lldbPrettyPrinters: File = dir.resolve("tools/konan_lldb.py") val properties: Properties by lazy { dir.resolve("konan/konan.properties").inputStream().use { Properties().apply { load(it) } } } } +internal class DebugUtils { + val lldbIsAvailable: Boolean by lazy { + try { + val exitCode = ProcessBuilder("lldb", "-version").start().waitFor() + exitCode == 0 + } catch (e: IOException) { + false + } + } +} + /** * Lazy-initialized class loader with the Kotlin/Native embedded compiler. */ diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/LldbSessionSpecification.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/LldbSessionSpecification.kt new file mode 100644 index 00000000000..81564e59698 --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/LldbSessionSpecification.kt @@ -0,0 +1,119 @@ +/* + * 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.konan.blackboxtest.support.util + +import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail +import java.io.File + +private class Step(val command: String, val body: List) + +class LldbSessionSpecification private constructor( + private val steps: List, +) { + + + fun generateCLIArguments(pp: File): List { + val args = mutableListOf("-b", "-o", "command script import ${pp.absolutePath}") + args.addAll(steps.flatMap { listOf("-o", it.command) }) + return args + } + + // TODO: Re-introduce this when we add support for iOS simulator based testing. + fun targetIsHost() = true + + fun matchOutput(output: String): Boolean { + val blocks = output.split(lldbOutputCommand).filterNot(String::isEmpty) + if (targetIsHost()) { + check(blocks[0].startsWith("(lldb) target create")) { "Missing block \"target create\". Got: ${blocks[0]}" } + check(blocks[1].startsWith("(lldb) command script import")) { + "Missing block \"command script import\". Got: ${blocks[1]}" + } + } + val responses = if (targetIsHost()) + blocks.drop(2) + else + blocks.drop(2).dropLast(1) + + val recordedSteps = responses.map { Step(it.lines().first(), it.lines().drop(1)) } + + if (recordedSteps.size != steps.size) { + val message = """ + |Responses do not match commands. + | + |COMMANDS: |${steps.map { it.command }} (${steps.size}) + |RESPONSES: |${recordedSteps.map { it.command }} (${recordedSteps.size}) + """.trimMargin() + fail { message } + } + + steps.zip(recordedSteps).all { (step, recordedStep) -> recordedStep.command == "(lldb) ${step.command}" } + + for ((step, recordedStep) in steps.zip(recordedSteps)) { + check(recordedStep.command == "(lldb) ${step.command}") { + "Wrong command in response. Expected: (lldb) ${step.command}. Got: ${recordedStep.command}" + } + val mismatch = findMismatch(step.body, recordedStep.body) + if (mismatch != null) { + val message = """ + |Wrong LLDB output. + | + |COMMAND: ${step.command} + |PATTERN: $mismatch + |OUTPUT: + |${recordedStep.body.joinToString("\n")} + """.trimMargin() + fail { message } + } + } + return true + } + + private fun findMismatch(patterns: List, actualLines: List): String? { + val indices = mutableListOf() + for (pattern in patterns) { + val idx = actualLines.indexOfFirst { match(pattern, it) } + if (idx == -1) { + return pattern + } + indices += idx + } + check(indices == indices.sorted()) + return null + } + + private fun match(pattern: String, line: String): Boolean { + val chunks = pattern.split(wildcard) + .filter { it.isNotBlank() } + .map { it.trim() } + check(chunks.isNotEmpty()) + val trimmedLine = line.trim() + + val indices = chunks.map { trimmedLine.indexOf(it) } + if (indices.any { it == -1 } || indices != indices.sorted()) return false + if (!(trimmedLine.startsWith(chunks.first()) || pattern.startsWith("[..]"))) return false + if (!(trimmedLine.endsWith(chunks.last()) || pattern.endsWith("[..]"))) return false + return true + } + + companion object { + val lldbOutputCommand = """(?=\(lldb\))""".toRegex() + val wildcard = """\s*\[\.\.]\s*""".toRegex() + val separator = "(?=^>)".toRegex(RegexOption.MULTILINE) + + fun parse(spec: String): LldbSessionSpecification { + val blocks = spec.trimIndent() + .split(separator) + .filterNot(String::isEmpty) + for (cmd in blocks) { + check(cmd.startsWith(">")) { "Invalid lldb session specification: $cmd" } + } + val steps = blocks.map { + Step(it.lines().first().substring(1).trim(), it.lines().drop(1).filter { it.isNotBlank() }) + } + return LldbSessionSpecification(steps) + } + } +}