Skip to content

Commit c7cfc39

Browse files
committed
Added test for stack-trace recovery in JobCancellationException
Fixes #950
1 parent 73b456b commit c7cfc39

File tree

2 files changed

+45
-13
lines changed

2 files changed

+45
-13
lines changed

kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedChannelsTest.kt

+29
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,33 @@ class StackTraceRecoveryNestedChannelsTest : TestBase() {
133133
finish(3)
134134
deferred.await()
135135
}
136+
137+
// See https://github.com/Kotlin/kotlinx.coroutines/issues/950
138+
@Test
139+
fun testCancelledOffer() = runTest {
140+
expect(1)
141+
val job = Job()
142+
val actor = actor<Int>(job, Channel.UNLIMITED) {
143+
consumeEach {
144+
expectUnreached() // is cancelled before offer
145+
}
146+
}
147+
job.cancel()
148+
try {
149+
actor.offer(1)
150+
} catch (e: Exception) {
151+
verifyStackTrace(e,
152+
"kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@3af42ad0\n" +
153+
"\t(Coroutine boundary)\n" +
154+
"\tat kotlinx.coroutines.channels.AbstractSendChannel.offer(AbstractChannel.kt:186)\n" +
155+
"\tat kotlinx.coroutines.channels.ChannelCoroutine.offer(ChannelCoroutine.kt)\n" +
156+
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testCancelledOffer\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:150)\n" +
157+
"Caused by: kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@3af42ad0\n",
158+
// ... java.lang.* stuff and JobSupport.* snipped here ...
159+
"\tat kotlinx.coroutines.Job\$DefaultImpls.cancel\$default(Job.kt:164)\n" +
160+
"\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testCancelledOffer\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:148)"
161+
)
162+
finish(2)
163+
}
164+
}
136165
}

kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt

+16-13
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import kotlin.test.*
55

66
public fun verifyStackTrace(e: Throwable, vararg traces: String) {
77
val stacktrace = toStackTrace(e)
8+
val normalizedActual = stacktrace.normalizeStackTrace()
89
traces.forEach {
9-
assertTrue(
10-
stacktrace.trimStackTrace().contains(it.trimStackTrace()),
11-
"\nExpected trace element:\n$it\n\nActual stacktrace:\n$stacktrace"
12-
)
10+
val normalizedExpected = it.normalizeStackTrace()
11+
if (!normalizedActual.contains(normalizedExpected)) {
12+
// A more readable error message would be produced by assertEquals
13+
assertEquals(normalizedExpected, normalizedActual, "Actual trace does not contain expected one")
14+
}
1315
}
14-
16+
// Check "Caused by" counts
1517
val causes = stacktrace.count("Caused by")
1618
assertNotEquals(0, causes)
1719
assertEquals(traces.map { it.count("Caused by") }.sum(), causes)
@@ -23,14 +25,16 @@ public fun toStackTrace(t: Throwable): String {
2325
return sw.toString()
2426
}
2527

26-
public fun String.trimStackTrace(): String {
27-
return applyBackspace(trimIndent().replace(Regex(":[0-9]+"), "")
28-
.replace("kotlinx_coroutines_core_main", "") // yay source sets
29-
.replace("kotlinx_coroutines_core", ""))
30-
}
28+
public fun String.normalizeStackTrace(): String =
29+
applyBackspace()
30+
.replace(Regex(":[0-9]+"), "") // remove line numbers
31+
.replace("kotlinx_coroutines_core_main", "") // yay source sets
32+
.replace("kotlinx_coroutines_core", "")
33+
.replace(Regex("@[0-9a-f]+"), "") // remove hex addresses in debug toStrings
34+
.lines().joinToString("\n") // normalize line separators
3135

32-
public fun applyBackspace(line: String): String {
33-
val array = line.toCharArray()
36+
public fun String.applyBackspace(): String {
37+
val array = toCharArray()
3438
val stack = CharArray(array.size)
3539
var stackSize = -1
3640
for (c in array) {
@@ -40,7 +44,6 @@ public fun applyBackspace(line: String): String {
4044
--stackSize
4145
}
4246
}
43-
4447
return String(stack, 0, stackSize)
4548
}
4649

0 commit comments

Comments
 (0)