aboutsummaryrefslogtreecommitdiffstats
path: root/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt
blob: 5073b7fdfad03f63e415aca4a6966abc51feed11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*
 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

@file:Suppress("DeferredResultUnused")

package kotlinx.coroutines.exceptions

import kotlinx.coroutines.*
import org.junit.Test
import kotlin.test.*

class StackTraceRecoveryNestedTest : TestBase() {

    @Test
    fun testNestedAsync() = runTest {
        val rootAsync = async(NonCancellable) {
            expect(1)

            // Just a noise for unwrapping
            async {
                expect(2)
                delay(Long.MAX_VALUE)
            }

            // Do not catch, fail on cancellation
            async {
                expect(3)
                async {
                    expect(4)
                    delay(Long.MAX_VALUE)
                }

                async {
                    expect(5)
                    // 1) await(), catch, verify and rethrow
                    try {
                        val nested = async {
                            expect(6)
                            throw RecoverableTestException()
                        }

                        nested.awaitNested()
                    } catch (e: RecoverableTestException) {
                        expect(7)
                        e.verifyException(
                            "await\$suspendImpl",
                            "awaitNested",
                            "\$testNestedAsync\$1\$rootAsync\$1\$2\$2.invokeSuspend"
                        )
                        // Just rethrow it
                        throw e
                    }
                }
            }
        }

        try {
            rootAsync.awaitRootLevel()
        } catch (e: RecoverableTestException) {
            e.verifyException("await\$suspendImpl", "awaitRootLevel")
            finish(8)
        }
    }

    private suspend fun Deferred<*>.awaitRootLevel() {
        await()
        assertTrue(true)
    }

    private suspend fun Deferred<*>.awaitNested() {
        await()
        assertTrue(true)
    }

    private fun RecoverableTestException.verifyException(vararg expectedTraceElements: String) {
        // It is "recovered" only once
        assertEquals(1, depth())
        val stacktrace = stackTrace.map { it.methodName }.toSet()
        assertTrue(expectedTraceElements.all { stacktrace.contains(it) })
    }

    private fun Throwable.depth(): Int {
        val cause = cause ?: return 0
        return 1 + cause.depth()
    }
}