From 0304f090e851820e1e743843fb540c94cf15ef9d Mon Sep 17 00:00:00 2001 From: Sergey Rostov Date: Thu, 4 Apr 2019 14:16:21 +0300 Subject: [PATCH] Gradle: JS stack trace parser #KT-30526 --- .../internal/testing/ParsedStackTrace.kt | 11 +++++ .../testing/TCServiceMessagesClient.kt | 39 ++++----------- .../js/internal/NodeJsStackTraceParser.kt | 1 - .../targets/js/tasks/KotlinNodeJsTestTask.kt | 6 ++- .../gradle/testing/KotlinTestFailure.kt | 34 ++++++++++--- .../tcsmc/TCServiceMessagesClientTest.kt | 2 +- .../internal/NodeJsStackTraceParserKtTest.kt | 48 +++++++++++++++++++ 7 files changed, 101 insertions(+), 40 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/ParsedStackTrace.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/targets/js/internal/NodeJsStackTraceParserKtTest.kt diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/ParsedStackTrace.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/ParsedStackTrace.kt new file mode 100644 index 00000000000..ae2b13e1025 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/ParsedStackTrace.kt @@ -0,0 +1,11 @@ +/* + * 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.internal.testing + +data class ParsedStackTrace( + val message: String?, + val stackTrace: List? +) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesClient.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesClient.kt index 730bb9df609..64d43ba34ac 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesClient.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesClient.kt @@ -22,10 +22,9 @@ import java.lang.System.currentTimeMillis as currentTimeMillis1 data class TCServiceMessagesClientSettings( val rootNodeName: String, val testNameSuffix: String? = null, - val prependSuiteName: Boolean = false, + val prepandSuiteName: Boolean = false, val treatFailedTestOutputAsStacktrace: Boolean = false, - val stackTraceParser: (String) -> ParsedStackTrace? = { null }, - val ignoreOutOfRootNodes: Boolean = false + val stackTraceParser: (String) -> ParsedStackTrace? = { null } ) internal class TCServiceMessagesClient( @@ -33,11 +32,7 @@ internal class TCServiceMessagesClient( val settings: TCServiceMessagesClientSettings, val log: Logger ) : ServiceMessageParserCallback { - lateinit var rootOperationId: Any - inline fun root(operation: OperationIdentifier, actions: () -> Unit) { - rootOperationId = operation.id - RootNode(operation.id).open { actions() } @@ -88,7 +83,7 @@ internal class TCServiceMessagesClient( parent.requireReportingNode() val finalTestName = testName.let { - if (settings.prependSuiteName) "${parent.fullNameWithoutRoot}.$it" + if (settings.prepandSuiteName) "${parent.fullNameWithoutRoot}.$it" else it } @@ -131,13 +126,10 @@ internal class TCServiceMessagesClient( results.failure( descriptor.id, KotlinTestFailure( - (parsedStackTrace?.message ?: message.failureMessage)?.let { extractExceptionClassName(it) } - ?: "Unknown", + extractExceptionClassName(parsedStackTrace?.message ?: message.failureMessage), message.failureMessage, stacktrace, - patchStackTrace(this, parsedStackTrace?.stackTrace), - message.expected, - message.actual + patchStackTrace(this, parsedStackTrace?.stackTrace) ) ) } @@ -166,9 +158,9 @@ internal class TCServiceMessagesClient( ) { if (settings.treatFailedTestOutputAsStacktrace) { output.append(text) - } else { - results.output(descriptor.id, DefaultTestOutputEvent(destination, text)) } + + results.output(descriptor.id, DefaultTestOutputEvent(destination, text)) } private inline fun NodeType.open(contents: (NodeType) -> Unit) = open(System.currentTimeMillis()) { @@ -191,13 +183,8 @@ internal class TCServiceMessagesClient( private fun close(ts: Long, assertLocalId: String?) = pop().also { if (assertLocalId != null) { - if (it.localId != assertLocalId && settings.ignoreOutOfRootNodes && it.parent == null) { - push(it) - return it - } - check(it.localId == assertLocalId) { - "Bad TCSM: unexpected node to close `$assertLocalId`, expected `${it.localId}`, stack: ${ + "Bad TCSM: unexpected node to close: ${it.localId}, stack: ${ leaf.collectParents().joinToString("") { item -> "\n - ${item.localId}" } }\n" } @@ -346,7 +333,6 @@ internal class TCServiceMessagesClient( inner class RootNode(val ownerBuildOperationId: Any) : GroupNode(null, settings.rootNodeName) { override val descriptor: TestDescriptorInternal = object : DefaultTestSuiteDescriptor(settings.rootNodeName, localId) { override fun getOwnerBuildOperationId(): Any? = this@RootNode.ownerBuildOperationId - override fun getParent(): TestDescriptorInternal? = null } override fun requireReportingNode(): TestDescriptorInternal = descriptor @@ -390,9 +376,7 @@ internal class TCServiceMessagesClient( this.reportingParent = reportingParent descriptor = object : DefaultTestSuiteDescriptor(id, fullName) { - override fun getDisplayName(): String = fullNameWithoutRoot - override fun getOwnerBuildOperationId(): Any? = rootOperationId - override fun getParent(): TestDescriptorInternal = reportingParent.descriptor + override fun getParent(): TestDescriptorInternal? = reportingParent.descriptor } shouldReportComplete = true @@ -429,12 +413,9 @@ internal class TCServiceMessagesClient( ) : Node(parent, localId) { val output by lazy { StringBuilder() } - private val parentDescriptor = (this@TestNode.parent as GroupNode).requireReportingNode() - override val descriptor: TestDescriptorInternal = object : DefaultTestDescriptor(id, className, methodName, classDisplayName, displayName) { - override fun getOwnerBuildOperationId(): Any? = rootOperationId - override fun getParent(): TestDescriptorInternal = parentDescriptor + override fun getParent(): TestDescriptorInternal? = (this@TestNode.parent as GroupNode).requireReportingNode() } override fun markStarted(ts: Long) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/internal/NodeJsStackTraceParser.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/internal/NodeJsStackTraceParser.kt index dc8eb3ab490..ee73e50ba5c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/internal/NodeJsStackTraceParser.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/internal/NodeJsStackTraceParser.kt @@ -46,7 +46,6 @@ fun parseNodeJsStackTrace(stackTrace: String): NodeJsStackTrace { var firstLines = true val stack = mutableListOf() - // see examples at NodeJsStackTraceParserKtTest stackTrace.lines().forEach { val srcLine = it.trim() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestTask.kt index 475f8bed56c..ffa85160559 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestTask.kt @@ -13,9 +13,10 @@ import org.gradle.process.ProcessForkOptions 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.js.internal.parseNodeJsStackTrace +import org.jetbrains.kotlin.gradle.targets.js.internal.parseNodeJsStackTraceAsJvm import org.jetbrains.kotlin.gradle.tasks.KotlinTestTask import org.jetbrains.kotlin.gradle.testing.IgnoredTestSuites -import org.jetbrains.kotlin.gradle.testing.TestsGrouping import java.io.File open class KotlinNodeJsTestTask : KotlinTestTask() { @@ -74,7 +75,8 @@ open class KotlinNodeJsTestTask : KotlinTestTask() { val clientSettings = TCServiceMessagesClientSettings( name, testNameSuffix = if (showTestTargetName) targetName else null, - prepandSuiteName = true + prepandSuiteName = true, + stackTraceParser = ::parseNodeJsStackTraceAsJvm ) return TCServiceMessagesTestExecutionSpec( diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/KotlinTestFailure.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/KotlinTestFailure.kt index 3a7482aedf5..2af042c27bd 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/KotlinTestFailure.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/KotlinTestFailure.kt @@ -5,20 +5,40 @@ package org.jetbrains.kotlin.gradle.testing +import org.gradle.internal.serialize.PlaceholderException + /** * Class to be shown in default Gradle tests console reporter. * * Example console output: * ``` - * clientTest.CommonTest.test1 FAILED - * org.jetbrains.kotlin.gradle.testing.KotlinTestFailure + * sample.SampleTests.testMe FAILED + * AssertionError at mpplib2/src/commonTest/kotlin/sample/SampleTests.kt:9 * ``` */ -class KotlinTestFailure(message: String?, private val stackTrace: String?) : Throwable(message) { - override fun fillInStackTrace(): Throwable = this +class KotlinTestFailure( + className: String, + message: String?, + private val stackTraceString: String?, + private val stackTrace: List? = null +) : PlaceholderException( + className, + message, + null, + null, + null, + null +) { + override fun getStackTrace(): Array = + stackTrace?.toTypedArray() ?: arrayOf() + + override fun fillInStackTrace(): Throwable { + return this + } + override fun toString(): String = - if (stackTrace != null) { - if (message != null && message !in stackTrace) message + "\n" + stackTrace - else stackTrace + if (stackTraceString != null) { + if (message != null && message!! !in stackTraceString) message + "\n" + stackTraceString + else stackTraceString } else message ?: "Test failed" } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TCServiceMessagesClientTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TCServiceMessagesClientTest.kt index bc4d4ea1a83..2461407257a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TCServiceMessagesClientTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TCServiceMessagesClientTest.kt @@ -36,7 +36,7 @@ open class TCServiceMessagesClientTest { results, TCServiceMessagesClientSettings( rootNodeName, - treatFailedTestOutputAsStacktrace + treatFailedTestOutputAsStacktrace = treatFailedTestOutputAsStacktrace ), LoggerFactory.getLogger("test") ) diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/targets/js/internal/NodeJsStackTraceParserKtTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/targets/js/internal/NodeJsStackTraceParserKtTest.kt new file mode 100644 index 00000000000..ef23618bad6 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/targets/js/internal/NodeJsStackTraceParserKtTest.kt @@ -0,0 +1,48 @@ +/* + * 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.js.internal + +import org.junit.Test +import kotlin.test.assertEquals + +class NodeJsStackTraceParserKtTest { + @Test + fun parseNodeJsStackTrace() { + val parsed = parseNodeJsStackTrace( + """ +AssertionError: Expected value to be true. + at AssertionError_init_0 (/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-stdlib-js-1.3-SNAPSHOT.jar_730a1b227513cf16a9b639e009a985fc/kotlin/exceptions.kt:102:37) + at DefaultJsAsserter.failWithMessage_0 (/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:80:19) + at DefaultJsAsserter.assertTrue_o10pc4${'$'} (/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:60:13) + at DefaultJsAsserter.assertTrue_4mavae${'$'} (/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:67:9) + at assertTrue_0 (/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/Assertions.kt:36:21) + at DeepPackageTest${'$'}Inner.testHello (/Users/jetbrains/IdeaProjects/mpplib2/src/commonTest/kotlin/sample/a/DeepPackageTest.kt:16:13) + at /Users/jetbrains/IdeaProjects/mpplib2/build/js_test_node_modules/mpplib2_test.js:93:48 + at Object.fn [as test] (/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/src/KotlinTestRunner.ts:12:25) + at Object.test (/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/src/KotlinTestTeamCityReporter.ts:80:28) + at test (/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/TestApi.kt:57:15) """.trimIndent() + ).toString() + + assertEquals( + """ +NodeJsStackTrace( +message="AssertionError: Expected value to be true.", +stacktrace=[ +NodeJsStackTraceElement(className=AssertionError, methodName=init, fileName=/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-stdlib-js-1.3-SNAPSHOT.jar_730a1b227513cf16a9b639e009a985fc/kotlin/exceptions.kt, lineNumber=102, colNumber=37) +NodeJsStackTraceElement(className=DefaultJsAsserter, methodName=failWithMessage, fileName=/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt, lineNumber=80, colNumber=19) +NodeJsStackTraceElement(className=DefaultJsAsserter, methodName=assertTrue, fileName=/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt, lineNumber=60, colNumber=13) +NodeJsStackTraceElement(className=DefaultJsAsserter, methodName=assertTrue, fileName=/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt, lineNumber=67, colNumber=9) +NodeJsStackTraceElement(className=null, methodName=assertTrue, fileName=/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/Assertions.kt, lineNumber=36, colNumber=21) +NodeJsStackTraceElement(className=DeepPackageTest.Inner, methodName=testHello, fileName=/Users/jetbrains/IdeaProjects/mpplib2/src/commonTest/kotlin/sample/a/DeepPackageTest.kt, lineNumber=16, colNumber=13) +NodeJsStackTraceElement(className=null, methodName=null, fileName=/Users/jetbrains/IdeaProjects/mpplib2/build/js_test_node_modules/mpplib2_test.js, lineNumber=93, colNumber=48) +NodeJsStackTraceElement(className=Object, methodName=test, fileName=/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/src/KotlinTestRunner.ts, lineNumber=12, colNumber=25) +NodeJsStackTraceElement(className=Object, methodName=test, fileName=/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/src/KotlinTestTeamCityReporter.ts, lineNumber=80, colNumber=28) +NodeJsStackTraceElement(className=null, methodName=test, fileName=/Users/jetbrains/IdeaProjects/mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/TestApi.kt, lineNumber=57, colNumber=15) +]) + """.trim(), parsed + ) + } +} \ No newline at end of file