kotlinx.coroutines icon indicating copy to clipboard operation
kotlinx.coroutines copied to clipboard

[Test - coroutine 1.6.0] Unexpectedly propagate the exception thrown in an async {} block out to runTest return

Open mgtsai opened this issue 3 years ago • 5 comments
trafficstars

In normal coroutine cases, all things occured in async{} block should be confined in the returned Deferred object, and let the caller to determine the futher usage, such as calling await(), or getCompletionExceptionOrNull(), ... etc.

When using runTest{}, if we throw an exception in an async{} block, this exception is catched in the returned Deferred object, and this program continues running. But after runTest returns, this exception is unexpectedly thrown outside runTest{} block, and will cause testing failure

@Test
fun test() = runTest {
    val deferred = async {
        throw Exception()
    }

    println("This line is still running")

    // Maybe doing something for deferred
}

As the following output:

This line is still running

java.lang.Exception
    at xxx.xxx.xxx.xxx.invokeSuspend(xxxxx:xx)
    ... 

Library versions:

kotlin-stdlib:1.6.10 kotlinx-coroutines-jdk8:1.6.0 kotlinx-coroutines-test:1.6.0

mgtsai avatar Mar 04 '22 00:03 mgtsai

Failures in child coroutines are typically propagated to parents. In this case, async creates a coroutine that is a child to main one created for the test. Working as intended. Do you have examples of this working differently?

dkhalanskyjb avatar Mar 09 '22 09:03 dkhalanskyjb

I would like to add my +1 here

I think that as long as the behavior is consistent, then that's a design choice of the language.

But the fact that CustomException behaves differently from IllegalStateException I think is a bug - I would except these 2 programs to behave in the exact same way.

(These are not my examples, the author is welcome to chime in & take credit)

gmarcosb avatar Apr 25 '22 16:04 gmarcosb

The examples you provided differ in ways other than the exceptions used. If you take one example and just replace throw CustomException() with check(1 == 2) or vice versa, the behavior stays the same. So, the exceptions don't behave differently, and there's no reason why they should.

dkhalanskyjb avatar Apr 25 '22 17:04 dkhalanskyjb

Apologies, I did not include the right comparative examples; I've updated my original post but am also reposting the correction here.

I expect that these 2 programs, one throwing CustomException & the other IllegalStateException, would behave the same & consistently

gmarcosb avatar Apr 25 '22 17:04 gmarcosb

Ah nevermind it's because JobCancellationException is a IllegalStateException 🤦‍♂️

Alright I retract my +1, although maybe it makes sense to have CancellationException not be a IllegalStateException? 🤷‍♂️ But I guess this matches java.util.concurrent, so... c'est la vie.

One just has to be careful with catching IllegalStateException with coroutines I suppose

gmarcosb avatar Apr 25 '22 17:04 gmarcosb