So, I’m testing a method that uses both `suspendCa...
# coroutines
p
So, I’m testing a method that uses both
suspendCancellableCoroutine {}
and
withTimeout {}
, and I’m running that test using a TestCoroutineDispatcher. In particular, I’m testing that the timeout fires if
cont.resume()
is never called within
suspendCancellableCoroutine {}
. My test runs the code in question inside a <test coroutine dispatcher>.runBlockingTest {} block. What I’m finding is that sometimes, the test passes as expected. But other times (it really feels nondeterministic and timing-related --- it changes depending on which tests are called before it, and whether or not it’s called in a debugger), instead of hitting the timeout, the internal call to
dispatcher.advanceUntilIdle()
leaves the test dispatcher idle but the coroutine still running, resulting in an IllegalStateException.
Is this known behavior and/or indicative of a likely oversight on my part?
afaict no part of the code under test changes the coroutine context/switches to a different dispatcher. I’m not sure what there is here to be nondeterministic.
Confirmed by stepping into TestCoroutineDispatcher.advanceUntilIdle().
Sometimes, after my method returns (without resuming the continuation), there’s an additional event on the dispatcher’s queue: a TimeoutCoroutine, with a time of 30000 (my timeout)
sometimes, there isn’t, and the test fails.
and whether or not it’s there seems to be entirely dependent on timing --- if I jump from the start of the test to a breakpoint at
while(!queue.isEmpty) {
in advanceUntilIdle(), queue.isEmpty tends to return true. If instead I set the same breakpoint, then step forward a little bit into my method before continuing to the breakpoint in TestCoroutineDispatcher, queue.isEmpty then returns false, and the remaining event in the queue is the timeout.
using kotlin 1.4.30 and kotlinx-coroutines 1.4.0.
But also confirmed on kotlinx-coroutines 1.4.3.
are there any known weird interactions between withTimeout and TestCoroutineDispatcher?
as it is, I’m tempted to switch to runBlocking {} for these tests.
Update: I was using reflection to change the visibility of Dispatchers.Default and replace it with my TestCoroutineDispatcher. Seems like that worked most of the time, but every once in a while (for reasons I may never fully grok) accessing Dispatchers.Default would give you a previous test’s TestCoroutineDispatcher, which resulted in work getting scheduled there. TL;DR: Play stupid games, win stupid prizes. I’ve given up on trying to mock the global Dispatchers.*, and am rephrasing things to allow the tests to directly inject their dispatchers.
I do wonder: is there any possibility that it might be possible to set the other global dispatchers (other than Main, for which there’s setMain()) in the future?