Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated eventually config #2027

Merged
merged 2 commits into from Jan 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,3 +1,5 @@
@file:Suppress("BlockingMethodInNonBlockingContext")

package com.sksamuel.kotest.assertions.timing

import io.kotest.assertions.assertSoftly
Expand Down Expand Up @@ -170,7 +172,7 @@ class EventuallyTest : WordSpec() {
5.seconds,
250.milliseconds.fixed(),
predicate = { t == "xxxxxxxxxxx" },
listener = { _ -> latch.countDown() },
listener = { latch.countDown() },
) {
t += "x"
t
Expand Down Expand Up @@ -204,10 +206,10 @@ class EventuallyTest : WordSpec() {
}

"eventually has a shareable configuration" {
val slow = EventuallyConfig<Int>(duration = 5.seconds)
val slow = EventuallyConfig(duration = 5.seconds)

var i = 0
val fast = slow.copy(retries = 1, predicate = { i == 1 })
val fast = slow.copy(retries = 1)

assertSoftly {
slow.retries shouldBe Int.MAX_VALUE
Expand All @@ -220,7 +222,7 @@ class EventuallyTest : WordSpec() {
5
}

eventually(fast) {
eventually(fast, predicate = { i == 1 }) {
i++
}

Expand Down
@@ -1,7 +1,5 @@
package io.kotest.assertions

typealias SuspendingPredicate<T> = suspend (T) -> Boolean

typealias SuspendingProducer<T> = suspend () -> T


Expand Down
@@ -1,13 +1,15 @@
package io.kotest.assertions.timing

import io.kotest.assertions.SuspendingPredicate
import io.kotest.assertions.SuspendingProducer
import io.kotest.assertions.failure
import io.kotest.assertions.until.Interval
import io.kotest.assertions.until.fixed
import kotlinx.coroutines.delay
import kotlin.reflect.KClass
import kotlin.time.*
import kotlin.time.Duration
import kotlin.time.TimeMark
import kotlin.time.TimeSource
import kotlin.time.milliseconds

/**
* Runs a function until it doesn't throw as long as the specified duration hasn't passed
Expand All @@ -19,14 +21,21 @@ suspend fun <T : Any> eventually(
duration: Duration,
interval: Interval,
f: SuspendingProducer<T>
): T = eventually(EventuallyConfig(duration, interval), f)
): T = eventually(EventuallyConfig(duration, interval), f = f)

suspend fun <T> eventually(
duration: Duration,
interval: Interval,
predicate: EventuallyPredicate<T>,
f: SuspendingProducer<T>
): T = eventually(EventuallyConfig(duration = duration, interval), predicate = predicate, f = f)

suspend fun <T> eventually(
duration: Duration,
interval: Interval,
listener: EventuallyListener<T>,
f: SuspendingProducer<T>
): T = eventually(EventuallyConfig(duration = duration, interval, listener = listener), f)
): T = eventually(EventuallyConfig(duration = duration, interval), listener = listener, f = f)


@Deprecated(
Expand Down Expand Up @@ -61,19 +70,21 @@ suspend fun <T> eventually(duration: Duration, exceptionClass: KClass<out Throwa
suspend fun <T> eventually(
duration: Duration = Duration.INFINITE,
interval: Interval = 25.milliseconds.fixed(),
predicate: SuspendingPredicate<T> = { true },
predicate: EventuallyPredicate<T> = EventuallyPredicate { true },
listener: EventuallyListener<T> = EventuallyListener { },
retries: Int = Int.MAX_VALUE,
exceptionClass: KClass<out Throwable>? = null,
f: SuspendingProducer<T>
): T = eventually(EventuallyConfig(duration, interval, predicate, listener, retries, exceptionClass), f)
): T = eventually(EventuallyConfig(duration, interval, retries, exceptionClass), predicate, listener, f)

/**
* Runs a function until it doesn't throw and the result satisfies the predicate as long as the specified duration hasn't passed
* and uses [EventuallyConfig] to control the duration, interval, listener, retries, and exceptionClass.
*/
suspend fun <T> eventually(
config: EventuallyConfig<T>,
config: EventuallyConfig,
predicate: EventuallyPredicate<T> = EventuallyPredicate { true },
listener: EventuallyListener<T> = EventuallyListener { },
f: SuspendingProducer<T>,
): T {
val start = TimeSource.Monotonic.markNow()
Expand All @@ -85,8 +96,8 @@ suspend fun <T> eventually(
while (end.hasNotPassedNow() && times < config.retries) {
try {
val result = f()
config.listener.onEval(EventuallyState(result, start, end, times, firstError, lastError))
if (config.predicate(result)) {
listener.onEval(EventuallyState(result, start, end, times, firstError, lastError))
if (predicate.test(result)) {
return result
}
} catch (e: Throwable) {
Expand Down Expand Up @@ -121,28 +132,31 @@ suspend fun <T> eventually(
throw failure(message.toString())
}

data class EventuallyConfig<T>(
data class EventuallyConfig(
val duration: Duration = Duration.INFINITE,
val interval: Interval = 25.milliseconds.fixed(),
val predicate: SuspendingPredicate<T> = { true },
val listener: EventuallyListener<T> = EventuallyListener {},
val retries: Int = Int.MAX_VALUE,
val exceptionClass: KClass<out Throwable>? = null,
) {
init {
require(retries > 0) { "Retries should not be less than one" }
require(!duration.isNegative()) { "Duration cannot be negative" }
}
}

data class EventuallyState<T>(
val result: T,
val start: TimeMark,
val end: TimeMark,
val times: Int,
val iteration: Int,
val firstError: Throwable?,
val lastError: Throwable?,
val thisError: Throwable?,
)

fun interface EventuallyPredicate<T> {
fun test(result: T): Boolean
}

fun interface EventuallyListener<T> {
fun onEval(state: EventuallyState<T>)
}
@@ -1,7 +1,7 @@
package io.kotest.assertions.until

import io.kotest.assertions.SuspendingPredicate
import io.kotest.assertions.SuspendingProducer
import io.kotest.assertions.timing.EventuallyPredicate
import io.kotest.assertions.timing.eventually
import kotlin.time.Duration
import kotlin.time.seconds
Expand Down Expand Up @@ -42,7 +42,7 @@ suspend fun until(duration: Duration, interval: Interval = 1.seconds.fixed(), f:
"kotlin.time.seconds"
)
)
suspend fun <T> until(duration: Duration, predicate: SuspendingPredicate<T>, f: SuspendingProducer<T>): T =
suspend fun <T> until(duration: Duration, predicate: EventuallyPredicate<T>, f: SuspendingProducer<T>): T =
eventually(duration, 1.seconds.fixed(), predicate = predicate, f = f)

@Deprecated(
Expand All @@ -55,10 +55,9 @@ suspend fun <T> until(duration: Duration, predicate: SuspendingPredicate<T>, f:
suspend fun <T> until(
duration: Duration,
interval: Interval,
predicate: SuspendingPredicate<T>,
predicate: EventuallyPredicate<T>,
f: SuspendingProducer<T>
): T =
eventually(duration, interval, predicate = predicate, f = f)
): T = eventually(duration, interval, predicate = predicate, f = f)

@Deprecated(
"Use eventually", ReplaceWith(
Expand All @@ -69,7 +68,7 @@ suspend fun <T> until(
suspend fun <T> until(
duration: Duration,
interval: Interval,
predicate: SuspendingPredicate<T>,
predicate: EventuallyPredicate<T>,
listener: UntilListener<T>,
f: SuspendingProducer<T>
): T =
Expand Down