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 to have one type parameter; removed result … #2024

Merged
merged 3 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
13 changes: 5 additions & 8 deletions kotest-assertions/kotest-assertions-core/build.gradle.kts
Expand Up @@ -38,14 +38,6 @@ kotlin {
iosArm32()
}

targets.all {
compilations.all {
kotlinOptions {
freeCompilerArgs = freeCompilerArgs + listOf("-Xopt-in=kotlin.RequiresOptIn", "-Xopt-in=kotlin.time.ExperimentalTime")
}
}
}

sourceSets {

val commonMain by getting {
Expand Down Expand Up @@ -75,6 +67,11 @@ kotlin {
implementation(Libs.Apache.commonslang)
}
}

all {
languageSettings.useExperimentalAnnotation("kotlin.time.ExperimentalTime")
languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
}
}
}

Expand Down
Expand Up @@ -11,7 +11,6 @@ import kotlin.time.hours
import kotlin.time.milliseconds
import kotlin.time.seconds

@OptIn(ExperimentalTime::class)
class ContinuallyTest : WordSpec() {

init {
Expand Down
Expand Up @@ -20,7 +20,6 @@ import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.time.*

@OptIn(ExperimentalTime::class)
class EventuallyTest : WordSpec() {

init {
Expand Down Expand Up @@ -167,8 +166,12 @@ class EventuallyTest : WordSpec() {
"eventually with T predicate, interval, and listener" {
var t = ""
val latch = CountDownLatch(5)
val result = eventually(5.seconds, 250.milliseconds.fixed(),
listener = { _, _ -> latch.countDown() }, predicate = { t == "xxxxxxxxxxx" }) {
val result = eventually(
5.seconds,
250.milliseconds.fixed(),
predicate = { t == "xxxxxxxxxxx" },
listener = { _ -> latch.countDown() },
) {
t += "x"
t
}
Expand All @@ -187,8 +190,12 @@ class EventuallyTest : WordSpec() {
"support fibonacci intervals" {
var t = ""
val latch = CountDownLatch(5)
val result = eventually(10.seconds, 200.milliseconds.fibonacci(),
listener = { _, _ -> latch.countDown() }, predicate = { t == "xxxxxx" }) {
val result = eventually(
duration = 10.seconds,
interval = 200.milliseconds.fibonacci(),
predicate = { t == "xxxxxx" },
listener = { latch.countDown() },
) {
t += "x"
t
}
Expand All @@ -197,8 +204,10 @@ class EventuallyTest : WordSpec() {
}

"eventually has a shareable configuration" {
val slow = EventuallyConfig<Int, Throwable>(duration = 5.seconds)
val fast = slow.copy(retries = 1)
val slow = EventuallyConfig<Int>(duration = 5.seconds)

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

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

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

Expand Down
@@ -1,7 +1,6 @@
package com.sksamuel.kotest.assertions.until

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.timing.eventually
import io.kotest.assertions.until.fibonacci
import io.kotest.assertions.until.fixed
import io.kotest.assertions.until.until
Expand All @@ -10,11 +9,9 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.time.ExperimentalTime
import kotlin.time.milliseconds
import kotlin.time.seconds

@OptIn(ExperimentalTime::class)
class UntilTest : FunSpec({

test("until with boolean predicate") {
Expand Down
5 changes: 5 additions & 0 deletions kotest-assertions/kotest-assertions-shared/build.gradle.kts
Expand Up @@ -98,6 +98,11 @@ kotlin {
val tvosMain by getting {
dependsOn(desktopMain)
}

all {
languageSettings.useExperimentalAnnotation("kotlin.time.ExperimentalTime")
languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
}
}
}

Expand Down
Expand Up @@ -4,28 +4,26 @@ import io.kotest.mpp.bestName
import kotlin.reflect.KClass
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.ExperimentalTime
import kotlin.time.TimeSource
import kotlin.time.seconds
import kotlinx.coroutines.delay

/**
* Retry [f] until it's a success or [maxRetry]/[timeout] is reached
*
*
* This will treat any Exception as a failure, along with [AssertionError].
*
*
* Retry delay might increase exponentially if you choose a [multiplier] value. For example, if you want to configure
* 5 [maxRetry], with an initial [delay] of 1s between requests, the delay between requests will increase when you
* choose 2 as your [multiplier]:
* 1 - Failed (wait 1s before retrying)
* 2 - Failed (wait 2s before retrying)
* 3 - Failed (wait 4s before retrying)
* ..
*
*
* If either timeout or max retries is reached, the execution will be aborted and an exception will be thrown.
*
*
* */
@OptIn(ExperimentalTime::class)
suspend fun <T> retry(
maxRetry: Int,
timeout: Duration,
Expand All @@ -51,7 +49,6 @@ suspend fun <T> retry(
* If either timeout or max retries is reached, the execution will be aborted and an exception will be thrown.
*
* */
@OptIn(ExperimentalTime::class)
suspend fun <T, E : Throwable> retry(
maxRetry: Int,
timeout: Duration,
Expand Down
Expand Up @@ -7,7 +7,6 @@ import io.kotest.assertions.until.fixed
import kotlinx.coroutines.delay
import kotlin.time.*

@OptIn(ExperimentalTime::class)
data class ContinuallyState(val start: TimeMark, val end: TimeMark, val times: Int)

fun interface ContinuallyListener<in T> {
Expand All @@ -18,7 +17,6 @@ fun interface ContinuallyListener<in T> {
}
}

@OptIn(ExperimentalTime::class)
data class Continually<T> (
val duration: Duration = Duration.INFINITE,
val interval: Interval = 25.milliseconds.fixed(),
Expand Down Expand Up @@ -50,13 +48,11 @@ data class Continually<T> (
}
}

@OptIn(ExperimentalTime::class)
@Deprecated("Use continually with an interval, using Duration based poll is deprecated",
ReplaceWith("continually(duration, poll.fixed(), f = f)", "io.kotest.assertions.until.fixed")
)
suspend fun <T> continually(duration: Duration, poll: Duration, f: suspend () -> T) =
continually(duration, poll.fixed(), f = f)

@OptIn(ExperimentalTime::class)
suspend fun <T> continually(duration: Duration, interval: Interval = 10.milliseconds.fixed(), f: suspend () -> T) =
Continually<T>(duration, interval).invoke(f)
Expand Up @@ -12,34 +12,40 @@ import kotlin.time.*
/**
* Runs a function until it doesn't throw as long as the specified duration hasn't passed
*/
@OptIn(ExperimentalTime::class)
suspend fun <T> eventually(duration: Duration, f: SuspendingProducer<T>): T =
eventually(EventuallyConfig(duration = duration, exceptionClass = Throwable::class), f = f)

/**
* Runs a function until it doesn't throw and the result satisfies the predicate as long as the specified duration hasn't passed
*/
@OptIn(ExperimentalTime::class)
suspend fun <T> eventually(duration: Duration, predicate: SuspendingPredicate<T>, f: SuspendingProducer<T>): T =
eventually(EventuallyConfig(duration = duration, exceptionClass = Throwable::class), predicate, f)
suspend fun <T : Any> eventually(
duration: Duration,
interval: Interval,
f: SuspendingProducer<T>
): T = eventually(EventuallyConfig(duration, interval), f)

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


@OptIn(ExperimentalTime::class)
@Deprecated("""
@Deprecated(
"""
Use eventually with an interval, using Duration based poll is deprecated.
To convert an existing duration to an interval you can Duration.fixed(), Duration.exponential(), or Duration.fibonacci().
""",
ReplaceWith(
"eventually(duration, interval = poll.fixed(), f = f)",
"io.kotest.assertions.until.fixed"
))
)
)
suspend fun <T> eventually(duration: Duration, poll: Duration, f: SuspendingProducer<T>): T =
eventually(EventuallyConfig(duration = duration, interval = poll.fixed(), exceptionClass = Throwable::class), f = f)

/**
* Runs a function until it doesn't throw the specified exception as long as the specified duration hasn't passed
*/
@OptIn(ExperimentalTime::class)
suspend fun <T, E : Throwable> eventually(duration: Duration, exceptionClass: KClass<E>, f: SuspendingProducer<T>): T =
suspend fun <T> eventually(duration: Duration, exceptionClass: KClass<out Throwable>, f: SuspendingProducer<T>): T =
eventually(EventuallyConfig(duration = duration, exceptionClass = exceptionClass), f = f)

/**
Expand All @@ -52,25 +58,22 @@ suspend fun <T, E : Throwable> eventually(duration: Duration, exceptionClass: KC
* [eventually] will delay the specified [interval] between iterations, defaults to 25 [milliseconds]
* [eventually] will pass the resulting value and state (see [EventuallyState]) into the optional [listener]
*/
@OptIn(ExperimentalTime::class)
suspend fun <T> eventually(
duration: Duration = Duration.INFINITE,
interval: Interval = 25.milliseconds.fixed(),
listener: EventuallyListener<T> = EventuallyListener.noop,
retries: Int = Int.MAX_VALUE,
exceptionClass: KClass<Throwable>? = Throwable::class,
predicate: SuspendingPredicate<T> = { true },
listener: EventuallyListener<T> = EventuallyListener { },
retries: Int = Int.MAX_VALUE,
exceptionClass: KClass<out Throwable>? = null,
f: SuspendingProducer<T>
): T = eventually(EventuallyConfig(duration, interval, listener, retries, exceptionClass), predicate, f)
): T = eventually(EventuallyConfig(duration, interval, predicate, listener, retries, exceptionClass), 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.
*/
@OptIn(ExperimentalTime::class)
suspend fun <T, E : Throwable> eventually(
config: EventuallyConfig<T, E>,
predicate: SuspendingPredicate<T> = { true },
suspend fun <T> eventually(
config: EventuallyConfig<T>,
f: SuspendingProducer<T>,
): T {
val start = TimeSource.Monotonic.markNow()
Expand All @@ -82,8 +85,8 @@ suspend fun <T, E : Throwable> eventually(
while (end.hasNotPassedNow() && times < config.retries) {
try {
val result = f()
config.listener.onEval(result, EventuallyState(start, end, times, firstError, lastError))
if (predicate(result)) {
config.listener.onEval(EventuallyState(result, start, end, times, firstError, lastError))
if (config.predicate(result)) {
return result
}
} catch (e: Throwable) {
Expand Down Expand Up @@ -118,28 +121,28 @@ suspend fun <T, E : Throwable> eventually(
throw failure(message.toString())
}

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

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

fun interface EventuallyListener<in T> {
fun onEval(t: T, state: EventuallyState)

companion object {
val noop = EventuallyListener<Any?> { _, _ -> }
}
fun interface EventuallyListener<T> {
fun onEval(state: EventuallyState<T>)
}
Expand Up @@ -2,10 +2,8 @@ package io.kotest.assertions.until

import kotlin.math.pow
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.milliseconds

@OptIn(ExperimentalTime::class)
class ExponentialInterval(private val base: Duration) : Interval {
override fun toString() = "ExponentialInterval(${::base.name}=$base)"

Expand All @@ -15,5 +13,4 @@ class ExponentialInterval(private val base: Duration) : Interval {
}
}

@OptIn(ExperimentalTime::class)
fun Duration.exponential() = ExponentialInterval(this)
@@ -1,7 +1,6 @@
package io.kotest.assertions.until

import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.milliseconds

/**
Expand All @@ -14,7 +13,6 @@ import kotlin.time.milliseconds
* @param offset Added to the count, so if the offset is 4, then the first value will be the 4th fib number.
* @param base The duration that is multiplied by the fibonacci value
*/
@OptIn(ExperimentalTime::class)
class FibonacciInterval(private val base: Duration, private val offset: Int) : Interval {

init {
Expand All @@ -30,7 +28,6 @@ class FibonacciInterval(private val base: Duration, private val offset: Int) : I
}
}

@OptIn(ExperimentalTime::class)
fun Duration.fibonacci() = FibonacciInterval(this, 0)

fun fibonacci(n: Int): Int {
Expand Down
@@ -1,12 +1,10 @@
package io.kotest.assertions.until

import kotlin.time.Duration
import kotlin.time.ExperimentalTime

/**
* Generates a fixed (linear) poll interval based on the supplied duration
*/
@OptIn(ExperimentalTime::class)
class FixedInterval(private val duration: Duration) : Interval {
override fun toString() = "FixedInterval(${::duration.name}=$duration)"

Expand All @@ -15,5 +13,4 @@ class FixedInterval(private val duration: Duration) : Interval {
}
}

@OptIn(ExperimentalTime::class)
fun Duration.fixed() = FixedInterval(this)
fun Duration.fixed(): FixedInterval = FixedInterval(this)