Repository: Kotlin/kotlinx.coroutines
File: kotlinx-coroutines-core/jvm/test/exceptions/CoroutineExceptionHandlerJvmTest.kt (Kotlin)

Code before:
import org.junit.Test
import kotlin.test.*

class CoroutineExceptionHandlerJvmTest : TestBase() {

    private val exceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
    private lateinit var caughtException: Throwable

    @Before
    fun setUp() {
        Thread.setDefaultUncaughtExceptionHandler({ _, e -> caughtException = e })
    }

    @After
    fun tearDown() {
        Thread.setDefaultUncaughtExceptionHandler(exceptionHandler)
    }

    @Test
    fun testFailingHandler() = runBlocking {
        expect(1)
        val job = GlobalScope.launch(CoroutineExceptionHandler { _, _ -> throw AssertionError() }) {
            expect(2)
            throw TestException()
        }

        job.join()
        assertIs<RuntimeException>(caughtException)
        assertIs<AssertionError>(caughtException.cause)
        assertIs<TestException>(caughtException.suppressed[0])

        finish(3)
    }

    @Test
    fun testLastDitchHandlerContainsContextualInformation() = runBlocking {
        expect(1)
        GlobalScope.launch(CoroutineName("last-ditch")) {
            expect(2)
            throw TestException()
        }.join()
        assertIs<TestException>(caughtException)
        assertContains(caughtException.suppressed[0].toString(), "last-ditch")
        finish(3)
    }
}


Review comment (suggestion):
Now I realised that `CoroutineName("last-resort")` should be just `CoroutineName("some contextual information")`

Code after:
import org.junit.Test
import kotlin.test.*

class CoroutineExceptionHandlerJvmTest : TestBase() {

    @Test
    fun testThrowingHandler() = runBlocking {
        expect(1)
        val caughtException = catchingUncaughtException {
            GlobalScope.launch(CoroutineExceptionHandler { _, _ -> throw TestException2() }) {
                expect(2)
                throw TestException()
            }.join()
        }
        assertIs<RuntimeException>(caughtException)
        assertIs<TestException2>(caughtException.cause)
        assertIs<TestException>(caughtException.suppressed[0])

        finish(3)
    }

    @Test
    fun testLastResortHandlerContainsContextualInformation() = runBlocking {
        expect(1)
        val caughtException = catchingUncaughtException {
            GlobalScope.launch(CoroutineName("last-resort")) {
                expect(2)
                throw TestException()
            }.join()
        }
        assertIs<TestException>(caughtException)
        assertContains(caughtException.suppressed[0].toString(), "last-resort")
        finish(3)
    }

    @Test
    fun testThrowingUncaughtExceptionHandler() = runBlocking {
        expect(1)
        withUncaughtExceptionHandler({ _, e ->
            // should be above the `expect` so that we can observe the failure
            assertIs<TestException>(e)
            expect(3)
            throw TestException("will not be reported")
        }) {
            launch(Job()) {
                expect(2)
                throw TestException("will be passed to the uncaught exception handler")
            }.join()
        }
        finish(4)
    }