Skip to content

Commit

Permalink
test: delaySafe as a safe replacement for delay when used inside …
Browse files Browse the repository at this point in the history
  • Loading branch information
amal committed Jan 20, 2023
1 parent d286499 commit 0fd87fd
Show file tree
Hide file tree
Showing 10 changed files with 53 additions and 33 deletions.
26 changes: 26 additions & 0 deletions fluxo-core/src/commonTest/kotlin/kt/fluxo/test/DelaySafe.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package kt.fluxo.test

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext

/**
* Use as a safe replacement for [delay] when used inside a [runTest]/[TestScope].
*
* Delays coroutine for a given time without blocking a thread and resumes it after a specified time.
*
* [Documentation on delay-skipping in tests](https://github.com/Kotlin/kotlinx.coroutines/blob/81e17dd/kotlinx-coroutines-test/README.md#delay-skipping)
*
* @param timeMillis timeout time in milliseconds.
*
* @see delay
* @see withTimeoutSafe
*/
@Suppress("MaxLineLength")
suspend fun delaySafe(timeMillis: Long) {
withContext(Dispatchers.Default) {
delay(timeMillis)
}
}
20 changes: 7 additions & 13 deletions fluxo-core/src/commonTest/kotlin/kt/fluxo/test/TestFlowObserver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout


/**
Expand Down Expand Up @@ -45,18 +42,15 @@ class TestFlowObserver<T>(flow: Flow<T>) {
* @param throwTimeout Throw if timed out (by default)
*/
suspend fun awaitFor(timeout: Long = 5000L, throwTimeout: Boolean = true, condition: TestFlowObserver<T>.() -> Boolean) {
// https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/README.md#using-withtimeout-inside-runtest
withContext(Dispatchers.Default) {
try {
withTimeout(timeout) {
@Suppress("UNUSED_EXPRESSION")
while (!condition()) {
delay(AWAIT_TIMEOUT_MS)
}
try {
withTimeoutSafe(timeout) {
@Suppress("UNUSED_EXPRESSION")
while (!condition()) {
delaySafe(AWAIT_TIMEOUT_MS)
}
} catch (e: TimeoutCancellationException) {
if (throwTimeout) throw e
}
} catch (e: TimeoutCancellationException) {
if (throwTimeout) throw e
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import kotlin.contracts.contract
* @param timeMillis timeout time in milliseconds.
*
* @see withTimeout
* @see delaySafe
*/
@Suppress("MaxLineLength")
suspend inline fun <T> withTimeoutSafe(timeMillis: Long, crossinline block: suspend CoroutineScope.() -> T): T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@ package kt.fluxo.tests

import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import kt.fluxo.core.FluxoClosedException
import kt.fluxo.core.closeAndWait
import kt.fluxo.core.container
import kt.fluxo.core.updateState
import kt.fluxo.test.delaySafe
import kt.fluxo.test.getValue
import kt.fluxo.test.runUnitTest
import kt.fluxo.test.setValue
Expand Down Expand Up @@ -116,9 +114,7 @@ class ContainerExceptionHandlerTest {
flow {
while (true) {
emit(Unit)
withContext(Dispatchers.Default) {
delay(1000)
}
delaySafe(1000)
}
}.collect()
} catch (ce: CancellationException) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package kt.fluxo.tests

import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kt.fluxo.core.Container
import kt.fluxo.core.container
import kt.fluxo.core.updateState
import kt.fluxo.test.CoroutineScopeAwareTest
import kt.fluxo.test.delaySafe
import kt.fluxo.test.test
import kotlin.random.Random
import kotlin.test.Test
Expand All @@ -24,7 +24,7 @@ internal class ContainerThreadingTest : CoroutineScopeAwareTest() {
val newState = Random.nextInt()

container.send {
delay(Long.MAX_VALUE)
delaySafe(Long.MAX_VALUE)
}
container.send {
value = newState
Expand Down Expand Up @@ -104,7 +104,7 @@ internal class ContainerThreadingTest : CoroutineScopeAwareTest() {

private fun Container<TestState, Nothing>.one(delay: Boolean = false) = send {
if (delay) {
delay(Random.nextLong(20))
delaySafe(Random.nextLong(20))
}
updateState {
it.copy(ids = value.ids + 1)
Expand All @@ -113,7 +113,7 @@ internal class ContainerThreadingTest : CoroutineScopeAwareTest() {

private fun Container<TestState, Nothing>.two(delay: Boolean = false) = send {
if (delay) {
delay(Random.nextLong(20))
delaySafe(Random.nextLong(20))
}
updateState {
it.copy(ids = value.ids + 2)
Expand All @@ -122,7 +122,7 @@ internal class ContainerThreadingTest : CoroutineScopeAwareTest() {

private fun Container<TestState, Nothing>.three(delay: Boolean = false) = send {
if (delay) {
delay(Random.nextLong(20))
delaySafe(Random.nextLong(20))
}
updateState {
it.copy(ids = value.ids + 3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package kt.fluxo.tests
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.first
Expand All @@ -24,6 +23,7 @@ import kt.fluxo.core.store
import kt.fluxo.core.updateState
import kt.fluxo.test.CoroutineScopeAwareTest
import kt.fluxo.test.IgnoreNativeAndJs
import kt.fluxo.test.delaySafe
import kt.fluxo.test.runUnitTest
import kotlin.random.Random
import kotlin.test.Test
Expand All @@ -50,11 +50,11 @@ internal class InputStrategyTest : CoroutineScopeAwareTest() {

val results = ArrayList<Int>()
val store = this.store(ints.first(), handler = { intent: Int ->
if (!generic) delay(timeMillis = 1)
if (!generic) delaySafe(timeMillis = 1)
resLock.withLock { results.add(intent) }
value = intent
if (intent == ints.last()) {
if (generic) delay(timeMillis = 10)
if (generic) delaySafe(timeMillis = 10)
finishLock.unlock()
}
}) { inputStrategy = strategy }
Expand All @@ -65,7 +65,7 @@ internal class InputStrategyTest : CoroutineScopeAwareTest() {
if (generic) {
// TODO: Store.awaitIdle()
while (results.size < NUMBER_OF_ITEMS) {
delay(timeMillis = 10)
delaySafe(timeMillis = 10)
}
}
store.closeAndWait()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package kt.fluxo.tests

import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kt.fluxo.core.Bootstrapper
Expand All @@ -14,6 +13,7 @@ import kt.fluxo.core.repeatOnSubscription
import kt.fluxo.core.updateState
import kt.fluxo.test.CoroutineScopeAwareTest
import kt.fluxo.test.IgnoreNative
import kt.fluxo.test.delaySafe
import kt.fluxo.test.runUnitTest
import kotlin.test.Test
import kotlin.test.assertContentEquals
Expand Down Expand Up @@ -61,7 +61,7 @@ internal class RepeatOnSubscriptionTest : CoroutineScopeAwareTest() {
val states = container.take(2).toList()
assertContentEquals(listOf(initialState, State(1)), states)

delay(stopTimeout)
delaySafe(stopTimeout)
val states2 = container.take(2).toList()
assertContentEquals(listOf(State(1), State(2)), states2)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import kt.fluxo.core.dsl.accept
import kt.fluxo.core.intent
import kt.fluxo.core.store
import kt.fluxo.core.updateState
import kt.fluxo.test.delaySafe
import kt.fluxo.test.runUnitTest
import kotlin.test.Test
import kotlin.test.assertEquals
Expand Down Expand Up @@ -131,7 +132,7 @@ internal class SideJobTest {
store.intent {
sideJob { wasRestarted ->
assertFalse(wasRestarted)
delay(timeMillis = 1_000)
delaySafe(timeMillis = 1_000)
updateState { "a" }
}
sideJob { wasRestarted ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ class CoroutinesTest {
repeat(times = 10_000) {
val mutex = Mutex(locked = true)
val job = backgroundScope.launch(Dispatchers.Default) {
// Deliberately time-skipping delay here! (Dispatchers.Default used)
delay(10)
mutex.unlock()
// busy wait
@Suppress("ControlFlowWithEmptyBody", "EmptyWhileBlock")
while (currentCoroutineContext().isActive) {
}
}
// Deliberately unsafe withTimeout here!
withTimeout(TIMEOUT_MS) {
mutex.withLock {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
Expand All @@ -29,6 +28,7 @@ import kt.fluxo.test.IgnoreJs
import kt.fluxo.test.KMM_PLATFORM
import kt.fluxo.test.Platform
import kt.fluxo.test.TestLoggingStoreFactory
import kt.fluxo.test.delaySafe
import kt.fluxo.test.runUnitTest
import kt.fluxo.test.testLog
import kt.fluxo.test.withTimeoutSafe
Expand Down Expand Up @@ -307,7 +307,7 @@ internal class DslThreadingTest {

fun suspendingIntent() = intent {
intentMutex.doUnlock()
delay(Int.MAX_VALUE.toLong())
delaySafe(Int.MAX_VALUE.toLong())
}

fun simpleIntent() = intent {
Expand Down

0 comments on commit 0fd87fd

Please sign in to comment.