Use cooperative coroutine cancellation in test code

Our test code relies on `withTimeout*` and `while(true)`
in a few places to wait for things to happen. However,
having a timeout is not enough: if the coroutine is busy,
even if the timeout elapses, the work will not be interrupted.
That's because cancellation in Kotlin coroutines is cooperative,
see [the docs](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/cancellation-and-timeouts.md#cancellation-is-cooperative)
for more details.

This commit uses cooperative cancellation so that tests will
break, instead of hanging forever.
This commit is contained in:
Alessio Placitelli 2020-02-12 12:18:51 +01:00
Родитель 019c96cd0e
Коммит f9b20a7530
3 изменённых файлов: 7 добавлений и 4 удалений

Просмотреть файл

@ -8,6 +8,7 @@ import android.content.Context
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.testing.WorkManagerTestInitHelper import androidx.work.testing.WorkManagerTestInitHelper
import kotlinx.coroutines.isActive
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
@ -35,7 +36,7 @@ internal fun testFlushWorkManagerJob(context: Context, workTag: String, timeoutM
return@withTimeout return@withTimeout
} }
} }
} while (true) } while (isActive)
} }
} }
} }

Просмотреть файл

@ -9,6 +9,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Dispatchers as KotlinDispatchers import kotlinx.coroutines.Dispatchers as KotlinDispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.joinAll import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -107,7 +108,7 @@ class DispatchersTest {
// Wait for the flushed tasks to be executed. // Wait for the flushed tasks to be executed.
runBlocking { runBlocking {
withTimeoutOrNull(2000) { withTimeoutOrNull(2000) {
while (threadCanary.get() != 3 || Dispatchers.API.taskQueue.size > 0) { while (isActive && (threadCanary.get() != 3 || Dispatchers.API.taskQueue.size > 0)) {
delay(1) delay(1)
} }
} ?: assertTrue("Timed out waiting for tasks to execute", false) } ?: assertTrue("Timed out waiting for tasks to execute", false)
@ -157,7 +158,7 @@ class DispatchersTest {
runBlocking { runBlocking {
// Ensure that all the required jobs have been added to the list. // Ensure that all the required jobs have been added to the list.
withTimeoutOrNull(2000) { withTimeoutOrNull(2000) {
while (counter.get() < 100) { while (isActive && counter.get() < 100) {
delay(1) delay(1)
} }
} ?: assertEquals("Timed out waiting for tasks to execute", 100, counter.get()) } ?: assertEquals("Timed out waiting for tasks to execute", 100, counter.get())

Просмотреть файл

@ -13,6 +13,7 @@ import androidx.test.core.app.ApplicationProvider
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.testing.WorkManagerTestInitHelper import androidx.work.testing.WorkManagerTestInitHelper
import kotlinx.coroutines.isActive
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import org.json.JSONObject import org.json.JSONObject
@ -197,7 +198,7 @@ internal fun waitForEnqueuedWorker(
if (getWorkerStatus(context, workTag).isEnqueued) { if (getWorkerStatus(context, workTag).isEnqueued) {
return@withTimeout return@withTimeout
} }
} while (true) } while (isActive)
} }
} }