From 125dc65178e1e5a0f24cfd52f90720034263de82 Mon Sep 17 00:00:00 2001 From: Sergey Rostov Date: Thu, 4 Apr 2019 14:49:11 +0300 Subject: [PATCH] Gradle: Kotlin/Native stack trace parser #KT-30526 #KT-30529 --- .../internal/KotlinNativeStackTraceParser.kt | 138 ++++++++++++++++++ .../native/tasks/KotlinNativeTestTask.kt | 9 +- .../KotlinNativeStackTraceParserKtTest.kt | 95 ++++++++++++ 3 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/KotlinNativeStackTraceParser.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/KotlinNativeStackTraceParserKtTest.kt diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/KotlinNativeStackTraceParser.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/KotlinNativeStackTraceParser.kt new file mode 100644 index 00000000000..a883782bd2a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/KotlinNativeStackTraceParser.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.gradle.targets.native.internal + +import org.jetbrains.kotlin.gradle.internal.testing.ParsedStackTrace + +data class KotlinNativeStackTrace( + val message: String?, + val stackTrace: List? +) { + fun toJvm() = ParsedStackTrace( + message, + stackTrace?.map { it.toJvmStackTraceElement() } + ) + + + override fun toString(): String { + return "KotlinNativeStackTrace(\nmessage=\"$message\",\nstacktrace=[\n${stackTrace?.joinToString("\n")}\n])" + } +} + +data class KotlinNativeStackTraceElement( + val bin: String?, + val address: String?, + val className: String?, + val methodName: String?, + val signature: String?, + val offset: Int = -1, + val fileName: String?, + val lineNumber: Int = -1, + val columnNumber: Int = -1 +) { + fun toJvmStackTraceElement() = StackTraceElement( + className ?: "", + methodName ?: "", + fileName, + lineNumber + ) +} + +fun parseKotlinNativeStackTraceAsJvm(stackTrace: String): ParsedStackTrace? = + parseKotlinNativeStackTrace(stackTrace).toJvm() + +fun parseKotlinNativeStackTrace(stackTrace: String): KotlinNativeStackTrace { + val message = StringBuilder() + var firstLines = true + val stack = mutableListOf() + + // see examples in KotlinNativeStackTraceParserKtTest + stackTrace.lines().forEach { + val srcLine = it.trim() + + var bin: String? = null + var address: String? = null + var className: String? = null + var methodName: String? = null + var signature: String? = null + var offset: Int = -1 + var fileName: String? = null + var lineNumber: Int = -1 + var columnNumber: Int = -1 + + fun parsePos(fileAndPos: String) { + val fileAndPosComponents = fileAndPos.split(":") + fileName = fileAndPosComponents[0] + if (fileAndPosComponents.size > 1) lineNumber = fileAndPosComponents[1].toIntOrNull() ?: -1 + if (fileAndPosComponents.size > 2) columnNumber = fileAndPosComponents[2].toIntOrNull() ?: -1 + } + + // Example with debug info: + // at 15 test.kexe 0x0000000104902e12 kfun:f.q.n(f.q.n,f.q.n) + 50 (/file/name.kt:23:5) + // Without: + // at 15 test.kexe 0x0000000104902e12 kfun:f.q.n(f.q.n,f.q.n) + 50 + if (srcLine.startsWith("at ")) { + firstLines = false + val line = srcLine.removePrefix("at ") + + val offsetPos = line.indexOf('+') + if (offsetPos > 0) { + val withoutFileAndPos = if (line.indexOf('(', offsetPos) > 0) { + val fileAndPos = line.substringAfterLast("(").removeSuffix(")") + parsePos(fileAndPos) + line.substringBeforeLast("(") + } else line + + val components = withoutFileAndPos.split(Regex("\\s+")) + + if (components.size > 5) { + // val number = components[0] + bin = components[1] + address = components[2] + var classAndMethod = components[3] + // val plus = components[4] + offset = components[5].toIntOrNull() ?: -1 + + classAndMethod = classAndMethod.removePrefix("kfun:") + signature = "(" + classAndMethod.substringAfterLast("(") + classAndMethod = classAndMethod.substringBeforeLast("(") + + if ("." in classAndMethod) { + methodName = classAndMethod.substringAfterLast(".").trim() + className = classAndMethod.substringBeforeLast(".").trim() + } else { + methodName = classAndMethod.trim() + } + + if (methodName.isBlank()) methodName = null + + stack.add( + KotlinNativeStackTraceElement( + bin, + address, + className, + methodName, + signature, + offset, + fileName, + lineNumber, + columnNumber + ) + ) + } + } + } else { + if (firstLines) { + message.appendln(it) + } + } + } + + return KotlinNativeStackTrace( + message.toString().trim().let { if (it.isEmpty()) null else it }, + if (stack.isEmpty()) null else stack + ) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTestTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTestTask.kt index b0cc9cbb0e7..fa69a3554b9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTestTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTestTask.kt @@ -14,10 +14,11 @@ import org.gradle.process.internal.DefaultProcessForkOptions import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClientSettings import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec import org.jetbrains.kotlin.gradle.targets.native.internal.parseKotlinNativeStackTraceAsJvm -import org.jetbrains.kotlin.gradle.tasks.KotlinTest +import org.jetbrains.kotlin.gradle.tasks.KotlinTestTask +import org.jetbrains.kotlin.gradle.testing.TestsGrouping import java.io.File -open class KotlinNativeTest : KotlinTest() { +open class KotlinNativeTestTask : KotlinTestTask() { @Suppress("LeakingThis") @Internal val processOptions: ProcessForkOptions = DefaultProcessForkOptions(fileResolver) @@ -47,8 +48,8 @@ open class KotlinNativeTest : KotlinTest() { val clientSettings = TCServiceMessagesClientSettings( name, - testNameSuffix = targetName, - prependSuiteName = targetName != null, + testNameSuffix = if (showTestTargetName) targetName else null, + prepandSuiteName = showTestTargetName, treatFailedTestOutputAsStacktrace = true, stackTraceParser = ::parseKotlinNativeStackTraceAsJvm ) diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/KotlinNativeStackTraceParserKtTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/KotlinNativeStackTraceParserKtTest.kt new file mode 100644 index 00000000000..84bd1bb6066 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/KotlinNativeStackTraceParserKtTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.gradle.targets.native.internal + +import org.junit.Assert.assertEquals +import org.junit.Test + +class KotlinNativeStackTraceParserKtTest { + @Test + fun testDebug() { + assertEquals( + """ +KotlinNativeStackTrace( +message="kotlin.AssertionError: Expected <7>, actual <42>.", +stacktrace=[ +KotlinNativeStackTraceElement(bin=test.kexe, address=0x00000001048f3c86, className=kotlin.Error, methodName=, signature=(kotlin.String?)kotlin.Error, offset=70, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/Exceptions.kt, lineNumber=12, columnNumber=5) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x00000001048f3ada, className=kotlin.AssertionError, methodName=, signature=(kotlin.Any?)kotlin.AssertionError, offset=122, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/Exceptions.kt, lineNumber=128, columnNumber=5) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x0000000104987939, className=kotlin.test.DefaultAsserter, methodName=fail, signature=(kotlin.String?)kotlin.Nothing, offset=137, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/test/DefaultAsserter.kt, lineNumber=16, columnNumber=19) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x0000000104987797, className=kotlin.test.Asserter, methodName=assertTrue, signature=(kotlin.Function0;kotlin.Boolean), offset=263, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/test/Assertions.kt, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x000000010498840c, className=kotlin.test.Asserter, methodName=assertEquals, signature=(kotlin.String?;kotlin.Any?;kotlin.Any?), offset=380, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/test/Assertions.kt, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x000000010498b904, className=kotlin.test, methodName=assertEquals, signature=(#GENERIC;#GENERIC;kotlin.String?)Generic, offset=196, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/test/Assertions.kt, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x000000010498b7fe, className=kotlin.test, methodName=assertEquals${'$'}default, signature=(#GENERIC;#GENERIC;kotlin.String?;kotlin.Int)Generic, offset=158, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/test/Assertions.kt, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x000000010498b65c, className=sample.SampleTests, methodName=testMe, signature=(), offset=140, fileName=/Users/jetbrains/IdeaProjects/mpplib2/src/commonTest/kotlin/sample/SampleTests.kt, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x000000010498b593, className=sample.${'$'}SampleTests${'$'}test${'$'}0.${'$'}testMe${'$'}FUNCTION_REFERENCE${'$'}0, methodName=invoke#internal, signature=(sample.${'$'}SampleTests${'$'}test${'$'}0.${'$'}testMe${'$'}FUNCTION_REFERENCE${'$'}0.invoke#internal, offset=67, fileName=/Users/jetbrains/IdeaProjects/mpplib2/src/commonTest/kotlin/sample/SampleTests.kt, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x000000010498b4fb, className=sample.${'$'}SampleTests${'$'}test${'$'}0.${'$'}testMe${'$'}FUNCTION_REFERENCE${'$'}0, methodName=${'$'}invoke, signature=(#GENERIC)#internal, offset=75, fileName=/Users/jetbrains/IdeaProjects/mpplib2/src/commonTest/kotlin/sample/SampleTests.kt, lineNumber=10, columnNumber=6) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x0000000104985c1c, className=kotlin.native.internal.test.BaseClassSuite.TestCase, methodName=run, signature=(), offset=492, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/generated/_Collections.kt, lineNumber=98, columnNumber=7) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x000000010490649b, className=kotlin.native.internal.test.TestRunner, methodName=run#internal, signature=(kotlin.native.internal.test.TestRunner.run#internal, offset=1467, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/native/internal/test/TestRunner.kt, lineNumber=198, columnNumber=21) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x0000000104905287, className=kotlin.native.internal.test.TestRunner, methodName=runIteration#internal, signature=(kotlin.native.internal.test.TestRunner.runIteration#internal, offset=1863, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/system/Timing.kt, lineNumber=33, columnNumber=12) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x00000001049042d0, className=kotlin.native.internal.test.TestRunner, methodName=run, signature=()kotlin.Int, offset=816, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/native/internal/test/TestRunner.kt, lineNumber=232, columnNumber=17) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x0000000104902eae, className=kotlin.native.internal.test, methodName=testLauncherEntryPoint, signature=(kotlin.Array)kotlin.Int, offset=110, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/native/internal/test/Launcher.kt, lineNumber=19, columnNumber=47) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x0000000104902e12, className=kotlin.native.internal.test, methodName=main, signature=(kotlin.Array), offset=50, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/native/internal/test/Launcher.kt, lineNumber=23, columnNumber=5) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x0000000104902d67, className=null, methodName=Konan_start, signature=(Konan_start, offset=71, fileName=/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/launcher/kotlin/konan/start.kt, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x0000000104902cf1, className=null, methodName=Konan_run_start, signature=(Konan_run_start, offset=113, fileName=null, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=test.kexe, address=0x0000000104902c6b, className=null, methodName=Konan_main, signature=(Konan_main, offset=27, fileName=null, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=libdyld.dylib, address=0x00007fff5ab4fed9, className=null, methodName=start, signature=(start, offset=1, fileName=null, lineNumber=-1, columnNumber=-1) +]) + """.trim(), + parseKotlinNativeStackTrace( + """ +kotlin.AssertionError: Expected <7>, actual <42>. + at 0 test.kexe 0x00000001048f3c86 kfun:kotlin.Error.(kotlin.String?)kotlin.Error + 70 (/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/Exceptions.kt:12:5) + at 1 test.kexe 0x00000001048f3ada kfun:kotlin.AssertionError.(kotlin.Any?)kotlin.AssertionError + 122 (/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/Exceptions.kt:128:5) + at 2 test.kexe 0x0000000104987939 kfun:kotlin.test.DefaultAsserter.fail(kotlin.String?)kotlin.Nothing + 137 (/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/test/DefaultAsserter.kt:16:19) + at 3 test.kexe 0x0000000104987797 kfun:kotlin.test.Asserter.assertTrue(kotlin.Function0;kotlin.Boolean) + 263 (/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/test/Assertions.kt:) + at 4 test.kexe 0x000000010498840c kfun:kotlin.test.Asserter.assertEquals(kotlin.String?;kotlin.Any?;kotlin.Any?) + 380 (/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/test/Assertions.kt:) + at 5 test.kexe 0x000000010498b904 kfun:kotlin.test.assertEquals(#GENERIC;#GENERIC;kotlin.String?)Generic + 196 (/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/test/Assertions.kt:) + at 6 test.kexe 0x000000010498b7fe kfun:kotlin.test.assertEquals${'$'}default(#GENERIC;#GENERIC;kotlin.String?;kotlin.Int)Generic + 158 (/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/test/Assertions.kt:) + at 7 test.kexe 0x000000010498b65c kfun:sample.SampleTests.testMe() + 140 (/Users/jetbrains/IdeaProjects/mpplib2/src/commonTest/kotlin/sample/SampleTests.kt:) + at 8 test.kexe 0x000000010498b593 kfun:sample.${'$'}SampleTests${'$'}test${'$'}0.${'$'}testMe${'$'}FUNCTION_REFERENCE${'$'}0.invoke#internal + 67 (/Users/jetbrains/IdeaProjects/mpplib2/src/commonTest/kotlin/sample/SampleTests.kt:) + at 9 test.kexe 0x000000010498b4fb kfun:sample.${'$'}SampleTests${'$'}test${'$'}0.${'$'}testMe${'$'}FUNCTION_REFERENCE${'$'}0.${'$'}invoke(#GENERIC)#internal + 75 (/Users/jetbrains/IdeaProjects/mpplib2/src/commonTest/kotlin/sample/SampleTests.kt:10:6) + at 10 test.kexe 0x0000000104985c1c kfun:kotlin.native.internal.test.BaseClassSuite.TestCase.run() + 492 (/Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/generated/_Collections.kt:98:7) + at 11 test.kexe 0x000000010490649b kfun:kotlin.native.internal.test.TestRunner.run#internal + 1467 (/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/native/internal/test/TestRunner.kt:198:21) + at 12 test.kexe 0x0000000104905287 kfun:kotlin.native.internal.test.TestRunner.runIteration#internal + 1863 (/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/system/Timing.kt:33:12) + at 13 test.kexe 0x00000001049042d0 kfun:kotlin.native.internal.test.TestRunner.run()kotlin.Int + 816 (/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/native/internal/test/TestRunner.kt:232:17) + at 14 test.kexe 0x0000000104902eae kfun:kotlin.native.internal.test.testLauncherEntryPoint(kotlin.Array)kotlin.Int + 110 (/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/native/internal/test/Launcher.kt:19:47) + at 15 test.kexe 0x0000000104902e12 kfun:kotlin.native.internal.test.main(kotlin.Array) + 50 (/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/native/internal/test/Launcher.kt:23:5) + at 16 test.kexe 0x0000000104902d67 Konan_start + 71 (/Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/launcher/kotlin/konan/start.kt:) + at 17 test.kexe 0x0000000104902cf1 Konan_run_start + 113 + at 18 test.kexe 0x0000000104902c6b Konan_main + 27 + at 19 libdyld.dylib 0x00007fff5ab4fed9 start + 1 + """.trim() + ).toString() + ) + } + + @Test + fun testRelease() { + assertEquals( + """ +KotlinNativeStackTrace( +message="Uncaught Kotlin exception: kotlin.Exception: Foo!", +stacktrace=[ +KotlinNativeStackTraceElement(bin=program.kexe, address=0x000000010d6ad726, className=kotlin.Exception, methodName=, signature=(kotlin.String?)kotlin.Exception, offset=70, fileName=null, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=program.kexe, address=0x000000010d6bc7a9, className=org.test.A, methodName=, signature=()ValueType, offset=89, fileName=null, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=program.kexe, address=0x000000010d6bc712, className=org.test.A, methodName=baz, signature=()ValueType, offset=50, fileName=null, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=program.kexe, address=0x000000010d6bc633, className=org.test, methodName=bar, signature=()ValueType, offset=67, fileName=null, lineNumber=-1, columnNumber=-1) +KotlinNativeStackTraceElement(bin=program.kexe, address=0x000000010d6bc5e9, className=org.test, methodName=foo, signature=()ValueType, offset=9, fileName=null, lineNumber=-1, columnNumber=-1) +]) + """.trim(), + parseKotlinNativeStackTrace( + """ +Uncaught Kotlin exception: kotlin.Exception: Foo! + at 0 program.kexe 0x000000010d6ad726 kfun:kotlin.Exception.(kotlin.String?)kotlin.Exception + 70 + at 1 program.kexe 0x000000010d6bc7a9 kfun:org.test.A.()ValueType + 89 + at 2 program.kexe 0x000000010d6bc712 kfun:org.test.A.baz()ValueType + 50 + at 3 program.kexe 0x000000010d6bc633 kfun:org.test.bar()ValueType + 67 + at 4 program.kexe 0x000000010d6bc5e9 kfun:org.test.foo()ValueType + 9 + """.trim() + ).toString() + ) + } +} \ No newline at end of file