Gradle: JS stack trace parser

#KT-30526
This commit is contained in:
Sergey Rostov
2019-04-04 14:16:21 +03:00
parent e013645d87
commit 0304f090e8
7 changed files with 101 additions and 40 deletions
@@ -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<StackTraceElement>?
)
@@ -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 : Node> 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) {
@@ -46,7 +46,6 @@ fun parseNodeJsStackTrace(stackTrace: String): NodeJsStackTrace {
var firstLines = true
val stack = mutableListOf<NodeJsStackTraceElement>()
// see examples at NodeJsStackTraceParserKtTest
stackTrace.lines().forEach {
val srcLine = it.trim()
@@ -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(
@@ -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<StackTraceElement>? = null
) : PlaceholderException(
className,
message,
null,
null,
null,
null
) {
override fun getStackTrace(): Array<StackTraceElement> =
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"
}
@@ -36,7 +36,7 @@ open class TCServiceMessagesClientTest {
results,
TCServiceMessagesClientSettings(
rootNodeName,
treatFailedTestOutputAsStacktrace
treatFailedTestOutputAsStacktrace = treatFailedTestOutputAsStacktrace
),
LoggerFactory.getLogger("test")
)
@@ -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
)
}
}