Unexpected exception handling in unit tests with coroutines

I’m observing a strange result in my unit tests concerning the behavior of exception handling in coroutines.

When an exception E with a cause C is thrown from within a coroutine scope, and we catch E outside the scope, the cause of E is another exception E’ of the same type of E instead of its cause C. The cause C, however, can be found as the cause of E’.

This is better understandable through a concrete example. The following is a minimal test method that reproduces this behavior:

@Test
fun test() = runBlocking {
    try {
        coroutineScope {
            // Somewhere within a coroutineScope, an exception is thrown
            throw IllegalStateException("OOB", IndexOutOfBoundsException())
        }
    } catch (e: Exception) {
        // The caught exception is an IllegalStateException. So far, so good.
        println(e)               // java.lang.IllegalStateException: OOB
        
        // However, the cause of the caught is also an IllegalStateException.
        // Where does it come from?
        // We expected this to be an IndexOutOfBoundsException.
        println(e.cause)         // java.lang.IllegalStateException: OOB

        // The IndexOutOfBoundsExceptions is the cause of the nested IllegalStateException.
        println(e.cause?.cause)  // java.lang.IndexOutOfBoundsException
    }
}

For context, I’m using Kotlin 1.9.22 and the coroutines library 1.7.3.

This behavior is only present in unit tests. For instance, if the above code runs from a main function, the behavior is what we would expect:

fun main() = runBlocking {
    try {
        coroutineScope {
            throw IllegalStateException("OOB", IndexOutOfBoundsException())
        }
    } catch (e: Exception) {
        println(e)              // java.lang.IllegalStateException: OOB
        println(e.cause)        // java.lang.IndexOutOfBoundsException
        println(e.cause?.cause) // null
    }
}