diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/assertions/timing/EventuallyTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/assertions/timing/EventuallyTest.kt index d39628a791f..c75365ef3f6 100644 --- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/assertions/timing/EventuallyTest.kt +++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/assertions/timing/EventuallyTest.kt @@ -20,7 +20,10 @@ import java.io.FileNotFoundException import java.io.IOException import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -import kotlin.time.* +import kotlin.time.TimeSource +import kotlin.time.days +import kotlin.time.milliseconds +import kotlin.time.seconds class EventuallyTest : WordSpec() { diff --git a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/timing/eventually.kt b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/timing/eventually.kt index 14a52290964..ed7243e04e0 100644 --- a/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/timing/eventually.kt +++ b/kotest-assertions/kotest-assertions-shared/src/commonMain/kotlin/io/kotest/assertions/timing/eventually.kt @@ -70,7 +70,7 @@ suspend fun eventually(duration: Duration, exceptionClass: KClass eventually( duration: Duration = Duration.INFINITE, interval: Interval = 25.milliseconds.fixed(), - predicate: EventuallyPredicate = EventuallyPredicate { true }, + predicate: EventuallyPredicate = { true }, listener: EventuallyListener = EventuallyListener { }, retries: Int = Int.MAX_VALUE, exceptionClass: KClass? = null, @@ -83,7 +83,7 @@ suspend fun eventually( */ suspend fun eventually( config: EventuallyConfig, - predicate: EventuallyPredicate = EventuallyPredicate { true }, + predicate: EventuallyPredicate = { true }, listener: EventuallyListener = EventuallyListener { }, f: SuspendingProducer, ): T { @@ -92,13 +92,16 @@ suspend fun eventually( var times = 0 var firstError: Throwable? = null var lastError: Throwable? = null + var predicateFailedTimes = 0 while (end.hasNotPassedNow() && times < config.retries) { try { val result = f() listener.onEval(EventuallyState(result, start, end, times, firstError, lastError)) - if (predicate.test(result)) { + if (predicate(result)) { return result + } else { + predicateFailedTimes++ } } catch (e: Throwable) { if (AssertionError::class.isInstance(e) || config.exceptionClass?.isInstance(e) == true) { @@ -118,6 +121,10 @@ suspend fun eventually( val message = StringBuilder().apply { appendLine("Eventually block failed after ${config.duration}; attempted $times time(s); ${config.interval} delay between attempts") + if (predicateFailedTimes > 0) { + appendLine("The provided predicate failed $predicateFailedTimes times") + } + if (firstError != null) { appendLine("The first error was caused by: ${firstError.message}") appendLine(firstError.stackTraceToString()) @@ -153,9 +160,7 @@ data class EventuallyState( val thisError: Throwable?, ) -fun interface EventuallyPredicate { - fun test(result: T): Boolean -} +typealias EventuallyPredicate = (T) -> Boolean fun interface EventuallyListener { fun onEval(state: EventuallyState)