Gradle: JS stack trace parser
#KT-30526
This commit is contained in:
+11
@@ -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>?
|
||||
)
|
||||
+10
-29
@@ -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) {
|
||||
|
||||
-1
@@ -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()
|
||||
|
||||
|
||||
+4
-2
@@ -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(
|
||||
|
||||
+27
-7
@@ -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"
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ open class TCServiceMessagesClientTest {
|
||||
results,
|
||||
TCServiceMessagesClientSettings(
|
||||
rootNodeName,
|
||||
treatFailedTestOutputAsStacktrace
|
||||
treatFailedTestOutputAsStacktrace = treatFailedTestOutputAsStacktrace
|
||||
),
|
||||
LoggerFactory.getLogger("test")
|
||||
)
|
||||
|
||||
+48
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user