All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.kotest.framework.concurrency.continually.kt Maven / Gradle / Ivy

package io.kotest.framework.concurrency

import io.kotest.assertions.failure
import io.kotest.common.ExperimentalKotest
import io.kotest.mpp.timeInMillis
import kotlinx.coroutines.delay
import kotlin.time.Duration
import kotlin.time.ExperimentalTime

@OptIn(ExperimentalKotest::class)
typealias ContinuallyListener = (ContinuallyState) -> Unit

@ExperimentalKotest
data class ContinuallyConfig(
   val duration: Long = defaultDuration,
   val interval: Interval = defaultInterval,
   val initialDelay: Long = defaultDelay,
   val listener: ContinuallyListener? = null,
)

@ExperimentalKotest
class ContinuallyBuilder {
   var duration: Long = defaultDuration
   var interval: Interval = defaultInterval
   var initialDelay: Long = defaultDelay
   var listener: ContinuallyListener? = null

   fun toConfig() = ContinuallyConfig(
      duration = duration, interval = interval, initialDelay = initialDelay, listener = listener
   )

   constructor()

   constructor(config: ContinuallyConfig) {
      duration = config.duration
      interval = config.interval
      initialDelay = config.initialDelay
      listener = config.listener
   }
}

@ExperimentalKotest
data class ContinuallyState(val result: T, val start: Long, val end: Long, val times: Int)

@ExperimentalKotest
private suspend fun  ContinuallyConfig.invoke(f: suspend () -> T): T? {
   delay(initialDelay)

   val start = timeInMillis()
   val end = start + duration
   var times = 0
   var result: T? = null

   while (timeInMillis() < end) {
      try {
         result = f()
         listener?.invoke(ContinuallyState(result, start, end, times))
      } catch (e: AssertionError) {
         // if this is the first time the check was executed then just rethrow the underlying error
         if (times == 0)
            throw e
         // if not the first attempt then include how many times/for how long the test passed
         throw failure(
            "Test failed after ${start}ms; expected to pass for ${duration}ms; attempted $times times\nUnderlying failure was: ${e.message}",
            e
         )
      }
      delay(interval.next(++times))
   }
   return result
}

// region continually

@ExperimentalKotest
suspend fun  continually(
   config: ContinuallyConfig, configure: ContinuallyBuilder.() -> Unit, @BuilderInference test: suspend () -> T
): T? {
   val builder = ContinuallyBuilder(config).apply(configure)
   return builder.toConfig().invoke(test)
}

@ExperimentalKotest
suspend fun  continually(
   configure: ContinuallyBuilder.() -> Unit, @BuilderInference test: suspend () -> T
): T? {
   val builder = ContinuallyBuilder().apply(configure)
   return builder.toConfig().invoke(test)
}

@ExperimentalTime
@ExperimentalKotest
suspend fun  continually(duration: Duration, test: suspend () -> T): T? =
   continually(duration.inWholeMilliseconds, test)

@ExperimentalKotest
suspend fun  continually(duration: Long, test: suspend () -> T): T? = continually({ this.duration = duration }, test)

// endregion




© 2015 - 2025 Weber Informatics LLC | Privacy Policy