Has anyone experienced Android instrumented test m...
# coroutines
l
Has anyone experienced Android instrumented test methods that have
runBlocking
as their body, never returning while the inner coroutine is run successfully?
w
I haven’t done any instrumented tests yet using coroutines 😞
Regular unit tests work just fine though
I’d be pretty surprised if they don’t complete.
l
So I am. @elizarov did you ever observed this? I just checked by adding a log on the last line of the
runBlocking
block, and after it, and the one after doesn't show up while the one before the closing brace does. In some other tests, the issue doesn't appear though. What I added is a
suspend fun
using
suspendCancellableCoroutine
, but it resumes fine...
d
Where are you running this
runblocking
call? Your service/Activity might get killed by Android because you blocked it.
w
Be curious to see an example
l
I'm running it in a
@Test
annotated method in
androidTest
source set of an Android library module.
I'll try to find some time to make a reproducer tomorrow at office
d
I had the issue when I had
runBlocking
body with
@UiThreadTest
annotated functions.
l
Here's a reproducer (put it in a class):
Copy code
@Test
fun reproduceRunBlockingBug() {
    runBlocking {
        runBlockingBug()
        println("I will be printed!")
    }
    println("I should be printed, but I won't")
}

private suspend fun runBlockingBug() = suspendCancellableCoroutine<Unit> { cont ->
    val job = Job(parent = cont.context[Job]!!)
    launch(cont.context, parent = job) {
        cont.resume(Unit)
    }
}
@elizarov the same code works on try.kotl.in, but hangs on
runBlocking
closing brace in android Junit tests
e
Hm… here the
job
is a child of
runBlocking
, so
runBlocking
waits for it to complete but it will not. It works as expected if you just
launch(cont.cotext)
without creating an interim
job
object.
l
How can I make the created job complete?
d
join()
e
job.cancel()
But do you really need yet another job object? You can do
val job = launch(cont.context) { ... }
.
That this job will complete itself after it does
cont.resume
l
Yes I do need another job because I need to access it before I launch the coroutine in my use case. Here's my code:
Copy code
suspend fun <T> withSkyFacingCheck(
        zRef: Float,
        aRef: Float,
        useAvg: Boolean = true,
        avgSamplingDurationMillis: Long = maxSamplingWindowDurationMillis,
        block: suspend (SensorEventListener) -> T
) = suspendCancellableCoroutine<T> { cont ->
    val job = Job(parent = cont.context[Job]!!)
    val listener = SkyFacingCheckingSensorEventListener(zRef, aRef, useAvg, avgSamplingDurationMillis) {
        cont.cancel(IllegalStateException("Device moved!"))
        job.cancel()
    }
    launch(cont.context, parent = job) {
        val result = block(listener)
        cont.resume(result)
    }
}
The workaround would be to make the
job
property a
lateinit var
e
Mind the concurrency, though, in this case. It is tricky
l
In this case, the lambda of the listener is never run before the block, so it's safe enough to run, but I think
job.cancel()
with my previous approach is the safest option in regards to by the codebase example
TIL (and yesterday too 😉) Thanks!