Skip to content

Commit

Permalink
CE3: Catbird-native Runners for tests.
Browse files Browse the repository at this point in the history
This works with Await for Sync: it will need to become more sophisticated to support cancellation and
non-termination.
  • Loading branch information
dangerousben committed Jun 1, 2021
1 parent 9bf6b02 commit 33f3333
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 51 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ lazy val effect3 = project
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect" % catsEffect3Version,
"org.typelevel" %% "cats-effect-laws" % catsEffect3Version % Test,
"org.typelevel" %% "cats-effect-testkit" % catsEffect3Version % Test
"org.typelevel" %% "cats-effect-kernel-testkit" % catsEffect3Version % Test
),
(Test / scalacOptions) ~= {
_.filterNot(Set("-Yno-imports", "-Yno-predef"))
Expand Down
60 changes: 10 additions & 50 deletions effect3/src/test/scala/io/catbird/util/effect/Runners.scala
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
package io.catbird.util.effect

import cats.Eq
import cats.effect.{ IO, Outcome, unsafe }
import cats.effect.kernel.Outcome
import cats.effect.kernel.testkit.SyncGenerators
import cats.effect.testkit.TestContext
import cats.effect.unsafe.IORuntimeConfig
import com.twitter.util.{ Await, Return, Throw }
import io.catbird.util.{ EqInstances, Rerunnable }
import org.scalacheck.{ Arbitrary, Cogen, Prop }

import scala.annotation.implicitNotFound
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration._
import scala.language.implicitConversions

/**
* Test helpers mostly taken from the cats-effect IOSpec.
*/
trait Runners { self: EqInstances =>

implicit def arbitraryRerunnable[A: Arbitrary: Cogen]: Arbitrary[Rerunnable[A]] = {
Expand All @@ -30,48 +23,15 @@ trait Runners { self: EqInstances =>
Arbitrary(generators.generators[A])
}

implicit val ticker: Ticker = Ticker(TestContext())

implicit def eqIOA[A: Eq](implicit ticker: Ticker): Eq[IO[A]] =
implicit def eqRerunnableA[A: Eq]: Eq[Rerunnable[A]] =
Eq.by(unsafeRun(_))

implicit def rerunnableEq[A](implicit A: Eq[A]): Eq[Rerunnable[A]] =
Eq.by[Rerunnable[A], IO[A]](rerunnableToIO)

implicit def boolRunnings(rerunnableB: Rerunnable[Boolean])(implicit ticker: Ticker): Prop =
Prop(unsafeRun(rerunnableToIO(rerunnableB)).fold(false, _ => false, _.getOrElse(false)))

def unsafeRun[A](ioa: IO[A])(implicit ticker: Ticker): Outcome[Option, Throwable, A] =
try {
var results: Outcome[Option, Throwable, A] = Outcome.Succeeded(None)

ioa.unsafeRunAsync {
case Left(t) => results = Outcome.Errored(t)
case Right(a) => results = Outcome.Succeeded(Some(a))
}(unsafe.IORuntime(ticker.ctx, ticker.ctx, scheduler, () => (), IORuntimeConfig()))

ticker.ctx.tickAll(1.days)

results
} catch {
case t: Throwable =>
t.printStackTrace()
throw t
}

def scheduler(implicit ticker: Ticker): unsafe.Scheduler =
new unsafe.Scheduler {
import ticker.ctx

def sleep(delay: FiniteDuration, action: Runnable): Runnable = {
val cancel = ctx.schedule(delay, action)
new Runnable { def run() = cancel() }
}

def nowMillis() = ctx.now().toMillis
def monotonicNanos() = ctx.now().toNanos
}
implicit def boolRunnings(rerunnableB: Rerunnable[Boolean]): Prop =
Prop(unsafeRun(rerunnableB).fold(false, _ => false, _.getOrElse(false)))

@implicitNotFound("could not find an instance of Ticker; try using `in ticked { implicit ticker =>`")
case class Ticker(ctx: TestContext)
def unsafeRun[A](fa: Rerunnable[A]): Outcome[Option, Throwable, A] =
Await.result(fa.liftToTry.map[Outcome[Option, Throwable, A]] {
case Return(a) => Outcome.succeeded(Some(a))
case Throw(e) => Outcome.errored(e)
}.run)
}

0 comments on commit 33f3333

Please sign in to comment.