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

Various CE3 improvements #347

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package io.catbird.util.effect

import cats.effect.Clock
import cats.effect.kernel.{ MonadCancel, Outcome, Sync }
import com.twitter.util.{ Future, Monitor }
import com.twitter.util.{ Future, Monitor, Stopwatch, Time }
import io.catbird.util.{ Rerunnable, RerunnableMonadError }

import java.lang.Throwable
import java.util.concurrent.TimeUnit
import java.lang.System

import scala.Unit
import scala.concurrent.duration.FiniteDuration
Expand All @@ -24,10 +23,10 @@ trait RerunnableInstances {
Rerunnable(thunk)

final override def realTime: Rerunnable[FiniteDuration] =
Rerunnable(FiniteDuration(System.currentTimeMillis(), TimeUnit.MILLISECONDS))
Rerunnable(FiniteDuration(Time.nowNanoPrecision.inNanoseconds, TimeUnit.NANOSECONDS))

final override def monotonic: Rerunnable[FiniteDuration] =
Rerunnable(FiniteDuration(System.nanoTime(), TimeUnit.NANOSECONDS))
Rerunnable(FiniteDuration(Stopwatch.timeNanos(), TimeUnit.NANOSECONDS))

final override def forceR[A, B](fa: Rerunnable[A])(fb: Rerunnable[B]): Rerunnable[B] =
fa.liftToTry.flatMap { resultA =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.catbird.util
package effect

import com.twitter.util.Monitor
import org.scalatest.funsuite.AnyFunSuite
import org.scalacheck.{ Arbitrary, Prop, Test }
import org.scalacheck.rng.Seed
import org.scalacheck.util.Pretty
import org.scalatest.prop.Configuration
import org.scalatestplus.scalacheck.Checkers
import org.typelevel.discipline.scalatest.FunSuiteDiscipline

abstract class BaseLawSuite extends AnyFunSuite with FunSuiteDiscipline with Configuration with EqInstances {
protected val monitor = Monitor.mk { case e => println("Monitored: " + e); true }

override protected def withFixture(test: NoArgTest) =
Monitor.using(monitor)(test())

// For debugging an individual failing law. Call this and use `testOnly <suite> -- -z withSeed`. Eg:
//
// cats law:
// import cats.kernel.laws.discipline._
// testLawWithSeed(
// cats.laws.ApplicativeLaws[Rerunnable].applicativeIdentity[Int],
// "NRzb_Wsi6ki82wDgbifBkUvntAPN5kaO8FbKSYpKXiF=",
// )
//
// cats-effect law:
// testLawWithSeed(
// cats.effect.laws.MonadCancelLaws[Rerunnable, Throwable].uncancelablePollInverseNestIsUncancelable[Int],
// "957izWLFn5kHIug2eCpFHBfajl4zwQGrNdVvC5XWtFK=",
// )
//
// You may need to wrangle the law into a Function1 with .tupled.
protected def testLawWithSeed[A: Arbitrary: * => Pretty, L: * => Prop](law: A => L, seed: String) =
test("withSeed") {
Checkers.check(Prop.forAll(law), Test.Parameters.default.withInitialSeed(Seed.fromBase64(seed).get))
}
}
14 changes: 2 additions & 12 deletions effect3/src/test/scala/io/catbird/util/effect/FutureSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,15 @@ package io.catbird.util.effect
import cats.Eq
import cats.effect.laws.MonadCancelTests
import cats.instances.all._
import cats.laws.discipline.MonadErrorTests
import cats.laws.discipline.arbitrary._
import com.twitter.conversions.DurationOps._
import com.twitter.util.Future
import io.catbird.util.{ ArbitraryInstances, EqInstances, futureEqWithFailure }
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.prop.Configuration
import org.typelevel.discipline.scalatest.FunSuiteDiscipline
import io.catbird.util.{ ArbitraryInstances, futureEqWithFailure }

class FutureSuite
extends AnyFunSuite
with FunSuiteDiscipline
with Configuration
with ArbitraryInstances
with EqInstances {
class FutureSuite extends BaseLawSuite with ArbitraryInstances {

implicit def futureEq[A](implicit A: Eq[A]): Eq[Future[A]] =
futureEqWithFailure(1.seconds)

checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int])
checkAll("Future[Int]", MonadCancelTests[Future, Throwable].monadCancel[Int, Int, Int])
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
package io.catbird.util.effect

import cats.effect.MonadCancel
import cats.effect.kernel.{ Clock, MonadCancel, Outcome }
import cats.effect.kernel.testkit.SyncTypeGenerators
import cats.effect.laws.SyncTests
import cats.instances.either._
import cats.instances.int._
import cats.instances.tuple._
import cats.instances.unit._
import cats.laws.discipline.arbitrary._
import com.twitter.util.{ Await, Monitor, Throw }
import io.catbird.util.{ ArbitraryInstances, EqInstances, Rerunnable }
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.prop.Configuration
import org.typelevel.discipline.scalatest.FunSuiteDiscipline

class RerunnableSuite
extends AnyFunSuite
with FunSuiteDiscipline
with Configuration
with ArbitraryInstances
with SyncTypeGenerators
with EqInstances
with Runners {
import com.twitter.util.{ Await, Monitor, Throw, Time }
import io.catbird.util.Rerunnable

class RerunnableSuite extends BaseLawSuite with SyncTypeGenerators with Runners {

// This includes tests for Clock, MonadCancel, and MonadError
checkAll("Rerunnable[Int]", SyncTests[Rerunnable].sync[Int, Int, Int])

test("Retrieval of real time") {
val nanos = 123456789L
val result = Time.withTimeAt(Time.fromNanoseconds(nanos)) { _ =>
unsafeRun(Clock[Rerunnable].realTime.map(_.toNanos))
}
assert(result == Outcome.succeeded(Some(nanos)))
}

test("Retrieval of monotonic time") {
val nanos = 123456789L
val result = Time.withTimeAt(Time.fromNanoseconds(nanos)) { _ =>
unsafeRun(Clock[Rerunnable].monotonic.map(_.toNanos))
}
assert(result == Outcome.succeeded(Some(nanos)))
}

test("Exceptions thrown by release are handled by Monitor") {
val useException = new Exception("thrown by use")
val releaseException = new Exception("thrown by release")
Expand Down
75 changes: 26 additions & 49 deletions effect3/src/test/scala/io/catbird/util/effect/Runners.scala
Original file line number Diff line number Diff line change
@@ -1,64 +1,41 @@
package io.catbird.util.effect

import cats.Eq
import cats.effect.{ IO, Outcome, unsafe }
import cats.effect.testkit.TestContext
import cats.effect.unsafe.IORuntimeConfig
import cats.effect.kernel.Outcome
import cats.effect.kernel.testkit.SyncGenerators
import com.twitter.util.{ Await, Return, Throw }
import io.catbird.util.{ EqInstances, Rerunnable }
import org.scalacheck.Prop
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 val ticker: Ticker = Ticker(TestContext())
implicit def arbitraryRerunnable[A: Arbitrary: Cogen]: Arbitrary[Rerunnable[A]] = {
val generators = new SyncGenerators[Rerunnable] {
val F = rerunnableInstance

implicit def eqIOA[A: Eq](implicit ticker: Ticker): Eq[IO[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()))
val arbitraryE = Arbitrary.arbThrowable
val cogenE = Cogen.cogenThrowable

ticker.ctx.tickAll(1.days)

results
} catch {
case t: Throwable =>
t.printStackTrace()
throw t
val arbitraryFD = Arbitrary.arbFiniteDuration
}
Arbitrary(generators.generators[A])
}

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 eqRerunnableA[A: Eq]: Eq[Rerunnable[A]] =
Eq.by(unsafeRun(_))

@implicitNotFound("could not find an instance of Ticker; try using `in ticked { implicit ticker =>`")
case class Ticker(ctx: TestContext)
implicit def boolRunnings(rerunnableB: Rerunnable[Boolean]): Prop =
Prop(unsafeRun(rerunnableB).fold(false, _ => false, _.getOrElse(false)))

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
)
}