All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmTest.exceptions.StackTraceRecoveryTest.kt Maven / Gradle / Ivy

There is a newer version: 1.9.0
Show newest version
/*
 * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.coroutines.exceptions

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.intrinsics.*
import org.junit.Test
import java.lang.RuntimeException
import java.util.concurrent.*
import kotlin.concurrent.*
import kotlin.coroutines.*
import kotlin.test.*

/*
 * All stacktrace validation skips line numbers
 */
class StackTraceRecoveryTest : TestBase() {

    @Test
    fun testAsync() = runTest {
        fun createDeferred(depth: Int): Deferred<*> {
            return if (depth == 0) {
                async(coroutineContext + NonCancellable) {
                    throw ExecutionException(null)
                }
            } else {
                createDeferred(depth - 1)
            }
        }

        val deferred = createDeferred(3)
        val traces = listOf(
            "java.util.concurrent.ExecutionException\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testAsync\$1\$createDeferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:99)\n" +
                    "\t(Coroutine boundary)\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.oneMoreNestedMethod(StackTraceRecoveryTest.kt:49)\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.nestedMethod(StackTraceRecoveryTest.kt:44)\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testAsync\$1.invokeSuspend(StackTraceRecoveryTest.kt:17)\n",
            "Caused by: java.util.concurrent.ExecutionException\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testAsync\$1\$createDeferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:21)\n" +
                    "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n"
        )
        nestedMethod(deferred, *traces.toTypedArray())
        deferred.join()
    }

    @Test
    fun testCompletedAsync() = runTest {
        val deferred = async(coroutineContext + NonCancellable) {
            throw ExecutionException(null)
        }

        deferred.join()
        val stacktrace = listOf(
            "java.util.concurrent.ExecutionException\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCompletedAsync\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:44)\n" +
                    "\t(Coroutine boundary)\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.oneMoreNestedMethod(StackTraceRecoveryTest.kt:81)\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.nestedMethod(StackTraceRecoveryTest.kt:75)\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCompletedAsync\$1.invokeSuspend(StackTraceRecoveryTest.kt:71)",
            "Caused by: java.util.concurrent.ExecutionException\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCompletedAsync\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:44)\n" +
                    "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)"
        )
        nestedMethod(deferred, *stacktrace.toTypedArray())
    }

    private suspend fun nestedMethod(deferred: Deferred<*>, vararg traces: String) {
        oneMoreNestedMethod(deferred, *traces)
        assertTrue(true) // Prevent tail-call optimization
    }

    private suspend fun oneMoreNestedMethod(deferred: Deferred<*>, vararg traces: String) {
        try {
            deferred.await()
            expectUnreached()
        } catch (e: ExecutionException) {
            verifyStackTrace(e, *traces)
        }
    }

    @Test
    fun testWithContext() = runTest {
        val deferred = async(NonCancellable, start = CoroutineStart.LAZY) {
            throw RecoverableTestException()
        }

        outerMethod(deferred,
            "kotlinx.coroutines.RecoverableTestException\n" +
                "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testWithContext\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" +
                "\t(Coroutine boundary)\n" +
                "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.innerMethod(StackTraceRecoveryTest.kt:158)\n" +
                "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerMethod\$2.invokeSuspend(StackTraceRecoveryTest.kt:151)\n" +
                "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.outerMethod(StackTraceRecoveryTest.kt:150)\n" +
                "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testWithContext\$1.invokeSuspend(StackTraceRecoveryTest.kt:141)\n",
            "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
                "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testWithContext\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" +
                "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n")
        deferred.join()
    }

    private suspend fun outerMethod(deferred: Deferred<*>, vararg traces: String) {
        withContext(Dispatchers.IO) {
            innerMethod(deferred, *traces)
        }

        assertTrue(true)
    }

    private suspend fun innerMethod(deferred: Deferred<*>, vararg traces: String) {
        try {
            deferred.await()
            expectUnreached()
        } catch (e: RecoverableTestException) {
            verifyStackTrace(e, *traces)
        }
    }

    @Test
    fun testCoroutineScope() = runTest {
        val deferred = async(NonCancellable, start = CoroutineStart.LAZY) {
            throw RecoverableTestException()
        }

        outerScopedMethod(deferred,
            "kotlinx.coroutines.RecoverableTestException\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCoroutineScope\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" +
                    "\t(Coroutine boundary)\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.innerMethod(StackTraceRecoveryTest.kt:158)\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerScopedMethod\$2\$1.invokeSuspend(StackTraceRecoveryTest.kt:193)\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerScopedMethod\$2.invokeSuspend(StackTraceRecoveryTest.kt:151)\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCoroutineScope\$1.invokeSuspend(StackTraceRecoveryTest.kt:141)\n",
            "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
                    "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCoroutineScope\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" +
                    "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n")
        deferred.join()
    }

    public class TrickyException() : Throwable() {
        // To be sure ctor is never invoked
        @Suppress("UNUSED", "UNUSED_PARAMETER")
        private constructor(message: String, cause: Throwable): this() {
            error("Should never be called")
        }

        override fun initCause(cause: Throwable?): Throwable {
            error("Can't call initCause")
        }
    }

    @Test
    fun testThrowingInitCause() = runTest {
        val deferred = async(NonCancellable) {
            expect(2)
            throw TrickyException()
        }

        try {
            expect(1)
            deferred.await()
        } catch (e: TrickyException) {
            assertNull(e.cause)
            finish(3)
        }
    }

    private suspend fun outerScopedMethod(deferred: Deferred<*>, vararg traces: String) = coroutineScope {
        supervisorScope {
            innerMethod(deferred, *traces)
            assertTrue(true)
        }
        assertTrue(true)
    }

    @Test
    fun testSelfSuppression() = runTest {
        try {
            runBlocking {
                val job = launch {
                    coroutineScope {
                        throw RecoverableTestException()
                    }
                }

                job.join()
                expectUnreached()
            }
            expectUnreached()
        } catch (e: RecoverableTestException) {
            checkCycles(e)
        }
    }


    private suspend fun throws() {
        yield() // TCE
        throw RecoverableTestException()
    }

    private suspend fun awaiter() {
        val task = GlobalScope.async(Dispatchers.Default, start = CoroutineStart.LAZY) { throws() }
        task.await()
        yield() // TCE
    }

    @Test
    fun testNonDispatchedRecovery() {
        val await = suspend { awaiter() }

        val barrier = CyclicBarrier(2)
        var exception: Throwable? = null

        thread {
            await.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) {
                exception = it.exceptionOrNull()
                barrier.await()
            })
        }

        barrier.await()
        val e = exception
        assertNotNull(e)
        verifyStackTrace(e, "kotlinx.coroutines.RecoverableTestException\n" +
            "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.throws(StackTraceRecoveryTest.kt:280)\n" +
            "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.access\$throws(StackTraceRecoveryTest.kt:20)\n" +
            "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$throws\$1.invokeSuspend(StackTraceRecoveryTest.kt)\n" +
            "\t(Coroutine boundary)\n" +
            "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.awaiter(StackTraceRecoveryTest.kt:285)\n" +
            "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testNonDispatchedRecovery\$await\$1.invokeSuspend(StackTraceRecoveryTest.kt:291)\n" +
            "Caused by: kotlinx.coroutines.RecoverableTestException")
    }

    private class Callback(val cont: CancellableContinuation<*>)

    @Test
    fun testCancellableContinuation() = runTest {
        val channel = Channel(1)
        launch {
            try {
                awaitCallback(channel)
            } catch (e: Throwable) {
                verifyStackTrace(e, "kotlinx.coroutines.RecoverableTestException\n" +
                        "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCancellableContinuation\$1.invokeSuspend(StackTraceRecoveryTest.kt:329)\n" +
                        "\t(Coroutine boundary)\n" +
                        "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.awaitCallback(StackTraceRecoveryTest.kt:348)\n" +
                        "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCancellableContinuation\$1\$1.invokeSuspend(StackTraceRecoveryTest.kt:322)\n" +
                        "Caused by: kotlinx.coroutines.RecoverableTestException\n" +
                        "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCancellableContinuation\$1.invokeSuspend(StackTraceRecoveryTest.kt:329)")
            }
        }
        val callback = channel.receive()
        callback.cont.resumeWithException(RecoverableTestException())
    }

    private suspend fun awaitCallback(channel: Channel) {
        suspendCancellableCoroutine { cont ->
            channel.trySend(Callback(cont))
        }
        yield() // nop to make sure it is not a tail call
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy