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

commonMain.arrow.fx.coroutines.predef-test.kt Maven / Gradle / Ivy

package arrow.fx.coroutines

import arrow.core.Either
import arrow.core.Validated
import arrow.core.ValidatedNel
import arrow.core.identity
import arrow.core.invalid
import arrow.core.invalidNel
import arrow.core.left
import arrow.core.right
import arrow.core.valid
import arrow.core.validNel
import io.kotest.assertions.fail
import io.kotest.matchers.Matcher
import io.kotest.matchers.MatcherResult
import io.kotest.matchers.equalityMatcher
import io.kotest.property.Arb
import io.kotest.property.arbitrary.bind
import io.kotest.property.arbitrary.char
import io.kotest.property.arbitrary.choice
import io.kotest.property.arbitrary.choose
import io.kotest.property.arbitrary.constant
import io.kotest.property.arbitrary.int
import io.kotest.property.arbitrary.list
import io.kotest.property.arbitrary.long
import io.kotest.property.arbitrary.map
import io.kotest.property.arbitrary.string
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.intrinsics.intercepted
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.coroutines.resume
import kotlin.coroutines.startCoroutine
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.emptyFlow

public data class SideEffect(var counter: Int = 0) {
  public fun increment() {
    counter++
  }
}

public fun  Arb.Companion.flow(arbA: Arb): Arb> =
  Arb.choose(
    10 to Arb.list(arbA).map { it.asFlow() },
    10 to Arb.list(arbA).map { channelFlow { it.forEach { send(it) } }.buffer(Channel.RENDEZVOUS) },
    1 to Arb.constant(emptyFlow()),
  )

public fun Arb.Companion.throwable(): Arb =
  Arb.string().map(::RuntimeException)

public fun  Arb.Companion.either(left: Arb, right: Arb): Arb> {
  val failure: Arb> = left.map { l -> l.left() }
  val success: Arb> = right.map { r -> r.right() }
  return Arb.choice(failure, success)
}

public fun  Arb.Companion.validated(left: Arb, right: Arb): Arb> {
  val failure: Arb> = left.map { l -> l.invalid() }
  val success: Arb> = right.map { r -> r.valid() }
  return Arb.choice(failure, success)
}

public fun  Arb.Companion.validatedNel(left: Arb, right: Arb): Arb> {
  val failure: Arb> = left.map { l -> l.invalidNel() }
  val success: Arb> = right.map { r -> r.validNel() }
  return Arb.choice(failure, success)
}

public fun Arb.Companion.intRange(min: Int = Int.MIN_VALUE, max: Int = Int.MAX_VALUE): Arb =
  Arb.bind(Arb.int(min, max), Arb.int(min, max)) { a, b ->
    if (a < b) a..b else b..a
  }

public fun Arb.Companion.longRange(min: Long = Long.MIN_VALUE, max: Long = Long.MAX_VALUE): Arb =
  Arb.bind(Arb.long(min, max), Arb.long(min, max)) { a, b ->
    if (a < b) a..b else b..a
  }

public fun Arb.Companion.charRange(): Arb =
  Arb.bind(Arb.char(), Arb.char()) { a, b ->
    if (a < b) a..b else b..a
  }

public fun  Arb.Companion.function(arb: Arb): Arb<() -> O> =
  arb.map { { it } }

public fun Arb.Companion.unit(): Arb =
  Arb.constant(Unit)

public fun  Arb.Companion.functionAToB(arb: Arb): Arb<(A) -> B> =
  arb.map { b: B -> { _: A -> b } }

public fun  Arb.Companion.nullable(arb: Arb): Arb =
  Arb.Companion.choice(arb, arb.map { null })

/** Useful for testing success & error scenarios with an `Either` generator **/
public fun  Either.rethrow(): A =
  fold({ throw it }, ::identity)

public fun  Result.toEither(): Either =
  fold({ a -> Either.Right(a) }, { e -> Either.Left(e) })

public suspend fun Throwable.suspend(): Nothing =
  suspendCoroutineUninterceptedOrReturn { cont ->
    suspend { throw this }.startCoroutine(
      Continuation(Dispatchers.Default) {
        cont.intercepted().resumeWith(it)
      }
    )

    COROUTINE_SUSPENDED
  }

public suspend fun  A.suspend(): A =
  suspendCoroutineUninterceptedOrReturn { cont ->
    suspend { this }.startCoroutine(
      Continuation(Dispatchers.Default) {
        cont.intercepted().resumeWith(it)
      }
    )

    COROUTINE_SUSPENDED
  }

public fun  A.suspended(): suspend () -> A =
  suspend { suspend() }

/**
 * Example usage:
 * ```kotlin
 * import arrow.fx.coroutines.assertThrowable
 *
 * fun main() {
 *   val exception = assertThrowable {
 *     throw IllegalArgumentException("Talk to a duck")
 *   }
 *   require("Talk to a duck" == exception.message)
 * }
 * ```
 * 
 * @see Assertions.assertThrows
 */
public inline fun  assertThrowable(executable: () -> A): Throwable {
  val a = try {
    executable.invoke()
  } catch (e: Throwable) {
    e
  }

  return if (a is Throwable) a else fail("Expected an exception but found: $a")
}

public suspend fun CoroutineContext.shift(): Unit =
  suspendCoroutineUninterceptedOrReturn { cont ->
    suspend { this }.startCoroutine(
      Continuation(this) {
        cont.resume(Unit)
      }
    )

    COROUTINE_SUSPENDED
  }

public fun leftException(e: Throwable): Matcher> =
  object : Matcher> {
    override fun test(value: Either): MatcherResult =
      when (value) {
        is Either.Left -> when {
          value.value::class != e::class -> MatcherResult(
            false,
            "Expected exception of type ${e::class} but found ${value.value::class}",
            "Should not be exception of type ${e::class}"
          )
          value.value.message != e.message -> MatcherResult(
            false,
            "Expected exception with message ${e.message} but found ${value.value.message}",
            "Should not be exception with message ${e.message}"
          )
          else -> MatcherResult(
            true,
            "Expected exception of type ${e::class} and found ${value.value::class}",
            "Expected exception of type ${e::class} and found ${value.value::class}"
          )
        }
        is Either.Right -> MatcherResult(
          false,
          "Expected Either.Left with exception of type ${e::class} and found Right with ${value.value}",
          "Should not be Either.Left with exception"
        )
      }
  }

public fun  either(e: Either): Matcher> =
  object : Matcher> {
    override fun test(value: Either): MatcherResult =
      when (value) {
        is Either.Left -> when {
          value.value::class != (e.swap().orNull() ?: Int)::class -> MatcherResult(
            false,
            "Expected $e but found $value",
            "Should not be $e"
          )
          value.value.message != (e.swap().orNull()?.message ?: -1) -> MatcherResult(
            false,
            "Expected $e but found $value",
            "Should not be $e"
          )
          else -> MatcherResult(
            true,
            "Expected exception of type ${e::class} and found ${value.value::class}",
            "Expected exception of type ${e::class} and found ${value.value::class}"
          )
        }
        is Either.Right -> equalityMatcher(e).test(value)
      }
  }

public suspend fun  awaitExitCase(send: Channel, exit: CompletableDeferred): A =
  guaranteeCase({
    send.receive()
    awaitCancellation()
  }) { ex -> exit.complete(ex) }

public suspend fun  awaitExitCase(start: CompletableDeferred, exit: CompletableDeferred): A =
  guaranteeCase({
    start.complete(Unit)
    awaitCancellation()
  }) { ex -> exit.complete(ex) }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy