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

cats.effect.kernel.testkit.Generators.scala Maven / Gradle / Ivy

There is a newer version: 3.4-81c7f16
Show newest version
/*
 * Copyright 2020-2021 Typelevel
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cats.effect.kernel
package testkit

import cats.{Applicative, ApplicativeError, Eq, Monad, MonadError}
import cats.syntax.all._

import org.scalacheck.{Arbitrary, Cogen, Gen}, Arbitrary.arbitrary

import scala.collection.immutable.SortedMap
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.FiniteDuration

trait GenK[F[_]] {
  def apply[A: Arbitrary: Cogen]: Gen[F[A]]
}

// Generators for * -> * kinded types
trait Generators1[F[_]] {
  protected val maxDepth: Int = 10

  //todo: uniqueness based on... names, I guess. Have to solve the diamond problem somehow

  //Generators of base cases, with no recursion
  protected def baseGen[A: Arbitrary: Cogen]: List[(String, Gen[F[A]])] = {
    // prevent unused implicit param warnings, the params need to stay because
    // this method is overriden in subtraits
    val _ = (implicitly[Arbitrary[A]], implicitly[Cogen[A]])
    Nil
  }

  //Only recursive generators - the argument is a generator of the next level of depth
  protected def recursiveGen[A: Arbitrary: Cogen](
      deeper: GenK[F]): List[(String, Gen[F[A]])] = {
    // prevent unused params warnings, the params need to stay because
    // this method is overriden in subtraits
    val _ = (deeper, implicitly[Arbitrary[A]], implicitly[Cogen[A]])
    Nil
  }

  //All generators possible at depth [[depth]]
  private def gen[A: Arbitrary: Cogen](depth: Int): Gen[F[A]] = {
    val genK: GenK[F] = new GenK[F] {
      def apply[B: Arbitrary: Cogen]: Gen[F[B]] = Gen.delay(gen(depth + 1))
    }

    val gens =
      if (depth > maxDepth) baseGen[A]
      else baseGen[A] ++ recursiveGen[A](genK)

    Gen.oneOf(SortedMap(gens: _*).map(_._2)).flatMap(identity)
  }

  //All generators possible at depth 0 - the only public method
  def generators[A: Arbitrary: Cogen]: Gen[F[A]] = gen[A](0)
}

//Applicative is the first place that lets us introduce values in the context, if we discount InvariantMonoidal
trait ApplicativeGenerators[F[_]] extends Generators1[F] {
  implicit val F: Applicative[F]

  override protected def baseGen[A: Arbitrary: Cogen]: List[(String, Gen[F[A]])] =
    List("pure" -> genPure[A]) ++ super.baseGen[A]

  override protected def recursiveGen[A: Arbitrary: Cogen](
      deeper: GenK[F]): List[(String, Gen[F[A]])] =
    List(
      "map" -> genMap[A](deeper),
      "ap" -> genAp[A](deeper)
    ) ++ super.recursiveGen(deeper)

  private def genPure[A: Arbitrary]: Gen[F[A]] =
    arbitrary[A].map(_.pure[F])

  private def genMap[A: Arbitrary: Cogen](deeper: GenK[F]): Gen[F[A]] =
    for {
      fa <- deeper[A]
      f <- Arbitrary.arbitrary[A => A]
    } yield F.map(fa)(f)

  private def genAp[A: Arbitrary: Cogen](deeper: GenK[F]): Gen[F[A]] =
    for {
      fa <- deeper[A]
      ff <- deeper[A => A]
    } yield F.ap(ff)(fa)
}

trait MonadGenerators[F[_]] extends ApplicativeGenerators[F] {

  implicit val F: Monad[F]

  override protected def recursiveGen[A: Arbitrary: Cogen](
      deeper: GenK[F]): List[(String, Gen[F[A]])] =
    List(
      "flatMap" -> genFlatMap(deeper)
    ) ++ super.recursiveGen(deeper)

  private def genFlatMap[A: Arbitrary: Cogen](deeper: GenK[F]): Gen[F[A]] =
    for {
      fa <- deeper[A]
      f <- Gen.function1[A, F[A]](deeper[A])
    } yield fa.flatMap(f)
}

trait ApplicativeErrorGenerators[F[_], E] extends ApplicativeGenerators[F] {
  implicit val arbitraryE: Arbitrary[E]
  implicit val cogenE: Cogen[E]

  implicit val F: ApplicativeError[F, E]

  override protected def baseGen[A: Arbitrary: Cogen]: List[(String, Gen[F[A]])] =
    List(
      "raiseError" -> genRaiseError[A]
    ) ++ super.baseGen[A]

  override protected def recursiveGen[A](
      deeper: GenK[F])(implicit AA: Arbitrary[A], AC: Cogen[A]): List[(String, Gen[F[A]])] =
    List(
      "handleErrorWith" -> genHandleErrorWith[A](deeper)(AA, AC)
    ) ++ super.recursiveGen(deeper)(AA, AC)

  private def genRaiseError[A]: Gen[F[A]] =
    arbitrary[E].map(F.raiseError[A](_))

  private def genHandleErrorWith[A: Arbitrary: Cogen](deeper: GenK[F]): Gen[F[A]] =
    for {
      fa <- deeper[A]
      f <- Gen.function1[E, F[A]](deeper[A])
    } yield F.handleErrorWith(fa)(f)
}

trait MonadErrorGenerators[F[_], E]
    extends MonadGenerators[F]
    with ApplicativeErrorGenerators[F, E] {
  implicit val F: MonadError[F, E]
}

trait ClockGenerators[F[_]] extends Generators1[F] {
  implicit val F: Clock[F]

  implicit protected val arbitraryFD: Arbitrary[FiniteDuration]

  override protected def baseGen[A: Arbitrary: Cogen] =
    List("monotonic" -> genMonotonic[A], "realTime" -> genRealTime[A]) ++ super.baseGen[A]

  private def genMonotonic[A: Arbitrary] =
    arbitrary[A].map(F.applicative.as(F.monotonic, _))

  private def genRealTime[A: Arbitrary] =
    arbitrary[A].map(F.applicative.as(F.realTime, _))
}

trait SyncGenerators[F[_]] extends MonadErrorGenerators[F, Throwable] with ClockGenerators[F] {
  implicit val F: Sync[F]

  override protected def baseGen[A: Arbitrary: Cogen] =
    ("delay" -> arbitrary[A].map(F.delay(_))) :: super.baseGen[A]
}

trait MonadCancelGenerators[F[_], E] extends MonadErrorGenerators[F, E] {
  implicit val F: MonadCancel[F, E]

  override protected def baseGen[A: Arbitrary: Cogen]: List[(String, Gen[F[A]])] =
    ("canceled" -> genCanceled[A]) :: super.baseGen[A]

  override protected def recursiveGen[A](
      deeper: GenK[F])(implicit AA: Arbitrary[A], AC: Cogen[A]): List[(String, Gen[F[A]])] =
    List(
      "forceR" -> genForceR[A](deeper),
      "uncancelable" -> genUncancelable[A](deeper),
      "onCancel" -> genOnCancel[A](deeper)
    ) ++ super.recursiveGen(deeper)(AA, AC)

  private def genCanceled[A: Arbitrary]: Gen[F[A]] =
    arbitrary[A].map(F.canceled.as(_))

  private def genForceR[A: Arbitrary: Cogen](deeper: GenK[F]): Gen[F[A]] =
    for {
      left <- deeper[Unit]
      right <- deeper[A]
    } yield F.forceR(left)(right)

  // TODO we can't really use poll :-( since we can't Cogen FunctionK
  private def genUncancelable[A: Arbitrary: Cogen](deeper: GenK[F]): Gen[F[A]] =
    deeper[A].map(pc =>
      F.uncancelable(_ => pc)
        .flatMap(F.pure(_))
        .handleErrorWith(
          F.raiseError(_)
        )) // this is a bit of a hack to get around functor law breakage

  private def genOnCancel[A: Arbitrary: Cogen](deeper: GenK[F]): Gen[F[A]] =
    for {
      fa <- deeper[A]
      fin <- deeper[Unit]
    } yield F.onCancel(fa, fin)
}

trait GenSpawnGenerators[F[_], E] extends MonadCancelGenerators[F, E] {
  implicit val F: GenSpawn[F, E]

  override protected def baseGen[A: Arbitrary: Cogen]: List[(String, Gen[F[A]])] =
    List(
      "cede" -> genCede[A],
      "never" -> genNever[A]
    ) ++ super.baseGen[A]

  override protected def recursiveGen[A](
      deeper: GenK[F])(implicit AA: Arbitrary[A], AC: Cogen[A]): List[(String, Gen[F[A]])] =
    List(
      "racePair" -> genRacePair[A](deeper),
      "start" -> genStart[A](deeper),
      "join" -> genJoin[A](deeper)) ++ super.recursiveGen(deeper)(AA, AC)

  private def genCede[A: Arbitrary]: Gen[F[A]] =
    arbitrary[A].map(F.cede.as(_))

  private def genNever[A]: Gen[F[A]] =
    F.never[A]

  private def genStart[A: Arbitrary](deeper: GenK[F]): Gen[F[A]] =
    deeper[Unit].flatMap(pc => arbitrary[A].map(a => F.start(pc).as(a)))

  private def genJoin[A: Arbitrary: Cogen](deeper: GenK[F]): Gen[F[A]] =
    for {
      fiber <- deeper[A]
      cont <- deeper[Unit]
      a <- arbitrary[A]
    } yield F.start(fiber).flatMap(f => cont >> f.join).as(a)

  private def genRacePair[A: Arbitrary: Cogen](deeper: GenK[F]): Gen[F[A]] =
    for {
      fa <- deeper[A]
      fb <- deeper[A]

      cancel <- arbitrary[Boolean]

      back = F.racePair(fa, fb).flatMap {
        case Left((oc, f)) =>
          if (cancel)
            f.cancel *> oc.embedNever
          else
            f.join *> oc.embedNever

        case Right((f, oc)) =>
          if (cancel)
            f.cancel *> oc.embedNever
          else
            f.join *> oc.embedNever
      }
    } yield back
}

trait GenTemporalGenerators[F[_], E] extends GenSpawnGenerators[F, E] with ClockGenerators[F] {
  implicit val F: GenTemporal[F, E]

  override protected def baseGen[A: Arbitrary: Cogen] =
    ("sleep" -> genSleep[A]) :: super.baseGen[A]

  private def genSleep[A: Arbitrary] =
    for {
      t <- arbitraryFD.arbitrary
      a <- arbitrary[A]
    } yield F.sleep(t).as(a)
}

trait AsyncGenerators[F[_]] extends GenTemporalGenerators[F, Throwable] with SyncGenerators[F] {
  implicit val F: Async[F]

  implicit protected val arbitraryEC: Arbitrary[ExecutionContext]
  implicit protected val cogenFU: Cogen[F[Unit]]

  override protected def recursiveGen[A: Arbitrary: Cogen](deeper: GenK[F]) =
    List("async" -> genAsync[A](deeper), "evalOn" -> genEvalOn[A](deeper)) ++ super
      .recursiveGen[A](deeper)

  private def genAsync[A: Arbitrary](deeper: GenK[F]) =
    for {
      result <- arbitrary[Either[Throwable, A]]

      fo <- deeper[Option[F[Unit]]](
        Arbitrary(Gen.option[F[Unit]](deeper[Unit])),
        Cogen.cogenOption(cogenFU))
    } yield F
      .async[A](k => F.delay(k(result)) >> fo)
      .flatMap(F.pure(_))
      .handleErrorWith(F.raiseError(_))

  private def genEvalOn[A: Arbitrary: Cogen](deeper: GenK[F]) =
    for {
      fa <- deeper[A]
      ec <- arbitraryEC.arbitrary
    } yield F.evalOn(fa, ec)
}

trait ParallelFGenerators {
  implicit def arbitraryParallelF[F[_], A](
      implicit ArbF: Arbitrary[F[A]]): Arbitrary[ParallelF[F, A]] =
    Arbitrary {
      ArbF.arbitrary.map(f => ParallelF(f))
    }

  implicit def eqParallelF[F[_], A](implicit EqF: Eq[F[A]]): Eq[ParallelF[F, A]] =
    EqF.imap(ParallelF.apply)(ParallelF.value)
}
object ParallelFGenerators extends ParallelFGenerators

trait OutcomeGenerators {
  def outcomeGenerators[F[_]: Applicative, E: Arbitrary: Cogen] =
    new ApplicativeErrorGenerators[Outcome[F, E, *], E] {
      val arbitraryE: Arbitrary[E] = implicitly
      val cogenE: Cogen[E] = implicitly
      implicit val F: ApplicativeError[Outcome[F, E, *], E] = Outcome.applicativeError[F, E]

      override protected def baseGen[A: Arbitrary: Cogen]
          : List[(String, Gen[Outcome[F, E, A]])] =
        List(
          "const(Canceled)" -> Gen.const(Outcome.Canceled[F, E, A]())
        ) ++ super.baseGen[A]
    }

  implicit def arbitraryOutcome[F[_]: Applicative, E: Arbitrary: Cogen, A: Arbitrary: Cogen]
      : Arbitrary[Outcome[F, E, A]] =
    Arbitrary {
      outcomeGenerators[F, E].generators[A]
    }

  implicit def cogenOutcome[F[_], E: Cogen, A](
      implicit A: Cogen[F[A]]): Cogen[Outcome[F, E, A]] =
    Cogen[Option[Either[E, F[A]]]].contramap {
      case Outcome.Canceled() => None
      case Outcome.Succeeded(fa) => Some(Right(fa))
      case Outcome.Errored(e) => Some(Left(e))
    }
}
object OutcomeGenerators extends OutcomeGenerators

trait SyncTypeGenerators {

  implicit val arbitrarySyncType: Arbitrary[Sync.Type] = {
    import Sync.Type._

    Arbitrary(Gen.oneOf(Delay, Blocking, InterruptibleOnce, InterruptibleMany))
  }

  implicit val cogenSyncType: Cogen[Sync.Type] = {
    import Sync.Type._

    Cogen[Int] contramap {
      case Delay => 0
      case Blocking => 1
      case InterruptibleOnce => 2
      case InterruptibleMany => 3
    }
  }
}
object SyncTypeGenerators extends SyncTypeGenerators




© 2015 - 2024 Weber Informatics LLC | Privacy Policy