From feaffd54941b457e001c938aa1fa24f3ab549fe8 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 21 Aug 2023 23:10:53 +0000 Subject: [PATCH 01/20] Migrate kernel and law tests to munit --- build.sbt | 7 ++-- .../{SyntaxSpec.scala => SyntaxSuite.scala} | 6 +-- .../{BaseSpec.scala => BaseSuite.scala} | 2 +- .../{OutcomeSpec.scala => OutcomeSuite.scala} | 7 ++-- .../{ClockSpec.scala => ClockSuite.scala} | 5 +-- ...cSpec.scala => EitherTFreeSyncSuite.scala} | 10 ++--- ...cSpec.scala => EitherTPureConcSuite.scala} | 6 +-- ...FreeSyncSpec.scala => FreeSyncSuite.scala} | 10 ++--- ...poralSpec.scala => GenTemporalSuite.scala} | 26 +++++++----- ...SyncSpec.scala => IorTFreeSyncSuite.scala} | 10 ++--- ...ConcSpec.scala => IorTPureConcSuite.scala} | 6 +-- ...cSpec.scala => KleisliFreeSyncSuite.scala} | 10 ++--- ...cSpec.scala => KleisliPureConcSuite.scala} | 20 +++++----- ...cSpec.scala => OptionTFreeSyncSuite.scala} | 5 +-- ...cSpec.scala => OptionTPureConcSuite.scala} | 19 ++++----- ...PureConcSpec.scala => PureConcSuite.scala} | 40 ++++++++++--------- ... => ReaderWriterStateTFreeSyncSuite.scala} | 17 ++++---- ... => ReaderWriterStateTPureConcSuite.scala} | 13 +++--- ...Spec.scala => ResourcePureConcSuite.scala} | 11 +++-- ...ncSpec.scala => StateTFreeSyncSuite.scala} | 10 ++--- ...ncSpec.scala => StateTPureConcSuite.scala} | 14 ++++--- ...cSpec.scala => WriterTFreeSyncSuite.scala} | 10 ++--- ...cSpec.scala => WriterTPureConcSuite.scala} | 6 +-- .../{TimeTSpec.scala => TimeTSuite.scala} | 6 +-- 24 files changed, 132 insertions(+), 144 deletions(-) rename kernel/shared/src/test/scala/cats/effect/kernel/{SyntaxSpec.scala => SyntaxSuite.scala} (97%) rename laws/shared/src/test/scala/cats/effect/{BaseSpec.scala => BaseSuite.scala} (99%) rename laws/shared/src/test/scala/cats/effect/kernel/{OutcomeSpec.scala => OutcomeSuite.scala} (90%) rename laws/shared/src/test/scala/cats/effect/laws/{ClockSpec.scala => ClockSuite.scala} (83%) rename laws/shared/src/test/scala/cats/effect/laws/{EitherTFreeSyncSpec.scala => EitherTFreeSyncSuite.scala} (89%) rename laws/shared/src/test/scala/cats/effect/laws/{EitherTPureConcSpec.scala => EitherTPureConcSuite.scala} (89%) rename laws/shared/src/test/scala/cats/effect/laws/{FreeSyncSpec.scala => FreeSyncSuite.scala} (89%) rename laws/shared/src/test/scala/cats/effect/laws/{GenTemporalSpec.scala => GenTemporalSuite.scala} (87%) rename laws/shared/src/test/scala/cats/effect/laws/{IorTFreeSyncSpec.scala => IorTFreeSyncSuite.scala} (88%) rename laws/shared/src/test/scala/cats/effect/laws/{IorTPureConcSpec.scala => IorTPureConcSuite.scala} (89%) rename laws/shared/src/test/scala/cats/effect/laws/{KleisliFreeSyncSpec.scala => KleisliFreeSyncSuite.scala} (88%) rename laws/shared/src/test/scala/cats/effect/laws/{KleisliPureConcSpec.scala => KleisliPureConcSuite.scala} (93%) rename laws/shared/src/test/scala/cats/effect/laws/{OptionTFreeSyncSpec.scala => OptionTFreeSyncSuite.scala} (87%) rename laws/shared/src/test/scala/cats/effect/laws/{OptionTPureConcSpec.scala => OptionTPureConcSuite.scala} (79%) rename laws/shared/src/test/scala/cats/effect/laws/{PureConcSpec.scala => PureConcSuite.scala} (63%) rename laws/shared/src/test/scala/cats/effect/laws/{ReaderWriterStateTFreeSyncSpec.scala => ReaderWriterStateTFreeSyncSuite.scala} (81%) rename laws/shared/src/test/scala/cats/effect/laws/{ReaderWriterStateTPureConcSpec.scala => ReaderWriterStateTPureConcSuite.scala} (85%) rename laws/shared/src/test/scala/cats/effect/laws/{ResourcePureConcSpec.scala => ResourcePureConcSuite.scala} (89%) rename laws/shared/src/test/scala/cats/effect/laws/{StateTFreeSyncSpec.scala => StateTFreeSyncSuite.scala} (88%) rename laws/shared/src/test/scala/cats/effect/laws/{StateTPureConcSpec.scala => StateTPureConcSuite.scala} (78%) rename laws/shared/src/test/scala/cats/effect/laws/{WriterTFreeSyncSpec.scala => WriterTFreeSyncSuite.scala} (88%) rename laws/shared/src/test/scala/cats/effect/laws/{WriterTPureConcSpec.scala => WriterTPureConcSuite.scala} (89%) rename laws/shared/src/test/scala/cats/effect/testkit/{TimeTSpec.scala => TimeTSuite.scala} (92%) diff --git a/build.sbt b/build.sbt index b71524a1a9..c6277b1ab4 100644 --- a/build.sbt +++ b/build.sbt @@ -306,6 +306,8 @@ val Specs2Version = "4.20.5" val ScalaCheckVersion = "1.17.1" val DisciplineVersion = "1.4.0" val CoopVersion = "1.2.0" +val MUnitVersion = "1.0.0-M8" +val DisciplineMUnitVersion = "2.0.0-M3" val MacrotaskExecutorVersion = "1.1.1" @@ -394,8 +396,7 @@ lazy val kernel = crossProject(JSPlatform, JVMPlatform, NativePlatform) .settings( name := "cats-effect-kernel", libraryDependencies ++= Seq( - "org.typelevel" %%% "cats-core" % CatsVersion, - "org.specs2" %%% "specs2-core" % Specs2Version % Test + "org.typelevel" %%% "cats-core" % CatsVersion ), mimaBinaryIssueFilters ++= Seq( ProblemFilters.exclude[MissingClassProblem]("cats.effect.kernel.Ref$SyncRef"), @@ -451,7 +452,7 @@ lazy val laws = crossProject(JSPlatform, JVMPlatform, NativePlatform) name := "cats-effect-laws", libraryDependencies ++= Seq( "org.typelevel" %%% "cats-laws" % CatsVersion, - "org.typelevel" %%% "discipline-specs2" % DisciplineVersion % Test) + "org.typelevel" %%% "discipline-munit" % DisciplineMUnitVersion % Test) ) /** diff --git a/kernel/shared/src/test/scala/cats/effect/kernel/SyntaxSpec.scala b/kernel/shared/src/test/scala/cats/effect/kernel/SyntaxSuite.scala similarity index 97% rename from kernel/shared/src/test/scala/cats/effect/kernel/SyntaxSpec.scala rename to kernel/shared/src/test/scala/cats/effect/kernel/SyntaxSuite.scala index 0536eb3c03..58debfa29b 100644 --- a/kernel/shared/src/test/scala/cats/effect/kernel/SyntaxSpec.scala +++ b/kernel/shared/src/test/scala/cats/effect/kernel/SyntaxSuite.scala @@ -18,14 +18,10 @@ package cats.effect.kernel import cats.implicits._ -import org.specs2.mutable.Specification - import scala.concurrent.ExecutionContext import scala.concurrent.duration.{Duration, FiniteDuration} -class SyntaxSpec extends Specification { - - "kernel syntax" >> ok +class SyntaxSuite { def concurrentForwarder[F[_]: Concurrent] = Concurrent[F] diff --git a/laws/shared/src/test/scala/cats/effect/BaseSpec.scala b/laws/shared/src/test/scala/cats/effect/BaseSuite.scala similarity index 99% rename from laws/shared/src/test/scala/cats/effect/BaseSpec.scala rename to laws/shared/src/test/scala/cats/effect/BaseSuite.scala index 8cffc5d793..669c3082e0 100644 --- a/laws/shared/src/test/scala/cats/effect/BaseSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/BaseSuite.scala @@ -27,7 +27,7 @@ import org.scalacheck.{Arbitrary, Gen, Prop} import org.scalacheck.util.Pretty // A dumping ground for random helpers for property tests -private[effect] trait BaseSpec { +private[effect] trait BaseSuite { implicit def kleisliEq[F[_], A, B](implicit ev: Eq[A => F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) diff --git a/laws/shared/src/test/scala/cats/effect/kernel/OutcomeSpec.scala b/laws/shared/src/test/scala/cats/effect/kernel/OutcomeSuite.scala similarity index 90% rename from laws/shared/src/test/scala/cats/effect/kernel/OutcomeSpec.scala rename to laws/shared/src/test/scala/cats/effect/kernel/OutcomeSuite.scala index 92ee1e355e..b8f1eb05d6 100644 --- a/laws/shared/src/test/scala/cats/effect/kernel/OutcomeSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/kernel/OutcomeSuite.scala @@ -21,10 +21,11 @@ import cats.{Eq, Eval, Id, MonadError} import cats.effect.kernel.testkit.OutcomeGenerators import cats.laws.discipline.{ApplicativeErrorTests, MonadErrorTests} -import org.specs2.mutable.Specification -import org.typelevel.discipline.specs2.mutable.Discipline +import org.scalacheck.{Arbitrary, Cogen} -class OutcomeSpec extends Specification with Discipline { +import munit.DisciplineSuite + +class OutcomeSuite extends DisciplineSuite { import OutcomeGenerators._ { diff --git a/laws/shared/src/test/scala/cats/effect/laws/ClockSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/ClockSuite.scala similarity index 83% rename from laws/shared/src/test/scala/cats/effect/laws/ClockSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/ClockSuite.scala index 6977c5b073..e805a500ff 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/ClockSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/ClockSuite.scala @@ -20,10 +20,9 @@ package laws import cats.data.ContT import cats.effect.kernel.testkit.freeEval._ -import org.specs2.mutable.Specification -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class ClockSpec extends Specification with Discipline with BaseSpec { +class ClockSuite extends DisciplineSuite with BaseSuite { // we only need to test the ones that *aren't* also Sync checkAll("ContT[FreeEitherSync, Int, *]", ClockTests[ContT[FreeEitherSync, Int, *]].clock) diff --git a/laws/shared/src/test/scala/cats/effect/laws/EitherTFreeSyncSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/EitherTFreeSyncSuite.scala similarity index 89% rename from laws/shared/src/test/scala/cats/effect/laws/EitherTFreeSyncSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/EitherTFreeSyncSuite.scala index a1e4c127b3..b25283ca98 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/EitherTFreeSyncSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/EitherTFreeSyncSuite.scala @@ -24,13 +24,11 @@ import cats.effect.kernel.testkit.freeEval.{syncForFreeT, FreeEitherSync} import cats.free.FreeT import cats.laws.discipline.arbitrary._ -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class EitherTFreeSyncSpec - extends Specification - with Discipline - with BaseSpec +class EitherTFreeSyncSuite + extends DisciplineSuite + with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/EitherTPureConcSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/EitherTPureConcSuite.scala similarity index 89% rename from laws/shared/src/test/scala/cats/effect/laws/EitherTPureConcSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/EitherTPureConcSuite.scala index e5662cb4c4..7e3e5e7300 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/EitherTPureConcSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/EitherTPureConcSuite.scala @@ -24,12 +24,12 @@ import cats.effect.kernel.testkit.pure._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Prop -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class EitherTPureConcSpec extends Specification with Discipline with BaseSpec { +import munit.DisciplineSuite + +class EitherTPureConcSuite extends DisciplineSuite with BaseSuite { import PureConcGenerators._ import OutcomeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/FreeSyncSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/FreeSyncSuite.scala similarity index 89% rename from laws/shared/src/test/scala/cats/effect/laws/FreeSyncSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/FreeSyncSuite.scala index 0aaad68f86..4d21d26c59 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/FreeSyncSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/FreeSyncSuite.scala @@ -23,13 +23,11 @@ import cats.effect.kernel.testkit.freeEval.{syncForFreeT, FreeEitherSync} import cats.free.FreeT import cats.laws.discipline.arbitrary._ -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class FreeSyncSpec - extends Specification - with Discipline - with BaseSpec +class FreeSyncSuite + extends DisciplineSuite + with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSuite.scala similarity index 87% rename from laws/shared/src/test/scala/cats/effect/laws/GenTemporalSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/GenTemporalSuite.scala index 7a268de612..b6838cbde5 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSuite.scala @@ -23,12 +23,12 @@ import cats.effect.kernel.testkit.TimeT import cats.effect.kernel.testkit.pure._ import cats.syntax.all._ -import org.specs2.mutable.Specification - -import scala.concurrent.TimeoutException import scala.concurrent.duration._ -class GenTemporalSpec extends Specification { outer => +import munit.FunSuite +// import scala.concurrent.TimeoutException + +class GenTemporalSuite extends FunSuite { outer => type F[A] = PureConc[Throwable, A] @@ -37,6 +37,10 @@ class GenTemporalSpec extends Specification { outer => val loop: TimeT[F, Unit] = F.sleep(5.millis).foreverM + test("timeout should return identity when infinite duration") { + val fa = F.pure(true) + assertEquals(F.timeout(fa, Duration.Inf), fa) + } "temporal" should { "timeout" should { "return identity when infinite duration" in { @@ -79,6 +83,11 @@ class GenTemporalSpec extends Specification { outer => } } + test("timeoutTo should return identity when infinite duration") { + val fa: TimeT[F, Boolean] = F.pure(true) + val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) + assertEquals(F.timeoutTo(fa, Duration.Inf, fallback), fa) + } "timeoutTo" should { "return identity when infinite duration" in { val fa: TimeT[F, Boolean] = F.pure(true) @@ -125,12 +134,9 @@ class GenTemporalSpec extends Specification { outer => } } - "timeoutAndForget" should { - "return identity when infinite duration" in { - val fa: TimeT[F, Boolean] = F.pure(true) - F.timeoutAndForget(fa, Duration.Inf) mustEqual fa - } - } + test("timeoutAndForget should return identity when infinite duration") { + val fa: TimeT[F, Boolean] = F.pure(true) + assertEquals(F.timeoutAndForget(fa, Duration.Inf), fa) } // TODO enable these tests once Temporal for TimeT is fixed diff --git a/laws/shared/src/test/scala/cats/effect/laws/IorTFreeSyncSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/IorTFreeSyncSuite.scala similarity index 88% rename from laws/shared/src/test/scala/cats/effect/laws/IorTFreeSyncSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/IorTFreeSyncSuite.scala index b1d2fefdde..048fdc3017 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/IorTFreeSyncSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/IorTFreeSyncSuite.scala @@ -24,13 +24,11 @@ import cats.effect.kernel.testkit.freeEval.{syncForFreeT, FreeEitherSync} import cats.free.FreeT import cats.laws.discipline.arbitrary._ -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class IorTFreeSyncSpec - extends Specification - with Discipline - with BaseSpec +class IorTFreeSyncSuite + extends DisciplineSuite + with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/IorTPureConcSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/IorTPureConcSuite.scala similarity index 89% rename from laws/shared/src/test/scala/cats/effect/laws/IorTPureConcSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/IorTPureConcSuite.scala index 7d66ecff8a..10b0036353 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/IorTPureConcSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/IorTPureConcSuite.scala @@ -24,12 +24,12 @@ import cats.effect.kernel.testkit.pure._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Prop -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class IorTPureConcSpec extends Specification with Discipline with BaseSpec { +import munit.DisciplineSuite + +class IorTPureConcSuite extends DisciplineSuite with BaseSuite { import PureConcGenerators._ import OutcomeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/KleisliFreeSyncSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/KleisliFreeSyncSuite.scala similarity index 88% rename from laws/shared/src/test/scala/cats/effect/laws/KleisliFreeSyncSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/KleisliFreeSyncSuite.scala index 906277b7b0..8e000bbc21 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/KleisliFreeSyncSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/KleisliFreeSyncSuite.scala @@ -26,13 +26,11 @@ import cats.laws.discipline.MiniInt import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class KleisliFreeSyncSpec - extends Specification - with Discipline - with BaseSpec +class KleisliFreeSyncSuite + extends DisciplineSuite + with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/KleisliPureConcSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/KleisliPureConcSuite.scala similarity index 93% rename from laws/shared/src/test/scala/cats/effect/laws/KleisliPureConcSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/KleisliPureConcSuite.scala index 40af10706b..6421436baa 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/KleisliPureConcSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/KleisliPureConcSuite.scala @@ -26,16 +26,14 @@ import cats.effect.kernel.testkit.pure._ import cats.laws.discipline.{arbitrary, MiniInt} import org.scalacheck.{Arbitrary, Cogen, Prop} -import org.specs2.mutable._ -import org.specs2.scalacheck.Parameters -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class KleisliPureConcSpec - extends Specification - with Discipline - with BaseSpec +import munit.DisciplineSuite + +class KleisliPureConcSuite + extends DisciplineSuite + with BaseSuite with LowPriorityKleisliInstances { import PureConcGenerators._ import arbitrary.{catsLawsArbitraryForKleisli => _, _} @@ -78,12 +76,16 @@ class KleisliPureConcSpec : Cogen[Outcome[Kleisli[TimeT[PureConc[Int, *], *], MiniInt, *], Int, A]] = OutcomeGenerators.cogenOutcome[Kleisli[TimeT[PureConc[Int, *], *], MiniInt, *], Int, A] + override def scalaCheckTestParameters = + super.scalaCheckTestParameters + // we need to bound this a little tighter because these tests take FOREVER + .withMinSuccessfulTests(25) + checkAll( "Kleisli[PureConc]", GenTemporalTests[Kleisli[TimeT[PureConc[Int, *], *], MiniInt, *], Int] .temporal[Int, Int, Int](10.millis) - // we need to bound this a little tighter because these tests take FOREVER - )(Parameters(minTestsOk = 25)) + ) } //Push the priority of Kleisli instances down so we can explicitly summon more diff --git a/laws/shared/src/test/scala/cats/effect/laws/OptionTFreeSyncSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/OptionTFreeSyncSuite.scala similarity index 87% rename from laws/shared/src/test/scala/cats/effect/laws/OptionTFreeSyncSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/OptionTFreeSyncSuite.scala index 46dd37fb80..afdfa000c6 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/OptionTFreeSyncSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/OptionTFreeSyncSuite.scala @@ -24,10 +24,9 @@ import cats.effect.kernel.testkit.freeEval.{syncForFreeT, FreeEitherSync} import cats.free.FreeT import cats.laws.discipline.arbitrary._ -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class OptionTFreeSyncSpec extends Specification with Discipline with BaseSpec with FreeSyncEq { +class OptionTFreeSyncSuite extends DisciplineSuite with BaseSuite with FreeSyncEq { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSuite.scala similarity index 79% rename from laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSuite.scala index 6e67eb1f53..61337983f2 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSuite.scala @@ -27,12 +27,12 @@ import cats.effect.kernel.testkit.pure._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Prop -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class OptionTPureConcSpec extends Specification with Discipline with BaseSpec { +import munit.DisciplineSuite + +class OptionTPureConcSuite extends DisciplineSuite with BaseSuite { import PureConcGenerators._ import OutcomeGenerators._ @@ -46,26 +46,21 @@ class OptionTPureConcSpec extends Specification with Discipline with BaseSpec { bO => bO.flatten.fold(false)(_ => true) )) - "optiont bracket" should { - "forward completed zeros on to the handler" in { + test("optiont bracket forward completed zeros on to the handler") { var observed = false val test = OptionT.none[PureConc[Int, *], Unit] guaranteeCase { case Outcome.Succeeded(fa) => observed = true - OptionT(fa.value.map(_ must beNone).map(_ => None)) + OptionT(fa.value.map(assertEquals(_, None)).map(_ => None)) case _ => Applicative[OptionT[PureConc[Int, *], *]].unit } - pure.run(test.value) must beLike { - case Outcome.Succeeded(Some(None)) => ok - case _ => ko - } + assert(pure.run(test.value) === Outcome.Succeeded(Some(Option.empty[Unit]))) - observed must beTrue - } + assert(observed) } checkAll( diff --git a/laws/shared/src/test/scala/cats/effect/laws/PureConcSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/PureConcSuite.scala similarity index 63% rename from laws/shared/src/test/scala/cats/effect/laws/PureConcSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/PureConcSuite.scala index 5b367f4037..e5f205ddc6 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/PureConcSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/PureConcSuite.scala @@ -23,19 +23,19 @@ import cats.effect.kernel.testkit.pure._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Prop -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class PureConcSpec extends Specification with Discipline with BaseSpec { +import munit.DisciplineSuite + +class PureConcSuite extends DisciplineSuite with BaseSuite { import PureConcGenerators._ import OutcomeGenerators._ implicit def exec(fb: TimeT[PureConc[Int, *], Boolean]): Prop = Prop(pure.run(TimeT.run(fb)).fold(false, _ => false, _.getOrElse(false))) - "parallel utilities" should { + { import cats.effect.kernel.{GenConcurrent, Outcome} import cats.effect.kernel.implicits._ import cats.syntax.all._ @@ -43,35 +43,37 @@ class PureConcSpec extends Specification with Discipline with BaseSpec { type F[A] = PureConc[Int, A] val F = GenConcurrent[F] - "short-circuit on error" in { - pure.run((F.never[Unit], F.raiseError[Unit](42)).parTupled) mustEqual Outcome.Errored(42) - pure.run((F.raiseError[Unit](42), F.never[Unit]).parTupled) mustEqual Outcome.Errored(42) + test("short-circuit on error") { + assert( + pure.run((F.never[Unit], F.raiseError[Unit](42)).parTupled) === Outcome.Errored(42)) + assertEquals( + pure.run((F.raiseError[Unit](42), F.never[Unit]).parTupled), + Outcome.Errored[Option, Int, (Unit, Unit)](42)) } - "short-circuit on canceled" in { - pure.run((F.never[Unit], F.canceled).parTupled.start.flatMap(_.join)) mustEqual Outcome - .Succeeded(Some(Outcome.canceled[F, Nothing, Unit])) - pure.run((F.canceled, F.never[Unit]).parTupled.start.flatMap(_.join)) mustEqual Outcome - .Succeeded(Some(Outcome.canceled[F, Nothing, Unit])) + test("short-circuit on canceled") { + assert( + pure.run((F.never[Unit], F.canceled).parTupled.start.flatMap(_.join)) === Outcome + .Succeeded(Some(Outcome.canceled[F, Int, (Unit, Unit)]))) + assert( + pure.run((F.canceled, F.never[Unit]).parTupled.start.flatMap(_.join)) === Outcome + .Succeeded(Some(Outcome.canceled[F, Int, (Unit, Unit)]))) } - "not run forever on chained product" in { + test("not run forever on chained product") { import cats.effect.kernel.Par.ParallelF val fa: F[String] = F.pure("a") val fb: F[String] = F.pure("b") val fc: F[Unit] = F.raiseError[Unit](42) - pure.run( - ParallelF.value( - ParallelF(fa).product(ParallelF(fb)).product(ParallelF(fc)))) mustEqual Outcome - .Errored(42) + assert(pure.run(ParallelF.value( + ParallelF(fa).product(ParallelF(fb)).product(ParallelF(fc)))) === Outcome.Errored(42)) } - "ignore unmasking in finalizers" in { + test("ignore unmasking in finalizers") { val fa = F.uncancelable { poll => F.onCancel(poll(F.unit), poll(F.unit)) } pure.run(fa.start.flatMap(_.cancel)) - ok } } diff --git a/laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTFreeSyncSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTFreeSyncSuite.scala similarity index 81% rename from laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTFreeSyncSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTFreeSyncSuite.scala index 79401a66be..bbbf6ec587 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTFreeSyncSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTFreeSyncSuite.scala @@ -25,23 +25,20 @@ import cats.free.FreeT import cats.laws.discipline.MiniInt import cats.laws.discipline.arbitrary._ -import org.specs2.mutable._ -import org.specs2.scalacheck._ -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class ReaderWriterStateTFreeSyncSpec - extends Specification - with Discipline - with BaseSpec +class ReaderWriterStateTFreeSyncSuite + extends DisciplineSuite + with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ - implicit val params: Parameters = + override def scalaCheckTestParameters = if (cats.platform.Platform.isNative) - Parameters(minTestsOk = 5) + super.scalaCheckTestParameters.withMinSuccessfulTests(5) else - Parameters(minTestsOk = 100) + super.scalaCheckTestParameters implicit val scala_2_12_is_buggy : Eq[FreeT[Eval, Either[Throwable, *], Either[Int, Either[Throwable, Int]]]] = diff --git a/laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTPureConcSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTPureConcSuite.scala similarity index 85% rename from laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTPureConcSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTPureConcSuite.scala index 821b581950..8e92d3308f 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTPureConcSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/ReaderWriterStateTPureConcSuite.scala @@ -25,21 +25,22 @@ import cats.laws.discipline.MiniInt import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ -import org.specs2.mutable._ -import org.specs2.scalacheck.Parameters -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class ReaderWriterStateTPureConcSpec extends Specification with Discipline with BaseSpec { +class ReaderWriterStateTPureConcSuite extends DisciplineSuite with BaseSuite { import PureConcGenerators._ implicit def rwstEq[F[_]: Monad, E, L, S, A]( implicit ev: Eq[(E, S) => F[(L, S, A)]]): Eq[ReaderWriterStateT[F, E, L, S, A]] = Eq.by[ReaderWriterStateT[F, E, L, S, A], (E, S) => F[(L, S, A)]](_.run) + override def scalaCheckTestParameters = + // we need to bound this a little tighter because these tests take FOREVER, especially on scalajs + super.scalaCheckTestParameters.withMinSuccessfulTests(1) + checkAll( "ReaderWriterStateT[PureConc]", MonadCancelTests[ReaderWriterStateT[PureConc[Int, *], MiniInt, Int, MiniInt, *], Int] .monadCancel[Int, Int, Int] - // we need to bound this a little tighter because these tests take FOREVER, especially on scalajs - )(Parameters(minTestsOk = 1)) + ) } diff --git a/laws/shared/src/test/scala/cats/effect/laws/ResourcePureConcSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/ResourcePureConcSuite.scala similarity index 89% rename from laws/shared/src/test/scala/cats/effect/laws/ResourcePureConcSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/ResourcePureConcSuite.scala index 6a676f59e3..f031e2dd8f 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/ResourcePureConcSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/ResourcePureConcSuite.scala @@ -23,13 +23,12 @@ import cats.laws.discipline.arbitrary._ import cats.syntax.all._ import org.scalacheck.{Cogen, Prop} -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline -class ResourcePureConcSpec - extends Specification - with Discipline - with BaseSpec +import munit.DisciplineSuite + +class ResourcePureConcSuite + extends DisciplineSuite + with BaseSuite with TestInstances { import PureConcGenerators._ import OutcomeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/StateTFreeSyncSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/StateTFreeSyncSuite.scala similarity index 88% rename from laws/shared/src/test/scala/cats/effect/laws/StateTFreeSyncSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/StateTFreeSyncSuite.scala index bddaa5cf4d..5dee4c690a 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/StateTFreeSyncSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/StateTFreeSyncSuite.scala @@ -25,13 +25,11 @@ import cats.free.FreeT import cats.laws.discipline.MiniInt import cats.laws.discipline.arbitrary._ -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class StateTFreeSyncSpec - extends Specification - with Discipline - with BaseSpec +class StateTFreeSyncSuite + extends DisciplineSuite + with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/StateTPureConcSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/StateTPureConcSuite.scala similarity index 78% rename from laws/shared/src/test/scala/cats/effect/laws/StateTPureConcSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/StateTPureConcSuite.scala index 0b393219ee..f7b84a9bc3 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/StateTPureConcSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/StateTPureConcSuite.scala @@ -25,20 +25,22 @@ import cats.laws.discipline.MiniInt import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ -import org.specs2.mutable._ -import org.specs2.scalacheck.Parameters -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class StateTPureConcSpec extends Specification with Discipline with BaseSpec { +class StateTPureConcSuite extends DisciplineSuite with BaseSuite { import PureConcGenerators._ implicit def stateTEq[F[_]: FlatMap, S, A]( implicit ev: Eq[S => F[(S, A)]]): Eq[StateT[F, S, A]] = Eq.by[StateT[F, S, A], S => F[(S, A)]](_.run) + override def scalaCheckTestParameters = + super.scalaCheckTestParameters.withMinSuccessfulTests(25) + + // override def scalaCheckInitialSeed = "Ky43MND8m5h-10MZTckMFFAW6ea2pXWkFDE2A7ddtML=" + checkAll( "StateT[PureConc]", MonadCancelTests[StateT[PureConc[Int, *], MiniInt, *], Int].monadCancel[Int, Int, Int] - )(Parameters(minTestsOk = - 25 /*, seed = Some(Seed.fromBase64("Ky43MND8m5h-10MZTckMFFAW6ea2pXWkFDE2A7ddtML=").get*/ )) + ) } diff --git a/laws/shared/src/test/scala/cats/effect/laws/WriterTFreeSyncSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/WriterTFreeSyncSuite.scala similarity index 88% rename from laws/shared/src/test/scala/cats/effect/laws/WriterTFreeSyncSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/WriterTFreeSyncSuite.scala index de9298e9bc..f365552c08 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/WriterTFreeSyncSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/WriterTFreeSyncSuite.scala @@ -24,13 +24,11 @@ import cats.effect.kernel.testkit.freeEval.{syncForFreeT, FreeEitherSync} import cats.free.FreeT import cats.laws.discipline.arbitrary._ -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class WriterTFreeSyncSpec - extends Specification - with Discipline - with BaseSpec +class WriterTFreeSyncSuite + extends DisciplineSuite + with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/WriterTPureConcSpec.scala b/laws/shared/src/test/scala/cats/effect/laws/WriterTPureConcSuite.scala similarity index 89% rename from laws/shared/src/test/scala/cats/effect/laws/WriterTPureConcSpec.scala rename to laws/shared/src/test/scala/cats/effect/laws/WriterTPureConcSuite.scala index 7658826d3b..bd2cd7b6f7 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/WriterTPureConcSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/WriterTPureConcSuite.scala @@ -24,12 +24,12 @@ import cats.effect.kernel.testkit.pure._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Prop -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class WriterTPureConcSpec extends Specification with Discipline with BaseSpec { +import munit.DisciplineSuite + +class WriterTPureConcSuite extends DisciplineSuite with BaseSuite { import PureConcGenerators._ import OutcomeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/testkit/TimeTSpec.scala b/laws/shared/src/test/scala/cats/effect/testkit/TimeTSuite.scala similarity index 92% rename from laws/shared/src/test/scala/cats/effect/testkit/TimeTSpec.scala rename to laws/shared/src/test/scala/cats/effect/testkit/TimeTSuite.scala index d3d8418298..143b253c47 100644 --- a/laws/shared/src/test/scala/cats/effect/testkit/TimeTSpec.scala +++ b/laws/shared/src/test/scala/cats/effect/testkit/TimeTSuite.scala @@ -23,20 +23,20 @@ import cats.effect.laws.GenTemporalTests import cats.laws.discipline.arbitrary._ import org.scalacheck.{Arbitrary, Cogen, Gen, Prop} -import org.specs2.mutable._ -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ import java.util.concurrent.TimeUnit +import munit.DisciplineSuite + private[testkit] trait LowPriorityInstances { implicit def eqTimeT[F[_], A](implicit FA: Eq[F[A]]): Eq[TimeT[F, A]] = Eq.by(TimeT.run(_)) } -class TimeTSpec extends Specification with Discipline with LowPriorityInstances { +class TimeTSuite extends DisciplineSuite with LowPriorityInstances { import PureConcGenerators._ import OutcomeGenerators._ From dff642f4132e1d70055dac894c9fe72efbb36f26 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 21 Aug 2023 23:23:09 +0000 Subject: [PATCH 02/20] Formatting --- .../effect/laws/EitherTFreeSyncSuite.scala | 5 +---- .../cats/effect/laws/FreeSyncSuite.scala | 5 +---- .../cats/effect/laws/IorTFreeSyncSuite.scala | 5 +---- .../effect/laws/KleisliFreeSyncSuite.scala | 5 +---- .../effect/laws/KleisliPureConcSuite.scala | 5 +++-- .../effect/laws/OptionTPureConcSuite.scala | 20 +++++++++---------- .../effect/laws/ResourcePureConcSuite.scala | 5 +---- .../effect/laws/StateTFreeSyncSuite.scala | 5 +---- .../effect/laws/WriterTFreeSyncSuite.scala | 5 +---- 9 files changed, 20 insertions(+), 40 deletions(-) diff --git a/laws/shared/src/test/scala/cats/effect/laws/EitherTFreeSyncSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/EitherTFreeSyncSuite.scala index b25283ca98..8ab8a66dc9 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/EitherTFreeSyncSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/EitherTFreeSyncSuite.scala @@ -26,10 +26,7 @@ import cats.laws.discipline.arbitrary._ import munit.DisciplineSuite -class EitherTFreeSyncSuite - extends DisciplineSuite - with BaseSuite - with LowPriorityImplicits { +class EitherTFreeSyncSuite extends DisciplineSuite with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/FreeSyncSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/FreeSyncSuite.scala index 4d21d26c59..cfd23db2cd 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/FreeSyncSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/FreeSyncSuite.scala @@ -25,10 +25,7 @@ import cats.laws.discipline.arbitrary._ import munit.DisciplineSuite -class FreeSyncSuite - extends DisciplineSuite - with BaseSuite - with LowPriorityImplicits { +class FreeSyncSuite extends DisciplineSuite with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/IorTFreeSyncSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/IorTFreeSyncSuite.scala index 048fdc3017..66c2170e59 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/IorTFreeSyncSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/IorTFreeSyncSuite.scala @@ -26,10 +26,7 @@ import cats.laws.discipline.arbitrary._ import munit.DisciplineSuite -class IorTFreeSyncSuite - extends DisciplineSuite - with BaseSuite - with LowPriorityImplicits { +class IorTFreeSyncSuite extends DisciplineSuite with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/KleisliFreeSyncSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/KleisliFreeSyncSuite.scala index 8e000bbc21..b5ffd29bbd 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/KleisliFreeSyncSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/KleisliFreeSyncSuite.scala @@ -28,10 +28,7 @@ import cats.laws.discipline.eq._ import munit.DisciplineSuite -class KleisliFreeSyncSuite - extends DisciplineSuite - with BaseSuite - with LowPriorityImplicits { +class KleisliFreeSyncSuite extends DisciplineSuite with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/KleisliPureConcSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/KleisliPureConcSuite.scala index 6421436baa..c66c107381 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/KleisliPureConcSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/KleisliPureConcSuite.scala @@ -77,8 +77,9 @@ class KleisliPureConcSuite OutcomeGenerators.cogenOutcome[Kleisli[TimeT[PureConc[Int, *], *], MiniInt, *], Int, A] override def scalaCheckTestParameters = - super.scalaCheckTestParameters - // we need to bound this a little tighter because these tests take FOREVER + super + .scalaCheckTestParameters + // we need to bound this a little tighter because these tests take FOREVER .withMinSuccessfulTests(25) checkAll( diff --git a/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSuite.scala index 61337983f2..db49ee6aff 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSuite.scala @@ -46,21 +46,21 @@ class OptionTPureConcSuite extends DisciplineSuite with BaseSuite { bO => bO.flatten.fold(false)(_ => true) )) - test("optiont bracket forward completed zeros on to the handler") { - var observed = false + test("optiont bracket forward completed zeros on to the handler") { + var observed = false - val test = OptionT.none[PureConc[Int, *], Unit] guaranteeCase { - case Outcome.Succeeded(fa) => - observed = true + val test = OptionT.none[PureConc[Int, *], Unit] guaranteeCase { + case Outcome.Succeeded(fa) => + observed = true - OptionT(fa.value.map(assertEquals(_, None)).map(_ => None)) + OptionT(fa.value.map(assertEquals(_, None)).map(_ => None)) - case _ => Applicative[OptionT[PureConc[Int, *], *]].unit - } + case _ => Applicative[OptionT[PureConc[Int, *], *]].unit + } - assert(pure.run(test.value) === Outcome.Succeeded(Some(Option.empty[Unit]))) + assert(pure.run(test.value) === Outcome.Succeeded(Some(Option.empty[Unit]))) - assert(observed) + assert(observed) } checkAll( diff --git a/laws/shared/src/test/scala/cats/effect/laws/ResourcePureConcSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/ResourcePureConcSuite.scala index f031e2dd8f..4729e65f32 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/ResourcePureConcSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/ResourcePureConcSuite.scala @@ -26,10 +26,7 @@ import org.scalacheck.{Cogen, Prop} import munit.DisciplineSuite -class ResourcePureConcSuite - extends DisciplineSuite - with BaseSuite - with TestInstances { +class ResourcePureConcSuite extends DisciplineSuite with BaseSuite with TestInstances { import PureConcGenerators._ import OutcomeGenerators._ import pure._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/StateTFreeSyncSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/StateTFreeSyncSuite.scala index 5dee4c690a..4e5b4301d4 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/StateTFreeSyncSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/StateTFreeSyncSuite.scala @@ -27,10 +27,7 @@ import cats.laws.discipline.arbitrary._ import munit.DisciplineSuite -class StateTFreeSyncSuite - extends DisciplineSuite - with BaseSuite - with LowPriorityImplicits { +class StateTFreeSyncSuite extends DisciplineSuite with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ diff --git a/laws/shared/src/test/scala/cats/effect/laws/WriterTFreeSyncSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/WriterTFreeSyncSuite.scala index f365552c08..8d420b6d37 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/WriterTFreeSyncSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/WriterTFreeSyncSuite.scala @@ -26,10 +26,7 @@ import cats.laws.discipline.arbitrary._ import munit.DisciplineSuite -class WriterTFreeSyncSuite - extends DisciplineSuite - with BaseSuite - with LowPriorityImplicits { +class WriterTFreeSyncSuite extends DisciplineSuite with BaseSuite with LowPriorityImplicits { import FreeSyncGenerators._ import SyncTypeGenerators._ From e1b4f9e2232c464337066572c108ddcf73055bdc Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 27 Dec 2024 08:58:53 +0200 Subject: [PATCH 03/20] add munit to tests module --- build.sbt | 15 +- .../cats/effect/kernel/OutcomeSuite.scala | 2 - .../cats/effect/laws/GenTemporalSuite.scala | 154 ++++++++++-------- .../effect/laws/OptionTPureConcSuite.scala | 4 +- .../scala/cats/effect/std/SyntaxSpec.scala | 6 +- .../scala/cats/effect/RunnersPlatform.scala | 31 ++++ .../src/test/scala/cats/effect/BaseSpec.scala | 2 + .../scala/cats/effect/EitherTIOSpec.scala | 69 ++++---- ...ExitCodeSpec.scala => ExitCodeSuite.scala} | 6 +- .../src/test/scala/cats/effect/Runners.scala | 26 ++- 10 files changed, 188 insertions(+), 127 deletions(-) rename tests/shared/src/test/scala/cats/effect/{ExitCodeSpec.scala => ExitCodeSuite.scala} (85%) diff --git a/build.sbt b/build.sbt index c6277b1ab4..1c6bf4c4ce 100644 --- a/build.sbt +++ b/build.sbt @@ -304,9 +304,9 @@ val CatsVersion = "2.11.0" val CatsMtlVersion = "1.3.1" val Specs2Version = "4.20.5" val ScalaCheckVersion = "1.17.1" -val DisciplineVersion = "1.4.0" val CoopVersion = "1.2.0" -val MUnitVersion = "1.0.0-M8" +val MUnitVersion = "1.0.0-M11" +val MUnitScalaCheckVersion = "1.0.0-M11" val DisciplineMUnitVersion = "2.0.0-M3" val MacrotaskExecutorVersion = "1.1.1" @@ -908,7 +908,6 @@ lazy val testkit = crossProject(JSPlatform, JVMPlatform, NativePlatform) name := "cats-effect-testkit", libraryDependencies ++= Seq( "org.scalacheck" %%% "scalacheck" % ScalaCheckVersion, - "org.specs2" %%% "specs2-core" % Specs2Version % Test ) ) @@ -923,8 +922,13 @@ lazy val tests: CrossProject = crossProject(JSPlatform, JVMPlatform, NativePlatf name := "cats-effect-tests", libraryDependencies ++= Seq( "org.scalacheck" %%% "scalacheck" % ScalaCheckVersion, + "org.specs2" %%% "specs2-scalacheck" % Specs2Version % Test, - "org.typelevel" %%% "discipline-specs2" % DisciplineVersion % Test, + "org.typelevel" %%% "discipline-specs2" % "1.4.0" % Test, + + "org.scalameta" %%% "munit" % MUnitVersion % Test, + "org.scalameta" %%% "munit-scalacheck" % MUnitScalaCheckVersion % Test, + "org.typelevel" %%% "discipline-munit" % DisciplineMUnitVersion % Test, "org.typelevel" %%% "cats-kernel-laws" % CatsVersion % Test, "org.typelevel" %%% "cats-mtl-laws" % CatsMtlVersion % Test ), @@ -996,8 +1000,7 @@ lazy val std = crossProject(JSPlatform, JVMPlatform, NativePlatform) .settings( name := "cats-effect-std", libraryDependencies ++= Seq( - "org.scalacheck" %%% "scalacheck" % ScalaCheckVersion % Test, - "org.specs2" %%% "specs2-scalacheck" % Specs2Version % Test + "org.scalameta" %%% "munit" % MUnitVersion % Test, ), mimaBinaryIssueFilters ++= { if (tlIsScala3.value) { diff --git a/laws/shared/src/test/scala/cats/effect/kernel/OutcomeSuite.scala b/laws/shared/src/test/scala/cats/effect/kernel/OutcomeSuite.scala index b8f1eb05d6..75dbf03f6f 100644 --- a/laws/shared/src/test/scala/cats/effect/kernel/OutcomeSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/kernel/OutcomeSuite.scala @@ -21,8 +21,6 @@ import cats.{Eq, Eval, Id, MonadError} import cats.effect.kernel.testkit.OutcomeGenerators import cats.laws.discipline.{ApplicativeErrorTests, MonadErrorTests} -import org.scalacheck.{Arbitrary, Cogen} - import munit.DisciplineSuite class OutcomeSuite extends DisciplineSuite { diff --git a/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSuite.scala index b6838cbde5..9fd52b890d 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSuite.scala @@ -24,11 +24,12 @@ import cats.effect.kernel.testkit.pure._ import cats.syntax.all._ import scala.concurrent.duration._ - import munit.FunSuite -// import scala.concurrent.TimeoutException -class GenTemporalSuite extends FunSuite { outer => +import scala.concurrent.TimeoutException + +class GenTemporalSuite extends FunSuite { + outer => type F[A] = PureConc[Throwable, A] @@ -41,98 +42,107 @@ class GenTemporalSuite extends FunSuite { outer => val fa = F.pure(true) assertEquals(F.timeout(fa, Duration.Inf), fa) } - "temporal" should { - "timeout" should { - "return identity when infinite duration" in { - val fa = F.pure(true) - F.timeout(fa, Duration.Inf) mustEqual fa - } - "succeed on a fast action" in { - val fa: TimeT[F, Boolean] = F.pure(true) - val op = F.timeout(fa, Duration.Zero) + test("timeout should return identity when infinite duration") { + val fa = F.pure(true) + assertEquals(F.timeout(fa, Duration.Inf), fa) + } - run(TimeT.run(op)) mustEqual Outcome.Succeeded(Some(true)) - } + test("timeout should succeed on a fast action") { + val fa: TimeT[F, Boolean] = F.pure(true) + val op = F.timeout(fa, Duration.Zero) - "error out on a slow action" in { - val fa: TimeT[F, Boolean] = F.never *> F.pure(true) - val op = F.timeout(fa, Duration.Zero) + assertEquals( + run(TimeT.run(op)), + Outcome.Succeeded(Some(true)): Outcome[Option, Throwable, Boolean]) + } - run(TimeT.run(op)) must beLike { - case Outcome.Errored(e) => e must haveClass[TimeoutException] - } - } + test("timeout should error out on a slow action") { + val fa: TimeT[F, Boolean] = F.never *> F.pure(true) + val op = F.timeout(fa, Duration.Zero) - "propagate successful outcome of uncancelable action" in { - val fa = F.uncancelable(_ => F.sleep(50.millis) *> F.pure(true)) - val op = F.timeout(fa, Duration.Zero) + run(TimeT.run(op)) match { + case Outcome.Errored(e) => assert(e.isInstanceOf[TimeoutException]) + case other => fail(s"Expected Outcome.Errored, got $other") + } + } - run(TimeT.run(op)) mustEqual Outcome.Succeeded(Some(true)) - } + test("timeout should propagate successful outcome of uncancelable action") { + val fa = F.uncancelable(_ => F.sleep(50.millis) *> F.pure(true)) + val op = F.timeout(fa, Duration.Zero) - "propagate errors from uncancelable action" in { - val fa = F.uncancelable { _ => - F.sleep(50.millis) *> F.raiseError(new RuntimeException("fa failed")) *> F.pure(true) - } - val op = F.timeout(fa, Duration.Zero) + assertEquals( + run(TimeT.run(op)), + Outcome.Succeeded(Some(true)): Outcome[Option, Throwable, Boolean]) + } - run(TimeT.run(op)) must beLike { - case Outcome.Errored(e: RuntimeException) => e.getMessage mustEqual "fa failed" - } - } + test("timeout should propagate errors from uncancelable action") { + val fa = F.uncancelable { _ => + F.sleep(50.millis) *> F.raiseError(new RuntimeException("fa failed")) *> F.pure(true) } + val op = F.timeout(fa, Duration.Zero) + + run(TimeT.run(op)) match { + case Outcome.Errored(e: RuntimeException) => assertEquals(e.getMessage, "fa failed") + case other => fail(s"Expected Outcome.Errored, got $other") + } + } + + test("timeoutTo should return identity when infinite duration") { + val fa: TimeT[F, Boolean] = F.pure(true) + val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) + assertEquals(F.timeoutTo(fa, Duration.Inf, fallback), fa) + } test("timeoutTo should return identity when infinite duration") { val fa: TimeT[F, Boolean] = F.pure(true) val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) assertEquals(F.timeoutTo(fa, Duration.Inf, fallback), fa) } - "timeoutTo" should { - "return identity when infinite duration" in { - val fa: TimeT[F, Boolean] = F.pure(true) - val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) - F.timeoutTo(fa, Duration.Inf, fallback) mustEqual fa - } - "succeed on a fast action" in { - val fa: TimeT[F, Boolean] = F.pure(true) - val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) - val op = F.timeoutTo(fa, Duration.Zero, fallback) + test("timeoutTo should succeed on a fast action") { + val fa: TimeT[F, Boolean] = F.pure(true) + val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) + val op = F.timeoutTo(fa, Duration.Zero, fallback) - run(TimeT.run(op)) mustEqual Outcome.Succeeded(Some(true)) - } + assertEquals( + run(TimeT.run(op)), + Outcome.Succeeded(Some(true)): Outcome[Option, Throwable, Boolean]) + } - "error out on a slow action" in { - val fa: TimeT[F, Boolean] = F.never *> F.pure(true) - val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) - val op = F.timeoutTo(fa, Duration.Zero, fallback) + test("timeoutTo should error out on a slow action") { + val fa: TimeT[F, Boolean] = F.never *> F.pure(true) + val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) + val op = F.timeoutTo(fa, Duration.Zero, fallback) - run(TimeT.run(op)) must beLike { - case Outcome.Errored(e) => e must haveClass[RuntimeException] - } - } + run(TimeT.run(op)) match { + case Outcome.Errored(e) => assert(e.isInstanceOf[RuntimeException]) + case other => fail(s"Expected OutcomeErrored, got $other") + } + } - "propagate successful outcome of uncancelable action" in { - val fa = F.uncancelable(_ => F.sleep(50.millis) *> F.pure(true)) - val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) - val op = F.timeoutTo(fa, Duration.Zero, fallback) + test("timeoutTo should propagate successful outcome of uncancelable action") { + val fa = F.uncancelable(_ => F.sleep(50.millis) *> F.pure(true)) + val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) + val op = F.timeoutTo(fa, Duration.Zero, fallback) - run(TimeT.run(op)) mustEqual Outcome.Succeeded(Some(true)) - } + assertEquals( + run(TimeT.run(op)), + Outcome.Succeeded(Some(true)): Outcome[Option, Throwable, Boolean]) + } - "propagate errors from uncancelable action" in { - val fa = F.uncancelable { _ => - F.sleep(50.millis) *> F.raiseError(new RuntimeException("fa failed")) *> F.pure(true) - } - val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) - val op = F.timeoutTo(fa, Duration.Zero, fallback) + test("timeoutTo should propagate errors from uncancelable action") { + val fa = F.uncancelable { _ => + F.sleep(50.millis) *> F.raiseError(new RuntimeException("fa failed")) *> F.pure(true) + } + val fallback: TimeT[F, Boolean] = F.raiseError(new RuntimeException) + val op = F.timeoutTo(fa, Duration.Zero, fallback) - run(TimeT.run(op)) must beLike { - case Outcome.Errored(e: RuntimeException) => e.getMessage mustEqual "fa failed" - } - } + run(TimeT.run(op)) match { + case Outcome.Errored(e: RuntimeException) => assertEquals(e.getMessage, "fa failed") + case other => fail(s"Expected Outcome.Errored, got $other") } + } test("timeoutAndForget should return identity when infinite duration") { val fa: TimeT[F, Boolean] = F.pure(true) @@ -154,7 +164,7 @@ class GenTemporalSuite extends FunSuite { outer => "use fallback" in { val op: TimeT[F, Boolean] = F.timeoutTo(loop >> F.pure(false), 5.millis, F.pure(true)) - run(TimeT.run(op)) mustEqual Succeeded(Some(true)) + run(TimeT.run(op)) , Succeeded(Some(true)) }.pendingUntilFixed } }*/ diff --git a/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSuite.scala index db49ee6aff..08e909a4f7 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/OptionTPureConcSuite.scala @@ -58,7 +58,9 @@ class OptionTPureConcSuite extends DisciplineSuite with BaseSuite { case _ => Applicative[OptionT[PureConc[Int, *], *]].unit } - assert(pure.run(test.value) === Outcome.Succeeded(Some(Option.empty[Unit]))) + assertEquals( + pure.run(test.value), + Outcome.Succeeded(Some(Option.empty[Unit])): Outcome[Option, Int, Option[Unit]]) assert(observed) } diff --git a/std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala b/std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala index 81d297c5c0..068fe82915 100644 --- a/std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala +++ b/std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala @@ -17,11 +17,11 @@ package cats.effect.std import cats.effect.kernel.{Async, Concurrent, Deferred, GenConcurrent, Ref, Sync} +import munit.FunSuite -import org.specs2.mutable.Specification +class SyntaxSpec extends FunSuite { -class SyntaxSpec extends Specification { - "concurrent data structure construction syntax" >> ok + test("concurrent data structure construction syntax") {} def async[F[_]: Async] = { Ref.of[F, String]("foo") diff --git a/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala b/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala index 19bb62b57d..adfe63b3ea 100644 --- a/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala +++ b/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala @@ -50,3 +50,34 @@ trait RunnersPlatform extends BeforeAfterAll { def afterAll(): Unit = runtime().shutdown() } + +trait MUnitRunnersPlatform { self: munit.Suite => + + private[this] var runtime0: IORuntime = _ + + protected def runtime(): IORuntime = runtime0 + + override def beforeAll(): Unit = { + val (blocking, blockDown) = + IORuntime.createDefaultBlockingExecutionContext(threadPrefix = + s"io-blocking-${getClass.getName}") + val (compute, poller, compDown) = + IORuntime.createWorkStealingComputeThreadPool( + threadPrefix = s"io-compute-${getClass.getName}", + blockerThreadPrefix = s"io-blocker-${getClass.getName}") + + runtime0 = IORuntime( + compute, + blocking, + compute, + List(poller), + { () => + compDown() + blockDown() + }, + IORuntimeConfig() + ) + } + + override def afterAll(): Unit = runtime().shutdown() +} \ No newline at end of file diff --git a/tests/shared/src/test/scala/cats/effect/BaseSpec.scala b/tests/shared/src/test/scala/cats/effect/BaseSpec.scala index 9863b51082..7b80b43258 100644 --- a/tests/shared/src/test/scala/cats/effect/BaseSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/BaseSpec.scala @@ -19,3 +19,5 @@ package cats.effect import org.specs2.mutable.Specification trait BaseSpec extends Specification with Runners + +trait BaseSuite extends munit.FunSuite with MUnitRunners \ No newline at end of file diff --git a/tests/shared/src/test/scala/cats/effect/EitherTIOSpec.scala b/tests/shared/src/test/scala/cats/effect/EitherTIOSpec.scala index 44233acc0d..f2f0640372 100644 --- a/tests/shared/src/test/scala/cats/effect/EitherTIOSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/EitherTIOSpec.scala @@ -22,53 +22,44 @@ import cats.effect.laws.AsyncTests import cats.effect.syntax.all._ import cats.laws.discipline.arbitrary._ import cats.syntax.all._ - +import munit.DisciplineSuite import org.scalacheck.Prop -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class EitherTIOSpec extends BaseSpec with Discipline { - - // we just need this because of the laws testing, since the prop runs can interfere with each other - sequential - - "EitherT" should { - "execute finalizers" in ticked { implicit ticker => - type F[A] = EitherT[IO, String, A] +class EitherTIOSpec extends BaseSuite with DisciplineSuite { - val test = for { - gate1 <- Deferred[F, Unit] - gate2 <- Deferred[F, Unit] - _ <- EitherT.leftT[IO, Unit]("boom").guarantee(gate1.complete(()).void).start - _ <- EitherT.rightT[IO, String](()).guarantee(gate2.complete(()).void).start - _ <- gate1.get - _ <- gate2.get - } yield () + ticked("EitherT should execute finalizers") { implicit ticker => + type F[A] = EitherT[IO, String, A] - test.value must completeAs(Right(())) - } + val test = for { + gate1 <- Deferred[F, Unit] + gate2 <- Deferred[F, Unit] + _ <- EitherT.leftT[IO, Unit]("boom").guarantee(gate1.complete(()).void).start + _ <- EitherT.rightT[IO, String](()).guarantee(gate2.complete(()).void).start + _ <- gate1.get + _ <- gate2.get + } yield () - "execute finalizers when doubly nested" in ticked { implicit ticker => - type F[A] = EitherT[OptionT[IO, *], String, A] - - val test = for { - gate1 <- Deferred[F, Unit] - gate2 <- Deferred[F, Unit] - gate3 <- Deferred[F, Unit] - _ <- EitherT - .leftT[OptionT[IO, *], Unit]("boom") - .guarantee(gate1.complete(()).void) - .start - _ <- EitherT.rightT[OptionT[IO, *], String](()).guarantee(gate2.complete(()).void).start - _ <- EitherT.liftF(OptionT.none[IO, Unit]).guarantee(gate3.complete(()).void).start - _ <- gate1.get - _ <- gate2.get - _ <- gate3.get - } yield () + assertCompleteAs(test.value, Right(())) + } - test.value.value must completeAs(Some(Right(()))) - } + ticked("EitherT should execute finalizers when doubly nested") { implicit ticker => + type F[A] = EitherT[OptionT[IO, *], String, A] + + val test = for { + gate1 <- Deferred[F, Unit] + gate2 <- Deferred[F, Unit] + gate3 <- Deferred[F, Unit] + _ <- EitherT.leftT[OptionT[IO, *], Unit]("boom").guarantee(gate1.complete(()).void).start + _ <- EitherT.rightT[OptionT[IO, *], String](()).guarantee(gate2.complete(()).void).start + _ <- EitherT.liftF(OptionT.none[IO, Unit]).guarantee(gate3.complete(()).void).start + _ <- gate1.get + _ <- gate2.get + _ <- gate3.get + } yield () + + assertCompleteAs(test.value.value, Some(Right(()))) } implicit def ordEitherTIOFD( diff --git a/tests/shared/src/test/scala/cats/effect/ExitCodeSpec.scala b/tests/shared/src/test/scala/cats/effect/ExitCodeSuite.scala similarity index 85% rename from tests/shared/src/test/scala/cats/effect/ExitCodeSpec.scala rename to tests/shared/src/test/scala/cats/effect/ExitCodeSuite.scala index 27d2d47745..d19bd03205 100644 --- a/tests/shared/src/test/scala/cats/effect/ExitCodeSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/ExitCodeSuite.scala @@ -16,11 +16,11 @@ package cats.effect -class ExitCodeSpec extends BaseSpec { +class ExitCodeSuite extends BaseSuite { - "ExitCode.unapply is exhaustive" >> { + test("ExitCode.unapply is exhaustive") { ExitCode(0) match { // if not, should be a fatal warning in CI - case ExitCode(_) => ok + case ExitCode(_) => assert(true) } } diff --git a/tests/shared/src/test/scala/cats/effect/Runners.scala b/tests/shared/src/test/scala/cats/effect/Runners.scala index 68213d0720..979d15c25d 100644 --- a/tests/shared/src/test/scala/cats/effect/Runners.scala +++ b/tests/shared/src/test/scala/cats/effect/Runners.scala @@ -20,7 +20,7 @@ import cats.{Eq, Show} import cats.effect.testkit.{TestContext, TestInstances} import cats.effect.unsafe.IORuntime import cats.syntax.all._ - +import munit.TestOptions import org.scalacheck.Gen import org.specs2.execute.AsResult import org.specs2.matcher.Matcher @@ -133,4 +133,28 @@ trait Runners extends SpecificationLike with TestInstances with RunnersPlatform } } +trait MUnitRunners extends TestInstances with MUnitRunnersPlatform { + self: munit.FunSuite => + + def executionTimeout: FiniteDuration = 20.seconds + + def ticked(options: TestOptions)(body: Ticker => Any)(implicit loc: munit.Location): Unit = + test(options)(body(Ticker(TestContext()))) + + def assertCompleteAs[A: Eq: Show](ioa: IO[A], expected: A)(implicit ticker: Ticker): Unit = + tickTo(ioa, Outcome.Succeeded(Some(expected))) + + /*def completeAsSync[A: Eq: Show](expected: A): Matcher[SyncIO[A]] = { (ioa: SyncIO[A]) => + val a = ioa.unsafeRunSync() + (a eqv expected, s"${a.show} !== ${expected.show}") + }*/ + + def tickTo[A: Eq: Show](ioa: IO[A], expected: Outcome[Option, Throwable, A])( + implicit ticker: Ticker): Unit = { + val oc = unsafeRun(ioa) + assert(oc eqv expected, s"${oc.show} !== ${expected.show}") + } + +} + class TestTimeoutException extends Exception From 0adbb6c80c94900b6a30adff45ce45e3fbe48113 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 27 Dec 2024 09:04:50 +0200 Subject: [PATCH 04/20] rename *Spec -> *Suite --- .../std/{ConsoleJSSpec.scala => ConsoleJSSuite.scala} | 2 +- ...torSpec.scala => BatchingMacrotaskExecutorSuite.scala} | 2 +- .../{JSArrayQueueSpec.scala => JSArrayQueueSuite.scala} | 2 +- .../unsafe/{SchedulerSpec.scala => SchedulerSuite.scala} | 2 +- ...pedHashtableSpec.scala => StripedHashtableSuite.scala} | 2 +- .../{ParasiticECSpec.scala => ParasiticECSuite.scala} | 2 +- .../{ResourceJVMSpec.scala => ResourceJVMSuite.scala} | 2 +- .../effect/{SelectorSpec.scala => SelectorSuite.scala} | 2 +- .../{AsyncPlatformSpec.scala => AsyncPlatformSuite.scala} | 2 +- .../std/{ConsoleJVMSpec.scala => ConsoleJVMSuite.scala} | 2 +- .../std/{DeferredJVMSpec.scala => DeferredJVMSuite.scala} | 0 .../{DispatcherJVMSpec.scala => DispatcherJVMSuite.scala} | 2 +- .../std/{MapRefJVMSpec.scala => MapRefJVMSuite.scala} | 2 +- ...BlockingStressSpec.scala => BlockingStressSuite.scala} | 2 +- .../{DrainBatchSpec.scala => DrainBatchSuite.scala} | 2 +- .../{FiberMonitorSpec.scala => FiberMonitorSuite.scala} | 2 +- ...erThreadParkSpec.scala => HelperThreadParkSuite.scala} | 2 +- .../unsafe/{IOLocalsSpec.scala => IOLocalsSuite.scala} | 2 +- .../unsafe/{SleepersSpec.scala => SleepersSuite.scala} | 2 +- ...pedHashtableSpec.scala => StripedHashtableSuite.scala} | 2 +- .../unsafe/{TimerHeapSpec.scala => TimerHeapSuite.scala} | 2 +- ...erThreadNameSpec.scala => WorkerThreadNameSuite.scala} | 2 +- ...orPollerSpec.scala => FileDescriptorPollerSuite.scala} | 2 +- .../unsafe/{SchedulerSpec.scala => SchedulerSuite.scala} | 2 +- .../{IOImplicitSpec.scala => IOImplicitSuite.scala} | 2 +- .../{IOParImplicitSpec.scala => IOParImplicitSuite.scala} | 2 +- .../scala/cats/effect/{BaseSpec.scala => BaseSuite.scala} | 0 .../{CallbackStackSpec.scala => CallbackStackSuite.scala} | 2 +- .../scala/cats/effect/{ContSpec.scala => ContSuite.scala} | 4 ++-- .../effect/{EitherTIOSpec.scala => EitherTIOSuite.scala} | 2 +- .../cats/effect/{IOFiberSpec.scala => IOFiberSuite.scala} | 2 +- .../cats/effect/{IOLocalSpec.scala => IOLocalSuite.scala} | 2 +- .../{IOMtlLocalSpec.scala => IOMtlLocalSuite.scala} | 2 +- .../cats/effect/{IOParSpec.scala => IOParSuite.scala} | 2 +- .../cats/effect/{IOPropSpec.scala => IOPropSuite.scala} | 2 +- .../scala/cats/effect/{IOSpec.scala => IOSuite.scala} | 2 +- .../cats/effect/{IorTIOSpec.scala => IorTIOSuite.scala} | 2 +- .../effect/{KleisliIOSpec.scala => KleisliIOSuite.scala} | 2 +- .../cats/effect/{MemoizeSpec.scala => MemoizeSuite.scala} | 2 +- .../effect/{OptionTIOSpec.scala => OptionTIOSuite.scala} | 2 +- .../effect/{ResourceSpec.scala => ResourceSuite.scala} | 2 +- .../effect/{StateTIOSpec.scala => StateTIOSuite.scala} | 2 +- .../cats/effect/{SyncIOSpec.scala => SyncIOSuite.scala} | 2 +- .../cats/effect/{ThunkSpec.scala => ThunkSuite.scala} | 2 +- .../effect/{WriterTIOSpec.scala => WriterTIOSuite.scala} | 2 +- .../effect/kernel/{AsyncSpec.scala => AsyncSuite.scala} | 2 +- .../kernel/{DeferredSpec.scala => DeferredSuite.scala} | 2 +- ...finementSpec.scala => DerivationRefinementSuite.scala} | 2 +- .../kernel/{LensRefSpec.scala => LensRefSuite.scala} | 2 +- .../{MiniSemaphoreSpec.scala => MiniSemaphoreSuite.scala} | 2 +- .../kernel/{ParallelFSpec.scala => ParallelFSuite.scala} | 2 +- .../cats/effect/kernel/{RefSpec.scala => RefSuite.scala} | 2 +- .../std/{AtomicCellSpec.scala => AtomicCellSuite.scala} | 2 +- .../{BackpressureSpec.scala => BackpressureSuite.scala} | 2 +- .../effect/std/{ConsoleSpec.scala => ConsoleSuite.scala} | 2 +- ...CountDownLatchSpec.scala => CountDownLatchSuite.scala} | 2 +- .../{CyclicBarrierSpec.scala => CyclicBarrierSuite.scala} | 2 +- .../effect/std/{DequeueSpec.scala => DequeueSuite.scala} | 4 ++-- .../std/{DispatcherSpec.scala => DispatcherSuite.scala} | 2 +- .../cats/effect/std/{EnvSpec.scala => EnvSuite.scala} | 2 +- .../effect/std/{HotswapSpec.scala => HotswapSuite.scala} | 2 +- .../effect/std/{MapRefSpec.scala => MapRefSuite.scala} | 2 +- .../cats/effect/std/{MutexSpec.scala => MutexSuite.scala} | 2 +- .../effect/std/{PQueueSpec.scala => PQueueSuite.scala} | 4 ++-- .../cats/effect/std/{QueueSpec.scala => QueueSuite.scala} | 8 ++++---- .../effect/std/{RandomSpec.scala => RandomSuite.scala} | 2 +- .../{SecureRandomSpec.scala => SecureRandomSuite.scala} | 2 +- .../std/{SemaphoreSpec.scala => SemaphoreSuite.scala} | 2 +- .../std/{SupervisorSpec.scala => SupervisorSuite.scala} | 2 +- ...emPropertiesSpec.scala => SystemPropertiesSuite.scala} | 2 +- .../effect/std/{UUIDGenSpec.scala => UUIDGenSuite.scala} | 2 +- .../{UnsafeBoundedSpec.scala => UnsafeBoundedSuite.scala} | 2 +- ...safeUnboundedSpec.scala => UnsafeUnboundedSuite.scala} | 2 +- .../{BankersQueueSpec.scala => BankersQueueSuite.scala} | 2 +- .../{BinomialHeapSpec.scala => BinomialHeapSuite.scala} | 2 +- .../{TestControlSpec.scala => TestControlSuite.scala} | 2 +- .../effect/tracing/{TraceSpec.scala => TraceSuite.scala} | 2 +- .../tracing/{TracingSpec.scala => TracingSuite.scala} | 2 +- ...ntimeBuilderSpec.scala => IORuntimeBuilderSuite.scala} | 2 +- ...RuntimeConfigSpec.scala => IORuntimeConfigSuite.scala} | 2 +- .../unsafe/{IORuntimeSpec.scala => IORuntimeSuite.scala} | 2 +- 81 files changed, 85 insertions(+), 85 deletions(-) rename tests/js/src/test/scala/cats/effect/std/{ConsoleJSSpec.scala => ConsoleJSSuite.scala} (95%) rename tests/js/src/test/scala/cats/effect/unsafe/{BatchingMacrotaskExecutorSpec.scala => BatchingMacrotaskExecutorSuite.scala} (97%) rename tests/js/src/test/scala/cats/effect/unsafe/{JSArrayQueueSpec.scala => JSArrayQueueSuite.scala} (97%) rename tests/js/src/test/scala/cats/effect/unsafe/{SchedulerSpec.scala => SchedulerSuite.scala} (98%) rename tests/js/src/test/scala/cats/effect/unsafe/{StripedHashtableSpec.scala => StripedHashtableSuite.scala} (98%) rename tests/jvm/src/test/scala/cats/effect/{ParasiticECSpec.scala => ParasiticECSuite.scala} (95%) rename tests/jvm/src/test/scala/cats/effect/{ResourceJVMSpec.scala => ResourceJVMSuite.scala} (98%) rename tests/jvm/src/test/scala/cats/effect/{SelectorSpec.scala => SelectorSuite.scala} (98%) rename tests/jvm/src/test/scala/cats/effect/kernel/{AsyncPlatformSpec.scala => AsyncPlatformSuite.scala} (98%) rename tests/jvm/src/test/scala/cats/effect/std/{ConsoleJVMSpec.scala => ConsoleJVMSuite.scala} (99%) rename tests/jvm/src/test/scala/cats/effect/std/{DeferredJVMSpec.scala => DeferredJVMSuite.scala} (100%) rename tests/jvm/src/test/scala/cats/effect/std/{DispatcherJVMSpec.scala => DispatcherJVMSuite.scala} (97%) rename tests/jvm/src/test/scala/cats/effect/std/{MapRefJVMSpec.scala => MapRefJVMSuite.scala} (99%) rename tests/jvm/src/test/scala/cats/effect/unsafe/{BlockingStressSpec.scala => BlockingStressSuite.scala} (97%) rename tests/jvm/src/test/scala/cats/effect/unsafe/{DrainBatchSpec.scala => DrainBatchSuite.scala} (97%) rename tests/jvm/src/test/scala/cats/effect/unsafe/{FiberMonitorSpec.scala => FiberMonitorSuite.scala} (97%) rename tests/jvm/src/test/scala/cats/effect/unsafe/{HelperThreadParkSpec.scala => HelperThreadParkSuite.scala} (97%) rename tests/jvm/src/test/scala/cats/effect/unsafe/{IOLocalsSpec.scala => IOLocalsSuite.scala} (97%) rename tests/jvm/src/test/scala/cats/effect/unsafe/{SleepersSpec.scala => SleepersSuite.scala} (98%) rename tests/jvm/src/test/scala/cats/effect/unsafe/{StripedHashtableSpec.scala => StripedHashtableSuite.scala} (98%) rename tests/jvm/src/test/scala/cats/effect/unsafe/{TimerHeapSpec.scala => TimerHeapSuite.scala} (99%) rename tests/jvm/src/test/scala/cats/effect/unsafe/{WorkerThreadNameSpec.scala => WorkerThreadNameSuite.scala} (98%) rename tests/native/src/test/scala/cats/effect/{FileDescriptorPollerSpec.scala => FileDescriptorPollerSuite.scala} (98%) rename tests/native/src/test/scala/cats/effect/unsafe/{SchedulerSpec.scala => SchedulerSuite.scala} (97%) rename tests/shared/src/test/scala-2.13+/cats/effect/{IOImplicitSpec.scala => IOImplicitSuite.scala} (97%) rename tests/shared/src/test/scala-2.13+/not/cats/effect/{IOParImplicitSpec.scala => IOParImplicitSuite.scala} (95%) rename tests/shared/src/test/scala/cats/effect/{BaseSpec.scala => BaseSuite.scala} (100%) rename tests/shared/src/test/scala/cats/effect/{CallbackStackSpec.scala => CallbackStackSuite.scala} (96%) rename tests/shared/src/test/scala/cats/effect/{ContSpec.scala => ContSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/{EitherTIOSpec.scala => EitherTIOSuite.scala} (97%) rename tests/shared/src/test/scala/cats/effect/{IOFiberSpec.scala => IOFiberSuite.scala} (97%) rename tests/shared/src/test/scala/cats/effect/{IOLocalSpec.scala => IOLocalSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/{IOMtlLocalSpec.scala => IOMtlLocalSuite.scala} (95%) rename tests/shared/src/test/scala/cats/effect/{IOParSpec.scala => IOParSuite.scala} (97%) rename tests/shared/src/test/scala/cats/effect/{IOPropSpec.scala => IOPropSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/{IOSpec.scala => IOSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/{IorTIOSpec.scala => IorTIOSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/{KleisliIOSpec.scala => KleisliIOSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/{MemoizeSpec.scala => MemoizeSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/{OptionTIOSpec.scala => OptionTIOSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/{ResourceSpec.scala => ResourceSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/{StateTIOSpec.scala => StateTIOSuite.scala} (97%) rename tests/shared/src/test/scala/cats/effect/{SyncIOSpec.scala => SyncIOSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/{ThunkSpec.scala => ThunkSuite.scala} (95%) rename tests/shared/src/test/scala/cats/effect/{WriterTIOSpec.scala => WriterTIOSuite.scala} (97%) rename tests/shared/src/test/scala/cats/effect/kernel/{AsyncSpec.scala => AsyncSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/kernel/{DeferredSpec.scala => DeferredSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/kernel/{DerivationRefinementSpec.scala => DerivationRefinementSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/kernel/{LensRefSpec.scala => LensRefSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/kernel/{MiniSemaphoreSpec.scala => MiniSemaphoreSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/kernel/{ParallelFSpec.scala => ParallelFSuite.scala} (97%) rename tests/shared/src/test/scala/cats/effect/kernel/{RefSpec.scala => RefSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/std/{AtomicCellSpec.scala => AtomicCellSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/std/{BackpressureSpec.scala => BackpressureSuite.scala} (97%) rename tests/shared/src/test/scala/cats/effect/std/{ConsoleSpec.scala => ConsoleSuite.scala} (96%) rename tests/shared/src/test/scala/cats/effect/std/{CountDownLatchSpec.scala => CountDownLatchSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/std/{CyclicBarrierSpec.scala => CyclicBarrierSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/std/{DequeueSpec.scala => DequeueSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/std/{DispatcherSpec.scala => DispatcherSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/std/{EnvSpec.scala => EnvSuite.scala} (96%) rename tests/shared/src/test/scala/cats/effect/std/{HotswapSpec.scala => HotswapSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/std/{MapRefSpec.scala => MapRefSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/std/{MutexSpec.scala => MutexSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/std/{PQueueSpec.scala => PQueueSuite.scala} (97%) rename tests/shared/src/test/scala/cats/effect/std/{QueueSpec.scala => QueueSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/std/{RandomSpec.scala => RandomSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/std/{SecureRandomSpec.scala => SecureRandomSuite.scala} (97%) rename tests/shared/src/test/scala/cats/effect/std/{SemaphoreSpec.scala => SemaphoreSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/std/{SupervisorSpec.scala => SupervisorSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/std/{SystemPropertiesSpec.scala => SystemPropertiesSuite.scala} (96%) rename tests/shared/src/test/scala/cats/effect/std/{UUIDGenSpec.scala => UUIDGenSuite.scala} (96%) rename tests/shared/src/test/scala/cats/effect/std/{UnsafeBoundedSpec.scala => UnsafeBoundedSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/std/{UnsafeUnboundedSpec.scala => UnsafeUnboundedSuite.scala} (97%) rename tests/shared/src/test/scala/cats/effect/std/internal/{BankersQueueSpec.scala => BankersQueueSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/std/internal/{BinomialHeapSpec.scala => BinomialHeapSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/testkit/{TestControlSpec.scala => TestControlSuite.scala} (99%) rename tests/shared/src/test/scala/cats/effect/tracing/{TraceSpec.scala => TraceSuite.scala} (95%) rename tests/shared/src/test/scala/cats/effect/tracing/{TracingSpec.scala => TracingSuite.scala} (96%) rename tests/shared/src/test/scala/cats/effect/unsafe/{IORuntimeBuilderSpec.scala => IORuntimeBuilderSuite.scala} (93%) rename tests/shared/src/test/scala/cats/effect/unsafe/{IORuntimeConfigSpec.scala => IORuntimeConfigSuite.scala} (98%) rename tests/shared/src/test/scala/cats/effect/unsafe/{IORuntimeSpec.scala => IORuntimeSuite.scala} (96%) diff --git a/tests/js/src/test/scala/cats/effect/std/ConsoleJSSpec.scala b/tests/js/src/test/scala/cats/effect/std/ConsoleJSSuite.scala similarity index 95% rename from tests/js/src/test/scala/cats/effect/std/ConsoleJSSpec.scala rename to tests/js/src/test/scala/cats/effect/std/ConsoleJSSuite.scala index af588c26f8..2cde878364 100644 --- a/tests/js/src/test/scala/cats/effect/std/ConsoleJSSpec.scala +++ b/tests/js/src/test/scala/cats/effect/std/ConsoleJSSuite.scala @@ -17,7 +17,7 @@ package cats.effect package std -class ConsoleJSSpec extends BaseSpec { +class ConsoleJSSuite extends BaseSpec { "Console" should { "work in any JS environment" in real { diff --git a/tests/js/src/test/scala/cats/effect/unsafe/BatchingMacrotaskExecutorSpec.scala b/tests/js/src/test/scala/cats/effect/unsafe/BatchingMacrotaskExecutorSuite.scala similarity index 97% rename from tests/js/src/test/scala/cats/effect/unsafe/BatchingMacrotaskExecutorSpec.scala rename to tests/js/src/test/scala/cats/effect/unsafe/BatchingMacrotaskExecutorSuite.scala index 607e60ee6e..0eaae4bc59 100644 --- a/tests/js/src/test/scala/cats/effect/unsafe/BatchingMacrotaskExecutorSpec.scala +++ b/tests/js/src/test/scala/cats/effect/unsafe/BatchingMacrotaskExecutorSuite.scala @@ -24,7 +24,7 @@ import org.scalajs.macrotaskexecutor.MacrotaskExecutor import scala.concurrent.duration._ -class BatchingMacrotaskExecutorSpec extends BaseSpec { +class BatchingMacrotaskExecutorSuite extends BaseSpec { "BatchingMacrotaskExecutor" should { "batch fibers" in real { // fails if running on MacrotaskExecutor diff --git a/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSpec.scala b/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala similarity index 97% rename from tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSpec.scala rename to tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala index 5975c29f3f..75706f2586 100644 --- a/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSpec.scala +++ b/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala @@ -22,7 +22,7 @@ import org.specs2.ScalaCheck import scala.collection.mutable.{ListBuffer, Queue} -class JSArrayQueueSpec extends BaseSpec with ScalaCheck { +class JSArrayQueueSuite extends BaseSpec with ScalaCheck { "JSArrayQueue" should { "be fifo" in { diff --git a/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSpec.scala b/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala similarity index 98% rename from tests/js/src/test/scala/cats/effect/unsafe/SchedulerSpec.scala rename to tests/js/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala index afab02ae90..c7d9708fa3 100644 --- a/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSpec.scala +++ b/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala @@ -21,7 +21,7 @@ import scala.concurrent.duration._ import java.util.concurrent.atomic.AtomicBoolean -class SchedulerSpec extends BaseSpec { +class SchedulerSuite extends BaseSpec { "Default scheduler" should { "correctly handle very long sleeps" in real { diff --git a/tests/js/src/test/scala/cats/effect/unsafe/StripedHashtableSpec.scala b/tests/js/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala similarity index 98% rename from tests/js/src/test/scala/cats/effect/unsafe/StripedHashtableSpec.scala rename to tests/js/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala index abed3e1600..5c745c89a5 100644 --- a/tests/js/src/test/scala/cats/effect/unsafe/StripedHashtableSpec.scala +++ b/tests/js/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala @@ -22,7 +22,7 @@ import cats.syntax.traverse._ import scala.concurrent.{Future, Promise} import scala.concurrent.duration._ -class StripedHashtableSpec extends BaseSpec { +class StripedHashtableSuite extends BaseSpec { override def executionTimeout: FiniteDuration = 2.minutes diff --git a/tests/jvm/src/test/scala/cats/effect/ParasiticECSpec.scala b/tests/jvm/src/test/scala/cats/effect/ParasiticECSuite.scala similarity index 95% rename from tests/jvm/src/test/scala/cats/effect/ParasiticECSpec.scala rename to tests/jvm/src/test/scala/cats/effect/ParasiticECSuite.scala index 20ea86d867..07e7376ef7 100644 --- a/tests/jvm/src/test/scala/cats/effect/ParasiticECSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/ParasiticECSuite.scala @@ -23,7 +23,7 @@ import org.scalacheck.Arbitrary import scala.concurrent.duration._ -class ParasiticECSpec extends BaseSpec with TestInstances { +class ParasiticECSuite extends BaseSpec with TestInstances { override def executionTimeout: FiniteDuration = 60.seconds diff --git a/tests/jvm/src/test/scala/cats/effect/ResourceJVMSpec.scala b/tests/jvm/src/test/scala/cats/effect/ResourceJVMSuite.scala similarity index 98% rename from tests/jvm/src/test/scala/cats/effect/ResourceJVMSpec.scala rename to tests/jvm/src/test/scala/cats/effect/ResourceJVMSuite.scala index 848039bb03..c8e2ce12fe 100644 --- a/tests/jvm/src/test/scala/cats/effect/ResourceJVMSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/ResourceJVMSuite.scala @@ -19,7 +19,7 @@ package cats.effect import cats.arrow.FunctionK import cats.syntax.eq._ -class ResourceJVMSpec extends BaseSpec { +class ResourceJVMSuite extends BaseSpec { "platform" should { diff --git a/tests/jvm/src/test/scala/cats/effect/SelectorSpec.scala b/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala similarity index 98% rename from tests/jvm/src/test/scala/cats/effect/SelectorSpec.scala rename to tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala index 78ea55d4aa..0334199b3b 100644 --- a/tests/jvm/src/test/scala/cats/effect/SelectorSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala @@ -24,7 +24,7 @@ import java.nio.ByteBuffer import java.nio.channels.Pipe import java.nio.channels.SelectionKey._ -class SelectorSpec extends BaseSpec { +class SelectorSuite extends BaseSpec { def getSelector: IO[Selector] = IO.pollers.map(_.collectFirst { case selector: Selector => selector }).map(_.get) diff --git a/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSpec.scala b/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSuite.scala similarity index 98% rename from tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSpec.scala rename to tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSuite.scala index 30f9dcc387..350b53f58b 100644 --- a/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSuite.scala @@ -25,7 +25,7 @@ import scala.concurrent.duration._ import java.util.concurrent.{CancellationException, CompletableFuture} import java.util.concurrent.atomic.AtomicBoolean -class AsyncPlatformSpec extends BaseSpec { +class AsyncPlatformSuite extends BaseSpec { val smallDelay: IO[Unit] = IO.sleep(1.second) diff --git a/tests/jvm/src/test/scala/cats/effect/std/ConsoleJVMSpec.scala b/tests/jvm/src/test/scala/cats/effect/std/ConsoleJVMSuite.scala similarity index 99% rename from tests/jvm/src/test/scala/cats/effect/std/ConsoleJVMSpec.scala rename to tests/jvm/src/test/scala/cats/effect/std/ConsoleJVMSuite.scala index dcd1fd0d3d..bb4cb4c375 100644 --- a/tests/jvm/src/test/scala/cats/effect/std/ConsoleJVMSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/std/ConsoleJVMSuite.scala @@ -36,7 +36,7 @@ import java.io.{ } import java.nio.charset.{Charset, StandardCharsets} -class ConsoleJVMSpec extends BaseSpec { +class ConsoleJVMSuite extends BaseSpec { sequential private def printStream(out: ByteArrayOutputStream): Resource[IO, PrintStream] = diff --git a/tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSpec.scala b/tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSuite.scala similarity index 100% rename from tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSpec.scala rename to tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSuite.scala diff --git a/tests/jvm/src/test/scala/cats/effect/std/DispatcherJVMSpec.scala b/tests/jvm/src/test/scala/cats/effect/std/DispatcherJVMSuite.scala similarity index 97% rename from tests/jvm/src/test/scala/cats/effect/std/DispatcherJVMSpec.scala rename to tests/jvm/src/test/scala/cats/effect/std/DispatcherJVMSuite.scala index 8945b843e9..f9d397d9bf 100644 --- a/tests/jvm/src/test/scala/cats/effect/std/DispatcherJVMSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/std/DispatcherJVMSuite.scala @@ -22,7 +22,7 @@ import cats.syntax.all._ import scala.concurrent.duration.DurationInt -class DispatcherJVMSpec extends BaseSpec { +class DispatcherJVMSuite extends BaseSpec { "async dispatcher" should { "run multiple IOs in parallel with blocking threads" in real { diff --git a/tests/jvm/src/test/scala/cats/effect/std/MapRefJVMSpec.scala b/tests/jvm/src/test/scala/cats/effect/std/MapRefJVMSuite.scala similarity index 99% rename from tests/jvm/src/test/scala/cats/effect/std/MapRefJVMSpec.scala rename to tests/jvm/src/test/scala/cats/effect/std/MapRefJVMSuite.scala index c6ebecbd9a..f977af35ae 100644 --- a/tests/jvm/src/test/scala/cats/effect/std/MapRefJVMSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/std/MapRefJVMSuite.scala @@ -23,7 +23,7 @@ import cats.implicits._ import scala.concurrent.duration._ -class MapRefJVMSpec extends BaseSpec { +class MapRefJVMSuite extends BaseSpec { private val smallDelay: IO[Unit] = IO.sleep(20.millis) private def awaitEqual[A: Eq](t: IO[A], success: A): IO[Unit] = diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/BlockingStressSpec.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/BlockingStressSuite.scala similarity index 97% rename from tests/jvm/src/test/scala/cats/effect/unsafe/BlockingStressSpec.scala rename to tests/jvm/src/test/scala/cats/effect/unsafe/BlockingStressSuite.scala index a89d412d82..364c37fbd3 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/BlockingStressSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/BlockingStressSuite.scala @@ -25,7 +25,7 @@ import scala.util.Random import java.util.concurrent.CountDownLatch -class BlockingStressSpec extends BaseSpec { +class BlockingStressSuite extends BaseSpec { override def executionTimeout: FiniteDuration = 30.seconds diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/DrainBatchSpec.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/DrainBatchSuite.scala similarity index 97% rename from tests/jvm/src/test/scala/cats/effect/unsafe/DrainBatchSpec.scala rename to tests/jvm/src/test/scala/cats/effect/unsafe/DrainBatchSuite.scala index 4d6d35f408..a13164a805 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/DrainBatchSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/DrainBatchSuite.scala @@ -19,7 +19,7 @@ package unsafe import cats.effect.std.Queue -class DrainBatchSpec extends BaseSpec { +class DrainBatchSuite extends BaseSpec { "Batch draining" should { "work correctly in the presence of concurrent stealers" in real { diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSpec.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala similarity index 97% rename from tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSpec.scala rename to tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala index 9923cbdc22..8c29a0c7ce 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala @@ -22,7 +22,7 @@ import cats.effect.testkit.TestInstances import scala.concurrent.duration._ -class FiberMonitorSpec extends BaseSpec with TestInstances { +class FiberMonitorSuite extends BaseSpec with TestInstances { "FiberMonitor" should { diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSpec.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSuite.scala similarity index 97% rename from tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSpec.scala rename to tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSuite.scala index 4c08026578..74da745f9c 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSuite.scala @@ -22,7 +22,7 @@ import cats.syntax.all._ import scala.concurrent.{Await, Promise} import scala.concurrent.duration._ -class HelperThreadParkSpec extends BaseSpec { +class HelperThreadParkSuite extends BaseSpec { "HelperThread" should { "not give up when fibers are late" in real { diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/IOLocalsSpec.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/IOLocalsSuite.scala similarity index 97% rename from tests/jvm/src/test/scala/cats/effect/unsafe/IOLocalsSpec.scala rename to tests/jvm/src/test/scala/cats/effect/unsafe/IOLocalsSuite.scala index f356d8796a..c0a6e58135 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/IOLocalsSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/IOLocalsSuite.scala @@ -17,7 +17,7 @@ package cats.effect package unsafe -class IOLocalsSpec extends BaseSpec { +class IOLocalsSuite extends BaseSpec { "IOLocals" should { "return a default value" in real { diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSpec.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSuite.scala similarity index 98% rename from tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSpec.scala rename to tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSuite.scala index de3e4c5a9e..a0bd06a38f 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSuite.scala @@ -21,7 +21,7 @@ import org.specs2.mutable.Specification import scala.annotation.tailrec import scala.concurrent.duration._ -class SleepersSpec extends Specification { +class SleepersSuite extends Specification { "SleepCallback" should { "have a trigger time in the future" in { diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/StripedHashtableSpec.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala similarity index 98% rename from tests/jvm/src/test/scala/cats/effect/unsafe/StripedHashtableSpec.scala rename to tests/jvm/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala index bfffc7ec25..ac933964c6 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/StripedHashtableSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala @@ -23,7 +23,7 @@ import scala.concurrent.duration._ import java.util.concurrent.CountDownLatch -class StripedHashtableSpec extends BaseSpec { +class StripedHashtableSuite extends BaseSpec { override def executionTimeout: FiniteDuration = 30.seconds diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSpec.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSuite.scala similarity index 99% rename from tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSpec.scala rename to tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSuite.scala index 798e75c073..a80a4f39f7 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSuite.scala @@ -18,7 +18,7 @@ package cats.effect.unsafe import org.specs2.mutable.Specification -class TimerHeapSpec extends Specification { +class TimerHeapSuite extends Specification { /** * Creates a new callback, making sure it's a separate object diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/WorkerThreadNameSpec.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/WorkerThreadNameSuite.scala similarity index 98% rename from tests/jvm/src/test/scala/cats/effect/unsafe/WorkerThreadNameSpec.scala rename to tests/jvm/src/test/scala/cats/effect/unsafe/WorkerThreadNameSuite.scala index c99b390abf..0e730745b1 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/WorkerThreadNameSpec.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/WorkerThreadNameSuite.scala @@ -21,7 +21,7 @@ import cats.effect.testkit.TestInstances import scala.concurrent.duration._ -class WorkerThreadNameSpec extends BaseSpec with TestInstances { +class WorkerThreadNameSuite extends BaseSpec with TestInstances { override def runtime(): IORuntime = { lazy val rt: IORuntime = { diff --git a/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSpec.scala b/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSuite.scala similarity index 98% rename from tests/native/src/test/scala/cats/effect/FileDescriptorPollerSpec.scala rename to tests/native/src/test/scala/cats/effect/FileDescriptorPollerSuite.scala index 879f122fdc..ff104b5f7c 100644 --- a/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSpec.scala +++ b/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSuite.scala @@ -30,7 +30,7 @@ import scala.scalanative.unsigned._ import java.io.IOException -class FileDescriptorPollerSpec extends BaseSpec { +class FileDescriptorPollerSuite extends BaseSpec { final class Pipe( val readFd: Int, diff --git a/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSpec.scala b/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala similarity index 97% rename from tests/native/src/test/scala/cats/effect/unsafe/SchedulerSpec.scala rename to tests/native/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala index 1a266c9ed1..b1b9fcc7eb 100644 --- a/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSpec.scala +++ b/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala @@ -19,7 +19,7 @@ package unsafe import scala.concurrent.duration._ -class SchedulerSpec extends BaseSpec { +class SchedulerSuite extends BaseSpec { "Default scheduler" should { "use high-precision time" in real { diff --git a/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSpec.scala b/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSuite.scala similarity index 97% rename from tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSpec.scala rename to tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSuite.scala index 1e98bf7b5e..9ae9b4487e 100644 --- a/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSpec.scala +++ b/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSuite.scala @@ -16,7 +16,7 @@ package cats.effect -class IOImplicitSpec extends BaseSpec { +class IOImplicitSuite extends BaseSpec { "Can resolve IO sequence ops without import of cats.syntax.all" in { // compilation test for { diff --git a/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSpec.scala b/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSuite.scala similarity index 95% rename from tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSpec.scala rename to tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSuite.scala index 48d3b299a5..d3ffcad497 100644 --- a/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSpec.scala +++ b/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSuite.scala @@ -20,7 +20,7 @@ import cats.{Align, CommutativeApplicative} import cats.effect.{BaseSpec, IO} import cats.syntax.all._ -class IOParImplicitSpec extends BaseSpec { +class IOParImplicitSuite extends BaseSpec { "Can resolve CommutativeApplicative instance" in { List(1, 2, 3).parUnorderedTraverse(_ => IO.unit) // compilation test diff --git a/tests/shared/src/test/scala/cats/effect/BaseSpec.scala b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala similarity index 100% rename from tests/shared/src/test/scala/cats/effect/BaseSpec.scala rename to tests/shared/src/test/scala/cats/effect/BaseSuite.scala diff --git a/tests/shared/src/test/scala/cats/effect/CallbackStackSpec.scala b/tests/shared/src/test/scala/cats/effect/CallbackStackSuite.scala similarity index 96% rename from tests/shared/src/test/scala/cats/effect/CallbackStackSpec.scala rename to tests/shared/src/test/scala/cats/effect/CallbackStackSuite.scala index 8f0dd5899d..63c038f8cd 100644 --- a/tests/shared/src/test/scala/cats/effect/CallbackStackSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/CallbackStackSuite.scala @@ -16,7 +16,7 @@ package cats.effect -class CallbackStackSpec extends BaseSpec with DetectPlatform { +class CallbackStackSuite extends BaseSpec with DetectPlatform { "CallbackStack" should { "correctly report the number removed" in { diff --git a/tests/shared/src/test/scala/cats/effect/ContSpec.scala b/tests/shared/src/test/scala/cats/effect/ContSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/ContSpec.scala rename to tests/shared/src/test/scala/cats/effect/ContSuite.scala index df2012fcc4..5362b3b808 100644 --- a/tests/shared/src/test/scala/cats/effect/ContSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/ContSuite.scala @@ -24,12 +24,12 @@ import org.specs2.execute._ import scala.concurrent.duration._ -class ContSpec extends ContSpecBase { +class ContSuite extends ContSpecBase { def cont[K, R](body: Cont[IO, K, R]): IO[R] = IO.cont(body) } -class DefaultContSpec extends ContSpecBase { +class DefaultContSuite extends ContSpecBase { def cont[K, R](body: Cont[IO, K, R]): IO[R] = Async.defaultCont(body) } diff --git a/tests/shared/src/test/scala/cats/effect/EitherTIOSpec.scala b/tests/shared/src/test/scala/cats/effect/EitherTIOSuite.scala similarity index 97% rename from tests/shared/src/test/scala/cats/effect/EitherTIOSpec.scala rename to tests/shared/src/test/scala/cats/effect/EitherTIOSuite.scala index f2f0640372..08874e88db 100644 --- a/tests/shared/src/test/scala/cats/effect/EitherTIOSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/EitherTIOSuite.scala @@ -27,7 +27,7 @@ import org.scalacheck.Prop import scala.concurrent.duration._ -class EitherTIOSpec extends BaseSuite with DisciplineSuite { +class EitherTIOSuite extends BaseSuite with DisciplineSuite { ticked("EitherT should execute finalizers") { implicit ticker => type F[A] = EitherT[IO, String, A] diff --git a/tests/shared/src/test/scala/cats/effect/IOFiberSpec.scala b/tests/shared/src/test/scala/cats/effect/IOFiberSuite.scala similarity index 97% rename from tests/shared/src/test/scala/cats/effect/IOFiberSpec.scala rename to tests/shared/src/test/scala/cats/effect/IOFiberSuite.scala index 429115d279..c6269d0309 100644 --- a/tests/shared/src/test/scala/cats/effect/IOFiberSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/IOFiberSuite.scala @@ -20,7 +20,7 @@ import cats.effect.{BaseSpec, DetectPlatform, IO} import scala.concurrent.duration._ -class IOFiberSpec extends BaseSpec with DetectPlatform { +class IOFiberSuite extends BaseSpec with DetectPlatform { "IOFiber" should { if (!isJS || !isWSL) { diff --git a/tests/shared/src/test/scala/cats/effect/IOLocalSpec.scala b/tests/shared/src/test/scala/cats/effect/IOLocalSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/IOLocalSpec.scala rename to tests/shared/src/test/scala/cats/effect/IOLocalSuite.scala index c187f3aac1..db6c5789e2 100644 --- a/tests/shared/src/test/scala/cats/effect/IOLocalSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/IOLocalSuite.scala @@ -19,7 +19,7 @@ package effect import scala.annotation.tailrec -class IOLocalSpec extends BaseSpec { +class IOLocalSuite extends BaseSpec { ioLocalTests( "IOLocal[Int]", diff --git a/tests/shared/src/test/scala/cats/effect/IOMtlLocalSpec.scala b/tests/shared/src/test/scala/cats/effect/IOMtlLocalSuite.scala similarity index 95% rename from tests/shared/src/test/scala/cats/effect/IOMtlLocalSpec.scala rename to tests/shared/src/test/scala/cats/effect/IOMtlLocalSuite.scala index 33e9557de5..52c59ae43d 100644 --- a/tests/shared/src/test/scala/cats/effect/IOMtlLocalSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/IOMtlLocalSuite.scala @@ -24,7 +24,7 @@ import org.typelevel.discipline.specs2.mutable.Discipline import java.util.concurrent.CancellationException -class IOMtlLocalSpec extends BaseSpec with Discipline { +class IOMtlLocalSuite extends BaseSpec with Discipline { sequential implicit val ticker: Ticker = Ticker() diff --git a/tests/shared/src/test/scala/cats/effect/IOParSpec.scala b/tests/shared/src/test/scala/cats/effect/IOParSuite.scala similarity index 97% rename from tests/shared/src/test/scala/cats/effect/IOParSpec.scala rename to tests/shared/src/test/scala/cats/effect/IOParSuite.scala index 71065c1570..3663b4e9a2 100644 --- a/tests/shared/src/test/scala/cats/effect/IOParSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/IOParSuite.scala @@ -24,7 +24,7 @@ import cats.syntax.all._ import org.typelevel.discipline.specs2.mutable.Discipline -class IOParSpec extends BaseSpec with Discipline { +class IOParSuite extends BaseSpec with Discipline { // we just need this because of the laws testing, since the prop runs can interfere with each other sequential diff --git a/tests/shared/src/test/scala/cats/effect/IOPropSpec.scala b/tests/shared/src/test/scala/cats/effect/IOPropSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/IOPropSpec.scala rename to tests/shared/src/test/scala/cats/effect/IOPropSuite.scala index 2784c3f497..ef57a13312 100644 --- a/tests/shared/src/test/scala/cats/effect/IOPropSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/IOPropSuite.scala @@ -27,7 +27,7 @@ import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ //We allow these tests to have a longer timeout than IOSpec as they run lots of iterations -class IOPropSpec extends BaseSpec with Discipline { +class IOPropSuite extends BaseSpec with Discipline { override def executionTimeout: FiniteDuration = 2.minutes diff --git a/tests/shared/src/test/scala/cats/effect/IOSpec.scala b/tests/shared/src/test/scala/cats/effect/IOSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/IOSpec.scala rename to tests/shared/src/test/scala/cats/effect/IOSuite.scala index 9749e9a440..73aa963a80 100644 --- a/tests/shared/src/test/scala/cats/effect/IOSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/IOSuite.scala @@ -34,7 +34,7 @@ import scala.concurrent.duration._ import Prop.forAll -class IOSpec extends BaseSpec with Discipline with IOPlatformSpecification { +class IOSuite extends BaseSpec with Discipline with IOPlatformSpecification { // we just need this because of the laws testing, since the prop runs can interfere with each other sequential diff --git a/tests/shared/src/test/scala/cats/effect/IorTIOSpec.scala b/tests/shared/src/test/scala/cats/effect/IorTIOSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/IorTIOSpec.scala rename to tests/shared/src/test/scala/cats/effect/IorTIOSuite.scala index 264f68d045..d86a960c8b 100644 --- a/tests/shared/src/test/scala/cats/effect/IorTIOSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/IorTIOSuite.scala @@ -28,7 +28,7 @@ import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class IorTIOSpec extends BaseSpec with Discipline { +class IorTIOSuite extends BaseSpec with Discipline { // we just need this because of the laws testing, since the prop runs can interfere with each other sequential diff --git a/tests/shared/src/test/scala/cats/effect/KleisliIOSpec.scala b/tests/shared/src/test/scala/cats/effect/KleisliIOSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/KleisliIOSpec.scala rename to tests/shared/src/test/scala/cats/effect/KleisliIOSuite.scala index 4b2e689b42..f519106a55 100644 --- a/tests/shared/src/test/scala/cats/effect/KleisliIOSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/KleisliIOSuite.scala @@ -31,7 +31,7 @@ import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class KleisliIOSpec extends BaseSpec with Discipline { +class KleisliIOSuite extends BaseSpec with Discipline { // we just need this because of the laws testing, since the prop runs can interfere with each other sequential diff --git a/tests/shared/src/test/scala/cats/effect/MemoizeSpec.scala b/tests/shared/src/test/scala/cats/effect/MemoizeSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/MemoizeSpec.scala rename to tests/shared/src/test/scala/cats/effect/MemoizeSuite.scala index e37aa4e757..77847d1f57 100644 --- a/tests/shared/src/test/scala/cats/effect/MemoizeSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/MemoizeSuite.scala @@ -32,7 +32,7 @@ import scala.util.Success import Prop.forAll -class MemoizeSpec extends BaseSpec with Discipline { +class MemoizeSuite extends BaseSpec with Discipline { sequential diff --git a/tests/shared/src/test/scala/cats/effect/OptionTIOSpec.scala b/tests/shared/src/test/scala/cats/effect/OptionTIOSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/OptionTIOSpec.scala rename to tests/shared/src/test/scala/cats/effect/OptionTIOSuite.scala index 9a2e3e9824..18eaa1c9ad 100644 --- a/tests/shared/src/test/scala/cats/effect/OptionTIOSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/OptionTIOSuite.scala @@ -28,7 +28,7 @@ import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class OptionTIOSpec extends BaseSpec with Discipline { +class OptionTIOSuite extends BaseSpec with Discipline { // we just need this because of the laws testing, since the prop runs can interfere with each other sequential diff --git a/tests/shared/src/test/scala/cats/effect/ResourceSpec.scala b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/ResourceSpec.scala rename to tests/shared/src/test/scala/cats/effect/ResourceSuite.scala index f45bb0e90a..0c1cec6af8 100644 --- a/tests/shared/src/test/scala/cats/effect/ResourceSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala @@ -37,7 +37,7 @@ import scala.concurrent.duration._ import java.util.concurrent.atomic.AtomicBoolean -class ResourceSpec extends BaseSpec with ScalaCheck with Discipline { +class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { // We need this for testing laws: prop runs can interfere with each other sequential diff --git a/tests/shared/src/test/scala/cats/effect/StateTIOSpec.scala b/tests/shared/src/test/scala/cats/effect/StateTIOSuite.scala similarity index 97% rename from tests/shared/src/test/scala/cats/effect/StateTIOSpec.scala rename to tests/shared/src/test/scala/cats/effect/StateTIOSuite.scala index c276b15c70..72464a4013 100644 --- a/tests/shared/src/test/scala/cats/effect/StateTIOSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/StateTIOSuite.scala @@ -28,7 +28,7 @@ import org.scalacheck.Prop import org.specs2.scalacheck.Parameters import org.typelevel.discipline.specs2.mutable.Discipline -class StateTIOSpec extends BaseSpec with Discipline { +class StateTIOSuite extends BaseSpec with Discipline { sequential diff --git a/tests/shared/src/test/scala/cats/effect/SyncIOSpec.scala b/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/SyncIOSpec.scala rename to tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala index 6daa362392..20d10f4bb5 100644 --- a/tests/shared/src/test/scala/cats/effect/SyncIOSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala @@ -26,7 +26,7 @@ import cats.syntax.all._ import org.scalacheck.Prop.forAll import org.typelevel.discipline.specs2.mutable.Discipline -class SyncIOSpec extends BaseSpec with Discipline with SyncIOPlatformSpecification { +class SyncIOSuite extends BaseSpec with Discipline with SyncIOPlatformSpecification { "sync io monad" should { "produce a pure value when run" in { diff --git a/tests/shared/src/test/scala/cats/effect/ThunkSpec.scala b/tests/shared/src/test/scala/cats/effect/ThunkSuite.scala similarity index 95% rename from tests/shared/src/test/scala/cats/effect/ThunkSpec.scala rename to tests/shared/src/test/scala/cats/effect/ThunkSuite.scala index 695790c59b..33b3ad9acd 100644 --- a/tests/shared/src/test/scala/cats/effect/ThunkSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/ThunkSuite.scala @@ -16,7 +16,7 @@ package cats.effect -class ThunkSpec extends BaseSpec { +class ThunkSuite extends BaseSpec { "IO.delay" should { "return the same function" in { diff --git a/tests/shared/src/test/scala/cats/effect/WriterTIOSpec.scala b/tests/shared/src/test/scala/cats/effect/WriterTIOSuite.scala similarity index 97% rename from tests/shared/src/test/scala/cats/effect/WriterTIOSpec.scala rename to tests/shared/src/test/scala/cats/effect/WriterTIOSuite.scala index b6fc977d6e..de2a3d26c3 100644 --- a/tests/shared/src/test/scala/cats/effect/WriterTIOSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/WriterTIOSuite.scala @@ -28,7 +28,7 @@ import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class WriterTIOSpec extends BaseSpec with Discipline { +class WriterTIOSuite extends BaseSpec with Discipline { // we just need this because of the laws testing, since the prop runs can interfere with each other sequential diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala rename to tests/shared/src/test/scala/cats/effect/kernel/AsyncSuite.scala index 6670e7266f..802dd22b3b 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSuite.scala @@ -33,7 +33,7 @@ import scala.concurrent.duration._ import java.util.concurrent.atomic.AtomicBoolean -class AsyncSpec extends BaseSpec with Discipline { +class AsyncSuite extends BaseSpec with Discipline { // we just need this because of the laws testing, since the prop runs can interfere with each other sequential diff --git a/tests/shared/src/test/scala/cats/effect/kernel/DeferredSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/DeferredSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/kernel/DeferredSpec.scala rename to tests/shared/src/test/scala/cats/effect/kernel/DeferredSuite.scala index fc9e73af35..52b0197c11 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/DeferredSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/DeferredSuite.scala @@ -22,7 +22,7 @@ import cats.syntax.all._ import scala.concurrent.duration._ -class DeferredSpec extends BaseSpec with DetectPlatform { outer => +class DeferredSuite extends BaseSpec with DetectPlatform { outer => "Deferred for Async" should { tests(IO(Deferred.unsafe), IO(Deferred.unsafe)) diff --git a/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSpec.scala rename to tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSuite.scala index 5d7b7376f4..985156c21e 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSuite.scala @@ -23,7 +23,7 @@ import org.specs2.matcher.Matchers import scala.reflect.ClassTag -class DerivationRefinementSpec extends BaseSpec with Matchers { +class DerivationRefinementSuite extends BaseSpec with Matchers { type AsyncStack[F[_], A] = Kleisli[OptionT[EitherT[IorT[F, Int, *], String, *], *], Unit, A] type SyncStack[F[_], A] = StateT[ReaderWriterStateT[F, String, Int, Unit, *], Boolean, A] diff --git a/tests/shared/src/test/scala/cats/effect/kernel/LensRefSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/LensRefSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/kernel/LensRefSpec.scala rename to tests/shared/src/test/scala/cats/effect/kernel/LensRefSuite.scala index bb3bba6cdc..8cb93c4b1b 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/LensRefSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/LensRefSuite.scala @@ -23,7 +23,7 @@ import cats.data.State import scala.concurrent.duration._ -class LensRefSpec extends BaseSpec with DetectPlatform { outer => +class LensRefSuite extends BaseSpec with DetectPlatform { outer => val smallDelay: IO[Unit] = IO.sleep(20.millis) diff --git a/tests/shared/src/test/scala/cats/effect/kernel/MiniSemaphoreSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/MiniSemaphoreSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/kernel/MiniSemaphoreSpec.scala rename to tests/shared/src/test/scala/cats/effect/kernel/MiniSemaphoreSuite.scala index cd67b0a8dd..90fd292065 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/MiniSemaphoreSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/MiniSemaphoreSuite.scala @@ -20,7 +20,7 @@ package kernel import scala.concurrent.duration._ -class MiniSemaphoreSpec extends BaseSpec { outer => +class MiniSemaphoreSuite extends BaseSpec { outer => "mini semaphore" should { "throw on negative n" in real { diff --git a/tests/shared/src/test/scala/cats/effect/kernel/ParallelFSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/ParallelFSuite.scala similarity index 97% rename from tests/shared/src/test/scala/cats/effect/kernel/ParallelFSpec.scala rename to tests/shared/src/test/scala/cats/effect/kernel/ParallelFSuite.scala index 6118a62ee7..452032823d 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/ParallelFSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/ParallelFSuite.scala @@ -28,7 +28,7 @@ import cats.syntax.all._ import org.specs2.scalacheck._ import org.typelevel.discipline.specs2.mutable.Discipline -class ParallelFSpec extends BaseSpec with Discipline with DetectPlatform { +class ParallelFSuite extends BaseSpec with Discipline with DetectPlatform { implicit val params: Parameters = if (isNative) diff --git a/tests/shared/src/test/scala/cats/effect/kernel/RefSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/RefSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/kernel/RefSpec.scala rename to tests/shared/src/test/scala/cats/effect/kernel/RefSuite.scala index 1ef569f0f1..919058b15a 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/RefSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/RefSuite.scala @@ -22,7 +22,7 @@ import cats.data.State import scala.concurrent.duration._ -class RefSpec extends BaseSpec with DetectPlatform { outer => +class RefSuite extends BaseSpec with DetectPlatform { outer => val smallDelay: IO[Unit] = IO.sleep(20.millis) diff --git a/tests/shared/src/test/scala/cats/effect/std/AtomicCellSpec.scala b/tests/shared/src/test/scala/cats/effect/std/AtomicCellSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/std/AtomicCellSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/AtomicCellSuite.scala index 9f3a6a73f0..5ad9e60562 100644 --- a/tests/shared/src/test/scala/cats/effect/std/AtomicCellSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/AtomicCellSuite.scala @@ -22,7 +22,7 @@ import org.specs2.specification.core.Fragments import scala.concurrent.duration._ -final class AtomicCellSpec extends BaseSpec { +final class AtomicCellSuite extends BaseSpec { "AsyncAtomicCell" should { tests(AtomicCell.async) } diff --git a/tests/shared/src/test/scala/cats/effect/std/BackpressureSpec.scala b/tests/shared/src/test/scala/cats/effect/std/BackpressureSuite.scala similarity index 97% rename from tests/shared/src/test/scala/cats/effect/std/BackpressureSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/BackpressureSuite.scala index ad8148b1cf..ebff9e64d4 100644 --- a/tests/shared/src/test/scala/cats/effect/std/BackpressureSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/BackpressureSuite.scala @@ -21,7 +21,7 @@ import cats.syntax.all._ import scala.concurrent.duration._ -class BackpressureSpec extends BaseSpec { +class BackpressureSuite extends BaseSpec { "Backpressure" should { "Lossy Strategy should return IO[None] when no permits are available" in ticked { diff --git a/tests/shared/src/test/scala/cats/effect/std/ConsoleSpec.scala b/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala similarity index 96% rename from tests/shared/src/test/scala/cats/effect/std/ConsoleSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala index 6b6f48f7a0..0a9aadddce 100644 --- a/tests/shared/src/test/scala/cats/effect/std/ConsoleSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala @@ -17,7 +17,7 @@ package cats.effect package std -class ConsoleSpec extends BaseSpec { +class ConsoleSuite extends BaseSpec { sequential "Console" should { diff --git a/tests/shared/src/test/scala/cats/effect/std/CountDownLatchSpec.scala b/tests/shared/src/test/scala/cats/effect/std/CountDownLatchSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/std/CountDownLatchSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/CountDownLatchSuite.scala index cacac0dd4d..13ac8142ef 100644 --- a/tests/shared/src/test/scala/cats/effect/std/CountDownLatchSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/CountDownLatchSuite.scala @@ -32,7 +32,7 @@ import scala.concurrent.duration._ import java.util.concurrent.TimeoutException -class CountDownLatchSpec extends BaseSpec { +class CountDownLatchSuite extends BaseSpec { "CountDownLatch" should { boundedQueueTests("CountDownLatch", CountDownLatch.apply[IO]) diff --git a/tests/shared/src/test/scala/cats/effect/std/CyclicBarrierSpec.scala b/tests/shared/src/test/scala/cats/effect/std/CyclicBarrierSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/std/CyclicBarrierSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/CyclicBarrierSuite.scala index 4877ac5aa9..e53d551df6 100644 --- a/tests/shared/src/test/scala/cats/effect/std/CyclicBarrierSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/CyclicBarrierSuite.scala @@ -24,7 +24,7 @@ import org.specs2.specification.core.Fragments import scala.concurrent.duration._ -class CyclicBarrierSpec extends BaseSpec { +class CyclicBarrierSuite extends BaseSpec { "Cyclic barrier" should { cyclicBarrierTests("Cyclic barrier", CyclicBarrier.apply) diff --git a/tests/shared/src/test/scala/cats/effect/std/DequeueSpec.scala b/tests/shared/src/test/scala/cats/effect/std/DequeueSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/std/DequeueSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/DequeueSuite.scala index b936db25c8..9bbd6233a0 100644 --- a/tests/shared/src/test/scala/cats/effect/std/DequeueSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/DequeueSuite.scala @@ -26,7 +26,7 @@ import org.specs2.specification.core.Fragments import scala.collection.immutable.{Queue => ScalaQueue} import scala.concurrent.duration._ -class BoundedDequeueSpec extends BaseSpec with DequeueTests { +class BoundedDequeueSuite extends BaseSpec with DequeueTests { sequential override def executionTimeout = 20.seconds @@ -161,7 +161,7 @@ class BoundedDequeueSpec extends BaseSpec with DequeueTests { } } -class UnboundedDequeueSpec extends BaseSpec with QueueTests[Dequeue] { +class UnboundedDequeueSuite extends BaseSpec with QueueTests[Dequeue] { sequential "UnboundedDequeue (forward)" should { diff --git a/tests/shared/src/test/scala/cats/effect/std/DispatcherSpec.scala b/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/std/DispatcherSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala index c7c5146677..9e3c780855 100644 --- a/tests/shared/src/test/scala/cats/effect/std/DispatcherSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala @@ -26,7 +26,7 @@ import scala.concurrent.duration._ import java.util.concurrent.atomic.AtomicInteger -class DispatcherSpec extends BaseSpec with DetectPlatform { +class DispatcherSuite extends BaseSpec with DetectPlatform { override def executionTimeout = 30.seconds diff --git a/tests/shared/src/test/scala/cats/effect/std/EnvSpec.scala b/tests/shared/src/test/scala/cats/effect/std/EnvSuite.scala similarity index 96% rename from tests/shared/src/test/scala/cats/effect/std/EnvSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/EnvSuite.scala index dd98329325..b0b4526126 100644 --- a/tests/shared/src/test/scala/cats/effect/std/EnvSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/EnvSuite.scala @@ -17,7 +17,7 @@ package cats.effect package std -class EnvSpec extends BaseSpec { +class EnvSuite extends BaseSpec { "Env" should { "retrieve a variable from the environment" in real { diff --git a/tests/shared/src/test/scala/cats/effect/std/HotswapSpec.scala b/tests/shared/src/test/scala/cats/effect/std/HotswapSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/std/HotswapSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/HotswapSuite.scala index 1eb6fa8f8f..32572b67b8 100644 --- a/tests/shared/src/test/scala/cats/effect/std/HotswapSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/HotswapSuite.scala @@ -25,7 +25,7 @@ import cats.effect.unsafe.IORuntimeConfig import scala.concurrent.duration._ -class HotswapSpec extends BaseSpec { outer => +class HotswapSuite extends BaseSpec { outer => sequential diff --git a/tests/shared/src/test/scala/cats/effect/std/MapRefSpec.scala b/tests/shared/src/test/scala/cats/effect/std/MapRefSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/std/MapRefSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/MapRefSuite.scala index b4cc3b2162..280196bdd1 100644 --- a/tests/shared/src/test/scala/cats/effect/std/MapRefSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/MapRefSuite.scala @@ -23,7 +23,7 @@ import cats.implicits._ import scala.concurrent.duration._ -class MapRefSpec extends BaseSpec { +class MapRefSuite extends BaseSpec { private val smallDelay: IO[Unit] = IO.sleep(20.millis) private def awaitEqual[A: Eq](t: IO[A], success: A): IO[Unit] = t.flatMap(a => if (Eq[A].eqv(a, success)) IO.unit else smallDelay *> awaitEqual(t, success)) diff --git a/tests/shared/src/test/scala/cats/effect/std/MutexSpec.scala b/tests/shared/src/test/scala/cats/effect/std/MutexSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/std/MutexSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/MutexSuite.scala index e92deffd22..f1fb25829f 100644 --- a/tests/shared/src/test/scala/cats/effect/std/MutexSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/MutexSuite.scala @@ -25,7 +25,7 @@ import org.specs2.specification.core.Fragments import scala.concurrent.duration._ -final class MutexSpec extends BaseSpec with DetectPlatform { +final class MutexSuite extends BaseSpec with DetectPlatform { final override def executionTimeout = 2.minutes diff --git a/tests/shared/src/test/scala/cats/effect/std/PQueueSpec.scala b/tests/shared/src/test/scala/cats/effect/std/PQueueSuite.scala similarity index 97% rename from tests/shared/src/test/scala/cats/effect/std/PQueueSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/PQueueSuite.scala index 9824cfc117..3bcf7b8f6b 100644 --- a/tests/shared/src/test/scala/cats/effect/std/PQueueSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/PQueueSuite.scala @@ -32,7 +32,7 @@ import org.specs2.specification.core.Fragments import scala.collection.immutable.{Queue => ScalaQueue} import scala.concurrent.duration._ -class BoundedPQueueSpec extends BaseSpec with PQueueTests { +class BoundedPQueueSuite extends BaseSpec with PQueueTests { override def executionTimeout = 20.seconds @@ -114,7 +114,7 @@ class BoundedPQueueSpec extends BaseSpec with PQueueTests { } } -class UnboundedPQueueSpec extends BaseSpec with PQueueTests { +class UnboundedPQueueSuite extends BaseSpec with PQueueTests { sequential override def executionTimeout = 20.seconds diff --git a/tests/shared/src/test/scala/cats/effect/std/QueueSpec.scala b/tests/shared/src/test/scala/cats/effect/std/QueueSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/std/QueueSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/QueueSuite.scala index 7f66f2c718..02b519ebb7 100644 --- a/tests/shared/src/test/scala/cats/effect/std/QueueSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/QueueSuite.scala @@ -31,7 +31,7 @@ import org.specs2.specification.core.Fragments import scala.collection.immutable.{Queue => ScalaQueue} import scala.concurrent.duration._ -class BoundedQueueSpec extends BaseSpec with QueueTests[Queue] with DetectPlatform { +class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatform { "BoundedQueue (concurrent)" should { boundedQueueTests(i => if (i == 0) Queue.synchronous else Queue.boundedForConcurrent(i)) @@ -378,7 +378,7 @@ class BoundedQueueSpec extends BaseSpec with QueueTests[Queue] with DetectPlatfo } } -class UnboundedQueueSpec extends BaseSpec with QueueTests[Queue] { +class UnboundedQueueSuite extends BaseSpec with QueueTests[Queue] { sequential "UnboundedQueue (concurrent)" should { @@ -415,7 +415,7 @@ class UnboundedQueueSpec extends BaseSpec with QueueTests[Queue] { } } -class DroppingQueueSpec extends BaseSpec with QueueTests[Queue] { +class DroppingQueueSuite extends BaseSpec with QueueTests[Queue] { sequential "DroppingQueue (concurrent)" should { @@ -443,7 +443,7 @@ class DroppingQueueSpec extends BaseSpec with QueueTests[Queue] { } } -class CircularBufferQueueSpec extends BaseSpec with QueueTests[Queue] { +class CircularBufferQueueSuite extends BaseSpec with QueueTests[Queue] { sequential "CircularBuffer" should { diff --git a/tests/shared/src/test/scala/cats/effect/std/RandomSpec.scala b/tests/shared/src/test/scala/cats/effect/std/RandomSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/std/RandomSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/RandomSuite.scala index 039f7a30d4..dc325e7b8c 100644 --- a/tests/shared/src/test/scala/cats/effect/std/RandomSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/RandomSuite.scala @@ -21,7 +21,7 @@ import org.specs2.specification.core.Fragments import scala.annotation.nowarn -class RandomSpec extends BaseSpec { +class RandomSuite extends BaseSpec { "scala.util.Random" should { testRandom(Random.scalaUtilRandom[IO]) } diff --git a/tests/shared/src/test/scala/cats/effect/std/SecureRandomSpec.scala b/tests/shared/src/test/scala/cats/effect/std/SecureRandomSuite.scala similarity index 97% rename from tests/shared/src/test/scala/cats/effect/std/SecureRandomSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/SecureRandomSuite.scala index ffc3dccd42..0320314979 100644 --- a/tests/shared/src/test/scala/cats/effect/std/SecureRandomSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/SecureRandomSuite.scala @@ -17,7 +17,7 @@ package cats.effect package std -class SecureRandomSpec extends BaseSpec { +class SecureRandomSuite extends BaseSpec { "SecureRandom" should { "securely generate random bytes" in real { diff --git a/tests/shared/src/test/scala/cats/effect/std/SemaphoreSpec.scala b/tests/shared/src/test/scala/cats/effect/std/SemaphoreSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/std/SemaphoreSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/SemaphoreSuite.scala index 43d4c46647..6651b8585f 100644 --- a/tests/shared/src/test/scala/cats/effect/std/SemaphoreSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/SemaphoreSuite.scala @@ -25,7 +25,7 @@ import org.specs2.specification.core.Fragments import scala.concurrent.duration._ -class SemaphoreSpec extends BaseSpec { outer => +class SemaphoreSuite extends BaseSpec { outer => "Semaphore" should { tests(n => Semaphore[IO](n)) diff --git a/tests/shared/src/test/scala/cats/effect/std/SupervisorSpec.scala b/tests/shared/src/test/scala/cats/effect/std/SupervisorSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/std/SupervisorSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/SupervisorSuite.scala index d33f6e8030..5edb00906f 100644 --- a/tests/shared/src/test/scala/cats/effect/std/SupervisorSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/SupervisorSuite.scala @@ -23,7 +23,7 @@ import org.specs2.specification.core.Fragments import scala.concurrent.duration._ -class SupervisorSpec extends BaseSpec with DetectPlatform { +class SupervisorSuite extends BaseSpec with DetectPlatform { "Supervisor" should { "concurrent" >> { diff --git a/tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSpec.scala b/tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSuite.scala similarity index 96% rename from tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSuite.scala index d730c72b53..1c7b585242 100644 --- a/tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSuite.scala @@ -17,7 +17,7 @@ package cats.effect package std -class SystemPropertiesSpec extends BaseSpec { +class SystemPropertiesSuite extends BaseSpec { "SystemProperties" should { "retrieve a property just set" in real { diff --git a/tests/shared/src/test/scala/cats/effect/std/UUIDGenSpec.scala b/tests/shared/src/test/scala/cats/effect/std/UUIDGenSuite.scala similarity index 96% rename from tests/shared/src/test/scala/cats/effect/std/UUIDGenSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/UUIDGenSuite.scala index 07d0a2a1dd..1d769e5f6a 100644 --- a/tests/shared/src/test/scala/cats/effect/std/UUIDGenSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/UUIDGenSuite.scala @@ -17,7 +17,7 @@ package cats.effect package std -class UUIDGenSpec extends BaseSpec { +class UUIDGenSuite extends BaseSpec { "UUIDGen" should { "securely generate UUIDs" in real { diff --git a/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSpec.scala b/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSuite.scala index 6905fbb123..425e3d7419 100644 --- a/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSuite.scala @@ -21,7 +21,7 @@ import cats.syntax.all._ import scala.concurrent.duration._ -class UnsafeBoundedSpec extends BaseSpec { +class UnsafeBoundedSuite extends BaseSpec { import Queue.UnsafeBounded override def executionTimeout = 30.seconds diff --git a/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSpec.scala b/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSuite.scala similarity index 97% rename from tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSuite.scala index a655c11364..755e97920d 100644 --- a/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSuite.scala @@ -19,7 +19,7 @@ package std import cats.syntax.all._ -class UnsafeUnboundedSpec extends BaseSpec { +class UnsafeUnboundedSuite extends BaseSpec { "unsafe unbounded queue" should { val length = 1000 diff --git a/tests/shared/src/test/scala/cats/effect/std/internal/BankersQueueSpec.scala b/tests/shared/src/test/scala/cats/effect/std/internal/BankersQueueSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/std/internal/BankersQueueSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/internal/BankersQueueSuite.scala index 5224b7d2a8..679d07d8a1 100644 --- a/tests/shared/src/test/scala/cats/effect/std/internal/BankersQueueSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/internal/BankersQueueSuite.scala @@ -27,7 +27,7 @@ import org.scalacheck.Arbitrary.arbitrary import org.specs2.ScalaCheck import org.specs2.mutable.Specification -class BankersQueueSpec extends Specification with ScalaCheck { +class BankersQueueSuite extends Specification with ScalaCheck { "bankers queue" should { /* diff --git a/tests/shared/src/test/scala/cats/effect/std/internal/BinomialHeapSpec.scala b/tests/shared/src/test/scala/cats/effect/std/internal/BinomialHeapSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/std/internal/BinomialHeapSpec.scala rename to tests/shared/src/test/scala/cats/effect/std/internal/BinomialHeapSuite.scala index 2b50cddb3f..5cf057e9d0 100644 --- a/tests/shared/src/test/scala/cats/effect/std/internal/BinomialHeapSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/std/internal/BinomialHeapSuite.scala @@ -28,7 +28,7 @@ import org.scalacheck.Arbitrary.arbitrary import org.specs2.ScalaCheck import org.specs2.mutable.Specification -class BinomialHeapSpec extends Specification with ScalaCheck { +class BinomialHeapSuite extends Specification with ScalaCheck { implicit val orderForInt: Order[Int] = Order.fromLessThan((x, y) => x < y) diff --git a/tests/shared/src/test/scala/cats/effect/testkit/TestControlSpec.scala b/tests/shared/src/test/scala/cats/effect/testkit/TestControlSuite.scala similarity index 99% rename from tests/shared/src/test/scala/cats/effect/testkit/TestControlSpec.scala rename to tests/shared/src/test/scala/cats/effect/testkit/TestControlSuite.scala index 7f1ee0c736..fdfe0ee91b 100644 --- a/tests/shared/src/test/scala/cats/effect/testkit/TestControlSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/testkit/TestControlSuite.scala @@ -25,7 +25,7 @@ import org.specs2.matcher.Matcher import scala.concurrent.CancellationException import scala.concurrent.duration._ -class TestControlSpec extends BaseSpec { +class TestControlSuite extends BaseSpec { val simple = IO.unit diff --git a/tests/shared/src/test/scala/cats/effect/tracing/TraceSpec.scala b/tests/shared/src/test/scala/cats/effect/tracing/TraceSuite.scala similarity index 95% rename from tests/shared/src/test/scala/cats/effect/tracing/TraceSpec.scala rename to tests/shared/src/test/scala/cats/effect/tracing/TraceSuite.scala index 641ca350ff..cc28587a55 100644 --- a/tests/shared/src/test/scala/cats/effect/tracing/TraceSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/tracing/TraceSuite.scala @@ -20,7 +20,7 @@ import cats.effect.{BaseSpec, DetectPlatform, IO} import cats.effect.testkit.TestInstances // Separate from TracingSpec so it can exist outside of cats.effect package -class TraceSpec extends BaseSpec with TestInstances with DetectPlatform { self => +class TraceSuite extends BaseSpec with TestInstances with DetectPlatform { self => "IO" should { if (!isJS || !isWSL) { diff --git a/tests/shared/src/test/scala/cats/effect/tracing/TracingSpec.scala b/tests/shared/src/test/scala/cats/effect/tracing/TracingSuite.scala similarity index 96% rename from tests/shared/src/test/scala/cats/effect/tracing/TracingSpec.scala rename to tests/shared/src/test/scala/cats/effect/tracing/TracingSuite.scala index b143b1ed59..cf997256b0 100644 --- a/tests/shared/src/test/scala/cats/effect/tracing/TracingSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/tracing/TracingSuite.scala @@ -19,7 +19,7 @@ package cats.effect.tracing import cats.effect.{Async, BaseSpec, IO} import cats.effect.testkit.TestInstances -class TracingSpec extends BaseSpec with TestInstances { +class TracingSuite extends BaseSpec with TestInstances { "IO.delay" should { "generate identical traces" in { diff --git a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSpec.scala b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSuite.scala similarity index 93% rename from tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSpec.scala rename to tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSuite.scala index 4a15824150..804ee8addb 100644 --- a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSuite.scala @@ -17,7 +17,7 @@ package cats.effect package unsafe -class IORuntimeBuilderSpec extends BaseSpec with DetectPlatform { +class IORuntimeBuilderSuite extends BaseSpec with DetectPlatform { "IORuntimeBuilder" should { if (isNative) "configure the failure reporter" in pending diff --git a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSpec.scala b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSuite.scala similarity index 98% rename from tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSpec.scala rename to tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSuite.scala index f3ad1db7cf..a66761150f 100644 --- a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSuite.scala @@ -19,7 +19,7 @@ package unsafe import scala.util.Try -class IORuntimeConfigSpec extends BaseSpec { +class IORuntimeConfigSuite extends BaseSpec { "IORuntimeConfig" should { diff --git a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSpec.scala b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSuite.scala similarity index 96% rename from tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSpec.scala rename to tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSuite.scala index a7db35fe3e..8595cd879c 100644 --- a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSuite.scala @@ -18,7 +18,7 @@ package cats.effect.unsafe import cats.effect.BaseSpec -class IORuntimeSpec extends BaseSpec { +class IORuntimeSuite extends BaseSpec { "IORuntimeSpec" should { "cleanup allRuntimes collection on shutdown" in { From 37a55733bde9e24066e4a13a8fa5081167ca6742 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 27 Dec 2024 09:57:51 +0200 Subject: [PATCH 05/20] Replace specs2 with munit --- build.sbt | 7 +- ioapp-tests/src/test/scala/IOAppSpec.scala | 187 +- .../cats/effect/laws/GenTemporalSuite.scala | 4 +- .../scala/cats/effect/std/SyntaxSpec.scala | 1 + .../cats/effect/IOPlatformSpecification.scala | 57 - .../scala/cats/effect/IOPlatformSuite.scala | 55 + .../cats/effect/SyncIOPlatformSuite.scala} | 24 +- .../cats/effect/std/ConsoleJSSuite.scala | 14 +- .../BatchingMacrotaskExecutorSuite.scala | 68 +- .../effect/unsafe/JSArrayQueueSuite.scala | 79 +- .../cats/effect/unsafe/SchedulerSuite.scala | 66 +- .../effect/unsafe/StripedHashtableSuite.scala | 52 +- .../cats/effect/SyncIOPlatformSuite.scala} | 24 +- .../cats/effect/IOPlatformSpecification.scala | 569 --- .../scala/cats/effect/IOPlatformSuite.scala | 567 +++ .../scala/cats/effect/ParasiticECSuite.scala | 24 +- .../scala/cats/effect/ResourceJVMSuite.scala | 126 +- .../scala/cats/effect/RunnersPlatform.scala | 37 +- .../scala/cats/effect/SelectorSuite.scala | 115 +- .../effect/kernel/AsyncPlatformSuite.scala | 72 +- .../cats/effect/std/ConsoleJVMSuite.scala | 185 +- .../cats/effect/std/DeferredJVMSuite.scala | 38 +- .../cats/effect/std/DispatcherJVMSuite.scala | 66 +- .../cats/effect/std/MapRefJVMSuite.scala | 280 +- .../effect/unsafe/BlockingStressSuite.scala | 37 +- .../cats/effect/unsafe/DrainBatchSuite.scala | 48 +- .../effect/unsafe/FiberMonitorSuite.scala | 48 +- .../effect/unsafe/HelperThreadParkSuite.scala | 76 +- .../cats/effect/unsafe/IOLocalsSuite.scala | 53 +- .../cats/effect/unsafe/SleepersSuite.scala | 153 +- .../effect/unsafe/StripedHashtableSuite.scala | 46 +- .../cats/effect/unsafe/TimerHeapSuite.scala | 218 +- .../effect/unsafe/WorkerThreadNameSuite.scala | 12 +- .../effect/FileDescriptorPollerSuite.scala | 65 +- ...cification.scala => IOPlatformSuite.scala} | 10 +- .../cats/effect/unsafe/SchedulerSuite.scala | 34 +- .../cats/effect/IOImplicitSuite.scala | 6 +- .../not/cats/effect/IOParImplicitSuite.scala | 8 +- .../test/scala/cats/effect/BaseSuite.scala | 6 +- .../cats/effect/CallbackStackSuite.scala | 54 +- .../test/scala/cats/effect/ContSuite.scala | 40 +- .../scala/cats/effect/EitherTIOSuite.scala | 4 +- .../test/scala/cats/effect/IOFiberSuite.scala | 80 +- .../test/scala/cats/effect/IOLocalSuite.scala | 70 +- .../scala/cats/effect/IOMtlLocalSuite.scala | 7 +- .../test/scala/cats/effect/IOParSuite.scala | 7 +- .../test/scala/cats/effect/IOPropSuite.scala | 156 +- .../src/test/scala/cats/effect/IOSuite.scala | 3396 +++++++++-------- .../test/scala/cats/effect/IorTIOSuite.scala | 86 +- .../scala/cats/effect/KleisliIOSuite.scala | 102 +- .../test/scala/cats/effect/MemoizeSuite.scala | 323 +- .../scala/cats/effect/OptionTIOSuite.scala | 74 +- .../scala/cats/effect/ResourceSuite.scala | 1771 ++++----- .../src/test/scala/cats/effect/Runners.scala | 116 +- .../scala/cats/effect/StateTIOSuite.scala | 45 +- .../test/scala/cats/effect/SyncIOSuite.scala | 308 +- .../test/scala/cats/effect/ThunkSuite.scala | 12 +- .../scala/cats/effect/WriterTIOSuite.scala | 77 +- .../scala/cats/effect/kernel/AsyncSuite.scala | 127 +- .../cats/effect/kernel/DeferredSuite.scala | 55 +- .../kernel/DerivationRefinementSuite.scala | 147 +- .../cats/effect/kernel/LensRefSuite.scala | 305 +- .../effect/kernel/MiniSemaphoreSuite.scala | 122 +- .../cats/effect/kernel/ParallelFSuite.scala | 16 +- .../scala/cats/effect/kernel/RefSuite.scala | 245 +- .../cats/effect/std/AtomicCellSuite.scala | 156 +- .../cats/effect/std/BackpressureSuite.scala | 51 +- .../scala/cats/effect/std/ConsoleSuite.scala | 30 +- .../cats/effect/std/CountDownLatchSuite.scala | 61 +- .../cats/effect/std/CyclicBarrierSuite.scala | 48 +- .../scala/cats/effect/std/DequeueSuite.scala | 260 +- .../cats/effect/std/DispatcherSuite.scala | 366 +- .../test/scala/cats/effect/std/EnvSuite.scala | 20 +- .../scala/cats/effect/std/HotswapSuite.scala | 223 +- .../scala/cats/effect/std/MapRefSuite.scala | 410 +- .../scala/cats/effect/std/MutexSuite.scala | 113 +- .../scala/cats/effect/std/PQueueSuite.scala | 91 +- .../scala/cats/effect/std/QueueSuite.scala | 625 ++- .../scala/cats/effect/std/RandomSuite.scala | 462 +-- .../cats/effect/std/SecureRandomSuite.scala | 36 +- .../cats/effect/std/SemaphoreSuite.scala | 94 +- .../cats/effect/std/SupervisorSuite.scala | 119 +- .../effect/std/SystemPropertiesSuite.scala | 29 +- .../scala/cats/effect/std/UUIDGenSuite.scala | 26 +- .../cats/effect/std/UnsafeBoundedSuite.scala | 94 +- .../effect/std/UnsafeUnboundedSuite.scala | 83 +- .../std/internal/BankersQueueSuite.scala | 44 +- .../std/internal/BinomialHeapSuite.scala | 54 +- .../effect/testkit/TestControlSuite.scala | 265 +- .../cats/effect/tracing/TraceSuite.scala | 42 +- .../cats/effect/tracing/TracingSuite.scala | 64 +- .../effect/unsafe/IORuntimeBuilderSuite.scala | 18 +- .../effect/unsafe/IORuntimeConfigSuite.scala | 63 +- .../cats/effect/unsafe/IORuntimeSuite.scala | 19 +- 94 files changed, 7526 insertions(+), 7722 deletions(-) delete mode 100644 tests/js/src/test/scala/cats/effect/IOPlatformSpecification.scala create mode 100644 tests/js/src/test/scala/cats/effect/IOPlatformSuite.scala rename tests/{jvm-native/src/test/scala/cats/effect/SyncIOPlatformSpecification.scala => js/src/test/scala/cats/effect/SyncIOPlatformSuite.scala} (55%) rename tests/{js/src/test/scala/cats/effect/SyncIOPlatformSpecification.scala => jvm-native/src/test/scala/cats/effect/SyncIOPlatformSuite.scala} (55%) delete mode 100644 tests/jvm/src/test/scala/cats/effect/IOPlatformSpecification.scala create mode 100644 tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala rename tests/native/src/test/scala/cats/effect/{IOPlatformSpecification.scala => IOPlatformSuite.scala} (75%) diff --git a/build.sbt b/build.sbt index 1c6bf4c4ce..50c804bb87 100644 --- a/build.sbt +++ b/build.sbt @@ -302,7 +302,6 @@ ThisBuild / autoAPIMappings := true val CatsVersion = "2.11.0" val CatsMtlVersion = "1.3.1" -val Specs2Version = "4.20.5" val ScalaCheckVersion = "1.17.1" val CoopVersion = "1.2.0" val MUnitVersion = "1.0.0-M11" @@ -922,10 +921,6 @@ lazy val tests: CrossProject = crossProject(JSPlatform, JVMPlatform, NativePlatf name := "cats-effect-tests", libraryDependencies ++= Seq( "org.scalacheck" %%% "scalacheck" % ScalaCheckVersion, - - "org.specs2" %%% "specs2-scalacheck" % Specs2Version % Test, - "org.typelevel" %%% "discipline-specs2" % "1.4.0" % Test, - "org.scalameta" %%% "munit" % MUnitVersion % Test, "org.scalameta" %%% "munit-scalacheck" % MUnitScalaCheckVersion % Test, "org.typelevel" %%% "discipline-munit" % DisciplineMUnitVersion % Test, @@ -952,7 +947,7 @@ def configureIOAppTests(p: Project): Project = p.enablePlugins(NoPublishPlugin, BuildInfoPlugin) .settings( Test / unmanagedSourceDirectories += (LocalRootProject / baseDirectory).value / "ioapp-tests" / "src" / "test" / "scala", - libraryDependencies += "org.specs2" %%% "specs2-core" % Specs2Version % Test, + libraryDependencies += "org.scalameta" %%% "munit" % MUnitVersion % Test, buildInfoPackage := "cats.effect", buildInfoKeys ++= Seq( "jsRunner" -> (tests.js / Compile / fastOptJS / artifactPath).value, diff --git a/ioapp-tests/src/test/scala/IOAppSpec.scala b/ioapp-tests/src/test/scala/IOAppSpec.scala index 11527ae72c..0ab5cec144 100644 --- a/ioapp-tests/src/test/scala/IOAppSpec.scala +++ b/ioapp-tests/src/test/scala/IOAppSpec.scala @@ -16,14 +16,14 @@ package cats.effect -import org.specs2.mutable.Specification - import scala.io.Source import scala.sys.process.{BasicIO, Process, ProcessBuilder} import java.io.File -class IOAppSpec extends Specification { +import munit.FunSuite + +class IOAppSpec extends FunSuite { abstract class Platform(val id: String) { outer => def builder(proto: String, args: List[String]): ProcessBuilder @@ -145,118 +145,123 @@ class IOAppSpec extends Specification { platform == JVM && sys.props.get("java.version").filter(_.startsWith("1.8")).isDefined lazy val isWindows = System.getProperty("os.name").toLowerCase.contains("windows") - s"IOApp (${platform.id})" should { + { if (!isWindows) { // these tests have all been emperically flaky on Windows CI builds, so they're disabled - "evaluate and print hello world" in { + test(s"IOApp (${platform.id}) - evaluate and print hello world") { val h = platform("HelloWorld", Nil) - h.awaitStatus() mustEqual 0 - h.stdout() mustEqual s"Hello, World!${System.lineSeparator()}" + assertEquals(h.awaitStatus(), 0) + assertEquals(h.stdout(), s"Hello, World!${System.lineSeparator()}") } - "pass all arguments to child" in { + test(s"IOApp (${platform.id}) - pass all arguments to child") { val expected = List("the", "quick", "brown", "fox jumped", "over") val h = platform("Arguments", expected) - h.awaitStatus() mustEqual 0 - h.stdout() mustEqual expected.mkString( - "", - System.lineSeparator(), - System.lineSeparator()) + assertEquals(h.awaitStatus(), 0) + assertEquals( + h.stdout(), + expected.mkString("", System.lineSeparator(), System.lineSeparator()) + ) } - "exit on non-fatal error" in { + test(s"IOApp (${platform.id}) - exit on non-fatal error") { val h = platform("NonFatalError", List.empty) - h.awaitStatus() mustEqual 1 - h.stderr() must contain("Boom!") + assertEquals(h.awaitStatus(), 1) + assert(h.stderr().contains("Boom!")) } - "exit with leaked fibers" in { + test(s"IOApp (${platform.id}) - exit with leaked fibers") { val h = platform("LeakedFiber", List.empty) - h.awaitStatus() mustEqual 0 + assertEquals(h.awaitStatus(), 0) } - "exit on fatal error" in { + test(s"IOApp (${platform.id}) - exit on fatal error") { val h = platform("FatalError", List.empty) - h.awaitStatus() mustEqual 1 - h.stderr() must contain("Boom!") - h.stdout() must not(contain("sadness")) + assertEquals(h.awaitStatus(), 1) + assert(h.stderr().contains("Boom!")) + assert(!h.stdout().contains("sadness")) } - "exit on fatal error with other unsafe runs" in { + test(s"IOApp (${platform.id}) - exit on fatal error with other unsafe runs") { val h = platform("FatalErrorUnsafeRun", List.empty) - h.awaitStatus() mustEqual 1 - h.stderr() must contain("Boom!") + assertEquals(h.awaitStatus(), 1) + assert(h.stderr().contains("Boom!")) } - "exit on raising a fatal error with attempt" in { + test(s"IOApp (${platform.id}) - exit on raising a fatal error with attempt") { val h = platform("RaiseFatalErrorAttempt", List.empty) - h.awaitStatus() mustEqual 1 - h.stderr() must contain("Boom!") - h.stdout() must not(contain("sadness")) + assertEquals(h.awaitStatus(), 1) + assert(h.stderr().contains("Boom!")) + assert(!h.stdout().contains("sadness")) } - "exit on raising a fatal error with handleError" in { + test(s"IOApp (${platform.id}) - exit on raising a fatal error with handleError") { val h = platform("RaiseFatalErrorHandle", List.empty) - h.awaitStatus() mustEqual 1 - h.stderr() must contain("Boom!") - h.stdout() must not(contain("sadness")) + assertEquals(h.awaitStatus(), 1) + assert(h.stderr().contains("Boom!")) + assert(!h.stdout().contains("sadness")) } - "exit on raising a fatal error inside a map" in { + test(s"IOApp (${platform.id}) - exit on raising a fatal error inside a map") { val h = platform("RaiseFatalErrorMap", List.empty) - h.awaitStatus() mustEqual 1 - h.stderr() must contain("Boom!") - h.stdout() must not(contain("sadness")) + assertEquals(h.awaitStatus(), 1) + assert(h.stderr().contains("Boom!")) + assert(!h.stdout().contains("sadness")) } - "exit on raising a fatal error inside a flatMap" in { + test(s"IOApp (${platform.id}) - exit on raising a fatal error inside a flatMap") { val h = platform("RaiseFatalErrorFlatMap", List.empty) - h.awaitStatus() mustEqual 1 - h.stderr() must contain("Boom!") - h.stdout() must not(contain("sadness")) + assertEquals(h.awaitStatus(), 1) + assert(h.stderr().contains("Boom!")) + assert(!h.stdout().contains("sadness")) } - "warn on global runtime collision" in { + test(s"IOApp (${platform.id}) - warn on global runtime collision") { val h = platform("GlobalRacingInit", List.empty) - h.awaitStatus() mustEqual 0 - h.stderr() must contain( - "Cats Effect global runtime already initialized; custom configurations will be ignored") - h.stderr() must not(contain("boom")) + assertEquals(h.awaitStatus(), 0) + assert( + h.stderr() + .contains( + "Cats Effect global runtime already initialized; custom configurations will be ignored")) + assert(!h.stderr().contains("boom")) } - "reset global runtime on shutdown" in { + test(s"IOApp (${platform.id}) - reset global runtime on shutdown") { val h = platform("GlobalShutdown", List.empty) - h.awaitStatus() mustEqual 0 - h.stderr() must not contain - "Cats Effect global runtime already initialized; custom configurations will be ignored" - h.stderr() must not(contain("boom")) + assertEquals(h.awaitStatus(), 0) + assert( + !h.stderr() + .contains( + "Cats Effect global runtime already initialized; custom configurations will be ignored")) + assert(!h.stderr().contains("boom")) } // TODO reenable this test (#3919) - "warn on cpu starvation" in skipped { + test(s"IOApp (${platform.id}) - warn on cpu starvation".ignore) { val h = platform("CpuStarvation", List.empty) h.awaitStatus() val err = h.stderr() - err must not(contain("[WARNING] Failed to register Cats Effect CPU")) - err must contain("[WARNING] Your app's responsiveness") + assert(!err.contains("[WARNING] Failed to register Cats Effect CPU")) + assert(err.contains("[WARNING] Your app's responsiveness")) // we use a regex because time has too many corner cases - a test run at just the wrong // moment on new year's eve, etc - err must beMatching( - // (?s) allows matching across line breaks - """(?s)^\d{4}-[01]\d-[0-3]\dT[012]\d:[0-6]\d:[0-6]\d(?:\.\d{1,3})?Z \[WARNING\] Your app's responsiveness.*""" - ) + assert( + err.matches( + // (?s) allows matching across line breaks + """(?s)^\d{4}-[01]\d-[0-3]\dT[012]\d:[0-6]\d:[0-6]\d(?:\.\d{1,3})?Z \[WARNING\] Your app's responsiveness.*""" + )) } - "custom runtime installed as global" in { + test(s"IOApp (${platform.id}) - custom runtime installed as global") { val h = platform("CustomRuntime", List.empty) - h.awaitStatus() mustEqual 0 + assertEquals(h.awaitStatus(), 0) } if (platform != Native) { - "abort awaiting shutdown hooks" in { + test(s"IOApp (${platform.id}) - abort awaiting shutdown hooks") { val h = platform("ShutdownHookImmediateTimeout", List.empty) - h.awaitStatus() mustEqual 0 + assertEquals(h.awaitStatus(), 0) } () } @@ -266,7 +271,7 @@ class IOAppSpec extends Specification { // The jvm cannot gracefully terminate processes on Windows, so this // test cannot be carried out properly. Same for testing IOApp in sbt. - "run finalizers on TERM" in { + test(s"IOApp (${platform.id}) - run finalizers on TERM") { import _root_.java.io.{BufferedReader, FileReader} // we have to resort to this convoluted approach because Process#destroy kills listeners before killing the process @@ -293,32 +298,32 @@ class IOAppSpec extends Specification { ) // give thread scheduling just a sec to catch up and get us into the latch.await() h.term() - h.awaitStatus() mustEqual 143 + assertEquals(h.awaitStatus(), 143) i = 0 while (readTest() == null && i < 100) { i += 1 } - readTest() must contain("canceled") + assert(readTest().contains("canceled")) } } else () - "exit on fatal error without IOApp" in { + test(s"IOApp (${platform.id}) - exit on fatal error without IOApp") { val h = platform("FatalErrorRaw", List.empty) h.awaitStatus() - h.stdout() must not(contain("sadness")) - h.stderr() must not(contain("Promise already completed")) + assert(!h.stdout().contains("sadness")) + assert(!h.stderr().contains("Promise already completed")) } - "exit on canceled" in { + test(s"IOApp (${platform.id}) - exit on canceled") { val h = platform("Canceled", List.empty) - h.awaitStatus() mustEqual 1 + assertEquals(h.awaitStatus(), 1) } if (!isJava8 && !isWindows && platform != Native) { // JDK 8 does not have free signals for live fiber snapshots // cannot observe signals sent to process termination on Windows - "live fiber snapshot" in { + test(s"IOApp (${platform.id}) - live fiber snapshot") { val h = platform("LiveFiberSnapshot", List.empty) // wait for the application to fully start before trying to send the signal @@ -327,60 +332,56 @@ class IOAppSpec extends Specification { } val pid = h.pid() - pid must beSome + assert(pid.isDefined) pid.foreach(platform.sendSignal) h.awaitStatus() val stderr = h.stderr() - stderr must contain("cats.effect.IOFiber") + assert(stderr.contains("cats.effect.IOFiber")) } () } if (platform == JVM) { - "shutdown on worker thread interruption" in { + test(s"IOApp (${platform.id}) - shutdown on worker thread interruption") { val h = platform("WorkerThreadInterrupt", List.empty) - h.awaitStatus() mustEqual 1 - h.stderr() must contain("java.lang.InterruptedException") - ok + assertEquals(h.awaitStatus(), 1) + assert(h.stderr().contains("java.lang.InterruptedException")) } - "support main thread evaluation" in { + test(s"IOApp (${platform.id}) - support main thread evaluation") { val h = platform("EvalOnMainThread", List.empty) - h.awaitStatus() mustEqual 0 + assertEquals(h.awaitStatus(), 0) } - "use configurable reportFailure for MainThread" in { + test(s"IOApp (${platform.id}) - use configurable reportFailure for MainThread") { val h = platform("MainThreadReportFailure", List.empty) - h.awaitStatus() mustEqual 0 + assertEquals(h.awaitStatus(), 0) } - "warn on blocked threads" in { + test(s"IOApp (${platform.id}) - warn on blocked threads") { val h = platform("BlockedThreads", List.empty) h.awaitStatus() val err = h.stderr() - err must contain( - "[WARNING] A Cats Effect worker thread was detected to be in a blocked state") + assert( + err.contains( + "[WARNING] A Cats Effect worker thread was detected to be in a blocked state")) } - "shut down WSTP on fatal error without IOApp" in { + test(s"IOApp (${platform.id}) - shut down WSTP on fatal error without IOApp") { val h = platform("FatalErrorShutsDownRt", List.empty) h.awaitStatus() - h.stdout() must not(contain("sadness")) - h.stdout() must contain("done") + assert(!h.stdout().contains("sadness")) + assert(h.stdout().contains("done")) } - - () } if (platform == Node) { - "gracefully ignore undefined process.exit" in { + test(s"IOApp (${platform.id}) - gracefully ignore undefined process.exit") { val h = platform("UndefinedProcessExit", List.empty) - h.awaitStatus() mustEqual 0 + assertEquals(h.awaitStatus(), 0) } - () } - "make specs2 happy" in ok } trait Handle { diff --git a/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSuite.scala b/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSuite.scala index 9fd52b890d..cbc8758b3b 100644 --- a/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSuite.scala +++ b/laws/shared/src/test/scala/cats/effect/laws/GenTemporalSuite.scala @@ -23,10 +23,10 @@ import cats.effect.kernel.testkit.TimeT import cats.effect.kernel.testkit.pure._ import cats.syntax.all._ +import scala.concurrent.TimeoutException import scala.concurrent.duration._ -import munit.FunSuite -import scala.concurrent.TimeoutException +import munit.FunSuite class GenTemporalSuite extends FunSuite { outer => diff --git a/std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala b/std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala index 068fe82915..178d5271ae 100644 --- a/std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala +++ b/std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala @@ -17,6 +17,7 @@ package cats.effect.std import cats.effect.kernel.{Async, Concurrent, Deferred, GenConcurrent, Ref, Sync} + import munit.FunSuite class SyntaxSpec extends FunSuite { diff --git a/tests/js/src/test/scala/cats/effect/IOPlatformSpecification.scala b/tests/js/src/test/scala/cats/effect/IOPlatformSpecification.scala deleted file mode 100644 index 81532a55fb..0000000000 --- a/tests/js/src/test/scala/cats/effect/IOPlatformSpecification.scala +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020-2024 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 - -import cats.syntax.all._ - -import org.scalacheck.Prop.forAll -import org.specs2.ScalaCheck - -import scala.scalajs.js - -trait IOPlatformSpecification { self: BaseSpec with ScalaCheck => - - def platformSpecs = - "platform" should { - - "round trip through js.Promise" in ticked { implicit ticker => - forAll { (ioa: IO[Int]) => - ioa.eqv(IO.fromPromise(IO(ioa.unsafeToPromise()))) - }.pendingUntilFixed // "callback scheduling gets in the way here since Promise doesn't use TestContext" - } - - "round trip through js.Promise via Async" in ticked { implicit ticker => - def lossy[F[_]: Async, A](fa: F[A])(f: F[A] => js.Promise[A]): F[A] = - Async[F].fromPromise(Sync[F].delay(f(fa))).map(x => x) - - forAll { (ioa: IO[Int]) => - ioa.eqv(lossy(ioa)(_.unsafeToPromise())) - }.pendingUntilFixed // "callback scheduling gets in the way here since Promise doesn't use TestContext" - } - - "realTimeDate should return a js.Date constructed from realTime" in ticked { - implicit ticker => - val op = for { - jsDate <- IO.realTimeDate - realTime <- IO.realTime - } yield jsDate.getTime().toLong == realTime.toMillis - - op must completeAs(true) - } - - } -} diff --git a/tests/js/src/test/scala/cats/effect/IOPlatformSuite.scala b/tests/js/src/test/scala/cats/effect/IOPlatformSuite.scala new file mode 100644 index 0000000000..30f92f8809 --- /dev/null +++ b/tests/js/src/test/scala/cats/effect/IOPlatformSuite.scala @@ -0,0 +1,55 @@ +/* + * Copyright 2020-2024 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 + +import cats.syntax.all._ + +import org.scalacheck.Prop.forAll + +import scala.scalajs.js + +trait IOPlatformSuite { self: BaseSuite with munit.ScalaCheckSuite => + + def platformTests() = { + + ticked("round trip through js.Promise".ignore) { implicit ticker => + forAll { (ioa: IO[Int]) => + ioa.eqv(IO.fromPromise(IO(ioa.unsafeToPromise()))) + } // "callback scheduling gets in the way here since Promise doesn't use TestContext" + } + + ticked("round trip through js.Promise via Async".ignore) { implicit ticker => + def lossy[F[_]: Async, A](fa: F[A])(f: F[A] => js.Promise[A]): F[A] = + Async[F].fromPromise(Sync[F].delay(f(fa))).map(x => x) + + forAll { (ioa: IO[Int]) => + ioa.eqv(lossy(ioa)(_.unsafeToPromise())) + } // "callback scheduling gets in the way here since Promise doesn't use TestContext" + } + + ticked("realTimeDate should return a js.Date constructed from realTime") { + implicit ticker => + val op = for { + jsDate <- IO.realTimeDate + realTime <- IO.realTime + } yield jsDate.getTime().toLong == realTime.toMillis + + assertCompleteAs(op, true) + } + + } +} diff --git a/tests/jvm-native/src/test/scala/cats/effect/SyncIOPlatformSpecification.scala b/tests/js/src/test/scala/cats/effect/SyncIOPlatformSuite.scala similarity index 55% rename from tests/jvm-native/src/test/scala/cats/effect/SyncIOPlatformSpecification.scala rename to tests/js/src/test/scala/cats/effect/SyncIOPlatformSuite.scala index 13be3abad0..1b80c7f7bf 100644 --- a/tests/jvm-native/src/test/scala/cats/effect/SyncIOPlatformSpecification.scala +++ b/tests/js/src/test/scala/cats/effect/SyncIOPlatformSuite.scala @@ -16,20 +16,20 @@ package cats.effect -trait SyncIOPlatformSpecification { self: BaseSpec => - def platformSpecs = { - "platform" should { - "realTimeInstant should return an Instant constructed from realTime" in { - // Unfortunately since SyncIO doesn't use on a controllable - // clock source, so a diff best we can do - val op = for { - realTime <- SyncIO.realTime - now <- SyncIO.realTimeInstant - } yield (now.toEpochMilli - realTime.toMillis) <= 10000 +trait SyncIOPlatformSuite { self: BaseSuite => + def platformTests() = { - op must completeAsSync(true) - } + test("realTimeDate should return an Instant constructed from realTime") { + // Unfortunately since SyncIO doesn't rely on a controllable + // time source, this is the best I can do + val op = for { + realTime <- SyncIO.realTime + jsDate <- SyncIO.realTimeDate + } yield (jsDate.getTime().toLong - realTime.toMillis) <= 30 + + assertCompleteAsSync(op, true) } + } } diff --git a/tests/js/src/test/scala/cats/effect/std/ConsoleJSSuite.scala b/tests/js/src/test/scala/cats/effect/std/ConsoleJSSuite.scala index 2cde878364..f55245e43e 100644 --- a/tests/js/src/test/scala/cats/effect/std/ConsoleJSSuite.scala +++ b/tests/js/src/test/scala/cats/effect/std/ConsoleJSSuite.scala @@ -17,15 +17,13 @@ package cats.effect package std -class ConsoleJSSuite extends BaseSpec { +class ConsoleJSSuite extends BaseSuite { - "Console" should { - "work in any JS environment" in real { - Console[IO].println("printing") *> Console[IO].errorln("erroring") *> IO(true) - } - "println should not hang for large strings" in real { - Console[IO].println("foo" * 10000).as(true) - } + real("work in any JS environment") { + Console[IO].println("printing") *> Console[IO].errorln("erroring") *> IO(true) + } + real("println should not hang for large strings") { + Console[IO].println("foo" * 10000).as(true) } } diff --git a/tests/js/src/test/scala/cats/effect/unsafe/BatchingMacrotaskExecutorSuite.scala b/tests/js/src/test/scala/cats/effect/unsafe/BatchingMacrotaskExecutorSuite.scala index 0eaae4bc59..89e454a684 100644 --- a/tests/js/src/test/scala/cats/effect/unsafe/BatchingMacrotaskExecutorSuite.scala +++ b/tests/js/src/test/scala/cats/effect/unsafe/BatchingMacrotaskExecutorSuite.scala @@ -24,48 +24,46 @@ import org.scalajs.macrotaskexecutor.MacrotaskExecutor import scala.concurrent.duration._ -class BatchingMacrotaskExecutorSuite extends BaseSpec { +class BatchingMacrotaskExecutorSuite extends BaseSuite { - "BatchingMacrotaskExecutor" should { - "batch fibers" in real { // fails if running on MacrotaskExecutor - CountDownLatch[IO](10).flatMap { latch => - IO.ref(List.empty[Int]).flatMap { ref => - List.range(0, 10).traverse_ { i => - val task = ref.update(_ :+ i) *> latch.release - val taskOnEc = if (i == 0) task.evalOn(MacrotaskExecutor) else task - taskOnEc.start - } *> - latch.await *> - ref.get.map(_ must beEqualTo(List.range(1, 10) :+ 0)) - } + real("batch fibers") { // fails if running on MacrotaskExecutor + CountDownLatch[IO](10).flatMap { latch => + IO.ref(List.empty[Int]).flatMap { ref => + List.range(0, 10).traverse_ { i => + val task = ref.update(_ :+ i) *> latch.release + val taskOnEc = if (i == 0) task.evalOn(MacrotaskExecutor) else task + taskOnEc.start + } *> + latch.await *> + ref.get.map(assertEquals(_, List.range(1, 10) :+ 0)) } } + } - "cede to macrotasks" in real { // fails if running on Promises EC - IO.ref(false) - .flatMap { ref => - ref.set(true).evalOn(MacrotaskExecutor).start *> - (ref.get, IO.cede, ref.get).tupled.start - } - .flatMap { f => - f.join.flatMap(_.embedNever).flatMap { - case (before, (), after) => - IO { - before must beFalse - after must beTrue - } - } + real("cede to macrotasks") { // fails if running on Promises EC + IO.ref(false) + .flatMap { ref => + ref.set(true).evalOn(MacrotaskExecutor).start *> + (ref.get, IO.cede, ref.get).tupled.start + } + .flatMap { f => + f.join.flatMap(_.embedNever).flatMap { + case (before, (), after) => + IO { + assert(!before) + assert(after) + } } - } + } + } - "limit batch sizes" in real { - IO.ref(true).flatMap { continue => - def go: IO[Unit] = continue.get.flatMap { - IO.defer(go).both(IO.defer(go)).void.whenA(_) - } - val stop = IO.sleep(100.millis) *> continue.set(false) - go.both(stop) *> IO(true must beTrue) + real("limit batch sizes") { + IO.ref(true).flatMap { continue => + def go: IO[Unit] = continue.get.flatMap { + IO.defer(go).both(IO.defer(go)).void.whenA(_) } + val stop = IO.sleep(100.millis) *> continue.set(false) + go.both(stop) *> IO(assert(true)) } } diff --git a/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala b/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala index 75706f2586..fac9d79f0c 100644 --- a/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala +++ b/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala @@ -18,60 +18,57 @@ package cats.effect package unsafe import org.scalacheck.Prop.forAll -import org.specs2.ScalaCheck import scala.collection.mutable.{ListBuffer, Queue} -class JSArrayQueueSuite extends BaseSpec with ScalaCheck { +import munit.ScalaCheckSuite - "JSArrayQueue" should { - "be fifo" in { - forAll { (stuff: List[Option[Int]]) => - val queue = new JSArrayQueue[Int] - val taken = new ListBuffer[Int] +class JSArrayQueueSuite extends BaseSuite with ScalaCheckSuite { - stuff.foreach { - case Some(i) => queue.offer(i) - case None => - if (!queue.isEmpty()) taken += queue.take() - } + test("be fifo") { + forAll { (stuff: List[Option[Int]]) => + val queue = new JSArrayQueue[Int] + val taken = new ListBuffer[Int] - while (!queue.isEmpty()) taken += queue.take() - - taken.toList must beEqualTo(stuff.flatten) + stuff.foreach { + case Some(i) => queue.offer(i) + case None => + if (!queue.isEmpty()) taken += queue.take() } + + while (!queue.isEmpty()) taken += queue.take() + + assertEquals(taken.toList, stuff.flatten) } + } - "iterate over contents in foreach" in { - forAll { (stuff: List[Option[Int]]) => - val queue = new JSArrayQueue[Int] - val shadow = new Queue[Int] + test("iterate over contents in foreach") { + forAll { (stuff: List[Option[Int]]) => + val queue = new JSArrayQueue[Int] + val shadow = new Queue[Int] - def checkContents() = { - val builder = List.newBuilder[Int] - queue.foreach(builder += _) - builder.result() must beEqualTo(shadow.toList) - } + def checkContents() = { + val builder = List.newBuilder[Int] + queue.foreach(builder += _) + assertEquals(builder.result(), shadow.toList) + } - checkContents() + checkContents() - stuff.foreach { - case Some(i) => - queue.offer(i) - shadow.enqueue(i) + stuff.foreach { + case Some(i) => + queue.offer(i) + shadow.enqueue(i) + checkContents() + case None => + if (!shadow.isEmpty) { + val got = queue.take() + val expected = shadow.dequeue() + assertEquals(got, expected) checkContents() - case None => - if (!shadow.isEmpty) { - val got = queue.take() - val expected = shadow.dequeue() - got must beEqualTo(expected) - checkContents() - } else { - ok - } - } - - ok + } else { + () + } } } } diff --git a/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala b/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala index c7d9708fa3..7a5b3e7fbd 100644 --- a/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala +++ b/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala @@ -21,40 +21,42 @@ import scala.concurrent.duration._ import java.util.concurrent.atomic.AtomicBoolean -class SchedulerSuite extends BaseSpec { +class SchedulerSuite extends BaseSuite { - "Default scheduler" should { - "correctly handle very long sleeps" in real { - // When the provided timeout in milliseconds overflows a signed 32-bit int, the implementation defaults to 1 millisecond - IO.sleep(Long.MaxValue.nanos).race(IO.sleep(100.millis)) mustEqual Right(()) - } - "use the correct max timeout" in real { - IO.sleep(Int.MaxValue.millis).race(IO.sleep(100.millis)) mustEqual Right(()) - } - "use high-precision time" in real { - for { - start <- IO.realTime - times <- IO.realTime.replicateA(100) - deltas = times.map(_ - start) - } yield deltas.exists(_.toMicros % 1000 != 0) - } - "correctly calculate real time" in real { - IO.realTime.product(IO(System.currentTimeMillis())).map { - case (realTime, currentTime) => - (realTime.toMillis - currentTime) should be_<=(10L) - } - } - "cancel" in real { - val scheduler = IORuntime.global.scheduler - for { - ref <- IO(new AtomicBoolean(true)) - cancel <- IO(scheduler.sleep(200.millis, () => ref.set(false))) - _ <- IO.sleep(100.millis) - _ <- IO(cancel.run()) - _ <- IO.sleep(200.millis) - didItCancel <- IO(ref.get()) - } yield didItCancel + real("correctly handle very long sleeps") { + // When the provided timeout in milliseconds overflows a signed 32-bit int, the implementation defaults to 1 millisecond + IO.sleep(Long.MaxValue.nanos) + .race(IO.sleep(100.millis)) + .map(r => assertEquals(r, Right(()))) + } + real("use the correct max timeout") { + IO.sleep(Int.MaxValue.millis) + .race(IO.sleep(100.millis)) + .map(r => assertEquals(r, Right(()))) + } + real("use high-precision time") { + for { + start <- IO.realTime + times <- IO.realTime.replicateA(100) + deltas = times.map(_ - start) + } yield deltas.exists(_.toMicros % 1000 != 0) + } + real("correctly calculate real time") { + IO.realTime.product(IO(System.currentTimeMillis())).map { + case (realTime, currentTime) => + assert(realTime.toMillis - currentTime <= 10L) } } + real("cancel") { + val scheduler = IORuntime.global.scheduler + for { + ref <- IO(new AtomicBoolean(true)) + cancel <- IO(scheduler.sleep(200.millis, () => ref.set(false))) + _ <- IO.sleep(100.millis) + _ <- IO(cancel.run()) + _ <- IO.sleep(200.millis) + didItCancel <- IO(ref.get()) + } yield didItCancel + } } diff --git a/tests/js/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala b/tests/js/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala index 5c745c89a5..d93460f293 100644 --- a/tests/js/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala +++ b/tests/js/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala @@ -22,7 +22,7 @@ import cats.syntax.traverse._ import scala.concurrent.{Future, Promise} import scala.concurrent.duration._ -class StripedHashtableSuite extends BaseSpec { +class StripedHashtableSuite extends BaseSuite { override def executionTimeout: FiniteDuration = 2.minutes @@ -35,37 +35,35 @@ class StripedHashtableSuite extends BaseSpec { IORuntimeConfig() ) - "StripedHashtable" should { - "work correctly in the presence of many unsafeRuns" in real { - val iterations = 10000 + real("work correctly in the presence of many unsafeRuns") { + val iterations = 10000 - object Boom extends RuntimeException("Boom!") + object Boom extends RuntimeException("Boom!") - def io(n: Int): IO[Unit] = - (n % 3) match { - case 0 => IO.unit - case 1 => IO.canceled - case 2 => IO.raiseError[Unit](Boom) - } + def io(n: Int): IO[Unit] = + (n % 3) match { + case 0 => IO.unit + case 1 => IO.canceled + case 2 => IO.raiseError[Unit](Boom) + } - Resource.make(IO(hashtableRuntime()))(rt => IO(rt.shutdown())).use { rt => - IO(new CountDownLatch(iterations)).flatMap { counter => - (0 until iterations) - .toList - .traverse { n => IO(io(n).unsafeRunAsync { _ => counter.countDown() }(rt)) } - .flatMap { _ => IO.fromFuture(IO.delay(counter.await())) } - .flatMap { _ => - IO.blocking { - rt.fiberErrorCbs.synchronized { - rt.fiberErrorCbs.tables.forall { table => - // check that each component hashtable of the larger striped - // hashtable is empty and has shrunk to its initial capacity - table.isEmpty && table.unsafeCapacity() == table.unsafeInitialCapacity() - } mustEqual true - } + Resource.make(IO(hashtableRuntime()))(rt => IO(rt.shutdown())).use { rt => + IO(new CountDownLatch(iterations)).flatMap { counter => + (0 until iterations) + .toList + .traverse { n => IO(io(n).unsafeRunAsync { _ => counter.countDown() }(rt)) } + .flatMap { _ => IO.fromFuture(IO.delay(counter.await())) } + .flatMap { _ => + IO.blocking { + rt.fiberErrorCbs.synchronized { + assert(rt.fiberErrorCbs.tables.forall { table => + // check that each component hashtable of the larger striped + // hashtable is empty and has shrunk to its initial capacity + table.isEmpty && table.unsafeCapacity() == table.unsafeInitialCapacity() + }) } } - } + } } } } diff --git a/tests/js/src/test/scala/cats/effect/SyncIOPlatformSpecification.scala b/tests/jvm-native/src/test/scala/cats/effect/SyncIOPlatformSuite.scala similarity index 55% rename from tests/js/src/test/scala/cats/effect/SyncIOPlatformSpecification.scala rename to tests/jvm-native/src/test/scala/cats/effect/SyncIOPlatformSuite.scala index 7664df815a..2bfeb07911 100644 --- a/tests/js/src/test/scala/cats/effect/SyncIOPlatformSpecification.scala +++ b/tests/jvm-native/src/test/scala/cats/effect/SyncIOPlatformSuite.scala @@ -16,20 +16,20 @@ package cats.effect -trait SyncIOPlatformSpecification { self: BaseSpec => - def platformSpecs = { - "platform" should { - "realTimeDate should return an Instant constructed from realTime" in { - // Unfortunately since SyncIO doesn't rely on a controllable - // time source, this is the best I can do - val op = for { - realTime <- SyncIO.realTime - jsDate <- SyncIO.realTimeDate - } yield (jsDate.getTime().toLong - realTime.toMillis) <= 30 +trait SyncIOPlatformSuite { self: BaseSuite => + def platformTests() = { - op must completeAsSync(true) - } + test("realTimeInstant should return an Instant constructed from realTime") { + // Unfortunately since SyncIO doesn't use on a controllable + // clock source, so a diff best we can do + val op = for { + realTime <- SyncIO.realTime + now <- SyncIO.realTimeInstant + } yield (now.toEpochMilli - realTime.toMillis) <= 10000 + + assertCompleteAsSync(op, true) } + } } diff --git a/tests/jvm/src/test/scala/cats/effect/IOPlatformSpecification.scala b/tests/jvm/src/test/scala/cats/effect/IOPlatformSpecification.scala deleted file mode 100644 index 2af9e8616d..0000000000 --- a/tests/jvm/src/test/scala/cats/effect/IOPlatformSpecification.scala +++ /dev/null @@ -1,569 +0,0 @@ -/* - * Copyright 2020-2024 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 - -import cats.effect.std.Semaphore -import cats.effect.unsafe.{ - IORuntime, - IORuntimeConfig, - PollingContext, - PollingSystem, - SleepSystem, - WorkStealingThreadPool -} -import cats.effect.unsafe.metrics.PollerMetrics -import cats.syntax.all._ - -import org.scalacheck.Prop.forAll -import org.specs2.ScalaCheck - -import scala.concurrent.ExecutionContext -import scala.concurrent.duration._ - -import java.util.concurrent.{ - CancellationException, - CompletableFuture, - CountDownLatch, - ExecutorService, - Executors, - ThreadLocalRandom -} -import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference} - -trait IOPlatformSpecification extends DetectPlatform { self: BaseSpec with ScalaCheck => - - def platformSpecs = { - "platform" should { - - "shift delay evaluation within evalOn" in real { - val Exec1Name = "testing executor 1" - val exec1 = Executors.newSingleThreadExecutor { r => - val t = new Thread(r) - t.setName(Exec1Name) - t - } - - val Exec2Name = "testing executor 2" - val exec2 = Executors.newSingleThreadExecutor { r => - val t = new Thread(r) - t.setName(Exec2Name) - t - } - - val Exec3Name = "testing executor 3" - val exec3 = Executors.newSingleThreadExecutor { r => - val t = new Thread(r) - t.setName(Exec3Name) - t - } - - val nameF = IO(Thread.currentThread().getName()) - - val test = nameF flatMap { outer1 => - val inner1F = nameF flatMap { inner1 => - val inner2F = nameF map { inner2 => (outer1, inner1, inner2) } - - inner2F.evalOn(ExecutionContext.fromExecutor(exec2)) - } - - inner1F.evalOn(ExecutionContext.fromExecutor(exec1)).flatMap { - case (outer1, inner1, inner2) => - nameF.map(outer2 => (outer1, inner1, inner2, outer2)) - } - } - - test.evalOn(ExecutionContext.fromExecutor(exec3)).flatMap { result => - IO { - result mustEqual ((Exec3Name, Exec1Name, Exec2Name, Exec3Name)) - } - } - } - - "start 1000 fibers in parallel and await them all" in real { - val input = (0 until 1000).toList - - val ioa = for { - fibers <- input.traverse(i => IO.pure(i).start) - _ <- fibers.traverse_(_.join.void) - } yield () - - ioa.as(ok) - } - - "start 1000 fibers in series and await them all" in real { - val input = (0 until 1000).toList - val ioa = input.traverse(i => IO.pure(i).start.flatMap(_.join)) - - ioa.as(ok) - } - - "race many things" in real { - val task = (0 until 100).foldLeft(IO.never[Int]) { (acc, _) => - IO.race(acc, IO(1)).map { - case Left(i) => i - case Right(i) => i - } - } - - task.replicateA(100).as(ok) - } - - "round trip non-canceled through j.u.c.CompletableFuture" in ticked { implicit ticker => - forAll { (ioa: IO[Int]) => - val normalized = ioa.onCancel(IO.never) - normalized.eqv(IO.fromCompletableFuture(IO(normalized.unsafeToCompletableFuture()))) - } - } - - "canceled through j.u.c.CompletableFuture is errored" in ticked { implicit ticker => - val test = - IO.fromCompletableFuture(IO(IO.canceled.as(-1).unsafeToCompletableFuture())) - .handleError(_ => 42) - - test must completeAs(42) - } - - "errors in j.u.c.CompletableFuture are not wrapped" in ticked { implicit ticker => - val e = new RuntimeException("stuff happened") - val test = IO - .fromCompletableFuture[Int](IO { - val root = new CompletableFuture[Int] - root.completeExceptionally(e) - root.thenApply(_ + 1) - }) - .attempt - - test must completeAs(Left(e)) - } - - "interrupt well-behaved blocking synchronous effect" in real { - var interrupted = true - val latch = new CountDownLatch(1) - - val await = IO.interruptible { - latch.countDown() - Thread.sleep(15000) - interrupted = false - } - - for { - f <- await.start - _ <- IO.blocking(latch.await()) - _ <- f.cancel - _ <- IO(interrupted must beTrue) - } yield ok - } - - "interrupt ill-behaved blocking synchronous effect" in real { - var interrupted = true - val latch = new CountDownLatch(1) - - val await = IO.interruptibleMany { - latch.countDown() - - try { - Thread.sleep(15000) - } catch { - case _: InterruptedException => () - } - - // psych! - try { - Thread.sleep(15000) - } catch { - case _: InterruptedException => () - } - - // I AM INVINCIBLE - Thread.sleep(15000) - - interrupted = false - } - - for { - f <- await.start - _ <- IO.blocking(latch.await()) - _ <- f.cancel - _ <- IO(interrupted must beTrue) - } yield ok - } - - "auto-cede" in real { - val forever = IO.unit.foreverM - - val ec = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor()) - - val run = for { - // Run in a tight loop on single-threaded ec so only hope of - // seeing cancelation status is auto-cede - fiber <- forever.start - // Allow the tight loop to be scheduled - _ <- IO.sleep(5.millis) - // Only hope for the cancelation being run is auto-yielding - _ <- fiber.cancel - } yield true - - run.evalOn(ec).guarantee(IO(ec.shutdown())).flatMap { res => IO(res must beTrue) } - } - - "realTimeInstant should return an Instant constructed from realTime" in ticked { - implicit ticker => - val op = for { - now <- IO.realTimeInstant - realTime <- IO.realTime - } yield now.toEpochMilli == realTime.toMillis - - op must completeAs(true) - } - - "cancel all inner effects when canceled" in ticked { implicit ticker => - val deadlock = for { - gate1 <- Semaphore[IO](2) - _ <- gate1.acquireN(2) - - gate2 <- Semaphore[IO](2) - _ <- gate2.acquireN(2) - - io = IO { - // these finalizers never return, so this test is intentionally designed to hang - // they flip their gates first though; this is just testing that both run in parallel - val a = (gate1.release *> IO.never) onCancel { - gate2.release *> IO.never - } - - val b = (gate1.release *> IO.never) onCancel { - gate2.release *> IO.never - } - - a.unsafeRunAndForget() - b.unsafeRunAndForget() - } - - _ <- io.flatMap(_ => gate1.acquireN(2)).start - _ <- gate2.acquireN(2) // if both are not run in parallel, then this will hang - } yield () - - val test = for { - t <- IO(deadlock.unsafeToFutureCancelable()) - (f, ct) = t - _ <- IO.fromFuture(IO(ct())) - _ <- IO.blocking(scala.concurrent.Await.result(f, Duration.Inf)) - } yield () - - test.attempt.map { - case Left(t) => t.isInstanceOf[CancellationException] - case Right(_) => false - } must completeAs(true) - } - - "run a timer which crosses into a blocking region" in realWithRuntime { rt => - rt.scheduler match { - case sched: WorkStealingThreadPool[_] => - // we structure this test by calling the runtime directly to avoid nondeterminism - val delay = IO.async[Unit] { cb => - IO { - // register a timer (use sleepInternal to ensure we get the worker-local version) - val cancel = sched.sleepInternal(1.second, cb) - - // convert the worker to a blocker - scala.concurrent.blocking(()) - - Some(IO(cancel.run())) - } - } - - // if the timer fires correctly, the timeout will not be hit - delay.race(IO.sleep(2.seconds)).flatMap(res => IO(res must beLeft)).map(_.toResult) - - case _ => IO.pure(skipped("test not running against WSTP")) - } - } - - "run timers exactly once when crossing into a blocking region" in realWithRuntime { rt => - rt.scheduler match { - case sched: WorkStealingThreadPool[_] => - IO defer { - val ai = new AtomicInteger(0) - - sched.sleepInternal(500.millis, { _ => ai.getAndIncrement(); () }) - - // if we aren't careful, this conversion can duplicate the timer - scala.concurrent.blocking { - IO.sleep(1.second) >> IO(ai.get() mustEqual 1).map(_.toResult) - } - } - - case _ => IO.pure(skipped("test not running against WSTP")) - } - } - - "run a timer registered on a blocker" in realWithRuntime { rt => - rt.scheduler match { - case sched: WorkStealingThreadPool[_] => - // we structure this test by calling the runtime directly to avoid nondeterminism - val delay = IO.async[Unit] { cb => - IO { - scala.concurrent.blocking { - // register a timer (use sleepInternal to ensure we get the worker-local version) - val cancel = sched.sleepInternal(1.second, cb) - Some(IO(cancel.run())) - } - } - } - - // if the timer fires correctly, the timeout will not be hit - delay.race(IO.sleep(2.seconds)).flatMap(res => IO(res must beLeft)).map(_.toResult) - - case _ => IO.pure(skipped("test not running against WSTP")) - } - } - - "safely detect hard-blocked threads even while blockers are being created" in { - val (compute, _, shutdown) = - IORuntime.createWorkStealingComputeThreadPool(blockedThreadDetectionEnabled = true) - - implicit val runtime: IORuntime = - IORuntime.builder().setCompute(compute, shutdown).build() - - try { - val test = for { - _ <- IO.unit.foreverM.start.replicateA_(200) - _ <- 0.until(200).toList.parTraverse_(_ => IO.blocking(())) - } yield ok // we can't actually test this directly because the symptom is vaporizing a worker - - test.unsafeRunSync() - } finally { - runtime.shutdown() - } - } - - // this test ensures that the parkUntilNextSleeper bit works - "run a timer when parking thread" in { - val (pool, _, shutdown) = IORuntime.createWorkStealingComputeThreadPool(threads = 1) - - implicit val runtime: IORuntime = IORuntime.builder().setCompute(pool, shutdown).build() - - try { - // longer sleep all-but guarantees this timer is fired *after* the worker is parked - val test = IO.sleep(500.millis) *> IO.pure(true) - test.unsafeRunTimed(5.seconds) must beSome(true) - } finally { - runtime.shutdown() - } - } - - // this test ensures that we always see the timer, even when it fires just as we're about to park - "run a timer when detecting just prior to park" in { - val (pool, _, shutdown) = IORuntime.createWorkStealingComputeThreadPool(threads = 1) - - implicit val runtime: IORuntime = IORuntime.builder().setCompute(pool, shutdown).build() - - try { - // shorter sleep makes it more likely this timer fires *before* the worker is parked - val test = IO.sleep(1.milli) *> IO.pure(true) - test.unsafeRunTimed(1.second) must beSome(true) - } finally { - runtime.shutdown() - } - } - - "random racing sleeps" in real { - def randomSleep: IO[Unit] = IO.defer { - val n = ThreadLocalRandom.current().nextInt(2000000) - IO.sleep(n.micros) // less than 2 seconds - } - - def raceAll(ios: List[IO[Unit]]): IO[Unit] = { - ios match { - case head :: tail => tail.foldLeft(head) { (x, y) => IO.race(x, y).void } - case Nil => IO.unit - } - } - - // we race a lot of "sleeps", it must not hang - // (this includes inserting and cancelling - // a lot of callbacks into the heap, - // thus hopefully stressing the data structure): - List - .fill(500) { - raceAll(List.fill(500) { randomSleep }) - } - .parSequence_ - .as(ok) - } - - "steal timers" in realWithRuntime { rt => - val spin = IO.cede *> IO { // first make sure we're on a `WorkerThread` - // The `WorkerThread` which executes this IO - // will never exit the `while` loop, unless - // the timer is triggered, so it will never - // be able to trigger the timer itself. The - // only way this works is if some other worker - // steals the the timer. - val flag = new AtomicBoolean(false) - val _ = rt.scheduler.sleep(500.millis, () => { flag.set(true) }) - var ctr = 0L - while (!flag.get()) { - if ((ctr % 8192L) == 0L) { - // Make sure there is another unparked - // worker searching (and stealing timers): - rt.compute.execute(() => { () }) - } - ctr += 1L - } - } - - spin.as(ok) - } - - "lots of externally-canceled timers" in real { - Resource - .make(IO(Executors.newSingleThreadExecutor()))(exec => IO(exec.shutdownNow()).void) - .map(ExecutionContext.fromExecutor(_)) - .use { ec => - IO.sleep(1.day).start.flatMap(_.cancel.evalOn(ec)).parReplicateA_(100000) - } - .as(ok) - } - - "not lose cedeing threads from the bypass when blocker transitioning" in { - // writing this test in terms of IO seems to not reproduce the issue - 0.until(5) foreach { _ => - val wstp = new WorkStealingThreadPool[AnyRef]( - threadCount = 2, - threadPrefix = "testWorker", - blockerThreadPrefix = "testBlocker", - runtimeBlockingExpiration = 3.seconds, - reportFailure0 = _.printStackTrace(), - blockedThreadDetectionEnabled = false, - shutdownTimeout = 1.second, - system = SleepSystem - ) - - val runtime = IORuntime - .builder() - .setCompute(wstp, () => wstp.shutdown()) - .setConfig(IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 2)) - .build() - - try { - val ctr = new AtomicLong - - val tsk1 = IO { ctr.incrementAndGet() }.foreverM - val fib1 = tsk1.unsafeRunFiber((), _ => (), { (_: Any) => () })(runtime) - for (_ <- 1 to 10) { - val tsk2 = IO.blocking { Thread.sleep(5L) } - tsk2.unsafeRunFiber((), _ => (), _ => ())(runtime) - } - fib1.join.unsafeRunFiber((), _ => (), _ => ())(runtime) - - Thread.sleep(1000L) - val results = 0.until(3).toList map { _ => - Thread.sleep(100L) - ctr.get() - } - - results must not[List[Long]](beEqualTo(List.fill(3)(results.head))) - } finally { - runtime.shutdown() - } - } - - ok - } - - "wake parked thread for polled events" in { - - trait DummyPoller { - def poll: IO[Unit] - } - - object DummySystem extends PollingSystem { - type Api = DummyPoller - type Poller = AtomicReference[List[Either[Throwable, Unit] => Unit]] - - def close() = () - - def makePoller() = new AtomicReference(List.empty[Either[Throwable, Unit] => Unit]) - def needsPoll(poller: Poller) = poller.get.nonEmpty - def closePoller(poller: Poller) = () - def metrics(poller: Poller): PollerMetrics = PollerMetrics.noop - - def interrupt(targetThread: Thread, targetPoller: Poller) = - SleepSystem.interrupt(targetThread, SleepSystem.makePoller()) - - def poll(poller: Poller, nanos: Long, reportFailure: Throwable => Unit) = { - poller.getAndSet(Nil) match { - case Nil => - SleepSystem.poll(SleepSystem.makePoller(), nanos, reportFailure) - case cbs => - cbs.foreach(_.apply(Right(()))) - true - } - } - - def makeApi(ctx: PollingContext[Poller]): DummySystem.Api = - new DummyPoller { - def poll = IO.async_[Unit] { cb => - ctx.accessPoller { poller => - poller.getAndUpdate(cb :: _) - () - } - } - } - } - - val (pool, poller, shutdown) = IORuntime.createWorkStealingComputeThreadPool( - threads = 2, - pollingSystem = DummySystem) - - implicit val runtime: IORuntime = - IORuntime.builder().setCompute(pool, shutdown).addPoller(poller, () => ()).build() - - try { - val test = - IO.pollers.map(_.head.asInstanceOf[DummyPoller]).flatMap { poller => - val blockAndPoll = IO.blocking(Thread.sleep(10)) *> poller.poll - blockAndPoll.replicateA(100).as(true) - } - test.unsafeRunSync() must beTrue - } finally { - runtime.shutdown() - } - } - - if (javaMajorVersion >= 21) - "block in-place on virtual threads" in real { - val loomExec = classOf[Executors] - .getDeclaredMethod("newVirtualThreadPerTaskExecutor") - .invoke(null) - .asInstanceOf[ExecutorService] - - val loomEc = ExecutionContext.fromExecutor(loomExec) - - IO.blocking { - classOf[Thread] - .getDeclaredMethod("isVirtual") - .invoke(Thread.currentThread()) - .asInstanceOf[Boolean] - }.evalOn(loomEc) - } - else - "block in-place on virtual threads" in skipped("virtual threads not supported") - } - } -} diff --git a/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala b/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala new file mode 100644 index 0000000000..76d2152a0a --- /dev/null +++ b/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala @@ -0,0 +1,567 @@ +/* + * Copyright 2020-2024 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 + +import cats.effect.std.Semaphore +import cats.effect.unsafe.{ + IORuntime, + IORuntimeConfig, + PollingContext, + PollingSystem, + SleepSystem, + WorkStealingThreadPool +} +import cats.effect.unsafe.metrics.PollerMetrics +import cats.syntax.all._ + +import org.scalacheck.Prop.forAll + +import scala.concurrent.ExecutionContext +import scala.concurrent.duration._ + +import java.util.concurrent.{ + CancellationException, + CompletableFuture, + CountDownLatch, + ExecutorService, + Executors, + ThreadLocalRandom +} +import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference} + +trait IOPlatformSuite extends DetectPlatform { + self: BaseSuite with munit.ScalaCheckSuite => + + def platformTests() = { + real("shift delay evaluation within evalOn") { + val Exec1Name = "testing executor 1" + val exec1 = Executors.newSingleThreadExecutor { r => + val t = new Thread(r) + t.setName(Exec1Name) + t + } + + val Exec2Name = "testing executor 2" + val exec2 = Executors.newSingleThreadExecutor { r => + val t = new Thread(r) + t.setName(Exec2Name) + t + } + + val Exec3Name = "testing executor 3" + val exec3 = Executors.newSingleThreadExecutor { r => + val t = new Thread(r) + t.setName(Exec3Name) + t + } + + val nameF = IO(Thread.currentThread().getName()) + + val test = nameF flatMap { outer1 => + val inner1F = nameF flatMap { inner1 => + val inner2F = nameF map { inner2 => (outer1, inner1, inner2) } + + inner2F.evalOn(ExecutionContext.fromExecutor(exec2)) + } + + inner1F.evalOn(ExecutionContext.fromExecutor(exec1)).flatMap { + case (outer1, inner1, inner2) => + nameF.map(outer2 => (outer1, inner1, inner2, outer2)) + } + } + + test.evalOn(ExecutionContext.fromExecutor(exec3)).flatMap { result => + IO { + assertEquals(result, (Exec3Name, Exec1Name, Exec2Name, Exec3Name)) + } + } + } + + real("start 1000 fibers in parallel and await them all") { + val input = (0 until 1000).toList + + val ioa = for { + fibers <- input.traverse(i => IO.pure(i).start) + _ <- fibers.traverse_(_.join.void) + } yield () + + ioa + } + + real("start 1000 fibers in series and await them all") { + val input = (0 until 1000).toList + val ioa = input.traverse(i => IO.pure(i).start.flatMap(_.join)) + + ioa + } + + real("race many things") { + val task = (0 until 100).foldLeft(IO.never[Int]) { (acc, _) => + IO.race(acc, IO(1)).map { + case Left(i) => i + case Right(i) => i + } + } + + task.replicateA(100) + } + + ticked("round trip non-canceled through j.u.c.CompletableFuture") { implicit ticker => + forAll { (ioa: IO[Int]) => + val normalized = ioa.onCancel(IO.never) + normalized.eqv(IO.fromCompletableFuture(IO(normalized.unsafeToCompletableFuture()))) + } + } + + ticked("canceled through j.u.c.CompletableFuture is errored") { implicit ticker => + val test = + IO.fromCompletableFuture(IO(IO.canceled.as(-1).unsafeToCompletableFuture())) + .handleError(_ => 42) + + assertCompleteAs(test, 42) + } + + ticked("errors in j.u.c.CompletableFuture are not wrapped") { implicit ticker => + val e = new RuntimeException("stuff happened") + val test = IO + .fromCompletableFuture[Int](IO { + val root = new CompletableFuture[Int] + root.completeExceptionally(e) + root.thenApply(_ + 1) + }) + .attempt + + assertCompleteAs(test, Left(e)) + } + + real("interrupt well-behaved blocking synchronous effect") { + var interrupted = true + val latch = new CountDownLatch(1) + + val await = IO.interruptible { + latch.countDown() + Thread.sleep(15000) + interrupted = false + } + + for { + f <- await.start + _ <- IO.blocking(latch.await()) + _ <- f.cancel + _ <- IO(assert(interrupted)) + } yield () + } + + real("interrupt ill-behaved blocking synchronous effect") { + var interrupted = true + val latch = new CountDownLatch(1) + + val await = IO.interruptibleMany { + latch.countDown() + + try { + Thread.sleep(15000) + } catch { + case _: InterruptedException => () + } + + // psych! + try { + Thread.sleep(15000) + } catch { + case _: InterruptedException => () + } + + // I AM INVINCIBLE + Thread.sleep(15000) + + interrupted = false + } + + for { + f <- await.start + _ <- IO.blocking(latch.await()) + _ <- f.cancel + _ <- IO(assert(interrupted)) + } yield () + } + + real("auto-cede") { + val forever = IO.unit.foreverM + + val ec = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor()) + + val run = for { + // Run in a tight loop on single-threaded ec so only hope of + // seeing cancelation status is auto-cede + fiber <- forever.start + // Allow the tight loop to be scheduled + _ <- IO.sleep(5.millis) + // Only hope for the cancelation being run is auto-yielding + _ <- fiber.cancel + } yield true + + run.evalOn(ec).guarantee(IO(ec.shutdown())).flatMap { res => IO(assert(res)) } + } + + ticked("realTimeInstant should return an Instant constructed from realTime") { + implicit ticker => + val op = for { + now <- IO.realTimeInstant + realTime <- IO.realTime + } yield now.toEpochMilli == realTime.toMillis + + assertCompleteAs(op, true) + } + + ticked("cancel all inner effects when canceled") { implicit ticker => + val deadlock = for { + gate1 <- Semaphore[IO](2) + _ <- gate1.acquireN(2) + + gate2 <- Semaphore[IO](2) + _ <- gate2.acquireN(2) + + io = IO { + // these finalizers never return, so this test is intentionally designed to hang + // they flip their gates first though; this is just testing that both run in parallel + val a = (gate1.release *> IO.never) onCancel { + gate2.release *> IO.never + } + + val b = (gate1.release *> IO.never) onCancel { + gate2.release *> IO.never + } + + a.unsafeRunAndForget() + b.unsafeRunAndForget() + } + + _ <- io.flatMap(_ => gate1.acquireN(2)).start + _ <- gate2.acquireN(2) // if both are not run in parallel, then this will hang + } yield () + + val test = for { + t <- IO(deadlock.unsafeToFutureCancelable()) + (f, ct) = t + _ <- IO.fromFuture(IO(ct())) + _ <- IO.blocking(scala.concurrent.Await.result(f, Duration.Inf)) + } yield () + + val io = test.attempt.map { + case Left(t) => t.isInstanceOf[CancellationException] + case Right(_) => false + } + assertCompleteAs(io, true) + } + + realWithRuntime("run a timer which crosses into a blocking region") { rt => + rt.scheduler match { + case sched: WorkStealingThreadPool[_] => + // we structure this test by calling the runtime directly to avoid nondeterminism + val delay = IO.async[Unit] { cb => + IO { + // register a timer (use sleepInternal to ensure we get the worker-local version) + val cancel = sched.sleepInternal(1.second, cb) + + // convert the worker to a blocker + scala.concurrent.blocking(()) + + Some(IO(cancel.run())) + } + } + + // if the timer fires correctly, the timeout will not be hit + delay.race(IO.sleep(2.seconds)).flatMap(res => IO(assert(res.isLeft))) + + case _ => + IO.println("test not running against WSTP") + } + } + + realWithRuntime("run timers exactly once when crossing into a blocking region") { rt => + rt.scheduler match { + case sched: WorkStealingThreadPool[_] => + IO defer { + val ai = new AtomicInteger(0) + + sched.sleepInternal(500.millis, { _ => ai.getAndIncrement(); () }) + + // if we aren't careful, this conversion can duplicate the timer + scala.concurrent.blocking { + IO.sleep(1.second) >> IO(assertEquals(ai.get(), 1)) + } + } + + case _ => + IO.println("test not running against WSTP") + } + } + + realWithRuntime("run a timer registered on a blocker") { rt => + rt.scheduler match { + case sched: WorkStealingThreadPool[_] => + // we structure this test by calling the runtime directly to avoid nondeterminism + val delay = IO.async[Unit] { cb => + IO { + scala.concurrent.blocking { + // register a timer (use sleepInternal to ensure we get the worker-local version) + val cancel = sched.sleepInternal(1.second, cb) + Some(IO(cancel.run())) + } + } + } + + // if the timer fires correctly, the timeout will not be hit + delay.race(IO.sleep(2.seconds)).flatMap(res => IO(assert(res.isLeft))) + + case _ => IO.println("test not running against WSTP") + } + } + + test("safely detect hard-blocked threads even while blockers are being created") { + val (compute, _, shutdown) = + IORuntime.createWorkStealingComputeThreadPool(blockedThreadDetectionEnabled = true) + + implicit val runtime: IORuntime = + IORuntime.builder().setCompute(compute, shutdown).build() + + try { + val test = for { + _ <- IO.unit.foreverM.start.replicateA_(200) + _ <- 0.until(200).toList.parTraverse_(_ => IO.blocking(())) + } yield () // we can't actually test this directly because the symptom is vaporizing a worker + + test.unsafeRunSync() + } finally { + runtime.shutdown() + } + } + + // this test ensures that the parkUntilNextSleeper bit works + test("run a timer when parking thread") { + val (pool, _, shutdown) = IORuntime.createWorkStealingComputeThreadPool(threads = 1) + + implicit val runtime: IORuntime = IORuntime.builder().setCompute(pool, shutdown).build() + + try { + // longer sleep all-but guarantees this timer is fired *after* the worker is parked + val test = IO.sleep(500.millis) *> IO.pure(true) + assertEquals(test.unsafeRunTimed(5.seconds), Some(true)) + } finally { + runtime.shutdown() + } + } + + // this test ensures that we always see the timer, even when it fires just as we're about to park + test("run a timer when detecting just prior to park") { + val (pool, _, shutdown) = IORuntime.createWorkStealingComputeThreadPool(threads = 1) + + implicit val runtime: IORuntime = IORuntime.builder().setCompute(pool, shutdown).build() + + try { + // shorter sleep makes it more likely this timer fires *before* the worker is parked + val test = IO.sleep(1.milli) *> IO.pure(true) + assertEquals(test.unsafeRunTimed(1.second), Some(true)) + } finally { + runtime.shutdown() + } + } + + real("random racing sleeps") { + def randomSleep: IO[Unit] = IO.defer { + val n = ThreadLocalRandom.current().nextInt(2000000) + IO.sleep(n.micros) // less than 2 seconds + } + + def raceAll(ios: List[IO[Unit]]): IO[Unit] = { + ios match { + case head :: tail => tail.foldLeft(head) { (x, y) => IO.race(x, y).void } + case Nil => IO.unit + } + } + + // we race a lot of "sleeps", it must not hang + // (this includes inserting and cancelling + // a lot of callbacks into the heap, + // thus hopefully stressing the data structure): + List + .fill(500) { + raceAll(List.fill(500) { randomSleep }) + } + .parSequence_ + + } + + realWithRuntime("steal timers") { rt => + val spin = IO.cede *> IO { // first make sure we're on a `WorkerThread` + // The `WorkerThread` which executes this IO + // will never exit the `while` loop, unless + // the timer is triggered, so it will never + // be able to trigger the timer itself. The + // only way this works is if some other worker + // steals the the timer. + val flag = new AtomicBoolean(false) + val _ = rt.scheduler.sleep(500.millis, () => { flag.set(true) }) + var ctr = 0L + while (!flag.get()) { + if ((ctr % 8192L) == 0L) { + // Make sure there is another unparked + // worker searching (and stealing timers): + rt.compute.execute(() => { () }) + } + ctr += 1L + } + } + + spin + } + + real("lots of externally-canceled timers") { + Resource + .make(IO(Executors.newSingleThreadExecutor()))(exec => IO(exec.shutdownNow()).void) + .map(ExecutionContext.fromExecutor(_)) + .use { ec => IO.sleep(1.day).start.flatMap(_.cancel.evalOn(ec)).parReplicateA_(100000) } + + } + + test("not lose cedeing threads from the bypass when blocker transitioning") { + // writing this test in terms of IO seems to not reproduce the issue + 0.until(5) foreach { _ => + val wstp = new WorkStealingThreadPool[AnyRef]( + threadCount = 2, + threadPrefix = "testWorker", + blockerThreadPrefix = "testBlocker", + runtimeBlockingExpiration = 3.seconds, + reportFailure0 = _.printStackTrace(), + blockedThreadDetectionEnabled = false, + shutdownTimeout = 1.second, + system = SleepSystem + ) + + val runtime = IORuntime + .builder() + .setCompute(wstp, () => wstp.shutdown()) + .setConfig(IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 2)) + .build() + + try { + val ctr = new AtomicLong + + val tsk1 = IO { ctr.incrementAndGet() }.foreverM + val fib1 = tsk1.unsafeRunFiber((), _ => (), { (_: Any) => () })(runtime) + for (_ <- 1 to 10) { + val tsk2 = IO.blocking { Thread.sleep(5L) } + tsk2.unsafeRunFiber((), _ => (), _ => ())(runtime) + } + fib1.join.unsafeRunFiber((), _ => (), _ => ())(runtime) + + Thread.sleep(1000L) + val results = 0.until(3).toList map { _ => + Thread.sleep(100L) + ctr.get() + } + + assertNotEquals(results, List.fill(3)(results.head)) + } finally { + runtime.shutdown() + } + } + + () + } + + test("wake parked thread for polled events") { + + trait DummyPoller { + def poll: IO[Unit] + } + + object DummySystem extends PollingSystem { + type Api = DummyPoller + type Poller = AtomicReference[List[Either[Throwable, Unit] => Unit]] + + def close() = () + + def makePoller() = new AtomicReference(List.empty[Either[Throwable, Unit] => Unit]) + def needsPoll(poller: Poller) = poller.get.nonEmpty + def closePoller(poller: Poller) = () + def metrics(poller: Poller): PollerMetrics = PollerMetrics.noop + + def interrupt(targetThread: Thread, targetPoller: Poller) = + SleepSystem.interrupt(targetThread, SleepSystem.makePoller()) + + def poll(poller: Poller, nanos: Long, reportFailure: Throwable => Unit) = { + poller.getAndSet(Nil) match { + case Nil => + SleepSystem.poll(SleepSystem.makePoller(), nanos, reportFailure) + case cbs => + cbs.foreach(_.apply(Right(()))) + true + } + } + + def makeApi(ctx: PollingContext[Poller]): DummySystem.Api = + new DummyPoller { + def poll = IO.async_[Unit] { cb => + ctx.accessPoller { poller => + poller.getAndUpdate(cb :: _) + () + } + } + } + } + + val (pool, poller, shutdown) = + IORuntime.createWorkStealingComputeThreadPool(threads = 2, pollingSystem = DummySystem) + + implicit val runtime: IORuntime = + IORuntime.builder().setCompute(pool, shutdown).addPoller(poller, () => ()).build() + + try { + val test = + IO.pollers.map(_.head.asInstanceOf[DummyPoller]).flatMap { poller => + val blockAndPoll = IO.blocking(Thread.sleep(10)) *> poller.poll + blockAndPoll.replicateA(100).as(true) + } + assert(test.unsafeRunSync()) + } finally { + runtime.shutdown() + } + } + + if (javaMajorVersion >= 21) + real("block in-place on virtual threads") { + val loomExec = classOf[Executors] + .getDeclaredMethod("newVirtualThreadPerTaskExecutor") + .invoke(null) + .asInstanceOf[ExecutorService] + + val loomEc = ExecutionContext.fromExecutor(loomExec) + + IO.blocking { + classOf[Thread] + .getDeclaredMethod("isVirtual") + .invoke(Thread.currentThread()) + .asInstanceOf[Boolean] + }.evalOn(loomEc) + } + // else + // "block in-place on virtual threads" in skipped("virtual threads not supported") + + } +} diff --git a/tests/jvm/src/test/scala/cats/effect/ParasiticECSuite.scala b/tests/jvm/src/test/scala/cats/effect/ParasiticECSuite.scala index 07e7376ef7..6b9956d47f 100644 --- a/tests/jvm/src/test/scala/cats/effect/ParasiticECSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/ParasiticECSuite.scala @@ -23,26 +23,24 @@ import org.scalacheck.Arbitrary import scala.concurrent.duration._ -class ParasiticECSuite extends BaseSpec with TestInstances { +class ParasiticECSuite extends BaseSuite with TestInstances { override def executionTimeout: FiniteDuration = 60.seconds - "IO monad" should { - "evaluate fibers correctly in presence of a parasitic execution context" in real { - val test = { - implicit val ticker = Ticker() + real("evaluate fibers correctly in presence of a parasitic execution context") { + val test = { + implicit val ticker = Ticker() - IO(implicitly[Arbitrary[IO[Int]]].arbitrary.sample.get).flatMap { io => - IO.delay(io.eqv(io)) - } + IO(implicitly[Arbitrary[IO[Int]]].arbitrary.sample.get).flatMap { io => + IO.delay(io.eqv(io)) } + } - val iterations = 15000 + val iterations = 15000 - List.fill(iterations)(test).sequence.map(_.count(identity)).flatMap { c => - IO { - c mustEqual iterations - } + List.fill(iterations)(test).sequence.map(_.count(identity)).flatMap { c => + IO { + assertEquals(c, iterations) } } } diff --git a/tests/jvm/src/test/scala/cats/effect/ResourceJVMSuite.scala b/tests/jvm/src/test/scala/cats/effect/ResourceJVMSuite.scala index c8e2ce12fe..71eee9c547 100644 --- a/tests/jvm/src/test/scala/cats/effect/ResourceJVMSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/ResourceJVMSuite.scala @@ -19,81 +19,79 @@ package cats.effect import cats.arrow.FunctionK import cats.syntax.eq._ -class ResourceJVMSuite extends BaseSpec { +class ResourceJVMSuite extends BaseSuite { - "platform" should { + /** + * Recursively calls itself until a [[StackOverflowError]] is encountered, at which point, the + * current depth is returned. + * + * @return + * the stack depth at which [[StackOverflowError]] occurs + */ + def verifyThatSoeIsReproducibleWithStackDepth(): Int = { + var depth = 0 - /** - * Recursively calls itself until a [[StackOverflowError]] is encountered, at which point, - * the current depth is returned. - * - * @return - * the stack depth at which [[StackOverflowError]] occurs - */ - def verifyThatSoeIsReproducibleWithStackDepth(): Int = { - var depth = 0 + def triggerStackOverflowError(n: Int): Int = { + depth = n + n + triggerStackOverflowError(n + 1) + } - def triggerStackOverflowError(n: Int): Int = { - depth = n - n + triggerStackOverflowError(n + 1) - } + try triggerStackOverflowError(0) + catch { + case _: StackOverflowError => depth + } + } - try triggerStackOverflowError(0) - catch { - case _: StackOverflowError => depth + ticked("verify use is stack-safe over binds") { implicit ticker => + val stackDepth = verifyThatSoeIsReproducibleWithStackDepth() + val r = (0 to stackDepth) + .foldLeft(Resource.eval(IO.unit)) { + case (r, _) => + r.flatMap(_ => Resource.eval(IO.unit)) } - } + .use_ + r eqv IO.unit + } - "verify use is stack-safe over binds" in ticked { implicit ticker => - val stackDepth = verifyThatSoeIsReproducibleWithStackDepth() - val r = (0 to stackDepth) - .foldLeft(Resource.eval(IO.unit)) { - case (r, _) => - r.flatMap(_ => Resource.eval(IO.unit)) + real("verify use is stack-safe over binds - 2") { + val stackDepth = verifyThatSoeIsReproducibleWithStackDepth() + def p(i: Int): Resource[IO, Int] = + Resource + .pure { + if (i < stackDepth) Left(i + 1) + else Right(i) + } + .flatMap { + case Left(a) => p(a) + case Right(b) => Resource.pure(b) } - .use_ - r eqv IO.unit - } - - "verify use is stack-safe over binds - 2" in real { - val stackDepth = verifyThatSoeIsReproducibleWithStackDepth() - def p(i: Int): Resource[IO, Int] = - Resource - .pure { - if (i < stackDepth) Left(i + 1) - else Right(i) - } - .flatMap { - case Left(a) => p(a) - case Right(b) => Resource.pure(b) - } - p(0).use(IO.pure).mustEqual(stackDepth) - } + p(0).use(IO.pure).mustEqual(stackDepth) + } - "verify mapK is stack-safe over binds" in ticked { implicit ticker => - val stackDepth = verifyThatSoeIsReproducibleWithStackDepth() - val r = (0 to stackDepth) - .foldLeft(Resource.eval(IO.unit)) { - case (r, _) => - r.flatMap(_ => Resource.eval(IO.unit)) - } - .mapK(FunctionK.id) - .use_ + ticked("verify mapK is stack-safe over binds") { implicit ticker => + val stackDepth = verifyThatSoeIsReproducibleWithStackDepth() + val r = (0 to stackDepth) + .foldLeft(Resource.eval(IO.unit)) { + case (r, _) => + r.flatMap(_ => Resource.eval(IO.unit)) + } + .mapK(FunctionK.id) + .use_ - r eqv IO.unit - } + r eqv IO.unit + } - "verify attempt is stack-safe over binds" in ticked { implicit ticker => - val stackDepth = verifyThatSoeIsReproducibleWithStackDepth() - val r = (0 to stackDepth) - .foldLeft(Resource.eval(IO.unit)) { - case (r, _) => - r.flatMap(_ => Resource.eval(IO.unit)) - } - .attempt + ticked("verify attempt is stack-safe over binds") { implicit ticker => + val stackDepth = verifyThatSoeIsReproducibleWithStackDepth() + val r = (0 to stackDepth) + .foldLeft(Resource.eval(IO.unit)) { + case (r, _) => + r.flatMap(_ => Resource.eval(IO.unit)) + } + .attempt - r.use_ must completeAs(()) - } + assertCompleteAs(r.use_, ()) } + } diff --git a/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala b/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala index adfe63b3ea..a10838bf24 100644 --- a/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala +++ b/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala @@ -18,40 +18,7 @@ package cats.effect import cats.effect.unsafe.{IORuntime, IORuntimeConfig} -import org.specs2.specification.BeforeAfterAll - -trait RunnersPlatform extends BeforeAfterAll { - - private[this] var runtime0: IORuntime = _ - - protected def runtime(): IORuntime = runtime0 - - def beforeAll(): Unit = { - val (blocking, blockDown) = - IORuntime.createDefaultBlockingExecutionContext(threadPrefix = - s"io-blocking-${getClass.getName}") - val (compute, poller, compDown) = - IORuntime.createWorkStealingComputeThreadPool( - threadPrefix = s"io-compute-${getClass.getName}", - blockerThreadPrefix = s"io-blocker-${getClass.getName}") - - runtime0 = IORuntime( - compute, - blocking, - compute, - List(poller), - { () => - compDown() - blockDown() - }, - IORuntimeConfig() - ) - } - - def afterAll(): Unit = runtime().shutdown() -} - -trait MUnitRunnersPlatform { self: munit.Suite => +trait RunnersPlatform { self: munit.Suite => private[this] var runtime0: IORuntime = _ @@ -80,4 +47,4 @@ trait MUnitRunnersPlatform { self: munit.Suite => } override def afterAll(): Unit = runtime().shutdown() -} \ No newline at end of file +} diff --git a/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala b/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala index 0334199b3b..7a7ec3d379 100644 --- a/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala @@ -24,7 +24,7 @@ import java.nio.ByteBuffer import java.nio.channels.Pipe import java.nio.channels.SelectionKey._ -class SelectorSuite extends BaseSpec { +class SelectorSuite extends BaseSuite { def getSelector: IO[Selector] = IO.pollers.map(_.collectFirst { case selector: Selector => selector }).map(_.get) @@ -44,76 +44,73 @@ class SelectorSuite extends BaseSpec { } } - "Selector" should { - - "notify read-ready events" in real { - mkPipe.use { pipe => - for { - selector <- getSelector - buf <- IO(ByteBuffer.allocate(4)) - _ <- IO(pipe.sink.write(ByteBuffer.wrap(Array(1, 2, 3)))).background.surround { - selector.select(pipe.source, OP_READ) *> IO(pipe.source.read(buf)) - } - _ <- IO(pipe.sink.write(ByteBuffer.wrap(Array(42)))).background.surround { - selector.select(pipe.source, OP_READ) *> IO(pipe.source.read(buf)) - } - } yield buf.array().toList must be_==(List[Byte](1, 2, 3, 42)) - } + real("notify read-ready events") { + mkPipe.use { pipe => + for { + selector <- getSelector + buf <- IO(ByteBuffer.allocate(4)) + _ <- IO(pipe.sink.write(ByteBuffer.wrap(Array(1, 2, 3)))).background.surround { + selector.select(pipe.source, OP_READ) *> IO(pipe.source.read(buf)) + } + _ <- IO(pipe.sink.write(ByteBuffer.wrap(Array(42)))).background.surround { + selector.select(pipe.source, OP_READ) *> IO(pipe.source.read(buf)) + } + } yield assertEquals(buf.array().toList, List[Byte](1, 2, 3, 42)) } + } - "setup multiple callbacks" in real { - mkPipe.use { pipe => - for { - selector <- getSelector - _ <- selector.select(pipe.source, OP_READ).parReplicateA_(10) <& - IO(pipe.sink.write(ByteBuffer.wrap(Array(1, 2, 3)))) - } yield ok - } + real("setup multiple callbacks") { + mkPipe.use { pipe => + for { + selector <- getSelector + _ <- selector.select(pipe.source, OP_READ).parReplicateA_(10) <& + IO(pipe.sink.write(ByteBuffer.wrap(Array(1, 2, 3)))) + } yield () } + } - "works after blocking" in real { - mkPipe.use { pipe => - for { - selector <- getSelector - _ <- IO.blocking(()) - _ <- selector.select(pipe.sink, OP_WRITE) - } yield ok - } + real("works after blocking") { + mkPipe.use { pipe => + for { + selector <- getSelector + _ <- IO.blocking(()) + _ <- selector.select(pipe.sink, OP_WRITE) + } yield () } + } - "gracefully handles illegal ops" in real { - mkPipe.use { pipe => - // get off the wstp to test async codepaths - IO.blocking(()) *> getSelector.flatMap { selector => - selector.select(pipe.sink, OP_READ).attempt.map { - case Left(_: IllegalArgumentException) => true - case _ => false - } + real("gracefully handles illegal ops") { + mkPipe.use { pipe => + // get off the wstp to test async codepaths + IO.blocking(()) *> getSelector.flatMap { selector => + selector.select(pipe.sink, OP_READ).attempt.map { + case Left(_: IllegalArgumentException) => true + case _ => false } } } + } - "handles concurrent close" in { - val (pool, poller, shutdown) = IORuntime.createWorkStealingComputeThreadPool(threads = 1) - implicit val runtime: IORuntime = - IORuntime.builder().setCompute(pool, shutdown).addPoller(poller, () => ()).build() + test("handles concurrent close") { + val (pool, poller, shutdown) = IORuntime.createWorkStealingComputeThreadPool(threads = 1) + implicit val runtime: IORuntime = + IORuntime.builder().setCompute(pool, shutdown).addPoller(poller, () => ()).build() - try { - val test = getSelector - .flatMap { selector => - mkPipe.allocated.flatMap { - case (pipe, close) => - selector.select(pipe.source, OP_READ).background.surround { - IO.sleep(1.millis) *> close *> IO.sleep(1.millis) - } - } + try { + val test = getSelector + .flatMap { selector => + mkPipe.allocated.flatMap { + case (pipe, close) => + selector.select(pipe.source, OP_READ).background.surround { + IO.sleep(1.millis) *> close *> IO.sleep(1.millis) + } } - .replicateA_(1000) - .as(true) - test.unsafeRunSync() must beTrue - } finally { - runtime.shutdown() - } + } + .replicateA_(1000) + .as(true) + assert(test.unsafeRunSync()) + } finally { + runtime.shutdown() } } diff --git a/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSuite.scala b/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSuite.scala index 350b53f58b..86c2717d23 100644 --- a/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSuite.scala @@ -16,7 +16,7 @@ package cats.effect.kernel -import cats.effect.{BaseSpec, IO} +import cats.effect.{BaseSuite, IO} import cats.effect.testkit.TestControl import cats.effect.unsafe.IORuntimeConfig @@ -25,48 +25,46 @@ import scala.concurrent.duration._ import java.util.concurrent.{CancellationException, CompletableFuture} import java.util.concurrent.atomic.AtomicBoolean -class AsyncPlatformSuite extends BaseSpec { +class AsyncPlatformSuite extends BaseSuite { val smallDelay: IO[Unit] = IO.sleep(1.second) - "AsyncPlatform CompletableFuture conversion" should { - "cancel CompletableFuture on fiber cancellation" in real { - lazy val cf = CompletableFuture.supplyAsync { () => - Thread.sleep(2000) // some computation - } - - for { - fiber <- IO.fromCompletableFuture(IO(cf)).start - _ <- smallDelay // time for the callback to be set-up - _ <- fiber.cancel - _ <- IO(cf.join() must throwA[CancellationException]) - } yield ok + real("cancel CompletableFuture on fiber cancellation") { + lazy val cf = CompletableFuture.supplyAsync { () => + Thread.sleep(2000) // some computation } - "backpressure on CompletableFuture cancelation" in real { - // a non-cancelable, never-completing CompletableFuture - def mkcf() = new CompletableFuture[Unit] { - override def cancel(mayInterruptIfRunning: Boolean) = false - } - - def go = for { - started <- IO(new AtomicBoolean) - fiber <- IO.fromCompletableFuture { - IO { - started.set(true) - mkcf() - } - }.start - _ <- IO.cede.whileM_(IO(!started.get)) - _ <- fiber.cancel - } yield () + for { + fiber <- IO.fromCompletableFuture(IO(cf)).start + _ <- smallDelay // time for the callback to be set-up + _ <- fiber.cancel + _ <- IO(intercept[CancellationException](cf.join())) + } yield () + } - TestControl - .executeEmbed(go, IORuntimeConfig(1, 2)) - .as(false) - .recover { case _: TestControl.NonTerminationException => true } - .replicateA(1000) - .map(_.forall(identity(_))) + real("backpressure on CompletableFuture cancelation") { + // a non-cancelable, never-completing CompletableFuture + def mkcf() = new CompletableFuture[Unit] { + override def cancel(mayInterruptIfRunning: Boolean) = false } + + def go = for { + started <- IO(new AtomicBoolean) + fiber <- IO.fromCompletableFuture { + IO { + started.set(true) + mkcf() + } + }.start + _ <- IO.cede.whileM_(IO(!started.get)) + _ <- fiber.cancel + } yield () + + TestControl + .executeEmbed(go, IORuntimeConfig(1, 2)) + .as(false) + .recover { case _: TestControl.NonTerminationException => true } + .replicateA(1000) + .map(_.forall(identity(_))) } } diff --git a/tests/jvm/src/test/scala/cats/effect/std/ConsoleJVMSuite.scala b/tests/jvm/src/test/scala/cats/effect/std/ConsoleJVMSuite.scala index bb4cb4c375..2d62a0820b 100644 --- a/tests/jvm/src/test/scala/cats/effect/std/ConsoleJVMSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/std/ConsoleJVMSuite.scala @@ -21,8 +21,6 @@ import cats.Show import cats.effect.kernel.Sync import cats.syntax.all._ -import org.specs2.matcher.MatchResult - import scala.concurrent.duration._ import scala.io.Source @@ -36,8 +34,7 @@ import java.io.{ } import java.nio.charset.{Charset, StandardCharsets} -class ConsoleJVMSuite extends BaseSpec { - sequential +class ConsoleJVMSuite extends BaseSuite { private def printStream(out: ByteArrayOutputStream): Resource[IO, PrintStream] = Resource.make(IO(new PrintStream(out)))(ps => IO(ps.close())) @@ -140,138 +137,138 @@ class ConsoleJVMSuite extends BaseSpec { test.use(_ => loop(Nil)) } - private def readLineTest(name: String, charset: Charset): IO[MatchResult[List[String]]] = + private def readLineTest(name: String, charset: Charset): IO[Unit] = for { rawLines <- fileLines(name, charset) lines <- consoleReadLines(rawLines, charset) - result <- IO(lines must beEqualTo(rawLines)) + result <- IO(assertEquals(lines, rawLines)) } yield result - "Console" should { - "print to the standard output" in real { - val message = "Message" - standardOutTest(IO.print(message)).flatMap { msg => - IO { - msg must beEqualTo(message) - } + real("print to the standard output") { + val message = "Message" + standardOutTest(IO.print(message)).flatMap { msg => + IO { + assertEquals(msg, message) } } + } - "println to the standard output" in real { - val message = "Message" - standardOutTest(IO.println(message)).flatMap { msg => - IO { - msg must beEqualTo(s"$message${System.lineSeparator()}") - } + real("println to the standard output") { + val message = "Message" + standardOutTest(IO.println(message)).flatMap { msg => + IO { + assertEquals(msg, s"$message${System.lineSeparator()}") } } + } - "print to the standard error" in real { - val error = "Error" - standardErrTest(Console[IO].error(error)).flatMap { err => - IO { - err must beEqualTo(error) - } + real("print to the standard error") { + val error = "Error" + standardErrTest(Console[IO].error(error)).flatMap { err => + IO { + assertEquals(err, error) } } + } - "println to the standard error" in real { - val error = "Error" - standardErrTest(Console[IO].errorln(error)).flatMap { err => - IO { - err must beEqualTo(s"$error${System.lineSeparator()}") - } + real("println to the standard error") { + val error = "Error" + standardErrTest(Console[IO].errorln(error)).flatMap { err => + IO { + assertEquals(err, s"$error${System.lineSeparator()}") } } + } - "printStackTrace to the standard error output" in real { - val e = new Throwable("error!") + real("printStackTrace to the standard error output") { + val e = new Throwable("error!") - val stackTraceString = throwableToString(e) + val stackTraceString = throwableToString(e) - standardErrTest(Console[IO].printStackTrace(e)).flatMap { err => - IO { - err must beEqualTo(stackTraceString) - } + standardErrTest(Console[IO].printStackTrace(e)).flatMap { err => + IO { + assertEquals(err, stackTraceString) } } + } - "default printStackTrace implementation copies the throwable stack trace and prints it to the standard error" in real { - - final class DummyConsole[F[_]](implicit F: Sync[F]) extends Console[F] { - def readLineWithCharset(charset: Charset): F[String] = F.pure("line") + real( + "default printStackTrace implementation copies the throwable stack trace and prints it to the standard error") { - def print[A](a: A)(implicit S: Show[A] = Show.fromToString[A]): F[Unit] = F.unit + final class DummyConsole[F[_]](implicit F: Sync[F]) extends Console[F] { + def readLineWithCharset(charset: Charset): F[String] = F.pure("line") - def println[A](a: A)(implicit S: Show[A] = Show.fromToString[A]): F[Unit] = F.unit + def print[A](a: A)(implicit S: Show[A] = Show.fromToString[A]): F[Unit] = F.unit - def error[A](a: A)(implicit S: Show[A] = Show.fromToString[A]): F[Unit] = { - val text = a.show - F.blocking(System.err.print(text)) - } + def println[A](a: A)(implicit S: Show[A] = Show.fromToString[A]): F[Unit] = F.unit - def errorln[A](a: A)(implicit S: Show[A] = Show.fromToString[A]): F[Unit] = F.unit + def error[A](a: A)(implicit S: Show[A] = Show.fromToString[A]): F[Unit] = { + val text = a.show + F.blocking(System.err.print(text)) } - val e = new Throwable("error!") + def errorln[A](a: A)(implicit S: Show[A] = Show.fromToString[A]): F[Unit] = F.unit + } - val stackTraceString = throwableToString(e) + val e = new Throwable("error!") - val console = new DummyConsole[IO] + val stackTraceString = throwableToString(e) - standardErrTest(console.printStackTrace(e)).flatMap { err => - IO { - err must beEqualTo(stackTraceString) - } + val console = new DummyConsole[IO] + + standardErrTest(console.printStackTrace(e)).flatMap { err => + IO { + assertEquals(err, stackTraceString) } } + } - "read all lines from an ISO-8859-1 encoded file" in real { - val cs = StandardCharsets.ISO_8859_1 - readLineTest(cs.name(), cs) - } + real("read all lines from an ISO-8859-1 encoded file") { + val cs = StandardCharsets.ISO_8859_1 + readLineTest(cs.name(), cs) + } - "read all lines from a US-ASCII encoded file" in real { - val cs = StandardCharsets.US_ASCII - readLineTest(cs.name(), cs) - } + real("read all lines from a US-ASCII encoded file") { + val cs = StandardCharsets.US_ASCII + readLineTest(cs.name(), cs) + } - "read all lines from a UTF-8 encoded file" in real { - val cs = StandardCharsets.UTF_8 - readLineTest(cs.name(), cs) - } + real("read all lines from a UTF-8 encoded file") { + val cs = StandardCharsets.UTF_8 + readLineTest(cs.name(), cs) + } - "read all lines from a UTF-16 encoded file" in real { - val cs = StandardCharsets.UTF_16 - readLineTest(cs.name(), cs) - } + real("read all lines from a UTF-16 encoded file") { + val cs = StandardCharsets.UTF_16 + readLineTest(cs.name(), cs) + } - "read all lines from a UTF-16BE encoded file" in real { - val cs = StandardCharsets.UTF_16BE - readLineTest(cs.name(), cs) - } + real("read all lines from a UTF-16BE encoded file") { + val cs = StandardCharsets.UTF_16BE + readLineTest(cs.name(), cs) + } - "read all lines from a UTF-16LE encoded file" in real { - val cs = StandardCharsets.UTF_16LE - readLineTest(cs.name(), cs) - } + real("read all lines from a UTF-16LE encoded file") { + val cs = StandardCharsets.UTF_16LE + readLineTest(cs.name(), cs) + } - "readLine is cancelable and does not lose lines" in real { - IO(new PipedOutputStream).flatMap { out => - IO(new PipedInputStream(out)).flatMap { in => - replaceStandardIn(in).surround { - for { - read1 <- IO.readLine.timeout(100.millis).attempt - _ <- IO(read1 should beLeft) - _ <- IO(out.write("unblocked\n".getBytes())) - read2 <- Console[IO].readLineWithCharset(StandardCharsets.US_ASCII).attempt - _ <- IO(read2 should beLeft) - read3 <- IO.readLine - _ <- IO(read3 must beEqualTo("unblocked")) - } yield ok - } + real("readLine is cancelable and does not lose lines") { + IO(new PipedOutputStream).flatMap { out => + IO(new PipedInputStream(out)).flatMap { in => + replaceStandardIn(in).surround { + for { + read1 <- IO.readLine.timeout(100.millis).attempt + _ <- IO(assert(read1.isLeft)) + _ <- IO(out.write("unblocked\n".getBytes())) + read2 <- Console[IO].readLineWithCharset(StandardCharsets.US_ASCII).attempt + _ <- IO(assert(read2.isLeft)) + read3 <- IO.readLine + _ <- IO(assertEquals(read3, "unblocked")) + } yield () } } } } + } diff --git a/tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSuite.scala b/tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSuite.scala index 21e6453ffb..ff612136d5 100644 --- a/tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSuite.scala @@ -20,22 +20,19 @@ package std import cats.effect.kernel.Deferred import cats.effect.unsafe.{IORuntime, IORuntimeConfig, Scheduler} -import org.specs2.mutable.Specification -import org.specs2.specification.BeforeAfterEach - import scala.concurrent.ExecutionContext import scala.concurrent.duration._ import java.util.concurrent.{ExecutorService, Executors, ThreadFactory, TimeUnit} import java.util.concurrent.atomic.AtomicLong +import munit.FunSuite + class DeferredJVMParallelism1Tests extends BaseDeferredJVMTests(1) class DeferredJVMParallelism2Tests extends BaseDeferredJVMTests(2) class DeferredJVMParallelism4Tests extends BaseDeferredJVMTests(4) -abstract class BaseDeferredJVMTests(parallelism: Int) - extends Specification - with BeforeAfterEach { +abstract class BaseDeferredJVMTests(parallelism: Int) extends FunSuite { var service: ExecutorService = _ implicit val context: ExecutionContext = new ExecutionContext { @@ -67,7 +64,6 @@ abstract class BaseDeferredJVMTests(parallelism: Int) } // ---------------------------------------------------------------------------- - val isCI = System.getenv("TRAVIS") == "true" || System.getenv("CI") == "true" val iterations = if (isCI) 1000 else 10000 val timeout = if (isCI) 30.seconds else 10.seconds @@ -80,7 +76,7 @@ abstract class BaseDeferredJVMTests(parallelism: Int) IO.unit } - // "Deferred — issue #380: producer keeps its thread, consumer stays forked" in { + // test("Deferred — issue #380: producer keeps its thread, consumer stays forked") { // for (_ <- 0 until iterations) { // val name = Thread.currentThread().getName @@ -94,19 +90,19 @@ abstract class BaseDeferredJVMTests(parallelism: Int) // val task = for { // df <- Deferred[IO, Unit] // fb <- get(df).start - // _ <- IO(Thread.currentThread().getName mustEqual name) + // _ <- IO(Thread.currentThread().assertEquals(getName, name)) // _ <- df.complete(()) - // _ <- IO(Thread.currentThread().getName mustEqual name) + // _ <- IO(Thread.currentThread().assertEquals(getName, name)) // _ <- fb.join // } yield () - // task.unsafeRunTimed(timeout).nonEmpty must beTrue + // task.unsafeRunTimed(timeout).assert(nonEmpty) // } // success // } - // "Deferred — issue #380: with foreverM" in { + // test("Deferred — issue #380: with foreverM") { // for (_ <- 0 until iterations) { // val cancelLoop = new AtomicBoolean(false) // val unit = IO { @@ -123,7 +119,7 @@ abstract class BaseDeferredJVMTests(parallelism: Int) // _ <- fb.cancel // } yield () - // task.unsafeRunTimed(timeout).nonEmpty must beTrue + // task.unsafeRunTimed(timeout).assert(nonEmpty) // } finally { // cancelLoop.set(true) // } @@ -132,7 +128,7 @@ abstract class BaseDeferredJVMTests(parallelism: Int) // success // } - "Deferred — issue #380: with cooperative light async boundaries" in { + test("Deferred — issue #380: with cooperative light async boundaries") { def run = { def foreverAsync(i: Int): IO[Unit] = if (i == 512) IO.async[Unit](cb => IO(cb(Right(()))).as(None)) >> foreverAsync(0) @@ -149,13 +145,11 @@ abstract class BaseDeferredJVMTests(parallelism: Int) } for (_ <- 0 until iterations) { - run.unsafeRunTimed(timeout).nonEmpty must beTrue + assert(run.unsafeRunTimed(timeout).nonEmpty) } - - success } - "Deferred — issue #380: with cooperative full async boundaries" in { + test("Deferred — issue #380: with cooperative full async boundaries") { def run = { def foreverAsync(i: Int): IO[Unit] = if (i == 512) IO.unit.start.flatMap(_.join) >> foreverAsync(0) @@ -172,15 +166,13 @@ abstract class BaseDeferredJVMTests(parallelism: Int) } for (_ <- 0 until iterations) { - run.unsafeRunTimed(timeout).nonEmpty must beTrue + assert(run.unsafeRunTimed(timeout).nonEmpty) } - - success } // TODO move this back to run on both JVM and JS once we have a better test // setup than unsafeRunRealistic - "issue #380: complete doesn't block, test #2" in { + test("issue #380: complete doesn't block, test #2") { def execute(times: Int): IO[Boolean] = { val task = for { d <- Deferred[IO, Unit] @@ -198,7 +190,7 @@ abstract class BaseDeferredJVMTests(parallelism: Int) } } - unsafeRunRealistic(execute(100))() must beEqualTo(Some(true)) + assertEquals(unsafeRunRealistic(execute(100))(), Some(true)) } def unsafeRunRealistic[A](ioa: IO[A])( diff --git a/tests/jvm/src/test/scala/cats/effect/std/DispatcherJVMSuite.scala b/tests/jvm/src/test/scala/cats/effect/std/DispatcherJVMSuite.scala index f9d397d9bf..b60a09d67d 100644 --- a/tests/jvm/src/test/scala/cats/effect/std/DispatcherJVMSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/std/DispatcherJVMSuite.scala @@ -22,50 +22,48 @@ import cats.syntax.all._ import scala.concurrent.duration.DurationInt -class DispatcherJVMSuite extends BaseSpec { +class DispatcherJVMSuite extends BaseSuite { - "async dispatcher" should { - "run multiple IOs in parallel with blocking threads" in real { - val num = 100 + real("run multiple IOs in parallel with blocking threads") { + val num = 100 - for { - latches <- (0 until num).toList.traverse(_ => Deferred[IO, Unit]) - awaitAll = latches.parTraverse_(_.get) - - // engineer a deadlock: all subjects must be run in parallel or this will hang - subjects = latches.map(latch => latch.complete(()) >> awaitAll) + for { + latches <- (0 until num).toList.traverse(_ => Deferred[IO, Unit]) + awaitAll = latches.parTraverse_(_.get) - _ <- { - val rec = Dispatcher.parallel[IO](await = false) flatMap { runner => - Resource.eval(subjects.parTraverse_(act => IO(runner.unsafeRunSync(act)))) - } + // engineer a deadlock: all subjects must be run in parallel or this will hang + subjects = latches.map(latch => latch.complete(()) >> awaitAll) - rec.use(_ => IO.unit) + _ <- { + val rec = Dispatcher.parallel[IO](await = false) flatMap { runner => + Resource.eval(subjects.parTraverse_(act => IO(runner.unsafeRunSync(act)))) } - } yield ok - } - "propagate Java thread interruption in unsafeRunSync" in real { - Dispatcher.parallel[IO](await = true).use { dispatcher => - for { - pre <- IO.deferred[Unit] - canceled <- IO.deferred[Unit] + rec.use(_ => IO.unit) + } + } yield () + } + + real("propagate Java thread interruption in unsafeRunSync") { + Dispatcher.parallel[IO](await = true).use { dispatcher => + for { + pre <- IO.deferred[Unit] + canceled <- IO.deferred[Unit] - io = (pre.complete(()) *> IO.never).onCancel(canceled.complete(()).void) + io = (pre.complete(()) *> IO.never).onCancel(canceled.complete(()).void) - f <- IO.interruptible { - try dispatcher.unsafeRunSync(io) - catch { case _: InterruptedException => } - }.start + f <- IO.interruptible { + try dispatcher.unsafeRunSync(io) + catch { case _: InterruptedException => } + }.start - _ <- pre.get - _ <- f.cancel + _ <- pre.get + _ <- f.cancel - _ <- canceled - .get - .timeoutTo(1.second, IO.raiseError(new Exception("io was not canceled"))) - } yield ok - } + _ <- canceled + .get + .timeoutTo(1.second, IO.raiseError(new Exception("io was not canceled"))) + } yield () } } } diff --git a/tests/jvm/src/test/scala/cats/effect/std/MapRefJVMSuite.scala b/tests/jvm/src/test/scala/cats/effect/std/MapRefJVMSuite.scala index f977af35ae..06b982bff9 100644 --- a/tests/jvm/src/test/scala/cats/effect/std/MapRefJVMSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/std/MapRefJVMSuite.scala @@ -23,171 +23,167 @@ import cats.implicits._ import scala.concurrent.duration._ -class MapRefJVMSuite extends BaseSpec { +class MapRefJVMSuite extends BaseSuite { private val smallDelay: IO[Unit] = IO.sleep(20.millis) private def awaitEqual[A: Eq](t: IO[A], success: A): IO[Unit] = t.flatMap(a => if (Eq[A].eqv(a, success)) IO.unit else smallDelay *> awaitEqual(t, success)) - "MapRef.ofScalaConcurrentTrieMap" should { - - "concurrent modifications" in real { - val finalValue = 100 - val r = MapRef.inScalaConcurrentTrieMap[SyncIO, IO, Unit, Int].unsafeRunSync() - val modifies = List.fill(finalValue)(r(()).update(_.map(_ + 1))).parSequence - val test = r(()).set(Some(0)) *> modifies.start *> awaitEqual(r(()).get, finalValue.some) - test.map(_ => ok) - } - - "getAndSet - successful" in real { - val op = for { - r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] - _ <- r(()).set(Some(0)) - getAndSetResult <- r(()).getAndSet(Some(1)) - getResult <- r(()).get - } yield getAndSetResult == Some(0) && getResult == Some(1) - - op.map(a => a must_=== true) - } - - "access - successful" in real { - val op = for { - r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] - _ <- r(()).set(Some(0)) - accessed <- r(()).access - (value, setter) = accessed - success <- setter(value.map(_ + 1)) - result <- r(()).get - } yield success && result == Some(1) - - op.map(a => a must_=== true) - } + real("MapRef.ofScalaConcurrentTrieMap - concurrent modifications") { + val finalValue = 100 + val r = MapRef.inScalaConcurrentTrieMap[SyncIO, IO, Unit, Int].unsafeRunSync() + val modifies = List.fill(finalValue)(r(()).update(_.map(_ + 1))).parSequence + r(()).set(Some(0)) *> modifies.start *> awaitEqual(r(()).get, finalValue.some) + } - "access - setter should fail if value is modified before setter is called with None/Some" in real { - val op = for { - r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] - accessed <- r(()).access - (value, setter) = accessed - _ <- r(()).set(Some(5)) - success <- setter(value.map(_ + 1)) - result <- r(()).get - } yield !success && result == Some(5) - - op.map(a => a must_=== true) + real("MapRef.ofScalaConcurrentTrieMap - getAndSet - successful") { + for { + r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] + _ <- r(()).set(Some(0)) + getAndSetResult <- r(()).getAndSet(Some(1)) + getResult <- r(()).get + } yield { + assertEquals(getAndSetResult, Some(0)) + assertEquals(getResult, Some(1)) } + } - "access - setter should fail if value is modified before setter is called with init Some/Some" in real { - val op = for { - r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] - _ <- r(()).set(Some(0)) - accessed <- r(()).access - (value, setter) = accessed - _ <- r(()).set(Some(5)) - success <- setter(value.map(_ + 1)) - result <- r(()).get - } yield !success && result == Some(5) - - op.map(a => a must_=== true) + real("MapRef.ofScalaConcurrentTrieMap - access - successful") { + for { + r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] + _ <- r(()).set(Some(0)) + accessed <- r(()).access + (value, setter) = accessed + success <- setter(value.map(_ + 1)) + result <- r(()).get + } yield { + assert(success) + assertEquals(result, Some(1)) } + } - "access - setter should fail if value is modified before setter is called with init Some/None" in real { - val op = for { - r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] - _ <- r(()).set(Some(0)) - accessed <- r(()).access - (value, setter) = accessed - _ <- r(()).set(Some(5)) - success <- setter(None) - result <- r(()).get - } yield !success && result == Some(5) - - op.map(a => a must_=== true) + real( + "MapRef.ofScalaConcurrentTrieMap - access - setter should fail if value is modified before setter is called with None/Some") { + for { + r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] + accessed <- r(()).access + (value, setter) = accessed + _ <- r(()).set(Some(5)) + success <- setter(value.map(_ + 1)) + result <- r(()).get + } yield { + assert(!success) + assertEquals(result, Some(5)) } + } - "tryUpdate - modification occurs successfully" in real { - val op = for { - r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] - _ <- r(()).set(Some(0)) - result <- r(()).tryUpdate(_.map(_ + 1)) - value <- r(()).get - } yield result && value == Some(1) - - op.map(a => a must_=== true) + real( + "MapRef.ofScalaConcurrentTrieMap - access - setter should fail if value is modified before setter is called with init Some/Some") { + for { + r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] + _ <- r(()).set(Some(0)) + accessed <- r(()).access + (value, setter) = accessed + _ <- r(()).set(Some(5)) + success <- setter(value.map(_ + 1)) + result <- r(()).get + } yield { + assert(!success) + assertEquals(result, Some(5)) } + } - "tryUpdate - should fail to update if modification has occurred" in real { - import cats.effect.unsafe.implicits.global - val updateRefUnsafely: Ref[IO, Option[Int]] => Unit = - _.update(_.map(_ + 1)).unsafeRunSync() - - val op = for { - r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] - _ <- r(()).set(Some(0)) - result <- r(()).tryUpdate(currentValue => { - updateRefUnsafely(r(())) - currentValue.map(_ + 1) - }) - } yield result - - op.map(a => a must_=== false) + real( + "MapRef.ofScalaConcurrentTrieMap - access - setter should fail if value is modified before setter is called with init Some/None") { + for { + r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] + _ <- r(()).set(Some(0)) + accessed <- r(()).access + (value, setter) = accessed + _ <- r(()).set(Some(5)) + success <- setter(None) + result <- r(()).get + } yield { + assert(!success) + assertEquals(result, Some(5)) } + } - "tryModifyState - modification occurs successfully" in real { - val op = for { - r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] - _ <- r(()).set(Some(0)) - result <- r(()).tryModifyState(State.pure(Some(1))) - } yield result.contains(Some(1)) - - op.map(a => a must_=== true) + real("MapRef.ofScalaConcurrentTrieMap - tryUpdate - modification occurs successfully") { + for { + r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] + _ <- r(()).set(Some(0)) + result <- r(()).tryUpdate(_.map(_ + 1)) + value <- r(()).get + } yield { + assert(result) + assertEquals(value, Some(1)) } + } - "modifyState - modification occurs successfully" in real { - val op = for { - r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] - _ <- r(()).set(Some(0)) - result <- r(()).modifyState(State.pure(Some(1))) - } yield result == Some(1) + real( + "MapRef.ofScalaConcurrentTrieMap - tryUpdate - should fail to update if modification has occurred") { + import cats.effect.unsafe.implicits.global + val updateRefUnsafely: Ref[IO, Option[Int]] => Unit = + _.update(_.map(_ + 1)).unsafeRunSync() + + for { + r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] + _ <- r(()).set(Some(0)) + result <- r(()).tryUpdate(currentValue => { + updateRefUnsafely(r(())) + currentValue.map(_ + 1) + }) + } yield assert(!result) + } - op.map(a => a must_=== true) - } + real("MapRef.ofScalaConcurrentTrieMap - tryModifyState - modification occurs successfully") { + for { + r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] + _ <- r(()).set(Some(0)) + result <- r(()).tryModifyState(State.pure(Some(1))) + } yield assertEquals(result, Option(Some(1))) + } + real("MapRef.ofScalaConcurrentTrieMap - modifyState - modification occurs successfully") { + for { + r <- MapRef.ofScalaConcurrentTrieMap[IO, Unit, Int] + _ <- r(()).set(Some(0)) + result <- r(()).modifyState(State.pure(Some(1))) + } yield assertEquals(result, Some(1)) } - "MapRef.ofSingleImmutableMapRef" should { // Requires unsafeRunSync so doesn't work with JS - "tryUpdate - should fail to update if modification has occurred" in real { - import cats.effect.unsafe.implicits.global - val updateRefUnsafely: Ref[IO, Option[Int]] => Unit = - _.update(_.map(_ + 1)).unsafeRunSync() - - val op = for { - r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - result <- r(()).tryUpdate(currentValue => { - updateRefUnsafely(r(())) - currentValue.map(_ + 1) - }) - } yield result - - op.map(a => a must_=== false) - } + // Requires unsafeRunSync so doesn't work with JS + real( + "MapRef.ofSingleImmutableMapRef - tryUpdate - should fail to update if modification has occurred") { + import cats.effect.unsafe.implicits.global + val updateRefUnsafely: Ref[IO, Option[Int]] => Unit = + _.update(_.map(_ + 1)).unsafeRunSync() + + for { + r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + result <- r(()).tryUpdate(currentValue => { + updateRefUnsafely(r(())) + currentValue.map(_ + 1) + }) + } yield assert(!result) + } - "tryUpdate - should fail to update if modification has occurred" in real { - import cats.effect.unsafe.implicits.global - val updateRefUnsafely: Ref[IO, Option[Int]] => Unit = - _.update(_.map(_ + 1)).unsafeRunSync() - - val op = for { - r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - result <- r(()).tryUpdate(currentValue => { - updateRefUnsafely(r(())) - currentValue.map(_ + 1) - }) - } yield result - - op.map(a => a must_=== false) - } + real( + "MapRef.ofSingleImmutableMapRef - tryUpdate - should fail to update if modification has occurred") { + import cats.effect.unsafe.implicits.global + val updateRefUnsafely: Ref[IO, Option[Int]] => Unit = + _.update(_.map(_ + 1)).unsafeRunSync() + + for { + r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + result <- r(()).tryUpdate(currentValue => { + updateRefUnsafely(r(())) + currentValue.map(_ + 1) + }) + } yield assert(!result) } } diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/BlockingStressSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/BlockingStressSuite.scala index 364c37fbd3..e4f6242061 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/BlockingStressSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/BlockingStressSuite.scala @@ -25,31 +25,28 @@ import scala.util.Random import java.util.concurrent.CountDownLatch -class BlockingStressSuite extends BaseSpec { +class BlockingStressSuite extends BaseSuite { override def executionTimeout: FiniteDuration = 30.seconds // This test spawns a lot of helper threads. private val count = 1000 - "Blocking" should { - "work properly with many blocking actions and helper threads" in realWithRuntime { rt => - def io(latch: CountDownLatch) = for { - p <- IO(Promise[Unit]()) - d1 <- IO(Random.nextInt(50)) - d2 <- IO(Random.nextInt(100)) - _ <- (IO.sleep(d1.millis) *> IO( - rt.scheduler.sleep(d2.millis, () => p.success(())))).start - _ <- IO(Await.result(p.future, Duration.Inf)) - _ <- IO(blocking(latch.countDown())) - } yield () - - for { - latch <- IO(new CountDownLatch(count)) - _ <- List.fill(count)(io(latch).start.void).sequence.void - _ <- IO(blocking(latch.await())) - res <- IO(ok) - } yield res - } + realWithRuntime("work properly with many blocking actions and helper threads") { rt => + def io(latch: CountDownLatch) = for { + p <- IO(Promise[Unit]()) + d1 <- IO(Random.nextInt(50)) + d2 <- IO(Random.nextInt(100)) + _ <- (IO.sleep(d1.millis) *> IO(rt.scheduler.sleep(d2.millis, () => p.success(())))).start + _ <- IO(Await.result(p.future, Duration.Inf)) + _ <- IO(blocking(latch.countDown())) + } yield () + + for { + latch <- IO(new CountDownLatch(count)) + _ <- List.fill(count)(io(latch).start.void).sequence.void + _ <- IO(blocking(latch.await())) + } yield () } + } diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/DrainBatchSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/DrainBatchSuite.scala index a13164a805..ca270b6f19 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/DrainBatchSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/DrainBatchSuite.scala @@ -19,32 +19,30 @@ package unsafe import cats.effect.std.Queue -class DrainBatchSuite extends BaseSpec { - - "Batch draining" should { - "work correctly in the presence of concurrent stealers" in real { - val iterations = 500000 - - def catsEffectRepeat[A](n: Int)(io: IO[A]): IO[A] = - if (n <= 1) io - else io.flatMap(_ => catsEffectRepeat(n - 1)(io)) - - def iterate(deferred: Deferred[IO, Unit], n: Int): IO[Any] = - for { - ref <- IO.ref(n) - queue <- Queue.bounded[IO, Unit](1) - effect = queue.offer(()).start >> - queue.take >> - ref.modify(n => (n - 1, if (n == 1) deferred.complete(()) else IO.unit)).flatten - _ <- catsEffectRepeat(iterations)(effect.start) - } yield () +class DrainBatchSuite extends BaseSuite { + real("work correctly in the presence of concurrent stealers") { + val iterations = 500000 + + def catsEffectRepeat[A](n: Int)(io: IO[A]): IO[A] = + if (n <= 1) io + else io.flatMap(_ => catsEffectRepeat(n - 1)(io)) + + def iterate(deferred: Deferred[IO, Unit], n: Int): IO[Any] = for { - deferred <- IO.deferred[Unit] - _ <- iterate(deferred, iterations).start - _ <- deferred.get - res <- IO(ok) - } yield res - } + ref <- IO.ref(n) + queue <- Queue.bounded[IO, Unit](1) + effect = queue.offer(()).start >> + queue.take >> + ref.modify(n => (n - 1, if (n == 1) deferred.complete(()) else IO.unit)).flatten + _ <- catsEffectRepeat(iterations)(effect.start) + } yield () + + for { + deferred <- IO.deferred[Unit] + _ <- iterate(deferred, iterations).start + _ <- deferred.get + } yield () } + } diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala index 8c29a0c7ce..6c2df80c8a 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala @@ -16,42 +16,38 @@ package cats.effect.unsafe -import cats.effect.{BaseSpec, FiberIO, IO, Outcome} +import cats.effect.{BaseSuite, FiberIO, IO, Outcome} import cats.effect.std.CountDownLatch import cats.effect.testkit.TestInstances import scala.concurrent.duration._ -class FiberMonitorSuite extends BaseSpec with TestInstances { +class FiberMonitorSuite extends BaseSuite with TestInstances { - "FiberMonitor" should { + realWithRuntime("show only active fibers in a live snapshot") { (runtime: IORuntime) => + val waitingPattern = raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ WAITING((.|\n)*)" + val completedPattern = raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ COMPLETED" - "show only active fibers in a live snapshot" in realWithRuntime { (runtime: IORuntime) => - val waitingPattern = raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ WAITING((.|\n)*)" - val completedPattern = raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ COMPLETED" + for { + cdl <- CountDownLatch[IO](1) + fiber <- cdl.await.start // create a 'waiting' fiber + fiberId <- IO(extractFiberId(fiber)) + _ <- IO.sleep(100.millis) - for { - cdl <- CountDownLatch[IO](1) - fiber <- cdl.await.start // create a 'waiting' fiber - fiberId <- IO(extractFiberId(fiber)) - _ <- IO.sleep(100.millis) + snapshot <- IO(makeSnapshot(runtime)) + _ <- IO(assertEquals(snapshot.size, 2)) // root and awaiting fibers + fiberSnapshot <- IO(snapshot.filter(_.contains(fiberId))) + _ <- IO(assertEquals(fiberSnapshot.size, 1)) // only awaiting fiber + _ <- IO(assert(fiberSnapshot.exists(_.matches(waitingPattern)))) - snapshot <- IO(makeSnapshot(runtime)) - _ <- IO(snapshot must have size 2) // root and awaiting fibers - fiberSnapshot <- IO(snapshot.filter(_.contains(fiberId))) - _ <- IO(fiberSnapshot must have size 1) // only awaiting fiber - _ <- IO(fiberSnapshot must containPattern(waitingPattern)) - - _ <- cdl.release // allow further execution - outcome <- fiber.join - _ <- IO.sleep(100.millis) - - _ <- IO(outcome must beEqualTo(Outcome.succeeded[IO, Throwable, Unit](IO.unit))) - _ <- IO(fiber.toString must beMatching(completedPattern)) - _ <- IO(makeSnapshot(runtime) must have size 1) // only root fiber - } yield ok - } + _ <- cdl.release // allow further execution + outcome <- fiber.join + _ <- IO.sleep(100.millis) + _ <- IO(assertEquals(outcome, Outcome.succeeded[IO, Throwable, Unit](IO.unit))) + _ <- IO(assert(fiber.toString.matches(completedPattern))) + _ <- IO(assertEquals(makeSnapshot(runtime).size, 1)) // only root fiber + } yield () } // keep only fibers diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSuite.scala index 74da745f9c..01cae0daba 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSuite.scala @@ -22,52 +22,46 @@ import cats.syntax.all._ import scala.concurrent.{Await, Promise} import scala.concurrent.duration._ -class HelperThreadParkSuite extends BaseSpec { +class HelperThreadParkSuite extends BaseSuite { - "HelperThread" should { - "not give up when fibers are late" in real { - def smallRuntime(): IORuntime = { - lazy val rt: IORuntime = { - val (blocking, blockDown) = - IORuntime.createDefaultBlockingExecutionContext(threadPrefix = - s"io-blocking-${getClass.getName}") - val (scheduler, schedDown) = - IORuntime.createDefaultScheduler(threadPrefix = s"io-scheduler-${getClass.getName}") - val (compute, _, compDown) = - IORuntime.createWorkStealingComputeThreadPool( - threadPrefix = s"io-compute-${getClass.getName}", - threads = 2) + real("not give up when fibers are late") { + def smallRuntime(): IORuntime = { + lazy val rt: IORuntime = { + val (blocking, blockDown) = + IORuntime.createDefaultBlockingExecutionContext(threadPrefix = + s"io-blocking-${getClass.getName}") + val (scheduler, schedDown) = + IORuntime.createDefaultScheduler(threadPrefix = s"io-scheduler-${getClass.getName}") + val (compute, _, compDown) = + IORuntime.createWorkStealingComputeThreadPool( + threadPrefix = s"io-compute-${getClass.getName}", + threads = 2) - IORuntime( - compute, - blocking, - scheduler, - { () => - compDown() - blockDown() - schedDown() - }, - IORuntimeConfig() - ) - } - - rt + IORuntime( + compute, + blocking, + scheduler, + { () => + compDown() + blockDown() + schedDown() + }, + IORuntimeConfig() + ) } - Resource.make(IO(smallRuntime()))(rt => IO(rt.shutdown())).use { rt => - val io = for { - p <- IO(Promise[Unit]()) - _ <- (IO.sleep(50.millis) *> IO( - rt.scheduler.sleep(100.millis, () => p.success(())))).start - _ <- IO(Await.result(p.future, Duration.Inf)) - } yield () + rt + } + + Resource.make(IO(smallRuntime()))(rt => IO(rt.shutdown())).use { rt => + val io = for { + p <- IO(Promise[Unit]()) + _ <- (IO.sleep(50.millis) *> IO( + rt.scheduler.sleep(100.millis, () => p.success(())))).start + _ <- IO(Await.result(p.future, Duration.Inf)) + } yield () - List - .fill(10)(io.start) - .sequence - .flatMap(_.traverse(_.joinWithNever)) - .evalOn(rt.compute) >> IO(ok) - } + List.fill(10)(io.start).sequence.flatMap(_.traverse(_.joinWithNever)).evalOn(rt.compute) } } } diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/IOLocalsSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/IOLocalsSuite.scala index c0a6e58135..e06fd3a6c8 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/IOLocalsSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/IOLocalsSuite.scala @@ -17,39 +17,34 @@ package cats.effect package unsafe -class IOLocalsSuite extends BaseSpec { +class IOLocalsSuite extends BaseSuite { - "IOLocals" should { - "return a default value" in real { - IOLocal(42) - .flatMap(local => IO(local.unsafeThreadLocal().get())) - .map(_ must beEqualTo(42)) - } - - "return a set value" in real { - for { - local <- IOLocal(42) - threadLocal <- IO(local.unsafeThreadLocal()) - _ <- local.set(24) - got <- IO(threadLocal.get()) - } yield got must beEqualTo(24) - } + real("return a default value") { + IOLocal(42).flatMap(local => IO(local.unsafeThreadLocal().get())).map(assertEquals(_, 42)) + } - "unsafely set" in real { - IOLocal(42).flatMap(local => - IO(local.unsafeThreadLocal().set(24)) *> local.get.map(_ must beEqualTo(24))) - } + real("return a set value") { + for { + local <- IOLocal(42) + threadLocal <- IO(local.unsafeThreadLocal()) + _ <- local.set(24) + got <- IO(threadLocal.get()) + } yield assertEquals(got, 24) + } - "unsafely reset" in real { - for { - local <- IOLocal(42) - threadLocal <- IO(local.unsafeThreadLocal()) - _ <- local.set(24) - _ <- IO(threadLocal.remove()) - got <- local.get - } yield got must beEqualTo(42) - } + real("unsafely set") { + IOLocal(42).flatMap(local => + IO(local.unsafeThreadLocal().set(24)) *> local.get.map(assertEquals(_, 24))) + } + real("unsafely reset") { + for { + local <- IOLocal(42) + threadLocal <- IO(local.unsafeThreadLocal()) + _ <- local.set(24) + _ <- IO(threadLocal.remove()) + got <- local.get + } yield assertEquals(got, 42) } } diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSuite.scala index a0bd06a38f..cfbbc1bca0 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSuite.scala @@ -16,108 +16,107 @@ package cats.effect.unsafe -import org.specs2.mutable.Specification +import cats.effect.BaseSuite import scala.annotation.tailrec import scala.concurrent.duration._ -class SleepersSuite extends Specification { +class SleepersSuite extends BaseSuite { - "SleepCallback" should { - "have a trigger time in the future" in { - val sleepers = new TimerHeap - val now = 100.millis.toNanos - val delay = 500.millis.toNanos - sleepers.insert(now, delay, _ => (), new Array(1)) - val triggerTime = sleepers.peekFirstTriggerTime() - val expected = 600.millis.toNanos // delay + now + test("have a trigger time in the future") { + val sleepers = new TimerHeap + val now = 100.millis.toNanos + val delay = 500.millis.toNanos + sleepers.insert(now, delay, _ => (), new Array(1)) + val triggerTime = sleepers.peekFirstTriggerTime() + val expected = 600.millis.toNanos // delay + now - triggerTime mustEqual expected - } + assertEquals(triggerTime, expected) + } - def collectOuts(outs: (Long, Array[Right[Nothing, Unit] => Unit])*) - : List[(Long, Right[Nothing, Unit] => Unit)] = - outs.toList.flatMap { - case (now, out) => - Option(out(0)).map(now -> _).toList - } + def collectOuts(outs: (Long, Array[Right[Nothing, Unit] => Unit])*) + : List[(Long, Right[Nothing, Unit] => Unit)] = + outs.toList.flatMap { + case (now, out) => + Option(out(0)).map(now -> _).toList + } - def dequeueAll(sleepers: TimerHeap): List[(Long, Right[Nothing, Unit] => Unit)] = { - @tailrec - def loop(acc: List[(Long, Right[Nothing, Unit] => Unit)]) - : List[(Long, Right[Nothing, Unit] => Unit)] = { - val tt = sleepers.peekFirstTriggerTime() - if (tt == Long.MinValue) acc.reverse - else { - val cb = sleepers.pollFirstIfTriggered(now = tt) - loop((tt, cb) :: acc) - } + private def dequeueAll(sleepers: TimerHeap): List[(Long, Right[Nothing, Unit] => Unit)] = { + @tailrec + def loop(acc: List[(Long, Right[Nothing, Unit] => Unit)]) + : List[(Long, Right[Nothing, Unit] => Unit)] = { + val tt = sleepers.peekFirstTriggerTime() + if (tt == Long.MinValue) acc.reverse + else { + val cb = sleepers.pollFirstIfTriggered(now = tt) + loop((tt, cb) :: acc) } - - loop(Nil) } - // creates a new callback, making sure it's a separate object: - def newCb(): Right[Nothing, Unit] => Unit = { - new Function1[Right[Nothing, Unit], Unit] { def apply(x: Right[Nothing, Unit]) = () } - } + loop(Nil) + } - "be ordered according to the trigger time" in { - val sleepers = new TimerHeap + // creates a new callback, making sure it's a separate object: + def newCb(): Right[Nothing, Unit] => Unit = { + new Function1[Right[Nothing, Unit], Unit] { def apply(x: Right[Nothing, Unit]) = () } + } - val now1 = 100.millis.toNanos - val delay1 = 500.millis.toNanos - val expected1 = 600.millis.toNanos // delay1 + now1 + test("be ordered according to the trigger time") { + val sleepers = new TimerHeap - val now2 = 200.millis.toNanos - val delay2 = 100.millis.toNanos - val expected2 = 300.millis.toNanos // delay2 + now2 + val now1 = 100.millis.toNanos + val delay1 = 500.millis.toNanos + val expected1 = 600.millis.toNanos // delay1 + now1 - val now3 = 300.millis.toNanos - val delay3 = 50.millis.toNanos - val expected3 = 350.millis.toNanos // delay3 + now3 + val now2 = 200.millis.toNanos + val delay2 = 100.millis.toNanos + val expected2 = 300.millis.toNanos // delay2 + now2 - val cb1 = newCb() - val cb2 = newCb() - val cb3 = newCb() + val now3 = 300.millis.toNanos + val delay3 = 50.millis.toNanos + val expected3 = 350.millis.toNanos // delay3 + now3 - val out1 = new Array[Right[Nothing, Unit] => Unit](1) - val out2 = new Array[Right[Nothing, Unit] => Unit](1) - val out3 = new Array[Right[Nothing, Unit] => Unit](1) - sleepers.insert(now1, delay1, cb1, out1) - sleepers.insert(now2, delay2, cb2, out2) - sleepers.insert(now3, delay3, cb3, out3) + val cb1 = newCb() + val cb2 = newCb() + val cb3 = newCb() - val ordering = - collectOuts(now1 -> out1, now2 -> out2, now3 -> out3) ::: dequeueAll(sleepers) - val expectedOrdering = List(expected2 -> cb2, expected3 -> cb3, expected1 -> cb1) + val out1 = new Array[Right[Nothing, Unit] => Unit](1) + val out2 = new Array[Right[Nothing, Unit] => Unit](1) + val out3 = new Array[Right[Nothing, Unit] => Unit](1) + sleepers.insert(now1, delay1, cb1, out1) + sleepers.insert(now2, delay2, cb2, out2) + sleepers.insert(now3, delay3, cb3, out3) - ordering mustEqual expectedOrdering - } + val ordering = + collectOuts(now1 -> out1, now2 -> out2, now3 -> out3) ::: dequeueAll(sleepers) + val expectedOrdering = List(expected2 -> cb2, expected3 -> cb3, expected1 -> cb1) + + assertEquals(ordering, expectedOrdering) + } - "be ordered correctly even if Long overflows" in { - val sleepers = new TimerHeap + test("be ordered correctly even if Long overflows") { + val sleepers = new TimerHeap - val now1 = Long.MaxValue - 20L - val delay1 = 10.nanos.toNanos - // val expected1 = Long.MaxValue - 10L // no overflow yet + val now1 = Long.MaxValue - 20L + val delay1 = 10.nanos.toNanos + // val expected1 = Long.MaxValue - 10L // no overflow yet - val now2 = Long.MaxValue - 5L - val delay2 = 10.nanos.toNanos - val expected2 = Long.MinValue + 4L // overflow + val now2 = Long.MaxValue - 5L + val delay2 = 10.nanos.toNanos + val expected2 = Long.MinValue + 4L // overflow - val cb1 = newCb() - val cb2 = newCb() + val cb1 = newCb() + val cb2 = newCb() - val out1 = new Array[Right[Nothing, Unit] => Unit](1) - val out2 = new Array[Right[Nothing, Unit] => Unit](1) - sleepers.insert(now1, delay1, cb1, out1) - sleepers.insert(now2, delay2, cb2, out2) + val out1 = new Array[Right[Nothing, Unit] => Unit](1) + val out2 = new Array[Right[Nothing, Unit] => Unit](1) + sleepers.insert(now1, delay1, cb1, out1) + sleepers.insert(now2, delay2, cb2, out2) - val ordering = collectOuts(now1 -> out1, now2 -> out2) ::: dequeueAll(sleepers) - val expectedOrdering = List(now2 -> cb1, expected2 -> cb2) + val ordering = collectOuts(now1 -> out1, now2 -> out2) ::: dequeueAll(sleepers) + val expectedOrdering = List(now2 -> cb1, expected2 -> cb2) - ordering mustEqual expectedOrdering - } + assertEquals(ordering, expectedOrdering) } + } diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala index ac933964c6..a4aa4bf189 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/StripedHashtableSuite.scala @@ -23,7 +23,7 @@ import scala.concurrent.duration._ import java.util.concurrent.CountDownLatch -class StripedHashtableSuite extends BaseSpec { +class StripedHashtableSuite extends BaseSuite { override def executionTimeout: FiniteDuration = 30.seconds @@ -52,37 +52,37 @@ class StripedHashtableSuite extends BaseSpec { rt } - "StripedHashtable" should { - "work correctly in the presence of many unsafeRuns" in real { - val iterations = 1000000 + real("work correctly in the presence of many unsafeRuns") { + val iterations = 1000000 - object Boom extends RuntimeException("Boom!") + object Boom extends RuntimeException("Boom!") - def io(n: Int): IO[Unit] = - (n % 3) match { - case 0 => IO.unit - case 1 => IO.canceled - case 2 => IO.raiseError[Unit](Boom) - } + def io(n: Int): IO[Unit] = + (n % 3) match { + case 0 => IO.unit + case 1 => IO.canceled + case 2 => IO.raiseError[Unit](Boom) + } - Resource.make(IO(hashtableRuntime()))(rt => IO(rt.shutdown())).use { rt => - IO(new CountDownLatch(iterations)).flatMap { counter => - (0 until iterations) - .toList - .traverse { n => IO(io(n).unsafeRunAsync { _ => counter.countDown() }(rt)) } - .flatMap { _ => IO.blocking(counter.await()) } - .flatMap { _ => - IO.blocking { - rt.fiberErrorCbs.synchronized { + Resource.make(IO(hashtableRuntime()))(rt => IO(rt.shutdown())).use { rt => + IO(new CountDownLatch(iterations)).flatMap { counter => + (0 until iterations) + .toList + .traverse { n => IO(io(n).unsafeRunAsync { _ => counter.countDown() }(rt)) } + .flatMap { _ => IO.blocking(counter.await()) } + .flatMap { _ => + IO.blocking { + rt.fiberErrorCbs.synchronized { + assert( rt.fiberErrorCbs.tables.forall { table => // check that each component hashtable of the larger striped // hashtable is empty and has shrunk to its initial capacity table.isEmpty && table.unsafeCapacity() == table.unsafeInitialCapacity() - } mustEqual true - } + } + ) } } - } + } } } } diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSuite.scala index a80a4f39f7..cd0645f566 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSuite.scala @@ -16,9 +16,9 @@ package cats.effect.unsafe -import org.specs2.mutable.Specification +import cats.effect.BaseSuite -class TimerHeapSuite extends Specification { +class TimerHeapSuite extends BaseSuite { /** * Creates a new callback, making sure it's a separate object @@ -34,121 +34,117 @@ class TimerHeapSuite extends Specification { private val cb4 = newCb() private val cb5 = newCb() - "TimerHeap" should { + test("correctly insert / pollFirstIfTriggered") { + val m = new TimerHeap + val out = new Array[Right[Nothing, Unit] => Unit](1) + assert(m.pollFirstIfTriggered(Long.MinValue) eq null) + assert(m.pollFirstIfTriggered(Long.MaxValue) eq null) + assertEquals(m.toString, "TimerHeap()") - "correctly insert / pollFirstIfTriggered" in { - val m = new TimerHeap - val out = new Array[Right[Nothing, Unit] => Unit](1) - m.pollFirstIfTriggered(Long.MinValue) must beNull - m.pollFirstIfTriggered(Long.MaxValue) must beNull - m.toString mustEqual "TimerHeap()" + m.insert(0L, 0L, cb0, out) + assert(out(0) eq null) + assertEquals(m.toString, "TimerHeap(...)") + assert(m.pollFirstIfTriggered(Long.MinValue + 1) eq null) + assertEquals(m.pollFirstIfTriggered(Long.MaxValue), cb0) + assert(m.pollFirstIfTriggered(Long.MaxValue) eq null) + assert(m.pollFirstIfTriggered(Long.MinValue) eq null) - m.insert(0L, 0L, cb0, out) - out(0) must beNull - m.toString mustEqual "TimerHeap(...)" - m.pollFirstIfTriggered(Long.MinValue + 1) must beNull - m.pollFirstIfTriggered(Long.MaxValue) mustEqual cb0 - m.pollFirstIfTriggered(Long.MaxValue) must beNull - m.pollFirstIfTriggered(Long.MinValue) must beNull + m.insert(0L, 10L, cb0, out) + assert(out(0) eq null) + m.insert(0L, 30L, cb1, out) + assert(out(0) eq null) + m.insert(0L, 0L, cb2, out) + assert(out(0) eq null) + m.insert(0L, 20L, cb3, out) + assertEquals(out(0), cb2) + assert(m.pollFirstIfTriggered(-1L) eq null) + assert(m.pollFirstIfTriggered(0L) eq null) + assertEquals(m.pollFirstIfTriggered(10L), cb0) + assert(m.pollFirstIfTriggered(10L) eq null) + assertEquals(m.pollFirstIfTriggered(20L), cb3) + assert(m.pollFirstIfTriggered(20L) eq null) + assertEquals(m.pollFirstIfTriggered(30L), cb1) + assert(m.pollFirstIfTriggered(30L) eq null) + assert(m.pollFirstIfTriggered(Long.MaxValue) eq null) + } - m.insert(0L, 10L, cb0, out) - out(0) must beNull - m.insert(0L, 30L, cb1, out) - out(0) must beNull - m.insert(0L, 0L, cb2, out) - out(0) must beNull - m.insert(0L, 20L, cb3, out) - out(0) mustEqual cb2 - m.pollFirstIfTriggered(-1L) must beNull - m.pollFirstIfTriggered(0L) must beNull - m.pollFirstIfTriggered(10L) mustEqual cb0 - m.pollFirstIfTriggered(10L) must beNull - m.pollFirstIfTriggered(20L) mustEqual cb3 - m.pollFirstIfTriggered(20L) must beNull - m.pollFirstIfTriggered(30L) mustEqual cb1 - m.pollFirstIfTriggered(30L) must beNull - m.pollFirstIfTriggered(Long.MaxValue) must beNull - } + test("correctly insert / remove (cancel)") { + val m = new TimerHeap + val out = new Array[Right[Nothing, Unit] => Unit](1) + val r0 = m.insert(0L, 1L, cb0, out) + assert(out(0) eq null) + val r1 = m.insert(0L, 2L, cb1, out) + assert(out(0) eq null) + val r5 = m.insert(0L, 6L, cb5, out) + assert(out(0) eq null) + val r4 = m.insert(0L, 5L, cb4, out) + assert(out(0) eq null) + val r2 = m.insert(0L, 3L, cb2, out) + assert(out(0) eq null) + val r3 = m.insert(0L, 4L, cb3, out) + assert(out(0) eq null) - "correctly insert / remove (cancel)" in { - val m = new TimerHeap - val out = new Array[Right[Nothing, Unit] => Unit](1) - val r0 = m.insert(0L, 1L, cb0, out) - out(0) must beNull - val r1 = m.insert(0L, 2L, cb1, out) - out(0) must beNull - val r5 = m.insert(0L, 6L, cb5, out) - out(0) must beNull - val r4 = m.insert(0L, 5L, cb4, out) - out(0) must beNull - val r2 = m.insert(0L, 3L, cb2, out) - out(0) must beNull - val r3 = m.insert(0L, 4L, cb3, out) - out(0) must beNull + assertEquals(m.peekFirstQuiescent(), cb0) + assertEquals(m.peekFirstTriggerTime(), 1L) + r0.run() + assertEquals(m.peekFirstTriggerTime(), 2L) + assertEquals(m.peekFirstQuiescent(), cb1) + assertEquals(m.pollFirstIfTriggered(Long.MaxValue), cb1) + assertEquals(m.peekFirstQuiescent(), cb2) + assertEquals(m.peekFirstTriggerTime(), 3L) + r1.run() // NOP + r3.run() + m.packIfNeeded() + assertEquals(m.peekFirstQuiescent(), cb2) + assertEquals(m.peekFirstTriggerTime(), 3L) + assertEquals(m.pollFirstIfTriggered(Long.MaxValue), cb2) + assertEquals(m.peekFirstQuiescent(), cb4) + assertEquals(m.peekFirstTriggerTime(), 5L) + assertEquals(m.pollFirstIfTriggered(Long.MaxValue), cb4) + assertEquals(m.peekFirstQuiescent(), cb5) + assertEquals(m.peekFirstTriggerTime(), 6L) + r2.run() + r5.run() + m.packIfNeeded() + assert(m.peekFirstQuiescent() eq null) + assertEquals(m.peekFirstTriggerTime(), Long.MinValue) + assert(m.pollFirstIfTriggered(Long.MaxValue) eq null) + r4.run() // NOP + m.packIfNeeded() + assert(m.pollFirstIfTriggered(Long.MaxValue) eq null) + } - m.peekFirstQuiescent() mustEqual cb0 - m.peekFirstTriggerTime() mustEqual 1L - r0.run() - m.peekFirstTriggerTime() mustEqual 2L - m.peekFirstQuiescent() mustEqual cb1 - m.pollFirstIfTriggered(Long.MaxValue) mustEqual cb1 - m.peekFirstQuiescent() mustEqual cb2 - m.peekFirstTriggerTime() mustEqual 3L - r1.run() // NOP - r3.run() - m.packIfNeeded() - m.peekFirstQuiescent() mustEqual cb2 - m.peekFirstTriggerTime() mustEqual 3L - m.pollFirstIfTriggered(Long.MaxValue) mustEqual cb2 - m.peekFirstQuiescent() mustEqual cb4 - m.peekFirstTriggerTime() mustEqual 5L - m.pollFirstIfTriggered(Long.MaxValue) mustEqual cb4 - m.peekFirstQuiescent() mustEqual cb5 - m.peekFirstTriggerTime() mustEqual 6L - r2.run() - r5.run() - m.packIfNeeded() - m.peekFirstQuiescent() must beNull - m.peekFirstTriggerTime() mustEqual Long.MinValue - m.pollFirstIfTriggered(Long.MaxValue) must beNull - r4.run() // NOP - m.packIfNeeded() - m.pollFirstIfTriggered(Long.MaxValue) must beNull + test("behave correctly when nanoTime wraps around") { + val m = new TimerHeap + val startFrom = Long.MaxValue - 100L + var nanoTime = startFrom + val removers = new Array[Runnable](200) + val callbacksBuilder = Vector.newBuilder[Right[Nothing, Unit] => Unit] + val triggeredBuilder = Vector.newBuilder[Right[Nothing, Unit] => Unit] + for (i <- 0 until 200) { + if (i >= 10 && i % 2 == 0) removers(i - 10).run() + val cb = newCb() + val out = new Array[Right[Nothing, Unit] => Unit](1) + val r = m.insert(nanoTime, 10L, cb, out) + triggeredBuilder ++= Option(out(0)) + removers(i) = r + callbacksBuilder += cb + nanoTime += 1L } - - "behave correctly when nanoTime wraps around" in { - val m = new TimerHeap - val startFrom = Long.MaxValue - 100L - var nanoTime = startFrom - val removers = new Array[Runnable](200) - val callbacksBuilder = Vector.newBuilder[Right[Nothing, Unit] => Unit] - val triggeredBuilder = Vector.newBuilder[Right[Nothing, Unit] => Unit] - for (i <- 0 until 200) { - if (i >= 10 && i % 2 == 0) removers(i - 10).run() - val cb = newCb() - val out = new Array[Right[Nothing, Unit] => Unit](1) - val r = m.insert(nanoTime, 10L, cb, out) - triggeredBuilder ++= Option(out(0)) - removers(i) = r - callbacksBuilder += cb - nanoTime += 1L - } - for (idx <- 190 until removers.size by 2) { - removers(idx).run() - } - nanoTime += 100L - val callbacks = callbacksBuilder.result() - while ({ - val cb = m.pollFirstIfTriggered(nanoTime) - triggeredBuilder ++= Option(cb) - cb ne null - }) {} - val triggered = triggeredBuilder.result() - - val nonCanceled = callbacks.grouped(2).map(_.last).toVector - triggered should beEqualTo(nonCanceled) - - ok + for (idx <- 190 until removers.size by 2) { + removers(idx).run() } + nanoTime += 100L + val callbacks = callbacksBuilder.result() + while ({ + val cb = m.pollFirstIfTriggered(nanoTime) + triggeredBuilder ++= Option(cb) + cb ne null + }) {} + val triggered = triggeredBuilder.result() + + val nonCanceled = callbacks.grouped(2).map(_.last).toVector + assertEquals(triggered, nonCanceled) } + } diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/WorkerThreadNameSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/WorkerThreadNameSuite.scala index 0e730745b1..a5b5c54a7c 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/WorkerThreadNameSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/WorkerThreadNameSuite.scala @@ -16,12 +16,12 @@ package cats.effect.unsafe -import cats.effect.BaseSpec +import cats.effect.BaseSuite import cats.effect.testkit.TestInstances import scala.concurrent.duration._ -class WorkerThreadNameSuite extends BaseSpec with TestInstances { +class WorkerThreadNameSuite extends BaseSuite with TestInstances { override def runtime(): IORuntime = { lazy val rt: IORuntime = { @@ -53,7 +53,7 @@ class WorkerThreadNameSuite extends BaseSpec with TestInstances { rt } - "WorkerThread" should { + /*"WorkerThread" should { "rename itself when entering and exiting blocking region" in skipped( "this test is quite flaky in CI" ) /*real { @@ -87,13 +87,13 @@ class WorkerThreadNameSuite extends BaseSpec with TestInstances { } resetBlockerThread must beSome[String].setMessage( "blocker thread not found after reset") - resetBlockerThread must beSome((_: String).startsWith("io-compute")) + assertEquals(resetBlockerThread, Some((_: String)).startsWith("io-compute")) .setMessage("blocker thread name was not reset") - resetBlockerThread must beSome((_: String).endsWith("-0")) + assertEquals(resetBlockerThread, Some((_: String)).endsWith("-0")) .setMessage("blocker thread index was not correct") } }*/ - } + }*/ /*private val threadInfo = IO((Thread.currentThread().getName(), Thread.currentThread().getId()))*/ diff --git a/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSuite.scala b/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSuite.scala index ff104b5f7c..42d423d1b2 100644 --- a/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSuite.scala +++ b/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSuite.scala @@ -30,7 +30,7 @@ import scala.scalanative.unsigned._ import java.io.IOException -class FileDescriptorPollerSuite extends BaseSpec { +class FileDescriptorPollerSuite extends BaseSuite { final class Pipe( val readFd: Int, @@ -101,44 +101,41 @@ class FileDescriptorPollerSuite extends BaseSpec { } } - "FileDescriptorPoller" should { - - "notify read-ready events" in real { - mkPipe.use { pipe => - for { - buf <- IO(new Array[Byte](4)) - _ <- pipe.write(Array[Byte](1, 2, 3), 0, 3).background.surround(pipe.read(buf, 0, 3)) - _ <- pipe.write(Array[Byte](42), 0, 1).background.surround(pipe.read(buf, 3, 1)) - } yield buf.toList must be_==(List[Byte](1, 2, 3, 42)) - } + real("notify read-ready events") { + mkPipe.use { pipe => + for { + buf <- IO(new Array[Byte](4)) + _ <- pipe.write(Array[Byte](1, 2, 3), 0, 3).background.surround(pipe.read(buf, 0, 3)) + _ <- pipe.write(Array[Byte](42), 0, 1).background.surround(pipe.read(buf, 3, 1)) + } yield assertEquals(buf.toList, List[Byte](1, 2, 3, 42)) } + } - "handle lots of simultaneous events" in real { - def test(n: Int) = mkPipe.replicateA(n).use { pipes => - CountDownLatch[IO](n).flatMap { latch => - pipes - .traverse_ { pipe => - (pipe.read(new Array[Byte](1), 0, 1) *> latch.release).background - } - .surround { - IO { // trigger all the pipes at once - pipes.foreach { pipe => - unistd.write(pipe.writeFd, Array[Byte](42).atUnsafe(0), 1.toULong) - } - }.background.surround(latch.await.as(true)) - } - } + real("handle lots of simultaneous events") { + def test(n: Int) = mkPipe.replicateA(n).use { pipes => + CountDownLatch[IO](n).flatMap { latch => + pipes + .traverse_ { pipe => + (pipe.read(new Array[Byte](1), 0, 1) *> latch.release).background + } + .surround { + IO { // trigger all the pipes at once + pipes.foreach { pipe => + unistd.write(pipe.writeFd, Array[Byte](42).atUnsafe(0), 1.toULong) + } + }.background.surround(latch.await.as(true)) + } } - - // multiples of 64 to exercise ready queue draining logic - test(64) *> test(128) *> - test(1000) // a big, non-64-multiple } - "hang if never ready" in real { - mkPipe.use { pipe => - pipe.read(new Array[Byte](1), 0, 1).as(false).timeoutTo(1.second, IO.pure(true)) - } + // multiples of 64 to exercise ready queue draining logic + test(64) *> test(128) *> + test(1000) // a big, non-64-multiple + } + + real("hang if never ready") { + mkPipe.use { pipe => + pipe.read(new Array[Byte](1), 0, 1).as(false).timeoutTo(1.second, IO.pure(true)) } } diff --git a/tests/native/src/test/scala/cats/effect/IOPlatformSpecification.scala b/tests/native/src/test/scala/cats/effect/IOPlatformSuite.scala similarity index 75% rename from tests/native/src/test/scala/cats/effect/IOPlatformSpecification.scala rename to tests/native/src/test/scala/cats/effect/IOPlatformSuite.scala index 05dfc8973d..bc77537401 100644 --- a/tests/native/src/test/scala/cats/effect/IOPlatformSpecification.scala +++ b/tests/native/src/test/scala/cats/effect/IOPlatformSuite.scala @@ -16,19 +16,17 @@ package cats.effect -import org.specs2.ScalaCheck +trait IOPlatformSuite { self: BaseSuite with munit.ScalaCheckSuite => -trait IOPlatformSpecification { self: BaseSpec with ScalaCheck => - - def platformSpecs = "platform" should { - "realTimeInstant should return an Instant constructed from realTime" in ticked { + def platformTests() = { + ticked("realTimeInstant should return an Instant constructed from realTime") { implicit ticker => val op = for { now <- IO.realTimeInstant realTime <- IO.realTime } yield now.toEpochMilli == realTime.toMillis - op must completeAs(true) + assertCompleteAs(op, true) } } } diff --git a/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala b/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala index b1b9fcc7eb..74c2c6eef8 100644 --- a/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala +++ b/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala @@ -19,28 +19,26 @@ package unsafe import scala.concurrent.duration._ -class SchedulerSuite extends BaseSpec { +class SchedulerSuite extends BaseSuite { - "Default scheduler" should { - "use high-precision time" in real { - for { - start <- IO.realTime - times <- IO.realTime.replicateA(100) - deltas = times.map(_ - start) - } yield deltas.exists(_.toMicros % 1000 != 0) - } + real("use high-precision time") { + for { + start <- IO.realTime + times <- IO.realTime.replicateA(100) + deltas = times.map(_ - start) + } yield deltas.exists(_.toMicros % 1000 != 0) + } - "correctly calculate real time" in real { - IO.realTime.product(IO(System.currentTimeMillis())).map { - case (realTime, currentTime) => - (realTime.toMillis - currentTime) should be_<=(1L) - } + real("correctly calculate real time") { + IO.realTime.product(IO(System.currentTimeMillis())).map { + case (realTime, currentTime) => + assert(realTime.toMillis - currentTime <= 1L) } + } - "sleep for correct duration" in real { - val duration = 1500.millis - IO.sleep(duration).timed.map(_._1 should be_>=(duration)) - } + real("sleep for correct duration") { + val duration = 1500.millis + IO.sleep(duration).timed.map(r => assert(r._1 >= duration)) } } diff --git a/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSuite.scala b/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSuite.scala index 9ae9b4487e..40b8004e47 100644 --- a/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSuite.scala +++ b/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSuite.scala @@ -16,9 +16,9 @@ package cats.effect -class IOImplicitSuite extends BaseSpec { +class IOImplicitSuite extends BaseSuite { - "Can resolve IO sequence ops without import of cats.syntax.all" in { // compilation test + test("Can resolve IO sequence ops without import of cats.syntax.all") { // compilation test for { _ <- List(IO(1)).sequence_ _ <- Option(IO(1)).sequence @@ -28,7 +28,7 @@ class IOImplicitSuite extends BaseSpec { true } - "Can resolve IO.Par ops without import of cats.syntax.all" in { // compilation test + test("Can resolve IO.Par ops without import of cats.syntax.all") { // compilation test for { _ <- Option(IO(1)).parSequence _ <- Option(IO(1)).parSequence_ diff --git a/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSuite.scala b/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSuite.scala index d3ffcad497..92de0eb7a9 100644 --- a/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSuite.scala +++ b/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSuite.scala @@ -17,17 +17,17 @@ package not.cats.effect // verifies scoping outside of CE import cats.{Align, CommutativeApplicative} -import cats.effect.{BaseSpec, IO} +import cats.effect.{BaseSuite, IO} import cats.syntax.all._ -class IOParImplicitSuite extends BaseSpec { +class IOParImplicitSuite extends BaseSuite { - "Can resolve CommutativeApplicative instance" in { + test("Can resolve CommutativeApplicative instance") { List(1, 2, 3).parUnorderedTraverse(_ => IO.unit) // compilation test true } - "Can resolve IO.Par instances" in { // compilation test + test("Can resolve IO.Par instances") { // compilation test Align[IO.Par] CommutativeApplicative[IO.Par] true diff --git a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala index 7b80b43258..2d65c8622f 100644 --- a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala @@ -16,8 +16,6 @@ package cats.effect -import org.specs2.mutable.Specification +import munit.FunSuite -trait BaseSpec extends Specification with Runners - -trait BaseSuite extends munit.FunSuite with MUnitRunners \ No newline at end of file +trait BaseSuite extends FunSuite with Runners diff --git a/tests/shared/src/test/scala/cats/effect/CallbackStackSuite.scala b/tests/shared/src/test/scala/cats/effect/CallbackStackSuite.scala index 63c038f8cd..44d1c3e253 100644 --- a/tests/shared/src/test/scala/cats/effect/CallbackStackSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/CallbackStackSuite.scala @@ -16,40 +16,36 @@ package cats.effect -class CallbackStackSuite extends BaseSpec with DetectPlatform { - - "CallbackStack" should { - "correctly report the number removed" in { - val stack = CallbackStack.of[Unit](null) - val handle = stack.push(_ => ()) - stack.push(_ => ()) - val removed = stack.clearHandle(handle) - if (removed) - stack.pack(1) mustEqual 0 - else - stack.pack(1) mustEqual 1 - } +class CallbackStackSuite extends BaseSuite with DetectPlatform { + + test("correctly report the number removed") { + val stack = CallbackStack.of[Unit](null) + val handle = stack.push(_ => ()) + stack.push(_ => ()) + val removed = stack.clearHandle(handle) + if (removed) + assertEquals(stack.pack(1), 0) + else + assertEquals(stack.pack(1), 1) + } - "handle race conditions in pack" in real { + real("handle race conditions in pack") { - IO(CallbackStack.of[Unit](null)).flatMap { stack => - val pushClearPack = for { - handle <- IO(stack.push(_ => ())) - removed <- IO(stack.clearHandle(handle)) - packed <- IO(stack.pack(1)) - } yield (if (removed) 1 else 0) + packed + IO(CallbackStack.of[Unit](null)).flatMap { stack => + val pushClearPack = for { + handle <- IO(stack.push(_ => ())) + removed <- IO(stack.clearHandle(handle)) + packed <- IO(stack.pack(1)) + } yield (if (removed) 1 else 0) + packed - val concurrency = Math.max(2, Runtime.getRuntime().availableProcessors()) + val concurrency = Math.max(2, Runtime.getRuntime().availableProcessors()) - pushClearPack - .parReplicateA(concurrency) - .product(IO(stack.pack(1))) - .flatMap { case (xs, y) => IO((xs.sum + y) mustEqual concurrency) } - .replicateA_(if (isJS || isNative) 1 else 1000) - .as(ok) - } + pushClearPack + .parReplicateA(concurrency) + .product(IO(stack.pack(1))) + .flatMap { case (xs, y) => IO(assertEquals(xs.sum + y, concurrency)) } + .replicateA_(if (isJS || isNative) 1 else 1000) } - } } diff --git a/tests/shared/src/test/scala/cats/effect/ContSuite.scala b/tests/shared/src/test/scala/cats/effect/ContSuite.scala index 5362b3b808..ef33f87d41 100644 --- a/tests/shared/src/test/scala/cats/effect/ContSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/ContSuite.scala @@ -20,8 +20,6 @@ package effect import cats.effect.syntax.all._ import cats.syntax.all._ -import org.specs2.execute._ - import scala.concurrent.duration._ class ContSuite extends ContSpecBase { @@ -34,18 +32,18 @@ class DefaultContSuite extends ContSpecBase { Async.defaultCont(body) } -trait ContSpecBase extends BaseSpec with ContSpecBasePlatform { outer => +trait ContSpecBase extends BaseSuite with ContSpecBasePlatform { outer => def cont[K, R](body: Cont[IO, K, R]): IO[R] - def execute(io: IO[_], times: Int, i: Int = 0): IO[Success] = { - if (i == times) IO(success) + def execute(io: IO[_], times: Int, i: Int = 0): IO[Unit] = { + if (i == times) IO.unit else io >> execute(io, times, i + 1) } type Cancelable[F[_]] = MonadCancel[F, Throwable] - "get resumes" in real { + real("get resumes") { val io = cont { new Cont[IO, Int, String] { def apply[F[_]: Cancelable] = { (resume, get, lift) => @@ -54,12 +52,12 @@ trait ContSpecBase extends BaseSpec with ContSpecBasePlatform { outer => } } - val test = io.flatMap(r => IO(r mustEqual "42")) + val test = io.flatMap(r => IO(assertEquals(r, "42"))) execute(test, iterations) } - "callback resumes" in realWithRuntime { rt => + realWithRuntime("callback resumes") { rt => val io = cont { new Cont[IO, Int, String] { def apply[F[_]: Cancelable] = { (resume, get, lift) => @@ -70,12 +68,12 @@ trait ContSpecBase extends BaseSpec with ContSpecBasePlatform { outer => } } - val test = io.flatMap(r => IO(r mustEqual "42")) + val test = io.flatMap(r => IO(assertEquals(r, "42"))) execute(test, 100) } - "get can be canceled" in real { + real("get can be canceled") { def never = cont { new Cont[IO, Int, String] { @@ -89,7 +87,7 @@ trait ContSpecBase extends BaseSpec with ContSpecBasePlatform { outer => execute(io, iterations) } - "nondeterministic cancelation corner case: get running finalisers " in real { + real("nondeterministic cancelation corner case: get running finalisers ") { import kernel._ def wait(syncLatch: Ref[IO, Boolean]): IO[Unit] = @@ -109,7 +107,7 @@ trait ContSpecBase extends BaseSpec with ContSpecBasePlatform { outer => execute(io, iterations) } - "get within onCancel - 1" in realWithRuntime { rt => + realWithRuntime("get within onCancel - 1") { rt => val flag = Ref[IO].of(false) val io = @@ -127,10 +125,10 @@ trait ContSpecBase extends BaseSpec with ContSpecBasePlatform { outer => }.timeoutTo(1.second, ().pure[IO]) >> (start.get, end.get).tupled } - io.flatMap { r => IO(r mustEqual true -> true) } + io.flatMap { r => IO(assertEquals(r, true -> true)) } } - "get within onCancel - 2" in realWithRuntime { rt => + realWithRuntime("get within onCancel - 2") { rt => val flag = Ref[IO].of(false) val io = @@ -148,10 +146,10 @@ trait ContSpecBase extends BaseSpec with ContSpecBasePlatform { outer => }.timeoutTo(1.second, ().pure[IO]) >> (start.get, end.get).tupled } - io.flatMap { r => IO(r mustEqual true -> true) } + io.flatMap { r => IO(assertEquals(r, true -> true)) } } - "get exclusively within onCancel" in realWithRuntime { rt => + realWithRuntime("get exclusively within onCancel") { rt => val test = cont { new Cont[IO, Unit, Unit] { def apply[F[_]: Cancelable] = { (resume, get, lift) => @@ -161,10 +159,10 @@ trait ContSpecBase extends BaseSpec with ContSpecBasePlatform { outer => } } - test.timeoutTo(500.millis, IO.unit).as(ok) + test.timeoutTo(500.millis, IO.unit) } - "get is idempotent - 1" in real { + real("get is idempotent - 1") { val io = cont { new Cont[IO, Int, String] { def apply[F[_]: Cancelable] = { (resume, get, lift) => @@ -173,12 +171,12 @@ trait ContSpecBase extends BaseSpec with ContSpecBasePlatform { outer => } } - val test = io.flatMap(r => IO(r mustEqual "42")) + val test = io.flatMap(r => IO(assertEquals(r, "42"))) execute(test, iterations) } - "get is idempotent - 2" in realWithRuntime { rt => + realWithRuntime("get is idempotent - 2") { rt => val io = cont { new Cont[IO, Int, String] { def apply[F[_]: Cancelable] = { (resume, get, lift) => @@ -189,7 +187,7 @@ trait ContSpecBase extends BaseSpec with ContSpecBasePlatform { outer => } } - val test = io.flatMap(r => IO(r mustEqual "42")) + val test = io.flatMap(r => IO(assertEquals(r, "42"))) execute(test, 100) } diff --git a/tests/shared/src/test/scala/cats/effect/EitherTIOSuite.scala b/tests/shared/src/test/scala/cats/effect/EitherTIOSuite.scala index 08874e88db..6fcd22f922 100644 --- a/tests/shared/src/test/scala/cats/effect/EitherTIOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/EitherTIOSuite.scala @@ -22,11 +22,13 @@ import cats.effect.laws.AsyncTests import cats.effect.syntax.all._ import cats.laws.discipline.arbitrary._ import cats.syntax.all._ -import munit.DisciplineSuite + import org.scalacheck.Prop import scala.concurrent.duration._ +import munit.DisciplineSuite + class EitherTIOSuite extends BaseSuite with DisciplineSuite { ticked("EitherT should execute finalizers") { implicit ticker => diff --git a/tests/shared/src/test/scala/cats/effect/IOFiberSuite.scala b/tests/shared/src/test/scala/cats/effect/IOFiberSuite.scala index c6269d0309..e92aeca177 100644 --- a/tests/shared/src/test/scala/cats/effect/IOFiberSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IOFiberSuite.scala @@ -16,54 +16,52 @@ package fiber // Get out of CE package b/c trace filtering -import cats.effect.{BaseSpec, DetectPlatform, IO} +import cats.effect.{BaseSuite, DetectPlatform, IO} import scala.concurrent.duration._ -class IOFiberSuite extends BaseSpec with DetectPlatform { +class IOFiberSuite extends BaseSuite with DetectPlatform { - "IOFiber" should { - if (!isJS || !isWSL) { - "toString a running fiber" in real { - def loop: IO[Unit] = IO.unit.flatMap(_ => loop) - val pattern = - raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ RUNNING(: flatMap @ fiber.IOFiberSpec.loop\$$[0-9]\(((.*IOFiberSpec.scala:[0-9]{2})|(Unknown Source))\))?" - for { - f <- loop.start - _ <- IO.sleep(1.milli) - s <- IO(f.toString) - // _ <- IO.println(s) - _ <- f.cancel - _ <- IO(s must beMatching(pattern)) - } yield ok - } - - "toString a suspended fiber" in real { - // separate method to have it in the trace: - def foreverNever = - IO.async[Unit](_ => IO.pure(Some(IO.unit))) - val pattern = - raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ SUSPENDED(: async @ fiber.IOFiberSpec.foreverNever\$$[0-9]\(((.*IOFiberSpec.scala:[0-9]{2})|(Unknown Source))\))?" - for { - f <- foreverNever.start - _ <- IO.sleep(100.milli) - s <- IO(f.toString) - _ <- f.cancel - _ <- IO(s must beMatching(pattern)) - } yield ok - } - } else { - "toString a running fiber" in skipped("Scala.js exception unmangling is buggy on WSL") - "toString a suspended fiber" in skipped("Scala.js exception unmangling is buggy on WSL") + if (!isJS || !isWSL) { + real("toString a running fiber") { + def loop: IO[Unit] = IO.unit.flatMap(_ => loop) + val pattern = + raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ RUNNING(: flatMap @ fiber.IOFiberSuite.loop\$$[0-9]\(((.*IOFiberSuite.scala:[0-9]{2})|(Unknown Source))\))?" + for { + f <- loop.start + _ <- IO.sleep(1.milli) + s <- IO(f.toString) + // _ <- IO.println(s) + _ <- f.cancel + _ <- IO(assert(s.matches(pattern))) + } yield () } - "toString a completed fiber" in real { - val pattern = raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ COMPLETED" + real("toString a suspended fiber") { + // separate method to have it in the trace: + def foreverNever = + IO.async[Unit](_ => IO.pure(Some(IO.unit))) + val pattern = + raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ SUSPENDED(: async @ fiber.IOFiberSuite.foreverNever\$$[0-9]\(((.*IOFiberSuite.scala:[0-9]{2})|(Unknown Source))\))?" for { - f <- IO.unit.start - _ <- f.joinWithNever - _ <- IO(f.toString must beMatching(pattern)) - } yield ok + f <- foreverNever.start + _ <- IO.sleep(100.milli) + s <- IO(f.toString) + _ <- f.cancel + _ <- IO(assert(s.matches(pattern))) + } yield () } + } else { + // "toString a running fiber" in skipped("Scala.js exception unmangling is buggy on WSL") + // "toString a suspended fiber" in skipped("Scala.js exception unmangling is buggy on WSL") + } + + real("toString a completed fiber") { + val pattern = raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ COMPLETED" + for { + f <- IO.unit.start + _ <- f.joinWithNever + _ <- IO(assert(f.toString.matches(pattern))) + } yield () } } diff --git a/tests/shared/src/test/scala/cats/effect/IOLocalSuite.scala b/tests/shared/src/test/scala/cats/effect/IOLocalSuite.scala index db6c5789e2..701f89dee9 100644 --- a/tests/shared/src/test/scala/cats/effect/IOLocalSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IOLocalSuite.scala @@ -19,7 +19,7 @@ package effect import scala.annotation.tailrec -class IOLocalSuite extends BaseSpec { +class IOLocalSuite extends BaseSuite { ioLocalTests( "IOLocal[Int]", @@ -53,28 +53,26 @@ class IOLocalSuite extends BaseSpec { } )((0, ""), (10, "lorem"), _._1, _._1) - "IOLocal.lens" should { - "be stack safe" in ticked { implicit ticker => - @tailrec def stackLens(lens: IOLocal[Int], height: Int): IOLocal[Int] = - if (height <= 0) lens - else stackLens(lens.lens(_ + 1)((_: Int) => (y: Int) => y - 1), height - 1) - - val size = 16384 - val io = for { - lens <- IOLocal(0) - stack = stackLens(lens, size) - d1 <- lens.get - d2 <- stack.get - _ <- stack.set(size) - s2 <- stack.get - s1 <- lens.get - _ <- stack.update(_ / 2) - u2 <- stack.get - u1 <- lens.get - } yield (d1, d2, s2, s1, u2, u1) - - io must completeAs((0, size, size, 0, size / 2, -size / 2)) - } + ticked("IOLocal.lens be stack safe") { implicit ticker => + @tailrec def stackLens(lens: IOLocal[Int], height: Int): IOLocal[Int] = + if (height <= 0) lens + else stackLens(lens.lens(_ + 1)((_: Int) => (y: Int) => y - 1), height - 1) + + val size = 16384 + val io = for { + lens <- IOLocal(0) + stack = stackLens(lens, size) + d1 <- lens.get + d2 <- stack.get + _ <- stack.set(size) + s2 <- stack.get + s1 <- lens.get + _ <- stack.update(_ / 2) + u2 <- stack.get + u1 <- lens.get + } yield (d1, d2, s2, s1, u2, u1) + + assertCompleteAs(io, (0, size, size, 0, size / 2, -size / 2)) } private def ioLocalTests[A, B, C: Eq: Show]( @@ -85,14 +83,14 @@ class IOLocalSuite extends BaseSpec { update: B, checkA: A => C, checkB: B => C - ) = name should { - "return a default value" in ticked { implicit ticker => + ) = { + ticked(s"$name return a default value") { implicit ticker => val io = localF(initial).flatMap(_._2.get) - io must completeAs(checkA(initial)) + assertCompleteAs(io, checkA(initial)) } - "set and get a value" in ticked { implicit ticker => + ticked(s"$name set and get a value") { implicit ticker => val io = localF(initial).flatMap { case (writer, reader) => for { @@ -101,10 +99,10 @@ class IOLocalSuite extends BaseSpec { } yield value } - io must completeAs(checkB(update)) + assertCompleteAs(io, checkB(update)) } - "preserve locals across async boundaries" in ticked { implicit ticker => + ticked(s"$name preserve locals across async boundaries") { implicit ticker => val io = localF(initial).flatMap { case (writer, reader) => for { @@ -114,10 +112,10 @@ class IOLocalSuite extends BaseSpec { } yield value } - io must completeAs(checkB(update)) + assertCompleteAs(io, checkB(update)) } - "copy locals to children fibers" in ticked { implicit ticker => + ticked(s"$name copy locals to children fibers") { implicit ticker => val io = localF(initial).flatMap { case (writer, reader) => for { @@ -127,10 +125,10 @@ class IOLocalSuite extends BaseSpec { } yield value } - io must completeAs(checkB(update)) + assertCompleteAs(io, checkB(update)) } - "child local manipulation is invisible to parents" in ticked { implicit ticker => + ticked(s"$name child local manipulation is invisible to parents") { implicit ticker => val io = localF(initial).flatMap { case (writer, reader) => for { @@ -140,10 +138,10 @@ class IOLocalSuite extends BaseSpec { } yield value } - io must completeAs(checkA(initial)) + assertCompleteAs(io, checkA(initial)) } - "parent local manipulation is invisible to children" in ticked { implicit ticker => + ticked(s"$name parent local manipulation is invisible to children") { implicit ticker => val io = localF(initial).flatMap { case (writer, reader) => for { @@ -155,7 +153,7 @@ class IOLocalSuite extends BaseSpec { } yield value } - io must completeAs(checkA(initial)) + assertCompleteAs(io, checkA(initial)) } } diff --git a/tests/shared/src/test/scala/cats/effect/IOMtlLocalSuite.scala b/tests/shared/src/test/scala/cats/effect/IOMtlLocalSuite.scala index 52c59ae43d..64f286666b 100644 --- a/tests/shared/src/test/scala/cats/effect/IOMtlLocalSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IOMtlLocalSuite.scala @@ -20,12 +20,11 @@ package effect import cats.mtl.Local import cats.mtl.laws.discipline._ -import org.typelevel.discipline.specs2.mutable.Discipline - import java.util.concurrent.CancellationException -class IOMtlLocalSuite extends BaseSpec with Discipline { - sequential +import munit.DisciplineSuite + +class IOMtlLocalSuite extends BaseSuite with DisciplineSuite { implicit val ticker: Ticker = Ticker() diff --git a/tests/shared/src/test/scala/cats/effect/IOParSuite.scala b/tests/shared/src/test/scala/cats/effect/IOParSuite.scala index 3663b4e9a2..0b95c82bf9 100644 --- a/tests/shared/src/test/scala/cats/effect/IOParSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IOParSuite.scala @@ -22,12 +22,9 @@ import cats.laws.discipline.{AlignTests, CommutativeApplicativeTests, ParallelTe import cats.laws.discipline.arbitrary._ import cats.syntax.all._ -import org.typelevel.discipline.specs2.mutable.Discipline +import munit.DisciplineSuite -class IOParSuite extends BaseSpec with Discipline { - - // we just need this because of the laws testing, since the prop runs can interfere with each other - sequential +class IOParSuite extends BaseSuite with DisciplineSuite { // an alley-eq implicit override def eqIOA[A: Eq](implicit ticker: Ticker): Eq[IO[A]] = { (x, y) => diff --git a/tests/shared/src/test/scala/cats/effect/IOPropSuite.scala b/tests/shared/src/test/scala/cats/effect/IOPropSuite.scala index ef57a13312..c91865e44e 100644 --- a/tests/shared/src/test/scala/cats/effect/IOPropSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IOPropSuite.scala @@ -22,102 +22,96 @@ import cats.syntax.all._ import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Gen -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ +import munit.DisciplineSuite + //We allow these tests to have a longer timeout than IOSpec as they run lots of iterations -class IOPropSuite extends BaseSpec with Discipline { +class IOPropSuite extends BaseSuite with DisciplineSuite { override def executionTimeout: FiniteDuration = 2.minutes - "io monad" should { - - "parTraverseN" should { - "give the same result as parTraverse" in realProp( - Gen.posNum[Int].flatMap(n => arbitrary[List[Int]].map(n -> _))) { - case (n, l) => - val f: Int => IO[Int] = n => IO.pure(n + 1) + realProp( + "parTraverseN should give the same result as parTraverse", + Gen.posNum[Int].flatMap(n => arbitrary[List[Int]].map(n -> _))) { + case (n, l) => + val f: Int => IO[Int] = n => IO.pure(n + 1) - l.parTraverse(f).flatMap { expected => l.parTraverseN(n)(f).mustEqual(expected) } - } + l.parTraverse(f).flatMap { expected => l.parTraverseN(n)(f).mustEqual(expected) } + } - "never exceed the maximum bound of concurrent tasks" in realProp { - for { - length <- Gen.chooseNum(0, 50) - limit <- Gen.chooseNum(1, 15, 2, 5) - } yield length -> limit - } { - case (length, limit) => - Queue.unbounded[IO, Int].flatMap { q => - val task = q.offer(1) >> IO.sleep(7.millis) >> q.offer(-1) - val testRun = List.fill(length)(task).parSequenceN(limit) - def check(acc: Int = 0): IO[Unit] = - q.tryTake.flatMap { - case None => IO.unit - case Some(n) => - val newAcc = acc + n - if (newAcc > limit) - IO.raiseError(new Exception(s"Limit of $limit exceeded, was $newAcc")) - else check(newAcc) - } - - testRun >> check().mustEqual(()) + realProp( + "parTraverseN should never exceed the maximum bound of concurrent tasks", + for { + length <- Gen.chooseNum(0, 50) + limit <- Gen.chooseNum(1, 15, 2, 5) + } yield length -> limit + ) { + case (length, limit) => + Queue.unbounded[IO, Int].flatMap { q => + val task = q.offer(1) >> IO.sleep(7.millis) >> q.offer(-1) + val testRun = List.fill(length)(task).parSequenceN(limit) + def check(acc: Int = 0): IO[Unit] = + q.tryTake.flatMap { + case None => IO.unit + case Some(n) => + val newAcc = acc + n + if (newAcc > limit) + IO.raiseError(new Exception(s"Limit of $limit exceeded, was $newAcc")) + else check(newAcc) } + + testRun >> check().mustEqual(()) } - } - - "parTraverseN_" should { - - "never exceed the maximum bound of concurrent tasks" in realProp { - for { - length <- Gen.chooseNum(0, 50) - limit <- Gen.chooseNum(1, 15, 2, 5) - } yield length -> limit - } { - case (length, limit) => - Queue.unbounded[IO, Int].flatMap { q => - val task = q.offer(1) >> IO.sleep(7.millis) >> q.offer(-1) - val testRun = List.fill(length)(task).parSequenceN_(limit) - def check(acc: Int = 0): IO[Unit] = - q.tryTake.flatMap { - case None => IO.unit - case Some(n) => - val newAcc = acc + n - if (newAcc > limit) - IO.raiseError(new Exception(s"Limit of $limit exceeded, was $newAcc")) - else check(newAcc) - } - - testRun >> check().mustEqual(()) + } + + realProp( + "parTraverseN_ should never exceed the maximum bound of concurrent tasks", + for { + length <- Gen.chooseNum(0, 50) + limit <- Gen.chooseNum(1, 15, 2, 5) + } yield length -> limit + ) { + case (length, limit) => + Queue.unbounded[IO, Int].flatMap { q => + val task = q.offer(1) >> IO.sleep(7.millis) >> q.offer(-1) + val testRun = List.fill(length)(task).parSequenceN_(limit) + def check(acc: Int = 0): IO[Unit] = + q.tryTake.flatMap { + case None => IO.unit + case Some(n) => + val newAcc = acc + n + if (newAcc > limit) + IO.raiseError(new Exception(s"Limit of $limit exceeded, was $newAcc")) + else check(newAcc) } + + testRun >> check().mustEqual(()) } - } - - "parSequenceN" should { - "give the same result as parSequence" in realProp( - Gen.posNum[Int].flatMap(n => arbitrary[List[Int]].map(n -> _))) { - case (n, l) => - l.map(IO.pure(_)).parSequence.flatMap { expected => - l.map(IO.pure(_)).parSequenceN(n).mustEqual(expected) - } + } + + realProp( + "parSequenceN should give the same result as parSequence", + Gen.posNum[Int].flatMap(n => arbitrary[List[Int]].map(n -> _))) { + case (n, l) => + l.map(IO.pure(_)).parSequence.flatMap { expected => + l.map(IO.pure(_)).parSequenceN(n).mustEqual(expected) } - } - - "parReplicateAN" should { - "give the same result as replicateA" in realProp( - for { - n <- Gen.posNum[Int] - replicas <- Gen.chooseNum(0, 50) - value <- Gen.posNum[Int] - } yield (n, replicas, value) - ) { - case (n, replicas, value) => - IO.pure(value).replicateA(replicas).flatMap { expected => - IO.pure(value).parReplicateAN(n)(replicas).mustEqual(expected) - } + } + + realProp( + "parReplicateAN should give the same result as replicateA", + for { + n <- Gen.posNum[Int] + replicas <- Gen.chooseNum(0, 50) + value <- Gen.posNum[Int] + } yield (n, replicas, value) + ) { + case (n, replicas, value) => + IO.pure(value).replicateA(replicas).flatMap { expected => + IO.pure(value).parReplicateAN(n)(replicas).mustEqual(expected) } - } } } diff --git a/tests/shared/src/test/scala/cats/effect/IOSuite.scala b/tests/shared/src/test/scala/cats/effect/IOSuite.scala index 73aa963a80..557c411522 100644 --- a/tests/shared/src/test/scala/cats/effect/IOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IOSuite.scala @@ -27,1486 +27,1551 @@ import cats.syntax.all._ import cats.~> import org.scalacheck.Prop -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.{CancellationException, ExecutionContext, Promise, TimeoutException} import scala.concurrent.duration._ -import Prop.forAll - -class IOSuite extends BaseSpec with Discipline with IOPlatformSpecification { +import munit.{DisciplineSuite, ScalaCheckSuite} - // we just need this because of the laws testing, since the prop runs can interfere with each other - sequential - - "io monad" should { +import Prop.forAll - "free monad" should { +class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with IOPlatformSuite { - "produce a pure value when run" in ticked { implicit ticker => - IO.pure(42) must completeAs(42) - } + ticked("free monad - produce a pure value when run") { implicit ticker => + assertCompleteAs(IO.pure(42), 42) + } - "map results to a new type" in ticked { implicit ticker => - IO.pure(42).map(_.toString) must completeAs("42") - } + ticked("free monad - map results to a new type") { implicit ticker => + assertCompleteAs(IO.pure(42).map(_.toString), "42") + } - "flatMap results sequencing both effects" in ticked { implicit ticker => - var i = 0 - IO.pure(42).flatMap(i2 => IO { i = i2 }) must completeAs(()) - i mustEqual 42 - } + ticked("free monad - flatMap results sequencing both effects") { implicit ticker => + var i = 0 + assertCompleteAs(IO.pure(42).flatMap(i2 => IO { i = i2 }), ()) + assertEquals(i, 42) + } - "preserve monad identity on asyncCheckAttempt immediate result" in ticked { - implicit ticker => - val fa = IO.asyncCheckAttempt[Int](_ => IO(Right(42))) - fa.flatMap(i => IO.pure(i)) must completeAs(42) - fa must completeAs(42) - } + ticked("free monad - preserve monad identity on asyncCheckAttempt immediate result") { + implicit ticker => + val fa = IO.asyncCheckAttempt[Int](_ => IO(Right(42))) + assertCompleteAs(fa.flatMap(i => IO.pure(i)), 42) + assertCompleteAs(fa, 42) + } - "preserve monad identity on asyncCheckAttempt suspended result" in ticked { - implicit ticker => - val fa = IO.asyncCheckAttempt[Int](cb => IO(cb(Right(42))).as(Left(None))) - fa.flatMap(i => IO.pure(i)) must completeAs(42) - fa must completeAs(42) - } + ticked("free monad - preserve monad identity on asyncCheckAttempt suspended result") { + implicit ticker => + val fa = IO.asyncCheckAttempt[Int](cb => IO(cb(Right(42))).as(Left(None))) + assertCompleteAs(fa.flatMap(i => IO.pure(i)), 42) + assertCompleteAs(fa, 42) + } - "preserve monad identity on async" in ticked { implicit ticker => - val fa = IO.async[Int](cb => IO(cb(Right(42))).as(None)) - fa.flatMap(i => IO.pure(i)) must completeAs(42) - fa must completeAs(42) - } - } + ticked("free monad - preserve monad identity on async") { implicit ticker => + val fa = IO.async[Int](cb => IO(cb(Right(42))).as(None)) + assertCompleteAs(fa.flatMap(i => IO.pure(i)), 42) + assertCompleteAs(fa, 42) + } - "error handling" should { - "capture errors in suspensions" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO(throw TestException) must failAs(TestException) - } + ticked("error handling - capture errors in suspensions") { implicit ticker => + case object TestException extends RuntimeException + assertFailAs(IO(throw TestException), TestException) + } - "resume error continuation within asyncCheckAttempt" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.asyncCheckAttempt[Unit](k => IO(k(Left(TestException))).as(Left(None))) must failAs( - TestException) - } + ticked("error handling - resume error continuation within asyncCheckAttempt") { + implicit ticker => + case object TestException extends RuntimeException + assertFailAs( + IO.asyncCheckAttempt[Unit](k => IO(k(Left(TestException))).as(Left(None))), + TestException) + } - "resume error continuation within async" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.async[Unit](k => IO(k(Left(TestException))).as(None)) must failAs(TestException) - } + ticked("error handling - resume error continuation within async") { implicit ticker => + case object TestException extends RuntimeException + assertFailAs(IO.async[Unit](k => IO(k(Left(TestException))).as(None)), TestException) + } - "raiseError propagates out" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.raiseError(TestException).void.flatMap(_ => IO.pure(())) must failAs(TestException) - } + ticked("error handling - raiseError propagates out") { implicit ticker => + case object TestException extends RuntimeException + assertFailAs(IO.raiseError(TestException).void.flatMap(_ => IO.pure(())), TestException) + } - "errors can be handled" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.raiseError[Unit](TestException).attempt must completeAs(Left(TestException)) - } + ticked("error handling - errors can be handled") { implicit ticker => + case object TestException extends RuntimeException + assertCompleteAs(IO.raiseError[Unit](TestException).attempt, Left(TestException)) + } - "orElse must return other if previous IO Fails" in ticked { implicit ticker => - case object TestException extends RuntimeException - (IO.raiseError[Int](TestException) orElse IO.pure(42)) must completeAs(42) - } + ticked("error handling - orElse must return other if previous IO Fails") { implicit ticker => + case object TestException extends RuntimeException + assertCompleteAs(IO.raiseError[Int](TestException) orElse IO.pure(42), 42) + } - "Return current IO if successful" in ticked { implicit ticker => - case object TestException extends RuntimeException - (IO.pure(42) orElse IO.raiseError[Int](TestException)) must completeAs(42) - } + ticked("error handling - Return current IO if successful") { implicit ticker => + case object TestException extends RuntimeException + assertCompleteAs(IO.pure(42) orElse IO.raiseError[Int](TestException), 42) + } - "adaptError is a no-op for a successful effect" in ticked { implicit ticker => - IO(42).adaptError { case x => x } must completeAs(42) - } + ticked("error handling - adaptError is a no-op for a successful effect") { implicit ticker => + assertCompleteAs(IO(42).adaptError { case x => x }, 42) + } - "adaptError is a no-op for a non-matching error" in ticked { implicit ticker => - case object TestException1 extends RuntimeException - case object TestException2 extends RuntimeException - IO.raiseError[Unit](TestException1).adaptError { - case TestException2 => TestException2 - } must failAs(TestException1) - } + ticked("error handling - adaptError is a no-op for a non-matching error") { implicit ticker => + case object TestException1 extends RuntimeException + case object TestException2 extends RuntimeException + assertFailAs( + IO.raiseError[Unit](TestException1).adaptError { case TestException2 => TestException2 }, + TestException1) + } - "adaptError transforms the error in a failed effect" in ticked { implicit ticker => - case object TestException1 extends RuntimeException - case object TestException2 extends RuntimeException + ticked("error handling - adaptError transforms the error in a failed effect") { + implicit ticker => + case object TestException1 extends RuntimeException + case object TestException2 extends RuntimeException + assertFailAs( IO.raiseError[Unit](TestException1).adaptError { case TestException1 => TestException2 - } must failAs(TestException2) - } + }, + TestException2) + } - "attempt is redeem with Left(_) for recover and Right(_) for map" in ticked { - implicit ticker => - forAll { (io: IO[Int]) => io.attempt eqv io.redeem(Left(_), Right(_)) } - } + ticked("error handling - attempt is redeem with Left(_) for recover and Right(_) for map") { + implicit ticker => forAll { (io: IO[Int]) => io.attempt eqv io.redeem(Left(_), Right(_)) } + } - "attempt is flattened redeemWith" in ticked { implicit ticker => - forAll { (io: IO[Int], recover: Throwable => IO[String], bind: Int => IO[String]) => - io.attempt.flatMap(_.fold(recover, bind)) eqv io.redeemWith(recover, bind) - } - } + ticked("error handling - attempt is flattened redeemWith") { implicit ticker => + forAll { (io: IO[Int], recover: Throwable => IO[String], bind: Int => IO[String]) => + io.attempt.flatMap(_.fold(recover, bind)) eqv io.redeemWith(recover, bind) + } + } - "attemptTap(f) is an alias for attempt.flatTap(f).rethrow" in ticked { implicit ticker => - forAll { (io: IO[Int], f: Either[Throwable, Int] => IO[Int]) => - io.attemptTap(f) eqv io.attempt.flatTap(f).rethrow - } + ticked("error handling - attemptTap(f) is an alias for attempt.flatTap(f).rethrow") { + implicit ticker => + forAll { (io: IO[Int], f: Either[Throwable, Int] => IO[Int]) => + io.attemptTap(f) eqv io.attempt.flatTap(f).rethrow } + } - "rethrow is inverse of attempt" in ticked { implicit ticker => - forAll { (io: IO[Int]) => io.attempt.rethrow eqv io } - } + ticked("error handling - rethrow is inverse of attempt") { implicit ticker => + forAll { (io: IO[Int]) => io.attempt.rethrow eqv io } + } - "redeem is flattened redeemWith" in ticked { implicit ticker => - forAll { (io: IO[Int], recover: Throwable => IO[String], bind: Int => IO[String]) => - io.redeem(recover, bind).flatten eqv io.redeemWith(recover, bind) - } - } + ticked("error handling - redeem is flattened redeemWith") { implicit ticker => + forAll { (io: IO[Int], recover: Throwable => IO[String], bind: Int => IO[String]) => + io.redeem(recover, bind).flatten eqv io.redeemWith(recover, bind) + } + } - "redeem subsumes handleError" in ticked { implicit ticker => - forAll { (io: IO[Int], recover: Throwable => Int) => - // we have to workaround functor law weirdness here... again... sigh... because of self-cancelation - io.redeem(recover, identity).flatMap(IO.pure(_)) eqv io.handleError(recover) - } - } + ticked("error handling - redeem subsumes handleError") { implicit ticker => + forAll { (io: IO[Int], recover: Throwable => Int) => + // we have to workaround functor law weirdness here... again... sigh... because of self-cancelation + io.redeem(recover, identity).flatMap(IO.pure(_)) eqv io.handleError(recover) + } + } - "redeemWith subsumes handleErrorWith" in ticked { implicit ticker => - forAll { (io: IO[Int], recover: Throwable => IO[Int]) => - io.redeemWith(recover, IO.pure) eqv io.handleErrorWith(recover) - } - } + ticked("error handling - redeemWith subsumes handleErrorWith") { implicit ticker => + forAll { (io: IO[Int], recover: Throwable => IO[Int]) => + io.redeemWith(recover, IO.pure) eqv io.handleErrorWith(recover) + } + } - "redeem correctly recovers from errors" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.raiseError[Unit](TestException).redeem(_ => 42, _ => 43) must completeAs(42) - } + ticked("error handling - redeem correctly recovers from errors") { implicit ticker => + case object TestException extends RuntimeException + assertCompleteAs(IO.raiseError[Unit](TestException).redeem(_ => 42, _ => 43), 42) + } - "redeem maps successful results" in ticked { implicit ticker => - IO.unit.redeem(_ => 41, _ => 42) must completeAs(42) - } + ticked("error handling - redeem maps successful results") { implicit ticker => + assertCompleteAs(IO.unit.redeem(_ => 41, _ => 42), 42) + } - "redeem catches exceptions thrown in recovery function" in ticked { implicit ticker => - case object TestException extends RuntimeException - case object ThrownException extends RuntimeException - IO.raiseError[Unit](TestException) - .redeem(_ => throw ThrownException, _ => 42) - .attempt must completeAs(Left(ThrownException)) - } + ticked("error handling - redeem catches exceptions thrown in recovery function") { + implicit ticker => + case object TestException extends RuntimeException + case object ThrownException extends RuntimeException + assertCompleteAs( + IO.raiseError[Unit](TestException).redeem(_ => throw ThrownException, _ => 42).attempt, + Left(ThrownException)) + } - "redeem catches exceptions thrown in map function" in ticked { implicit ticker => - case object ThrownException extends RuntimeException - IO.unit.redeem(_ => 41, _ => throw ThrownException).attempt must completeAs( - Left(ThrownException)) - } + ticked("error handling - redeem catches exceptions thrown in map function") { + implicit ticker => + case object ThrownException extends RuntimeException + assertCompleteAs( + IO.unit.redeem(_ => 41, _ => throw ThrownException).attempt, + Left(ThrownException)) + } - "redeemWith correctly recovers from errors" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.raiseError[Unit](TestException) - .redeemWith(_ => IO.pure(42), _ => IO.pure(43)) must completeAs(42) - } + ticked("error handling - redeemWith correctly recovers from errors") { implicit ticker => + case object TestException extends RuntimeException + assertCompleteAs( + IO.raiseError[Unit](TestException).redeemWith(_ => IO.pure(42), _ => IO.pure(43)), + 42) + } - "recover correctly recovers from errors" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.raiseError[Int](TestException).recover { case TestException => 42 } must completeAs( - 42) - } + ticked("error handling - recover correctly recovers from errors") { implicit ticker => + case object TestException extends RuntimeException + assertCompleteAs(IO.raiseError[Int](TestException).recover { case TestException => 42 }, 42) + } - "recoverWith correctly recovers from errors" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.raiseError[Int](TestException).recoverWith { - case TestException => IO.pure(42) - } must completeAs(42) - } + ticked("error handling - recoverWith correctly recovers from errors") { implicit ticker => + case object TestException extends RuntimeException + assertCompleteAs( + IO.raiseError[Int](TestException).recoverWith { case TestException => IO.pure(42) }, + 42) + } - "recoverWith does not recover from unmatched errors" in ticked { implicit ticker => - case object UnmatchedException extends RuntimeException - case object ThrownException extends RuntimeException + ticked("error handling - recoverWith does not recover from unmatched errors") { + implicit ticker => + case object UnmatchedException extends RuntimeException + case object ThrownException extends RuntimeException + assertCompleteAs( IO.raiseError[Int](ThrownException) .recoverWith { case UnmatchedException => IO.pure(42) } - .attempt must completeAs(Left(ThrownException)) - } + .attempt, + Left(ThrownException)) + } - "recover does not recover from unmatched errors" in ticked { implicit ticker => - case object UnmatchedException extends RuntimeException - case object ThrownException extends RuntimeException - IO.raiseError[Int](ThrownException) - .recover { case UnmatchedException => 42 } - .attempt must completeAs(Left(ThrownException)) - } + ticked("error handling - recover does not recover from unmatched errors") { implicit ticker => + case object UnmatchedException extends RuntimeException + case object ThrownException extends RuntimeException + assertCompleteAs( + IO.raiseError[Int](ThrownException).recover { case UnmatchedException => 42 }.attempt, + Left(ThrownException)) + } - "redeemWith binds successful results" in ticked { implicit ticker => - IO.unit.redeemWith(_ => IO.pure(41), _ => IO.pure(42)) must completeAs(42) - } + ticked("error handling - redeemWith binds successful results") { implicit ticker => + assertCompleteAs(IO.unit.redeemWith(_ => IO.pure(41), _ => IO.pure(42)), 42) + } - "redeemWith catches exceptions throw in recovery function" in ticked { implicit ticker => - case object TestException extends RuntimeException - case object ThrownException extends RuntimeException + ticked("error handling - redeemWith catches exceptions throw in recovery function") { + implicit ticker => + case object TestException extends RuntimeException + case object ThrownException extends RuntimeException + assertCompleteAs( IO.raiseError[Unit](TestException) .redeemWith(_ => throw ThrownException, _ => IO.pure(42)) - .attempt must completeAs(Left(ThrownException)) - } + .attempt, + Left(ThrownException)) + } - "redeemWith catches exceptions thrown in bind function" in ticked { implicit ticker => - case object ThrownException extends RuntimeException - IO.unit.redeem(_ => IO.pure(41), _ => throw ThrownException).attempt must completeAs( - Left(ThrownException)) - } + ticked("error handling - redeemWith catches exceptions thrown in bind function") { + implicit ticker => + case object ThrownException extends RuntimeException + assertCompleteAs( + IO.unit.redeem(_ => IO.pure(41), _ => throw ThrownException).attempt, + Left(ThrownException)) + } - "catch exceptions thrown in map functions" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.unit.map(_ => (throw TestException): Unit).attempt must completeAs( - Left(TestException)) - } + ticked("error handling - catch exceptions thrown in map functions") { implicit ticker => + case object TestException extends RuntimeException + assertCompleteAs(IO.unit.map(_ => (throw TestException): Unit).attempt, Left(TestException)) + } - "catch exceptions thrown in flatMap functions" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.unit.flatMap(_ => (throw TestException): IO[Unit]).attempt must completeAs( - Left(TestException)) - } + ticked("error handling - catch exceptions thrown in flatMap functions") { implicit ticker => + case object TestException extends RuntimeException + assertCompleteAs( + IO.unit.flatMap(_ => (throw TestException): IO[Unit]).attempt, + Left(TestException)) + } - "catch exceptions thrown in handleErrorWith functions" in ticked { implicit ticker => - case object TestException extends RuntimeException - case object WrongException extends RuntimeException + ticked("error handling - catch exceptions thrown in handleErrorWith functions") { + implicit ticker => + case object TestException extends RuntimeException + case object WrongException extends RuntimeException + assertCompleteAs( IO.raiseError[Unit](WrongException) .handleErrorWith(_ => (throw TestException): IO[Unit]) - .attempt must completeAs(Left(TestException)) - } + .attempt, + Left(TestException)) + } - "raise first bracket release exception if use effect succeeded" in ticked( - implicit ticker => { - case object TestException extends RuntimeException - case object WrongException extends RuntimeException - val io = - IO.unit - .bracket { _ => - IO.unit.bracket(_ => IO.unit)(_ => IO.raiseError(TestException)) - }(_ => IO.raiseError(WrongException)) - io.attempt must completeAs(Left(TestException)) - }) - - "report unhandled failure to the execution context" in ticked { implicit ticker => - case object TestException extends RuntimeException + ticked("error handling - raise first bracket release exception if use effect succeeded") { + implicit ticker => + case object TestException extends RuntimeException + case object WrongException extends RuntimeException + val io = + IO.unit + .bracket { _ => IO.unit.bracket(_ => IO.unit)(_ => IO.raiseError(TestException)) }( + _ => IO.raiseError(WrongException)) + assertCompleteAs(io.attempt, Left(TestException)) + } - val action = IO.executionContext flatMap { ec => - IO defer { - var ts: List[Throwable] = Nil + ticked("error handling - report unhandled failure to the execution context") { + implicit ticker => + case object TestException extends RuntimeException - val ec2 = new ExecutionContext { - def reportFailure(t: Throwable) = ts ::= t - def execute(r: Runnable) = ec.execute(r) - } + val action = IO.executionContext flatMap { ec => + IO defer { + var ts: List[Throwable] = Nil - IO.raiseError(TestException).start.evalOn(ec2) *> IO.sleep(10.millis) *> IO(ts) + val ec2 = new ExecutionContext { + def reportFailure(t: Throwable) = ts ::= t + def execute(r: Runnable) = ec.execute(r) } - } - action must completeAs(List(TestException)) + IO.raiseError(TestException).start.evalOn(ec2) *> IO.sleep(10.millis) *> IO(ts) + } } - "not report observed failures to the execution context" in ticked { implicit ticker => - case object TestException extends RuntimeException + assertCompleteAs(action, List(TestException)) + } - val action = IO.executionContext flatMap { ec => - IO defer { - var ts: List[Throwable] = Nil + ticked("error handling - not report observed failures to the execution context") { + implicit ticker => + case object TestException extends RuntimeException - val ec2 = new ExecutionContext { - def reportFailure(t: Throwable) = ts ::= t - def execute(r: Runnable) = ec.execute(r) - } + val action = IO.executionContext flatMap { ec => + IO defer { + var ts: List[Throwable] = Nil - for { - f <- (IO.sleep(10.millis) *> IO.raiseError(TestException)).start.evalOn(ec2) - _ <- f.join - back <- IO(ts) - } yield back + val ec2 = new ExecutionContext { + def reportFailure(t: Throwable) = ts ::= t + def execute(r: Runnable) = ec.execute(r) } - } - action must completeAs(Nil) + for { + f <- (IO.sleep(10.millis) *> IO.raiseError(TestException)).start.evalOn(ec2) + _ <- f.join + back <- IO(ts) + } yield back + } } - // https://github.com/typelevel/cats-effect/issues/2962 - "not report failures in timeout" in ticked { implicit ticker => - case object TestException extends RuntimeException + assertCompleteAs(action, Nil) + } - val action = IO.executionContext flatMap { ec => - IO defer { - var ts: List[Throwable] = Nil + // https://github.com/typelevel/cats-effect/issues/2962 + ticked("error handling - not report failures in timeout") { implicit ticker => + case object TestException extends RuntimeException - val ec2 = new ExecutionContext { - def reportFailure(t: Throwable) = ts ::= t - def execute(r: Runnable) = ec.execute(r) - } + val action = IO.executionContext flatMap { ec => + IO defer { + var ts: List[Throwable] = Nil - for { - f <- (IO.sleep(10.millis) *> IO - .raiseError(TestException) - .timeoutTo(1.minute, IO.pure(42))).start.evalOn(ec2) - _ <- f.join - back <- IO(ts) - } yield back - } + val ec2 = new ExecutionContext { + def reportFailure(t: Throwable) = ts ::= t + def execute(r: Runnable) = ec.execute(r) } - action must completeAs(Nil) + for { + f <- (IO.sleep(10.millis) *> IO + .raiseError(TestException) + .timeoutTo(1.minute, IO.pure(42))).start.evalOn(ec2) + _ <- f.join + back <- IO(ts) + } yield back } + } - "report errors raised during unsafeRunAndForget" in ticked { implicit ticker => - import cats.effect.unsafe.IORuntime - import scala.concurrent.Promise + assertCompleteAs(action, Nil) + } - def ec2(ec1: ExecutionContext, er: Promise[Boolean]) = new ExecutionContext { - def reportFailure(t: Throwable) = er.success(true) - def execute(r: Runnable) = ec1.execute(r) - } + ticked("error handling - report errors raised during unsafeRunAndForget") { implicit ticker => + import cats.effect.unsafe.IORuntime + import scala.concurrent.Promise - val test = for { - ec <- IO.executionContext - errorReporter <- IO(Promise[Boolean]()) - customRuntime = IORuntime - .builder() - .setCompute(ec2(ec, errorReporter), () => ()) - .build() - _ <- IO(IO.raiseError(new RuntimeException).unsafeRunAndForget()(customRuntime)) - reported <- IO.fromFuture(IO(errorReporter.future)) - } yield reported - test must completeAs(true) - } + def ec2(ec1: ExecutionContext, er: Promise[Boolean]) = new ExecutionContext { + def reportFailure(t: Throwable) = er.success(true) + def execute(r: Runnable) = ec1.execute(r) } - "suspension of side effects" should { + val test = for { + ec <- IO.executionContext + errorReporter <- IO(Promise[Boolean]()) + customRuntime = IORuntime.builder().setCompute(ec2(ec, errorReporter), () => ()).build() + _ <- IO(IO.raiseError(new RuntimeException).unsafeRunAndForget()(customRuntime)) + reported <- IO.fromFuture(IO(errorReporter.future)) + } yield reported + assertCompleteAs(test, true) + } - "suspend a side-effect without memoizing" in ticked { implicit ticker => - var i = 42 + // + // suspension of side effects + // - val ioa = IO { - i += 1 - i - } + ticked("suspension of side effects - suspend a side-effect without memoizing") { + implicit ticker => + var i = 42 - ioa must completeAs(43) - ioa must completeAs(44) + val ioa = IO { + i += 1 + i } - "result in a null if lifting a pure null value" in ticked { implicit ticker => - // convoluted in order to avoid scalac warnings - IO.pure(null).map(_.asInstanceOf[Any]).map(_ == null) must completeAs(true) - } + assertCompleteAs(ioa, 43) + assertCompleteAs(ioa, 44) + } - "result in a null if delaying a null value" in ticked { implicit ticker => - IO(null).map(_.asInstanceOf[Any]).map(_ == null) must completeAs(true) - IO.delay(null).map(_.asInstanceOf[Any]).map(_ == null) must completeAs(true) - } + ticked("result in a null if lifting a pure null value") { implicit ticker => + // convoluted in order to avoid scalac warnings + assertCompleteAs(IO.pure(null).map(_.asInstanceOf[Any]).map(_ == null), true) + } - "result in an NPE if deferring a null IO" in ticked { implicit ticker => - IO.defer(null) - .attempt - .map(_.left.toOption.get.isInstanceOf[NullPointerException]) must completeAs(true) - } - } + ticked("result in a null if delaying a null value") { implicit ticker => + assertCompleteAs(IO(null).map(_.asInstanceOf[Any]).map(_ == null), true) + assertCompleteAs(IO.delay(null).map(_.asInstanceOf[Any]).map(_ == null), true) + } - "fibers" should { + ticked("result in an NPE if deferring a null IO") { implicit ticker => + assertCompleteAs( + IO.defer(null).attempt.map(_.left.toOption.get.isInstanceOf[NullPointerException]), + true) + } - "start and join on a successful fiber" in ticked { implicit ticker => - IO.pure(42).map(_ + 1).start.flatMap(_.join) must completeAs( - Outcome.succeeded[IO, Throwable, Int](IO.pure(43))) - } + // + // Fibers + // - "start and join on a failed fiber" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.raiseError[Unit](TestException).start.flatMap(_.join) must completeAs( - Outcome.errored[IO, Throwable, Unit](TestException)) - } + ticked("fibers - start and join on a successful fiber") { implicit ticker => + assertCompleteAs( + IO.pure(42).map(_ + 1).start.flatMap(_.join), + Outcome.succeeded[IO, Throwable, Int](IO.pure(43))) + } - "start and ignore a non-terminating fiber" in ticked { implicit ticker => - IO.never.start.as(42) must completeAs(42) - } + ticked("fibers - start and join on a failed fiber") { implicit ticker => + case object TestException extends RuntimeException + assertCompleteAs( + IO.raiseError[Unit](TestException).start.flatMap(_.join), + Outcome.errored[IO, Throwable, Unit](TestException)) + } - "start a fiber then continue with its results" in ticked { implicit ticker => - IO.pure(42).start.flatMap(_.join).flatMap { oc => - oc.fold(IO.pure(0), _ => IO.pure(-1), ioa => ioa) - } must completeAs(42) - } + ticked("fibers - start and ignore a non-terminating fiber") { implicit ticker => + assertCompleteAs(IO.never.start.as(42), 42) + } - "joinWithNever on a canceled fiber" in ticked { implicit ticker => - (for { - fib <- IO.sleep(2.seconds).start - _ <- fib.cancel - _ <- fib.joinWithNever - } yield ()) must nonTerminate - } + ticked("fibers - start a fiber then continue with its results") { implicit ticker => + assertCompleteAs( + IO.pure(42).start.flatMap(_.join).flatMap { oc => + oc.fold(IO.pure(0), _ => IO.pure(-1), ioa => ioa) + }, + 42) + } - "joinWithNever on a successful fiber" in ticked { implicit ticker => - (for { - fib <- IO.pure(1).start - res <- fib.joinWithNever - } yield res) must completeAs(1) - } + ticked("fibers - joinWithNever on a canceled fiber") { implicit ticker => + assertNonTerminate(for { + fib <- IO.sleep(2.seconds).start + _ <- fib.cancel + _ <- fib.joinWithNever + } yield ()) + } - "joinWithNever on a failed fiber" in ticked { implicit ticker => - case object TestException extends RuntimeException - (for { - fib <- IO.raiseError[Unit](TestException).start - res <- fib.joinWithNever - } yield res) must failAs(TestException) - } + ticked("fibers - joinWithNever on a successful fiber") { implicit ticker => + assertCompleteAs( + for { + fib <- IO.pure(1).start + res <- fib.joinWithNever + } yield res, + 1) + } - "preserve contexts through start" in ticked { implicit ticker => - val ec = ticker.ctx.derive() + ticked("fibers - joinWithNever on a failed fiber") { implicit ticker => + case object TestException extends RuntimeException + assertFailAs( + for { + fib <- IO.raiseError[Unit](TestException).start + res <- fib.joinWithNever + } yield res, + TestException) + } - val ioa = for { - f <- IO.executionContext.start.evalOn(ec) - _ <- IO(ticker.ctx.tick()) - oc <- f.join - } yield oc + ticked("fibers - preserve contexts through start") { implicit ticker => + val ec = ticker.ctx.derive() - ioa must completeAs(Outcome.succeeded[IO, Throwable, ExecutionContext](IO.pure(ec))) - } + val ioa = for { + f <- IO.executionContext.start.evalOn(ec) + _ <- IO(ticker.ctx.tick()) + oc <- f.join + } yield oc - "produce Canceled from start of canceled" in ticked { implicit ticker => - IO.canceled.start.flatMap(_.join) must completeAs(Outcome.canceled[IO, Throwable, Unit]) - } + assertCompleteAs(ioa, Outcome.succeeded[IO, Throwable, ExecutionContext](IO.pure(ec))) + } - "cancel an already canceled fiber" in ticked { implicit ticker => - val test = for { - f <- IO.canceled.start - _ <- IO(ticker.ctx.tick()) - _ <- f.cancel - } yield () + ticked("fibers - produce Canceled from start of canceled") { implicit ticker => + assertCompleteAs(IO.canceled.start.flatMap(_.join), Outcome.canceled[IO, Throwable, Unit]) + } - test must completeAs(()) - } + ticked("fibers - cancel an already canceled fiber") { implicit ticker => + val test = for { + f <- IO.canceled.start + _ <- IO(ticker.ctx.tick()) + _ <- f.cancel + } yield () - } + assertCompleteAs(test, ()) + } - "asyncCheckAttempt" should { + // + // asyncCheckAttempt + // - "resume value continuation within asyncCheckAttempt with immediate result" in ticked { - implicit ticker => IO.asyncCheckAttempt[Int](_ => IO(Right(42))) must completeAs(42) - } + ticked( + "asyncCheckAttempt - resume value continuation within asyncCheckAttempt with immediate result") { + implicit ticker => assertCompleteAs(IO.asyncCheckAttempt[Int](_ => IO(Right(42))), 42) + } - "resume value continuation within asyncCheckAttempt with suspended result" in ticked { - implicit ticker => - IO.asyncCheckAttempt[Int](k => IO(k(Right(42))).as(Left(None))) must completeAs(42) - } + ticked( + "asyncCheckAttempt - resume value continuation within asyncCheckAttempt with suspended result") { + implicit ticker => + assertCompleteAs(IO.asyncCheckAttempt[Int](k => IO(k(Right(42))).as(Left(None))), 42) + } - "continue from the results of an asyncCheckAttempt immediate result produced prior to registration" in ticked { - implicit ticker => - val fa = IO.asyncCheckAttempt[Int](_ => IO(Right(42))).map(_ + 2) - fa must completeAs(44) - } + ticked( + "asyncCheckAttempt - continue from the results of an asyncCheckAttempt immediate result produced prior to registration") { + implicit ticker => + val fa = IO.asyncCheckAttempt[Int](_ => IO(Right(42))).map(_ + 2) + assertCompleteAs(fa, 44) + } - "continue from the results of an asyncCheckAttempt suspended result produced prior to registration" in ticked { - implicit ticker => - val fa = IO.asyncCheckAttempt[Int](cb => IO(cb(Right(42))).as(Left(None))).map(_ + 2) - fa must completeAs(44) - } + ticked( + "asyncCheckAttempt - continue from the results of an asyncCheckAttempt suspended result produced prior to registration") { + implicit ticker => + val fa = IO.asyncCheckAttempt[Int](cb => IO(cb(Right(42))).as(Left(None))).map(_ + 2) + assertCompleteAs(fa, 44) + } // format: off - "produce a failure when the registration raises an error after result" in ticked { implicit ticker => + ticked("asyncCheckAttempt - produce a failure when the registration raises an error after result") { implicit ticker => case object TestException extends RuntimeException - IO.asyncCheckAttempt[Int](_ => IO(Right(42)) + assertFailAs(IO.asyncCheckAttempt[Int](_ => IO(Right(42)) .flatMap(_ => IO.raiseError(TestException))) - .void must failAs(TestException) + .void, TestException) } // format: on // format: off - "produce a failure when the registration raises an error after callback" in ticked { implicit ticker => + ticked("asyncCheckAttempt - produce a failure when the registration raises an error after callback") { implicit ticker => case object TestException extends RuntimeException val fa = IO.asyncCheckAttempt[Int](cb => IO(cb(Right(42))) .flatMap(_ => IO.raiseError(TestException))) .void - fa must failAs(TestException) + assertFailAs(fa, TestException) } // format: on - "ignore asyncCheckAttempt callback" in ticked { implicit ticker => - case object TestException extends RuntimeException + ticked("asyncCheckAttempt - ignore asyncCheckAttempt callback") { implicit ticker => + case object TestException extends RuntimeException - var cb: Either[Throwable, Int] => Unit = null + var cb: Either[Throwable, Int] => Unit = null - val asyncCheckAttempt = IO.asyncCheckAttempt[Int] { cb0 => - IO { cb = cb0 } *> IO.pure(Right(42)) - } + val asyncCheckAttempt = IO.asyncCheckAttempt[Int] { cb0 => + IO { cb = cb0 } *> IO.pure(Right(42)) + } - val test = for { - fiber <- asyncCheckAttempt.start - _ <- IO(ticker.ctx.tick()) - _ <- IO(cb(Right(43))) - _ <- IO(ticker.ctx.tick()) - _ <- IO(cb(Left(TestException))) - _ <- IO(ticker.ctx.tick()) - value <- fiber.joinWithNever - } yield value - - test must completeAs(42) - } + val test = for { + fiber <- asyncCheckAttempt.start + _ <- IO(ticker.ctx.tick()) + _ <- IO(cb(Right(43))) + _ <- IO(ticker.ctx.tick()) + _ <- IO(cb(Left(TestException))) + _ <- IO(ticker.ctx.tick()) + value <- fiber.joinWithNever + } yield value + + assertCompleteAs(test, 42) + } - "ignore asyncCheckAttempt callback real" in real { - case object TestException extends RuntimeException + real("asyncCheckAttempt - ignore asyncCheckAttempt callback real") { + case object TestException extends RuntimeException + + var cb: Either[Throwable, Int] => Unit = null + + val test = for { + latch1 <- Deferred[IO, Unit] + latch2 <- Deferred[IO, Unit] + fiber <- + IO.asyncCheckAttempt[Int] { cb0 => + IO { cb = cb0 } *> latch1.complete(()) *> latch2.get *> IO.pure(Right(42)) + }.start + _ <- latch1.get + _ <- IO(cb(Right(43))) + _ <- IO(cb(Left(TestException))) + _ <- latch2.complete(()) + value <- fiber.joinWithNever + } yield value + + test.attempt.flatMap { n => IO(assertEquals(n, Right(42))) } + } - var cb: Either[Throwable, Int] => Unit = null - - val test = for { - latch1 <- Deferred[IO, Unit] - latch2 <- Deferred[IO, Unit] - fiber <- - IO.asyncCheckAttempt[Int] { cb0 => - IO { cb = cb0 } *> latch1.complete(()) *> latch2.get *> IO.pure(Right(42)) - }.start - _ <- latch1.get - _ <- IO(cb(Right(43))) - _ <- IO(cb(Left(TestException))) - _ <- latch2.complete(()) - value <- fiber.joinWithNever - } yield value - - test.attempt.flatMap { n => IO(n mustEqual Right(42)) } - } + ticked("asyncCheckAttempt - repeated asyncCheckAttempt callback") { implicit ticker => + case object TestException extends RuntimeException - "repeated asyncCheckAttempt callback" in ticked { implicit ticker => - case object TestException extends RuntimeException + var cb: Either[Throwable, Int] => Unit = null - var cb: Either[Throwable, Int] => Unit = null + val asyncCheckAttempt = IO.asyncCheckAttempt[Int] { cb0 => + IO { cb = cb0 } *> IO.pure(Left(None)) + } - val asyncCheckAttempt = IO.asyncCheckAttempt[Int] { cb0 => - IO { cb = cb0 } *> IO.pure(Left(None)) - } + val test = for { + fiber <- asyncCheckAttempt.start + _ <- IO(ticker.ctx.tick()) + _ <- IO(cb(Right(42))) + _ <- IO(ticker.ctx.tick()) + _ <- IO(cb(Right(43))) + _ <- IO(ticker.ctx.tick()) + _ <- IO(cb(Left(TestException))) + _ <- IO(ticker.ctx.tick()) + value <- fiber.joinWithNever + } yield value + + assertCompleteAs(test, 42) + } - val test = for { - fiber <- asyncCheckAttempt.start - _ <- IO(ticker.ctx.tick()) - _ <- IO(cb(Right(42))) - _ <- IO(ticker.ctx.tick()) - _ <- IO(cb(Right(43))) - _ <- IO(ticker.ctx.tick()) - _ <- IO(cb(Left(TestException))) - _ <- IO(ticker.ctx.tick()) - value <- fiber.joinWithNever - } yield value - - test must completeAs(42) - } + real("asyncCheckAttempt - repeated asyncCheckAttempt callback real") { + case object TestException extends RuntimeException + + var cb: Either[Throwable, Int] => Unit = null + + val test = for { + latch1 <- Deferred[IO, Unit] + latch2 <- Deferred[IO, Unit] + fiber <- + IO.asyncCheckAttempt[Int] { cb0 => + IO { cb = cb0 } *> latch1.complete(()) *> latch2.get *> IO.pure(Left(None)) + }.start + _ <- latch1.get + _ <- IO(cb(Right(42))) + _ <- IO(cb(Right(43))) + _ <- IO(cb(Left(TestException))) + _ <- latch2.complete(()) + value <- fiber.joinWithNever + } yield value + + test.attempt.flatMap { n => IO(assertEquals(n, Right(42))) } + } - "repeated asyncCheckAttempt callback real" in real { - case object TestException extends RuntimeException + ticked("asyncCheckAttempt - allow for misordered nesting") { implicit ticker => + var outerR = 0 + var innerR = 0 - var cb: Either[Throwable, Int] => Unit = null - - val test = for { - latch1 <- Deferred[IO, Unit] - latch2 <- Deferred[IO, Unit] - fiber <- - IO.asyncCheckAttempt[Int] { cb0 => - IO { cb = cb0 } *> latch1.complete(()) *> latch2.get *> IO.pure(Left(None)) - }.start - _ <- latch1.get - _ <- IO(cb(Right(42))) - _ <- IO(cb(Right(43))) - _ <- IO(cb(Left(TestException))) - _ <- latch2.complete(()) - value <- fiber.joinWithNever - } yield value - - test.attempt.flatMap { n => IO(n mustEqual Right(42)) } + val outer = IO.asyncCheckAttempt[Int] { cb1 => + val inner = IO.asyncCheckAttempt[Int] { cb2 => + IO(cb1(Right(1))) *> + IO.executionContext.flatMap(ec => IO(ec.execute(() => cb2(Right(2))))).as(Left(None)) } - "allow for misordered nesting" in ticked { implicit ticker => - var outerR = 0 - var innerR = 0 - - val outer = IO.asyncCheckAttempt[Int] { cb1 => - val inner = IO.asyncCheckAttempt[Int] { cb2 => - IO(cb1(Right(1))) *> - IO.executionContext - .flatMap(ec => IO(ec.execute(() => cb2(Right(2))))) - .as(Left(None)) - } - - inner.flatMap(i => IO { innerR = i }).as(Left(None)) - } + inner.flatMap(i => IO { innerR = i }).as(Left(None)) + } - val test = outer.flatMap(i => IO { outerR = i }) + val test = outer.flatMap(i => IO { outerR = i }) - test must completeAs(()) - outerR mustEqual 1 - innerR mustEqual 2 - } + assertCompleteAs(test, ()) + assertEquals(outerR, 1) + assertEquals(innerR, 2) + } - "be uncancelable if None finalizer" in ticked { implicit ticker => - val t = IO.asyncCheckAttempt[Int] { _ => IO.pure(Left(None)) } - val test = for { - fib <- t.start - _ <- IO(ticker.ctx.tick()) - _ <- fib.cancel - } yield () + ticked("asyncCheckAttempt - be uncancelable if None finalizer") { implicit ticker => + val t = IO.asyncCheckAttempt[Int] { _ => IO.pure(Left(None)) } + val test = for { + fib <- t.start + _ <- IO(ticker.ctx.tick()) + _ <- fib.cancel + } yield () - test must nonTerminate - } - } + assertNonTerminate(test) + } - "async" should { + // + // async + // - "resume value continuation within async" in ticked { implicit ticker => - IO.async[Int](k => IO(k(Right(42))).map(_ => None)) must completeAs(42) - } + ticked("async - resume value continuation within async") { implicit ticker => + assertCompleteAs(IO.async[Int](k => IO(k(Right(42))).map(_ => None)), 42) + } - "continue from the results of an async produced prior to registration" in ticked { - implicit ticker => - IO.async[Int](cb => IO(cb(Right(42))).as(None)).map(_ + 2) must completeAs(44) - } + ticked("async - continue from the results of an async produced prior to registration") { + implicit ticker => + assertCompleteAs(IO.async[Int](cb => IO(cb(Right(42))).as(None)).map(_ + 2), 44) + } // format: off - "produce a failure when the registration raises an error after callback" in ticked { implicit ticker => + ticked("async - produce a failure when the registration raises an error after callback") { implicit ticker => case object TestException extends RuntimeException - IO.async[Int](cb => IO(cb(Right(42))) + assertFailAs(IO.async[Int](cb => IO(cb(Right(42))) .flatMap(_ => IO.raiseError(TestException))) - .void must failAs(TestException) + .void, TestException) } // format: on - "repeated async callback" in ticked { implicit ticker => - case object TestException extends RuntimeException + ticked("async - repeated async callback") { implicit ticker => + case object TestException extends RuntimeException - var cb: Either[Throwable, Int] => Unit = null + var cb: Either[Throwable, Int] => Unit = null - val async = IO.async_[Int] { cb0 => cb = cb0 } + val async = IO.async_[Int] { cb0 => cb = cb0 } - val test = for { - fiber <- async.start - _ <- IO(ticker.ctx.tick()) - _ <- IO(cb(Right(42))) - _ <- IO(ticker.ctx.tick()) - _ <- IO(cb(Right(43))) - _ <- IO(ticker.ctx.tick()) - _ <- IO(cb(Left(TestException))) - _ <- IO(ticker.ctx.tick()) - value <- fiber.joinWithNever - } yield value + val test = for { + fiber <- async.start + _ <- IO(ticker.ctx.tick()) + _ <- IO(cb(Right(42))) + _ <- IO(ticker.ctx.tick()) + _ <- IO(cb(Right(43))) + _ <- IO(ticker.ctx.tick()) + _ <- IO(cb(Left(TestException))) + _ <- IO(ticker.ctx.tick()) + value <- fiber.joinWithNever + } yield value - test must completeAs(42) - } + assertCompleteAs(test, 42) + } - "repeated async callback real" in real { - case object TestException extends RuntimeException + real("async - repeated async callback real") { + case object TestException extends RuntimeException + + var cb: Either[Throwable, Int] => Unit = null + + val test = for { + latch1 <- Deferred[IO, Unit] + latch2 <- Deferred[IO, Unit] + fiber <- + IO.async[Int] { cb0 => + IO { cb = cb0 } *> latch1.complete(()) *> latch2.get *> IO.pure(None) + }.start + _ <- latch1.get + _ <- IO(cb(Right(42))) + _ <- IO(cb(Right(43))) + _ <- IO(cb(Left(TestException))) + _ <- latch2.complete(()) + value <- fiber.joinWithNever + } yield value + + test.attempt.flatMap { n => IO(assertEquals(n, Right(42))) } + } - var cb: Either[Throwable, Int] => Unit = null - - val test = for { - latch1 <- Deferred[IO, Unit] - latch2 <- Deferred[IO, Unit] - fiber <- - IO.async[Int] { cb0 => - IO { cb = cb0 } *> latch1.complete(()) *> latch2.get *> IO.pure(None) - }.start - _ <- latch1.get - _ <- IO(cb(Right(42))) - _ <- IO(cb(Right(43))) - _ <- IO(cb(Left(TestException))) - _ <- latch2.complete(()) - value <- fiber.joinWithNever - } yield value - - test.attempt.flatMap { n => IO(n mustEqual Right(42)) } + ticked("async - calling async callback with null during registration (ticked)") { + implicit ticker => + IO.async[Int] { cb => IO(cb(null)).as(None) }.map(_ + 1).attempt.map { e => + assertCompleteAs( + IO { + e match { + case Left(err) => assert(err.isInstanceOf[NullPointerException]) + case Right(v) => fail(s"Expected Left, got $v") + } + }, + () + ) } + } - "calling async callback with null during registration (ticked)" in ticked { - implicit ticker => - IO.async[Int] { cb => IO(cb(null)).as(None) } - .map(_ + 1) - .attempt - .flatMap(e => - IO(e must beLeft(beAnInstanceOf[NullPointerException])).void) must completeAs(()) - } + ticked("async - calling async callback with null after registration (ticked)") { + implicit ticker => + val test = for { + cbp <- Deferred[IO, Either[Throwable, Int] => Unit] + fib <- IO.async[Int] { cb => cbp.complete(cb).as(None) }.start + _ <- IO(ticker.ctx.tickAll()) + cb <- cbp.get + _ <- IO(ticker.ctx.tickAll()) + _ <- IO(cb(null)) + e <- fib.joinWithNever.attempt + _ <- IO { + e match { + case Left(e) => assert(e.isInstanceOf[NullPointerException]) + case Right(v) => fail(s"Expected Left, got $v") + } + } + } yield () - "calling async callback with null after registration (ticked)" in ticked { - implicit ticker => - val test = for { - cbp <- Deferred[IO, Either[Throwable, Int] => Unit] - fib <- IO.async[Int] { cb => cbp.complete(cb).as(None) }.start - _ <- IO(ticker.ctx.tickAll()) - cb <- cbp.get - _ <- IO(ticker.ctx.tickAll()) - _ <- IO(cb(null)) - e <- fib.joinWithNever.attempt - _ <- IO(e must beLeft(beAnInstanceOf[NullPointerException])) - } yield () - - test must completeAs(()) - } + assertCompleteAs(test, ()) + } - "calling async callback with null during registration (real)" in real { - IO.async[Int] { cb => IO(cb(null)).as(None) } - .map(_ + 1) - .attempt - .flatMap(e => IO(e must beLeft(beAnInstanceOf[NullPointerException]))) + real("async - calling async callback with null during registration (real)") { + IO.async[Int] { cb => IO(cb(null)).as(None) }.map(_ + 1).attempt.flatMap { e => + IO { + e match { + case Left(e) => assert(e.isInstanceOf[NullPointerException]) + case Right(v) => fail(s"Expected Left, got $v") + } } + } + } - "calling async callback with null after registration (real)" in real { - for { - cbp <- Deferred[IO, Either[Throwable, Int] => Unit] - latch <- Deferred[IO, Unit] - fib <- IO.async[Int] { cb => cbp.complete(cb) *> latch.get.as(None) }.start - cb <- cbp.get - _r <- IO.both( - latch.complete(()) *> IO.sleep(0.1.second) *> IO(cb(null)), - fib.joinWithNever.attempt - ) - (_, r) = _r - _ <- IO(r must beLeft(beAnInstanceOf[NullPointerException])) - } yield ok - } + real("async - calling async callback with null after registration (real)") { + for { + cbp <- Deferred[IO, Either[Throwable, Int] => Unit] + latch <- Deferred[IO, Unit] + fib <- IO.async[Int] { cb => cbp.complete(cb) *> latch.get.as(None) }.start + cb <- cbp.get + _r <- IO.both( + latch.complete(()) *> IO.sleep(0.1.second) *> IO(cb(null)), + fib.joinWithNever.attempt + ) + (_, r) = _r + _ <- IO { + r match { + case Left(e) => assert(e.isInstanceOf[NullPointerException]) + case Right(v) => fail(s"Expected Left, got $v") + } + } + } yield () + } - "complete a fiber with Canceled under finalizer on poll" in ticked { implicit ticker => - val ioa = - IO.uncancelable(p => IO.canceled >> p(IO.unit).guarantee(IO.unit)) - .start - .flatMap(_.join) + ticked("async - complete a fiber with Canceled under finalizer on poll") { implicit ticker => + val ioa = + IO.uncancelable(p => IO.canceled >> p(IO.unit).guarantee(IO.unit)).start.flatMap(_.join) - ioa must completeAs(Outcome.canceled[IO, Throwable, Unit]) - } + assertCompleteAs(ioa, Outcome.canceled[IO, Throwable, Unit]) + } - "invoke multiple joins on fiber completion" in real { - val test = for { - f <- IO.pure(42).start - - delegate1 <- f.join.start - delegate2 <- f.join.start - delegate3 <- f.join.start - delegate4 <- f.join.start - - _ <- IO.cede - - r1 <- delegate1.join - r2 <- delegate2.join - r3 <- delegate3.join - r4 <- delegate4.join - } yield List(r1, r2, r3, r4) - - test.flatMap { results => - results.traverse { result => - IO(result must beLike { case Outcome.Succeeded(_) => ok }).flatMap { _ => - result match { - case Outcome.Succeeded(ioa) => - ioa.flatMap { oc => - IO(result must beLike { case Outcome.Succeeded(_) => ok }).flatMap { _ => - oc match { - case Outcome.Succeeded(ioa) => - ioa flatMap { i => IO(i mustEqual 42) } - - case _ => sys.error("nope") - } - } + real("async - invoke multiple joins on fiber completion") { + val test = for { + f <- IO.pure(42).start + + delegate1 <- f.join.start + delegate2 <- f.join.start + delegate3 <- f.join.start + delegate4 <- f.join.start + + _ <- IO.cede + + r1 <- delegate1.join + r2 <- delegate2.join + r3 <- delegate3.join + r4 <- delegate4.join + } yield List(r1, r2, r3, r4) + + test.flatMap { results => + results.traverse { result => + IO(assert(result.isSuccess)).flatMap { _ => + result match { + case Outcome.Succeeded(ioa) => + ioa.flatMap { oc => + IO(assert(result.isSuccess)).flatMap { _ => + oc match { + case Outcome.Succeeded(ioa) => + ioa flatMap { i => IO(assertEquals(i, 42)) } + + case _ => sys.error("nope") } - - case _ => sys.error("nope") + } } - } + + case _ => sys.error("nope") } } } + } + } - "both" should { - - "succeed if both sides succeed" in ticked { implicit ticker => - IO.both(IO.pure(1), IO.pure(2)) must completeAs((1, 2)) - } - - "fail if lhs fails" in ticked { implicit ticker => - case object TestException extends Throwable - IO.both(IO.raiseError(TestException), IO.pure(2)).void must failAs(TestException) - } - - "fail if rhs fails" in ticked { implicit ticker => - case object TestException extends Throwable - IO.both(IO.pure(2), IO.raiseError(TestException)).void must failAs(TestException) - } - - "cancel if lhs cancels" in ticked { implicit ticker => - IO.both(IO.canceled, IO.unit).void.start.flatMap(_.join) must completeAs( - Outcome.canceled[IO, Throwable, Unit]) - - } - - "cancel if rhs cancels" in ticked { implicit ticker => - IO.both(IO.unit, IO.canceled).void.start.flatMap(_.join) must completeAs( - Outcome.canceled[IO, Throwable, Unit]) - } - - "non terminate if lhs never completes" in ticked { implicit ticker => - IO.both(IO.never, IO.pure(1)).void must nonTerminate - } + ticked("async - both - succeed if both sides succeed") { implicit ticker => + assertCompleteAs(IO.both(IO.pure(1), IO.pure(2)), (1, 2)) + } - "non terminate if rhs never completes" in ticked { implicit ticker => - IO.both(IO.pure(1), IO.never).void must nonTerminate - } + ticked("async - both - fail if lhs fails") { implicit ticker => + case object TestException extends Throwable + assertFailAs(IO.both(IO.raiseError(TestException), IO.pure(2)).void, TestException) + } - "propagate cancelation" in ticked { implicit ticker => - (for { - fiber <- IO.both(IO.never, IO.never).void.start - _ <- IO(ticker.ctx.tick()) - _ <- fiber.cancel - _ <- IO(ticker.ctx.tick()) - oc <- fiber.join - } yield oc) must completeAs(Outcome.canceled[IO, Throwable, Unit]) - } + ticked("async - both - fail if rhs fails") { implicit ticker => + case object TestException extends Throwable + assertFailAs(IO.both(IO.pure(2), IO.raiseError(TestException)).void, TestException) + } - "cancel both fibers" in ticked { implicit ticker => - (for { - l <- Ref[IO].of(false) - r <- Ref[IO].of(false) - fiber <- - IO.both(IO.never.onCancel(l.set(true)), IO.never.onCancel(r.set(true))).start - _ <- IO(ticker.ctx.tick()) - _ <- fiber.cancel - _ <- IO(ticker.ctx.tick()) - l2 <- l.get - r2 <- r.get - } yield l2 -> r2) must completeAs(true -> true) - } + ticked("async - both - cancel if lhs cancels") { implicit ticker => + assertCompleteAs( + IO.both(IO.canceled, IO.unit).void.start.flatMap(_.join), + Outcome.canceled[IO, Throwable, Unit]) - } + } - "bothOutcome" should { - "cancel" in ticked { implicit ticker => - (for { - g1 <- IO.deferred[Unit] - g2 <- IO.deferred[Unit] - f <- IO - .bothOutcome(g1.complete(()) *> IO.never[Unit], g2.complete(()) *> IO.never[Unit]) - .start - _ <- g1.get - _ <- g2.get - _ <- f.cancel - } yield ()) must completeAs(()) - } - } + ticked("async - both - cancel if rhs cancels") { implicit ticker => + assertCompleteAs( + IO.both(IO.unit, IO.canceled).void.start.flatMap(_.join), + Outcome.canceled[IO, Throwable, Unit]) + } - "raceOutcome" should { - "cancel both fibers" in ticked { implicit ticker => - (for { - l <- Ref.of[IO, Boolean](false) - r <- Ref.of[IO, Boolean](false) - fiber <- - IO.never[Int] - .onCancel(l.set(true)) - .raceOutcome(IO.never[Int].onCancel(r.set(true))) - .start - _ <- IO(ticker.ctx.tick()) - _ <- fiber.cancel - _ <- IO(ticker.ctx.tick()) - l2 <- l.get - r2 <- r.get - } yield l2 -> r2) must completeAs(true -> true) - } - } + ticked("async - both - non terminate if lhs never completes") { implicit ticker => + assertNonTerminate(IO.both(IO.never, IO.pure(1)).void) + } - "race" should { - "succeed with faster side" in ticked { implicit ticker => - IO.race(IO.sleep(10.minutes) >> IO.pure(1), IO.pure(2)) must completeAs(Right(2)) - } + ticked("async - both - non terminate if rhs never completes") { implicit ticker => + assertNonTerminate(IO.both(IO.pure(1), IO.never).void) + } - "fail if lhs fails" in ticked { implicit ticker => - case object TestException extends Throwable - IO.race(IO.raiseError[Int](TestException), IO.sleep(10.millis) >> IO.pure(1)) - .void must failAs(TestException) - } + ticked("async - both - propagate cancelation") { implicit ticker => + assertCompleteAs( + for { + fiber <- IO.both(IO.never, IO.never).void.start + _ <- IO(ticker.ctx.tick()) + _ <- fiber.cancel + _ <- IO(ticker.ctx.tick()) + oc <- fiber.join + } yield oc, + Outcome.canceled[IO, Throwable, Unit] + ) + } - "fail if rhs fails" in ticked { implicit ticker => - case object TestException extends Throwable - IO.race(IO.sleep(10.millis) >> IO.pure(1), IO.raiseError[Int](TestException)) - .void must failAs(TestException) - } + ticked("async - both - cancel both fibers") { implicit ticker => + assertCompleteAs( + for { + l <- Ref[IO].of(false) + r <- Ref[IO].of(false) + fiber <- + IO.both(IO.never.onCancel(l.set(true)), IO.never.onCancel(r.set(true))).start + _ <- IO(ticker.ctx.tick()) + _ <- fiber.cancel + _ <- IO(ticker.ctx.tick()) + l2 <- l.get + r2 <- r.get + } yield l2 -> r2, + true -> true + ) + } - "fail if lhs fails and rhs never completes" in ticked { implicit ticker => - case object TestException extends Throwable - IO.race(IO.raiseError[Int](TestException), IO.never).void must failAs(TestException) - } + ticked("async - bothOutcome - cancel") { implicit ticker => + assertCompleteAs( + for { + g1 <- IO.deferred[Unit] + g2 <- IO.deferred[Unit] + f <- IO + .bothOutcome(g1.complete(()) *> IO.never[Unit], g2.complete(()) *> IO.never[Unit]) + .start + _ <- g1.get + _ <- g2.get + _ <- f.cancel + } yield (), + () + ) + } - "fail if rhs fails and lhs never completes" in ticked { implicit ticker => - case object TestException extends Throwable - IO.race(IO.never, IO.raiseError[Int](TestException)).void must failAs(TestException) - } + ticked("async - raceOutcome - cancel both fibers") { implicit ticker => + assertCompleteAs( + for { + l <- Ref.of[IO, Boolean](false) + r <- Ref.of[IO, Boolean](false) + fiber <- + IO.never[Int] + .onCancel(l.set(true)) + .raceOutcome(IO.never[Int].onCancel(r.set(true))) + .start + _ <- IO(ticker.ctx.tick()) + _ <- fiber.cancel + _ <- IO(ticker.ctx.tick()) + l2 <- l.get + r2 <- r.get + } yield l2 -> r2, + true -> true + ) + } - "succeed if lhs never completes" in ticked { implicit ticker => - IO.race(IO.never[Int], IO.pure(2)) must completeAs(Right(2)) - } + ticked("async - race - succeed with faster side") { implicit ticker => + assertCompleteAs(IO.race(IO.sleep(10.minutes) >> IO.pure(1), IO.pure(2)), Right(2)) + } - "succeed if rhs never completes" in ticked { implicit ticker => - IO.race(IO.pure(2), IO.never[Int]) must completeAs(Left(2)) - } + ticked("async - race - fail if lhs fails") { implicit ticker => + case object TestException extends Throwable + assertFailAs( + IO.race(IO.raiseError[Int](TestException), IO.sleep(10.millis) >> IO.pure(1)).void, + TestException) + } - "cancel if both sides cancel" in ticked { implicit ticker => - IO.both(IO.canceled, IO.canceled).void.start.flatMap(_.join) must completeAs( - Outcome.canceled[IO, Throwable, Unit]) - } + ticked("async - race - fail if rhs fails") { implicit ticker => + case object TestException extends Throwable + assertFailAs( + IO.race(IO.sleep(10.millis) >> IO.pure(1), IO.raiseError[Int](TestException)).void, + TestException) + } - "cancel if lhs cancels and rhs succeeds" in ticked { implicit ticker => - IO.race(IO.canceled, IO.sleep(1.milli) *> IO.pure(1)).void must selfCancel - } + ticked("async - race - fail if lhs fails and rhs never completes") { implicit ticker => + case object TestException extends Throwable + assertFailAs(IO.race(IO.raiseError[Int](TestException), IO.never).void, TestException) + } - "cancel if rhs cancels and lhs succeeds" in ticked { implicit ticker => - IO.race(IO.sleep(1.milli) *> IO.pure(1), IO.canceled).void must selfCancel - } + ticked("async - race - fail if rhs fails and lhs never completes") { implicit ticker => + case object TestException extends Throwable + assertFailAs(IO.race(IO.never, IO.raiseError[Int](TestException)).void, TestException) + } - "cancel if lhs cancels and rhs fails" in ticked { implicit ticker => - case object TestException extends Throwable - IO.race(IO.canceled, IO.sleep(1.milli) *> IO.raiseError[Unit](TestException)) - .void must selfCancel - } + ticked("async - race - succeed if lhs never completes") { implicit ticker => + assertCompleteAs(IO.race(IO.never[Int], IO.pure(2)), Right(2)) + } - "cancel if rhs cancels and lhs fails" in ticked { implicit ticker => - case object TestException extends Throwable - IO.race(IO.sleep(1.milli) *> IO.raiseError[Unit](TestException), IO.canceled) - .void must selfCancel - } + ticked("async - race - succeed if rhs never completes") { implicit ticker => + assertCompleteAs(IO.race(IO.pure(2), IO.never[Int]), Left(2)) + } - "cancel both fibers" in ticked { implicit ticker => - (for { - l <- Ref.of[IO, Boolean](false) - r <- Ref.of[IO, Boolean](false) - fiber <- - IO.race(IO.never.onCancel(l.set(true)), IO.never.onCancel(r.set(true))).start - _ <- IO(ticker.ctx.tick()) - _ <- fiber.cancel - _ <- IO(ticker.ctx.tick()) - l2 <- l.get - r2 <- r.get - } yield l2 -> r2) must completeAs(true -> true) - } + ticked("async - race - cancel if both sides cancel") { implicit ticker => + assertCompleteAs( + IO.both(IO.canceled, IO.canceled).void.start.flatMap(_.join), + Outcome.canceled[IO, Throwable, Unit]) + } - "evaluate a timeout using sleep and race" in ticked { implicit ticker => - IO.race(IO.never[Unit], IO.sleep(2.seconds)) must completeAs(Right(())) - } + ticked("async - race - cancel if lhs cancels and rhs succeeds") { implicit ticker => + assertSelfCancel(IO.race(IO.canceled, IO.sleep(1.milli) *> IO.pure(1)).void) + } - "evaluate a timeout using sleep and race in real time" in real { - IO.race(IO.never[Unit], IO.sleep(10.millis)).flatMap { res => - IO { - res must beRight(()) - } - } - } + ticked("async - race - cancel if rhs cancels and lhs succeeds") { implicit ticker => + assertSelfCancel(IO.race(IO.sleep(1.milli) *> IO.pure(1), IO.canceled).void) + } - "immediately cancel when timing out canceled" in real { - val program = IO.canceled.timeout(2.seconds) + ticked("async - race - cancel if lhs cancels and rhs fails") { implicit ticker => + case object TestException extends Throwable + assertSelfCancel( + IO.race(IO.canceled, IO.sleep(1.milli) *> IO.raiseError[Unit](TestException)).void) + } - val test = TestControl.execute(program.start.flatMap(_.join)) flatMap { ctl => - ctl.tickFor(1.second) *> ctl.results - } + ticked("async - race - cancel if rhs cancels and lhs fails") { implicit ticker => + case object TestException extends Throwable + assertSelfCancel( + IO.race(IO.sleep(1.milli) *> IO.raiseError[Unit](TestException), IO.canceled).void) + } - test flatMap { results => - IO { - results must beLike { case Some(Outcome.Succeeded(Outcome.Canceled())) => ok } - } - } - } + ticked("async - race - cancel both fibers") { implicit ticker => + assertCompleteAs( + for { + l <- Ref.of[IO, Boolean](false) + r <- Ref.of[IO, Boolean](false) + fiber <- + IO.race(IO.never.onCancel(l.set(true)), IO.never.onCancel(r.set(true))).start + _ <- IO(ticker.ctx.tick()) + _ <- fiber.cancel + _ <- IO(ticker.ctx.tick()) + l2 <- l.get + r2 <- r.get + } yield l2 -> r2, + true -> true + ) + } - "immediately cancel when timing out and forgetting canceled" in real { - val program = IO.canceled.timeoutAndForget(2.seconds) - val test = TestControl.execute(program.start.flatMap(_.join)) flatMap { ctl => - ctl.tickFor(1.second) *> ctl.results - } + ticked("async - race - evaluate a timeout using sleep and race") { implicit ticker => + assertCompleteAs(IO.race(IO.never[Unit], IO.sleep(2.seconds)), Right(())) + } - test flatMap { results => - IO { - results must beLike { case Some(Outcome.Succeeded(Outcome.Canceled())) => ok } - } - } - } + real("easync - race - valuate a timeout using sleep and race in real time") { + IO.race(IO.never[Unit], IO.sleep(10.millis)).flatMap { res => + IO { + assertEquals(res, Right(())) + } + } + } - "timeout a suspended timeoutAndForget" in real { - val program = IO.never.timeoutAndForget(2.seconds).timeout(1.second) - val test = TestControl.execute(program.start.flatMap(_.join)) flatMap { ctl => - ctl.tickFor(1.second) *> ctl.results - } + real("async - race - immediately cancel when timing out canceled") { + val program = IO.canceled.timeout(2.seconds) - test.flatMap(results => IO(results must beSome)) - } + val test = TestControl.execute(program.start.flatMap(_.join)) flatMap { ctl => + ctl.tickFor(1.second) *> ctl.results + } - "return the left when racing against never" in ticked { implicit ticker => - IO.pure(42) - .racePair(IO.never: IO[Unit]) - .map(_.left.toOption.map(_._1).get) must completeAs( - Outcome.succeeded[IO, Throwable, Int](IO.pure(42))) + test flatMap { results => + IO { + results match { + case Some(Outcome.Succeeded(Outcome.Canceled())) => () + case other => fail(s"Expected Outcome.Succeeded(Outcome.Canceled), got $other") } + } + } + } - "immediately cancel inner race when outer unit" in real { - for { - start <- IO.monotonic - _ <- IO.race(IO.unit, IO.race(IO.never, IO.sleep(10.seconds))) - end <- IO.monotonic + real("async - race - immediately cancel when timing out and forgetting canceled") { + val program = IO.canceled.timeoutAndForget(2.seconds) + val test = TestControl.execute(program.start.flatMap(_.join)) flatMap { ctl => + ctl.tickFor(1.second) *> ctl.results + } - result <- IO((end - start) must beLessThan(5.seconds)) - } yield result + test flatMap { results => + IO { + results match { + case Some(Outcome.Succeeded(Outcome.Canceled())) => () + case other => fail(s"Expected Outcome.Succeeded(Outcome.Canceled), got $other") } } + } + } - "allow for misordered nesting" in ticked { implicit ticker => - var outerR = 0 - var innerR = 0 - - val outer = IO.async[Int] { cb1 => - val inner = IO.async[Int] { cb2 => - IO(cb1(Right(1))) *> - IO.executionContext.flatMap(ec => IO(ec.execute(() => cb2(Right(2))))).as(None) - } + real("async - race - timeout a suspended timeoutAndForget") { + val program = IO.never.timeoutAndForget(2.seconds).timeout(1.second) + val test = TestControl.execute(program.start.flatMap(_.join)) flatMap { ctl => + ctl.tickFor(1.second) *> ctl.results + } - inner.flatMap(i => IO { innerR = i }).as(None) - } + test.flatMap(results => IO(assert(results.isDefined))) + } - val test = outer.flatMap(i => IO { outerR = i }) + ticked("async - race - return the left when racing against never") { implicit ticker => + assertCompleteAs( + IO.pure(42).racePair(IO.never: IO[Unit]).map(_.left.toOption.map(_._1).get), + Outcome.succeeded[IO, Throwable, Int](IO.pure(42))) + } - test must completeAs(()) - outerR mustEqual 1 - innerR mustEqual 2 - } - } + real("async - race - immediately cancel inner race when outer unit") { + for { + start <- IO.monotonic + _ <- IO.race(IO.unit, IO.race(IO.never, IO.sleep(10.seconds))) + end <- IO.monotonic - "cancelation" should { + result <- IO(assert((end - start) < 5.seconds)) + } yield result + } - "implement never with non-terminating semantics" in ticked { implicit ticker => - IO.never must nonTerminate - } + ticked("async - allow for misordered nesting") { implicit ticker => + var outerR = 0 + var innerR = 0 - "cancel an infinite chain of right-binds" in ticked { implicit ticker => - lazy val infinite: IO[Unit] = IO.unit.flatMap(_ => infinite) - infinite.start.flatMap(f => f.cancel >> f.join) must completeAs( - Outcome.canceled[IO, Throwable, Unit]) + val outer = IO.async[Int] { cb1 => + val inner = IO.async[Int] { cb2 => + IO(cb1(Right(1))) *> + IO.executionContext.flatMap(ec => IO(ec.execute(() => cb2(Right(2))))).as(None) } - "cancel never" in ticked { implicit ticker => - (IO.never: IO[Unit]).start.flatMap(f => f.cancel >> f.join) must completeAs( - Outcome.canceled[IO, Throwable, Unit]) - } + inner.flatMap(i => IO { innerR = i }).as(None) + } - "cancel never after scheduling" in ticked { implicit ticker => - val ioa = for { - f <- (IO.never: IO[Unit]).start - ec <- IO.executionContext - _ <- IO(ec.asInstanceOf[TestContext].tick()) - _ <- f.cancel - oc <- f.join - } yield oc + val test = outer.flatMap(i => IO { outerR = i }) - ioa must completeAs(Outcome.canceled[IO, Throwable, Unit]) - } + assertCompleteAs(test, ()) + assertEquals(outerR, 1) + assertEquals(innerR, 2) + } - "sequence async cancel token upon cancelation during suspension" in ticked { - implicit ticker => - var affected = false + ticked("cancelation - implement never with non-terminating semantics") { implicit ticker => + assertNonTerminate(IO.never) + } - val target = IO.async[Unit] { _ => IO.pure(Some(IO { affected = true })) } + ticked("cancelation - cancel an infinite chain of right-binds") { implicit ticker => + lazy val infinite: IO[Unit] = IO.unit.flatMap(_ => infinite) + assertCompleteAs( + infinite.start.flatMap(f => f.cancel >> f.join), + Outcome.canceled[IO, Throwable, Unit]) + } - val ioa = for { - f <- target.start - _ <- IO(ticker.ctx.tick()) - _ <- f.cancel - } yield () + ticked("cancelation - cancel never") { implicit ticker => + assertCompleteAs( + (IO.never: IO[Unit]).start.flatMap(f => f.cancel >> f.join), + Outcome.canceled[IO, Throwable, Unit]) + } - ioa must completeAs(()) - affected must beTrue - } + ticked("cancelation - cancel never after scheduling") { implicit ticker => + val ioa = for { + f <- (IO.never: IO[Unit]).start + ec <- IO.executionContext + _ <- IO(ec.asInstanceOf[TestContext].tick()) + _ <- f.cancel + oc <- f.join + } yield oc - "suppress async cancel token upon cancelation in masked region" in ticked { - implicit ticker => - var affected = false + assertCompleteAs(ioa, Outcome.canceled[IO, Throwable, Unit]) + } - val target = IO uncancelable { _ => - IO.async[Unit] { _ => IO.pure(Some(IO { affected = true })) } - } + ticked("cancelation - sequence async cancel token upon cancelation during suspension") { + implicit ticker => + var affected = false - val ioa = for { - f <- target.start - _ <- IO(ticker.ctx.tick()) - _ <- f.cancel - } yield () + val target = IO.async[Unit] { _ => IO.pure(Some(IO { affected = true })) } - ioa must nonTerminate // we're canceling an uncancelable never - affected must beFalse - } + val ioa = for { + f <- target.start + _ <- IO(ticker.ctx.tick()) + _ <- f.cancel + } yield () - "cancel flatMap continuations following a canceled uncancelable block" in ticked { - implicit ticker => - IO.uncancelable(_ => IO.canceled).flatMap(_ => IO.pure(())) must selfCancel - } + assertCompleteAs(ioa, ()) + assert(affected) + } - "sequence onCancel when canceled before registration" in ticked { implicit ticker => - var passed = false - val test = IO.uncancelable { poll => - IO.canceled >> poll(IO.unit).onCancel(IO { passed = true }) - } + ticked("cancelation - suppress async cancel token upon cancelation in masked region") { + implicit ticker => + var affected = false - test must selfCancel - passed must beTrue + val target = IO uncancelable { _ => + IO.async[Unit] { _ => IO.pure(Some(IO { affected = true })) } } - "break out of uncancelable when canceled before poll" in ticked { implicit ticker => - var passed = true - val test = IO.uncancelable { poll => - IO.canceled >> poll(IO.unit) >> IO { passed = false } - } + val ioa = for { + f <- target.start + _ <- IO(ticker.ctx.tick()) + _ <- f.cancel + } yield () - test must selfCancel - passed must beTrue - } - - "not invoke onCancel when previously canceled within uncancelable" in ticked { - implicit ticker => - var failed = false - IO.uncancelable(_ => - IO.canceled >> IO.unit.onCancel(IO { failed = true })) must selfCancel - failed must beFalse - } + assertNonTerminate(ioa) // we're canceling an uncancelable never + assert(!affected) + } - "support re-enablement via cancelable" in ticked { implicit ticker => - IO.deferred[Unit].flatMap { gate => - val test = IO.deferred[Unit] flatMap { latch => - (gate.complete(()) *> latch.get).uncancelable.cancelable(latch.complete(()).void) - } + ticked("cancelation - cancel flatMap continuations following a canceled uncancelable block") { + implicit ticker => + assertSelfCancel(IO.uncancelable(_ => IO.canceled).flatMap(_ => IO.pure(()))) + } - test.start.flatMap(gate.get *> _.cancel) - } must completeAs(()) + ticked("cancelation - sequence onCancel when canceled before registration") { + implicit ticker => + var passed = false + val test = IO.uncancelable { poll => + IO.canceled >> poll(IO.unit).onCancel(IO { passed = true }) } - "cancelable waits for termination" in ticked { implicit ticker => - def test(fin: IO[Unit]) = { - val go = IO.never.uncancelable.cancelable(fin) - go.start.flatMap(IO.sleep(1.second) *> _.cancel) - } + assertSelfCancel(test) + assert(passed) + } - test(IO.unit) must nonTerminate - test(IO.raiseError(new Exception)) must nonTerminate - test(IO.canceled) must nonTerminate + ticked("cancelation - break out of uncancelable when canceled before poll") { + implicit ticker => + var passed = true + val test = IO.uncancelable { poll => + IO.canceled >> poll(IO.unit) >> IO { passed = false } } - "cancelable cancels task" in ticked { implicit ticker => - def test(fin: IO[Unit]) = - IO.deferred[Unit].flatMap { latch => - val go = IO.never[Unit].onCancel(latch.complete(()).void).cancelable(fin) - go.start.flatMap(IO.sleep(1.second) *> _.cancel) *> latch.get - } + assertSelfCancel(test) + assert(passed) + } - test(IO.unit) must completeAs(()) - test(IO.raiseError(new Exception)) must completeAs(()) - test(IO.canceled) must completeAs(()) - } + ticked("cancelation - not invoke onCancel when previously canceled within uncancelable") { + implicit ticker => + var failed = false + assertSelfCancel( + IO.uncancelable(_ => IO.canceled >> IO.unit.onCancel(IO { failed = true }))) + assert(!failed) + } - "only unmask within current fiber" in ticked { implicit ticker => - var passed = false - val test = IO uncancelable { poll => - IO.uncancelable(_ => poll(IO.canceled >> IO { passed = true })) - .start - .flatMap(_.join) - .void + ticked("cancelation - support re-enablement via cancelable") { implicit ticker => + assertCompleteAs( + IO.deferred[Unit].flatMap { gate => + val test = IO.deferred[Unit] flatMap { latch => + (gate.complete(()) *> latch.get).uncancelable.cancelable(latch.complete(()).void) } - test must completeAs(()) - passed must beTrue - } + test.start.flatMap(gate.get *> _.cancel) + }, + () + ) + } - "polls from unrelated fibers are no-ops" in ticked { implicit ticker => - var canceled = false - val test = for { - deferred <- Deferred[IO, Poll[IO]] - started <- Deferred[IO, Unit] - _ <- IO.uncancelable(deferred.complete).void.start - f <- (started.complete(()) *> - deferred.get.flatMap(poll => poll(IO.never[Unit]).onCancel(IO { canceled = true }))) - .uncancelable - .start - _ <- started.get - _ <- f.cancel - } yield () + ticked("cancelation - cancelable waits for termination") { implicit ticker => + def test(fin: IO[Unit]) = { + val go = IO.never.uncancelable.cancelable(fin) + go.start.flatMap(IO.sleep(1.second) *> _.cancel) + } - test must nonTerminate - canceled must beFalse + assertNonTerminate(test(IO.unit)) + assertNonTerminate(test(IO.raiseError(new Exception))) + assertNonTerminate(test(IO.canceled)) + } + + ticked("cancelation - cancelable cancels task") { implicit ticker => + def test(fin: IO[Unit]) = + IO.deferred[Unit].flatMap { latch => + val go = IO.never[Unit].onCancel(latch.complete(()).void).cancelable(fin) + go.start.flatMap(IO.sleep(1.second) *> _.cancel) *> latch.get } - "run three finalizers when an async is canceled while suspended" in ticked { - implicit ticker => - var results = List[Int]() + assertCompleteAs(test(IO.unit), ()) + assertCompleteAs(test(IO.raiseError(new Exception)), ()) + assertCompleteAs(test(IO.canceled), ()) + } - val body = IO.async[Nothing] { _ => IO.pure(Some(IO(results ::= 3))) } + ticked("cancelation - only unmask within current fiber") { implicit ticker => + var passed = false + val test = IO uncancelable { poll => + IO.uncancelable(_ => poll(IO.canceled >> IO { passed = true })).start.flatMap(_.join).void + } - val test = for { - f <- body.onCancel(IO(results ::= 2)).onCancel(IO(results ::= 1)).start - _ <- IO(ticker.ctx.tick()) - _ <- f.cancel - back <- IO(results) - } yield back + assertCompleteAs(test, ()) + assert(passed) + } - test must completeAs(List(1, 2, 3)) - } + ticked("cancelation - polls from unrelated fibers are no-ops") { implicit ticker => + var canceled = false + val test = for { + deferred <- Deferred[IO, Poll[IO]] + started <- Deferred[IO, Unit] + _ <- IO.uncancelable(deferred.complete).void.start + f <- (started.complete(()) *> + deferred.get.flatMap(poll => poll(IO.never[Unit]).onCancel(IO { canceled = true }))) + .uncancelable + .start + _ <- started.get + _ <- f.cancel + } yield () + + assertNonTerminate(test) + assert(!canceled) + } - "uncancelable canceled with finalizer within fiber should not block" in ticked { - implicit ticker => - val fab = IO.uncancelable(_ => IO.canceled.onCancel(IO.unit)).start.flatMap(_.join) + ticked("cancelation - run three finalizers when an async is canceled while suspended") { + implicit ticker => + var results = List[Int]() - fab must completeAs(Outcome.succeeded[IO, Throwable, Unit](IO.unit)) - } + val body = IO.async[Nothing] { _ => IO.pure(Some(IO(results ::= 3))) } - "uncancelable canceled with finalizer within fiber should flatMap another day" in ticked { - implicit ticker => - val fa = IO.pure(42) - val fab: IO[Int => Int] = - IO.uncancelable(_ => IO.canceled.onCancel(IO.unit)) - .start - .flatMap(_.join) - .flatMap(_ => IO.pure((i: Int) => i)) - - fab.ap(fa) must completeAs(42) - fab.flatMap(f => fa.map(f)) must completeAs(42) - } + val test = for { + f <- body.onCancel(IO(results ::= 2)).onCancel(IO(results ::= 1)).start + _ <- IO(ticker.ctx.tick()) + _ <- f.cancel + back <- IO(results) + } yield back - "ignore repeated polls" in ticked { implicit ticker => - var passed = true + assertCompleteAs(test, List(1, 2, 3)) + } - val test = IO.uncancelable { poll => - poll(poll(IO.unit) >> IO.canceled) >> IO { passed = false } - } + ticked("cancelation - uncancelable canceled with finalizer within fiber should not block") { + implicit ticker => + val fab = IO.uncancelable(_ => IO.canceled.onCancel(IO.unit)).start.flatMap(_.join) - test must selfCancel - passed must beTrue - } + assertCompleteAs(fab, Outcome.succeeded[IO, Throwable, Unit](IO.unit)) + } - "never terminate when racing infinite cancels" in ticked { implicit ticker => - var started = false + ticked( + "cancelation - uncancelable canceled with finalizer within fiber should flatMap another day") { + implicit ticker => + val fa = IO.pure(42) + val fab: IO[Int => Int] = + IO.uncancelable(_ => IO.canceled.onCancel(IO.unit)) + .start + .flatMap(_.join) + .flatMap(_ => IO.pure((i: Int) => i)) - val markStarted = IO { started = true } - lazy val cedeUntilStarted: IO[Unit] = - IO(started).ifM(IO.unit, IO.cede >> cedeUntilStarted) + assertCompleteAs(fab.ap(fa), 42) + assertCompleteAs(fab.flatMap(f => fa.map(f)), 42) + } - val test = for { - f <- (markStarted *> IO.never).onCancel(IO.never).start - _ <- cedeUntilStarted - _ <- IO.race(f.cancel, f.cancel) - } yield () + ticked("cancelation - ignore repeated polls") { implicit ticker => + var passed = true - test should nonTerminate - } + val test = IO.uncancelable { poll => + poll(poll(IO.unit) >> IO.canceled) >> IO { passed = false } + } - "first canceller backpressures subsequent cancellers" in ticked { implicit ticker => - var started = false + assertSelfCancel(test) + assert(passed) + } - val markStarted = IO { started = true } - lazy val cedeUntilStarted: IO[Unit] = - IO(started).ifM(IO.unit, IO.cede >> cedeUntilStarted) + ticked("cancelation - never terminate when racing infinite cancels") { implicit ticker => + var started = false - var started2 = false + val markStarted = IO { started = true } + lazy val cedeUntilStarted: IO[Unit] = + IO(started).ifM(IO.unit, IO.cede >> cedeUntilStarted) - val markStarted2 = IO { started2 = true } - lazy val cedeUntilStarted2: IO[Unit] = - IO(started2).ifM(IO.unit, IO.cede >> cedeUntilStarted2) + val test = for { + f <- (markStarted *> IO.never).onCancel(IO.never).start + _ <- cedeUntilStarted + _ <- IO.race(f.cancel, f.cancel) + } yield () - val test = for { - first <- (markStarted *> IO.never).onCancel(IO.never).start - _ <- (cedeUntilStarted *> markStarted2 *> first.cancel).start - _ <- cedeUntilStarted2 *> first.cancel - } yield () + assertNonTerminate(test) + } - test must nonTerminate - } + ticked("cancelation - first canceller backpressures subsequent cancellers") { + implicit ticker => + var started = false - "reliably cancel infinite IO.unit(s)" in real { - IO.unit.foreverM.start.flatMap(f => IO.sleep(50.millis) >> f.cancel).as(ok) - } + val markStarted = IO { started = true } + lazy val cedeUntilStarted: IO[Unit] = + IO(started).ifM(IO.unit, IO.cede >> cedeUntilStarted) - "reliably cancel infinite IO.cede(s)" in real { - IO.cede.foreverM.start.flatMap(f => IO.sleep(50.millis) >> f.cancel).as(ok) - } + var started2 = false - "cancel a long sleep with a short one" in real { - IO.sleep(10.seconds).race(IO.sleep(50.millis)).flatMap { res => - IO { - res must beRight(()) - } - } - } + val markStarted2 = IO { started2 = true } + lazy val cedeUntilStarted2: IO[Unit] = + IO(started2).ifM(IO.unit, IO.cede >> cedeUntilStarted2) - "cancel a long sleep with a short one through evalOn" in real { - IO.executionContext flatMap { ec => - val ec2 = new ExecutionContext { - def execute(r: Runnable) = ec.execute(r) - def reportFailure(t: Throwable) = ec.reportFailure(t) - } + val test = for { + first <- (markStarted *> IO.never).onCancel(IO.never).start + _ <- (cedeUntilStarted *> markStarted2 *> first.cancel).start + _ <- cedeUntilStarted2 *> first.cancel + } yield () + + assertNonTerminate(test) + } - val ioa = IO.sleep(10.seconds).race(IO.sleep(50.millis)) - ioa.evalOn(ec2) flatMap { res => IO(res must beRight(())) } - } - } + real("cancelation - reliably cancel infinite IO.unit(s)") { + IO.unit.foreverM.start.flatMap(f => IO.sleep(50.millis) >> f.cancel) + } - "await uncancelable blocks in cancelation" in ticked { implicit ticker => - var started = false + real("cancelation - reliably cancel infinite IO.cede(s)") { + IO.cede.foreverM.start.flatMap(f => IO.sleep(50.millis) >> f.cancel) + } - val markStarted = IO { started = true } - lazy val cedeUntilStarted: IO[Unit] = - IO(started).ifM(IO.unit, IO.cede >> cedeUntilStarted) + real("cancelation - cancel a long sleep with a short one") { + IO.sleep(10.seconds).race(IO.sleep(50.millis)).flatMap { res => + IO { + assertEquals(res, Right(())) + } + } + } - IO.uncancelable(_ => markStarted *> IO.never) - .start - .flatMap(f => cedeUntilStarted *> f.cancel) must nonTerminate + real("cancelation - cancel a long sleep with a short one through evalOn") { + IO.executionContext flatMap { ec => + val ec2 = new ExecutionContext { + def execute(r: Runnable) = ec.execute(r) + def reportFailure(t: Throwable) = ec.reportFailure(t) } - "await cancelation of cancelation of uncancelable never" in ticked { implicit ticker => - var started = false + val ioa = IO.sleep(10.seconds).race(IO.sleep(50.millis)) + ioa.evalOn(ec2) flatMap { res => IO(assertEquals(res, Right(()))) } + } + } - val markStarted = IO { started = true } - lazy val cedeUntilStarted: IO[Unit] = - IO(started).ifM(IO.unit, IO.cede >> cedeUntilStarted) + ticked("cancelation - await uncancelable blocks in cancelation") { implicit ticker => + var started = false - var started2 = false + val markStarted = IO { started = true } + lazy val cedeUntilStarted: IO[Unit] = + IO(started).ifM(IO.unit, IO.cede >> cedeUntilStarted) - val markStarted2 = IO { started2 = true } - lazy val cedeUntilStarted2: IO[Unit] = - IO(started2).ifM(IO.unit, IO.cede >> cedeUntilStarted2) + assertNonTerminate( + IO.uncancelable(_ => markStarted *> IO.never) + .start + .flatMap(f => cedeUntilStarted *> f.cancel)) + } - val test = for { - first <- IO.uncancelable(_ => markStarted *> IO.never).start - second <- - IO.uncancelable(p => cedeUntilStarted *> markStarted2 *> p(first.cancel)).start - _ <- cedeUntilStarted2 - _ <- second.cancel - } yield () + ticked("cancelation - await cancelation of cancelation of uncancelable never") { + implicit ticker => + var started = false - test must nonTerminate - } + val markStarted = IO { started = true } + lazy val cedeUntilStarted: IO[Unit] = + IO(started).ifM(IO.unit, IO.cede >> cedeUntilStarted) - "catch stray exceptions in uncancelable" in ticked { implicit ticker => - IO.uncancelable[Unit](_ => throw new RuntimeException).voidError must completeAs(()) - } + var started2 = false - "unmask following stray exceptions in uncancelable" in ticked { implicit ticker => - IO.uncancelable[Unit](_ => throw new RuntimeException) - .handleErrorWith(_ => IO.canceled *> IO.never) must selfCancel - } + val markStarted2 = IO { started2 = true } + lazy val cedeUntilStarted2: IO[Unit] = + IO(started2).ifM(IO.unit, IO.cede >> cedeUntilStarted2) - "catch exceptions in cont" in ticked { implicit ticker => - IO.cont[Unit, Unit](new Cont[IO, Unit, Unit] { - override def apply[F[_]](implicit F: MonadCancel[F, Throwable]) - : (Either[Throwable, Unit] => Unit, F[Unit], cats.effect.IO ~> F) => F[Unit] = { - (_, _, _) => throw new Exception - } - }).voidError must completeAs(()) - } - } + val test = for { + first <- IO.uncancelable(_ => markStarted *> IO.never).start + second <- + IO.uncancelable(p => cedeUntilStarted *> markStarted2 *> p(first.cancel)).start + _ <- cedeUntilStarted2 + _ <- second.cancel + } yield () - "finalization" should { + assertNonTerminate(test) + } - "mapping something with a finalizer should complete" in ticked { implicit ticker => - IO.pure(42).onCancel(IO.unit).as(()) must completeAs(()) - } + ticked("cancelation - catch stray exceptions in uncancelable") { implicit ticker => + assertCompleteAs(IO.uncancelable[Unit](_ => throw new RuntimeException).voidError, ()) + } - "run an identity finalizer" in ticked { implicit ticker => - var affected = false + ticked("cancelation - unmask following stray exceptions in uncancelable") { implicit ticker => + assertSelfCancel( + IO.uncancelable[Unit](_ => throw new RuntimeException) + .handleErrorWith(_ => IO.canceled *> IO.never)) + } - IO.unit.guaranteeCase { case _ => IO { affected = true } } must completeAs(()) + ticked("cancelation - catch exceptions in cont") { implicit ticker => + assertCompleteAs( + IO.cont[Unit, Unit](new Cont[IO, Unit, Unit] { + override def apply[F[_]](implicit F: MonadCancel[F, Throwable]) + : (Either[Throwable, Unit] => Unit, F[Unit], cats.effect.IO ~> F) => F[Unit] = { + (_, _, _) => throw new Exception + } + }).voidError, + () + ) + } - affected must beTrue - } + ticked("finalization - mapping something with a finalizer should complete") { + implicit ticker => assertCompleteAs(IO.pure(42).onCancel(IO.unit).as(()), ()) + } - "run an identity finalizer and continue" in ticked { implicit ticker => - var affected = false + ticked("finalization - run an identity finalizer") { implicit ticker => + var affected = false - val seed = IO.unit.guaranteeCase { case _ => IO { affected = true } } + assertCompleteAs(IO.unit.guaranteeCase { case _ => IO { affected = true } }, ()) - seed.as(42) must completeAs(42) + assert(affected) + } - affected must beTrue - } + ticked("finalization - run an identity finalizer and continue") { implicit ticker => + var affected = false - "run multiple nested finalizers on cancel" in ticked { implicit ticker => - var inner = false - var outer = false + val seed = IO.unit.guaranteeCase { case _ => IO { affected = true } } - IO.canceled - .guarantee(IO { inner = true }) - .guarantee(IO { outer = true }) must selfCancel + assertCompleteAs(seed.as(42), 42) - inner must beTrue - outer must beTrue - } + assert(affected) + } - "run multiple nested finalizers on completion exactly once" in ticked { implicit ticker => - var inner = 0 - var outer = 0 + ticked("finalization - run multiple nested finalizers on cancel") { implicit ticker => + var inner = false + var outer = false - IO.unit.guarantee(IO(inner += 1)).guarantee(IO(outer += 1)) must completeAs(()) + assertSelfCancel(IO.canceled.guarantee(IO { inner = true }).guarantee(IO { outer = true })) - inner mustEqual 1 - outer mustEqual 1 - } + assert(inner) + assert(outer) + } - "invoke onCase finalizer when cancelable async returns" in ticked { implicit ticker => - var passed = false + ticked("finalization - run multiple nested finalizers on completion exactly once") { + implicit ticker => + var inner = 0 + var outer = 0 - // convenient proxy for an async that returns a cancelToken - val test = IO.sleep(1.day) guaranteeCase { - case Outcome.Succeeded(_) => IO { passed = true } - case _ => IO.unit - } + assertCompleteAs(IO.unit.guarantee(IO(inner += 1)).guarantee(IO(outer += 1)), ()) - test must completeAs(()) - passed must beTrue - } + assertEquals(inner, 1) + assertEquals(outer, 1) + } - "hold onto errors through multiple finalizers" in ticked { implicit ticker => - case object TestException extends RuntimeException - IO.raiseError(TestException).guarantee(IO.unit).guarantee(IO.unit) must failAs( - TestException) - } + ticked("finalization - invoke onCase finalizer when cancelable async returns") { + implicit ticker => + var passed = false - "cede unit in a finalizer" in ticked { implicit ticker => - val body = IO.sleep(1.second).start.flatMap(_.join).map(_ => 42) - body.guarantee(IO.cede.map(_ => ())) must completeAs(42) + // convenient proxy for an async that returns a cancelToken + val test = IO.sleep(1.day) guaranteeCase { + case Outcome.Succeeded(_) => IO { passed = true } + case _ => IO.unit } - "ensure async callback is suppressed during suspension of async finalizers" in ticked { - implicit ticker => - var cb: Either[Throwable, Unit] => Unit = null + assertCompleteAs(test, ()) + assert(passed) + } + + ticked("finalization - hold onto errors through multiple finalizers") { implicit ticker => + case object TestException extends RuntimeException + assertFailAs( + IO.raiseError(TestException).guarantee(IO.unit).guarantee(IO.unit), + TestException) + } - val subject = IO.async[Unit] { cb0 => - IO { - cb = cb0 + ticked("finalization - cede unit in a finalizer") { implicit ticker => + val body = IO.sleep(1.second).start.flatMap(_.join).map(_ => 42) + assertCompleteAs(body.guarantee(IO.cede.map(_ => ())), 42) + } - Some(IO.never) - } - } + ticked( + "finalization - ensure async callback is suppressed during suspension of async finalizers") { + implicit ticker => + var cb: Either[Throwable, Unit] => Unit = null - val test = for { - f <- subject.start - _ <- IO(ticker.ctx.tick()) // schedule everything - _ <- f.cancel.start - _ <- IO(ticker.ctx.tick()) // get inside the finalizer suspension - _ <- IO(cb(Right(()))) - _ <- IO(ticker.ctx.tick()) // show that the finalizer didn't explode - } yield () + val subject = IO.async[Unit] { cb0 => + IO { + cb = cb0 - test must completeAs(()) // ...but not throw an exception + Some(IO.never) + } } - "run the continuation of an async finalizer within async" in ticked { implicit ticker => - var success = false + val test = for { + f <- subject.start + _ <- IO(ticker.ctx.tick()) // schedule everything + _ <- f.cancel.start + _ <- IO(ticker.ctx.tick()) // get inside the finalizer suspension + _ <- IO(cb(Right(()))) + _ <- IO(ticker.ctx.tick()) // show that the finalizer didn't explode + } yield () - val target = IO.async[Unit] { _ => - val fin = IO.async_[Unit] { cb => ticker.ctx.execute(() => cb(Right(()))) } *> IO { - success = true - } + assertCompleteAs(test, ()) // ...but not throw an exception + } - IO.pure(Some(fin)) - } + ticked("finalization - run the continuation of an async finalizer within async") { + implicit ticker => + var success = false - val test = target.start flatMap { f => IO(ticker.ctx.tick()) *> f.cancel } + val target = IO.async[Unit] { _ => + val fin = IO.async_[Unit] { cb => ticker.ctx.execute(() => cb(Right(()))) } *> IO { + success = true + } - test must completeAs(()) - success must beTrue + IO.pure(Some(fin)) } + val test = target.start flatMap { f => IO(ticker.ctx.tick()) *> f.cancel } + + assertCompleteAs(test, ()) + assert(success) + } + // format: off - "not finalize after uncancelable with suppressed cancelation (succeeded)" in ticked { implicit ticker => + ticked("finalization - not finalize after uncancelable with suppressed cancelation (succeeded)") { implicit ticker => var finalized = false val test = @@ -1514,13 +1579,13 @@ class IOSuite extends BaseSpec with Discipline with IOPlatformSpecification { .onCancel(IO { finalized = true }) .void - test must selfCancel - finalized must beFalse + assertSelfCancel(test ) + assert(!finalized) } // format: on // format: off - "not finalize after uncancelable with suppressed cancelation (errored)" in ticked { implicit ticker => + ticked("finalization - not finalize after uncancelable with suppressed cancelation (errored)") { implicit ticker => case object TestException extends RuntimeException var finalized = false @@ -1530,640 +1595,621 @@ class IOSuite extends BaseSpec with Discipline with IOPlatformSpecification { .onCancel(IO { finalized = true }) .void - test must selfCancel - finalized must beFalse + assertSelfCancel(test ) + assert(!finalized) } // format: on - "finalize on uncaught errors in bracket use clauses" in ticked { implicit ticker => - val test = for { - ref <- Ref[IO].of(false) - _ <- - IO.asyncForIO - .bracketFull[Unit, Unit](_ => IO.unit)(_ => sys.error("borked!")) { - case _ => - ref.set(true) - } - .attempt - flag <- ref.get - } yield flag + ticked("finalization - finalize on uncaught errors in bracket use clauses") { + implicit ticker => + val test = for { + ref <- Ref[IO].of(false) + _ <- + IO.asyncForIO + .bracketFull[Unit, Unit](_ => IO.unit)(_ => sys.error("borked!")) { + case _ => + ref.set(true) + } + .attempt + flag <- ref.get + } yield flag - test must completeAs(true) - } - } + assertCompleteAs(test, true) + } - "stack-safety" should { + ticked("stack-safety - evaluate 10,000 consecutive map continuations") { implicit ticker => + def loop(i: Int): IO[Unit] = + if (i < 10000) + IO.unit.flatMap(_ => loop(i + 1)).map(u => u) + else + IO.unit - "evaluate 10,000 consecutive map continuations" in ticked { implicit ticker => - def loop(i: Int): IO[Unit] = - if (i < 10000) - IO.unit.flatMap(_ => loop(i + 1)).map(u => u) - else - IO.unit + assertCompleteAs(loop(0), ()) + } - loop(0) must completeAs(()) - } + ticked("stack-safety - evaluate 10,000 consecutive handleErrorWith continuations") { + implicit ticker => + def loop(i: Int): IO[Unit] = + if (i < 10000) + IO.unit.flatMap(_ => loop(i + 1)).handleErrorWith(IO.raiseError(_)) + else + IO.unit - "evaluate 10,000 consecutive handleErrorWith continuations" in ticked { implicit ticker => - def loop(i: Int): IO[Unit] = - if (i < 10000) - IO.unit.flatMap(_ => loop(i + 1)).handleErrorWith(IO.raiseError(_)) - else - IO.unit + assertCompleteAs(loop(0), ()) + } + + ticked("stack-safety - evaluate 10,000 consecutive attempt continuations") { + implicit ticker => + var acc: IO[Any] = IO.unit - loop(0) must completeAs(()) + var j = 0 + while (j < 10000) { + acc = acc.attempt + j += 1 } - "evaluate 10,000 consecutive attempt continuations" in ticked { implicit ticker => - var acc: IO[Any] = IO.unit + assertCompleteAs(acc.void, ()) + } - var j = 0 - while (j < 10000) { - acc = acc.attempt - j += 1 - } + real("parTraverseN - throw when n < 1") { + IO.defer { + List.empty[Int].parTraverseN(0)(_.pure[IO]) + }.mustFailWith[IllegalArgumentException] + } - acc.void must completeAs(()) + real("parTraverseN - propagate errors") { + List(1, 2, 3) + .parTraverseN(2) { (n: Int) => + if (n == 2) IO.raiseError(new RuntimeException) else n.pure[IO] } + .mustFailWith[RuntimeException] + } - } + ticked("parTraverseN - be cancelable") { implicit ticker => + val p = for { + f <- List(1, 2, 3).parTraverseN(2)(_ => IO.never).start + _ <- IO.sleep(100.millis) + _ <- f.cancel + } yield true - "parTraverseN" should { + assertCompleteAs(p, true) + } - "throw when n < 1" in real { - IO.defer { - List.empty[Int].parTraverseN(0)(_.pure[IO]) - }.mustFailWith[IllegalArgumentException] - } + real("parTraverseN_ - throw when n < 1") { + IO.defer { + List.empty[Int].parTraverseN_(0)(_.pure[IO]) + }.mustFailWith[IllegalArgumentException] + } - "propagate errors" in real { - List(1, 2, 3) - .parTraverseN(2) { (n: Int) => - if (n == 2) IO.raiseError(new RuntimeException) else n.pure[IO] - } - .mustFailWith[RuntimeException] + real("parTraverseN_ - propagate errors") { + List(1, 2, 3) + .parTraverseN_(2) { (n: Int) => + if (n == 2) IO.raiseError(new RuntimeException) else n.pure[IO] } + .mustFailWith[RuntimeException] + } - "be cancelable" in ticked { implicit ticker => - val p = for { - f <- List(1, 2, 3).parTraverseN(2)(_ => IO.never).start - _ <- IO.sleep(100.millis) - _ <- f.cancel - } yield true - - p must completeAs(true) - } + ticked("parTraverseN_ - be cancelable") { implicit ticker => + val p = for { + f <- List(1, 2, 3).parTraverseN_(2)(_ => IO.never).start + _ <- IO.sleep(100.millis) + _ <- f.cancel + } yield true - } + assertCompleteAs(p, true) + } - "parTraverseN_" should { + real("parallel - run parallel actually in parallel") { + val x = IO.sleep(2.seconds) >> IO.pure(1) + val y = IO.sleep(2.seconds) >> IO.pure(2) - "throw when n < 1" in real { - IO.defer { - List.empty[Int].parTraverseN_(0)(_.pure[IO]) - }.mustFailWith[IllegalArgumentException] + List(x, y).parSequence.timeout(3.seconds).flatMap { res => + IO { + assertEquals(res, List(1, 2)) } + } + } - "propagate errors" in real { - List(1, 2, 3) - .parTraverseN_(2) { (n: Int) => - if (n == 2) IO.raiseError(new RuntimeException) else n.pure[IO] - } - .mustFailWith[RuntimeException] - } + ticked("parallel - short-circuit on error") { implicit ticker => + case object TestException extends RuntimeException - "be cancelable" in ticked { implicit ticker => - val p = for { - f <- List(1, 2, 3).parTraverseN_(2)(_ => IO.never).start - _ <- IO.sleep(100.millis) - _ <- f.cancel - } yield true + assertFailAs( + (IO.never[Unit], IO.raiseError[Unit](TestException)).parTupled.void, + TestException) + assertFailAs( + (IO.raiseError[Unit](TestException), IO.never[Unit]).parTupled.void, + TestException) + } - p must completeAs(true) - } + ticked("parallel - short-circuit on canceled") { implicit ticker => + assertCompleteAs( + (IO.never[Unit], IO.canceled).parTupled.start.flatMap(_.join.map(_.isCanceled)), + true) + assertCompleteAs( + (IO.canceled, IO.never[Unit]).parTupled.start.flatMap(_.join.map(_.isCanceled)), + true) + } + ticked("parallel - run finalizers when canceled") { implicit ticker => + val tsk = IO.ref(0).flatMap { ref => + val t = IO.never[Unit].onCancel(ref.update(_ + 1)) + for { + fib <- (t, t).parTupled.start + _ <- IO { ticker.ctx.tickAll() } + _ <- fib.cancel + c <- ref.get + } yield c } - "parallel" should { - "run parallel actually in parallel" in real { - val x = IO.sleep(2.seconds) >> IO.pure(1) - val y = IO.sleep(2.seconds) >> IO.pure(2) + assertCompleteAs(tsk, 2) + } - List(x, y).parSequence.timeout(3.seconds).flatMap { res => - IO { - res mustEqual List(1, 2) - } - } + ticked( + "parallel - run right side finalizer when canceled (and left side already completed)") { + implicit ticker => + val tsk = IO.ref(0).flatMap { ref => + for { + fib <- (IO.unit, IO.never[Unit].onCancel(ref.update(_ + 1))).parTupled.start + _ <- IO { ticker.ctx.tickAll() } + _ <- fib.cancel + c <- ref.get + } yield c } - "short-circuit on error" in ticked { implicit ticker => - case object TestException extends RuntimeException + assertCompleteAs(tsk, 1) + } - (IO.never[Unit], IO.raiseError[Unit](TestException)).parTupled.void must failAs( - TestException) - (IO.raiseError[Unit](TestException), IO.never[Unit]).parTupled.void must failAs( - TestException) + ticked( + "parallel - run left side finalizer when canceled (and right side already completed)") { + implicit ticker => + val tsk = IO.ref(0).flatMap { ref => + for { + fib <- (IO.never[Unit].onCancel(ref.update(_ + 1)), IO.unit).parTupled.start + _ <- IO { ticker.ctx.tickAll() } + _ <- fib.cancel + c <- ref.get + } yield c } - "short-circuit on canceled" in ticked { implicit ticker => - (IO.never[Unit], IO.canceled) - .parTupled - .start - .flatMap(_.join.map(_.isCanceled)) must completeAs(true) - (IO.canceled, IO.never[Unit]) - .parTupled - .start - .flatMap(_.join.map(_.isCanceled)) must completeAs(true) - } + assertCompleteAs(tsk, 1) + } - "run finalizers when canceled" in ticked { implicit ticker => - val tsk = IO.ref(0).flatMap { ref => - val t = IO.never[Unit].onCancel(ref.update(_ + 1)) - for { - fib <- (t, t).parTupled.start - _ <- IO { ticker.ctx.tickAll() } - _ <- fib.cancel - c <- ref.get - } yield c - } + ticked("parallel - complete if both sides complete") { implicit ticker => + val tsk = ( + IO.sleep(2.seconds).as(20), + IO.sleep(3.seconds).as(22) + ).parTupled.map { case (l, r) => l + r } - tsk must completeAs(2) - } + assertCompleteAs(tsk, 42) + } - "run right side finalizer when canceled (and left side already completed)" in ticked { - implicit ticker => - val tsk = IO.ref(0).flatMap { ref => - for { - fib <- (IO.unit, IO.never[Unit].onCancel(ref.update(_ + 1))).parTupled.start - _ <- IO { ticker.ctx.tickAll() } - _ <- fib.cancel - c <- ref.get - } yield c - } + ticked("parallel - not run forever on chained product") { implicit ticker => + import cats.effect.kernel.Par.ParallelF - tsk must completeAs(1) - } + case object TestException extends RuntimeException - "run left side finalizer when canceled (and right side already completed)" in ticked { - implicit ticker => - val tsk = IO.ref(0).flatMap { ref => - for { - fib <- (IO.never[Unit].onCancel(ref.update(_ + 1)), IO.unit).parTupled.start - _ <- IO { ticker.ctx.tickAll() } - _ <- fib.cancel - c <- ref.get - } yield c - } + val fa: IO[String] = IO.pure("a") + val fb: IO[String] = IO.pure("b") + val fc: IO[Unit] = IO.raiseError[Unit](TestException) + val tsk = + ParallelF.value(ParallelF(fa).product(ParallelF(fb)).product(ParallelF(fc))).void + assertFailAs(tsk, TestException) + } - tsk must completeAs(1) - } + ticked("miscellaneous - round trip non-canceled through s.c.Future") { implicit ticker => + forAll { (ioa: IO[Int]) => + val normalized = ioa.onCancel(IO.never) + normalized eqv IO.fromFuture(IO(normalized.unsafeToFuture())) + } + } + + ticked("miscellaneous - round trip cancelable through s.c.Future") { implicit ticker => + forAll { (ioa: IO[Int]) => + ioa eqv IO + .fromFutureCancelable( + IO(ioa.unsafeToFutureCancelable()).map { + case (fut, fin) => (fut, IO.fromFuture(IO(fin()))) + } + ) + .recoverWith { case _: CancellationException => IO.canceled *> IO.never[Int] } + } + } - "complete if both sides complete" in ticked { implicit ticker => - val tsk = ( - IO.sleep(2.seconds).as(20), - IO.sleep(3.seconds).as(22) - ).parTupled.map { case (l, r) => l + r } + ticked("miscellaneous - canceled through s.c.Future is errored") { implicit ticker => + val test = + IO.fromFuture(IO(IO.canceled.as(-1).unsafeToFuture())).handleError(_ => 42) - tsk must completeAs(42) - } + assertCompleteAs(test, 42) + } - "not run forever on chained product" in ticked { implicit ticker => - import cats.effect.kernel.Par.ParallelF + ticked("miscellaneous - run a synchronous IO") { implicit ticker => + val ioa = IO(1).map(_ + 2) + val test = IO.fromFuture(IO(ioa.unsafeToFuture())) + assertCompleteAs(test, 3) + } - case object TestException extends RuntimeException + ticked("miscellaneous - run an asynchronous IO") { implicit ticker => + val ioa = (IO(1) <* IO.cede).map(_ + 2) + val test = IO.fromFuture(IO(ioa.unsafeToFuture())) + assertCompleteAs(test, 3) + } - val fa: IO[String] = IO.pure("a") - val fb: IO[String] = IO.pure("b") - val fc: IO[Unit] = IO.raiseError[Unit](TestException) - val tsk = - ParallelF.value(ParallelF(fa).product(ParallelF(fb)).product(ParallelF(fc))).void - tsk must failAs(TestException) - } + ticked("miscellaneous - run several IOs back to back") { implicit ticker => + var counter = 0 + val increment = IO { + counter += 1 } - "miscellaneous" should { + val num = 10 - "round trip non-canceled through s.c.Future" in ticked { implicit ticker => - forAll { (ioa: IO[Int]) => - val normalized = ioa.onCancel(IO.never) - normalized eqv IO.fromFuture(IO(normalized.unsafeToFuture())) - } - } + val test = IO.fromFuture(IO(increment.unsafeToFuture())).replicateA(num).void - "round trip cancelable through s.c.Future" in ticked { implicit ticker => - forAll { (ioa: IO[Int]) => - ioa eqv IO - .fromFutureCancelable( - IO(ioa.unsafeToFutureCancelable()).map { - case (fut, fin) => (fut, IO.fromFuture(IO(fin()))) - } - ) - .recoverWith { case _: CancellationException => IO.canceled *> IO.never[Int] } - } - } + assertCompleteAs(test.flatMap(_ => IO(counter)), num) + } - "canceled through s.c.Future is errored" in ticked { implicit ticker => - val test = - IO.fromFuture(IO(IO.canceled.as(-1).unsafeToFuture())).handleError(_ => 42) + ticked("miscellaneous - run multiple IOs in parallel") { implicit ticker => + val num = 10 - test must completeAs(42) - } + val test = for { + latches <- (0 until num).toList.traverse(_ => Deferred[IO, Unit]) + awaitAll = latches.parTraverse_(_.get) - "run a synchronous IO" in ticked { implicit ticker => - val ioa = IO(1).map(_ + 2) - val test = IO.fromFuture(IO(ioa.unsafeToFuture())) - test must completeAs(3) - } + // engineer a deadlock: all subjects must be run in parallel or this will hang + subjects = latches.map(latch => latch.complete(()) >> awaitAll) - "run an asynchronous IO" in ticked { implicit ticker => - val ioa = (IO(1) <* IO.cede).map(_ + 2) - val test = IO.fromFuture(IO(ioa.unsafeToFuture())) - test must completeAs(3) - } + _ <- subjects.parTraverse_(act => IO(act.unsafeRunAndForget())) + } yield () - "run several IOs back to back" in ticked { implicit ticker => - var counter = 0 - val increment = IO { - counter += 1 - } + assertCompleteAs(test, ()) + } + + ticked("miscellaneous - forward cancelation onto the inner action") { implicit ticker => + var canceled = false - val num = 10 + val run = IO { + IO.never.onCancel(IO { canceled = true }).unsafeRunCancelable() + } - val test = IO.fromFuture(IO(increment.unsafeToFuture())).replicateA(num).void + val test = IO.defer { + run.flatMap(ct => IO.sleep(500.millis) >> IO.fromFuture(IO(ct()))) + } - test.flatMap(_ => IO(counter)) must completeAs(num) - } + assertCompleteAs(test.flatMap(_ => IO(canceled)), true) + } - "run multiple IOs in parallel" in ticked { implicit ticker => - val num = 10 + ticked("temporal - sleep for ten seconds") { implicit ticker => + assertCompleteAs(IO.sleep(10.seconds).as(1), 1) + } - val test = for { - latches <- (0 until num).toList.traverse(_ => Deferred[IO, Unit]) - awaitAll = latches.parTraverse_(_.get) + ticked("temporal - sleep for ten seconds and continue") { implicit ticker => + var affected = false + assertCompleteAs(IO.sleep(10.seconds) >> IO { affected = true }, ()) + assert(affected) + } - // engineer a deadlock: all subjects must be run in parallel or this will hang - subjects = latches.map(latch => latch.complete(()) >> awaitAll) + real("temporal - round up negative sleeps") { + IO.sleep(-1.seconds) + } - _ <- subjects.parTraverse_(act => IO(act.unsafeRunAndForget())) - } yield () + real("timeout - succeed") { + val op = IO.pure(true).timeout(100.millis) - test must completeAs(()) + op.flatMap { res => + IO { + assert(res) } + } + } - "forward cancelation onto the inner action" in ticked { implicit ticker => - var canceled = false + real("timeout - cancel a loop") { + val loop = IO.cede.foreverM - val run = IO { - IO.never.onCancel(IO { canceled = true }).unsafeRunCancelable() - } + val op = loop.timeout(5.millis).attempt - val test = IO.defer { - run.flatMap(ct => IO.sleep(500.millis) >> IO.fromFuture(IO(ct()))) + op.flatMap { res => + IO { + res match { + case Left(e) => assert(e.isInstanceOf[TimeoutException]) + case Right(_) => fail("Expected Left, got Right") } - - test.flatMap(_ => IO(canceled)) must completeAs(true) } } + } - "temporal" should { + real("timeout - invoke finalizers on timed out things") { + for { + ref <- Ref[IO].of(false) + _ <- IO.never.onCancel(ref.set(true)).timeoutTo(50.millis, IO.unit) + v <- ref.get + r <- IO(assert(v)) + } yield r + } - "sleep for ten seconds" in ticked { implicit ticker => - IO.sleep(10.seconds).as(1) must completeAs(1) - } + ticked("timeout - non-terminate on an uncancelable fiber") { implicit ticker => + assertNonTerminate(IO.never.uncancelable.timeout(1.second)) + } - "sleep for ten seconds and continue" in ticked { implicit ticker => - var affected = false - (IO.sleep(10.seconds) >> IO { affected = true }) must completeAs(()) - affected must beTrue - } + real("timeout - propagate successful result from a completed effect") { + IO.pure(true).delayBy(50.millis).uncancelable.timeout(10.millis).map { res => assert(res) } + } - "round up negative sleeps" in real { - IO.sleep(-1.seconds).as(ok) + real("timeout - propagate error from a completed effect") { + IO.raiseError(new RuntimeException) + .delayBy(50.millis) + .uncancelable + .timeout(10.millis) + .attempt + .map { + case Left(e) => assert(e.isInstanceOf[RuntimeException]) + case Right(_) => fail("Expected Left, got Right") } + } - "timeout" should { - "succeed" in real { - val op = IO.pure(true).timeout(100.millis) - - op.flatMap { res => - IO { - res must beTrue - } - } - } - - "cancel a loop" in real { - val loop = IO.cede.foreverM - - val op = loop.timeout(5.millis).attempt - - op.flatMap { res => - IO { - res must beLike { case Left(e) => e must haveClass[TimeoutException] } - } - } - } - - "invoke finalizers on timed out things" in real { - for { - ref <- Ref[IO].of(false) - _ <- IO.never.onCancel(ref.set(true)).timeoutTo(50.millis, IO.unit) - v <- ref.get - r <- IO(v must beTrue) - } yield r - } - - "non-terminate on an uncancelable fiber" in ticked { implicit ticker => - IO.never.uncancelable.timeout(1.second) must nonTerminate - } - - "propagate successful result from a completed effect" in real { - IO.pure(true).delayBy(50.millis).uncancelable.timeout(10.millis).map { res => - res must beTrue - } - } + real("timeoutTo - succeed") { + val op = + IO.pure(true).timeoutTo(5.millis, IO.raiseError(new RuntimeException)) - "propagate error from a completed effect" in real { - IO.raiseError(new RuntimeException) - .delayBy(50.millis) - .uncancelable - .timeout(10.millis) - .attempt - .map { res => - res must beLike { case Left(e) => e must haveClass[RuntimeException] } - } - } + op.flatMap { res => + IO { + assert(res) } + } + } - "timeoutTo" should { - "succeed" in real { - val op = - IO.pure(true).timeoutTo(5.millis, IO.raiseError(new RuntimeException)) - - op.flatMap { res => - IO { - res must beTrue - } - } - } - - "use fallback" in real { - val loop = IO.cede.foreverM + real("timeoutTo - use fallback") { + val loop = IO.cede.foreverM - val op = loop.timeoutTo(5.millis, IO.pure(true)) + val op = loop.timeoutTo(5.millis, IO.pure(true)) - op.flatMap { res => - IO { - res must beTrue - } - } - } + op.flatMap { res => + IO { + assert(res) } + } + } - "timeoutAndForget" should { - "terminate on an uncancelable fiber" in real { - IO.never.uncancelable.timeoutAndForget(1.second).attempt flatMap { r => - IO { - r must beLike { case Left(e) => e must haveClass[TimeoutException] } - } - } + real("timeoutAndForget - terminate on an uncancelable fiber") { + IO.never[Unit].uncancelable.timeoutAndForget(1.second).attempt flatMap { r => + IO { + r match { + case Left(e) => assert(e.isInstanceOf[TimeoutException]) + case Right(other) => fail(s"Expected Left, got $other") } } + } + } - "no-op when canceling an expired timer 1" in realWithRuntime { rt => - // this one excercises a timer removed via `TimerHeap#pollFirstIfTriggered` - IO(Promise[Unit]()) - .flatMap { p => - IO(rt.scheduler.sleep(1.nanosecond, () => p.success(()))).flatMap { cancel => - IO.fromFuture(IO(p.future)) *> IO(cancel.run()) - } - } - .as(ok) - } - - "no-op when canceling an expired timer 2" in realWithRuntime { rt => - // this one excercises a timer removed via `TimerHeap#insert` - IO(Promise[Unit]()) - .flatMap { p => - IO(rt.scheduler.sleep(1.nanosecond, () => p.success(()))).flatMap { cancel => - IO.sleep(1.nanosecond) *> IO.fromFuture(IO(p.future)) *> IO(cancel.run()) - } - } - .as(ok) + realWithRuntime("no-op when canceling an expired timer 1") { rt => + // this one excercises a timer removed via `TimerHeap#pollFirstIfTriggered` + IO(Promise[Unit]()).flatMap { p => + IO(rt.scheduler.sleep(1.nanosecond, () => p.success(()))).flatMap { cancel => + IO.fromFuture(IO(p.future)) *> IO(cancel.run()) } + } + } - "no-op when canceling a timer twice" in realWithRuntime { rt => - IO(rt.scheduler.sleep(1.day, () => ())) - .flatMap(cancel => IO(cancel.run()) *> IO(cancel.run())) - .as(ok) + realWithRuntime("no-op when canceling an expired timer 2") { rt => + // this one excercises a timer removed via `TimerHeap#insert` + IO(Promise[Unit]()).flatMap { p => + IO(rt.scheduler.sleep(1.nanosecond, () => p.success(()))).flatMap { cancel => + IO.sleep(1.nanosecond) *> IO.fromFuture(IO(p.future)) *> IO(cancel.run()) } } + } - "syncStep" should { - "run sync IO to completion" in { - var bool = false - - val zero = 0 + realWithRuntime("no-op when canceling a timer twice") { rt => + IO(rt.scheduler.sleep(1.day, () => ())).flatMap(cancel => + IO(cancel.run()) *> IO(cancel.run())) + } - val io = IO.pure(5).flatMap { n => - IO.delay { - 2 - }.map { m => n * m } - .flatMap { result => - IO { - bool = result % 2 == 0 - } - } - .map { _ => n / zero } - .handleErrorWith { t => IO.raiseError(t) } - .attempt - .flatMap { _ => IO.pure(42) } - } + test("syncStep - run sync IO to completion") { + var bool = false - io.syncStep(1024).map { - case Left(_) => throw new RuntimeException("Boom!") - case Right(n) => n - } must completeAsSync(42) + val zero = 0 - bool must beEqualTo(true) - } + val io = IO.pure(5).flatMap { n => + IO.delay { + 2 + }.map { m => n * m } + .flatMap { result => + IO { + bool = result % 2 == 0 + } + } + .map { _ => n / zero } + .handleErrorWith { t => IO.raiseError(t) } + .attempt + .flatMap { _ => IO.pure(42) } + } - "fail synchronously with a throwable" in { - case object TestException extends RuntimeException - val io = IO.raiseError[Unit](TestException) + assertCompleteAsSync( + io.syncStep(1024).map { + case Left(_) => throw new RuntimeException("Boom!") + case Right(n) => n + }, + 42) - io.syncStep(1024).map { - case Left(_) => throw new RuntimeException("Boom!") - case Right(()) => () - } must failAsSync(TestException) - } + assertEquals(bool, true) + } - "evaluate side effects until the first async boundary and nothing else" in ticked { - implicit ticker => - var inDelay = false - var inMap = false - var inAsync = false - var inFlatMap = false + test("syncStep - fail synchronously with a throwable") { + case object TestException extends RuntimeException + val io = IO.raiseError[Unit](TestException) - val io = IO - .delay { - inDelay = true - } - .map { _ => inMap = true } - .flatMap { _ => - IO.async_[Unit] { cb => - inAsync = true - cb(Right(())) - } - } - .flatMap { _ => - IO { - inFlatMap = true - } - } + assertFailAsSync( + io.syncStep(1024).map { + case Left(_) => throw new RuntimeException("Boom!") + case Right(()) => () + }, + TestException) + } - io.syncStep(1024).flatMap { - case Left(remaining) => - SyncIO.delay { - inDelay must beTrue - inMap must beTrue - inAsync must beFalse - inFlatMap must beFalse - - remaining must completeAs(()) - inAsync must beTrue - inFlatMap must beTrue - () - } + ticked("syncStep - evaluate side effects until the first async boundary and nothing else") { + implicit ticker => + var inDelay = false + var inMap = false + var inAsync = false + var inFlatMap = false + + val io = IO + .delay { + inDelay = true + } + .map { _ => inMap = true } + .flatMap { _ => + IO.async_[Unit] { cb => + inAsync = true + cb(Right(())) + } + } + .flatMap { _ => + IO { + inFlatMap = true + } + } - case Right(_) => SyncIO.raiseError[Unit](new RuntimeException("Boom!")) - } must completeAsSync(()) - } + assertCompleteAsSync( + io.syncStep(1024).flatMap { + case Left(remaining) => + SyncIO.delay { + assert(inDelay) + assert(inMap) + assert(!inAsync) + assert(!inFlatMap) - "evaluate up to limit and no further" in { - var first = false - var second = false + assertCompleteAs(remaining, ()) + assert(inAsync) + assert(inFlatMap) + () + } - val program = IO { first = true } *> IO { second = true } + case Right(_) => SyncIO.raiseError[Unit](new RuntimeException("Boom!")) + }, + () + ) + } - val test = program.syncStep(2) flatMap { results => - SyncIO { - first must beTrue - second must beFalse - results must beLeft + test("syncStep - evaluate up to limit and no further") { + var first = false + var second = false - () - } - } + val program = IO { first = true } *> IO { second = true } - test must completeAsSync(()) - } + val test = program.syncStep(2) flatMap { results => + SyncIO { + assert(first) + assert(!second) + assert(results.isLeft) - "should not execute effects twice for map (#2858)" in ticked { implicit ticker => - var i = 0 - val io = (IO(i += 1) *> IO.cede).void.syncStep(Int.MaxValue).unsafeRunSync() match { - case Left(io) => io - case Right(_) => IO.unit - } - io must completeAs(()) - i must beEqualTo(1) + () } + } - "should not execute effects twice for flatMap (#2858)" in ticked { implicit ticker => - var i = 0 - val io = - (IO(i += 1) *> IO.cede *> IO.unit).syncStep(Int.MaxValue).unsafeRunSync() match { - case Left(io) => io - case Right(_) => IO.unit - } - io must completeAs(()) - i must beEqualTo(1) - } + assertCompleteAsSync(test, ()) + } - "should not execute effects twice for attempt (#2858)" in ticked { implicit ticker => - var i = 0 - val io = - (IO(i += 1) *> IO.cede).attempt.void.syncStep(Int.MaxValue).unsafeRunSync() match { - case Left(io) => io - case Right(_) => IO.unit - } - io must completeAs(()) - i must beEqualTo(1) - } + ticked("syncStep - should not execute effects twice for map (#2858)") { implicit ticker => + var i = 0 + val io = (IO(i += 1) *> IO.cede).void.syncStep(Int.MaxValue).unsafeRunSync() match { + case Left(io) => io + case Right(_) => IO.unit + } + assertCompleteAs(io, ()) + assertEquals(i, 1) + } - "should not execute effects twice for handleErrorWith (#2858)" in ticked { - implicit ticker => - var i = 0 - val io = (IO(i += 1) *> IO.cede) - .handleErrorWith(_ => IO.unit) - .syncStep(Int.MaxValue) - .unsafeRunSync() match { - case Left(io) => io - case Right(_) => IO.unit - } - io must completeAs(()) - i must beEqualTo(1) + ticked("syncStep - should not execute effects twice for flatMap (#2858)") { implicit ticker => + var i = 0 + val io = + (IO(i += 1) *> IO.cede *> IO.unit).syncStep(Int.MaxValue).unsafeRunSync() match { + case Left(io) => io + case Right(_) => IO.unit } + assertCompleteAs(io, ()) + assertEquals(i, 1) + } - "handle uncancelable" in { - val sio = IO.unit.uncancelable.syncStep(Int.MaxValue) - sio.map(_.bimap(_ => (), _ => ())) must completeAsSync(Right(())) + ticked("syncStep - should not execute effects twice for attempt (#2858)") { implicit ticker => + var i = 0 + val io = + (IO(i += 1) *> IO.cede).attempt.void.syncStep(Int.MaxValue).unsafeRunSync() match { + case Left(io) => io + case Right(_) => IO.unit } + assertCompleteAs(io, ()) + assertEquals(i, 1) + } - "handle onCancel" in { - val sio = IO.unit.onCancel(IO.unit).syncStep(Int.MaxValue) - sio.map(_.bimap(_ => (), _ => ())) must completeAsSync(Right(())) - } + ticked("syncStep - should not execute effects twice for handleErrorWith (#2858)") { + implicit ticker => + var i = 0 + val io = (IO(i += 1) *> IO.cede) + .handleErrorWith(_ => IO.unit) + .syncStep(Int.MaxValue) + .unsafeRunSync() match { + case Left(io) => io + case Right(_) => IO.unit + } + assertCompleteAs(io, ()) + assertEquals(i, 1) + } - "synchronously allocate a vanilla resource" in { - val sio = - Resource.make(IO.unit)(_ => IO.unit).allocated.map(_._1).syncStep(Int.MaxValue) - sio.map(_.bimap(_ => (), _ => ())) must completeAsSync(Right(())) - } + test("syncStep - handle uncancelable") { + val sio = IO.unit.uncancelable.syncStep(Int.MaxValue) + assertCompleteAsSync(sio.map(_.bimap(_ => (), _ => ())), Right(())) + } - "synchronously allocate a evalMapped resource" in { - val sio = Resource - .make(IO.unit)(_ => IO.unit) - .evalMap(_ => IO.unit) - .allocated - .map(_._1) - .syncStep(Int.MaxValue) - sio.map(_.bimap(_ => (), _ => ())) must completeAsSync(Right(())) - } - } + test("syncStep - handle onCancel") { + val sio = IO.unit.onCancel(IO.unit).syncStep(Int.MaxValue) + assertCompleteAsSync(sio.map(_.bimap(_ => (), _ => ())), Right(())) + } - "fiber repeated yielding test" in real { - def yieldUntil(ref: Ref[IO, Boolean]): IO[Unit] = - ref.get.flatMap(b => if (b) IO.unit else IO.cede *> yieldUntil(ref)) + test("syncStep - synchronously allocate a vanilla resource") { + val sio = + Resource.make(IO.unit)(_ => IO.unit).allocated.map(_._1).syncStep(Int.MaxValue) + assertCompleteAsSync(sio.map(_.bimap(_ => (), _ => ())), Right(())) + } - for { - n <- IO(java.lang.Runtime.getRuntime.availableProcessors) - done <- Ref.of[IO, Boolean](false) - fibers <- List.range(0, n - 1).traverse(_ => yieldUntil(done).start) - _ <- IO.unit.start.replicateA(200) - _ <- done.set(true).start - _ <- IO.unit.start.replicateA(1000) - _ <- yieldUntil(done) - _ <- fibers.traverse(_.join) - res <- IO(ok) - } yield res - } + test("syncStep - synchronously allocate a evalMapped resource") { + val sio = Resource + .make(IO.unit)(_ => IO.unit) + .evalMap(_ => IO.unit) + .allocated + .map(_._1) + .syncStep(Int.MaxValue) + assertCompleteAsSync(sio.map(_.bimap(_ => (), _ => ())), Right(())) + } - "serialize" in { - forAll { (io: IO[Int]) => serializable(io) }( - implicitly, - arbitraryIOWithoutContextShift, - implicitly, - implicitly) - } + real("fiber repeated yielding test") { + def yieldUntil(ref: Ref[IO, Boolean]): IO[Unit] = + ref.get.flatMap(b => if (b) IO.unit else IO.cede *> yieldUntil(ref)) + + for { + n <- IO(java.lang.Runtime.getRuntime.availableProcessors) + done <- Ref.of[IO, Boolean](false) + fibers <- List.range(0, n - 1).traverse(_ => yieldUntil(done).start) + _ <- IO.unit.start.replicateA(200) + _ <- done.set(true).start + _ <- IO.unit.start.replicateA(1000) + _ <- yieldUntil(done) + _ <- fibers.traverse(_.join) + } yield () + } - "produce a specialized version of Deferred" in real { - IO.deferred[Unit].flatMap(d => IO(d must haveClass[IODeferred[_]])) - } + test("serialize") { + forAll { (io: IO[Int]) => serializable(io) }( + implicitly, + arbitraryIOWithoutContextShift, + implicitly, + implicitly) + } - platformSpecs + real("produce a specialized version of Deferred") { + IO.deferred[Unit].flatMap(d => IO(d.isInstanceOf[IODeferred[_]])) } + platformTests() + { implicit val ticker = Ticker() diff --git a/tests/shared/src/test/scala/cats/effect/IorTIOSuite.scala b/tests/shared/src/test/scala/cats/effect/IorTIOSuite.scala index d86a960c8b..fa88b3cb58 100644 --- a/tests/shared/src/test/scala/cats/effect/IorTIOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IorTIOSuite.scala @@ -24,54 +24,50 @@ import cats.laws.discipline.arbitrary._ import cats.syntax.all._ import org.scalacheck.Prop -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class IorTIOSuite extends BaseSpec with Discipline { - - // we just need this because of the laws testing, since the prop runs can interfere with each other - sequential - - "IorT" should { - "execute finalizers" in ticked { implicit ticker => - type F[A] = IorT[IO, String, A] - - val test = for { - gate1 <- Deferred[F, Unit] - gate2 <- Deferred[F, Unit] - gate3 <- Deferred[F, Unit] - _ <- IorT.leftT[IO, Unit]("boom").guarantee(gate1.complete(()).void).start - _ <- IorT.bothT[IO]("boom", ()).guarantee(gate2.complete(()).void).start - _ <- IorT.rightT[IO, String](()).guarantee(gate3.complete(()).void).start - _ <- gate1.get - _ <- gate2.get - _ <- gate3.get - } yield () - - test.value must completeAs(Ior.right(())) - } - - "execute finalizers when doubly nested" in ticked { implicit ticker => - type F[A] = IorT[OptionT[IO, *], String, A] - - val test = for { - gate1 <- Deferred[F, Unit] - gate2 <- Deferred[F, Unit] - gate3 <- Deferred[F, Unit] - gate4 <- Deferred[F, Unit] - _ <- IorT.leftT[OptionT[IO, *], Unit]("boom").guarantee(gate1.complete(()).void).start - _ <- IorT.bothT[OptionT[IO, *]]("boom", ()).guarantee(gate2.complete(()).void).start - _ <- IorT.rightT[OptionT[IO, *], String](()).guarantee(gate3.complete(()).void).start - _ <- IorT.liftF(OptionT.none[IO, Unit]).guarantee(gate4.complete(()).void).start - _ <- gate1.get - _ <- gate2.get - _ <- gate3.get - _ <- gate4.get - } yield () - - test.value.value must completeAs(Some(Ior.right(()))) - } +import munit.DisciplineSuite + +class IorTIOSuite extends BaseSuite with DisciplineSuite { + + ticked("IorT should execute finalizers") { implicit ticker => + type F[A] = IorT[IO, String, A] + + val test = for { + gate1 <- Deferred[F, Unit] + gate2 <- Deferred[F, Unit] + gate3 <- Deferred[F, Unit] + _ <- IorT.leftT[IO, Unit]("boom").guarantee(gate1.complete(()).void).start + _ <- IorT.bothT[IO]("boom", ()).guarantee(gate2.complete(()).void).start + _ <- IorT.rightT[IO, String](()).guarantee(gate3.complete(()).void).start + _ <- gate1.get + _ <- gate2.get + _ <- gate3.get + } yield () + + assertCompleteAs(test.value, Ior.right(())) + } + + ticked("IorT should execute finalizers when doubly nested") { implicit ticker => + type F[A] = IorT[OptionT[IO, *], String, A] + + val test = for { + gate1 <- Deferred[F, Unit] + gate2 <- Deferred[F, Unit] + gate3 <- Deferred[F, Unit] + gate4 <- Deferred[F, Unit] + _ <- IorT.leftT[OptionT[IO, *], Unit]("boom").guarantee(gate1.complete(()).void).start + _ <- IorT.bothT[OptionT[IO, *]]("boom", ()).guarantee(gate2.complete(()).void).start + _ <- IorT.rightT[OptionT[IO, *], String](()).guarantee(gate3.complete(()).void).start + _ <- IorT.liftF(OptionT.none[IO, Unit]).guarantee(gate4.complete(()).void).start + _ <- gate1.get + _ <- gate2.get + _ <- gate3.get + _ <- gate4.get + } yield () + + assertCompleteAs(test.value.value, Some(Ior.right(()))) } implicit def ordIorTIOFD(implicit ticker: Ticker): Order[IorT[IO, Int, FiniteDuration]] = diff --git a/tests/shared/src/test/scala/cats/effect/KleisliIOSuite.scala b/tests/shared/src/test/scala/cats/effect/KleisliIOSuite.scala index f519106a55..785451167b 100644 --- a/tests/shared/src/test/scala/cats/effect/KleisliIOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/KleisliIOSuite.scala @@ -25,71 +25,66 @@ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import cats.syntax.all._ -import org.scalacheck.{Cogen, Prop} -import org.specs2.scalacheck.Parameters -import org.typelevel.discipline.specs2.mutable.Discipline +import org.scalacheck.{Cogen, Prop, Test} import scala.concurrent.duration._ -class KleisliIOSuite extends BaseSpec with Discipline { +import munit.DisciplineSuite - // we just need this because of the laws testing, since the prop runs can interfere with each other - sequential +class KleisliIOSuite extends BaseSuite with DisciplineSuite { - "Kleisli[IO, R, *]" >> { - "should be stack safe in long traverse chains" in ticked { implicit ticker => - val N = 10000 + ticked("should be stack safe in long traverse chains") { implicit ticker => + val N = 10000 - val test = for { - ref <- Ref[IO].of(0) - _ <- List.fill(N)(0).traverse_(_ => Kleisli.liftF(ref.update(_ + 1))).run("Go...") - v <- ref.get - } yield v + val test = for { + ref <- Ref[IO].of(0) + _ <- List.fill(N)(0).traverse_(_ => Kleisli.liftF(ref.update(_ + 1))).run("Go...") + v <- ref.get + } yield v - test must completeAs(N) - } + assertCompleteAs(test, N) + } - "should be stack safe in long parTraverse chains" in ticked { implicit ticker => - val N = 10000 + ticked("should be stack safe in long parTraverse chains") { implicit ticker => + val N = 10000 - val test = for { - ref <- Ref[IO].of(0) - _ <- List.fill(N)(0).parTraverse_(_ => Kleisli.liftF(ref.update(_ + 1))).run("Go...") - v <- ref.get - } yield v + val test = for { + ref <- Ref[IO].of(0) + _ <- List.fill(N)(0).parTraverse_(_ => Kleisli.liftF(ref.update(_ + 1))).run("Go...") + v <- ref.get + } yield v - test must completeAs(N) - } + assertCompleteAs(test, N) + } - "execute finalizers" in ticked { implicit ticker => - type F[A] = Kleisli[IO, String, A] + ticked("execute finalizers") { implicit ticker => + type F[A] = Kleisli[IO, String, A] - val test = for { - gate <- Deferred[F, Unit] - _ <- Kleisli.ask[IO, String].guarantee(gate.complete(()).void).start - _ <- gate.get - } yield () + val test = for { + gate <- Deferred[F, Unit] + _ <- Kleisli.ask[IO, String].guarantee(gate.complete(()).void).start + _ <- gate.get + } yield () - test.run("kleisli") must completeAs(()) - } + assertCompleteAs(test.run("kleisli"), ()) + } - "execute finalizers when doubly nested" in ticked { implicit ticker => - type F[A] = Kleisli[OptionT[IO, *], String, A] - - val test = for { - gate1 <- Deferred[F, Unit] - gate2 <- Deferred[F, Unit] - _ <- Kleisli.ask[OptionT[IO, *], String].guarantee(gate1.complete(()).void).start - _ <- Kleisli - .liftF[OptionT[IO, *], String, Unit](OptionT.none[IO, Unit]) - .guarantee(gate2.complete(()).void) - .start - _ <- gate1.get - _ <- gate2.get - } yield () - - test.run("kleisli").value must completeAs(Some(())) - } + ticked("execute finalizers when doubly nested") { implicit ticker => + type F[A] = Kleisli[OptionT[IO, *], String, A] + + val test = for { + gate1 <- Deferred[F, Unit] + gate2 <- Deferred[F, Unit] + _ <- Kleisli.ask[OptionT[IO, *], String].guarantee(gate1.complete(()).void).start + _ <- Kleisli + .liftF[OptionT[IO, *], String, Unit](OptionT.none[IO, Unit]) + .guarantee(gate2.complete(()).void) + .start + _ <- gate1.get + _ <- gate2.get + } yield () + + assertCompleteAs(test.run("kleisli").value, Some(())) } implicit def kleisliEq[F[_], A, B](implicit ev: Eq[A => F[B]]): Eq[Kleisli[F, A, B]] = @@ -114,13 +109,16 @@ class KleisliIOSuite extends BaseSpec with Discipline { implicit F: Cogen[A => F[B]]): Cogen[Kleisli[F, A, B]] = F.contramap(_.run) + override protected def scalaCheckTestParameters: Test.Parameters = + super.scalaCheckTestParameters.withMinSuccessfulTests(25) + { implicit val ticker = Ticker() checkAll( "Kleisli[IO]", AsyncTests[Kleisli[IO, MiniInt, *]].async[Int, Int, Int](10.millis) - )(Parameters(minTestsOk = 25)) + ) } } diff --git a/tests/shared/src/test/scala/cats/effect/MemoizeSuite.scala b/tests/shared/src/test/scala/cats/effect/MemoizeSuite.scala index 77847d1f57..1fec65b9d6 100644 --- a/tests/shared/src/test/scala/cats/effect/MemoizeSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/MemoizeSuite.scala @@ -24,23 +24,23 @@ import cats.syntax.all._ import cats.~> import org.scalacheck.Prop -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.TimeoutException import scala.concurrent.duration._ import scala.util.Success -import Prop.forAll +import munit.DisciplineSuite -class MemoizeSuite extends BaseSpec with Discipline { +import Prop.forAll - sequential +class MemoizeSuite extends BaseSuite with DisciplineSuite { - def tests[F[_]: Concurrent: LiftIO](lowerK: F ~> IO) = { + def tests[F[_]: Concurrent: LiftIO](name: String, lowerK: F ~> IO) = { val liftK = LiftIO.liftK - "Concurrent.memoize does not evaluate the effect if the inner `F[A]` isn't bound" in ticked { + ticked( + s"$name Concurrent.memoize does not evaluate the effect if the inner `F[A]` isn't bound") { implicit ticker => import cats.syntax.all._ @@ -54,10 +54,10 @@ class MemoizeSuite extends BaseSpec with Discipline { val result = lowerK(op).unsafeToFuture() ticker.ctx.tick() - result.value mustEqual Some(Success(0)) + assertEquals(result.value, Some(Success(0))) } - "Concurrent.memoize evaluates effect once if inner `F[A]` is bound twice" in ticked { + ticked(s"$name Concurrent.memoize evaluates effect once if inner `F[A]` is bound twice") { implicit ticker => import cats.syntax.all._ @@ -76,10 +76,11 @@ class MemoizeSuite extends BaseSpec with Discipline { val result = lowerK(op).unsafeToFuture() ticker.ctx.tick() - result.value mustEqual Some(Success((1, 1, 1))) + assertEquals(result.value, Some(Success((1, 1, 1)))) } - "Concurrent.memoize effect evaluates effect once if the inner `F[A]` is bound twice (race)" in ticked { + ticked( + s"$name Concurrent.memoize effect evaluates effect once if the inner `F[A]` is bound twice (race)") { implicit ticker => import cats.syntax.all._ @@ -99,20 +100,21 @@ class MemoizeSuite extends BaseSpec with Discipline { val result = lowerK(op).unsafeToFuture() ticker.ctx.tick() - result.value mustEqual Some(Success((1, 1))) + assertEquals(result.value, Some(Success((1, 1)))) } - "Concurrent.memoize and then flatten is identity" in ticked { implicit ticker => + ticked(s"$name Concurrent.memoize and then flatten is identity") { implicit ticker => forAll { (fa: IO[Int]) => lowerK(Concurrent[F].memoize(liftK(fa)).flatten) eqv fa } } - "Concurrent.memoize uncancelable canceled and then flatten is identity" in ticked { + ticked(s"$name Concurrent.memoize uncancelable canceled and then flatten is identity") { implicit ticker => val fa = Concurrent[F].uncancelable(_ => Concurrent[F].canceled) lowerK(Concurrent[F].memoize(fa).flatten) eqv lowerK(fa) } - "Memoized effects can be canceled when there are no other active subscribers (1)" in ticked { + ticked( + s"$name Memoized effects can be canceled when there are no other active subscribers (1)") { implicit ticker => import cats.syntax.all._ @@ -130,10 +132,11 @@ class MemoizeSuite extends BaseSpec with Discipline { val result = lowerK(op).unsafeToFuture() ticker.ctx.tickAll() - result.value mustEqual Some(Success(false)) + assertEquals(result.value, Some(Success(false))) } - "Memoized effects can be canceled when there are no other active subscribers (2)" in ticked { + ticked( + s"$name Memoized effects can be canceled when there are no other active subscribers (2)") { implicit ticker => import cats.syntax.all._ @@ -154,10 +157,11 @@ class MemoizeSuite extends BaseSpec with Discipline { val result = lowerK(op).unsafeToFuture() ticker.ctx.tickAll() - result.value mustEqual Some(Success(false)) + assertEquals(result.value, Some(Success(false))) } - "Memoized effects can be canceled when there are no other active subscribers (3)" in ticked { + ticked( + s"$name Memoized effects can be canceled when there are no other active subscribers (3)") { implicit ticker => import cats.syntax.all._ @@ -178,10 +182,10 @@ class MemoizeSuite extends BaseSpec with Discipline { val result = lowerK(op).unsafeToFuture() ticker.ctx.tickAll() - result.value mustEqual Some(Success(false)) + assertEquals(result.value, Some(Success(false))) } - "Running a memoized effect after it was previously canceled reruns it" in ticked { + ticked(s"$name Running a memoized effect after it was previously canceled reruns it") { implicit ticker => import cats.syntax.all._ @@ -204,10 +208,10 @@ class MemoizeSuite extends BaseSpec with Discipline { val result = lowerK(op).unsafeToFuture() ticker.ctx.tickAll() - result.value mustEqual Some(Success((2, 1))) + assertEquals(result.value, Some(Success((2, 1)))) } - "Attempting to cancel a memoized effect with active subscribers is a no-op" in ticked { + ticked(s"$name Attempting to cancel a memoized effect with active subscribers is a no-op") { implicit ticker => import cats.syntax.all._ @@ -236,159 +240,164 @@ class MemoizeSuite extends BaseSpec with Discipline { val result = lowerK(op).unsafeToFuture() ticker.ctx.tickAll() - result.value mustEqual Some(Success(true)) + assertEquals(result.value, Some(Success(true))) } - "External cancelation does not affect subsequent access" in ticked { implicit ticker => - IO.ref(0).flatMap { counter => - val go = counter.getAndUpdate(_ + 1) <* IO.sleep(2.seconds) - go.memoize.flatMap { memo => - memo.parReplicateA_(10).timeoutTo(1.second, IO.unit) *> memo - } - } must completeAs(1) + ticked(s"$name External cancelation does not affect subsequent access") { implicit ticker => + assertCompleteAs( + IO.ref(0).flatMap { counter => + val go = counter.getAndUpdate(_ + 1) <* IO.sleep(2.seconds) + go.memoize.flatMap { memo => + memo.parReplicateA_(10).timeoutTo(1.second, IO.unit) *> memo + } + }, + 1 + ) } } - "Concurrent.memoize" >> { + tests[IO]("Concurrent.memoize >> IO", FunctionK.id) - "IO" >> tests[IO](FunctionK.id) - - "Resource[IO, *]" >> tests[Resource[IO, *]](new ~>[Resource[IO, *], IO] { + tests[Resource[IO, *]]( + "Concurrent.memoize >> Resource[IO, *]", + new ~>[Resource[IO, *], IO] { def apply[A](ra: Resource[IO, A]): IO[A] = ra.use(IO.pure) }) - "Monad transformers" >> { - - "OptionT" in ticked { implicit ticker => - val op = for { - counter <- IO.ref(0) - incr = counter.update(_ + 1) - optMemoOpt <- Concurrent[OptionT[IO, *]] - .memoize[Int]( - OptionT.liftF(incr) *> OptionT.none[IO, Int] - ) - .value - memoOpt <- optMemoOpt.fold(IO.raiseError[OptionT[IO, Int]](new Exception))(IO.pure(_)) - opt1 <- memoOpt.value - opt2 <- memoOpt.value - vOpt <- counter.get - } yield (opt1: Option[Int], opt2: Option[Int], vOpt: Int) - - val result = op.unsafeToFuture() - ticker.ctx.tickAll() - - result.value mustEqual Some(Success((None, None, 1))) - } + ticked("Monad transformers - OptionT") { implicit ticker => + val op = for { + counter <- IO.ref(0) + incr = counter.update(_ + 1) + optMemoOpt <- Concurrent[OptionT[IO, *]] + .memoize[Int]( + OptionT.liftF(incr) *> OptionT.none[IO, Int] + ) + .value + memoOpt <- optMemoOpt.fold(IO.raiseError[OptionT[IO, Int]](new Exception))(IO.pure(_)) + opt1 <- memoOpt.value + opt2 <- memoOpt.value + vOpt <- counter.get + } yield (opt1: Option[Int], opt2: Option[Int], vOpt: Int) - "EitherT" in ticked { implicit ticker => - val op = for { - counter <- IO.ref(0) - incr = counter.update(_ + 1) - eitMemoEit <- Concurrent[EitherT[IO, String, *]] - .memoize[Int]( - EitherT.liftF[IO, String, Unit](incr) *> EitherT.left(IO.pure("x")) - ) - .value - memoEit <- eitMemoEit.fold(_ => IO.raiseError(new Exception), IO.pure(_)) - eit1 <- memoEit.value - eit2 <- memoEit.value - vEit <- counter.get - } yield (eit1: Either[String, Int], eit2: Either[String, Int], vEit: Int) - - val result = op.unsafeToFuture() - ticker.ctx.tickAll() + val result = op.unsafeToFuture() + ticker.ctx.tickAll() - result.value mustEqual Some(Success((Left("x"), Left("x"), 1))) - } + assertEquals(result.value, Some(Success((None, None, 1)))) + } - "IorT" in ticked { implicit ticker => - val op = for { - counter <- IO.ref(0) - incr = counter.update(_ + 1) - // left: - iorMemoIor1 <- Concurrent[IorT[IO, String, *]] - .memoize[Int]( - IorT.liftF[IO, String, Unit](incr) *> IorT.left[Int](IO.pure("x")) - ) - .value - memoIor1 <- iorMemoIor1.fold( - _ => IO.raiseError[IorT[IO, String, Int]](new Exception), - IO.pure(_), - (_, _) => IO.raiseError(new Exception)) - ior1 <- memoIor1.value - ior2 <- memoIor1.value - vIor1 <- counter.get - // both: - iorMemoIor2 <- Concurrent[IorT[IO, String, *]] - .memoize[Int]( - IorT.liftF[IO, String, Unit](incr) *> IorT - .both[IO, String, Int](IO.pure("x"), IO.pure(42)) - ) - .value - memoIor2 <- iorMemoIor2.fold( - _ => IO.raiseError[IorT[IO, String, Int]](new Exception), - IO.pure(_), - (_, _) => IO.raiseError(new Exception)) - ior3 <- memoIor2.value - ior4 <- memoIor2.value - vIor2 <- counter.get - } yield ( - ior1: Ior[String, Int], - ior2: Ior[String, Int], - vIor1: Int, - ior3: Ior[String, Int], - ior4: Ior[String, Int], - vIor2: Int + ticked("Monad transformers - EitherT") { implicit ticker => + val op = for { + counter <- IO.ref(0) + incr = counter.update(_ + 1) + eitMemoEit <- Concurrent[EitherT[IO, String, *]] + .memoize[Int]( + EitherT.liftF[IO, String, Unit](incr) *> EitherT.left(IO.pure("x")) ) + .value + memoEit <- eitMemoEit.fold(_ => IO.raiseError(new Exception), IO.pure(_)) + eit1 <- memoEit.value + eit2 <- memoEit.value + vEit <- counter.get + } yield (eit1: Either[String, Int], eit2: Either[String, Int], vEit: Int) - val result = op.unsafeToFuture() - ticker.ctx.tickAll() + val result = op.unsafeToFuture() + ticker.ctx.tickAll() - result.value mustEqual Some( - Success( - ( - Ior.left("x"), - Ior.left("x"), - 1, - Ior.both("x", 42), - Ior.both("x", 42), - 2 - ))) - } - - "WriterT" in ticked { implicit ticker => - val op = for { - counter <- IO.ref(0) - incr = counter.update(_ + 1) - wriMemoWri <- Concurrent[WriterT[IO, List[String], *]] - .memoize[Int]( - WriterT.liftF[IO, List[String], Unit](incr) *> WriterT(IO.pure((List("x"), 42))) - ) - .run - (log, memoWri) = wriMemoWri - _ <- if (log.nonEmpty) IO.raiseError(new Exception) else IO.unit - wri1 <- memoWri.run - wri2 <- memoWri.run - vWri <- counter.get - } yield ( - wri1: (List[String], Int), - wri2: (List[String], Int), - vWri: Int - ) + assertEquals(result.value, Some(Success((Left("x"), Left("x"), 1)))) + } - val result = op.unsafeToFuture() - ticker.ctx.tickAll() + ticked(s"Monad transformers - IorT") { implicit ticker => + val op = for { + counter <- IO.ref(0) + incr = counter.update(_ + 1) + // left: + iorMemoIor1 <- Concurrent[IorT[IO, String, *]] + .memoize[Int]( + IorT.liftF[IO, String, Unit](incr) *> IorT.left[Int](IO.pure("x")) + ) + .value + memoIor1 <- iorMemoIor1.fold( + _ => IO.raiseError[IorT[IO, String, Int]](new Exception), + IO.pure(_), + (_, _) => IO.raiseError(new Exception)) + ior1 <- memoIor1.value + ior2 <- memoIor1.value + vIor1 <- counter.get + // both: + iorMemoIor2 <- Concurrent[IorT[IO, String, *]] + .memoize[Int]( + IorT.liftF[IO, String, Unit](incr) *> IorT + .both[IO, String, Int](IO.pure("x"), IO.pure(42)) + ) + .value + memoIor2 <- iorMemoIor2.fold( + _ => IO.raiseError[IorT[IO, String, Int]](new Exception), + IO.pure(_), + (_, _) => IO.raiseError(new Exception)) + ior3 <- memoIor2.value + ior4 <- memoIor2.value + vIor2 <- counter.get + } yield ( + ior1: Ior[String, Int], + ior2: Ior[String, Int], + vIor1: Int, + ior3: Ior[String, Int], + ior4: Ior[String, Int], + vIor2: Int + ) + + val result = op.unsafeToFuture() + ticker.ctx.tickAll() + + assertEquals( + result.value, + Some( + Success( + ( + Ior.left("x"), + Ior.left("x"), + 1, + Ior.both("x", 42), + Ior.both("x", 42), + 2 + ))) + ) + } - result.value mustEqual Some( - Success( - ( - (List("x"), 42), - (List("x"), 42), - 1 - ))) - } - } + ticked(s"Monad transformers - WriterT") { implicit ticker => + val op = for { + counter <- IO.ref(0) + incr = counter.update(_ + 1) + wriMemoWri <- Concurrent[WriterT[IO, List[String], *]] + .memoize[Int]( + WriterT.liftF[IO, List[String], Unit](incr) *> WriterT(IO.pure((List("x"), 42))) + ) + .run + (log, memoWri) = wriMemoWri + _ <- if (log.nonEmpty) IO.raiseError(new Exception) else IO.unit + wri1 <- memoWri.run + wri2 <- memoWri.run + vWri <- counter.get + } yield ( + wri1: (List[String], Int), + wri2: (List[String], Int), + vWri: Int + ) + + val result = op.unsafeToFuture() + ticker.ctx.tickAll() + + assertEquals( + result.value, + Some( + Success( + ( + (List("x"), 42), + (List("x"), 42), + 1 + ))) + ) } } diff --git a/tests/shared/src/test/scala/cats/effect/OptionTIOSuite.scala b/tests/shared/src/test/scala/cats/effect/OptionTIOSuite.scala index 18eaa1c9ad..89ec546e2f 100644 --- a/tests/shared/src/test/scala/cats/effect/OptionTIOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/OptionTIOSuite.scala @@ -24,48 +24,44 @@ import cats.laws.discipline.arbitrary._ import cats.syntax.all._ import org.scalacheck.Prop -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class OptionTIOSuite extends BaseSpec with Discipline { - - // we just need this because of the laws testing, since the prop runs can interfere with each other - sequential - - "OptionT" should { - "execute finalizers" in ticked { implicit ticker => - type F[A] = OptionT[IO, A] - - val test = for { - gate1 <- Deferred[F, Unit] - gate2 <- Deferred[F, Unit] - _ <- OptionT.none[IO, Unit].guarantee(gate1.complete(()).void).start - _ <- OptionT.some[IO](()).guarantee(gate2.complete(()).void).start - _ <- gate1.get - _ <- gate2.get - } yield () - - test.value must completeAs(Some(())) - } - - "execute finalizers when doubly nested" in ticked { implicit ticker => - type F[A] = OptionT[OptionT[IO, *], A] - - val test = for { - gate1 <- Deferred[F, Unit] - gate2 <- Deferred[F, Unit] - gate3 <- Deferred[F, Unit] - _ <- OptionT.none[OptionT[IO, *], Unit].guarantee(gate1.complete(()).void).start - _ <- OptionT.some[OptionT[IO, *]](()).guarantee(gate2.complete(()).void).start - _ <- OptionT.liftF(OptionT.none[IO, Unit]).guarantee(gate3.complete(()).void).start - _ <- gate1.get - _ <- gate2.get - _ <- gate3.get - } yield () - - test.value.value must completeAs(Some(Some(()))) - } +import munit.DisciplineSuite + +class OptionTIOSuite extends BaseSuite with DisciplineSuite { + + ticked("execute finalizers") { implicit ticker => + type F[A] = OptionT[IO, A] + + val test = for { + gate1 <- Deferred[F, Unit] + gate2 <- Deferred[F, Unit] + _ <- OptionT.none[IO, Unit].guarantee(gate1.complete(()).void).start + _ <- OptionT.some[IO](()).guarantee(gate2.complete(()).void).start + _ <- gate1.get + _ <- gate2.get + } yield () + + assertCompleteAs(test.value, Some(())) + } + + ticked("execute finalizers when doubly nested") { implicit ticker => + type F[A] = OptionT[OptionT[IO, *], A] + + val test = for { + gate1 <- Deferred[F, Unit] + gate2 <- Deferred[F, Unit] + gate3 <- Deferred[F, Unit] + _ <- OptionT.none[OptionT[IO, *], Unit].guarantee(gate1.complete(()).void).start + _ <- OptionT.some[OptionT[IO, *]](()).guarantee(gate2.complete(()).void).start + _ <- OptionT.liftF(OptionT.none[IO, Unit]).guarantee(gate3.complete(()).void).start + _ <- gate1.get + _ <- gate2.get + _ <- gate3.get + } yield () + + assertCompleteAs(test.value.value, Some(Some(()))) } implicit def ordOptionTIOFD(implicit ticker: Ticker): Order[OptionT[IO, FiniteDuration]] = diff --git a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala index 0c1cec6af8..f423d727be 100644 --- a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala @@ -27,68 +27,78 @@ import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.syntax.all._ -import org.scalacheck.Cogen +import org.scalacheck.{Cogen, Prop} import org.scalacheck.Prop.forAll -import org.specs2.ScalaCheck -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.ExecutionContext import scala.concurrent.duration._ import java.util.concurrent.atomic.AtomicBoolean -class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { - // We need this for testing laws: prop runs can interfere with each other - sequential +import munit.DisciplineSuite - "Resource[IO, *]" >> { - "releases resources in reverse order of acquisition" in ticked { implicit ticker => +class ResourceSuite extends BaseSuite with DisciplineSuite { + + ticked("Resource[IO, *] - releases resources in reverse order of acquisition") { + implicit ticker => forAll { (as: List[(Int, Either[Throwable, Unit])]) => var released: List[Int] = Nil val r = as.traverse { case (a, e) => Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) } - r.use_.attempt.void must completeAs(()) - released mustEqual as.map(_._1) + assertCompleteAs(r.use_.attempt.void, ()) + assertEquals(released, as.map(_._1)) } - } + } - "makes acquires non interruptible" in ticked { implicit ticker => + ticked("Resource[IO, *] - makes acquires non interruptible") { implicit ticker => + assertCompleteAs( IO.ref(false).flatMap { interrupted => val fa = IO.sleep(5.seconds).onCancel(interrupted.set(true)) Resource.make(fa)(_ => IO.unit).use_.timeout(1.second).attempt >> interrupted.get - } must completeAs(false) - } + }, + false + ) + } - "makes acquires non interruptible, overriding uncancelable" in ticked { implicit ticker => - IO.ref(false).flatMap { interrupted => - val fa = IO.uncancelable { poll => - poll(IO.sleep(5.seconds)).onCancel(interrupted.set(true)) - } + ticked("Resource[IO, *] - makes acquires non interruptible, overriding uncancelable") { + implicit ticker => + assertCompleteAs( + IO.ref(false).flatMap { interrupted => + val fa = IO.uncancelable { poll => + poll(IO.sleep(5.seconds)).onCancel(interrupted.set(true)) + } - Resource.make(fa)(_ => IO.unit).use_.timeout(1.second).attempt >> interrupted.get - } must completeAs(false) - } + Resource.make(fa)(_ => IO.unit).use_.timeout(1.second).attempt >> interrupted.get + }, + false + ) + } - "releases resource if interruption happens during use" in ticked { implicit ticker => + ticked("Resource[IO, *] - releases resource if interruption happens during use") { + implicit ticker => val flag = IO.ref(false) - (flag, flag).tupled.flatMap { - case (acquireFin, resourceFin) => - val action = IO.sleep(1.second).onCancel(acquireFin.set(true)) - val fin = resourceFin.set(true) - val res = Resource.makeFull[IO, Unit](poll => poll(action))(_ => fin) - - res.surround(IO.sleep(5.seconds)).timeout(3.seconds).attempt >> - (acquireFin.get, resourceFin.get).tupled - } must completeAs(false -> true) - } + assertCompleteAs( + (flag, flag).tupled.flatMap { + case (acquireFin, resourceFin) => + val action = IO.sleep(1.second).onCancel(acquireFin.set(true)) + val fin = resourceFin.set(true) + val res = Resource.makeFull[IO, Unit](poll => poll(action))(_ => fin) + + res.surround(IO.sleep(5.seconds)).timeout(3.seconds).attempt >> + (acquireFin.get, resourceFin.get).tupled + }, + false -> true + ) + } - "supports interruptible acquires" in ticked { implicit ticker => - val flag = IO.ref(false) + ticked("Resource[IO, *] - supports interruptible acquires") { implicit ticker => + val flag = IO.ref(false) + assertCompleteAs( (flag, flag).tupled.flatMap { case (acquireFin, resourceFin) => val action = IO.sleep(5.seconds).onCancel(acquireFin.set(true)) @@ -97,137 +107,149 @@ class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { res.use_.timeout(1.second).attempt >> (acquireFin.get, resourceFin.get).tupled - } must completeAs(true -> false) - } + }, + true -> false + ) + } - "supports interruptible acquires, respecting uncancelable" in ticked { implicit ticker => + ticked("Resource[IO, *] - supports interruptible acquires, respecting uncancelable") { + implicit ticker => val flag = IO.ref(false) val sleep = IO.sleep(1.second) val timeout = 500.millis - (flag, flag, flag, flag).tupled.flatMap { - case (acquireFin, resourceFin, a, b) => - val io = IO.uncancelable { poll => - sleep.onCancel(a.set(true)) >> poll(sleep).onCancel(b.set(true)) - } + assertCompleteAs( + (flag, flag, flag, flag).tupled.flatMap { + case (acquireFin, resourceFin, a, b) => + val io = IO.uncancelable { poll => + sleep.onCancel(a.set(true)) >> poll(sleep).onCancel(b.set(true)) + } - val resource = Resource.makeFull[IO, Unit] { poll => - poll(io).onCancel(acquireFin.set(true)) - }(_ => resourceFin.set(true)) + val resource = Resource.makeFull[IO, Unit] { poll => + poll(io).onCancel(acquireFin.set(true)) + }(_ => resourceFin.set(true)) - resource.use_.timeout(timeout).attempt >> - List(a.get, b.get, acquireFin.get, resourceFin.get).sequence - } must completeAs(List(false, true, true, false)) - } + resource.use_.timeout(timeout).attempt >> + List(a.get, b.get, acquireFin.get, resourceFin.get).sequence + }, + List(false, true, true, false) + ) + } - "release is always uninterruptible" in ticked { implicit ticker => - val flag = IO.ref(false) - val sleep = IO.sleep(1.second) - val timeout = 500.millis + ticked("Resource[IO, *] - release is always uninterruptible") { implicit ticker => + val flag = IO.ref(false) + val sleep = IO.sleep(1.second) + val timeout = 500.millis + assertCompleteAs( flag.flatMap { releaseComplete => val release = sleep >> releaseComplete.set(true) val resource = Resource.applyFull[IO, Unit] { poll => IO(() -> (_ => poll(release))) } resource.use_.timeout(timeout).attempt >> releaseComplete.get - } must completeAs(true) - } + }, + true + ) + } - "eval" in ticked { implicit ticker => - forAll { (fa: IO[String]) => Resource.eval(fa).use(IO.pure) eqv fa } - } + ticked("Resource[IO, *] - eval") { implicit ticker => + forAll { (fa: IO[String]) => Resource.eval(fa).use(IO.pure) eqv fa } + } - "eval - interruption" in ticked { implicit ticker => - def resource(d: Deferred[IO, Int]): Resource[IO, Unit] = - for { - _ <- Resource.make(IO.unit)(_ => d.complete(1).void) - _ <- Resource.eval(IO.never[Unit]) - } yield () - - def p = - for { - d <- Deferred[IO, Int] - r = resource(d).use_ - fiber <- r.start - _ <- IO.sleep(200.millis) - _ <- fiber.cancel - res <- d.get - } yield res - - p must completeAs(1) - } + ticked("Resource[IO, *] - eval - interruption") { implicit ticker => + def resource(d: Deferred[IO, Int]): Resource[IO, Unit] = + for { + _ <- Resource.make(IO.unit)(_ => d.complete(1).void) + _ <- Resource.eval(IO.never[Unit]) + } yield () - "eval(fa) <-> liftK.apply(fa)" in ticked { implicit ticker => - forAll { (fa: IO[String], f: String => IO[Int]) => - Resource.eval(fa).use(f) eqv Resource.liftK[IO].apply(fa).use(f) - } - } + def p = + for { + d <- Deferred[IO, Int] + r = resource(d).use_ + fiber <- r.start + _ <- IO.sleep(200.millis) + _ <- fiber.cancel + res <- d.get + } yield res + + assertCompleteAs(p, 1) + } - "evalMap" in ticked { implicit ticker => - forAll { (f: Int => IO[Int]) => Resource.eval(IO(0)).evalMap(f).use(IO.pure) eqv f(0) } + ticked("Resource[IO, *] - eval(fa) <-> liftK.apply(fa)") { implicit ticker => + forAll { (fa: IO[String], f: String => IO[Int]) => + Resource.eval(fa).use(f) eqv Resource.liftK[IO].apply(fa).use(f) } + } - "evalMap with error fails during use" in ticked { implicit ticker => - case object Foo extends Exception + ticked("Resource[IO, *] - evalMap") { implicit ticker => + forAll { (f: Int => IO[Int]) => Resource.eval(IO(0)).evalMap(f).use(IO.pure) eqv f(0) } + } - Resource.eval(IO.unit).evalMap(_ => IO.raiseError[Unit](Foo)).use(IO.pure) must failAs( - Foo) - } + ticked("Resource[IO, *] - evalMap with error fails during use") { implicit ticker => + case object Foo extends Exception - "evalTap" in ticked { implicit ticker => - forAll { (f: Int => IO[Int]) => - Resource.eval(IO(0)).evalTap(f).use(IO.pure) eqv f(0).as(0) - } + assertFailAs( + Resource.eval(IO.unit).evalMap(_ => IO.raiseError[Unit](Foo)).use(IO.pure), + Foo) + } + + ticked("Resource[IO, *] - evalTap") { implicit ticker => + forAll { (f: Int => IO[Int]) => + Resource.eval(IO(0)).evalTap(f).use(IO.pure) eqv f(0).as(0) } + } - "evalTap with error fails during use" in ticked { implicit ticker => - case object Foo extends Exception + ticked("Resource[IO, *] - evalTap with error fails during use") { implicit ticker => + case object Foo extends Exception - Resource.eval(IO(0)).evalTap(_ => IO.raiseError(Foo)).void.use(IO.pure) must failAs(Foo) - } + assertFailAs(Resource.eval(IO(0)).evalTap(_ => IO.raiseError(Foo)).void.use(IO.pure), Foo) + } - "releases resources that implement AutoCloseable" in ticked { implicit ticker => + ticked("Resource[IO, *] - releases resources that implement AutoCloseable") { + implicit ticker => var closed = false val autoCloseable = new AutoCloseable { override def close(): Unit = closed = true } - Resource - .fromAutoCloseable(IO(autoCloseable)) - .surround("Hello world".pure[IO]) must completeAs("Hello world") + assertCompleteAs( + Resource.fromAutoCloseable(IO(autoCloseable)).surround("Hello world".pure[IO]), + "Hello world") - closed must beTrue - } + assert(closed) + } - "allocated releases two resources" in real { - var a = false - var b = false + real("Resource[IO, *] - allocated releases two resources") { + var a = false + var b = false - val test = - Resource.make(IO.unit)(_ => IO { a = true }) >> - Resource.make(IO.unit)(_ => IO { b = true }) + val test = + Resource.make(IO.unit)(_ => IO { a = true }) >> + Resource.make(IO.unit)(_ => IO { b = true }) - test.allocated.flatMap(_._2) >> IO { - a must beTrue - b must beTrue - } + test.allocated.flatMap(_._2) >> IO { + assert(a) + assert(b) } + } - "allocated releases resources in reverse order of acquisition" in ticked { - implicit ticker => - forAll { (as: List[(Int, Either[Throwable, Unit])]) => - var released: List[Int] = Nil - val r = as.traverse { - case (a, e) => - Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) - } - r.allocated.flatMap(_._2).attempt.void must completeAs(()) - released mustEqual as.map(_._1) + ticked("Resource[IO, *] - allocated releases resources in reverse order of acquisition") { + implicit ticker => + forAll { (as: List[(Int, Either[Throwable, Unit])]) => + var released: List[Int] = Nil + val r = as.traverse { + case (a, e) => + Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) } - } + assertCompleteAs(r.allocated.flatMap(_._2).attempt.void, ()) + assertEquals(released, as.map(_._1)) + } + } - "allocated does not release until close is invoked" in ticked { implicit ticker => + ticked("Resource[IO, *] - allocated does not release until close is invoked") { + implicit ticker => val released = new java.util.concurrent.atomic.AtomicBoolean(false) val release = Resource.make(IO.unit)(_ => IO(released.set(true))) val resource = Resource.eval(IO.unit) @@ -238,36 +260,37 @@ class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { val prog = for { res <- ioa (_, close) = res - _ <- IO(released.get() must beFalse) + _ <- IO(assert(!released.get())) _ <- close - _ <- IO(released.get() must beTrue) + _ <- IO(assert(released.get())) } yield () - prog must completeAs(()) - } + assertCompleteAs(prog, ()) + } - "mapK" in ticked { implicit ticker => - forAll { (fa: Kleisli[IO, Int, Int]) => - val runWithTwo = new ~>[Kleisli[IO, Int, *], IO] { - override def apply[A](fa: Kleisli[IO, Int, A]): IO[A] = fa(2) - } - Resource.eval(fa).mapK(runWithTwo).use(IO.pure) eqv fa(2) + ticked("Resource[IO, *] - mapK") { implicit ticker => + forAll { (fa: Kleisli[IO, Int, Int]) => + val runWithTwo = new ~>[Kleisli[IO, Int, *], IO] { + override def apply[A](fa: Kleisli[IO, Int, A]): IO[A] = fa(2) } + Resource.eval(fa).mapK(runWithTwo).use(IO.pure) eqv fa(2) } + } - "attempt on Resource after mapK" in real { - class Err extends Exception + real("Resource[IO, *] - attempt on Resource after mapK") { + class Err extends Exception - Resource - .eval(IO.raiseError[Int](new Err)) - .mapK(Kleisli.liftK[IO, Int]) - .attempt - .surround(3.pure[Kleisli[IO, Int, *]]) - .run(0) - .mustEqual(3) - } + Resource + .eval(IO.raiseError[Int](new Err)) + .mapK(Kleisli.liftK[IO, Int]) + .attempt + .surround(3.pure[Kleisli[IO, Int, *]]) + .run(0) + .mustEqual(3) + } - "mapK should preserve ExitCode-specific behaviour" in ticked { implicit ticker => + ticked("Resource[IO, *] - mapK should preserve ExitCode-specific behaviour") { + implicit ticker => def sideEffectyResource: (AtomicBoolean, Resource[IO, Unit]) = { val cleanExit = new java.util.concurrent.atomic.AtomicBoolean(false) val res = Resource.makeCase(IO.unit) { @@ -281,50 +304,53 @@ class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { } val (clean, res) = sideEffectyResource - res.use(_ => IO.unit).attempt.void must completeAs(()) - clean.get() must beTrue + assertCompleteAs(res.use(_ => IO.unit).attempt.void, ()) + assert(clean.get()) val (clean1, res1) = sideEffectyResource - res1.use(_ => IO.raiseError(new Throwable("oh no"))).attempt.void must completeAs(()) - clean1.get() must beFalse + assertCompleteAs(res1.use(_ => IO.raiseError(new Throwable("oh no"))).attempt.void, ()) + assert(!clean1.get()) val (clean2, res2) = sideEffectyResource - res2.mapK(Kleisli.liftK[IO, Int]).use_.run(0).attempt.void must completeAs(()) - clean2.get() must beTrue + assertCompleteAs(res2.mapK(Kleisli.liftK[IO, Int]).use_.run(0).attempt.void, ()) + assert(clean2.get()) val (clean3, res3) = sideEffectyResource - res3 - .mapK(Kleisli.liftK[IO, Int]) - .use(_ => Kleisli.liftF(IO.raiseError[Unit](new Throwable("oh no")))) - .run(0) - .attempt - .void must completeAs(()) - clean3.get() must beFalse - } + assertCompleteAs( + res3 + .mapK(Kleisli.liftK[IO, Int]) + .use(_ => Kleisli.liftF(IO.raiseError[Unit](new Throwable("oh no")))) + .run(0) + .attempt + .void, + ()) + assert(!clean3.get()) + } - "mapK respects interruptible acquires" in ticked { implicit ticker => - val flag = IO.ref(false) - val sleep = IO.sleep(1.second) - val timeout = 500.millis + ticked("Resource[IO, *] - mapK respects interruptible acquires") { implicit ticker => + val flag = IO.ref(false) + val sleep = IO.sleep(1.second) + val timeout = 500.millis - def fa = - (flag, flag).tupled.flatMap { - case (a, b) => - val io = IO.uncancelable { poll => - sleep.onCancel(a.set(true)) >> poll(sleep).onCancel(b.set(true)) - } + def fa = + (flag, flag).tupled.flatMap { + case (a, b) => + val io = IO.uncancelable { poll => + sleep.onCancel(a.set(true)) >> poll(sleep).onCancel(b.set(true)) + } - val resource = Resource.makeFull[IO, Unit](poll => poll(io))(_ => IO.unit) + val resource = Resource.makeFull[IO, Unit](poll => poll(io))(_ => IO.unit) - val mapKd = resource.mapK(Kleisli.liftK[IO, Int]) + val mapKd = resource.mapK(Kleisli.liftK[IO, Int]) - mapKd.use_.timeout(timeout).run(0).attempt >> (a.get, b.get).tupled - } + mapKd.use_.timeout(timeout).run(0).attempt >> (a.get, b.get).tupled + } - fa must completeAs(false -> true) - } + assertCompleteAs(fa, false -> true) + } - "allocated produces the same value as the resource" in ticked { implicit ticker => + ticked("Resource[IO, *] - allocated produces the same value as the resource") { + implicit ticker => forAll { (resource: Resource[IO, Int]) => val a0 = IO.uncancelable { p => p(resource.allocated).flatMap { case (b, fin) => fin.as(b) } @@ -335,498 +361,492 @@ class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { .flatMap(IO.pure) .handleErrorWith(IO.raiseError) } - } + } - "allocated does not release until close is invoked on mapK'd Resources" in ticked { - implicit ticker => - val released = new java.util.concurrent.atomic.AtomicBoolean(false) + ticked( + "Resource[IO, *] - allocated does not release until close is invoked on mapK'd Resources") { + implicit ticker => + val released = new java.util.concurrent.atomic.AtomicBoolean(false) - val runWithTwo = new ~>[Kleisli[IO, Int, *], IO] { - override def apply[A](fa: Kleisli[IO, Int, A]): IO[A] = fa(2) - } - val takeAnInteger = new ~>[IO, Kleisli[IO, Int, *]] { - override def apply[A](fa: IO[A]): Kleisli[IO, Int, A] = Kleisli.liftF(fa) - } - val plusOne = Kleisli { (i: Int) => IO(i + 1) } - val plusOneResource = Resource.eval(plusOne) - - val release = Resource.make(IO.unit)(_ => IO(released.set(true))) - val resource = Resource.eval(IO.unit) - - // do not inline: it confuses Dotty - val ioa = ((release *> resource).mapK(takeAnInteger) *> plusOneResource) - .mapK(runWithTwo) - .allocated - - val prog = for { - res <- ioa - (_, close) = res - _ <- IO(released.get() must beFalse) - _ <- close - _ <- IO(released.get() must beTrue) - } yield () - - prog must completeAs(()) - } + val runWithTwo = new ~>[Kleisli[IO, Int, *], IO] { + override def apply[A](fa: Kleisli[IO, Int, A]): IO[A] = fa(2) + } + val takeAnInteger = new ~>[IO, Kleisli[IO, Int, *]] { + override def apply[A](fa: IO[A]): Kleisli[IO, Int, A] = Kleisli.liftF(fa) + } + val plusOne = Kleisli { (i: Int) => IO(i + 1) } + val plusOneResource = Resource.eval(plusOne) - "use is stack-safe over binds" in ticked { implicit ticker => - val r = (1 to 10000) - .foldLeft(Resource.eval(IO.unit)) { - case (r, _) => - r.flatMap(_ => Resource.eval(IO.unit)) - } - .use_ - r eqv IO.unit - } + val release = Resource.make(IO.unit)(_ => IO(released.set(true))) + val resource = Resource.eval(IO.unit) - "use is stack-safe over binds - 2" in real { - val n = 50000 - def p(i: Int = 0, n: Int = 50000): Resource[IO, Int] = - Resource - .pure { - if (i < n) Left(i + 1) - else Right(i) - } - .flatMap { - case Left(a) => p(a) - case Right(b) => Resource.pure(b) - } + // do not inline: it confuses Dotty + val ioa = ((release *> resource).mapK(takeAnInteger) *> plusOneResource) + .mapK(runWithTwo) + .allocated - p(n = n).use(IO.pure).mustEqual(n) - } + val prog = for { + res <- ioa + (_, close) = res + _ <- IO(assert(!released.get())) + _ <- close + _ <- IO(assert(released.get())) + } yield () - "mapK is stack-safe over binds" in ticked { implicit ticker => - val r = (1 to 10000) - .foldLeft(Resource.eval(IO.unit)) { - case (r, _) => - r.flatMap(_ => Resource.eval(IO.unit)) - } - .mapK { - new ~>[IO, IO] { - def apply[A](a: IO[A]): IO[A] = a - } - } - .use_ + assertCompleteAs(prog, ()) + } - r eqv IO.unit - } + ticked("Resource[IO, *] - use is stack-safe over binds") { implicit ticker => + val r = (1 to 10000) + .foldLeft(Resource.eval(IO.unit)) { + case (r, _) => + r.flatMap(_ => Resource.eval(IO.unit)) + } + .use_ + r eqv IO.unit + } - "attempt is stack-safe over binds" in ticked { implicit ticker => - val r = (1 to 10000) - .foldLeft(Resource.eval(IO.unit)) { - case (r, _) => - r.flatMap(_ => Resource.eval(IO.unit)) + real("Resource[IO, *] - use is stack-safe over binds - 2") { + val n = 50000 + def p(i: Int = 0, n: Int = 50000): Resource[IO, Int] = + Resource + .pure { + if (i < n) Left(i + 1) + else Right(i) + } + .flatMap { + case Left(a) => p(a) + case Right(b) => Resource.pure(b) } - .attempt - - r.use_ must completeAs(()) - } - - "safe attempt suspended resource" in ticked { implicit ticker => - val exception = new Exception("boom!") - val suspend = Resource.suspend[IO, Unit](IO.raiseError(exception)) - suspend.use_ must failAs(exception) - } - "both" >> { - "releases resources in reverse order of acquisition" in ticked { implicit ticker => - // conceptually asserts that: - // forAll (r: Resource[F, A]) then r <-> r.both(Resource.unit) <-> Resource.unit.both(r) - // needs to be tested manually to assert the equivalence during cleanup as well - forAll { (as: List[(Int, Either[Throwable, Unit])], rhs: Boolean) => - var released: List[Int] = Nil - val r = as.traverse { - case (a, e) => - Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) - } - val unit = ().pure[Resource[IO, *]] - val p = if (rhs) r.both(unit) else unit.both(r) + p(n = n).use(IO.pure).mustEqual(n) + } - p.use_.attempt.void must completeAs(()) - released mustEqual as.map(_._1) + ticked("Resource[IO, *] - mapK is stack-safe over binds") { implicit ticker => + val r = (1 to 10000) + .foldLeft(Resource.eval(IO.unit)) { + case (r, _) => + r.flatMap(_ => Resource.eval(IO.unit)) + } + .mapK { + new ~>[IO, IO] { + def apply[A](a: IO[A]): IO[A] = a } } + .use_ - "parallel acquisition and release" in ticked { implicit ticker => - var leftAllocated = false - var rightAllocated = false - var leftReleasing = false - var rightReleasing = false - var leftReleased = false - var rightReleased = false + r eqv IO.unit + } - val wait = IO.sleep(1.second) - val lhs = Resource.make(wait >> IO { leftAllocated = true }) { _ => - IO { leftReleasing = true } >> wait >> IO { leftReleased = true } - } - val rhs = Resource.make(wait >> IO { rightAllocated = true }) { _ => - IO { rightReleasing = true } >> wait >> IO { rightReleased = true } - } + ticked("Resource[IO, *] - attempt is stack-safe over binds") { implicit ticker => + val r = (1 to 10000) + .foldLeft(Resource.eval(IO.unit)) { + case (r, _) => + r.flatMap(_ => Resource.eval(IO.unit)) + } + .attempt - lhs.both(rhs).use(_ => wait).unsafeToFuture() + assertCompleteAs(r.use_, ()) + } - // after 1 second: - // both resources have allocated (concurrency, serially it would happen after 2 seconds) - // resources are still open during `use` (correctness) - ticker.ctx.tick() - ticker.ctx.advanceAndTick(1.second) - leftAllocated must beTrue - rightAllocated must beTrue - leftReleasing must beFalse - rightReleasing must beFalse + ticked("Resource[IO, *] - safe attempt suspended resource") { implicit ticker => + val exception = new Exception("boom!") + val suspend = Resource.suspend[IO, Unit](IO.raiseError(exception)) + assertFailAs(suspend.use_, exception) + } - // after 2 seconds: - // both resources have started cleanup (correctness) - ticker.ctx.advanceAndTick(1.second) - leftReleasing must beTrue - rightReleasing must beTrue - leftReleased must beFalse - rightReleased must beFalse + ticked("Resource[IO, *] - both - releases resources in reverse order of acquisition") { + implicit ticker => + // conceptually asserts that: + // forAll (r: Resource[F, A]) then r <-> r.both(Resource.unit) <-> Resource.unit.both(r) + // needs to be tested manually to assert the equivalence during cleanup as well + forAll { (as: List[(Int, Either[Throwable, Unit])], rhs: Boolean) => + var released: List[Int] = Nil + val r = as.traverse { + case (a, e) => + Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) + } + val unit = ().pure[Resource[IO, *]] + val p = if (rhs) r.both(unit) else unit.both(r) - // after 3 seconds: - // both resources have terminated cleanup (concurrency, serially it would happen after 4 seconds) - ticker.ctx.advanceAndTick(1.second) - leftReleased must beTrue - rightReleased must beTrue + assertCompleteAs(p.use_.attempt.void, ()) + assertEquals(released, as.map(_._1)) } + } - "safety: lhs error during rhs interruptible region" in ticked { implicit ticker => - var leftAllocated = false - var rightAllocated = false - var leftReleasing = false - var rightReleasing = false - var leftReleased = false - var rightReleased = false + ticked("Resource[IO, *] - both - parallel acquisition and release") { implicit ticker => + var leftAllocated = false + var rightAllocated = false + var leftReleasing = false + var rightReleasing = false + var leftReleased = false + var rightReleased = false - def wait(n: Int) = IO.sleep(n.seconds) - val lhs = for { - _ <- Resource.make(wait(1) >> IO { leftAllocated = true }) { _ => - IO { leftReleasing = true } >> wait(1) >> IO { leftReleased = true } - } - _ <- Resource.eval(wait(1) >> IO.raiseError[Unit](new Exception)) - } yield () + val wait = IO.sleep(1.second) + val lhs = Resource.make(wait >> IO { leftAllocated = true }) { _ => + IO { leftReleasing = true } >> wait >> IO { leftReleased = true } + } + val rhs = Resource.make(wait >> IO { rightAllocated = true }) { _ => + IO { rightReleasing = true } >> wait >> IO { rightReleased = true } + } - val rhs = for { - _ <- Resource.make(wait(1) >> IO { rightAllocated = true }) { _ => - IO { rightReleasing = true } >> wait(1) >> IO { rightReleased = true } - } - _ <- Resource.eval(wait(2)) - } yield () + lhs.both(rhs).use(_ => wait).unsafeToFuture() + + // after 1 second: + // both resources have allocated (concurrency, serially it would happen after 2 seconds) + // resources are still open during `use` (correctness) + ticker.ctx.tick() + ticker.ctx.advanceAndTick(1.second) + assert(leftAllocated) + assert(rightAllocated) + assert(!leftReleasing) + assert(!rightReleasing) + + // after 2 seconds: + // both resources have started cleanup (correctness) + ticker.ctx.advanceAndTick(1.second) + assert(leftReleasing) + assert(rightReleasing) + assert(!leftReleased) + assert(!rightReleased) + + // after 3 seconds: + // both resources have terminated cleanup (concurrency, serially it would happen after 4 seconds) + ticker.ctx.advanceAndTick(1.second) + assert(leftReleased) + assert(rightReleased) + } - lhs.both(rhs).use(_ => IO.unit).handleError(_ => ()).unsafeToFuture() + ticked("Resource[IO, *] - both - safety: lhs error during rhs interruptible region") { + implicit ticker => + var leftAllocated = false + var rightAllocated = false + var leftReleasing = false + var rightReleasing = false + var leftReleased = false + var rightReleased = false + + def wait(n: Int) = IO.sleep(n.seconds) + val lhs = for { + _ <- Resource.make(wait(1) >> IO { leftAllocated = true }) { _ => + IO { leftReleasing = true } >> wait(1) >> IO { leftReleased = true } + } + _ <- Resource.eval(wait(1) >> IO.raiseError[Unit](new Exception)) + } yield () - // after 1 second: - // both resources have allocated (concurrency, serially it would happen after 2 seconds) - // resources are still open during `flatMap` (correctness) - ticker.ctx.tick() - ticker.ctx.advanceAndTick(1.second) - leftAllocated must beTrue - rightAllocated must beTrue - leftReleasing must beFalse - rightReleasing must beFalse + val rhs = for { + _ <- Resource.make(wait(1) >> IO { rightAllocated = true }) { _ => + IO { rightReleasing = true } >> wait(1) >> IO { rightReleased = true } + } + _ <- Resource.eval(wait(2)) + } yield () - // after 2 seconds: - // both resources have started cleanup (interruption, or rhs would start releasing after 3 seconds) - ticker.ctx.advanceAndTick(1.second) - leftReleasing must beTrue - rightReleasing must beTrue - leftReleased must beFalse - rightReleased must beFalse + lhs.both(rhs).use(_ => IO.unit).handleError(_ => ()).unsafeToFuture() + + // after 1 second: + // both resources have allocated (concurrency, serially it would happen after 2 seconds) + // resources are still open during `flatMap` (correctness) + ticker.ctx.tick() + ticker.ctx.advanceAndTick(1.second) + assert(leftAllocated) + assert(rightAllocated) + assert(!leftReleasing) + assert(!rightReleasing) + + // after 2 seconds: + // both resources have started cleanup (interruption, or rhs would start releasing after 3 seconds) + ticker.ctx.advanceAndTick(1.second) + assert(leftReleasing) + assert(rightReleasing) + assert(!leftReleased) + assert(!rightReleased) + + // after 3 seconds: + // both resources have terminated cleanup (concurrency, serially it would happen after 4 seconds) + ticker.ctx.advanceAndTick(1.second) + assert(leftReleased) + assert(rightReleased) + } - // after 3 seconds: - // both resources have terminated cleanup (concurrency, serially it would happen after 4 seconds) - ticker.ctx.advanceAndTick(1.second) - leftReleased must beTrue - rightReleased must beTrue + ticked("Resource[IO, *] - both - safety: rhs error during lhs uninterruptible region") { + implicit ticker => + var leftAllocated = false + var rightAllocated = false + var rightErrored = false + var leftReleasing = false + var rightReleasing = false + var leftReleased = false + var rightReleased = false + + def wait(n: Int) = IO.sleep(n.seconds) + val lhs = Resource.make(wait(3) >> IO { leftAllocated = true }) { _ => + IO { leftReleasing = true } >> wait(1) >> IO { leftReleased = true } } - - "safety: rhs error during lhs uninterruptible region" in ticked { implicit ticker => - var leftAllocated = false - var rightAllocated = false - var rightErrored = false - var leftReleasing = false - var rightReleasing = false - var leftReleased = false - var rightReleased = false - - def wait(n: Int) = IO.sleep(n.seconds) - val lhs = Resource.make(wait(3) >> IO { leftAllocated = true }) { _ => - IO { leftReleasing = true } >> wait(1) >> IO { leftReleased = true } + val rhs = for { + _ <- Resource.make(wait(1) >> IO { rightAllocated = true }) { _ => + IO { rightReleasing = true } >> wait(1) >> IO { rightReleased = true } } - val rhs = for { - _ <- Resource.make(wait(1) >> IO { rightAllocated = true }) { _ => - IO { rightReleasing = true } >> wait(1) >> IO { rightReleased = true } - } - _ <- Resource.make( - wait(1) >> IO { rightErrored = true } >> IO.raiseError[Unit](new Exception))(_ => - IO.unit) - } yield () + _ <- Resource.make( + wait(1) >> IO { rightErrored = true } >> IO.raiseError[Unit](new Exception))(_ => + IO.unit) + } yield () - lhs.both(rhs).use(_ => wait(1)).handleError(_ => ()).unsafeToFuture() + lhs.both(rhs).use(_ => wait(1)).handleError(_ => ()).unsafeToFuture() + + // after 1 second: + // rhs has partially allocated, lhs executing + ticker.ctx.tick() + ticker.ctx.advanceAndTick(1.second) + assert(!leftAllocated) + assert(rightAllocated) + assert(!rightErrored) + assert(!leftReleasing) + assert(!rightReleasing) + + // after 2 seconds: + // rhs has failed, release blocked since lhs is in uninterruptible allocation + ticker.ctx.advanceAndTick(1.second) + assert(!leftAllocated) + assert(rightAllocated) + assert(rightErrored) + assert(!leftReleasing) + assert(!rightReleasing) + + // after 3 seconds: + // lhs completes allocation (concurrency, serially it would happen after 4 seconds) + // both resources have started cleanup (correctness, error propagates to both sides) + ticker.ctx.advanceAndTick(1.second) + assert(leftAllocated) + assert(leftReleasing) + assert(rightReleasing) + assert(!leftReleased) + assert(!rightReleased) + + // after 4 seconds: + // both resource have terminated cleanup (concurrency, serially it would happen after 5 seconds) + ticker.ctx.advanceAndTick(1.second) + assert(leftReleased) + assert(rightReleased) + } - // after 1 second: - // rhs has partially allocated, lhs executing - ticker.ctx.tick() - ticker.ctx.advanceAndTick(1.second) - leftAllocated must beFalse - rightAllocated must beTrue - rightErrored must beFalse - leftReleasing must beFalse - rightReleasing must beFalse + test("Resource[IO, *] - both - propagate the exit case") { + import Resource.ExitCase - // after 2 seconds: - // rhs has failed, release blocked since lhs is in uninterruptible allocation - ticker.ctx.advanceAndTick(1.second) - leftAllocated must beFalse - rightAllocated must beTrue - rightErrored must beTrue - leftReleasing must beFalse - rightReleasing must beFalse + ticked("use successfully, test left") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertCompleteAs(r.both(Resource.unit).use(_ => IO.unit), ()) + assertEquals(got, ExitCase.Succeeded) + } - // after 3 seconds: - // lhs completes allocation (concurrency, serially it would happen after 4 seconds) - // both resources have started cleanup (correctness, error propagates to both sides) - ticker.ctx.advanceAndTick(1.second) - leftAllocated must beTrue - leftReleasing must beTrue - rightReleasing must beTrue - leftReleased must beFalse - rightReleased must beFalse - - // after 4 seconds: - // both resource have terminated cleanup (concurrency, serially it would happen after 5 seconds) - ticker.ctx.advanceAndTick(1.second) - leftReleased must beTrue - rightReleased must beTrue - } + ticked("use successfully, test right") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertCompleteAs(Resource.unit.both(r).use(_ => IO.unit), ()) + assertEquals(got, ExitCase.Succeeded) + } - "propagate the exit case" in { - import Resource.ExitCase + ticked("use errored, test left") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertFailAs(r.both(Resource.unit).use(_ => IO.raiseError(ex)), ex) + assertEquals(got, ExitCase.Errored(ex)) + } - "use successfully, test left" >> ticked { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - r.both(Resource.unit).use(_ => IO.unit) must completeAs(()) - got mustEqual ExitCase.Succeeded - } + ticked("use errored, test right") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertFailAs(Resource.unit.both(r).use(_ => IO.raiseError(ex)), ex) + assertEquals(got, ExitCase.Errored(ex)) + } - "use successfully, test right" >> ticked { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - Resource.unit.both(r).use(_ => IO.unit) must completeAs(()) - got mustEqual ExitCase.Succeeded - } + ticked("right errored, test left") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertFailAs(r.both(Resource.eval(IO.sleep(1.second) *> IO.raiseError(ex))).use_, ex) + assertEquals(got, ExitCase.Errored(ex)) + } - "use errored, test left" >> ticked { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - r.both(Resource.unit).use(_ => IO.raiseError(ex)) must failAs(ex) - got mustEqual ExitCase.Errored(ex) - } + ticked("left errored, test right") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertFailAs(Resource.eval(IO.sleep(1.second) *> IO.raiseError(ex)).both(r).use_, ex) + assertEquals(got, ExitCase.Errored(ex)) + } - "use errored, test right" >> ticked { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - Resource.unit.both(r).use(_ => IO.raiseError(ex)) must failAs(ex) - got mustEqual ExitCase.Errored(ex) - } + ticked("use canceled, test left") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertSelfCancel(r.both(Resource.unit).use(_ => IO.canceled)) + assertEquals(got, ExitCase.Canceled) + } - "right errored, test left" >> ticked { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - r.both(Resource.eval(IO.sleep(1.second) *> IO.raiseError(ex))).use_ must failAs(ex) - got mustEqual ExitCase.Errored(ex) - } + ticked("use canceled, test right") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertSelfCancel(Resource.unit.both(r).use(_ => IO.canceled)) + assertEquals(got, ExitCase.Canceled) + } - "left errored, test right" >> ticked { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - Resource.eval(IO.sleep(1.second) *> IO.raiseError(ex)).both(r).use_ must failAs(ex) - got mustEqual ExitCase.Errored(ex) - } + ticked("right canceled, test left") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertSelfCancel(r.both(Resource.eval(IO.sleep(1.second) *> IO.canceled)).use_) + assertEquals(got, ExitCase.Canceled) + } - "use canceled, test left" >> ticked { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - r.both(Resource.unit).use(_ => IO.canceled) must selfCancel - got mustEqual ExitCase.Canceled - } + ticked("left canceled, test right") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertSelfCancel(Resource.eval(IO.sleep(1.second) *> IO.canceled).both(r).use_) + assertEquals(got, ExitCase.Canceled) + } + } - "use canceled, test right" >> ticked { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - Resource.unit.both(r).use(_ => IO.canceled) must selfCancel - got mustEqual ExitCase.Canceled - } + ticked("Resource[IO, *] - releases both resources on combineK") { implicit ticker => + var acquired: Set[Int] = Set.empty + var released: Set[Int] = Set.empty + def observe(a: Int) = + Resource.make(IO(acquired += a).as(a))(a => IO(released += a)) - "right canceled, test left" >> ticked { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - r.both(Resource.eval(IO.sleep(1.second) *> IO.canceled)).use_ must selfCancel - got mustEqual ExitCase.Canceled - } + assertCompleteAs(observe(1).combineK(observe(2)).use_.attempt.void, ()) + assertEquals(released, acquired) + } - "left canceled, test right" >> ticked { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - Resource.eval(IO.sleep(1.second) *> IO.canceled).both(r).use_ must selfCancel - got mustEqual ExitCase.Canceled - } + ticked( + "Resource[IO, *] - releases both resources on combineK when using a SemigroupK instance that discards allocated values") { + implicit ticker => + implicit val sgk: SemigroupK[IO] = new SemigroupK[IO] { + override def combineK[A](x: IO[A], y: IO[A]): IO[A] = x <* y } - } - - "releases both resources on combineK" in ticked { implicit ticker => var acquired: Set[Int] = Set.empty var released: Set[Int] = Set.empty def observe(a: Int) = - Resource.make(IO(acquired += a).as(a))(a => IO(released += a)) - - observe(1).combineK(observe(2)).use_.attempt.void must completeAs(()) - released mustEqual acquired - } - - "releases both resources on combineK when using a SemigroupK instance that discards allocated values" in ticked { - implicit ticker => - implicit val sgk: SemigroupK[IO] = new SemigroupK[IO] { - override def combineK[A](x: IO[A], y: IO[A]): IO[A] = x <* y - } - var acquired: Set[Int] = Set.empty - var released: Set[Int] = Set.empty - def observe(a: Int) = - Resource.make(IO(acquired += a) *> IO.pure(a))(a => IO(released += a)) + Resource.make(IO(acquired += a) *> IO.pure(a))(a => IO(released += a)) - observe(1).combineK(observe(2)).use_.attempt.void must completeAs(()) - released mustEqual acquired - } + assertCompleteAs(observe(1).combineK(observe(2)).use_.attempt.void, ()) + assertEquals(released, acquired) + } - "combineK" should { - "behave like orElse when underlying effect does" in ticked { implicit ticker => - prop { (r1: Resource[IO, Int], r2: Resource[IO, Int]) => - val lhs = r1.orElse(r2) - val rhs = r1 <+> r2 + ticked("Resource[IO, *] - combineK - behave like orElse when underlying effect does") { + implicit ticker => + Prop.forAll { (r1: Resource[IO, Int], r2: Resource[IO, Int]) => + val lhs = r1.orElse(r2) + val rhs = r1 <+> r2 - lhs eqv rhs - } + lhs eqv rhs } + } - "behave like underlying effect" in ticked { implicit ticker => - forAll { (ot1: OptionT[IO, Int], ot2: OptionT[IO, Int]) => - val lhs = Resource.eval(ot1 <+> ot2).use(OptionT.pure[IO](_)).value - val rhs = (Resource.eval(ot1) <+> Resource.eval(ot2)).use(OptionT.pure[IO](_)).value + ticked("Resource[IO, *] - combineK - behave like underlying effect") { implicit ticker => + forAll { (ot1: OptionT[IO, Int], ot2: OptionT[IO, Int]) => + val lhs = Resource.eval(ot1 <+> ot2).use(OptionT.pure[IO](_)).value + val rhs = (Resource.eval(ot1) <+> Resource.eval(ot2)).use(OptionT.pure[IO](_)).value - lhs eqv rhs - } - } + lhs eqv rhs + } + } - "propagate the exit case" in { - import Resource.ExitCase + test("Resource[IO, *] - combineK - propagate the exit case") { + import Resource.ExitCase - "use successfully, test left" >> ticked { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - r.combineK(Resource.unit).use(_ => IO.unit) must completeAs(()) - got mustEqual ExitCase.Succeeded - } + ticked("use successfully, test left") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertCompleteAs(r.combineK(Resource.unit).use(_ => IO.unit), ()) + assertEquals(got, ExitCase.Succeeded) + } - "use errored, test left" >> ticked { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - r.combineK(Resource.unit).use(_ => IO.raiseError(ex)) must failAs(ex) - got mustEqual ExitCase.Errored(ex) - } + ticked("use errored, test left") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertFailAs(r.combineK(Resource.unit).use(_ => IO.raiseError(ex)), ex) + assertEquals(got, ExitCase.Errored(ex)) + } - "left errored, test left" >> ticked { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) *> - Resource.eval(IO.raiseError(ex)) - r.combineK(Resource.unit).use_ must completeAs(()) - got mustEqual ExitCase.Succeeded - } + ticked("left errored, test left") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) *> + Resource.eval(IO.raiseError(ex)) + assertCompleteAs(r.combineK(Resource.unit).use_, ()) + assertEquals(got, ExitCase.Succeeded) + } - "left errored, test right" >> ticked { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - Resource.eval(IO.raiseError(ex)).combineK(r).use_ must completeAs(()) - got mustEqual ExitCase.Succeeded - } + ticked("left errored, test right") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertCompleteAs(Resource.eval(IO.raiseError(ex)).combineK(r).use_, ()) + assertEquals(got, ExitCase.Succeeded) + } - "left errored, use errored, test right" >> ticked { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - Resource - .eval(IO.raiseError(new Exception)) - .combineK(r) - .use(_ => IO.raiseError(ex)) must failAs(ex) - got mustEqual ExitCase.Errored(ex) - } + ticked("left errored, use errored, test right") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertFailAs( + Resource.eval(IO.raiseError(new Exception)).combineK(r).use(_ => IO.raiseError(ex)), + ex) + assertEquals(got, ExitCase.Errored(ex)) + } - "use canceled, test left" >> ticked { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - r.combineK(Resource.unit).use(_ => IO.canceled) must selfCancel - got mustEqual ExitCase.Canceled - } + ticked("use canceled, test left") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertSelfCancel(r.combineK(Resource.unit).use(_ => IO.canceled)) + assertEquals(got, ExitCase.Canceled) + } - "left errored, use canceled, test right" >> ticked { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - Resource - .eval(IO.raiseError(new Exception)) - .combineK(r) - .use(_ => IO.canceled) must selfCancel - got mustEqual ExitCase.Canceled - } - } + ticked("left errored, use canceled, test right") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertSelfCancel( + Resource.eval(IO.raiseError(new Exception)).combineK(r).use(_ => IO.canceled)) + assertEquals(got, ExitCase.Canceled) } + } - "surround" should { - "wrap an effect in a usage and ignore the value produced by resource" in ticked { - implicit ticker => - val r = Resource.eval(IO.pure(0)) - val surroundee = IO("hello") - val surrounded = r.surround(surroundee) + ticked("surround - wrap an effect in a usage and ignore the value produced by resource") { + implicit ticker => + val r = Resource.eval(IO.pure(0)) + val surroundee = IO("hello") + val surrounded = r.surround(surroundee) - surrounded eqv surroundee - } - } + surrounded eqv surroundee + } - "surroundK" should { - "wrap an effect in a usage, ignore the value produced by resource and return FunctionK" in ticked { - implicit ticker => - val r = Resource.eval(IO.pure(0)) - val surroundee = IO("hello") - val surround = r.surroundK - val surrounded = surround(surroundee) + ticked( + "Resource[IO, *] - surroundK - wrap an effect in a usage, ignore the value produced by resource and return FunctionK") { + implicit ticker => + val r = Resource.eval(IO.pure(0)) + val surroundee = IO("hello") + val surround = r.surroundK + val surrounded = surround(surroundee) - surrounded eqv surroundee - } - } + surrounded eqv surroundee + } - "evalOn" should { - "run acquire and release on provided ExecutionContext" in ticked { implicit ticker => - forAll { (executionContext: ExecutionContext) => - val assertion = - IO.executionContext.flatMap(ec => IO(ec mustEqual executionContext)).void - Resource.make(assertion)(_ => assertion).evalOn(executionContext).use_.as(true) - } + ticked("Resource[IO, *] - evalOn - run acquire and release on provided ExecutionContext") { + implicit ticker => + forAll { (executionContext: ExecutionContext) => + val assertion = + IO.executionContext.flatMap(ec => IO(assertEquals(ec, executionContext)).void) + Resource.make(assertion)(_ => assertion).evalOn(executionContext).use_.as(true) } - } } - "Async[Resource]" >> { + { val wait = IO.sleep(1.second) val waitR = Resource.eval(wait) - "onCancel" should { - "not fire when adjacent to uncancelable" in ticked { implicit ticker => + ticked("Async[Resource] - onCancel - not fire when adjacent to uncancelable") { + implicit ticker => var fired = false val test = @@ -835,12 +855,11 @@ class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { test.use_.unsafeToFuture() ticker.ctx.tick() - fired must beFalse - } + assert(!fired) } - "async" should { - "forward inner finalizers into outer scope" in ticked { implicit ticker => + ticked("Async[Resource] - async - forward inner finalizers into outer scope") { + implicit ticker => var innerClosed = false var outerClosed = false @@ -855,55 +874,52 @@ class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { ticker.ctx.tick() ticker.ctx.advanceAndTick(1.second) - innerClosed must beFalse - outerClosed must beFalse + assert(!innerClosed) + assert(!outerClosed) ticker.ctx.advanceAndTick(1.second) - innerClosed must beTrue - outerClosed must beTrue - } + assert(innerClosed) + assert(outerClosed) } - "forceR" >> { - "closes left scope" in ticked { implicit ticker => - var leftClosed = false - var rightClosed = false + ticked("Async[Resource] - forceR - closes left scope") { implicit ticker => + var leftClosed = false + var rightClosed = false - val target = - Resource.make(wait)(_ => IO { leftClosed = true }) !> - Resource.make(wait)(_ => IO { rightClosed = true }) + val target = + Resource.make(wait)(_ => IO { leftClosed = true }) !> + Resource.make(wait)(_ => IO { rightClosed = true }) - target.use_.unsafeToFuture() + target.use_.unsafeToFuture() - ticker.ctx.tick() - ticker.ctx.advanceAndTick(1.second) - leftClosed must beTrue - rightClosed must beFalse + ticker.ctx.tick() + ticker.ctx.advanceAndTick(1.second) + assert(leftClosed) + assert(!rightClosed) - ticker.ctx.advanceAndTick(1.second) - rightClosed must beTrue - } + ticker.ctx.advanceAndTick(1.second) + assert(rightClosed) } - "onCancel" >> { - "catches inner cancelation" in ticked { implicit ticker => - var innerClosed = false - var outerClosed = false - var canceled = false + ticked("Async[Resource] - onCancel - catches inner cancelation") { implicit ticker => + var innerClosed = false + var outerClosed = false + var canceled = false - val inner = - Resource.make(IO.unit)(_ => IO { innerClosed = true }) *> Resource.eval(IO.canceled) - val outer = Resource.make(IO.unit)(_ => IO { outerClosed = true }) + val inner = + Resource.make(IO.unit)(_ => IO { innerClosed = true }) *> Resource.eval(IO.canceled) + val outer = Resource.make(IO.unit)(_ => IO { outerClosed = true }) - val target = inner.onCancel(Resource.eval(IO { canceled = true })) *> outer + val target = inner.onCancel(Resource.eval(IO { canceled = true })) *> outer - target.use_ must selfCancel - innerClosed must beTrue - outerClosed must beFalse - canceled must beTrue - } + assertSelfCancel(target.use_) + assert(innerClosed) + assert(!outerClosed) + assert(canceled) + } - "does not extend across the region" in ticked { implicit ticker => + ticked("Async[Resource] - onCancel - does not extend across the region") { + implicit ticker => var innerClosed = false var outerClosed = false var canceled = false @@ -914,171 +930,172 @@ class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { val target = inner.onCancel(Resource.eval(IO { canceled = true })) *> outer - target.use_ must selfCancel - innerClosed must beTrue - outerClosed must beTrue - canceled must beFalse // if this is true, it means the scope was extended rather than being closed! - } + assertSelfCancel(target.use_) + assert(innerClosed) + assert(outerClosed) + assert( + !canceled + ) // if this is true, it means the scope was extended rather than being closed! } - "race" >> { - "two acquisitions, closing the loser and maintaining the winner" in ticked { - implicit ticker => - var winnerClosed = false - var loserClosed = false - var completed = false - - var results: Either[String, String] = null - - val winner = Resource - .make(IO.unit)(_ => IO { winnerClosed = true }) - .evalMap(_ => IO.sleep(100.millis)) - .map(_ => "winner") - val loser = Resource - .make(IO.unit)(_ => IO { loserClosed = true }) - .evalMap(_ => IO.sleep(200.millis)) - .map(_ => "loser") - - val target = - winner.race(loser).evalMap(e => IO { results = e }) *> waitR *> Resource.eval(IO { - completed = true - }) - - target.use_.unsafeToFuture() - - ticker.ctx.tick() - ticker.ctx.advanceAndTick(50.millis) - winnerClosed must beFalse - loserClosed must beFalse - completed must beFalse - results must beNull - - ticker.ctx.advanceAndTick(50.millis) - winnerClosed must beFalse - loserClosed must beTrue - completed must beFalse - results must beLeft("winner") - - ticker.ctx.advanceAndTick(50.millis) - winnerClosed must beFalse - loserClosed must beTrue - completed must beFalse - results must beLeft("winner") - - ticker.ctx.advanceAndTick(1.second) - winnerClosed must beTrue - completed must beTrue - } + ticked( + "Async[Resource] - race - two acquisitions, closing the loser and maintaining the winner") { + implicit ticker => + var winnerClosed = false + var loserClosed = false + var completed = false - "closes the loser eagerly" in real { - val go = ( - IO.ref(false), - IO.ref(false), - IO.deferred[Either[Unit, Unit]] - ).flatMapN { (acquiredLeft, acquiredRight, loserReleased) => - val left = Resource.make(acquiredLeft.set(true))(_ => - loserReleased.complete(Left[Unit, Unit](())).void) - - val right = Resource.make(acquiredRight.set(true))(_ => - loserReleased.complete(Right[Unit, Unit](())).void) - - Resource.race(left, right).use { - case Left(()) => - acquiredRight.get.ifM(loserReleased.get.map(_ must beRight[Unit]), IO.pure(ok)) - case Right(()) => - acquiredLeft.get.ifM(loserReleased.get.map(_ must beLeft[Unit]), IO.pure(ok)) - } - } + var results: Either[String, String] = null - TestControl.executeEmbed(go.replicateA_(100)).as(true) - } - } + val winner = Resource + .make(IO.unit)(_ => IO { winnerClosed = true }) + .evalMap(_ => IO.sleep(100.millis)) + .map(_ => "winner") + val loser = Resource + .make(IO.unit)(_ => IO { loserClosed = true }) + .evalMap(_ => IO.sleep(200.millis)) + .map(_ => "loser") - "start" >> { - "runs fibers in parallel" in ticked { implicit ticker => - var completed = false - val target = waitR.start *> waitR *> Resource.eval(IO { completed = true }) + val target = + winner.race(loser).evalMap(e => IO { results = e }) *> waitR *> Resource.eval(IO { + completed = true + }) target.use_.unsafeToFuture() - ticker.ctx.tickAll() - completed must beTrue - } - "run finalizers when completed and outer scope is closed" in ticked { implicit ticker => - var i = 0 - var completed = false - val target = Resource.make(IO { completed = true })(_ => IO(i += 1)).start *> waitR + ticker.ctx.tick() + ticker.ctx.advanceAndTick(50.millis) + assert(!winnerClosed) + assert(!loserClosed) + assert(!completed) + assert(results eq null) + + ticker.ctx.advanceAndTick(50.millis) + assert(!winnerClosed) + assert(loserClosed) + assert(!completed) + assertEquals(results, Left("winner")) + + ticker.ctx.advanceAndTick(50.millis) + assert(!winnerClosed) + assert(loserClosed) + assert(!completed) + assertEquals(results, Left("winner")) - target.use_ must completeAs(()) - completed must beTrue - i mustEqual 1 + ticker.ctx.advanceAndTick(1.second) + assert(winnerClosed) + assert(completed) + } + + real("Async[Resource] - race - closes the loser eagerly") { + val go = ( + IO.ref(false), + IO.ref(false), + IO.deferred[Either[Unit, Unit]] + ).flatMapN { (acquiredLeft, acquiredRight, loserReleased) => + val left = Resource.make(acquiredLeft.set(true))(_ => + loserReleased.complete(Left[Unit, Unit](())).void) + + val right = Resource.make(acquiredRight.set(true))(_ => + loserReleased.complete(Right[Unit, Unit](())).void) + + Resource.race(left, right).use { + case Left(()) => + acquiredRight.get.ifM(loserReleased.get.map(v => assert(v.isRight)), IO.unit) + case Right(()) => + acquiredLeft.get.ifM(loserReleased.get.map(v => assert(v.isLeft)), IO.unit) + } } - "run finalizers after completion when outer scope is closed" in ticked { - implicit ticker => - var i = 0 - var completed = false + TestControl.executeEmbed(go.replicateA_(100)).as(true) + } - val finish = Resource.eval(IO { completed = true }) - val target = Resource.make(wait)(_ => IO(i += 1)).start *> finish + ticked("Async[Resource] - start - runs fibers in parallel") { implicit ticker => + var completed = false + val target = waitR.start *> waitR *> Resource.eval(IO { completed = true }) - target.use_.unsafeToFuture() - ticker.ctx.advanceAndTick(50.millis) + target.use_.unsafeToFuture() + ticker.ctx.tickAll() + assert(completed) + } - completed must beTrue - i mustEqual 0 + ticked( + "Async[Resource] - start - run finalizers when completed and outer scope is closed") { + implicit ticker => + var i = 0 + var completed = false + val target = Resource.make(IO { completed = true })(_ => IO(i += 1)).start *> waitR - ticker.ctx.advanceAndTick(1.second) - i mustEqual 1 - } + assertCompleteAs(target.use_, ()) + assert(completed) + assertEquals(i, 1) + } - "run finalizers once when canceled" in ticked { implicit ticker => + ticked( + "Async[Resource] - start - run finalizers after completion when outer scope is closed") { + implicit ticker => var i = 0 + var completed = false - val fork = Resource.make(IO.unit)(_ => IO(i += 1)) *> waitR *> waitR - val target = fork.start flatMap { f => waitR *> f.cancel } + val finish = Resource.eval(IO { completed = true }) + val target = Resource.make(wait)(_ => IO(i += 1)).start *> finish target.use_.unsafeToFuture() + ticker.ctx.advanceAndTick(50.millis) - ticker.ctx.tick() - ticker.ctx.advanceAndTick(1.second) - i mustEqual 1 + assert(completed) + assertEquals(i, 0) ticker.ctx.advanceAndTick(1.second) - i mustEqual 1 - } + assertEquals(i, 1) + } - "join scope contained by outer" in ticked { implicit ticker => - var i = 0 - var completed = false + ticked("Async[Resource] - start - run finalizers once when canceled") { implicit ticker => + var i = 0 - val fork = Resource.make(IO.unit)(_ => IO(i += 1)) + val fork = Resource.make(IO.unit)(_ => IO(i += 1)) *> waitR *> waitR + val target = fork.start flatMap { f => waitR *> f.cancel } - val target = - fork.start.evalMap(f => (f.joinWithNever *> waitR).use_) *> - waitR *> - Resource.eval(IO { completed = true }) + target.use_.unsafeToFuture() - target.use_.unsafeToFuture() + ticker.ctx.tick() + ticker.ctx.advanceAndTick(1.second) + assertEquals(i, 1) - ticker.ctx.tick() - ticker.ctx.advanceAndTick(100.millis) - i mustEqual 0 + ticker.ctx.advanceAndTick(1.second) + assertEquals(i, 1) + } - ticker.ctx.advanceAndTick(900.millis) - i mustEqual 0 - completed must beFalse + ticked("Async[Resource] - start - join scope contained by outer") { implicit ticker => + var i = 0 + var completed = false - ticker.ctx.advanceAndTick(1.second) - i mustEqual 1 - completed must beTrue - } + val fork = Resource.make(IO.unit)(_ => IO(i += 1)) + + val target = + fork.start.evalMap(f => (f.joinWithNever *> waitR).use_) *> + waitR *> + Resource.eval(IO { completed = true }) + + target.use_.unsafeToFuture() + + ticker.ctx.tick() + ticker.ctx.advanceAndTick(100.millis) + assertEquals(i, 0) + + ticker.ctx.advanceAndTick(900.millis) + assertEquals(i, 0) + assert(!completed) + + ticker.ctx.advanceAndTick(1.second) + assertEquals(i, 1) + assert(completed) } } - "Concurrent[Resource]" >> { - "both" >> { - "parallel acquisition and release" in ticked { implicit ticker => + { + ticked("Concurrent[Resource] - both - parallel acquisition and release") { + implicit ticker => var leftAllocated = false var rightAllocated = false var leftReleasing = false @@ -1101,78 +1118,87 @@ class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { // resources are still open during `use` (correctness) ticker.ctx.tick() ticker.ctx.advanceAndTick(1.second) - leftAllocated must beTrue - rightAllocated must beTrue - leftReleasing must beFalse - rightReleasing must beFalse + assert(leftAllocated) + assert(rightAllocated) + assert(!leftReleasing) + assert(!rightReleasing) // after 2 seconds: // both resources have started cleanup (correctness) ticker.ctx.advanceAndTick(1.second) - leftReleasing must beTrue - rightReleasing must beTrue - leftReleased must beFalse - rightReleased must beFalse + assert(leftReleasing) + assert(rightReleasing) + assert(!leftReleased) + assert(!rightReleased) // after 3 seconds: // both resources have terminated cleanup (concurrency, serially it would happen after 4 seconds) ticker.ctx.advanceAndTick(1.second) - leftReleased must beTrue - rightReleased must beTrue - } + assert(leftReleased) + assert(rightReleased) } - "memoize" >> { - "memoize and then flatten is identity" in ticked { implicit ticker => - forAll { (r: Resource[IO, Int]) => r.memoize.flatten eqv r } - } - "allocates once and releases at end" in ticked { implicit ticker => - (IO.ref(0), IO.ref(0)) - .mapN { (acquired, released) => - val r = Resource.make(acquired.update(_ + 1).void)(_ => released.update(_ + 1)) - def acquiredMustBe(i: Int) = acquired.get.map(_ must be_==(i)).void - def releasedMustBe(i: Int) = released.get.map(_ must be_==(i)).void - r.memoize.use { memo => - acquiredMustBe(0) *> releasedMustBe(0) *> - memo.surround(acquiredMustBe(1) *> releasedMustBe(0)) *> - acquiredMustBe(1) *> releasedMustBe(0) *> - memo.surround(acquiredMustBe(1) *> releasedMustBe(0)) *> - acquiredMustBe(1) *> releasedMustBe(0) - } *> acquiredMustBe(1) *> releasedMustBe(1) - } - .flatten - .void must completeAs(()) - } - "does not allocate if not used" in ticked { implicit ticker => - (IO.ref(0), IO.ref(0)) - .mapN { (acquired, released) => - val r = Resource.make(acquired.update(_ + 1).void)(_ => released.update(_ + 1)) - def acquiredMustBe(i: Int) = acquired.get.map(_ must be_==(i)).void - def releasedMustBe(i: Int) = released.get.map(_ must be_==(i)).void - r.memoize.surround(acquiredMustBe(0) *> releasedMustBe(0)) *> - acquiredMustBe(0) *> releasedMustBe(0) - } - .flatten - .void must completeAs(()) - } - "does not leak if canceled" in ticked { implicit ticker => + ticked("Concurrent[Resource] - memoize - memoize and then flatten is identity") { + implicit ticker => forAll { (r: Resource[IO, Int]) => r.memoize.flatten eqv r } + } + ticked("Concurrent[Resource] - memoize - allocates once and releases at end") { + implicit ticker => + assertCompleteAs( + (IO.ref(0), IO.ref(0)) + .mapN { (acquired, released) => + val r = Resource.make(acquired.update(_ + 1).void)(_ => released.update(_ + 1)) + def acquiredMustBe(i: Int) = acquired.get.map(v => assertEquals(v, i)).void + def releasedMustBe(i: Int) = released.get.map(v => assertEquals(v, i)).void + r.memoize.use { memo => + acquiredMustBe(0) *> releasedMustBe(0) *> + memo.surround(acquiredMustBe(1) *> releasedMustBe(0)) *> + acquiredMustBe(1) *> releasedMustBe(0) *> + memo.surround(acquiredMustBe(1) *> releasedMustBe(0)) *> + acquiredMustBe(1) *> releasedMustBe(0) + } *> acquiredMustBe(1) *> releasedMustBe(1) + } + .flatten + .void, + () + ) + } + ticked("Concurrent[Resource] - memoize - does not allocate if not used") { + implicit ticker => + assertCompleteAs( + (IO.ref(0), IO.ref(0)) + .mapN { (acquired, released) => + val r = Resource.make(acquired.update(_ + 1).void)(_ => released.update(_ + 1)) + def acquiredMustBe(i: Int) = acquired.get.map(v => assertEquals(v, i)).void + def releasedMustBe(i: Int) = released.get.map(v => assertEquals(v, i)).void + r.memoize.surround(acquiredMustBe(0) *> releasedMustBe(0)) *> + acquiredMustBe(0) *> releasedMustBe(0) + } + .flatten + .void, + () + ) + } + ticked("Concurrent[Resource] - memoize - does not leak if canceled") { implicit ticker => + assertCompleteAs( (IO.ref(0), IO.ref(0)).flatMapN { (acquired, released) => val r = Resource.make(IO.sleep(2.seconds) *> acquired.update(_ + 1).void) { _ => released.update(_ + 1) } - def acquiredMustBe(i: Int) = acquired.get.map(_ must be_==(i)).void - def releasedMustBe(i: Int) = released.get.map(_ must be_==(i)).void + def acquiredMustBe(i: Int) = acquired.get.map(v => assertEquals(v, i)).void + def releasedMustBe(i: Int) = released.get.map(v => assertEquals(v, i)).void r.memoize.use { memo => memo.timeoutTo(1.second, Resource.unit[IO]).use_ } *> acquiredMustBe(1) *> releasedMustBe(1) - }.void must completeAs(()) - } + }.void, + () + ) } // TODO enable once `PureConc` finalizer bug is fixed. - "does not leak if canceled right after delayed acquire is canceled" in { + test( + "Concurrent[Resource] - does not leak if canceled right after delayed acquire is canceled".ignore) { import cats.effect.kernel.testkit.pure._ type F[A] = PureConc[Throwable, A] val F = Concurrent[F] @@ -1192,92 +1218,91 @@ class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { } yield acquireRun && releaseRun run(go) == Outcome.succeeded(Some(true)) - }.pendingUntilFixed + } } - "attempt" >> { - - "releases resource on error" in ticked { implicit ticker => + ticked("attempt - releases resource on error") { implicit ticker => + assertCompleteAs( IO.ref(0) .flatMap { ref => val resource = Resource.make(ref.update(_ + 1))(_ => ref.update(_ + 1)) val error = Resource.raiseError[IO, Unit, Throwable](new Exception) (resource *> error).attempt.use { r => - IO(r must beLeft) *> ref.get.map { _ must be_==(2) } + IO(assert(r.isLeft)) *> ref.get.map { v => assertEquals(v, 2) } } } - .void must completeAs(()) - } + .void, + () + ) + } - "acquire is interruptible" in ticked { implicit ticker => - val sleep = IO.never - val timeout = 500.millis + ticked("attempt - acquire is interruptible") { implicit ticker => + val sleep = IO.never + val timeout = 500.millis + assertCompleteAs( IO.ref(false).flatMap { ref => val r = Resource.makeFull[IO, Unit] { poll => poll(sleep).onCancel(ref.set(true)) }(_ => IO.unit) r.attempt.timeout(timeout).attempt.use_ *> ref.get - } must completeAs(true) - } + }, + true + ) } - "uncancelable" >> { - "does not suppress errors within use" in real { - case object TestException extends RuntimeException + real("uncancelable - does not suppress errors within use") { + case object TestException extends RuntimeException - for { - slot <- IO.deferred[Resource.ExitCase] - rsrc = Resource.makeCase(IO.unit)((_, ec) => slot.complete(ec).void) - _ <- rsrc.uncancelable.use(_ => IO.raiseError(TestException)).handleError(_ => ()) - results <- slot.get - - _ <- IO { - results mustEqual Resource.ExitCase.Errored(TestException) - } - } yield ok - } + for { + slot <- IO.deferred[Resource.ExitCase] + rsrc = Resource.makeCase(IO.unit)((_, ec) => slot.complete(ec).void) + _ <- rsrc.uncancelable.use(_ => IO.raiseError(TestException)).handleError(_ => ()) + results <- slot.get - "use is stack-safe over binds" in ticked { implicit ticker => - val res = Resource.make(IO.unit)(_ => IO.unit) - val r = (1 to 50000) - .foldLeft(res) { - case (r, _) => - r.flatMap(_ => res) - } - .uncancelable - .use_ - r eqv IO.unit - } + _ <- IO { + assertEquals(results, Resource.ExitCase.Errored(TestException)) + } + } yield () + } + ticked("uncancelable - use is stack-safe over binds") { implicit ticker => + val res = Resource.make(IO.unit)(_ => IO.unit) + val r = (1 to 50000) + .foldLeft(res) { + case (r, _) => + r.flatMap(_ => res) + } + .uncancelable + .use_ + r eqv IO.unit } - "allocatedCase" >> { - "is stack-safe over binds" in ticked { implicit ticker => - val res = Resource.make(IO.unit)(_ => IO.unit) - val r = (1 to 50000) - .foldLeft(res) { - case (r, _) => - r.flatMap(_ => res) - } - .allocatedCase - .map(_._1) - r eqv IO.unit + ticked("allocatedCase - is stack-safe over binds") { implicit ticker => + val res = Resource.make(IO.unit)(_ => IO.unit) + val r = (1 to 50000) + .foldLeft(res) { + case (r, _) => + r.flatMap(_ => res) + } + .allocatedCase + .map(_._1) + r eqv IO.unit - } } - "Resource[Resource[IO, *], *]" should { - "flatten with finalizers inside-out" in ticked { implicit ticker => + ticked("Resource[Resource[IO, *], *] - flatten with finalizers inside-out") { + implicit ticker => var results = "" val r: Resource[Resource[IO, *], Int] = Resource.make(Resource.make(IO.pure(42))(_ => IO(results += "a")))(_ => Resource.eval(IO(results += "b"))) - r.flattenK.use(IO.pure(_)) must completeAs(42) - results mustEqual "ab" - } + assertCompleteAs(r.flattenK.use(IO.pure(_)), 42) + assertEquals(results, "ab") + } - "flattenK is stack-safe over binds" in ticked { implicit ticker => + ticked("Resource[Resource[IO, *], *] - flattenK is stack-safe over binds") { + implicit ticker => val res = Resource.make(IO.unit)(_ => IO.unit) val r: Resource[IO, Unit] = (1 to 50000).foldLeft(res) { case (r, _) => { @@ -1286,8 +1311,6 @@ class ResourceSuite extends BaseSpec with ScalaCheck with Discipline { } r.use_ eqv IO.unit - } - } { diff --git a/tests/shared/src/test/scala/cats/effect/Runners.scala b/tests/shared/src/test/scala/cats/effect/Runners.scala index 979d15c25d..c6a315e8cf 100644 --- a/tests/shared/src/test/scala/cats/effect/Runners.scala +++ b/tests/shared/src/test/scala/cats/effect/Runners.scala @@ -20,88 +20,108 @@ import cats.{Eq, Show} import cats.effect.testkit.{TestContext, TestInstances} import cats.effect.unsafe.IORuntime import cats.syntax.all._ -import munit.TestOptions + import org.scalacheck.Gen -import org.specs2.execute.AsResult -import org.specs2.matcher.Matcher -import org.specs2.mutable.SpecificationLike -import org.specs2.specification.core.Execution import scala.concurrent.{Future, Promise} import scala.concurrent.duration._ -import scala.reflect.ClassTag +import scala.reflect.{classTag, ClassTag} + +import munit.{FunSuite, Location, TestOptions} +import munit.internal.PlatformCompat -trait Runners extends SpecificationLike with TestInstances with RunnersPlatform { outer => +trait Runners extends TestInstances with RunnersPlatform { + self: FunSuite => - def executionTimeout = 20.seconds + def executionTimeout: FiniteDuration = 20.seconds - def ticked[A: AsResult](test: Ticker => A): Execution = - Execution.result(test(Ticker(TestContext()))) + def ticked(options: TestOptions)(body: Ticker => Any)(implicit loc: Location): Unit = + test(options)(body(Ticker(TestContext()))) - def real[A: AsResult](test: => IO[A]): Execution = - Execution.withEnvAsync { _ => - val (fut, cancel) = test.unsafeToFutureCancelable()(runtime()) + def real[A](options: TestOptions)(body: => IO[A])(implicit loc: Location): Unit = + test(options) { + val (fut, cancel) = body.unsafeToFutureCancelable()(runtime()) timeout(fut, cancel, executionTimeout) } /* * Hacky implementation of effectful property testing */ - def realProp[A, B](gen: Gen[A])(f: A => IO[B])(implicit R: AsResult[List[B]]): Execution = - real(List.range(1, 100).traverse { _ => + def realProp[A, B](options: TestOptions, gen: Gen[A])(f: A => IO[B])( + implicit loc: Location): Unit = + real(options)(List.range(1, 100).traverse_ { _ => val a = gen.sample.get f(a) }) - def realWithRuntime[A: AsResult](test: IORuntime => IO[A]): Execution = - Execution.withEnvAsync { _ => + def realWithRuntime[A](options: TestOptions)(f: IORuntime => IO[A])( + implicit loc: Location): Unit = + test(options) { val rt = runtime() - val (fut, cancel) = test(rt).unsafeToFutureCancelable()(rt) + val (fut, cancel) = f(rt).unsafeToFutureCancelable()(rt) timeout(fut, cancel, executionTimeout) } - def completeAs[A: Eq: Show](expected: A)(implicit ticker: Ticker): Matcher[IO[A]] = - tickTo(Outcome.Succeeded(Some(expected))) + def assertCompleteAs[A: Eq: Show](ioa: IO[A], expected: A)( + implicit ticker: Ticker, + loc: Location): Unit = + assertTickTo(ioa, Outcome.Succeeded(Some(expected))) - def completeAsSync[A: Eq: Show](expected: A): Matcher[SyncIO[A]] = { (ioa: SyncIO[A]) => + def assertCompleteAsSync[A: Eq: Show](ioa: SyncIO[A], expected: A)( + implicit loc: Location): Unit = { val a = ioa.unsafeRunSync() - (a eqv expected, s"${a.show} !== ${expected.show}") + assert(a eqv expected, s"${a.show} !== ${expected.show}") } - def failAs(expected: Throwable)(implicit ticker: Ticker): Matcher[IO[Unit]] = - tickTo[Unit](Outcome.Errored(expected)) + def assertFailAs(io: IO[Unit], expected: Throwable)( + implicit ticker: Ticker, + loc: Location): Unit = + assertTickTo[Unit](io, Outcome.Errored(expected)) - def failAsSync[A](expected: Throwable): Matcher[SyncIO[A]] = { (ioa: SyncIO[A]) => + def assertFailAsSync[A](ioa: SyncIO[A], expected: Throwable)(implicit loc: Location): Unit = { val t = (try ioa.unsafeRunSync() catch { case t: Throwable => t }).asInstanceOf[Throwable] - (t eqv expected, s"${t.show} !== ${expected.show}") + assert(t eqv expected, s"${t.show} !== ${expected.show}") } - def nonTerminate(implicit ticker: Ticker): Matcher[IO[Unit]] = - tickTo[Unit](Outcome.Succeeded(None)) + def assertNonTerminate(io: IO[Unit])(implicit ticker: Ticker, loc: Location): Unit = + assertTickTo[Unit](io, Outcome.Succeeded(None)) - def selfCancel(implicit ticker: Ticker): Matcher[IO[Unit]] = - tickTo[Unit](Outcome.Canceled()) + def assertSelfCancel(io: IO[Unit])(implicit ticker: Ticker, loc: Location): Unit = + assertTickTo[Unit](io, Outcome.Canceled()) - def tickTo[A: Eq: Show](expected: Outcome[Option, Throwable, A])( - implicit ticker: Ticker): Matcher[IO[A]] = { (ioa: IO[A]) => + def assertTickTo[A: Eq: Show](ioa: IO[A], expected: Outcome[Option, Throwable, A])( + implicit ticker: Ticker, + loc: Location): Unit = { val oc = unsafeRun(ioa) - (oc eqv expected, s"${oc.show} !== ${expected.show}") + assert(oc eqv expected, s"${oc.show} !== ${expected.show}") + } + + implicit class TestOptionsSyntax(options: TestOptions) { + // todo: munit 1.0 has `options.pending` + def pendingNative: TestOptions = if (PlatformCompat.isNative) options.ignore else options + def ignoreNative: TestOptions = if (PlatformCompat.isNative) options.ignore else options + def ignoreJS: TestOptions = if (PlatformCompat.isJS) options.ignore else options } // useful for tests in the `real` context implicit class Assertions[A](fa: IO[A]) { - def mustFailWith[E <: Throwable: ClassTag] = + def mustFailWith[E <: Throwable: ClassTag](implicit loc: Location) = fa.attempt.flatMap { res => IO { - res must beLike { case Left(e) => e must haveClass[E] } + res match { + case Left(e) => assert(classTag[E].runtimeClass.isAssignableFrom(e.getClass)) + case Right(value) => + fail( + s"Expected Left(${classTag[E].runtimeClass.getSimpleName}) got Right($value)") + } } } - def mustEqual(a: A) = fa.flatMap { res => IO(res must beEqualTo(a)) } + def mustEqual(a: A)(implicit loc: Location) = fa.flatMap { res => IO(assertEquals(res, a)) } } private def timeout[A]( @@ -133,28 +153,4 @@ trait Runners extends SpecificationLike with TestInstances with RunnersPlatform } } -trait MUnitRunners extends TestInstances with MUnitRunnersPlatform { - self: munit.FunSuite => - - def executionTimeout: FiniteDuration = 20.seconds - - def ticked(options: TestOptions)(body: Ticker => Any)(implicit loc: munit.Location): Unit = - test(options)(body(Ticker(TestContext()))) - - def assertCompleteAs[A: Eq: Show](ioa: IO[A], expected: A)(implicit ticker: Ticker): Unit = - tickTo(ioa, Outcome.Succeeded(Some(expected))) - - /*def completeAsSync[A: Eq: Show](expected: A): Matcher[SyncIO[A]] = { (ioa: SyncIO[A]) => - val a = ioa.unsafeRunSync() - (a eqv expected, s"${a.show} !== ${expected.show}") - }*/ - - def tickTo[A: Eq: Show](ioa: IO[A], expected: Outcome[Option, Throwable, A])( - implicit ticker: Ticker): Unit = { - val oc = unsafeRun(ioa) - assert(oc eqv expected, s"${oc.show} !== ${expected.show}") - } - -} - class TestTimeoutException extends Exception diff --git a/tests/shared/src/test/scala/cats/effect/StateTIOSuite.scala b/tests/shared/src/test/scala/cats/effect/StateTIOSuite.scala index 72464a4013..b0c523eed9 100644 --- a/tests/shared/src/test/scala/cats/effect/StateTIOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/StateTIOSuite.scala @@ -25,37 +25,33 @@ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import org.scalacheck.Prop -import org.specs2.scalacheck.Parameters -import org.typelevel.discipline.specs2.mutable.Discipline -class StateTIOSuite extends BaseSpec with Discipline { +import munit.DisciplineSuite - sequential +class StateTIOSuite extends BaseSuite with DisciplineSuite { - "StateT" should { - "execute finalizers" in ticked { implicit ticker => - var guaranteed = false + ticked("execute finalizers") { implicit ticker => + var guaranteed = false - val test = for { - _ <- StateT.set[IO, String]("state").guarantee(StateT.liftF(IO { guaranteed = true })) - g <- StateT.liftF(IO(guaranteed)) - } yield g + val test = for { + _ <- StateT.set[IO, String]("state").guarantee(StateT.liftF(IO { guaranteed = true })) + g <- StateT.liftF(IO(guaranteed)) + } yield g - test.runA("") must completeAs(true) - } + assertCompleteAs(test.runA(""), true) + } - "execute finalizers when doubly nested" in ticked { implicit ticker => - var guaranteed = false + ticked("execute finalizers when doubly nested") { implicit ticker => + var guaranteed = false - val test = for { - _ <- StateT - .set[OptionT[IO, *], String]("state") - .guarantee(StateT.liftF(OptionT.liftF(IO { guaranteed = true }))) - g <- StateT.liftF(OptionT.liftF(IO(guaranteed))) - } yield g + val test = for { + _ <- StateT + .set[OptionT[IO, *], String]("state") + .guarantee(StateT.liftF(OptionT.liftF(IO { guaranteed = true }))) + g <- StateT.liftF(OptionT.liftF(IO(guaranteed))) + } yield g - test.runA("").value must completeAs(Some(true)) - } + assertCompleteAs(test.runA("").value, Some(true)) } implicit def stateTEq[F[_]: FlatMap, S, A]( @@ -74,7 +70,6 @@ class StateTIOSuite extends BaseSpec with Discipline { { implicit val ticker = Ticker() - checkAll("StateT[IO]", SyncTests[StateT[IO, MiniInt, *]].sync[Int, Int, Int])( - Parameters(minTestsOk = 25)) + checkAll("StateT[IO]", SyncTests[StateT[IO, MiniInt, *]].sync[Int, Int, Int]) } } diff --git a/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala b/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala index 20d10f4bb5..6d801d4937 100644 --- a/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala @@ -24,207 +24,219 @@ import cats.laws.discipline.arbitrary._ import cats.syntax.all._ import org.scalacheck.Prop.forAll -import org.typelevel.discipline.specs2.mutable.Discipline -class SyncIOSuite extends BaseSpec with Discipline with SyncIOPlatformSpecification { +import munit.DisciplineSuite - "sync io monad" should { - "produce a pure value when run" in { - SyncIO.pure(42) must completeAsSync(42) - } +class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuite { - "suspend a side-effect without memoizing" in { - var i = 42 + test("produce a pure value when run") { + assertCompleteAsSync(SyncIO.pure(42), 42) + } - val ioa = SyncIO { - i += 1 - i - } + test("suspend a side-effect without memoizing") { + var i = 42 - ioa must completeAsSync(43) - ioa must completeAsSync(44) + val ioa = SyncIO { + i += 1 + i } - "capture errors in suspensions" in { - case object TestException extends RuntimeException - SyncIO(throw TestException) must failAsSync(TestException) - } + assertCompleteAsSync(ioa, 43) + assertCompleteAsSync(ioa, 44) + } - "map results to a new type" in { - SyncIO.pure(42).map(_.toString) must completeAsSync("42") - } + test("capture errors in suspensions") { + case object TestException extends RuntimeException + assertFailAsSync(SyncIO(throw TestException), TestException) + } - "flatMap results sequencing both effects" in { - var i = 0 - SyncIO.pure(42).flatMap(i2 => SyncIO { i = i2 }) must completeAsSync(()) - i mustEqual 42 - } + test("map results to a new type") { + assertCompleteAsSync(SyncIO.pure(42).map(_.toString), "42") + } - "raiseError propagates out" in { - case object TestException extends RuntimeException - SyncIO.raiseError(TestException).void.flatMap(_ => SyncIO.pure(())) must failAsSync( - TestException) - } + test("flatMap results sequencing both effects") { + var i = 0 + assertCompleteAsSync(SyncIO.pure(42).flatMap(i2 => SyncIO { i = i2 }), ()) + assertEquals(i, 42) + } - "errors can be handled" in { - case object TestException extends RuntimeException - SyncIO.raiseError[Unit](TestException).attempt must completeAsSync(Left(TestException)) - } + test("raiseError propagates out") { + case object TestException extends RuntimeException + assertFailAsSync( + SyncIO.raiseError(TestException).void.flatMap(_ => SyncIO.pure(())), + TestException) + } - "attempt is redeem with Left(_) for recover and Right(_) for map" in { - forAll { (io: SyncIO[Int]) => io.attempt eqv io.redeem(Left(_), Right(_)) } - } + test("errors can be handled") { + case object TestException extends RuntimeException + assertCompleteAsSync(SyncIO.raiseError[Unit](TestException).attempt, Left(TestException)) + } - "attempt is flattened redeemWith" in { - forAll { - (io: SyncIO[Int], recover: Throwable => SyncIO[String], bind: Int => SyncIO[String]) => - io.attempt.flatMap(_.fold(recover, bind)) eqv io.redeemWith(recover, bind) - } - } + test("attempt is redeem with Left(_) for recover and Right(_) for map") { + forAll { (io: SyncIO[Int]) => io.attempt eqv io.redeem(Left(_), Right(_)) } + } - "redeem is flattened redeemWith" in { - forAll { - (io: SyncIO[Int], recover: Throwable => SyncIO[String], bind: Int => SyncIO[String]) => - io.redeem(recover, bind).flatMap(identity) eqv io.redeemWith(recover, bind) - } + test("attempt is flattened redeemWith") { + forAll { + (io: SyncIO[Int], recover: Throwable => SyncIO[String], bind: Int => SyncIO[String]) => + io.attempt.flatMap(_.fold(recover, bind)) eqv io.redeemWith(recover, bind) } + } - "redeem subsumes handleError" in { - forAll { (io: SyncIO[Int], recover: Throwable => Int) => - io.redeem(recover, identity) eqv io.handleError(recover) - } + test("redeem is flattened redeemWith") { + forAll { + (io: SyncIO[Int], recover: Throwable => SyncIO[String], bind: Int => SyncIO[String]) => + io.redeem(recover, bind).flatMap(identity) eqv io.redeemWith(recover, bind) } + } - "redeemWith subsumes handleErrorWith" in { - forAll { (io: SyncIO[Int], recover: Throwable => SyncIO[Int]) => - io.redeemWith(recover, SyncIO.pure) eqv io.handleErrorWith(recover) - } + test("redeem subsumes handleError") { + forAll { (io: SyncIO[Int], recover: Throwable => Int) => + io.redeem(recover, identity) eqv io.handleError(recover) } + } - "redeem correctly recovers from errors" in { - case object TestException extends RuntimeException - SyncIO.raiseError[Unit](TestException).redeem(_ => 42, _ => 43) must completeAsSync(42) + test("redeemWith subsumes handleErrorWith") { + forAll { (io: SyncIO[Int], recover: Throwable => SyncIO[Int]) => + io.redeemWith(recover, SyncIO.pure) eqv io.handleErrorWith(recover) } + } - "redeem maps successful results" in { - SyncIO.unit.redeem(_ => 41, _ => 42) must completeAsSync(42) - } + test("redeem correctly recovers from errors") { + case object TestException extends RuntimeException + assertCompleteAsSync(SyncIO.raiseError[Unit](TestException).redeem(_ => 42, _ => 43), 42) + } - "redeem catches exceptions thrown in recovery function" in { - case object TestException extends RuntimeException - case object ThrownException extends RuntimeException + test("redeem maps successful results") { + assertCompleteAsSync(SyncIO.unit.redeem(_ => 41, _ => 42), 42) + } + + test("redeem catches exceptions thrown in recovery function") { + case object TestException extends RuntimeException + case object ThrownException extends RuntimeException + assertCompleteAsSync( SyncIO .raiseError[Unit](TestException) .redeem(_ => throw ThrownException, _ => 42) - .attempt must completeAsSync(Left(ThrownException)) - } + .attempt, + Left(ThrownException)) + } - "redeem catches exceptions thrown in map function" in { - case object ThrownException extends RuntimeException - SyncIO.unit.redeem(_ => 41, _ => throw ThrownException).attempt must completeAsSync( - Left(ThrownException)) - } + test("redeem catches exceptions thrown in map function") { + case object ThrownException extends RuntimeException + assertCompleteAsSync( + SyncIO.unit.redeem(_ => 41, _ => throw ThrownException).attempt, + Left(ThrownException)) + } - "redeemWith correctly recovers from errors" in { - case object TestException extends RuntimeException + test("redeemWith correctly recovers from errors") { + case object TestException extends RuntimeException + assertCompleteAsSync( SyncIO .raiseError[Unit](TestException) - .redeemWith(_ => SyncIO.pure(42), _ => SyncIO.pure(43)) must completeAsSync(42) - } + .redeemWith(_ => SyncIO.pure(42), _ => SyncIO.pure(43)), + 42) + } - "redeemWith binds successful results" in { - SyncIO.unit.redeemWith(_ => SyncIO.pure(41), _ => SyncIO.pure(42)) must completeAsSync(42) - } + test("redeemWith binds successful results") { + assertCompleteAsSync(SyncIO.unit.redeemWith(_ => SyncIO.pure(41), _ => SyncIO.pure(42)), 42) + } - "redeemWith catches exceptions throw in recovery function" in { - case object TestException extends RuntimeException - case object ThrownException extends RuntimeException + test("redeemWith catches exceptions throw in recovery function") { + case object TestException extends RuntimeException + case object ThrownException extends RuntimeException + assertCompleteAsSync( SyncIO .raiseError[Unit](TestException) .redeemWith(_ => throw ThrownException, _ => SyncIO.pure(42)) - .attempt must completeAsSync(Left(ThrownException)) - } + .attempt, + Left(ThrownException)) + } - "redeemWith catches exceptions thrown in bind function" in { - case object ThrownException extends RuntimeException - SyncIO - .unit - .redeem(_ => SyncIO.pure(41), _ => throw ThrownException) - .attempt must completeAsSync(Left(ThrownException)) - } + test("redeemWith catches exceptions thrown in bind function") { + case object ThrownException extends RuntimeException + assertCompleteAsSync( + SyncIO.unit.redeem(_ => SyncIO.pure(41), _ => throw ThrownException).attempt, + Left(ThrownException)) + } - "evaluate 10,000 consecutive map continuations" in { - def loop(i: Int): SyncIO[Unit] = - if (i < 10000) - SyncIO.unit.flatMap(_ => loop(i + 1)).map(u => u) - else - SyncIO.unit + test("evaluate 10,000 consecutive map continuations") { + def loop(i: Int): SyncIO[Unit] = + if (i < 10000) + SyncIO.unit.flatMap(_ => loop(i + 1)).map(u => u) + else + SyncIO.unit - loop(0) must completeAsSync(()) - } + assertCompleteAsSync(loop(0), ()) + } - "evaluate 10,000 consecutive handleErrorWith continuations" in { - def loop(i: Int): SyncIO[Unit] = - if (i < 10000) - SyncIO.unit.flatMap(_ => loop(i + 1)).handleErrorWith(SyncIO.raiseError(_)) - else - SyncIO.unit + test("evaluate 10,000 consecutive handleErrorWith continuations") { + def loop(i: Int): SyncIO[Unit] = + if (i < 10000) + SyncIO.unit.flatMap(_ => loop(i + 1)).handleErrorWith(SyncIO.raiseError(_)) + else + SyncIO.unit - loop(0) must completeAsSync(()) - } + assertCompleteAsSync(loop(0), ()) + } - "catch exceptions thrown in map functions" in { - case object TestException extends RuntimeException - SyncIO.unit.map(_ => (throw TestException): Unit).attempt must completeAsSync( - Left(TestException)) - } + test("catch exceptions thrown in map functions") { + case object TestException extends RuntimeException + assertCompleteAsSync( + SyncIO.unit.map(_ => (throw TestException): Unit).attempt, + Left(TestException)) + } - "catch exceptions thrown in flatMap functions" in { - case object TestException extends RuntimeException - SyncIO.unit.flatMap(_ => (throw TestException): SyncIO[Unit]).attempt must completeAsSync( - Left(TestException)) - } + test("catch exceptions thrown in flatMap functions") { + case object TestException extends RuntimeException + assertCompleteAsSync( + SyncIO.unit.flatMap(_ => (throw TestException): SyncIO[Unit]).attempt, + Left(TestException)) + } - "catch exceptions thrown in handleErrorWith functions" in { - case object TestException extends RuntimeException - case object WrongException extends RuntimeException + test("catch exceptions thrown in handleErrorWith functions") { + case object TestException extends RuntimeException + case object WrongException extends RuntimeException + assertCompleteAsSync( SyncIO .raiseError[Unit](WrongException) .handleErrorWith(_ => (throw TestException): SyncIO[Unit]) - .attempt must completeAsSync(Left(TestException)) - } + .attempt, + Left(TestException)) + } - "preserve monad right identity on uncancelable" in { - val fa = MonadCancel[SyncIO].uncancelable(_ => MonadCancel[SyncIO].canceled) - fa.flatMap(SyncIO.pure(_)) must completeAsSync(()) - fa must completeAsSync(()) - } + test("preserve monad right identity on uncancelable") { + val fa = MonadCancel[SyncIO].uncancelable(_ => MonadCancel[SyncIO].canceled) + assertCompleteAsSync(fa.flatMap(SyncIO.pure(_)), ()) + assertCompleteAsSync(fa, ()) + } - "cancel flatMap continuations following a canceled uncancelable block" in { + test("cancel flatMap continuations following a canceled uncancelable block") { + assertCompleteAsSync( MonadCancel[SyncIO] .uncancelable(_ => MonadCancel[SyncIO].canceled) - .flatMap(_ => SyncIO.pure(())) must completeAsSync(()) - } + .flatMap(_ => SyncIO.pure(())), + ()) + } - "cancel map continuations following a canceled uncancelable block" in { - MonadCancel[SyncIO] - .uncancelable(_ => MonadCancel[SyncIO].canceled) - .map(_ => ()) must completeAsSync(()) - } + test("cancel map continuations following a canceled uncancelable block") { + assertCompleteAsSync( + MonadCancel[SyncIO].uncancelable(_ => MonadCancel[SyncIO].canceled).map(_ => ()), + ()) + } - "lift a SyncIO into IO" in realProp(arbitrarySyncIO[Int].arbitrary) { sio => - val io = sio.to[IO] + realProp("lift a SyncIO into IO", arbitrarySyncIO[Int].arbitrary) { sio => + val io = sio.to[IO] - for { - res1 <- IO.delay(sio.unsafeRunSync()).attempt - res2 <- io.attempt - res <- IO.delay(res1 mustEqual res2) - } yield res - } + for { + res1 <- IO.delay(sio.unsafeRunSync()).attempt + res2 <- io.attempt + res <- IO.delay(assertEquals(res1, res2)) + } yield res + } - "serialize" in { - forAll { (io: SyncIO[Int]) => serializable(io) } - } + test("serialize") { + forAll { (io: SyncIO[Int]) => serializable(io) } } { @@ -248,5 +260,5 @@ class SyncIOSuite extends BaseSpec with Discipline with SyncIOPlatformSpecificat ) } - platformSpecs + platformTests() } diff --git a/tests/shared/src/test/scala/cats/effect/ThunkSuite.scala b/tests/shared/src/test/scala/cats/effect/ThunkSuite.scala index 33b3ad9acd..bf8a66400a 100644 --- a/tests/shared/src/test/scala/cats/effect/ThunkSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/ThunkSuite.scala @@ -16,14 +16,12 @@ package cats.effect -class ThunkSuite extends BaseSpec { +class ThunkSuite extends BaseSuite { - "IO.delay" should { - "return the same function" in { - var i = 0 - val f = () => i += 1 - IO.delay(f()).asInstanceOf[IO.Delay[Unit]].thunk eq f - } + test("return the same function") { + var i = 0 + val f = () => i += 1 + IO.delay(f()).asInstanceOf[IO.Delay[Unit]].thunk eq f } } diff --git a/tests/shared/src/test/scala/cats/effect/WriterTIOSuite.scala b/tests/shared/src/test/scala/cats/effect/WriterTIOSuite.scala index de2a3d26c3..b98f6c760a 100644 --- a/tests/shared/src/test/scala/cats/effect/WriterTIOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/WriterTIOSuite.scala @@ -24,51 +24,48 @@ import cats.laws.discipline.arbitrary._ import cats.syntax.all._ import org.scalacheck.Prop -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.duration._ -class WriterTIOSuite extends BaseSpec with Discipline { +import munit.DisciplineSuite + +class WriterTIOSuite extends BaseSuite with DisciplineSuite { // we just need this because of the laws testing, since the prop runs can interfere with each other - sequential - - "WriterT" should { - "execute finalizers" in ticked { implicit ticker => - type F[A] = WriterT[IO, Chain[String], A] - - val test = for { - gate <- Deferred[F, Unit] - _ <- WriterT - .tell[IO, Chain[String]](Chain.one("hello")) - .guarantee(gate.complete(()).void) - .start - _ <- gate.get - } yield () - - test.run._2F must completeAs(()) - } - - "execute finalizers when doubly nested" in ticked { implicit ticker => - type F[A] = WriterT[OptionT[IO, *], Chain[String], A] - - val test = for { - gate1 <- Deferred[F, Unit] - gate2 <- Deferred[F, Unit] - _ <- WriterT - .tell[OptionT[IO, *], Chain[String]](Chain.one("hello")) - .guarantee(gate1.complete(()).void) - .start - _ <- WriterT - .liftF[OptionT[IO, *], Chain[String], Unit](OptionT.none) - .guarantee(gate2.complete(()).void) - .start - _ <- gate1.get - _ <- gate2.get - } yield () - - test.run._2F.value must completeAs(Some(())) - } + ticked("execute finalizers") { implicit ticker => + type F[A] = WriterT[IO, Chain[String], A] + + val test = for { + gate <- Deferred[F, Unit] + _ <- WriterT + .tell[IO, Chain[String]](Chain.one("hello")) + .guarantee(gate.complete(()).void) + .start + _ <- gate.get + } yield () + + assertCompleteAs(test.run._2F, ()) + } + + ticked("execute finalizers when doubly nested") { implicit ticker => + type F[A] = WriterT[OptionT[IO, *], Chain[String], A] + + val test = for { + gate1 <- Deferred[F, Unit] + gate2 <- Deferred[F, Unit] + _ <- WriterT + .tell[OptionT[IO, *], Chain[String]](Chain.one("hello")) + .guarantee(gate1.complete(()).void) + .start + _ <- WriterT + .liftF[OptionT[IO, *], Chain[String], Unit](OptionT.none) + .guarantee(gate2.complete(()).void) + .start + _ <- gate1.get + _ <- gate2.get + } yield () + + assertCompleteAs(test.run._2F.value, Some(())) } implicit def ordWriterTIOFD( diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSuite.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSuite.scala index 802dd22b3b..74c7ed1da5 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSuite.scala @@ -26,17 +26,15 @@ import cats.laws.discipline.arbitrary._ import org.scalacheck.{Arbitrary, Cogen, Prop} import org.scalacheck.Arbitrary.arbitrary -import org.typelevel.discipline.specs2.mutable.Discipline import scala.concurrent.{ExecutionContext, Promise} import scala.concurrent.duration._ import java.util.concurrent.atomic.AtomicBoolean -class AsyncSuite extends BaseSpec with Discipline { +import munit.DisciplineSuite - // we just need this because of the laws testing, since the prop runs can interfere with each other - sequential +class AsyncSuite extends BaseSuite with DisciplineSuite { { implicit val ticker = Ticker() @@ -47,77 +45,70 @@ class AsyncSuite extends BaseSpec with Discipline { ) /*(Parameters(seed = Some(Seed.fromBase64("ZxDXpm7_3Pdkl-Fvt8M90Cxfam9wKuzcifQ1QsIJxND=").get)))*/ } - "fromFuture" should { - "backpressure on cancelation" in real { - // a non-cancelable, never-completing Future - def mkf() = Promise[Unit]().future - - def go = for { - started <- IO(new AtomicBoolean) - fiber <- IO.fromFuture { - IO { - started.set(true) - mkf() - } - }.start - _ <- IO.cede.whileM_(IO(!started.get)) - _ <- fiber.cancel - } yield () - - TestControl - .executeEmbed(go, IORuntimeConfig(1, 2)) - .as(false) - .recover { case _: TestControl.NonTerminationException => true } - .replicateA(100) - .map(_.forall(identity(_))) - } + real("fromFuture should backpressure on cancelation") { + // a non-cancelable, never-completing Future + def mkf() = Promise[Unit]().future + def go = for { + started <- IO(new AtomicBoolean) + fiber <- IO.fromFuture { + IO { + started.set(true) + mkf() + } + }.start + _ <- IO.cede.whileM_(IO(!started.get)) + _ <- fiber.cancel + } yield () + + TestControl + .executeEmbed(go, IORuntimeConfig(1, 2)) + .as(false) + .recover { case _: TestControl.NonTerminationException => true } + .replicateA(100) + .map(_.forall(identity(_))) } - "fromFutureCancelable" should { - - "cancel on fiber cancelation" in real { - val smallDelay: IO[Unit] = IO.sleep(10.millis) - def mkf() = Promise[Unit]().future + real("fromFutureCancelable should cancel on fiber cancelation") { + val smallDelay: IO[Unit] = IO.sleep(10.millis) + def mkf() = Promise[Unit]().future - val go = for { - canceled <- IO(new AtomicBoolean) - fiber <- IO.fromFutureCancelable { - IO(mkf()).map(f => f -> IO(canceled.set(true))) - }.start - _ <- smallDelay - _ <- fiber.cancel - res <- IO(canceled.get() mustEqual true) - } yield res + val go = for { + canceled <- IO(new AtomicBoolean) + fiber <- IO.fromFutureCancelable { + IO(mkf()).map(f => f -> IO(canceled.set(true))) + }.start + _ <- smallDelay + _ <- fiber.cancel + res <- IO(assert(canceled.get())) + } yield res - TestControl.executeEmbed(go, IORuntimeConfig(1, 2)).replicateA(1000) + TestControl.executeEmbed(go, IORuntimeConfig(1, 2)).replicateA(1000) - } - - "backpressure on cancelation" in real { - // a non-cancelable, never-completing Future - def mkf() = Promise[Unit]().future - - val go = for { - started <- IO(new AtomicBoolean) - fiber <- IO.fromFutureCancelable { - IO { - started.set(true) - mkf() - }.map(f => f -> IO.never) - }.start - _ <- IO.cede.whileM_(IO(!started.get)) - _ <- fiber.cancel - } yield () - - TestControl - .executeEmbed(go, IORuntimeConfig(1, 2)) - .as(false) - .recover { case _: TestControl.NonTerminationException => true } - .replicateA(1000) - .map(_.forall(identity(_))) - } + } + real("fromFutureCancelable should backpressure on cancelation") { + // a non-cancelable, never-completing Future + def mkf() = Promise[Unit]().future + + val go = for { + started <- IO(new AtomicBoolean) + fiber <- IO.fromFutureCancelable { + IO { + started.set(true) + mkf() + }.map(f => f -> IO.never) + }.start + _ <- IO.cede.whileM_(IO(!started.get)) + _ <- fiber.cancel + } yield () + + TestControl + .executeEmbed(go, IORuntimeConfig(1, 2)) + .as(false) + .recover { case _: TestControl.NonTerminationException => true } + .replicateA(1000) + .map(_.forall(identity(_))) } final class AsyncIO[A](val io: IO[A]) diff --git a/tests/shared/src/test/scala/cats/effect/kernel/DeferredSuite.scala b/tests/shared/src/test/scala/cats/effect/kernel/DeferredSuite.scala index 52b0197c11..f6fa776107 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/DeferredSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/DeferredSuite.scala @@ -22,38 +22,37 @@ import cats.syntax.all._ import scala.concurrent.duration._ -class DeferredSuite extends BaseSpec with DetectPlatform { outer => +class DeferredSuite extends BaseSuite with DetectPlatform { outer => - "Deferred for Async" should { - tests(IO(Deferred.unsafe), IO(Deferred.unsafe)) - } + tests("Deferred for Async", IO(Deferred.unsafe), IO(Deferred.unsafe)) - "Deferred for IO" should { - tests(IO(new IODeferred), IO(new IODeferred)) - } + tests("Deferred for IO", IO(new IODeferred), IO(new IODeferred)) - def tests(deferredU: IO[Deferred[IO, Unit]], deferredI: IO[Deferred[IO, Int]]) = { - "complete" in real { + def tests( + name: String, + deferredU: IO[Deferred[IO, Unit]], + deferredI: IO[Deferred[IO, Int]]) = { + real(s"$name complete") { val op = deferredI.flatMap { p => p.complete(0) *> p.get } op.flatMap { res => IO { - res must beEqualTo(0) + assertEquals(res, 0) } } } - "complete is only successful once" in real { + real(s"$name complete is only successful once") { val op = deferredI.flatMap { p => p.complete(0) *> p.complete(1).product(p.get) } op.flatMap { res => IO { - res must beEqualTo((false, 0)) + assertEquals(res, (false, 0)) } } } - "get blocks until set" in real { + real(s"$name get blocks until set") { val op = for { state <- Ref[IO].of(0) modifyGate <- deferredU @@ -66,12 +65,12 @@ class DeferredSuite extends BaseSpec with DetectPlatform { outer => op.flatMap { res => IO { - res must beEqualTo(2) + assertEquals(res, 2) } } } - "concurrent - get - cancel before forcing" in real { + real(s"$name concurrent - get - cancel before forcing") { def cancelBeforeForcing: IO[Option[Int]] = for { r <- Ref[IO].of(Option.empty[Int]) @@ -94,22 +93,22 @@ class DeferredSuite extends BaseSpec with DetectPlatform { outer => cancelBeforeForcing.flatMap { res => IO { - res must beNone + assertEquals(res, None) } } } - "tryGet returns None for unset Deferred" in real { + real(s"$name tryGet returns None for unset Deferred") { val op = deferredU.flatMap(_.tryGet) op.flatMap { res => IO { - res must beNone + assertEquals(res, None) } } } - "tryGet returns Some() for set Deferred" in real { + real(s"$name tryGet returns Some() for set Deferred") { val op = for { d <- deferredU _ <- d.complete(()) @@ -118,12 +117,12 @@ class DeferredSuite extends BaseSpec with DetectPlatform { outer => op.flatMap { res => IO { - res must beEqualTo(Some(())) + assertEquals(res, Some(())) } } } - "issue #380: complete doesn't block, test #1" in real { + real(s"$name issue #380: complete doesn't block, test #1") { def execute(times: Int): IO[Boolean] = { def foreverAsync(i: Int): IO[Unit] = if (i == 512) IO.async[Unit] { cb => @@ -150,12 +149,12 @@ class DeferredSuite extends BaseSpec with DetectPlatform { outer => execute(100).flatMap { res => IO { - res must beTrue + assert(res) } } } - "issue #3283: complete must be uncancelable" in real { + real(s"$name issue #3283: complete must be uncancelable") { import cats.syntax.all._ @@ -166,18 +165,18 @@ class DeferredSuite extends BaseSpec with DetectPlatform { outer => IO.race(attemptCompletion(1), attemptCompletion(2)).void, d.get.void ).parSequence - r <- IO { (res == List((), ())) must beTrue } + r <- IO { assert(res == List((), ())) } } yield r } - "handle lots of canceled gets in parallel" in real { + real(s"$name handle lots of canceled gets in parallel") { List(10, 100, 1000) .traverse_ { n => deferredU .flatMap { d => (d.get.background.surround(IO.cede).replicateA_(n) *> d .complete(())).background.surround { - d.get.as(1).parReplicateA(n).map(_.sum must be_==(n)) + d.get.as(1).parReplicateA(n).map(r => assertEquals(r.sum, n)) } } .replicateA_(if (isJVM) 100 else 1) @@ -185,7 +184,7 @@ class DeferredSuite extends BaseSpec with DetectPlatform { outer => .as(true) } - "handle adversarial cancelations without loss of callbacks" in ticked { implicit ticker => + ticked("handle adversarial cancelations without loss of callbacks") { implicit ticker => val test = for { d <- deferredU @@ -205,7 +204,7 @@ class DeferredSuite extends BaseSpec with DetectPlatform { outer => _ <- remaining.toList.traverse_(fibers(_).join.void) } yield () - test must completeAs(()) + assertCompleteAs(test, ()) } } } diff --git a/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSuite.scala b/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSuite.scala index 985156c21e..7c32c0f8df 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSuite.scala @@ -17,94 +17,89 @@ package cats.effect.kernel import cats.data._ -import cats.effect.{BaseSpec, IO, SyncIO} - -import org.specs2.matcher.Matchers +import cats.effect.{BaseSuite, IO, SyncIO} import scala.reflect.ClassTag -class DerivationRefinementSuite extends BaseSpec with Matchers { +class DerivationRefinementSuite extends BaseSuite { type AsyncStack[F[_], A] = Kleisli[OptionT[EitherT[IorT[F, Int, *], String, *], *], Unit, A] type SyncStack[F[_], A] = StateT[ReaderWriterStateT[F, String, Int, Unit, *], Boolean, A] - "refined derived type class instances" >> { - - "returns Async for OptionT at runtime if possible" in { - check[IO, OptionT, Sync, Async] - check[IO, OptionT, Temporal, Async] - check[IO, OptionT, Concurrent, Async] - check[IO, OptionT, Spawn, Async] - check[IO, OptionT, MonadCancelThrow, Async] - } - - "returns Async for EitherT at runtime if possible" in { - type EitherTString[F[_], A] = EitherT[F, String, A] - check[IO, EitherTString, Sync, Async] - check[IO, EitherTString, Temporal, Async] - check[IO, EitherTString, Concurrent, Async] - check[IO, EitherTString, Spawn, Async] - check[IO, EitherTString, MonadCancelThrow, Async] - } - - "returns Async for Kleisli at runtime if possible" in { - type StringKleisli[F[_], A] = Kleisli[F, String, A] - check[IO, StringKleisli, Sync, Async] - check[IO, StringKleisli, Temporal, Async] - check[IO, StringKleisli, Concurrent, Async] - check[IO, StringKleisli, Spawn, Async] - check[IO, StringKleisli, MonadCancelThrow, Async] - } - - "returns Async for IorT at runtime if possible" in { - type StringIorT[F[_], A] = IorT[F, String, A] - check[IO, StringIorT, Sync, Async] - check[IO, StringIorT, Temporal, Async] - check[IO, StringIorT, Concurrent, Async] - check[IO, StringIorT, Spawn, Async] - check[IO, StringIorT, MonadCancelThrow, Async] - } - - "returns Async for WriterT at runtime if possible" in { - type StringWriterT[F[_], A] = WriterT[F, String, A] - check[IO, StringWriterT, Sync, Async] - check[IO, StringWriterT, Temporal, Async] - check[IO, StringWriterT, Concurrent, Async] - check[IO, StringWriterT, Spawn, Async] - check[IO, StringWriterT, MonadCancelThrow, Async] - } - - "returns Sync for StateT at runtime if possible" in { - type StringStateT[F[_], A] = StateT[F, String, A] - check[IO, StringStateT, MonadCancelThrow, Sync] - check[SyncIO, StringStateT, MonadCancelThrow, Sync] - } - - "returns Sync for ReaderWriterStateT at runtime if possible" in { - type TestRWST[F[_], A] = ReaderWriterStateT[F, String, Int, Unit, A] - check[IO, TestRWST, MonadCancelThrow, Sync] - check[SyncIO, TestRWST, MonadCancelThrow, Sync] - } - - "returns Async for stacked transformers at runtime if possible" in { - check[IO, AsyncStack, Sync, Async] - check[IO, AsyncStack, Temporal, Async] - check[IO, AsyncStack, Concurrent, Async] - check[IO, AsyncStack, Spawn, Async] - check[IO, AsyncStack, MonadCancelThrow, Async] - } - - "returns Sync for stacked transformers at runtime if possible" in { - check[IO, SyncStack, MonadCancelThrow, Sync] - check[SyncIO, SyncStack, MonadCancelThrow, Sync] - check[SyncIO, AsyncStack, MonadCancelThrow, Sync] - } + test("returns Async for OptionT at runtime if possible") { + check[IO, OptionT, Sync, Async] + check[IO, OptionT, Temporal, Async] + check[IO, OptionT, Concurrent, Async] + check[IO, OptionT, Spawn, Async] + check[IO, OptionT, MonadCancelThrow, Async] + } + + test("returns Async for EitherT at runtime if possible") { + type EitherTString[F[_], A] = EitherT[F, String, A] + check[IO, EitherTString, Sync, Async] + check[IO, EitherTString, Temporal, Async] + check[IO, EitherTString, Concurrent, Async] + check[IO, EitherTString, Spawn, Async] + check[IO, EitherTString, MonadCancelThrow, Async] + } + + test("returns Async for Kleisli at runtime if possible") { + type StringKleisli[F[_], A] = Kleisli[F, String, A] + check[IO, StringKleisli, Sync, Async] + check[IO, StringKleisli, Temporal, Async] + check[IO, StringKleisli, Concurrent, Async] + check[IO, StringKleisli, Spawn, Async] + check[IO, StringKleisli, MonadCancelThrow, Async] + } + + test("returns Async for IorT at runtime if possible") { + type StringIorT[F[_], A] = IorT[F, String, A] + check[IO, StringIorT, Sync, Async] + check[IO, StringIorT, Temporal, Async] + check[IO, StringIorT, Concurrent, Async] + check[IO, StringIorT, Spawn, Async] + check[IO, StringIorT, MonadCancelThrow, Async] + } + + test("returns Async for WriterT at runtime if possible") { + type StringWriterT[F[_], A] = WriterT[F, String, A] + check[IO, StringWriterT, Sync, Async] + check[IO, StringWriterT, Temporal, Async] + check[IO, StringWriterT, Concurrent, Async] + check[IO, StringWriterT, Spawn, Async] + check[IO, StringWriterT, MonadCancelThrow, Async] + } + + test("returns Sync for StateT at runtime if possible") { + type StringStateT[F[_], A] = StateT[F, String, A] + check[IO, StringStateT, MonadCancelThrow, Sync] + check[SyncIO, StringStateT, MonadCancelThrow, Sync] + } + + test("returns Sync for ReaderWriterStateT at runtime if possible") { + type TestRWST[F[_], A] = ReaderWriterStateT[F, String, Int, Unit, A] + check[IO, TestRWST, MonadCancelThrow, Sync] + check[SyncIO, TestRWST, MonadCancelThrow, Sync] + } + + test("returns Async for stacked transformers at runtime if possible") { + check[IO, AsyncStack, Sync, Async] + check[IO, AsyncStack, Temporal, Async] + check[IO, AsyncStack, Concurrent, Async] + check[IO, AsyncStack, Spawn, Async] + check[IO, AsyncStack, MonadCancelThrow, Async] + } + + test("returns Sync for stacked transformers at runtime if possible") { + check[IO, SyncStack, MonadCancelThrow, Sync] + check[SyncIO, SyncStack, MonadCancelThrow, Sync] + check[SyncIO, AsyncStack, MonadCancelThrow, Sync] } // read as: for base effect F, ensure the T instance for monad transformer M is actually of its subtype R def check[F[_], M[_[_], *], T[a[_]] <: MonadCancel[a, Throwable], R[a[_]] <: T[a]]( implicit T: T[M[F, *]], ct: ClassTag[R[F]]) = - T must beAnInstanceOf[R[F]] + assert(ct.runtimeClass.isAssignableFrom(T.getClass)) } diff --git a/tests/shared/src/test/scala/cats/effect/kernel/LensRefSuite.scala b/tests/shared/src/test/scala/cats/effect/kernel/LensRefSuite.scala index 8cb93c4b1b..a089e17e6e 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/LensRefSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/LensRefSuite.scala @@ -23,7 +23,7 @@ import cats.data.State import scala.concurrent.duration._ -class LensRefSuite extends BaseSpec with DetectPlatform { outer => +class LensRefSuite extends BaseSuite with DetectPlatform { outer => val smallDelay: IO[Unit] = IO.sleep(20.millis) @@ -60,192 +60,191 @@ class LensRefSuite extends BaseSpec with DetectPlatform { outer => def set(foo: Foo)(bar: Integer): Foo = foo.copy(bar = bar) } - "ref lens" should { + ticked("get - returns B") { implicit ticker => + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get(_), Foo.set(_)) + result <- refB.get + } yield result - "get - returns B" in ticked { implicit ticker => - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get(_), Foo.set(_)) - result <- refB.get - } yield result + assertCompleteAs(op, 0: Integer) + } - op must completeAs(0: Integer) - } + ticked("set - modifies underlying Ref") { implicit ticker => + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) + _ <- refB.set(1) + result <- refA.get + } yield result - "set - modifies underlying Ref" in ticked { implicit ticker => - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - _ <- refB.set(1) - result <- refA.get - } yield result + assertCompleteAs(op, Foo(1, -1)) + } - op must completeAs(Foo(1, -1)) - } + ticked("getAndSet - modifies underlying Ref and returns previous value") { implicit ticker => + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) + oldValue <- refB.getAndSet(1) + a <- refA.get + } yield (oldValue, a) + + assertCompleteAs(op, (0: Integer, Foo(1, -1))) + } - "getAndSet - modifies underlying Ref and returns previous value" in ticked { + ticked("update - modifies underlying Ref") { implicit ticker => + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) + _ <- refB.update(_ + 1) + a <- refA.get + } yield a + + assertCompleteAs(op, Foo(1, -1)) + } + + ticked("modify - modifies underlying Ref and returns a value") { implicit ticker => + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) + result <- refB.modify(bar => (bar + 1, 10)) + a <- refA.get + } yield (result, a) + + assertCompleteAs(op, (10, Foo(1, -1))) + } + + ticked("tryUpdate - successfully modifies underlying Ref") { implicit ticker => + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) + result <- refB.tryUpdate(_ + 1) + a <- refA.get + } yield (result, a) + + assertCompleteAs(op, (true, Foo(1, -1))) + } + + if (!isJS && !isNative) // concurrent modification impossible + ticked( + "tryUpdate - fails to modify original value if it's already been modified concurrently") { implicit ticker => + val updateRefUnsafely: Ref[IO, Integer] => Unit = { (ref: Ref[IO, Integer]) => + unsafeRun(ref.set(5)) + () + } + val op = for { refA <- Ref[IO].of(Foo(0, -1)) refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - oldValue <- refB.getAndSet(1) + result <- refB.tryUpdate { currentValue => + updateRefUnsafely(refB) + currentValue + 1 + } a <- refA.get - } yield (oldValue, a) + } yield (result, a) - op must completeAs((0: Integer, Foo(1, -1))) + assertCompleteAs(op, (false, Foo(5, -1))) } + else () - "update - modifies underlying Ref" in ticked { implicit ticker => - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - _ <- refB.update(_ + 1) - a <- refA.get - } yield a + ticked("tryModify - successfully modifies underlying Ref") { implicit ticker => + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) + result <- refB.tryModify(bar => (bar + 1, "A")) + a <- refA.get + } yield (result, a) - op must completeAs(Foo(1, -1)) - } - - "modify - modifies underlying Ref and returns a value" in ticked { implicit ticker => - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - result <- refB.modify(bar => (bar + 1, 10)) - a <- refA.get - } yield (result, a) - - op must completeAs((10, Foo(1, -1))) - } - - "tryUpdate - successfully modifies underlying Ref" in ticked { implicit ticker => - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - result <- refB.tryUpdate(_ + 1) - a <- refA.get - } yield (result, a) + assertCompleteAs(op, (Some("A"), Foo(1, -1))) + } - op must completeAs((true, Foo(1, -1))) - } + if (!isJS && !isNative) // concurrent modification impossible + ticked( + "tryModify - fails to modify original value if it's already been modified concurrently") { + implicit ticker => + val updateRefUnsafely: Ref[IO, Integer] => Unit = { (ref: Ref[IO, Integer]) => + unsafeRun(ref.set(5)) + () + } - if (!isJS && !isNative) // concurrent modification impossible - "tryUpdate - fails to modify original value if it's already been modified concurrently" in ticked { - implicit ticker => - val updateRefUnsafely: Ref[IO, Integer] => Unit = { (ref: Ref[IO, Integer]) => - unsafeRun(ref.set(5)) - () + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) + result <- refB.tryModify { currentValue => + updateRefUnsafely(refB) + (currentValue + 1, 10) } + a <- refA.get + } yield (result, a) - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - result <- refB.tryUpdate { currentValue => - updateRefUnsafely(refB) - currentValue + 1 - } - a <- refA.get - } yield (result, a) - - op must completeAs((false, Foo(5, -1))) - } - else () - - "tryModify - successfully modifies underlying Ref" in ticked { implicit ticker => - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - result <- refB.tryModify(bar => (bar + 1, "A")) - a <- refA.get - } yield (result, a) - - op must completeAs((Some("A"), Foo(1, -1))) + assertCompleteAs(op, (None, Foo(5, -1))) } + else () - if (!isJS && !isNative) // concurrent modification impossible - "tryModify - fails to modify original value if it's already been modified concurrently" in ticked { - implicit ticker => - val updateRefUnsafely: Ref[IO, Integer] => Unit = { (ref: Ref[IO, Integer]) => - unsafeRun(ref.set(5)) - () - } + ticked("tryModifyState - successfully modifies underlying Ref") { implicit ticker => + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) + result <- refB.tryModifyState(State.apply(x => (x + 1, "A"))) + a <- refA.get + } yield (result, a) - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - result <- refB.tryModify { currentValue => - updateRefUnsafely(refB) - (currentValue + 1, 10) - } - a <- refA.get - } yield (result, a) - - op must completeAs((None, Foo(5, -1))) - } - else () - - "tryModifyState - successfully modifies underlying Ref" in ticked { implicit ticker => - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - result <- refB.tryModifyState(State.apply(x => (x + 1, "A"))) - a <- refA.get - } yield (result, a) + assertCompleteAs(op, (Some("A"), Foo(1, -1))) + } - op must completeAs((Some("A"), Foo(1, -1))) - } + ticked("modifyState - successfully modifies underlying Ref") { implicit ticker => + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) + result <- refB.modifyState(State.apply(x => (x + 1, "A"))) + a <- refA.get + } yield (result, a) - "modifyState - successfully modifies underlying Ref" in ticked { implicit ticker => - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - result <- refB.modifyState(State.apply(x => (x + 1, "A"))) - a <- refA.get - } yield (result, a) + assertCompleteAs(op, ("A", Foo(1, -1))) + } - op must completeAs(("A", Foo(1, -1))) - } + ticked("access - successfully modifies underlying Ref") { implicit ticker => + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) + valueAndSetter <- refB.access + (value, setter) = valueAndSetter + success <- setter(value + 1) + a <- refA.get + } yield (success, a) + + assertCompleteAs(op, (true, Foo(1, -1))) + } - "access - successfully modifies underlying Ref" in ticked { implicit ticker => + ticked( + "access - setter fails to modify underlying Ref if value is modified before setter is called") { + implicit ticker => val op = for { refA <- Ref[IO].of(Foo(0, -1)) refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) valueAndSetter <- refB.access (value, setter) = valueAndSetter + _ <- refA.set(Foo(5, -1)) success <- setter(value + 1) a <- refA.get } yield (success, a) - op must completeAs((true, Foo(1, -1))) - } - - "access - setter fails to modify underlying Ref if value is modified before setter is called" in ticked { - implicit ticker => - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - valueAndSetter <- refB.access - (value, setter) = valueAndSetter - _ <- refA.set(Foo(5, -1)) - success <- setter(value + 1) - a <- refA.get - } yield (success, a) - - op must completeAs((false, Foo(5, -1))) - } - - "access - setter fails the second time" in ticked { implicit ticker => - val op = for { - refA <- Ref[IO].of(Foo(0, -1)) - refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) - valueAndSetter <- refB.access - (_, setter) = valueAndSetter - result1 <- setter(1) - result2 <- setter(2) - a <- refA.get - } yield (result1, result2, a) - - op must completeAs((true, false, Foo(1, -1))) - } + assertCompleteAs(op, (false, Foo(5, -1))) + } + ticked("access - setter fails the second time") { implicit ticker => + val op = for { + refA <- Ref[IO].of(Foo(0, -1)) + refB = Ref.lens[IO, Foo, Integer](refA)(Foo.get, Foo.set) + valueAndSetter <- refB.access + (_, setter) = valueAndSetter + result1 <- setter(1) + result2 <- setter(2) + a <- refA.get + } yield (result1, result2, a) + + assertCompleteAs(op, (true, false, Foo(1, -1))) } + } diff --git a/tests/shared/src/test/scala/cats/effect/kernel/MiniSemaphoreSuite.scala b/tests/shared/src/test/scala/cats/effect/kernel/MiniSemaphoreSuite.scala index 90fd292065..b34079893b 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/MiniSemaphoreSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/MiniSemaphoreSuite.scala @@ -20,78 +20,76 @@ package kernel import scala.concurrent.duration._ -class MiniSemaphoreSuite extends BaseSpec { outer => - - "mini semaphore" should { - "throw on negative n" in real { - IO.defer(MiniSemaphore[IO](-1)).mustFailWith[IllegalArgumentException] - } - - "block if no permits available" in ticked { implicit ticker => - MiniSemaphore[IO](0).flatMap { sem => sem.withPermit(IO.unit) } must nonTerminate - } - - "execute action if permit is available for it" in real { - MiniSemaphore[IO](1).flatMap { sem => sem.withPermit(IO.unit).mustEqual(()) } - } - - "unblock when permit is released" in ticked { implicit ticker => - val p = - for { - sem <- MiniSemaphore[IO](1) - ref <- IO.ref(false) - _ <- sem.withPermit { IO.sleep(1.second) >> ref.set(true) }.start - _ <- IO.sleep(500.millis) - _ <- sem.withPermit(IO.unit) - v <- ref.get - } yield v - - p must completeAs(true) - } - - "release permit if withPermit errors" in real { +class MiniSemaphoreSuite extends BaseSuite { outer => + + real("throw on negative n") { + IO.defer(MiniSemaphore[IO](-1)).mustFailWith[IllegalArgumentException] + } + + ticked("block if no permits available") { implicit ticker => + assertNonTerminate(MiniSemaphore[IO](0).flatMap { sem => sem.withPermit(IO.unit) }) + } + + real("execute action if permit is available for it") { + MiniSemaphore[IO](1).flatMap { sem => sem.withPermit(IO.unit).mustEqual(()) } + } + + ticked("unblock when permit is released") { implicit ticker => + val p = for { sem <- MiniSemaphore[IO](1) - _ <- sem.withPermit(IO.raiseError(new Exception)).attempt - res <- sem.withPermit(IO.unit).mustEqual(()) - } yield res - } - - "release permit if action gets canceled" in ticked { implicit ticker => - val p = - for { - sem <- MiniSemaphore[IO](1) - fiber <- sem.withPermit(IO.never).start - _ <- IO.sleep(1.second) - _ <- fiber.cancel - _ <- sem.withPermit(IO.unit) - } yield () - - p must completeAs(()) - } - - "allow cancelation if blocked waiting for permit" in ticked { implicit ticker => - val p = for { - sem <- MiniSemaphore[IO](0) ref <- IO.ref(false) - f <- sem.withPermit(IO.unit).onCancel(ref.set(true)).start - _ <- IO.sleep(1.second) - _ <- f.cancel + _ <- sem.withPermit { IO.sleep(1.second) >> ref.set(true) }.start + _ <- IO.sleep(500.millis) + _ <- sem.withPermit(IO.unit) v <- ref.get } yield v - p must completeAs(true) - } + assertCompleteAs(p, true) + } + + real("release permit if withPermit errors") { + for { + sem <- MiniSemaphore[IO](1) + _ <- sem.withPermit(IO.raiseError(new Exception)).attempt + res <- sem.withPermit(IO.unit).mustEqual(()) + } yield res + } - "not release permit when an acquire gets canceled" in ticked { implicit ticker => - val p = for { - sem <- MiniSemaphore[IO](0) - _ <- sem.withPermit(IO.unit).timeout(1.second).attempt + ticked("release permit if action gets canceled") { implicit ticker => + val p = + for { + sem <- MiniSemaphore[IO](1) + fiber <- sem.withPermit(IO.never).start + _ <- IO.sleep(1.second) + _ <- fiber.cancel _ <- sem.withPermit(IO.unit) } yield () - p must nonTerminate - } + assertCompleteAs(p, ()) + } + + ticked("allow cancelation if blocked waiting for permit") { implicit ticker => + val p = for { + sem <- MiniSemaphore[IO](0) + ref <- IO.ref(false) + f <- sem.withPermit(IO.unit).onCancel(ref.set(true)).start + _ <- IO.sleep(1.second) + _ <- f.cancel + v <- ref.get + } yield v + + assertCompleteAs(p, true) + } + + ticked("not release permit when an acquire gets canceled") { implicit ticker => + val p = for { + sem <- MiniSemaphore[IO](0) + _ <- sem.withPermit(IO.unit).timeout(1.second).attempt + _ <- sem.withPermit(IO.unit) + } yield () + + assertNonTerminate(p) } } diff --git a/tests/shared/src/test/scala/cats/effect/kernel/ParallelFSuite.scala b/tests/shared/src/test/scala/cats/effect/kernel/ParallelFSuite.scala index 452032823d..ba40d38ae2 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/ParallelFSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/ParallelFSuite.scala @@ -25,16 +25,16 @@ import cats.laws.discipline.{AlignTests, CommutativeApplicativeTests, ParallelTe import cats.laws.discipline.arbitrary.catsLawsCogenForIor import cats.syntax.all._ -import org.specs2.scalacheck._ -import org.typelevel.discipline.specs2.mutable.Discipline +import org.scalacheck.Test -class ParallelFSuite extends BaseSpec with Discipline with DetectPlatform { +import munit.DisciplineSuite - implicit val params: Parameters = - if (isNative) - Parameters(minTestsOk = 5) - else - Parameters(minTestsOk = 100) +class ParallelFSuite extends BaseSuite with DisciplineSuite with DetectPlatform { + + override protected def scalaCheckTestParameters: Test.Parameters = { + if (isNative) super.scalaCheckTestParameters.withMinSuccessfulTests(5) + else super.scalaCheckTestParameters.withMinSuccessfulTests(100) + } def alleyEq[E, A: Eq]: Eq[PureConc[E, A]] = { (x, y) => import Outcome._ diff --git a/tests/shared/src/test/scala/cats/effect/kernel/RefSuite.scala b/tests/shared/src/test/scala/cats/effect/kernel/RefSuite.scala index 919058b15a..ed94ff7984 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/RefSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/RefSuite.scala @@ -22,161 +22,158 @@ import cats.data.State import scala.concurrent.duration._ -class RefSuite extends BaseSpec with DetectPlatform { outer => +class RefSuite extends BaseSuite with DetectPlatform { outer => val smallDelay: IO[Unit] = IO.sleep(20.millis) - "ref" should { - // TODO need parallel instance for IO - // "support concurrent modifications" in ticked { implicit ticker => - // val finalValue = 100 - // val r = Ref.unsafe[IO, Int](0) - // val modifies = List.fill(finalValue)(r.update(_ + 1)).parSequence - // (modifies.start *> r.get) must completeAs(finalValue) + // TODO need parallel instance for IO + // ticked("support concurrent modifications") { implicit ticker => + // val finalValue = 100 + // val r = Ref.unsafe[IO, Int](0) + // val modifies = List.fill(finalValue)(r.update(_ + 1)).parSequence + // (modifies.start *> assertCompleteAs(r.get), finalValue) - // } + // } - "get and set successfully" in ticked { implicit ticker => - val op = for { - r <- Ref[IO].of(0) - getAndSetResult <- r.getAndSet(1) - getResult <- r.get - } yield getAndSetResult == 0 && getResult == 1 + ticked("get and set successfully") { implicit ticker => + val op = for { + r <- Ref[IO].of(0) + getAndSetResult <- r.getAndSet(1) + getResult <- r.get + } yield getAndSetResult == 0 && getResult == 1 - op must completeAs(true) + assertCompleteAs(op, true) - } + } - "get and update successfully" in ticked { implicit ticker => - val op = for { - r <- Ref[IO].of(0) - getAndUpdateResult <- r.getAndUpdate(_ + 1) - getResult <- r.get - } yield getAndUpdateResult == 0 && getResult == 1 + ticked("get and update successfully") { implicit ticker => + val op = for { + r <- Ref[IO].of(0) + getAndUpdateResult <- r.getAndUpdate(_ + 1) + getResult <- r.get + } yield getAndUpdateResult == 0 && getResult == 1 - op must completeAs(true) - } + assertCompleteAs(op, true) + } - "update and get successfully" in ticked { implicit ticker => - val op = for { - r <- Ref[IO].of(0) - updateAndGetResult <- r.updateAndGet(_ + 1) - getResult <- r.get - } yield updateAndGetResult == 1 && getResult == 1 + ticked("update and get successfully") { implicit ticker => + val op = for { + r <- Ref[IO].of(0) + updateAndGetResult <- r.updateAndGet(_ + 1) + getResult <- r.get + } yield updateAndGetResult == 1 && getResult == 1 - op must completeAs(true) - } + assertCompleteAs(op, true) + } - "access successfully" in ticked { implicit ticker => + ticked("access successfully") { implicit ticker => + val op = for { + r <- Ref[IO].of(0) + valueAndSetter <- r.access + (value, setter) = valueAndSetter + success <- setter(value + 1) + result <- r.get + } yield success && result == 1 + + assertCompleteAs(op, true) + } + + ticked("access - setter should fail if value is modified before setter is called") { + implicit ticker => val op = for { r <- Ref[IO].of(0) valueAndSetter <- r.access (value, setter) = valueAndSetter + _ <- r.set(5) success <- setter(value + 1) result <- r.get - } yield success && result == 1 + } yield !success && result == 5 - op must completeAs(true) - } + assertCompleteAs(op, true) + } - "access - setter should fail if value is modified before setter is called" in ticked { - implicit ticker => - val op = for { - r <- Ref[IO].of(0) - valueAndSetter <- r.access - (value, setter) = valueAndSetter - _ <- r.set(5) - success <- setter(value + 1) - result <- r.get - } yield !success && result == 5 - - op must completeAs(true) - } + ticked("tryUpdate - modification occurs successfully") { implicit ticker => + val op = for { + r <- Ref[IO].of(0) + result <- r.tryUpdate(_ + 1) + value <- r.get + } yield result && value == 1 - "tryUpdate - modification occurs successfully" in ticked { implicit ticker => - val op = for { - r <- Ref[IO].of(0) - result <- r.tryUpdate(_ + 1) - value <- r.get - } yield result && value == 1 + assertCompleteAs(op, true) + } - op must completeAs(true) - } + if (!isJS && !isNative) // concurrent modification impossible + ticked("tryUpdate - should fail to update if modification has occurred") { + implicit ticker => + val updateRefUnsafely: Ref[IO, Int] => Unit = { (ref: Ref[IO, Int]) => + unsafeRun(ref.update(_ + 1)) + () + } - if (!isJS && !isNative) // concurrent modification impossible - "tryUpdate - should fail to update if modification has occurred" in ticked { - implicit ticker => - val updateRefUnsafely: Ref[IO, Int] => Unit = { (ref: Ref[IO, Int]) => - unsafeRun(ref.update(_ + 1)) - () + val op = for { + r <- Ref[IO].of(0) + result <- r.tryUpdate { currentValue => + updateRefUnsafely(r) + currentValue + 1 } + } yield result - val op = for { - r <- Ref[IO].of(0) - result <- r.tryUpdate { currentValue => - updateRefUnsafely(r) - currentValue + 1 - } - } yield result - - op must completeAs(false) - } - else () - - "tryModifyState - modification occurs successfully" in ticked { implicit ticker => - val op = for { - r <- Ref[IO].of(0) - result <- r.tryModifyState(State.pure(1)) - } yield result.contains(1) - - op must completeAs(true) + assertCompleteAs(op, false) } + else () - "modifyState - modification occurs successfully" in ticked { implicit ticker => - val op = for { - r <- Ref[IO].of(0) - result <- r.modifyState(State.pure(1)) - } yield result == 1 + ticked("tryModifyState - modification occurs successfully") { implicit ticker => + val op = for { + r <- Ref[IO].of(0) + result <- r.tryModifyState(State.pure(1)) + } yield result.contains(1) - op must completeAs(true) - } + assertCompleteAs(op, true) + } - "flatModify - finalizer should be uncancelable" in ticked { implicit ticker => - var passed = false - val op = for { - ref <- Ref[IO].of(0) - _ <- ref - .flatModify(_ => (1, IO.canceled >> IO { passed = true })) - .start - .flatMap(_.join) - .void - result <- ref.get - } yield result == 1 - - op must completeAs(true) - passed must beTrue - } + ticked("modifyState - modification occurs successfully") { implicit ticker => + val op = for { + r <- Ref[IO].of(0) + result <- r.modifyState(State.pure(1)) + } yield result == 1 - "flatModifyFull - finalizer should mask cancellation" in ticked { implicit ticker => - var passed = false - var failed = false - val op = for { - ref <- Ref[IO].of(0) - _ <- ref - .flatModifyFull { (poll, _) => - (1, poll(IO.canceled >> IO { failed = true }).onCancel(IO { passed = true })) - } - .start - .flatMap(_.join) - .void - result <- ref.get - } yield result == 1 - - op must completeAs(true) - passed must beTrue - failed must beFalse - } + assertCompleteAs(op, true) + } + + ticked("flatModify - finalizer should be uncancelable") { implicit ticker => + var passed = false + val op = for { + ref <- Ref[IO].of(0) + _ <- ref + .flatModify(_ => (1, IO.canceled >> IO { passed = true })) + .start + .flatMap(_.join) + .void + result <- ref.get + } yield result == 1 + + assertCompleteAs(op, true) + assert(passed) + } + ticked("flatModifyFull - finalizer should mask cancellation") { implicit ticker => + var passed = false + var failed = false + val op = for { + ref <- Ref[IO].of(0) + _ <- ref + .flatModifyFull { (poll, _) => + (1, poll(IO.canceled >> IO { failed = true }).onCancel(IO { passed = true })) + } + .start + .flatMap(_.join) + .void + result <- ref.get + } yield result == 1 + + assertCompleteAs(op, true) + assert(passed) + assert(!failed) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/AtomicCellSuite.scala b/tests/shared/src/test/scala/cats/effect/std/AtomicCellSuite.scala index 5ad9e60562..6647abd07b 100644 --- a/tests/shared/src/test/scala/cats/effect/std/AtomicCellSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/AtomicCellSuite.scala @@ -18,104 +18,98 @@ package cats package effect package std -import org.specs2.specification.core.Fragments - import scala.concurrent.duration._ -final class AtomicCellSuite extends BaseSpec { - "AsyncAtomicCell" should { - tests(AtomicCell.async) - } +final class AtomicCellSuite extends BaseSuite { - "ConcurrentAtomicCell" should { - tests(AtomicCell.concurrent) - } + tests("AsyncAtomicCell", AtomicCell.async) + tests("ConcurrentAtomicCell", AtomicCell.concurrent) - def tests(factory: Int => IO[AtomicCell[IO, Int]]): Fragments = { - "AtomicCell" should { - "get and set successfully" in real { - val op = for { - cell <- factory(0) - getAndSetResult <- cell.getAndSet(1) - getResult <- cell.get - } yield getAndSetResult == 0 && getResult == 1 + def tests(name: String, factory: Int => IO[AtomicCell[IO, Int]]) = { - op.mustEqual(true) - } + real(s"$name get and set successfully") { + val op = for { + cell <- factory(0) + getAndSetResult <- cell.getAndSet(1) + getResult <- cell.get + } yield getAndSetResult == 0 && getResult == 1 - "get and update successfully" in real { - val op = for { - cell <- factory(0) - getAndUpdateResult <- cell.getAndUpdate(_ + 1) - getResult <- cell.get - } yield getAndUpdateResult == 0 && getResult == 1 + op.mustEqual(true) + } - op.mustEqual(true) - } + real(s"$name get and update successfully") { + val op = for { + cell <- factory(0) + getAndUpdateResult <- cell.getAndUpdate(_ + 1) + getResult <- cell.get + } yield getAndUpdateResult == 0 && getResult == 1 - "update and get successfully" in real { - val op = for { - cell <- factory(0) - updateAndGetResult <- cell.updateAndGet(_ + 1) - getResult <- cell.get - } yield updateAndGetResult == 1 && getResult == 1 + op.mustEqual(true) + } - op.mustEqual(true) - } + real(s"$name update and get successfully") { + val op = for { + cell <- factory(0) + updateAndGetResult <- cell.updateAndGet(_ + 1) + getResult <- cell.get + } yield updateAndGetResult == 1 && getResult == 1 - "evalModify successfully" in ticked { implicit ticker => - val op = factory(0).flatMap { cell => - cell.evalModify { x => - val y = x + 1 - IO.sleep(1.second).as((y, (x, y))) - } map { - case (oldValue, newValue) => - oldValue == 0 && newValue == 1 - } - } + op.mustEqual(true) + } - op must completeAs(true) + ticked(s"$name evalModify successfully") { implicit ticker => + val op = factory(0).flatMap { cell => + cell.evalModify { x => + val y = x + 1 + IO.sleep(1.second).as((y, (x, y))) + } map { + case (oldValue, newValue) => + oldValue == 0 && newValue == 1 + } } - "evalUpdate should block and cancel should release" in ticked { implicit ticker => - val op = for { - cell <- factory(0) - b <- cell.evalUpdate(x => IO.never.as(x + 1)).start - _ <- IO.sleep(1.second) - f <- cell.update(_ + 3).start - _ <- IO.sleep(1.second) - _ <- f.cancel - _ <- IO.sleep(1.second) - _ <- b.cancel - _ <- IO.sleep(1.second) - _ <- cell.update(_ + 1) - r <- cell.get - } yield r == 1 - - op must completeAs(true) - } + assertCompleteAs(op, true) + } - "evalModify should properly suspend read" in ticked { implicit ticker => - val op = for { - cell <- factory(0) - _ <- cell.update(_ + 1).replicateA_(2) - r <- cell.get - } yield r == 2 + ticked(s"$name evalUpdate should block and cancel should release") { implicit ticker => + val op = for { + cell <- factory(0) + b <- cell.evalUpdate(x => IO.never.as(x + 1)).start + _ <- IO.sleep(1.second) + f <- cell.update(_ + 3).start + _ <- IO.sleep(1.second) + _ <- f.cancel + _ <- IO.sleep(1.second) + _ <- b.cancel + _ <- IO.sleep(1.second) + _ <- cell.update(_ + 1) + r <- cell.get + } yield r == 1 + + assertCompleteAs(op, true) + } - op must completeAs(true) - } + ticked(s"$name evalModify should properly suspend read") { implicit ticker => + val op = for { + cell <- factory(0) + _ <- cell.update(_ + 1).replicateA_(2) + r <- cell.get + } yield r == 2 - "get should not block during concurrent modification" in ticked { implicit ticker => - val op = for { - cell <- factory(0) - gate <- IO.deferred[Unit] - _ <- cell.evalModify(_ => gate.complete(()) *> IO.never).start - _ <- gate.get - r <- cell.get - } yield r == 0 + assertCompleteAs(op, true) + } - op must completeAs(true) - } + ticked(s"$name get should not block during concurrent modification") { implicit ticker => + val op = for { + cell <- factory(0) + gate <- IO.deferred[Unit] + _ <- cell.evalModify(_ => gate.complete(()) *> IO.never).start + _ <- gate.get + r <- cell.get + } yield r == 0 + + assertCompleteAs(op, true) } + } } diff --git a/tests/shared/src/test/scala/cats/effect/std/BackpressureSuite.scala b/tests/shared/src/test/scala/cats/effect/std/BackpressureSuite.scala index ebff9e64d4..d9d33501ed 100644 --- a/tests/shared/src/test/scala/cats/effect/std/BackpressureSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/BackpressureSuite.scala @@ -21,31 +21,30 @@ import cats.syntax.all._ import scala.concurrent.duration._ -class BackpressureSuite extends BaseSpec { - - "Backpressure" should { - "Lossy Strategy should return IO[None] when no permits are available" in ticked { - implicit ticker => - val test = for { - backpressure <- Backpressure[IO](Backpressure.Strategy.Lossy, 1) - never = backpressure.metered(IO.never) - lost <- IO.race(never, never) - } yield lost.fold(identity, identity).isEmpty - - test must completeAs(true) - } - - "Lossless Strategy should complete effects even when no permits are available" in ticked { - implicit ticker => - val test = for { - backpressure <- Backpressure[IO](Backpressure.Strategy.Lossless, 1) - f1 <- backpressure.metered(IO.sleep(1.second) *> 1.pure[IO]).start - f2 <- backpressure.metered(IO.sleep(1.second) *> 2.pure[IO]).start - res1 <- f1.joinWithNever - res2 <- f2.joinWithNever - } yield (res1, res2) - - test must completeAs((Some(1), Some(2))) - } +class BackpressureSuite extends BaseSuite { + + ticked("Lossy Strategy should return IO[None] when no permits are available") { + implicit ticker => + val test = for { + backpressure <- Backpressure[IO](Backpressure.Strategy.Lossy, 1) + never = backpressure.metered(IO.never) + lost <- IO.race(never, never) + } yield lost.fold(identity, identity).isEmpty + + assertCompleteAs(test, true) } + + ticked("Lossless Strategy should complete effects even when no permits are available") { + implicit ticker => + val test = for { + backpressure <- Backpressure[IO](Backpressure.Strategy.Lossless, 1) + f1 <- backpressure.metered(IO.sleep(1.second) *> 1.pure[IO]).start + f2 <- backpressure.metered(IO.sleep(1.second) *> 2.pure[IO]).start + res1 <- f1.joinWithNever + res2 <- f2.joinWithNever + } yield (res1, res2) + + assertCompleteAs(test, (Some(1), Some(2))) + } + } diff --git a/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala b/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala index 0a9aadddce..30c988d955 100644 --- a/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala @@ -17,26 +17,20 @@ package cats.effect package std -class ConsoleSuite extends BaseSpec { - sequential +class ConsoleSuite extends BaseSuite { - "Console" should { + case class Foo(n: Int, b: Boolean) - case class Foo(n: Int, b: Boolean) - - "select default Show.fromToString (IO)" in { - IO.print(Foo(1, true)) // compilation test - IO.println(Foo(1, true)) // compilation test - true - } - - "select default Show.fromToString (Console[IO])" in { - Console[IO].print(Foo(1, true)) // compilation test - Console[IO].println(Foo(1, true)) // compilation test - Console[IO].error(Foo(1, true)) // compilation test - Console[IO].errorln(Foo(1, true)) // compilation test - true - } + test("select default Show.fromToString (IO)") { + IO.print(Foo(1, true)) // compilation test + IO.println(Foo(1, true)) // compilation test + } + test("select default Show.fromToString (Console[IO])") { + Console[IO].print(Foo(1, true)) // compilation test + Console[IO].println(Foo(1, true)) // compilation test + Console[IO].error(Foo(1, true)) // compilation test + Console[IO].errorln(Foo(1, true)) // compilation test } + } diff --git a/tests/shared/src/test/scala/cats/effect/std/CountDownLatchSuite.scala b/tests/shared/src/test/scala/cats/effect/std/CountDownLatchSuite.scala index 13ac8142ef..9ff1285592 100644 --- a/tests/shared/src/test/scala/cats/effect/std/CountDownLatchSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/CountDownLatchSuite.scala @@ -26,73 +26,78 @@ import cats.arrow.FunctionK import cats.effect.kernel.Outcome.Canceled import cats.implicits._ -import org.specs2.specification.core.Fragments - import scala.concurrent.duration._ import java.util.concurrent.TimeoutException -class CountDownLatchSuite extends BaseSpec { +class CountDownLatchSuite extends BaseSuite { - "CountDownLatch" should { - boundedQueueTests("CountDownLatch", CountDownLatch.apply[IO]) - boundedQueueTests( - "CountDownLatch mapK", - CountDownLatch.apply[IO](_).map(_.mapK(FunctionK.id))) - } + boundedQueueTests("CountDownLatch", CountDownLatch.apply[IO]) + boundedQueueTests( + "CountDownLatch mapK", + CountDownLatch.apply[IO](_).map(_.mapK(FunctionK.id))) - private def boundedQueueTests( - name: String, - constructor: Int => IO[CountDownLatch[IO]]): Fragments = { + private def boundedQueueTests(name: String, constructor: Int => IO[CountDownLatch[IO]]) = { - s"$name - should raise an exception when constructed with negative initial latches" in real { + real(s"$name - should raise an exception when constructed with negative initial latches") { val test = IO.defer(constructor(-1)).attempt test.flatMap { res => IO { - res must beLike { case Left(e) => e must haveClass[IllegalArgumentException] } + res match { + case Left(e) => assert(e.isInstanceOf[IllegalArgumentException]) + case Right(v) => fail(s"Expected Left, got $v") + } } } } - s"$name - should raise an exception when constructed with zero initial latches" in real { + real(s"$name - should raise an exception when constructed with zero initial latches") { val test = IO.defer(constructor(0)).attempt test.flatMap { res => IO { - res must beLike { case Left(e) => e must haveClass[IllegalArgumentException] } + res match { + case Left(e) => assert(e.isInstanceOf[IllegalArgumentException]) + case Right(v) => fail(s"Expected Left, got $v") + } } } } - s"$name - release and then await should complete" in real { + real(s"$name - release and then await should complete") { for { l <- constructor(1) _ <- l.release r <- l.await - res <- IO(r must beEqualTo(())) + res <- IO(assertEquals(r, ())) } yield res } - s"$name - await and then release should complete" in real { + real(s"$name - await and then release should complete") { for { l <- constructor(1) f <- l.await.start _ <- IO.sleep(1.milli) _ <- l.release r <- f.joinWithNever - res <- IO(r must beEqualTo(())) + res <- IO(assertEquals(r, ())) } yield res } - s"$name - await with > 1 latch unreleased should block" in real { + real(s"$name - await with > 1 latch unreleased should block") { for { l <- constructor(2) _ <- l.release r <- l.await.timeout(5.millis).attempt - res <- IO(r must beLike { case Left(e) => e must haveClass[TimeoutException] }) + res <- IO { + r match { + case Left(e) => assert(e.isInstanceOf[TimeoutException]) + case Right(v) => fail(s"Expected Left, got $v") + } + } } yield res } - s"$name - multiple awaits should all complete" in real { + real(s"$name - multiple awaits should all complete") { for { l <- constructor(1) f1 <- l.await.start @@ -100,27 +105,27 @@ class CountDownLatchSuite extends BaseSpec { _ <- IO.sleep(1.milli) _ <- l.release r <- (f1.joinWithNever, f2.joinWithNever).tupled - res <- IO(r must beEqualTo(((), ()))) + res <- IO(assertEquals(r, ((), ()))) } yield res } - s"$name - should release when latches == 0" in real { + real(s"$name - should release when latches == 0") { for { l <- constructor(1) _ <- l.release r <- l.release - res <- IO(r must beEqualTo(())) + res <- IO(assertEquals(r, ())) } yield res } - s"$name - blocking is cancelable" in real { + real(s"$name - blocking is cancelable") { for { l <- constructor(1) fib <- l.await.start _ <- IO.sleep(1.milli) _ <- fib.cancel oc <- fib.join - res <- IO(oc must beEqualTo(Canceled())) + res <- IO(assertEquals(oc, Canceled(): Outcome[IO, Throwable, Unit])) } yield res } } diff --git a/tests/shared/src/test/scala/cats/effect/std/CyclicBarrierSuite.scala b/tests/shared/src/test/scala/cats/effect/std/CyclicBarrierSuite.scala index e53d551df6..450464f19d 100644 --- a/tests/shared/src/test/scala/cats/effect/std/CyclicBarrierSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/CyclicBarrierSuite.scala @@ -20,61 +20,55 @@ package std import cats.arrow.FunctionK import cats.implicits._ -import org.specs2.specification.core.Fragments - import scala.concurrent.duration._ -class CyclicBarrierSuite extends BaseSpec { +class CyclicBarrierSuite extends BaseSuite { - "Cyclic barrier" should { - cyclicBarrierTests("Cyclic barrier", CyclicBarrier.apply) - cyclicBarrierTests( - "Cyclic barrier mapK", - CyclicBarrier.apply[IO](_).map(_.mapK(FunctionK.id))) - } + cyclicBarrierTests("Cyclic barrier", CyclicBarrier.apply) + cyclicBarrierTests( + "Cyclic barrier mapK", + CyclicBarrier.apply[IO](_).map(_.mapK(FunctionK.id))) - private def cyclicBarrierTests( - name: String, - newBarrier: Int => IO[CyclicBarrier[IO]]): Fragments = { - s"$name - should raise an exception when constructed with a negative capacity" in real { + private def cyclicBarrierTests(name: String, newBarrier: Int => IO[CyclicBarrier[IO]]) = { + real(s"$name - should raise an exception when constructed with a negative capacity") { IO.defer(newBarrier(-1)).mustFailWith[IllegalArgumentException] } - s"$name - should raise an exception when constructed with zero capacity" in real { + real(s"$name - should raise an exception when constructed with zero capacity") { IO.defer(newBarrier(0)).mustFailWith[IllegalArgumentException] } - s"$name - await is blocking" in ticked { implicit ticker => - newBarrier(2).flatMap(_.await) must nonTerminate + ticked(s"$name - await is blocking") { implicit ticker => + assertNonTerminate(newBarrier(2).flatMap(_.await)) } - s"$name - await is cancelable" in ticked { implicit ticker => - newBarrier(2).flatMap(_.await).timeoutTo(1.second, IO.unit) must completeAs(()) + ticked(s"$name - await is cancelable") { implicit ticker => + assertCompleteAs(newBarrier(2).flatMap(_.await).timeoutTo(1.second, IO.unit), ()) } - s"$name - await releases all fibers" in real { + real(s"$name - await releases all fibers") { newBarrier(2).flatMap { barrier => (barrier.await, barrier.await).parTupled.void.mustEqual(()) } } - s"$name - should reset once full" in ticked { implicit ticker => - newBarrier(2).flatMap { barrier => + ticked(s"$name - should reset once full") { implicit ticker => + assertNonTerminate(newBarrier(2).flatMap { barrier => (barrier.await, barrier.await).parTupled >> barrier.await - } must nonTerminate + }) } - s"$name - should clean up upon cancelation of await" in ticked { implicit ticker => - newBarrier(2).flatMap { barrier => + ticked(s"$name - should clean up upon cancelation of await") { implicit ticker => + assertNonTerminate(newBarrier(2).flatMap { barrier => // This will time out, so count goes back to 2 barrier.await.timeoutTo(1.second, IO.unit) >> // Therefore count goes only down to 1 when this awaits, and will block again barrier.await - } must nonTerminate + }) } - s"$name - barrier of capacity 1 is a no op" in real { + real(s"$name - barrier of capacity 1 is a no op") { newBarrier(1).flatMap(_.await).mustEqual(()) } @@ -83,7 +77,7 @@ class CyclicBarrierSuite extends BaseSpec { * had a race between cancelation of an awaiting fiber and * resetting the barrier once it's full */ - s"$name - race fiber cancel and barrier full" in real { + real(s"$name - race fiber cancel and barrier full") { val iterations = 100 val run = newBarrier(2) diff --git a/tests/shared/src/test/scala/cats/effect/std/DequeueSuite.scala b/tests/shared/src/test/scala/cats/effect/std/DequeueSuite.scala index 9bbd6233a0..1ada14a6e3 100644 --- a/tests/shared/src/test/scala/cats/effect/std/DequeueSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/DequeueSuite.scala @@ -21,65 +21,60 @@ import cats.arrow.FunctionK import cats.implicits._ import org.scalacheck.Arbitrary.arbitrary -import org.specs2.specification.core.Fragments import scala.collection.immutable.{Queue => ScalaQueue} import scala.concurrent.duration._ -class BoundedDequeueSuite extends BaseSpec with DequeueTests { - sequential +class BoundedDequeueSuite extends BaseSuite with DequeueTests { override def executionTimeout = 20.seconds - "BoundedDequeue (forward)" should { - boundedDequeueTests( - Dequeue.bounded(_), - _.offerBack(_), - _.tryOfferBack(_), - _.takeFront, - _.tryTakeFront, - _.tryTakeFrontN(_), - _.size - ) - } - - "BoundedDequeue (reversed)" should { - boundedDequeueTests( - Dequeue.bounded(_), - _.offerFront(_), - _.tryOfferFront(_), - _.takeBack, - _.tryTakeBack, - _.tryTakeBackN(_), - _.size - ) - } - - "BoundedDequeue mapK (forward)" should { - boundedDequeueTests( - Dequeue.bounded[IO, Int](_).map(_.mapK(FunctionK.id)), - _.offerBack(_), - _.tryOfferBack(_), - _.takeFront, - _.tryTakeFront, - _.tryTakeFrontN(_), - _.size - ) - } - - "BoundedDequeue mapK (reversed)" should { - boundedDequeueTests( - Dequeue.bounded[IO, Int](_).map(_.mapK(FunctionK.id)), - _.offerFront(_), - _.tryOfferFront(_), - _.takeBack, - _.tryTakeBack, - _.tryTakeBackN(_), - _.size - ) - } + boundedDequeueTests( + "BoundedDequeue (forward)", + Dequeue.bounded(_), + _.offerBack(_), + _.tryOfferBack(_), + _.takeFront, + _.tryTakeFront, + _.tryTakeFrontN(_), + _.size + ) + + boundedDequeueTests( + "BoundedDequeue (reversed)", + Dequeue.bounded(_), + _.offerFront(_), + _.tryOfferFront(_), + _.takeBack, + _.tryTakeBack, + _.tryTakeBackN(_), + _.size + ) + + boundedDequeueTests( + "BoundedDequeue mapK (forward)", + Dequeue.bounded[IO, Int](_).map(_.mapK(FunctionK.id)), + _.offerBack(_), + _.tryOfferBack(_), + _.takeFront, + _.tryTakeFront, + _.tryTakeFrontN(_), + _.size + ) + + boundedDequeueTests( + "BoundedDequeue mapK (reversed)", + Dequeue.bounded[IO, Int](_).map(_.mapK(FunctionK.id)), + _.offerFront(_), + _.tryOfferFront(_), + _.takeBack, + _.tryTakeBack, + _.tryTakeBackN(_), + _.size + ) private def boundedDequeueTests( + name: String, constructor: Int => IO[Dequeue[IO, Int]], offer: (Dequeue[IO, Int], Int) => IO[Unit], tryOffer: (Dequeue[IO, Int], Int) => IO[Boolean], @@ -87,8 +82,8 @@ class BoundedDequeueSuite extends BaseSpec with DequeueTests { tryTake: Dequeue[IO, Int] => IO[Option[Int]], tryTakeN: (Dequeue[IO, Int], Option[Int]) => IO[List[Int]], size: Dequeue[IO, Int] => IO[Int] - ): Fragments = { - "demonstrate offer and take with zero capacity" in real { + ) = { + real(s"$name - demonstrate offer and take with zero capacity") { for { q <- constructor(0) _ <- offer(q, 1).start @@ -96,26 +91,29 @@ class BoundedDequeueSuite extends BaseSpec with DequeueTests { f <- take(q).start _ <- offer(q, 2) v2 <- f.joinWithNever - r <- IO((v1 must beEqualTo(1)) and (v2 must beEqualTo(2))) + r <- IO { + assertEquals(v1, 1) + assertEquals(v2, 2) + } } yield r } - "async take with zero capacity" in realWithRuntime { implicit rt => + realWithRuntime(s"$name - async take with zero capacity") { implicit rt => for { q <- constructor(0) _ <- offer(q, 1).start v1 <- take(q) - _ <- IO(v1 must beEqualTo(1)) + _ <- IO(assertEquals(v1, 1)) ff <- IO(take(q).unsafeToFuture()).start f <- ff.joinWithNever - _ <- IO(f.value must beEqualTo(None)) + _ <- IO(assertEquals(f.value, None)) _ <- offer(q, 2) v2 <- IO.fromFuture(IO.pure(f)) - r <- IO(v2 must beEqualTo(2)) + r <- IO(assertEquals(v2, 2)) } yield r } - "offer/take with zero capacity" in real { + real(s"$name - offer/take with zero capacity") { val count = 1000 def producer(q: Dequeue[IO, Int], n: Int): IO[Unit] = @@ -138,107 +136,103 @@ class BoundedDequeueSuite extends BaseSpec with DequeueTests { c <- consumer(q, count).start _ <- p.join v <- c.joinWithNever - r <- IO(v must beEqualTo(count.toLong * (count - 1) / 2)) + r <- IO(assertEquals(v, count.toLong * (count - 1) / 2)) } yield r } - negativeCapacityConstructionTests(constructor) - tryOfferOnFullTests(constructor, offer, tryOffer, false) - cancelableOfferTests(constructor, offer, take, tryTake) - cancelableOfferBoundedTests(constructor, offer, take, tryTakeN) - cancelableTakeTests(constructor, offer, take) - tryOfferTryTakeTests(constructor, tryOffer, tryTake) - commonTests(constructor, offer, tryOffer, take, tryTake, size) - batchTakeTests(constructor, _.offer(_), _.tryTakeFrontN(_)) - batchTakeTests(constructor, _.offer(_), _.tryTakeBackN(_), _.reverse) - batchOfferTests(constructor, _.tryOfferBackN(_), _.tryTakeFrontN(_)) - batchOfferTests(constructor, _.tryOfferFrontN(_), _.tryTakeFrontN(_)) - batchOfferTests(constructor, _.tryOfferBackN(_), _.tryTakeBackN(_), _.reverse) - batchOfferTests(constructor, _.tryOfferFrontN(_), _.tryTakeBackN(_), _.reverse) - boundedBatchOfferTests(constructor, _.tryOfferBackN(_), _.tryTakeBackN(_), _.reverse) - boundedBatchOfferTests(constructor, _.tryOfferFrontN(_), _.tryTakeBackN(_), _.reverse) - reverse(constructor) + negativeCapacityConstructionTests(name, constructor) + tryOfferOnFullTests(name, constructor, offer, tryOffer, false) + cancelableOfferTests(name, constructor, offer, take, tryTake) + cancelableOfferBoundedTests(name, constructor, offer, take, tryTakeN) + cancelableTakeTests(name, constructor, offer, take) + tryOfferTryTakeTests(name, constructor, tryOffer, tryTake) + commonTests(name, constructor, offer, tryOffer, take, tryTake, size) + batchTakeTests(name, constructor, _.offer(_), _.tryTakeFrontN(_)) + batchTakeTests(name, constructor, _.offer(_), _.tryTakeBackN(_), _.reverse) + batchOfferTests(name, constructor, _.tryOfferBackN(_), _.tryTakeFrontN(_)) + batchOfferTests(name, constructor, _.tryOfferFrontN(_), _.tryTakeFrontN(_)) + batchOfferTests(name, constructor, _.tryOfferBackN(_), _.tryTakeBackN(_), _.reverse) + batchOfferTests(name, constructor, _.tryOfferFrontN(_), _.tryTakeBackN(_), _.reverse) + boundedBatchOfferTests(name, constructor, _.tryOfferBackN(_), _.tryTakeBackN(_), _.reverse) + boundedBatchOfferTests(name, constructor, _.tryOfferFrontN(_), _.tryTakeBackN(_), _.reverse) + reverse(name, constructor) } } -class UnboundedDequeueSuite extends BaseSpec with QueueTests[Dequeue] { - sequential - - "UnboundedDequeue (forward)" should { - unboundedDequeueTests( - Dequeue.unbounded, - _.offerBack(_), - _.tryOfferBack(_), - _.takeFront, - _.tryTakeFront, - _.size) - } - - "UnboundedDequeue (reversed)" should { - unboundedDequeueTests( - Dequeue.unbounded, - _.offerFront(_), - _.tryOfferFront(_), - _.takeBack, - _.tryTakeBack, - _.size) - } - - "UnboundedDequeue mapK (forward)" should { - unboundedDequeueTests( - Dequeue.unbounded[IO, Int].map(_.mapK(FunctionK.id)), - _.offerBack(_), - _.tryOfferBack(_), - _.takeFront, - _.tryTakeFront, - _.size - ) - } - - "UnboundedDequeue mapK (reversed)" should { - unboundedDequeueTests( - Dequeue.unbounded[IO, Int].map(_.mapK(FunctionK.id)), - _.offerFront(_), - _.tryOfferFront(_), - _.takeBack, - _.tryTakeBack, - _.size - ) - } +class UnboundedDequeueSuite extends BaseSuite with QueueTests[Dequeue] { + + unboundedDequeueTests( + "UnboundedDequeue (forward)", + Dequeue.unbounded, + _.offerBack(_), + _.tryOfferBack(_), + _.takeFront, + _.tryTakeFront, + _.size) + + unboundedDequeueTests( + "UnboundedDequeue (reversed)", + Dequeue.unbounded, + _.offerFront(_), + _.tryOfferFront(_), + _.takeBack, + _.tryTakeBack, + _.size) + + unboundedDequeueTests( + "UnboundedDequeue mapK (forward)", + Dequeue.unbounded[IO, Int].map(_.mapK(FunctionK.id)), + _.offerBack(_), + _.tryOfferBack(_), + _.takeFront, + _.tryTakeFront, + _.size + ) + + unboundedDequeueTests( + "UnboundedDequeue mapK (reversed)", + Dequeue.unbounded[IO, Int].map(_.mapK(FunctionK.id)), + _.offerFront(_), + _.tryOfferFront(_), + _.takeBack, + _.tryTakeBack, + _.size + ) private def unboundedDequeueTests( + name: String, constructor: IO[Dequeue[IO, Int]], offer: (Dequeue[IO, Int], Int) => IO[Unit], tryOffer: (Dequeue[IO, Int], Int) => IO[Boolean], take: Dequeue[IO, Int] => IO[Int], tryTake: Dequeue[IO, Int] => IO[Option[Int]], - size: Dequeue[IO, Int] => IO[Int]): Fragments = { - tryOfferOnFullTests(_ => constructor, offer, tryOffer, true) - tryOfferTryTakeTests(_ => constructor, tryOffer, tryTake) - commonTests(_ => constructor, offer, tryOffer, take, tryTake, size) - batchTakeTests(_ => constructor, _.offer(_), _.tryTakeFrontN(_)) - batchTakeTests(_ => constructor, _.offer(_), _.tryTakeBackN(_), _.reverse) - batchOfferTests(_ => constructor, _.tryOfferBackN(_), _.tryTakeFrontN(_)) - batchOfferTests(_ => constructor, _.tryOfferFrontN(_), _.tryTakeFrontN(_)) - batchOfferTests(_ => constructor, _.tryOfferBackN(_), _.tryTakeBackN(_), _.reverse) - batchOfferTests(_ => constructor, _.tryOfferFrontN(_), _.tryTakeBackN(_), _.reverse) + size: Dequeue[IO, Int] => IO[Int]) = { + tryOfferOnFullTests(name, _ => constructor, offer, tryOffer, true) + tryOfferTryTakeTests(name, _ => constructor, tryOffer, tryTake) + commonTests(name, _ => constructor, offer, tryOffer, take, tryTake, size) + batchTakeTests(name, _ => constructor, _.offer(_), _.tryTakeFrontN(_)) + batchTakeTests(name, _ => constructor, _.offer(_), _.tryTakeBackN(_), _.reverse) + batchOfferTests(name, _ => constructor, _.tryOfferBackN(_), _.tryTakeFrontN(_)) + batchOfferTests(name, _ => constructor, _.tryOfferFrontN(_), _.tryTakeFrontN(_)) + batchOfferTests(name, _ => constructor, _.tryOfferBackN(_), _.tryTakeBackN(_), _.reverse) + batchOfferTests(name, _ => constructor, _.tryOfferFrontN(_), _.tryTakeBackN(_), _.reverse) } } -trait DequeueTests extends QueueTests[Dequeue] { self: BaseSpec => +trait DequeueTests extends QueueTests[Dequeue] { self: BaseSuite => - def reverse(constructor: Int => IO[Dequeue[IO, Int]]): Fragments = { + def reverse(name: String, constructor: Int => IO[Dequeue[IO, Int]]) = { /** * Hand-rolled scalacheck effect as we don't have that for CE3 yet */ - "reverse" in realProp(arbitrary[List[Int]]) { in => + realProp(s"$name - reverse", arbitrary[List[Int]]) { in => for { q <- constructor(Int.MaxValue) _ <- in.traverse_(q.offer(_)) _ <- q.reverse out <- List.fill(in.length)(q.take).sequence - res <- IO(out must beEqualTo(in.reverse)) + res <- IO(assertEquals(out, in.reverse)) } yield res } diff --git a/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala b/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala index 9e3c780855..aed00eb189 100644 --- a/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala @@ -26,134 +26,138 @@ import scala.concurrent.duration._ import java.util.concurrent.atomic.AtomicInteger -class DispatcherSuite extends BaseSpec with DetectPlatform { +class DispatcherSuite extends BaseSuite with DetectPlatform { override def executionTimeout = 30.seconds - "sequential dispatcher (cancelable = false)" should { - "await = true" >> { - val D = Dispatcher.sequential[IO](await = true) + { + val D = Dispatcher.sequential[IO](await = true) - sequential(D, false) + sequential("sequential dispatcher (cancelable = false) - await = true", D, false) - awaitTermination(D) + awaitTermination("sequential dispatcher (cancelable = false) - await = true", D) - "not hang" in real { - D.use(dispatcher => IO(dispatcher.unsafeRunAndForget(IO.unit))) - .replicateA(if (isJS || isNative) 1 else 10000) - .as(true) - } + real("sequential dispatcher (cancelable = false) - await = true - not hang") { + D.use(dispatcher => IO(dispatcher.unsafeRunAndForget(IO.unit))) + .replicateA(if (isJS || isNative) 1 else 10000) + .as(true) + } - "await work queue drain on shutdown" in real { - val count = 1000 + real( + "sequential dispatcher (cancelable = false) - await = true - await work queue drain on shutdown") { + val count = 1000 - IO.ref(0) flatMap { resultsR => - val increments = D use { runner => - IO { - 0.until(count).foreach(_ => runner.unsafeRunAndForget(resultsR.update(_ + 1))) - } + IO.ref(0) flatMap { resultsR => + val increments = D use { runner => + IO { + 0.until(count).foreach(_ => runner.unsafeRunAndForget(resultsR.update(_ + 1))) } - - increments *> resultsR.get.flatMap(r => IO(r mustEqual count)) } + + increments *> resultsR.get.flatMap(r => IO(assertEquals(r, count))) } + } - "terminating worker preserves task order" in real { - val count = 10 + real( + "sequential dispatcher (cancelable = false) - await = true - terminating worker preserves task order") { + val count = 10 - IO.ref(Vector[Int]()) flatMap { resultsR => - val appends = D use { runner => - IO { - 0.until(count).foreach(i => runner.unsafeRunAndForget(resultsR.update(_ :+ i))) - } + IO.ref(Vector[Int]()) flatMap { resultsR => + val appends = D use { runner => + IO { + 0.until(count).foreach(i => runner.unsafeRunAndForget(resultsR.update(_ :+ i))) } - - appends *> resultsR.get.flatMap(r => IO(r mustEqual 0.until(count).toVector)) } + + appends *> resultsR.get.flatMap(r => IO(assertEquals(r, 0.until(count).toVector))) } + } - "correctly backpressure cancellation" in real { - D.use { dispatcher => - IO.ref(0).flatMap { ctr1 => - IO.ref(0).flatMap { ctr2 => - IO.fromFuture(IO { - val (_, cancel) = dispatcher.unsafeToFutureCancelable(IO.uncancelable { _ => - ctr1.update(_ + 1) *> IO.sleep(0.1.second) *> ctr2.update(_ + 1) - }) - val cancelFut = cancel() - cancelFut - }).flatMap { _ => - // if we're here, `cancel()` finished, so - // either the task didn't run at all (i.e., - // it was cancelled before starting), or - // it ran and already finished completely: - (ctr1.get, ctr2.get).flatMapN { (v1, v2) => IO(v1 mustEqual v2) } - } + real( + "sequential dispatcher (cancelable = false) - await = true - correctly backpressure cancellation") { + D.use { dispatcher => + IO.ref(0).flatMap { ctr1 => + IO.ref(0).flatMap { ctr2 => + IO.fromFuture(IO { + val (_, cancel) = dispatcher.unsafeToFutureCancelable(IO.uncancelable { _ => + ctr1.update(_ + 1) *> IO.sleep(0.1.second) *> ctr2.update(_ + 1) + }) + val cancelFut = cancel() + cancelFut + }).flatMap { _ => + // if we're here, `cancel()` finished, so + // either the task didn't run at all (i.e., + // it was cancelled before starting), or + // it ran and already finished completely: + (ctr1.get, ctr2.get).flatMapN { (v1, v2) => IO(assertEquals(v1, v2)) } } } - }.replicateA_(if (isJVM) 10000 else 1) - .as(ok) - } + } + }.replicateA_(if (isJVM) 10000 else 1) + } + } - "await = false" >> { - val D = Dispatcher.sequential[IO](await = false) + { + val D = Dispatcher.sequential[IO](await = false) - sequential(D, false) + sequential("sequential dispatcher (cancelable = false) - await = false", D, false) - "cancel all inner effects when canceled" in real { - var canceled = false + real( + "sequential dispatcher (cancelable = false) - await = false - cancel all inner effects when canceled") { + var canceled = false - val body = D use { runner => - IO(runner.unsafeRunAndForget(IO.never.onCancel(IO { canceled = true }))) *> IO.never - } + val body = D use { runner => + IO(runner.unsafeRunAndForget(IO.never.onCancel(IO { canceled = true }))) *> IO.never + } - val action = body.start.flatMap(f => IO.sleep(500.millis) *> f.cancel) + val action = body.start.flatMap(f => IO.sleep(500.millis) *> f.cancel) - TestControl.executeEmbed(action *> IO(canceled must beTrue)) - } + TestControl.executeEmbed(action *> IO(assert(canceled))) } } - "sequential dispatcher (cancelable = true)" should { - "await = true" >> { - val D = Dispatcher.sequentialCancelable[IO](await = true) + { + val D = Dispatcher.sequentialCancelable[IO](await = true) - sequential(D, true) + sequential("sequential dispatcher (cancelable = true) - await = true", D, true) - awaitTermination(D) + awaitTermination("sequential dispatcher (cancelable = true) - await = true", D) - "not hang" in real { - D.use(dispatcher => IO(dispatcher.unsafeRunAndForget(IO.unit))) - .replicateA(if (isJS || isNative) 1 else 10000) - .as(true) - } + real("sequential dispatcher (cancelable = true) - await = true - not hang") { + D.use(dispatcher => IO(dispatcher.unsafeRunAndForget(IO.unit))) + .replicateA(if (isJS || isNative) 1 else 10000) + .as(true) } + } - "await = false" >> { - val D = Dispatcher.sequentialCancelable[IO](await = false) + { + val D = Dispatcher.sequentialCancelable[IO](await = false) - sequential(D, true) + sequential("sequential dispatcher (cancelable = true) - await = false", D, true) - "cancel all inner effects when canceled" in real { - var canceled = false + real( + "sequential dispatcher (cancelable = true) - await = false - cancel all inner effects when canceled") { + var canceled = false - val body = D use { runner => - IO(runner.unsafeRunAndForget(IO.never.onCancel(IO { canceled = true }))) *> IO.never - } + val body = D use { runner => + IO(runner.unsafeRunAndForget(IO.never.onCancel(IO { canceled = true }))) *> IO.never + } - val action = body.start.flatMap(f => IO.sleep(500.millis) *> f.cancel) + val action = body.start.flatMap(f => IO.sleep(500.millis) *> f.cancel) - TestControl.executeEmbed(action *> IO(canceled must beTrue)) - } + TestControl.executeEmbed(action *> IO(assert(canceled))) } } - private def sequential(dispatcher: Resource[IO, Dispatcher[IO]], cancelable: Boolean) = { + private def sequential( + name: String, + dispatcher: Resource[IO, Dispatcher[IO]], + cancelable: Boolean) = { - common(dispatcher, cancelable) + common(name, dispatcher, cancelable) - "strictly sequentialize multiple IOs" in real { + real(s"$name - strictly sequentialize multiple IOs") { val length = 1000 for { @@ -169,23 +173,23 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { } vec <- results.get - _ <- IO(vec mustEqual 0.until(length).toVector) - } yield ok + _ <- IO(assertEquals(vec, 0.until(length).toVector)) + } yield () } - "reject new tasks after release action is submitted as a task" in ticked { + ticked(s"$name - reject new tasks after release action is submitted as a task") { implicit ticker => val test = dispatcher.allocated.flatMap { case (runner, release) => IO(runner.unsafeRunAndForget(release)) *> IO.sleep(100.millis) *> - IO(runner.unsafeRunAndForget(IO(ko)) must throwAn[IllegalStateException]) + IO(intercept[IllegalStateException](runner.unsafeRunAndForget(IO(ko)))) } - test.void must completeAs(()) + assertCompleteAs(test.void, ()) } - "invalidate cancelation action of task when complete" in real { + real(s"$name - invalidate cancelation action of task when complete") { val test = dispatcher use { runner => for { latch1 <- IO.deferred[Unit] @@ -203,13 +207,13 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { _ <- latch2.complete(()) _ <- latch3.get // this will hang if the test is failing - } yield ok + } yield () } - test.parReplicateA_(1000).as(ok) + test.parReplicateA_(1000) } - "invalidate cancelation action when racing with task" in real { + real(s"$name - invalidate cancelation action when racing with task") { val test = dispatcher use { runner => IO.ref(false) flatMap { resultR => for { @@ -233,65 +237,61 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { } } - test.flatMap(b => IO(b must beTrue)).parReplicateA_(1000).as(ok) + test.flatMap(b => IO(assert(b))).parReplicateA_(1000) } } - "parallel dispatcher" should { - "await = true" >> { - val D = Dispatcher.parallel[IO](await = true) - - parallel(D) + { + val D = Dispatcher.parallel[IO](await = true) + parallel("parallel dispatcher - await = true", D) + awaitTermination("parallel dispatcher - await = true", D) + } - awaitTermination(D) - } + { + val D = Dispatcher.parallel[IO](await = false) - "await = false" >> { - val D = Dispatcher.parallel[IO](await = false) + parallel("parallel dispatcher - await = false", D) - parallel(D) + real(s"parallel dispatcher - await = false - cancel all inner effects when canceled") { + for { + gate1 <- Semaphore[IO](2) + _ <- gate1.acquireN(2) - "cancel all inner effects when canceled" in real { - for { - gate1 <- Semaphore[IO](2) - _ <- gate1.acquireN(2) + gate2 <- Semaphore[IO](2) + _ <- gate2.acquireN(2) - gate2 <- Semaphore[IO](2) - _ <- gate2.acquireN(2) + rec = D flatMap { runner => + Resource eval { + IO { + // these finalizers never return, so this test is intentionally designed to hang + // they flip their gates first though; this is just testing that both run in parallel + val a = (gate1.release *> IO.never) onCancel { + gate2.release *> IO.never + } - rec = D flatMap { runner => - Resource eval { - IO { - // these finalizers never return, so this test is intentionally designed to hang - // they flip their gates first though; this is just testing that both run in parallel - val a = (gate1.release *> IO.never) onCancel { - gate2.release *> IO.never - } - - val b = (gate1.release *> IO.never) onCancel { - gate2.release *> IO.never - } - - runner.unsafeRunAndForget(a) - runner.unsafeRunAndForget(b) + val b = (gate1.release *> IO.never) onCancel { + gate2.release *> IO.never } + + runner.unsafeRunAndForget(a) + runner.unsafeRunAndForget(b) } } + } - _ <- rec.use(_ => gate1.acquireN(2)).start + _ <- rec.use(_ => gate1.acquireN(2)).start - // if both are not run in parallel, then this will hang - _ <- gate2.acquireN(2) - } yield ok - } + // if both are not run in parallel, then this will hang + _ <- gate2.acquireN(2) + } yield () } } - private def parallel(dispatcher: Resource[IO, Dispatcher[IO]]) = { + private def parallel(name: String, dispatcher: Resource[IO, Dispatcher[IO]]) = { - common(dispatcher, true) + common(name, dispatcher, true) - "run multiple IOs in parallel" in real { + real(s"$name - run multiple IOs in parallel") { val num = 10 for { @@ -310,10 +310,10 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { rec.use(_ => awaitAll) } - } yield ok + } yield () } - "run many IOs simultaneously to full completion" in real { + real(s"$name - run many IOs simultaneously to full completion") { val length = 256 // 10000 times out on my machine for { @@ -329,12 +329,12 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { } vec <- results.get - _ <- IO(vec must containAllOf(0.until(length).toVector)) - } yield ok + _ <- IO(assertEquals(vec.sorted, 0.until(length).toVector)) + } yield () } // https://github.com/typelevel/cats-effect/issues/3898 - "not hang when cancelling" in real { + real(s"$name - not hang when cancelling") { val test = dispatcher.use { dispatcher => val action = IO.fromFuture { IO { @@ -347,12 +347,12 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { } if (isJVM) - test.parReplicateA_(100).as(ok) + test.parReplicateA_(100) else - test.as(ok) + test } - "cancelation does not block a worker" in real { + real(s"$name - cancelation does not block a worker") { TestControl executeEmbed { dispatcher use { runner => (IO.deferred[Unit], IO.deferred[Unit]) flatMapN { (latch1, latch2) => @@ -362,14 +362,14 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { latch1.get *> IO(cancel()) *> IO(runner.unsafeRunAndForget(latch2.complete(()))) *> - latch2.get.as(ok) + latch2.get } } } } } - "cancelation race does not block a worker" in real { + real(s"$name - cancelation race does not block a worker") { dispatcher .use { runner => IO.deferred[Unit] flatMap { latch => @@ -388,29 +388,31 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { } } .replicateA_(if (isJVM) 500 else 1) - .as(ok) } } - private def common(dispatcher: Resource[IO, Dispatcher[IO]], cancelable: Boolean) = { + private def common( + name: String, + dispatcher: Resource[IO, Dispatcher[IO]], + cancelable: Boolean) = { - "run a synchronous IO" in real { + real(s"$name - run a synchronous IO") { val ioa = IO(1).map(_ + 2) val rec = dispatcher.flatMap(runner => Resource.eval(IO.fromFuture(IO(runner.unsafeToFuture(ioa))))) - rec.use(i => IO(i mustEqual 3)) + rec.use(i => IO(assertEquals(i, 3))) } - "run an asynchronous IO" in real { + real(s"$name - run an asynchronous IO") { val ioa = (IO(1) <* IO.cede).map(_ + 2) val rec = dispatcher.flatMap(runner => Resource.eval(IO.fromFuture(IO(runner.unsafeToFuture(ioa))))) - rec.use(i => IO(i mustEqual 3)) + rec.use(i => IO(assertEquals(i, 3))) } - "run several IOs back to back" in real { + real(s"$name - run several IOs back to back") { val counter = new AtomicInteger(0) val increment = IO(counter.getAndIncrement()).void @@ -420,18 +422,18 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { Resource.eval(IO.fromFuture(IO(runner.unsafeToFuture(increment))).replicateA(num).void) } - rec.use(_ => IO(counter.get() mustEqual num)) + rec.use(_ => IO(assertEquals(counter.get(), num))) } - "raise an error on leaked runner" in real { + real(s"$name - raise an error on leaked runner") { dispatcher.use(IO.pure(_)) flatMap { runner => IO { - runner.unsafeRunAndForget(IO(ko)) must throwAn[IllegalStateException] + intercept[IllegalStateException](runner.unsafeRunAndForget(IO(ko))) } } } - "report exception if raised during unsafeRunAndForget" in real { + real(s"$name - report exception if raised during unsafeRunAndForget") { def ec2(ec1: ExecutionContext, er: Promise[Boolean]) = new ExecutionContext { def reportFailure(t: Throwable) = er.success(true) def execute(r: Runnable) = ec1.execute(r) @@ -450,10 +452,10 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { test .use(t => IO.fromFutureCancelable(IO((t.future, IO.unit))).timeoutTo(1.second, IO.pure(false))) - .flatMap(t => IO(t mustEqual true)) + .flatMap(t => IO(assertEquals(t, true))) } - "do not treat exception in unsafeRunToFuture as unhandled" in real { + real(s"$name - do not treat exception in unsafeRunToFuture as unhandled") { import scala.concurrent.TimeoutException def ec2(ec1: ExecutionContext, er: Promise[Boolean]) = new ExecutionContext { def reportFailure(t: Throwable) = er.failure(t) @@ -476,7 +478,7 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { .mustFailWith[TimeoutException]) } - "respect self-cancelation" in real { + real(s"$name - respect self-cancelation") { dispatcher use { runner => for { resultR <- IO.ref(false) @@ -489,16 +491,16 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { _ <- latch.get result <- resultR.get - _ <- IO(result must beFalse) + _ <- IO(assert(!result)) secondLatch <- IO.deferred[Unit] _ <- IO(runner.unsafeRunAndForget(secondLatch.complete(()).void)) _ <- secondLatch.get // if the dispatcher itself is dead, this will hang - } yield ok + } yield () } } - "reject new tasks while shutting down" in real { + real(s"$name - reject new tasks while shutting down") { val test = (IO.deferred[Unit], IO.deferred[Unit]) flatMapN { (latch1, latch2) => dispatcher.allocated flatMap { case (runner, release) => @@ -516,18 +518,18 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { } } - test.attempt.flatMap(r => IO(r must beLeft)).parReplicateA_(50).as(ok) + test.attempt.flatMap(r => IO(assert(r.isLeft))).parReplicateA_(50) } - "cancel inner awaits when canceled" in ticked { implicit ticker => + ticked(s"$name - cancel inner awaits when canceled") { implicit ticker => val work = dispatcher.useForever val test = work.background.use(_ => IO.sleep(100.millis)) - test must completeAs(()) + assertCompleteAs(test, ()) } if (!cancelable) { - "ignore action cancelation" in real { + real(s"$name - ignore action cancelation") { var canceled = false val rec = dispatcher flatMap { runner => @@ -542,10 +544,10 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { } } - TestControl.executeEmbed(rec.use(_ => IO(canceled must beFalse))) + TestControl.executeEmbed(rec.use(_ => IO(assert(!canceled)))) } } else { - "forward cancelation onto the inner action" in real { + real(s"$name - forward cancelation onto the inner action") { val test = dispatcher use { runner => IO.ref(false) flatMap { resultsR => val action = IO.never.onCancel(resultsR.set(true)) @@ -556,10 +558,10 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { } } - TestControl.executeEmbed(test).flatMap(b => IO(b must beTrue)) + TestControl.executeEmbed(test).flatMap(b => IO(assert(b))) } - "support multiple concurrent cancelations" in real { + real(s"$name - support multiple concurrent cancelations") { dispatcher use { runner => val count = new AtomicInteger(0) @@ -584,16 +586,16 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { _ <- latch1.get _ <- IO.sleep(100.millis) - _ <- IO(count.get() mustEqual 0) + _ <- IO(assertEquals(count.get(), 0)) _ <- latch2.complete(()) _ <- IO.sleep(100.millis) - _ <- IO(count.get() mustEqual 3) - } yield ok + _ <- IO(assertEquals(count.get(), 3)) + } yield () } } - "complete / cancel race" in real { + real("complete / cancel race") { val tsk = dispatcher.use { dispatcher => IO.fromFuture(IO { val (_, cancel) = dispatcher.unsafeToFutureCancelable(IO.unit) @@ -602,14 +604,14 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { }) } - tsk.replicateA_(if (isJVM) 10000 else 1).as(ok) + tsk.replicateA_(if (isJVM) 10000 else 1) } } } - private def awaitTermination(dispatcher: Resource[IO, Dispatcher[IO]]) = { + private def awaitTermination(name: String, dispatcher: Resource[IO, Dispatcher[IO]]) = { - "wait for the completion of the active fibers" in real { + real(s"$name - wait for the completion of the active fibers") { def makeRunner(releaseInner: CountDownLatch[IO]) = for { runner <- dispatcher @@ -632,12 +634,12 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { _ <- fiberLatch.release released2 <- fiber.join.as(true).timeoutTo(200.millis, IO(false)) } yield { - released1 must beFalse - released2 must beTrue + assert(!released1) + assert(released2) } } - "issue #3506: await unsafeRunAndForget" in ticked { implicit ticker => + ticked(s"$name - issue #3506: await unsafeRunAndForget") { implicit ticker => val result = for { latch <- IO.deferred[Unit] @@ -645,10 +647,10 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { _ <- dispatcher.use(runner => IO(runner.unsafeRunAndForget(repro)) >> latch.get) } yield () - result must nonTerminate + assertNonTerminate(result) } - "cancel active fibers when an error is produced" in real { + real(s"$name - cancel active fibers when an error is produced") { case object TestException extends RuntimeException IO.deferred[Unit] flatMap { canceled => @@ -663,9 +665,11 @@ class DispatcherSuite extends BaseSpec with DetectPlatform { } yield () } - test.handleError(_ => ()) >> canceled.get.as(ok) + test.handleError(_ => ()) >> canceled.get } } } } + + private def ko(implicit loc: munit.Location) = fail("fail") } diff --git a/tests/shared/src/test/scala/cats/effect/std/EnvSuite.scala b/tests/shared/src/test/scala/cats/effect/std/EnvSuite.scala index b0b4526126..6437151524 100644 --- a/tests/shared/src/test/scala/cats/effect/std/EnvSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/EnvSuite.scala @@ -17,18 +17,16 @@ package cats.effect package std -class EnvSuite extends BaseSpec { +class EnvSuite extends BaseSuite { - "Env" should { - "retrieve a variable from the environment" in real { - Env[IO].get("HOME").flatMap(x => IO(x must beSome)) - } - "return none for non-existent environment variable" in real { - Env[IO].get("MADE_THIS_UP").flatMap(x => IO(x must beNone)) - } - "provide an iterable of all the things" in real { - Env[IO].entries.flatMap(x => IO(x must not(beEmpty))) - } + real("retrieve a variable from the environment") { + Env[IO].get("HOME").flatMap(x => IO(assert(x.isDefined))) + } + real("return none for non-existent environment variable") { + Env[IO].get("MADE_THIS_UP").flatMap(x => IO(assert(x.isEmpty))) + } + real("provide an iterable of all the things") { + Env[IO].entries.flatMap(x => IO(assert(x.nonEmpty))) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/HotswapSuite.scala b/tests/shared/src/test/scala/cats/effect/std/HotswapSuite.scala index 32572b67b8..d74aa2db1a 100644 --- a/tests/shared/src/test/scala/cats/effect/std/HotswapSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/HotswapSuite.scala @@ -25,155 +25,150 @@ import cats.effect.unsafe.IORuntimeConfig import scala.concurrent.duration._ -class HotswapSuite extends BaseSpec { outer => - - sequential +class HotswapSuite extends BaseSuite { outer => def logged(log: Ref[IO, List[String]], name: String): Resource[IO, Unit] = Resource.make(log.update(_ :+ s"open $name"))(_ => log.update(_ :+ s"close $name")) - "Hotswap" should { - "run finalizer of target run when hotswap is finalized" in real { - val op = for { - log <- Ref.of[IO, List[String]](List()) - _ <- Hotswap[IO, Unit](logged(log, "a")).use(_ => IO.unit) - value <- log.get - } yield value - - op.flatMap { res => - IO { - res must beEqualTo(List("open a", "close a")) - } + real("run finalizer of target run when hotswap is finalized") { + val op = for { + log <- Ref.of[IO, List[String]](List()) + _ <- Hotswap[IO, Unit](logged(log, "a")).use(_ => IO.unit) + value <- log.get + } yield value + + op.flatMap { res => + IO { + assertEquals(res, List("open a", "close a")) } } + } - "acquire new resource and finalize old resource on swap" in real { - val op = for { - log <- Ref.of[IO, List[String]](List()) - _ <- Hotswap[IO, Unit](logged(log, "a")).use { - case (hotswap, _) => - hotswap.swap(logged(log, "b")) - } - value <- log.get - } yield value + real("acquire new resource and finalize old resource on swap") { + val op = for { + log <- Ref.of[IO, List[String]](List()) + _ <- Hotswap[IO, Unit](logged(log, "a")).use { + case (hotswap, _) => + hotswap.swap(logged(log, "b")) + } + value <- log.get + } yield value - op.flatMap { res => - IO { - res must beEqualTo(List("open a", "open b", "close a", "close b")) - } + op.flatMap { res => + IO { + assertEquals(res, List("open a", "open b", "close a", "close b")) } } + } - "finalize old resource on clear" in real { - val op = for { - log <- Ref.of[IO, List[String]](List()) - _ <- Hotswap[IO, Unit](logged(log, "a")).use { - case (hotswap, _) => - hotswap.clear *> hotswap.swap(logged(log, "b")) - } - value <- log.get - } yield value + real("finalize old resource on clear") { + val op = for { + log <- Ref.of[IO, List[String]](List()) + _ <- Hotswap[IO, Unit](logged(log, "a")).use { + case (hotswap, _) => + hotswap.clear *> hotswap.swap(logged(log, "b")) + } + value <- log.get + } yield value - op.flatMap { res => - IO { - res must beEqualTo(List("open a", "close a", "open b", "close b")) + op.flatMap { res => + IO { + assertEquals(res, List("open a", "close a", "open b", "close b")) + } + } + } + + ticked("not release current resource while it is in use") { implicit ticker => + val r = Resource.make(IO.ref(true))(_.set(false)) + val go = Hotswap.create[IO, Ref[IO, Boolean]].use { hs => + hs.swap(r) *> (IO.sleep(1.second) *> hs.clear).background.surround { + hs.get.use { + case Some(ref) => + val notReleased = ref.get.flatMap(b => IO(assert(b))) + notReleased *> IO.sleep(2.seconds) *> notReleased.void + case None => IO(assert(false)).void } } } - "not release current resource while it is in use" in ticked { implicit ticker => - val r = Resource.make(IO.ref(true))(_.set(false)) - val go = Hotswap.create[IO, Ref[IO, Boolean]].use { hs => - hs.swap(r) *> (IO.sleep(1.second) *> hs.clear).background.surround { + assertCompleteAs(go, ()) + } + + ticked("not finalize Hotswap while resource is in use") { implicit ticker => + val r = Resource.make(IO.ref(true))(_.set(false)) + val go = Hotswap.create[IO, Ref[IO, Boolean]].allocated.flatMap { + case (hs, fin) => + hs.swap(r) *> (IO.sleep(1.second) *> fin).background.surround { hs.get.use { case Some(ref) => - val notReleased = ref.get.flatMap(b => IO(b must beTrue)) + val notReleased = ref.get.flatMap(b => IO(assert(b))) notReleased *> IO.sleep(2.seconds) *> notReleased.void - case None => IO(false must beTrue).void + case None => IO(assert(false)).void } } - } - - go must completeAs(()) } - "not finalize Hotswap while resource is in use" in ticked { implicit ticker => - val r = Resource.make(IO.ref(true))(_.set(false)) - val go = Hotswap.create[IO, Ref[IO, Boolean]].allocated.flatMap { - case (hs, fin) => - hs.swap(r) *> (IO.sleep(1.second) *> fin).background.surround { - hs.get.use { - case Some(ref) => - val notReleased = ref.get.flatMap(b => IO(b must beTrue)) - notReleased *> IO.sleep(2.seconds) *> notReleased.void - case None => IO(false must beTrue).void - } - } - } + assertCompleteAs(go, ()) + } - go must completeAs(()) + ticked("resource can be accessed concurrently") { implicit ticker => + val go = Hotswap.create[IO, Unit].use { hs => + hs.swap(Resource.unit) *> + hs.get.useForever.background.surround { + IO.sleep(1.second) *> hs.get.use_ + } } - "resource can be accessed concurrently" in ticked { implicit ticker => - val go = Hotswap.create[IO, Unit].use { hs => - hs.swap(Resource.unit) *> - hs.get.useForever.background.surround { - IO.sleep(1.second) *> hs.get.use_ - } - } + assertCompleteAs(go, ()) + } - go must completeAs(()) + ticked("not block current resource while swap is instantiating new one") { implicit ticker => + val go = Hotswap.create[IO, Unit].use { hs => + hs.swap(IO.sleep(1.minute).toResource).start *> + IO.sleep(5.seconds) *> + hs.get.use_.timeout(1.second).void } + assertCompleteAs(go, ()) + } - "not block current resource while swap is instantiating new one" in ticked { - implicit ticker => - val go = Hotswap.create[IO, Unit].use { hs => - hs.swap(IO.sleep(1.minute).toResource).start *> - IO.sleep(5.seconds) *> - hs.get.use_.timeout(1.second).void + ticked( + "successfully cancel during swap and run finalizer if cancelation is requested while waiting for get to release") { + implicit ticker => + val go = Ref.of[IO, List[String]](List()).flatMap { log => + Hotswap[IO, Unit](logged(log, "a")).use { + case (hs, _) => + for { + _ <- hs.get.evalMap(_ => IO.sleep(1.minute)).use_.start + _ <- IO.sleep(2.seconds) + _ <- hs.swap(logged(log, "b")).timeoutTo(1.second, IO.unit) + value <- log.get + } yield value } - go must completeAs(()) - } + } - "successfully cancel during swap and run finalizer if cancelation is requested while waiting for get to release" in ticked { - implicit ticker => - val go = Ref.of[IO, List[String]](List()).flatMap { log => - Hotswap[IO, Unit](logged(log, "a")).use { - case (hs, _) => - for { - _ <- hs.get.evalMap(_ => IO.sleep(1.minute)).use_.start - _ <- IO.sleep(2.seconds) - _ <- hs.swap(logged(log, "b")).timeoutTo(1.second, IO.unit) - value <- log.get - } yield value - } - } + assertCompleteAs(go, List("open a", "open b", "close b")) + } - go must completeAs(List("open a", "open b", "close b")) + ticked("swap is safe to concurrent cancelation") { implicit ticker => + val go = IO.ref(false).flatMap { open => + Hotswap[IO, Unit](Resource.unit) + .use { + case (hs, _) => + hs.swap( + Resource.make(open.set(true))(_ => open.getAndSet(false).map(assert(_)).void)) + } + .race(IO.unit) *> open.get.map(r => assert(!r)) } - "swap is safe to concurrent cancelation" in ticked { implicit ticker => - val go = IO.ref(false).flatMap { open => - Hotswap[IO, Unit](Resource.unit) - .use { - case (hs, _) => - hs.swap(Resource.make(open.set(true))(_ => - open.getAndSet(false).map(_ should beTrue).void)) - } - .race(IO.unit) *> open.get.map(_ must beFalse) - } - - TestControl.executeEmbed(go, IORuntimeConfig(1, 2)).replicateA_(1000) must completeAs(()) - } + assertCompleteAs(TestControl.executeEmbed(go, IORuntimeConfig(1, 2)).replicateA_(1000), ()) + } - "get should not acquire a lock when there is no resource present" in ticked { - implicit ticker => - val go = Hotswap.create[IO, Unit].use { hs => - hs.get.useForever.start *> - IO.sleep(2.seconds) *> - hs.swap(Resource.unit) - } - go must completeAs(()) + ticked("get should not acquire a lock when there is no resource present") { implicit ticker => + val go = Hotswap.create[IO, Unit].use { hs => + hs.get.useForever.start *> + IO.sleep(2.seconds) *> + hs.swap(Resource.unit) } + assertCompleteAs(go, ()) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/MapRefSuite.scala b/tests/shared/src/test/scala/cats/effect/std/MapRefSuite.scala index 280196bdd1..93f77cd883 100644 --- a/tests/shared/src/test/scala/cats/effect/std/MapRefSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/MapRefSuite.scala @@ -23,250 +23,246 @@ import cats.implicits._ import scala.concurrent.duration._ -class MapRefSuite extends BaseSpec { +class MapRefSuite extends BaseSuite { private val smallDelay: IO[Unit] = IO.sleep(20.millis) private def awaitEqual[A: Eq](t: IO[A], success: A): IO[Unit] = t.flatMap(a => if (Eq[A].eqv(a, success)) IO.unit else smallDelay *> awaitEqual(t, success)) - "MapRef.ofSingleImmutableMapRef" should { - - "concurrent modifications" in real { - val finalValue = 100 - val r = MapRef.inSingleImmutableMap[SyncIO, IO, Unit, Int]().unsafeRunSync() - val modifies = List.fill(finalValue)(r(()).update(_.map(_ + 1))).parSequence - val test = r(()).set(Some(0)) *> modifies.start *> awaitEqual(r(()).get, finalValue.some) - test.map(_ => ok) - } - - "getAndSet - successful" in real { - val op = for { - r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - getAndSetResult <- r(()).getAndSet(Some(1)) - getResult <- r(()).get - } yield getAndSetResult == Some(0) && getResult == Some(1) - - op.map(a => a must_=== true) - } - - "access - successful" in real { - val op = for { - r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - accessed <- r(()).access - (value, setter) = accessed - success <- setter(value.map(_ + 1)) - result <- r(()).get - } yield success && result == Some(1) - - op.map(a => a must_=== true) - } - - "access - setter should fail if value is modified before setter is called with None/Some" in real { - val op = for { - r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() - accessed <- r(()).access - (value, setter) = accessed - _ <- r(()).set(Some(5)) - success <- setter(value.map(_ + 1)) - result <- r(()).get - } yield !success && result == Some(5) - - op.map(a => a must_=== true) - } - - "access - setter should fail if value is modified before setter is called with init Some/Some" in real { - val op = for { - r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - accessed <- r(()).access - (value, setter) = accessed - _ <- r(()).set(Some(5)) - success <- setter(value.map(_ + 1)) - result <- r(()).get - } yield !success && result == Some(5) - - op.map(a => a must_=== true) - } - - "access - setter should fail if value is modified before setter is called with init Some/None" in real { - val op = for { - r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - accessed <- r(()).access - (value, setter) = accessed - _ <- r(()).set(Some(5)) - success <- setter(None) - result <- r(()).get - } yield !success && result == Some(5) + real("MapRef.ofSingleImmutableMapRef - concurrent modifications") { + val finalValue = 100 + val r = MapRef.inSingleImmutableMap[SyncIO, IO, Unit, Int]().unsafeRunSync() + val modifies = List.fill(finalValue)(r(()).update(_.map(_ + 1))).parSequence + r(()).set(Some(0)) *> modifies.start *> awaitEqual(r(()).get, finalValue.some) + } - op.map(a => a must_=== true) + real("MapRef.ofSingleImmutableMapRef - getAndSet - successful") { + for { + r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + getAndSetResult <- r(()).getAndSet(Some(1)) + getResult <- r(()).get + } yield { + assertEquals(getAndSetResult, Some(0)) + assertEquals(getResult, Some(1)) } + } - "tryUpdate - modification occurs successfully" in real { - val op = for { - r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - result <- r(()).tryUpdate(_.map(_ + 1)) - value <- r(()).get - } yield result && value == Some(1) - - op.map(a => a must_=== true) + real("MapRef.ofSingleImmutableMapRef - access - successful") { + for { + r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + accessed <- r(()).access + (value, setter) = accessed + success <- setter(value.map(_ + 1)) + result <- r(()).get + } yield { + assert(success) + assertEquals(result, Some(1)) } + } - "tryModifyState - modification occurs successfully" in real { - val op = for { - r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - result <- r(()).tryModifyState(State.pure(Some(1))) - } yield result.contains(Some(1)) - - op.map(a => a must_=== true) + real( + "MapRef.ofSingleImmutableMapRef - access - setter should fail if value is modified before setter is called with None/Some") { + for { + r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() + accessed <- r(()).access + (value, setter) = accessed + _ <- r(()).set(Some(5)) + success <- setter(value.map(_ + 1)) + result <- r(()).get + } yield { + assert(!success) + assertEquals(result, Some(5)) } + } - "modifyState - modification occurs successfully" in real { - val op = for { - r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - result <- r(()).modifyState(State.pure(Some(1))) - } yield result == Some(1) - - op.map(a => a must_=== true) + real( + "MapRef.ofSingleImmutableMapRef - access - setter should fail if value is modified before setter is called with init Some/Some") { + for { + r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + accessed <- r(()).access + (value, setter) = accessed + _ <- r(()).set(Some(5)) + success <- setter(value.map(_ + 1)) + result <- r(()).get + } yield { + assert(!success) + assertEquals(result, Some(5)) } } - "MapRef.ofShardedImmutableMapRef" should { - "return an updated value" in real { - val size = 10 - val key = 3 - val test = for { - map <- MapRef.ofShardedImmutableMap[IO, Int, String](size) - _ <- map(key).set("Foo".some) - out <- map(key).get - } yield out - - test.map(a => a must_=== Some("Foo")) + real( + "MapRef.ofSingleImmutableMapRef - access - setter should fail if value is modified before setter is called with init Some/None") { + for { + r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + accessed <- r(()).access + (value, setter) = accessed + _ <- r(()).set(Some(5)) + success <- setter(None) + result <- r(()).get + } yield { + assert(!success) + assertEquals(result, Some(5)) } + } - "work with convenience ops" in real { - val size = 10 - val key = 3 - val expect = "Foo" - val test = for { - map <- MapRef.ofShardedImmutableMap[IO, Int, String](size) - _ <- map.setKeyValue(key, expect) - out <- map(key).get - } yield out - - test.map(a => a must_=== Some(expect)) + real("MapRef.ofSingleImmutableMapRef - tryUpdate - modification occurs successfully") { + for { + r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + result <- r(()).tryUpdate(_.map(_ + 1)) + value <- r(()).get + } yield { + assert(result) + assertEquals(value, Some(1)) } } - "MapRef.ofConcurrentHashMap" should { + real("MapRef.ofSingleImmutableMapRef - tryModifyState - modification occurs successfully") { + for { + r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + result <- r(()).tryModifyState(State.pure(Some(1))) + } yield assertEquals(result, Option(Some(1))) + } - "concurrent modifications" in real { - val finalValue = 100 - val r = MapRef.inConcurrentHashMap[SyncIO, IO, Unit, Int]().unsafeRunSync() - val modifies = List.fill(finalValue)(r(()).update(_.map(_ + 1))).parSequence - val test = r(()).set(Some(0)) *> modifies.start *> awaitEqual(r(()).get, finalValue.some) - test.map(_ => ok) - } + real("MapRef.ofSingleImmutableMapRef - modifyState - modification occurs successfully") { + for { + r <- MapRef.ofSingleImmutableMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + result <- r(()).modifyState(State.pure(Some(1))) + } yield assertEquals(result, Some(1)) + } - "getAndSet - successful" in real { - val op = for { - r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - getAndSetResult <- r(()).getAndSet(Some(1)) - getResult <- r(()).get - } yield getAndSetResult == Some(0) && getResult == Some(1) + real("MapRef.ofSharedImmutableMapRef - return an updated value") { + val size = 10 + val key = 3 + for { + map <- MapRef.ofShardedImmutableMap[IO, Int, String](size) + _ <- map(key).set("Foo".some) + out <- map(key).get + } yield assertEquals(out, Some("Foo")) + } - op.map(a => a must_=== true) - } + real("MapRef.ofSharedImmutableMapRef - work with convenience ops") { + val size = 10 + val key = 3 + val expect = "Foo" + for { + map <- MapRef.ofShardedImmutableMap[IO, Int, String](size) + _ <- map.setKeyValue(key, expect) + out <- map(key).get + } yield assertEquals(out, Some(expect)) + } - "access - successful" in real { - val op = for { - r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - accessed <- r(()).access - (value, setter) = accessed - success <- setter(value.map(_ + 1)) - result <- r(()).get - } yield success && result == Some(1) + real("MapRef.ofConcurrentHashMap - concurrent modifications") { + val finalValue = 100 + val r = MapRef.inConcurrentHashMap[SyncIO, IO, Unit, Int]().unsafeRunSync() + val modifies = List.fill(finalValue)(r(()).update(_.map(_ + 1))).parSequence + r(()).set(Some(0)) *> modifies.start *> awaitEqual(r(()).get, finalValue.some) + } - op.map(a => a must_=== true) + real("MapRef.ofConcurrentHashMap - getAndSet - successful") { + for { + r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + getAndSetResult <- r(()).getAndSet(Some(1)) + getResult <- r(()).get + } yield { + assertEquals(getAndSetResult, Some(0)) + assertEquals(getResult, Some(1)) } + } - "access - setter should fail if value is modified before setter is called with None/Some" in real { - val op = for { - r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() - accessed <- r(()).access - (value, setter) = accessed - _ <- r(()).set(Some(5)) - success <- setter(value.map(_ + 1)) - result <- r(()).get - } yield !success && result == Some(5) - - op.map(a => a must_=== true) + real("MapRef.ofConcurrentHashMap - access - successful") { + for { + r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + accessed <- r(()).access + (value, setter) = accessed + success <- setter(value.map(_ + 1)) + result <- r(()).get + } yield { + assert(success) + assertEquals(result, Some(1)) } + } - "access - setter should fail if value is modified before setter is called with init Some/Some" in real { - val op = for { - r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - accessed <- r(()).access - (value, setter) = accessed - _ <- r(()).set(Some(5)) - success <- setter(value.map(_ + 1)) - result <- r(()).get - } yield !success && result == Some(5) - - op.map(a => a must_=== true) + real( + "MapRef.ofConcurrentHashMap - access - setter should fail if value is modified before setter is called with None/Some") { + for { + r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() + accessed <- r(()).access + (value, setter) = accessed + _ <- r(()).set(Some(5)) + success <- setter(value.map(_ + 1)) + result <- r(()).get + } yield { + assert(!success) + assertEquals(result, Some(5)) } + } - "access - setter should fail if value is modified before setter is called with init Some/None" in real { - val op = for { - r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - accessed <- r(()).access - (value, setter) = accessed - _ <- r(()).set(Some(5)) - success <- setter(None) - result <- r(()).get - } yield !success && result == Some(5) - - op.map(a => a must_=== true) + real( + "MapRef.ofConcurrentHashMap - access - setter should fail if value is modified before setter is called with init Some/Some") { + for { + r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + accessed <- r(()).access + (value, setter) = accessed + _ <- r(()).set(Some(5)) + success <- setter(value.map(_ + 1)) + result <- r(()).get + } yield { + assert(!success) + assertEquals(result, Some(5)) } + } - "tryUpdate - modification occurs successfully" in real { - val op = for { - r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - result <- r(()).tryUpdate(_.map(_ + 1)) - value <- r(()).get - } yield result && value == Some(1) - - op.map(a => a must_=== true) + real( + "MapRef.ofConcurrentHashMap - access - setter should fail if value is modified before setter is called with init Some/None") { + for { + r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + accessed <- r(()).access + (value, setter) = accessed + _ <- r(()).set(Some(5)) + success <- setter(None) + result <- r(()).get + } yield { + assert(!success) + assertEquals(result, Some(5)) } + } - "tryModifyState - modification occurs successfully" in real { - val op = for { - r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - result <- r(()).tryModifyState(State.pure(Some(1))) - } yield result.contains(Some(1)) - - op.map(a => a must_=== true) + real("MapRef.ofConcurrentHashMap - tryUpdate - modification occurs successfully") { + for { + r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + result <- r(()).tryUpdate(_.map(_ + 1)) + value <- r(()).get + } yield { + assert(result) + assertEquals(value, Some(1)) } + } - "modifyState - modification occurs successfully" in real { - val op = for { - r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() - _ <- r(()).set(Some(0)) - result <- r(()).modifyState(State.pure(Some(1))) - } yield result == Some(1) + real("MapRef.ofConcurrentHashMap - ryModifyState - modification occurs successfully") { + for { + r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + result <- r(()).tryModifyState(State.pure(Some(1))) + } yield assertEquals(result, Option(Some(1))) + } - op.map(a => a must_=== true) - } + real("MapRef.ofConcurrentHashMap - modifyState - modification occurs successfully") { + for { + r <- MapRef.ofConcurrentHashMap[IO, Unit, Int]() + _ <- r(()).set(Some(0)) + result <- r(()).modifyState(State.pure(Some(1))) + } yield assertEquals(result, Some(1)) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/MutexSuite.scala b/tests/shared/src/test/scala/cats/effect/std/MutexSuite.scala index f1fb25829f..0cf32e89de 100644 --- a/tests/shared/src/test/scala/cats/effect/std/MutexSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/MutexSuite.scala @@ -21,32 +21,22 @@ package std import cats.arrow.FunctionK import cats.syntax.all._ -import org.specs2.specification.core.Fragments - import scala.concurrent.duration._ -final class MutexSuite extends BaseSpec with DetectPlatform { +final class MutexSuite extends BaseSuite with DetectPlatform { final override def executionTimeout = 2.minutes - "ConcurrentMutex" should { - tests(Mutex.apply[IO]) - } - - "Mutex with dual constructors" should { - tests(Mutex.in[IO, IO]) - } + tests("ConcurrentMutex", Mutex.apply[IO]) + tests("Mutex with dual constructors", Mutex.in[IO, IO]) + tests("MapK'd Mutex", Mutex[IO].map(_.mapK[IO](FunctionK.id))) - "MapK'd Mutex" should { - tests(Mutex[IO].map(_.mapK[IO](FunctionK.id))) - } - - def tests(mutex: IO[Mutex[IO]]): Fragments = { - "execute action if free" in real { + def tests(name: String, mutex: IO[Mutex[IO]]) = { + real(s"$name execute action if free") { mutex.flatMap { m => m.lock.surround(IO.unit).mustEqual(()) } } - "be reusable" in real { + real(s"$name be reusable") { mutex.flatMap { m => val p = m.lock.surround(IO.unit) @@ -54,7 +44,7 @@ final class MutexSuite extends BaseSpec with DetectPlatform { } } - "free on error" in real { + real(s"$name free on error") { mutex.flatMap { m => val p = m.lock.surround(IO.raiseError(new Exception)).attempt >> @@ -64,24 +54,26 @@ final class MutexSuite extends BaseSpec with DetectPlatform { } } - "block action if not free" in ticked { implicit ticker => - mutex.flatMap { m => + ticked(s"$name block action if not free") { implicit ticker => + assertNonTerminate(mutex.flatMap { m => m.lock.surround(IO.never) >> m.lock.surround(IO.unit) - } must nonTerminate + }) } - "used concurrently" in ticked { implicit ticker => - mutex.flatMap { m => - val p = - IO.sleep(1.second) >> - m.lock.surround(IO.unit) + ticked(s"$name used concurrently") { implicit ticker => + assertCompleteAs( + mutex.flatMap { m => + val p = + IO.sleep(1.second) >> + m.lock.surround(IO.unit) - (p, p).parTupled - } must completeAs(((), ())) + (p, p).parTupled + }, + ((), ())) } - "free on cancellation" in ticked { implicit ticker => + ticked(s"$name free on cancellation") { implicit ticker => val p = for { m <- mutex f <- m.lock.surround(IO.never).start @@ -90,10 +82,10 @@ final class MutexSuite extends BaseSpec with DetectPlatform { _ <- m.lock.surround(IO.unit) } yield () - p must completeAs(()) + assertCompleteAs(p, ()) } - "allow cancellation if blocked waiting for lock" in ticked { implicit ticker => + ticked(s"$name allow cancellation if blocked waiting for lock") { implicit ticker => val p = for { m <- mutex ref <- IO.ref(false) @@ -107,10 +99,10 @@ final class MutexSuite extends BaseSpec with DetectPlatform { _ <- b.cancel } yield v - p must completeAs(true) + assertCompleteAs(p, true) } - "gracefully handle canceled waiters" in ticked { implicit ticker => + ticked(s"$name gracefully handle canceled waiters") { implicit ticker => val p = mutex.flatMap { m => m.lock.surround { for { @@ -120,14 +112,14 @@ final class MutexSuite extends BaseSpec with DetectPlatform { } yield () } } - p must completeAs(()) + assertCompleteAs(p, ()) } - "not deadlock when highly contended" in real { + real(s"$name not deadlock when highly contended") { mutex.flatMap(_.lock.use_.parReplicateA_(10)).replicateA_(10000).as(true) } - "handle cancelled acquire" in real { + real(s"$name handle cancelled acquire") { val t = mutex.flatMap { m => val short = m.lock.use { _ => IO.sleep(5.millis) } val long = m.lock.use { _ => IO.sleep(20.millis) } @@ -144,7 +136,7 @@ final class MutexSuite extends BaseSpec with DetectPlatform { t mustEqual (()) } - "handle multiple concurrent cancels during release" in real { + real(s"$name handle multiple concurrent cancels during release") { val t = mutex.flatMap { m => val task = for { f1 <- m.lock.allocated @@ -165,31 +157,32 @@ final class MutexSuite extends BaseSpec with DetectPlatform { t mustEqual (()) } - "preserve waiters order (FIFO) on a non-race cancellation" in ticked { implicit ticker => - val numbers = List.range(1, 10) - val p = (mutex, IO.ref(List.empty[Int])).flatMapN { - case (m, ref) => - for { - f1 <- m.lock.allocated - (_, f1Release) = f1 - f2 <- m.lock.use_.start - _ <- IO.sleep(1.millis) - t <- numbers.parTraverse_ { i => - IO.sleep(i.millis) >> - m.lock.surround(ref.update(acc => i :: acc)) - }.start - _ <- IO.sleep(100.millis) - _ <- f2.cancel - _ <- f1Release - _ <- t.join - r <- ref.get - } yield r.reverse - } + ticked(s"$name preserve waiters order (FIFO) on a non-race cancellation") { + implicit ticker => + val numbers = List.range(1, 10) + val p = (mutex, IO.ref(List.empty[Int])).flatMapN { + case (m, ref) => + for { + f1 <- m.lock.allocated + (_, f1Release) = f1 + f2 <- m.lock.use_.start + _ <- IO.sleep(1.millis) + t <- numbers.parTraverse_ { i => + IO.sleep(i.millis) >> + m.lock.surround(ref.update(acc => i :: acc)) + }.start + _ <- IO.sleep(100.millis) + _ <- f2.cancel + _ <- f1Release + _ <- t.join + r <- ref.get + } yield r.reverse + } - p must completeAs(numbers) + assertCompleteAs(p, numbers) } - "cancellation must not corrupt Mutex" in ticked { implicit ticker => + ticked(s"$name cancellation must not corrupt Mutex") { implicit ticker => val p = mutex.flatMap { m => for { f1 <- m.lock.allocated @@ -207,7 +200,7 @@ final class MutexSuite extends BaseSpec with DetectPlatform { } yield () } - p must nonTerminate + assertNonTerminate(p) } } } diff --git a/tests/shared/src/test/scala/cats/effect/std/PQueueSuite.scala b/tests/shared/src/test/scala/cats/effect/std/PQueueSuite.scala index 3bcf7b8f6b..9b6c102b51 100644 --- a/tests/shared/src/test/scala/cats/effect/std/PQueueSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/PQueueSuite.scala @@ -27,27 +27,21 @@ import cats.arrow.FunctionK import cats.implicits._ import org.scalacheck.Arbitrary.arbitrary -import org.specs2.specification.core.Fragments import scala.collection.immutable.{Queue => ScalaQueue} import scala.concurrent.duration._ -class BoundedPQueueSuite extends BaseSpec with PQueueTests { +class BoundedPQueueSuite extends BaseSuite with PQueueTests { override def executionTimeout = 20.seconds implicit val orderForInt: Order[Int] = Order.fromLessThan((x, y) => x < y) - "PQueue" should { - boundedPQueueTests(PQueue.bounded) - } - - "PQueue mapK" should { - boundedPQueueTests(PQueue.bounded[IO, Int](_).map(_.mapK(FunctionK.id))) - } + boundedPQueueTests("PQueue", PQueue.bounded) + boundedPQueueTests("PQueue mapK", PQueue.bounded[IO, Int](_).map(_.mapK(FunctionK.id))) - private def boundedPQueueTests(constructor: Int => IO[PQueue[IO, Int]]): Fragments = { - "demonstrate offer and take with zero capacity" in real { + private def boundedPQueueTests(name: String, constructor: Int => IO[PQueue[IO, Int]]) = { + real("demonstrate offer and take with zero capacity") { for { q <- constructor(0) _ <- q.offer(1).start @@ -55,26 +49,29 @@ class BoundedPQueueSuite extends BaseSpec with PQueueTests { f <- q.take.start _ <- q.offer(2) v2 <- f.joinWithNever - r <- IO((v1 must beEqualTo(1)) and (v2 must beEqualTo(2))) + r <- IO { + assertEquals(v1, 1) + assertEquals(v2, 2) + } } yield r } - "async take with zero capacity" in realWithRuntime { implicit rt => + realWithRuntime("async take with zero capacity") { implicit rt => for { q <- constructor(0) _ <- q.offer(1).start v1 <- q.take - _ <- IO(v1 must beEqualTo(1)) + _ <- IO(assertEquals(v1, 1)) ff <- IO(q.take.unsafeToFuture()).start f <- ff.joinWithNever - _ <- IO(f.value must beEqualTo(None)) + _ <- IO(assertEquals(f.value, None)) _ <- q.offer(2) v2 <- IO.fromFuture(IO.pure(f)) - r <- IO(v2 must beEqualTo(2)) + r <- IO(assertEquals(v2, 2)) } yield r } - "offer/take with zero capacity" in real { + real("offer/take with zero capacity") { val count = 1000 def producer(q: PQueue[IO, Int], n: Int): IO[Unit] = @@ -97,61 +94,57 @@ class BoundedPQueueSuite extends BaseSpec with PQueueTests { c <- consumer(q, count).start _ <- p.join v <- c.joinWithNever - r <- IO(v must beEqualTo(count.toLong * (count - 1) / 2)) + r <- IO(assertEquals(v, count.toLong * (count - 1) / 2)) } yield r } - negativeCapacityConstructionTests(constructor) - tryOfferOnFullTests(constructor, _.offer(_), _.tryOffer(_), false) - cancelableOfferTests(constructor, _.offer(_), _.take, _.tryTake) - cancelableOfferBoundedTests(constructor, _.offer(_), _.take, _.tryTakeN(_)) - cancelableTakeTests(constructor, _.offer(_), _.take) - tryOfferTryTakeTests(constructor, _.tryOffer(_), _.tryTake) - commonTests(constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) - dequeueInPriorityOrder(constructor) - batchTakeTests(constructor, _.offer(_), _.tryTakeN(_)) - batchOfferTests(constructor, _.tryOfferN(_), _.tryTakeN(_)) + negativeCapacityConstructionTests(name, constructor) + tryOfferOnFullTests(name, constructor, _.offer(_), _.tryOffer(_), false) + cancelableOfferTests(name, constructor, _.offer(_), _.take, _.tryTake) + cancelableOfferBoundedTests(name, constructor, _.offer(_), _.take, _.tryTakeN(_)) + cancelableTakeTests(name, constructor, _.offer(_), _.take) + tryOfferTryTakeTests(name, constructor, _.tryOffer(_), _.tryTake) + commonTests(name, constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) + dequeueInPriorityOrder(name, constructor) + batchTakeTests(name, constructor, _.offer(_), _.tryTakeN(_)) + batchOfferTests(name, constructor, _.tryOfferN(_), _.tryTakeN(_)) } } -class UnboundedPQueueSuite extends BaseSpec with PQueueTests { - sequential +class UnboundedPQueueSuite extends BaseSuite with PQueueTests { override def executionTimeout = 20.seconds implicit val orderForInt: Order[Int] = Order.fromLessThan((x, y) => x < y) - "UnboundedPQueue" should { - unboundedPQueueTests(PQueue.unbounded) - } - - "UnboundedPQueue mapK" should { - unboundedPQueueTests(PQueue.unbounded[IO, Int].map(_.mapK(FunctionK.id))) - } - - private def unboundedPQueueTests(constructor: IO[PQueue[IO, Int]]): Fragments = { - tryOfferOnFullTests(_ => constructor, _.offer(_), _.tryOffer(_), true) - tryOfferTryTakeTests(_ => constructor, _.tryOffer(_), _.tryTake) - commonTests(_ => constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) - dequeueInPriorityOrder(_ => constructor) - batchTakeTests(_ => constructor, _.offer(_), _.tryTakeN(_)) - batchOfferTests(_ => constructor, _.tryOfferN(_), _.tryTakeN(_)) + unboundedPQueueTests("UnboundedPQueue", PQueue.unbounded) + unboundedPQueueTests( + "UnboundedPQueue mapK", + PQueue.unbounded[IO, Int].map(_.mapK(FunctionK.id))) + + private def unboundedPQueueTests(name: String, constructor: IO[PQueue[IO, Int]]) = { + tryOfferOnFullTests(name, _ => constructor, _.offer(_), _.tryOffer(_), true) + tryOfferTryTakeTests(name, _ => constructor, _.tryOffer(_), _.tryTake) + commonTests(name, _ => constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) + dequeueInPriorityOrder(name, _ => constructor) + batchTakeTests(name, _ => constructor, _.offer(_), _.tryTakeN(_)) + batchOfferTests(name, _ => constructor, _.tryOfferN(_), _.tryTakeN(_)) } } -trait PQueueTests extends QueueTests[PQueue] { self: BaseSpec => +trait PQueueTests extends QueueTests[PQueue] { self: BaseSuite => - def dequeueInPriorityOrder(constructor: Int => IO[PQueue[IO, Int]]): Fragments = { + def dequeueInPriorityOrder(name: String, constructor: Int => IO[PQueue[IO, Int]]) = { /** * Hand-rolled scalacheck effect as we don't have that for CE3 yet */ - "should dequeue in priority order" in realProp(arbitrary[List[Int]]) { in => + realProp(s"$name - should dequeue in priority order", arbitrary[List[Int]]) { in => for { q <- constructor(Int.MaxValue) _ <- in.traverse_(q.offer(_)) out <- List.fill(in.length)(q.take).sequence - res <- IO(out must beEqualTo(in.sorted)) + res <- IO(assertEquals(out, in.sorted)) } yield res } } diff --git a/tests/shared/src/test/scala/cats/effect/std/QueueSuite.scala b/tests/shared/src/test/scala/cats/effect/std/QueueSuite.scala index 02b519ebb7..2f78e8b306 100644 --- a/tests/shared/src/test/scala/cats/effect/std/QueueSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/QueueSuite.scala @@ -25,136 +25,121 @@ package std import cats.arrow.FunctionK import cats.syntax.all._ -import org.specs2.execute.Result -import org.specs2.specification.core.Fragments - import scala.collection.immutable.{Queue => ScalaQueue} import scala.concurrent.duration._ -class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatform { +class BoundedQueueSuite extends BaseSuite with QueueTests[Queue] with DetectPlatform { - "BoundedQueue (concurrent)" should { - boundedQueueTests(i => if (i == 0) Queue.synchronous else Queue.boundedForConcurrent(i)) - } + boundedQueueTests( + "BoundedQueue (concurrent)", + i => if (i == 0) Queue.synchronous else Queue.boundedForConcurrent(i)) + boundedQueueTests("BoundedQueue (async)", Queue.bounded) - "BoundedQueue (async)" should { - boundedQueueTests(Queue.bounded) - } - - "BoundedQueue (unsafe)" should { - "permit tryOffer when empty" in real { - Queue.unsafeBounded[IO, Int](1024) flatMap { q => - for { - attempt <- IO(q.unsafeTryOffer(42)) - _ <- IO(attempt must beTrue) - i <- q.take - _ <- IO(i mustEqual 42) - } yield ok - } - } - - "forbid tryOffer when full" in real { - Queue.unsafeBounded[IO, Int](8) flatMap { q => - for { - _ <- 0.until(8).toList.traverse_(q.offer(_)) - attempt <- IO(q.unsafeTryOffer(42)) - _ <- IO(attempt must beFalse) - } yield ok - } + real("BoundedQueue (unsafe) - permit tryOffer when empty") { + Queue.unsafeBounded[IO, Int](1024) flatMap { q => + for { + attempt <- IO(q.unsafeTryOffer(42)) + _ <- IO(assert(attempt)) + i <- q.take + _ <- IO(assertEquals(i, 42)) + } yield () } } - "BoundedQueue constructor" should { - "not OOM" in real { - Queue.bounded[IO, Unit](Int.MaxValue).as(true) + real("BoundedQueue (unsafe) - forbid tryOffer when full") { + Queue.unsafeBounded[IO, Int](8) flatMap { q => + for { + _ <- 0.until(8).toList.traverse_(q.offer(_)) + attempt <- IO(q.unsafeTryOffer(42)) + _ <- IO(assert(!attempt)) + } yield () } } - "BoundedQueue mapK" should { - boundedQueueTests(Queue.bounded[IO, Int](_).map(_.mapK(FunctionK.id))) + real("BoundedQueue constructor - not OOM") { + Queue.bounded[IO, Unit](Int.MaxValue).as(true) } - "synchronous queue" should { - "respect fifo order" in ticked { implicit ticker => - val test = for { - q <- Queue.synchronous[IO, Int] + boundedQueueTests("BoundedQueue mapK", Queue.bounded[IO, Int](_).map(_.mapK(FunctionK.id))) - _ <- 0.until(5).toList traverse_ { i => - val f = for { - _ <- IO.sleep(i.second) - _ <- q.offer(i) - } yield () + ticked("synchronous queue - respect fifo order") { implicit ticker => + val test = for { + q <- Queue.synchronous[IO, Int] - f.start - } + _ <- 0.until(5).toList traverse_ { i => + val f = for { + _ <- IO.sleep(i.second) + _ <- q.offer(i) + } yield () - _ <- IO.sleep(5.seconds) - result <- q.take.replicateA(5) - } yield result + f.start + } - test must completeAs(0.until(5).toList) - } + _ <- IO.sleep(5.seconds) + result <- q.take.replicateA(5) + } yield result - "not lose offer when taker is canceled during exchange" in real { - val test = for { - q <- Queue.synchronous[IO, Unit] - latch <- CountDownLatch[IO](2) - offererDone <- IO.ref(false) + assertCompleteAs(test, 0.until(5).toList) + } - _ <- (latch.release *> latch.await *> q.offer(())) - .guarantee(offererDone.set(true)) - .start - taker <- (latch.release *> latch.await *> q.take).start + real("synchronous queue - not lose offer when taker is canceled during exchange") { + val test = for { + q <- Queue.synchronous[IO, Unit] + latch <- CountDownLatch[IO](2) + offererDone <- IO.ref(false) - _ <- latch.await - _ <- taker.cancel + _ <- (latch.release *> latch.await *> q.offer(())).guarantee(offererDone.set(true)).start + taker <- (latch.release *> latch.await *> q.take).start - // we should either have received the value successfully, or we left the value in queue - // what we *don't* want is to remove the value and then lose it due to cancelation - oc <- taker.join + _ <- latch.await + _ <- taker.cancel - _ <- - if (oc.isCanceled) { - // we (maybe) hit the race condition - // if we lost the value, q.take will hang - offererDone.get.flatMap(b => IO(b must beFalse)) *> q.take - } else { - // we definitely didn't hit the race condition, because we got the value in taker - IO.unit - } - } yield ok + // we should either have received the value successfully, or we left the value in queue + // what we *don't* want is to remove the value and then lose it due to cancelation + oc <- taker.join - test.parReplicateA_(if (isJVM) 10000 else 1).as(ok) - } + _ <- + if (oc.isCanceled) { + // we (maybe) hit the race condition + // if we lost the value, q.take will hang + offererDone.get.flatMap(b => IO(assert(!b))) *> q.take + } else { + // we definitely didn't hit the race condition, because we got the value in taker + IO.unit + } + } yield () - "not lose takers when offerer is canceled and there are no other takers" in real { - val test = for { - q <- Queue.synchronous[IO, Unit] - - latch1 <- IO.deferred[Unit] - offerer <- IO.uncancelable(p => latch1.complete(()) >> p(q.offer(()))).start - _ <- latch1.get - - // take and cancel the offerer at the same time - // the race condition we're going for is *simultaneous* - // failing to repeat the race is likely, in which case the - // offerer will be canceled before we finish registering the take - taker <- IO.both(q.take, offerer.cancel).start - - // if we failed the race condition above, this offer will unblock the take - // if we succeeded in replicating the race, this offer will have no taker - // with the bug, this will timeout since both will block - // with the bug fix, either the join will return immediately, or it will - // be unblocked by the offer - _ <- IO.race(taker.joinWithNever, q.offer(()).delayBy(500.millis)) - } yield () + test.parReplicateA_(if (isJVM) 10000 else 1) + } - test.parReplicateA(if (isJS || isNative) 1 else 1000).as(ok) - } + real( + "synchronous queue - not lose takers when offerer is canceled and there are no other takers") { + val test = for { + q <- Queue.synchronous[IO, Unit] + + latch1 <- IO.deferred[Unit] + offerer <- IO.uncancelable(p => latch1.complete(()) >> p(q.offer(()))).start + _ <- latch1.get + + // take and cancel the offerer at the same time + // the race condition we're going for is *simultaneous* + // failing to repeat the race is likely, in which case the + // offerer will be canceled before we finish registering the take + taker <- IO.both(q.take, offerer.cancel).start + + // if we failed the race condition above, this offer will unblock the take + // if we succeeded in replicating the race, this offer will have no taker + // with the bug, this will timeout since both will block + // with the bug fix, either the join will return immediately, or it will + // be unblocked by the offer + _ <- IO.race(taker.joinWithNever, q.offer(()).delayBy(500.millis)) + } yield () + + test.parReplicateA(if (isJS || isNative) 1 else 1000) } - private def boundedQueueTests(constructor: Int => IO[Queue[IO, Int]]): Fragments = { - "demonstrate offer and take with zero capacity" in real { + private def boundedQueueTests(name: String, constructor: Int => IO[Queue[IO, Int]]) = { + real(s"$name - demonstrate offer and take with zero capacity") { for { q <- constructor(0) _ <- q.offer(1).start @@ -162,11 +147,12 @@ class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatf f <- q.take.start _ <- q.offer(2) v2 <- f.joinWithNever - r <- IO((v1 must beEqualTo(1)) and (v2 must beEqualTo(2))) - } yield r + _ <- IO(assertEquals(v1, 1)) + _ <- IO(assertEquals(v2, 2)) + } yield () } - "respect fifo order with zero capacity" in ticked { implicit ticker => + ticked(s"$name - respect fifo order with zero capacity") { implicit ticker => val test = for { q <- constructor(0) @@ -177,10 +163,10 @@ class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatf results <- q.take.replicateA(5) } yield results - test must completeAs(List(0, 1, 2, 3, 4)) + assertCompleteAs(test, List(0, 1, 2, 3, 4)) } - "demonstrate cancelable offer with zero capacity" in ticked { implicit ticker => + ticked(s"$name - demonstrate cancelable offer with zero capacity") { implicit ticker => val test1 = for { q <- constructor(0) @@ -194,7 +180,7 @@ class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatf outcome <- offerer2.join } yield (result, outcome.isSuccess) - test1 must completeAs((1, true)) + assertCompleteAs(test1, (1, true)) val test2 = for { q <- constructor(0) @@ -209,10 +195,10 @@ class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatf outcome <- offerer1.join } yield (result, outcome.isSuccess) - test2 must completeAs((0, true)) + assertCompleteAs(test2, (0, true)) } - "demonstrate cancelable take with zero capacity" in ticked { implicit ticker => + ticked(s"$name - demonstrate cancelable take with zero capacity") { implicit ticker => val test1 = for { q <- constructor(0) @@ -226,7 +212,7 @@ class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatf result <- taker2.joinWithNever } yield result - test1 must completeAs(42) + assertCompleteAs(test1, 42) val test2 = for { q <- constructor(0) @@ -241,25 +227,25 @@ class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatf result <- taker2.joinWithNever } yield result - test2 must completeAs(42) + assertCompleteAs(test2, 42) } - "async take with zero capacity" in realWithRuntime { implicit rt => + realWithRuntime("async take with zero capacity") { implicit rt => for { q <- constructor(0) _ <- q.offer(1).start v1 <- q.take - _ <- IO(v1 must beEqualTo(1)) + _ <- IO(assertEquals(v1, 1)) ff <- IO(q.take.unsafeToFuture()).start f <- ff.joinWithNever - _ <- IO(f.value must beEqualTo(None)) + _ <- IO(assertEquals(f.value, None)) _ <- q.offer(2) v2 <- IO.fromFuture(IO.pure(f)) - r <- IO(v2 must beEqualTo(2)) + r <- IO(assertEquals(v2, 2)) } yield r } - "offer/take with zero capacity" in real { + real(s"$name - offer/take with zero capacity") { val count = 1000 def producer(q: Queue[IO, Int], n: Int): IO[Unit] = @@ -282,11 +268,11 @@ class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatf c <- consumer(q, count).start _ <- p.join v <- c.joinWithNever - r <- IO(v must beEqualTo(count.toLong * (count - 1) / 2)) + r <- IO(assertEquals(v, count.toLong * (count - 1) / 2)) } yield r } - "offer/take from many fibers simultaneously" in real { + real(s"$name - offer/take from many fibers simultaneously") { val fiberCount = 50 val expected = 0.until(fiberCount) flatMap { i => 0.until(i).map(_ => i) } @@ -309,11 +295,11 @@ class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatf results <- produce &> consume - _ <- IO(results must containTheSameElementsAs(expected)) - } yield ok + _ <- IO(assertEquals(results.sorted, expected.toList)) + } yield () } - "offer/take at high contention" in real { + real(s"$name - offer/take at high contention") { val size = if (isJS || isNative) 10000 else 100000 val action = constructor(size) flatMap { q => @@ -329,20 +315,20 @@ class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatf offerers &> takers } - action.as(ok) + action } - "offer/take with a single consumer and high contention" in real { + real(s"$name - offer/take with a single consumer and high contention") { constructor(8) flatMap { q => val offerer = List.fill(8)(List.fill(8)(0)).parTraverse_(_.traverse(q.offer(_))) val iter = if (isJVM) 1000 else 1 (offerer &> 0.until(8 * 8).toList.traverse_(_ => q.take)).replicateA_(iter) *> - q.size.flatMap(s => IO(s mustEqual 0)) + q.size.flatMap(s => IO(assertEquals(s, 0))) } } - "offer/take/takeN with a single consumer and high contention" in real { + real(s"$name - offer/take/takeN with a single consumer and high contention") { constructor(8) flatMap { q => val offerer = List.fill(8)(List.fill(8)(0)).parTraverse_(_.traverse(q.offer(_))) @@ -361,176 +347,167 @@ class BoundedQueueSuite extends BaseSpec with QueueTests[Queue] with DetectPlatf } (offerer &> taker(0)).replicateA_(if (isJVM) 1000 else 1) *> - q.size.flatMap(s => IO(s mustEqual 0)) + q.size.flatMap(s => IO(assertEquals(s, 0))) } } - negativeCapacityConstructionTests(constructor) - tryOfferOnFullTests(constructor, _.offer(_), _.tryOffer(_), false) - cancelableOfferTests(constructor, _.offer(_), _.take, _.tryTake) - cancelableOfferBoundedTests(constructor, _.offer(_), _.take, _.tryTakeN(_)) - cancelableTakeTests(constructor, _.offer(_), _.take) - tryOfferTryTakeTests(constructor, _.tryOffer(_), _.tryTake) - commonTests(constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) - batchTakeTests(constructor, _.offer(_), _.tryTakeN(_)) - batchOfferTests(constructor, _.tryOfferN(_), _.tryTakeN(_)) - boundedBatchOfferTests(constructor, _.tryOfferN(_), _.tryTakeN(_)) + negativeCapacityConstructionTests(name, constructor) + tryOfferOnFullTests(name, constructor, _.offer(_), _.tryOffer(_), false) + cancelableOfferTests(name, constructor, _.offer(_), _.take, _.tryTake) + cancelableOfferBoundedTests(name, constructor, _.offer(_), _.take, _.tryTakeN(_)) + cancelableTakeTests(name, constructor, _.offer(_), _.take) + tryOfferTryTakeTests(name, constructor, _.tryOffer(_), _.tryTake) + commonTests(name, constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) + batchTakeTests(name, constructor, _.offer(_), _.tryTakeN(_)) + batchOfferTests(name, constructor, _.tryOfferN(_), _.tryTakeN(_)) + boundedBatchOfferTests(name, constructor, _.tryOfferN(_), _.tryTakeN(_)) } } -class UnboundedQueueSuite extends BaseSpec with QueueTests[Queue] { - sequential +class UnboundedQueueSuite extends BaseSuite with QueueTests[Queue] { - "UnboundedQueue (concurrent)" should { - unboundedQueueTests(Queue.unboundedForConcurrent) - } + unboundedQueueTests("UnboundedQueue (concurrent)", Queue.unboundedForConcurrent) - "UnboundedQueue (async)" should { - unboundedQueueTests(Queue.unboundedForAsync) - } + unboundedQueueTests("UnboundedQueue (async)", Queue.unboundedForAsync) - "UnboundedQueue (unsafe)" should { - "pass a value from unsafeOffer to take" in real { - Queue.unsafeUnbounded[IO, Int] flatMap { q => - for { - _ <- IO(q.unsafeOffer(42)) - i <- q.take - _ <- IO(i mustEqual 42) - } yield ok - } + real("UnboundedQueue (unsafe) - pass a value from unsafeOffer to take") { + Queue.unsafeUnbounded[IO, Int] flatMap { q => + for { + _ <- IO(q.unsafeOffer(42)) + i <- q.take + _ <- IO(assertEquals(i, 42)) + } yield () } } - "UnboundedQueue mapk" should { - unboundedQueueTests(Queue.unbounded[IO, Int].map(_.mapK(FunctionK.id))) - } + unboundedQueueTests("UnboundedQueue mapk", Queue.unbounded[IO, Int].map(_.mapK(FunctionK.id))) - private def unboundedQueueTests(constructor: IO[Queue[IO, Int]]): Fragments = { - tryOfferOnFullTests(_ => constructor, _.offer(_), _.tryOffer(_), true) - tryOfferTryTakeTests(_ => constructor, _.tryOffer(_), _.tryTake) - commonTests(_ => constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) - batchTakeTests(_ => constructor, _.offer(_), _.tryTakeN(_)) - batchOfferTests(_ => constructor, _.tryOfferN(_), _.tryTakeN(_)) - cancelableTakeTests(_ => constructor, _.offer(_), _.take) + private def unboundedQueueTests(name: String, constructor: IO[Queue[IO, Int]]) = { + tryOfferOnFullTests(name, _ => constructor, _.offer(_), _.tryOffer(_), true) + tryOfferTryTakeTests(name, _ => constructor, _.tryOffer(_), _.tryTake) + commonTests(name, _ => constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) + batchTakeTests(name, _ => constructor, _.offer(_), _.tryTakeN(_)) + batchOfferTests(name, _ => constructor, _.tryOfferN(_), _.tryTakeN(_)) + cancelableTakeTests(name, _ => constructor, _.offer(_), _.take) } } -class DroppingQueueSuite extends BaseSpec with QueueTests[Queue] { - sequential - - "DroppingQueue (concurrent)" should { - droppingQueueTests(i => if (i < 1) Queue.dropping(i) else Queue.droppingForConcurrent(i)) - } - - "DroppingQueue (async)" should { - droppingQueueTests(Queue.dropping) - } - - "DroppingQueue mapK" should { - droppingQueueTests(Queue.dropping[IO, Int](_).map(_.mapK(FunctionK.id))) - } - - private def droppingQueueTests(constructor: Int => IO[Queue[IO, Int]]): Fragments = { - negativeCapacityConstructionTests(constructor) - zeroCapacityConstructionTests(constructor) - tryOfferOnFullTests(constructor, _.offer(_), _.tryOffer(_), false) - cancelableOfferTests(constructor, _.offer(_), _.take, _.tryTake) - cancelableTakeTests(constructor, _.offer(_), _.take) - tryOfferTryTakeTests(constructor, _.tryOffer(_), _.tryTake) - commonTests(constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) - batchTakeTests(constructor, _.offer(_), _.tryTakeN(_)) - batchOfferTests(constructor, _.tryOfferN(_), _.tryTakeN(_)) +class DroppingQueueSuite extends BaseSuite with QueueTests[Queue] { + + droppingQueueTests( + "DroppingQueue (concurrent)", + i => if (i < 1) Queue.dropping(i) else Queue.droppingForConcurrent(i)) + droppingQueueTests("DroppingQueue (async)", Queue.dropping) + droppingQueueTests("DroppingQueue mapK", Queue.dropping[IO, Int](_).map(_.mapK(FunctionK.id))) + + private def droppingQueueTests(name: String, constructor: Int => IO[Queue[IO, Int]]) = { + negativeCapacityConstructionTests(name, constructor) + zeroCapacityConstructionTests(name, constructor) + tryOfferOnFullTests(name, constructor, _.offer(_), _.tryOffer(_), false) + cancelableOfferTests(name, constructor, _.offer(_), _.take, _.tryTake) + cancelableTakeTests(name, constructor, _.offer(_), _.take) + tryOfferTryTakeTests(name, constructor, _.tryOffer(_), _.tryTake) + commonTests(name, constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) + batchTakeTests(name, constructor, _.offer(_), _.tryTakeN(_)) + batchOfferTests(name, constructor, _.tryOfferN(_), _.tryTakeN(_)) } } -class CircularBufferQueueSuite extends BaseSpec with QueueTests[Queue] { - sequential +class CircularBufferQueueSuite extends BaseSuite with QueueTests[Queue] { - "CircularBuffer" should { - slidingQueueTests(Queue.circularBuffer) - } + slidingQueueTests("CircularBuffer", Queue.circularBuffer) + slidingQueueTests( + "CircularBuffer mapK", + Queue.circularBuffer[IO, Int](_).map(_.mapK(FunctionK.id))) - "CircularBuffer mapK" should { - slidingQueueTests(Queue.circularBuffer[IO, Int](_).map(_.mapK(FunctionK.id))) - } - - private def slidingQueueTests(constructor: Int => IO[Queue[IO, Int]]): Fragments = { - negativeCapacityConstructionTests(constructor) - zeroCapacityConstructionTests(constructor) - tryOfferOnFullTests(constructor, _.offer(_), _.tryOffer(_), true) - commonTests(constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) - batchTakeTests(constructor, _.offer(_), _.tryTakeN(_)) - batchOfferTests(constructor, _.tryOfferN(_), _.tryTakeN(_)) + private def slidingQueueTests(name: String, constructor: Int => IO[Queue[IO, Int]]) = { + negativeCapacityConstructionTests(name, constructor) + zeroCapacityConstructionTests(name, constructor) + tryOfferOnFullTests(name, constructor, _.offer(_), _.tryOffer(_), true) + commonTests(name, constructor, _.offer(_), _.tryOffer(_), _.take, _.tryTake, _.size) + batchTakeTests(name, constructor, _.offer(_), _.tryTakeN(_)) + batchOfferTests(name, constructor, _.tryOfferN(_), _.tryTakeN(_)) } } -trait QueueTests[Q[_[_], _]] { self: BaseSpec => +trait QueueTests[Q[_[_], _]] { self: BaseSuite => - def zeroCapacityConstructionTests(constructor: Int => IO[Q[IO, Int]]): Fragments = { - "should raise an exception when constructed with zero capacity" in real { + def zeroCapacityConstructionTests(name: String, constructor: Int => IO[Q[IO, Int]]) = { + real(s"$name - should raise an exception when constructed with zero capacity") { val test = IO.defer(constructor(0)).attempt test.flatMap { res => IO { - res must beLike { case Left(e) => e must haveClass[IllegalArgumentException] } + res match { + case Left(e) => assert(e.isInstanceOf[IllegalArgumentException]) + case Right(v) => fail(s"Expected Left, got $v") + } } } } } - def negativeCapacityConstructionTests(constructor: Int => IO[Q[IO, Int]]): Fragments = { - "should raise an exception when constructed with a negative capacity" in real { + def negativeCapacityConstructionTests(name: String, constructor: Int => IO[Q[IO, Int]]) = { + real(s"$name - should raise an exception when constructed with a negative capacity") { val test = IO.defer(constructor(-1)).attempt test.flatMap { res => IO { - res must beLike { case Left(e) => e must haveClass[IllegalArgumentException] } + res match { + case Left(e) => assert(e.isInstanceOf[IllegalArgumentException]) + case Right(v) => fail(s"Expected Left, got $v") + } } } } } def batchOfferTests( + name: String, constructor: Int => IO[Q[IO, Int]], tryOfferN: (Q[IO, Int], List[Int]) => IO[List[Int]], tryTakeN: (Q[IO, Int], Option[Int]) => IO[List[Int]], transform: List[Int] => List[Int] = identity - ): Fragments = { - "should offer all records when there is room" in real { + ) = { + real(s"$name - should offer all records when there is room") { for { q <- constructor(5) offerR <- tryOfferN(q, List(1, 2, 3, 4, 5)) takeR <- tryTakeN(q, None) - r <- IO( - (transform(takeR) must beEqualTo(List(1, 2, 3, 4, 5))) and - (offerR must beEqualTo(List.empty))) + r <- IO { + assertEquals(transform(takeR), List(1, 2, 3, 4, 5)) + assertEquals(offerR, List.empty) + } } yield r } } def boundedBatchOfferTests( + name: String, constructor: Int => IO[Q[IO, Int]], tryOfferN: (Q[IO, Int], List[Int]) => IO[List[Int]], tryTakeN: (Q[IO, Int], Option[Int]) => IO[List[Int]], transform: List[Int] => List[Int] = identity - ): Fragments = { - "should offer some records when the queue is full" in real { + ) = { + real(s"$name - should offer some records when the queue is full") { for { q <- constructor(5) offerR <- tryOfferN(q, List(1, 2, 3, 4, 5, 6, 7)) takeR <- tryTakeN(q, None) - r <- IO( - (transform(takeR) must beEqualTo(List(1, 2, 3, 4, 5))) and - (offerR must beEqualTo(List(6, 7)))) + r <- IO { + assertEquals(transform(takeR), List(1, 2, 3, 4, 5)) + assertEquals(offerR, List(6, 7)) + } } yield r } } def batchTakeTests( + name: String, constructor: Int => IO[Q[IO, Int]], offer: (Q[IO, Int], Int) => IO[Unit], tryTakeN: (Q[IO, Int], Option[Int]) => IO[List[Int]], - transform: List[Int] => List[Int] = identity): Fragments = { + transform: List[Int] => List[Int] = identity) = { - "take batches for all records when None is provided" in real { + real(s"$name - take batches for all records when None is provided") { for { q <- constructor(5) _ <- offer(q, 1) @@ -539,11 +516,11 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => _ <- offer(q, 4) _ <- offer(q, 5) b <- tryTakeN(q, None) - r <- IO(transform(b) must beEqualTo(List(1, 2, 3, 4, 5))) + r <- IO(assertEquals(transform(b), List(1, 2, 3, 4, 5))) } yield r } - "take batches for all records when maxN is provided" in real { + real(s"$name - take batches for all records when maxN is provided") { for { q <- constructor(5) _ <- offer(q, 1) @@ -552,11 +529,11 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => _ <- offer(q, 4) _ <- offer(q, 5) b <- tryTakeN(q, Some(5)) - r <- IO(transform(b) must beEqualTo(List(1, 2, 3, 4, 5))) + r <- IO(assertEquals(transform(b), List(1, 2, 3, 4, 5))) } yield r } - "take all records when maxN > queue size" in real { + real(s"$name - take all records when maxN > queue size") { for { q <- constructor(5) _ <- offer(q, 1) @@ -565,19 +542,19 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => _ <- offer(q, 4) _ <- offer(q, 5) b <- tryTakeN(q, Some(7)) - r <- IO(transform(b) must beEqualTo(List(1, 2, 3, 4, 5))) + r <- IO(assertEquals(transform(b), List(1, 2, 3, 4, 5))) } yield r } - "be empty when queue is empty" in real { + real(s"$name - be empty when queue is empty") { for { q <- constructor(5) b <- tryTakeN(q, Some(5)) - r <- IO(transform(b) must beEqualTo(List.empty)) + r <- IO(assertEquals(transform(b), List.empty)) } yield r } - "raise an exception when maxN is not > 0" in real { + real(s"$name - raise an exception when maxN is not > 0") { val toAttempt = for { q <- constructor(5) _ <- tryTakeN(q, Some(-1)) @@ -586,12 +563,15 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => val test = IO.defer(toAttempt).attempt test.flatMap { res => IO { - res must beLike { case Left(e) => e must haveClass[IllegalArgumentException] } + res match { + case Left(e) => assert(e.isInstanceOf[IllegalArgumentException]) + case Right(v) => fail(s"Expected Left, got $v") + } } } } - "release one offerer when queue is full" in real { + real(s"$name - release one offerer when queue is full") { val test = for { q <- constructor(5) _ <- offer(q, 0).replicateA_(5) @@ -608,13 +588,13 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => if (results.nonEmpty) expected.await else - IO(skipped("did not take any results")) - } yield ok + IO.println("did not take any results") + } yield () - test.parReplicateA(10).as(ok) + test.parReplicateA(10) } - "release all offerers when queue is full" in real { + real(s"$name - release all offerers when queue is full") { val test = for { q <- constructor(5) _ <- offer(q, 0).replicateA_(5) @@ -632,34 +612,36 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => _ <- expected.await } yield () - test.parReplicateA(10).as(ok) + test.parReplicateA(10) } } def tryOfferOnFullTests( + name: String, constructor: Int => IO[Q[IO, Int]], offer: (Q[IO, Int], Int) => IO[Unit], tryOffer: (Q[IO, Int], Int) => IO[Boolean], - expected: Boolean): Fragments = { + expected: Boolean) = { - "return false on tryOffer when the queue is full" in real { + real(s"$name - return false on tryOffer when the queue is full") { for { q <- constructor(2) _ <- offer(q, 0) _ <- offer(q, 0) v <- tryOffer(q, 1) - r <- IO(v must beEqualTo(expected)) + r <- IO(assertEquals(v, expected)) } yield r } } def offerTakeOverCapacityTests( + name: String, constructor: Int => IO[Q[IO, Int]], offer: (Q[IO, Int], Int) => IO[Unit], take: Q[IO, Int] => IO[Int] - ): Fragments = { + ) = { - "offer/take over capacity" in real { + real(s"$name - offer/take over capacity") { val count = 1000 def producer(q: Q[IO, Int], n: Int): IO[Unit] = @@ -682,18 +664,19 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => c <- consumer(q, count).start _ <- p.join v <- c.joinWithNever - r <- IO(v must beEqualTo(count.toLong * (count - 1) / 2)) + r <- IO(assertEquals(v, count.toLong * (count - 1) / 2)) } yield r } } def cancelableOfferTests( + name: String, constructor: Int => IO[Q[IO, Int]], offer: (Q[IO, Int], Int) => IO[Unit], take: Q[IO, Int] => IO[Int], - tryTake: Q[IO, Int] => IO[Option[Int]]): Fragments = { + tryTake: Q[IO, Int] => IO[Option[Int]]) = { - "demonstrate cancelable offer" in real { + real(s"$name - demonstrate cancelable offer") { for { q <- constructor(2) _ <- offer(q, 1) @@ -706,13 +689,13 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => v2 <- tryTake(q) r <- IO { - v1 must beEqualTo(1) - v2 must beNone + assertEquals(v1, 1) + assertEquals(v2, None) } } yield r } - "ensure offerers are awakened under all circumstances" in real { + real(s"$name - ensure offerers are awakened under all circumstances") { val test = for { // _ <- IO.println(s"$prefix >> iterating...") q <- constructor(5) @@ -753,58 +736,61 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => offer2.join } yield () - test.parReplicateA(16).as(ok) + test.parReplicateA(16) } } def cancelableOfferBoundedTests( + name: String, constructor: Int => IO[Q[IO, Int]], offer: (Q[IO, Int], Int) => IO[Unit], take: Q[IO, Int] => IO[Int], - tryTakeN: (Q[IO, Int], Option[Int]) => IO[List[Int]]): Fragments = { + tryTakeN: (Q[IO, Int], Option[Int]) => IO[List[Int]]) = { - "ensure offerers are awakened by tryTakeN after cancelation" in ticked { implicit ticker => - val test = for { - q <- constructor(4) + ticked(s"$name - ensure offerers are awakened by tryTakeN after cancelation") { + implicit ticker => + val test = for { + q <- constructor(4) - // fill the queue - _ <- 0.until(4).toList.traverse_(offer(q, _)) + // fill the queue + _ <- 0.until(4).toList.traverse_(offer(q, _)) - offerers <- 0.until(4).toList traverse { i => - // the sleep is to ensure the offerers are ordered - (IO.sleep(i.millis) *> offer(q, 10 + i)).start - } + offerers <- 0.until(4).toList traverse { i => + // the sleep is to ensure the offerers are ordered + (IO.sleep(i.millis) *> offer(q, 10 + i)).start + } - // allow quiescence - _ <- IO.sleep(10.millis) + // allow quiescence + _ <- IO.sleep(10.millis) - // take the *not*-first offerer and cancel it - _ <- offerers(1).cancel + // take the *not*-first offerer and cancel it + _ <- offerers(1).cancel - // fill up the offerers again - _ <- offer(q, 20).start - _ <- IO.sleep(1.millis) + // fill up the offerers again + _ <- offer(q, 20).start + _ <- IO.sleep(1.millis) - // drain the queue. this most notify *all* offerers - taken1 <- tryTakeN(q, None) - _ <- IO(taken1 mustEqual List(0, 1, 2, 3)) + // drain the queue. this most notify *all* offerers + taken1 <- tryTakeN(q, None) + _ <- IO(assertEquals(taken1, List(0, 1, 2, 3))) - // drain it again - // if the offerers weren't awakened, this will hang - taken2 <- 0.until(4).toList.traverse(_ => take(q)) - _ <- IO(taken2 must containTheSameElementsAs(List(10, 12, 13, 20))) - } yield () + // drain it again + // if the offerers weren't awakened, this will hang + taken2 <- 0.until(4).toList.traverse(_ => take(q)) + _ <- IO(assertEquals(taken2.sorted, List(10, 12, 13, 20))) + } yield () - test should completeAs(()) + assertCompleteAs(test, ()) } } def cancelableTakeTests( + name: String, constructor: Int => IO[Q[IO, Int]], offer: (Q[IO, Int], Int) => IO[Unit], - take: Q[IO, Int] => IO[Int]): Fragments = { + take: Q[IO, Int] => IO[Int]) = { - "not lose data on canceled take" in real { + real(s"$name - not lose data on canceled take") { val test = for { q <- constructor(100) @@ -837,7 +823,7 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => // if this doesn't line up, then it means that we lost an element in the middle // namely, when we were canceled, we took an element from the queue without producing - _ <- IO(next mustEqual (max + 1)) + _ <- IO(assertEquals(next, max + 1)) } yield false } else { // we consumed the whole sequence from the queue before cancelation, so no possible race @@ -846,24 +832,24 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => } yield continue val Bound = 10 // only try ten times before skipping - def loop(i: Int): IO[Result] = { + def loop(i: Int): IO[Unit] = { // we try iterating a few times to get canceled in the middle if (i > Bound) { - IO.pure(skipped(s"attempted $i times and could not reproduce scenario")) + IO.println(s"attempted $i times and could not reproduce scenario") } else { test flatMap { case true => loop(i + 1) - case false => IO.pure(ok) + case false => IO.unit } } } // even if we replicate the "cancelation in the middle", we might not hit a race condition // replicate this a bunch of times to build confidence that it works - loop(0).replicateA_(100).as(ok) + loop(0).replicateA_(100) } - "ensure takers are awakened under all circumstances" in real { + real(s"$name - ensure takers are awakened under all circumstances") { val test = for { // _ <- IO.println(s"$prefix >> iterating...") q <- constructor(64) @@ -901,16 +887,17 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => } } yield () - test.parReplicateA(16).as(ok) + test.parReplicateA(16) } } def tryOfferTryTakeTests( + name: String, constructor: Int => IO[Q[IO, Int]], tryOffer: (Q[IO, Int], Int) => IO[Boolean], - tryTake: Q[IO, Int] => IO[Option[Int]]): Fragments = { + tryTake: Q[IO, Int] => IO[Option[Int]]) = { - "tryOffer/tryTake" in real { + real(s"$name - tryOffer/tryTake") { val count = 1000 def producer(q: Q[IO, Int], n: Int): IO[Unit] = @@ -941,86 +928,98 @@ trait QueueTests[Q[_[_], _]] { self: BaseSpec => c <- consumer(q, count).start _ <- p.join v <- c.joinWithNever - r <- IO(v must beEqualTo(count.toLong * (count - 1) / 2)) + r <- IO(assertEquals(v, count.toLong * (count - 1) / 2)) } yield r } } def commonTests( + name: String, constructor: Int => IO[Q[IO, Int]], offer: (Q[IO, Int], Int) => IO[Unit], tryOffer: (Q[IO, Int], Int) => IO[Boolean], take: Q[IO, Int] => IO[Int], tryTake: Q[IO, Int] => IO[Option[Int]], - size: Q[IO, Int] => IO[Int]): Fragments = { + size: Q[IO, Int] => IO[Int]) = { - "should return the queue size when added to" in real { + real(s"$name - should return the queue size when added to") { for { q <- constructor(2) _ <- offer(q, 1) _ <- take(q) _ <- offer(q, 2) sz <- size(q) - r <- IO(sz must beEqualTo(1)) + r <- IO(assertEquals(sz, 1)) } yield r } - "should return None on tryTake when the queue is empty" in real { + real(s"$name - should return None on tryTake when the queue is empty") { for { q <- constructor(2) v <- tryTake(q) - r <- IO(v must beNone) + r <- IO(assertEquals(v, None)) } yield r } - "demonstrate sequential offer and take" in real { + real(s"$name - demonstrate sequential offer and take") { for { q <- constructor(2) _ <- offer(q, 1) v1 <- take(q) _ <- offer(q, 2) v2 <- take(q) - r <- IO((v1 must beEqualTo(1)) and (v2 must beEqualTo(2))) + r <- IO { + assertEquals(v1, 1) + assertEquals(v2, 2) + } } yield r } - "demonstrate cancelable take" in real { + real(s"$name - demonstrate cancelable take") { for { q <- constructor(2) f <- take(q).start _ <- IO.sleep(10.millis) _ <- f.cancel v <- tryOffer(q, 1) - r <- IO(v must beTrue) + r <- IO(assert(v)) } yield r } - "async take" in realWithRuntime { implicit rt => + realWithRuntime("async take") { implicit rt => for { q <- constructor(10) _ <- offer(q, 1) v1 <- take(q) - _ <- IO(v1 must beEqualTo(1)) + _ <- IO(assertEquals(v1, 1)) f <- IO(take(q).unsafeToFuture()) - _ <- IO(f.value must beEqualTo(None)) + _ <- IO(assertEquals(f.value, None)) _ <- offer(q, 2) v2 <- IO.fromFuture(IO.pure(f)) - r <- IO(v2 must beEqualTo(2)) + r <- IO(assertEquals(v2, 2)) } yield r } - "should return the queue size when take precedes offer" in ticked { implicit ticker => - constructor(10).flatMap { q => - take(q).background.use { took => IO.sleep(1.second) *> offer(q, 1) *> took *> size(q) } - } must completeAs(0) + ticked(s"$name - should return the queue size when take precedes offer") { + implicit ticker => + assertCompleteAs( + constructor(10).flatMap { q => + take(q).background.use { took => + IO.sleep(1.second) *> offer(q, 1) *> took *> size(q) + } + }, + 0) } - "should return the queue size when take precedes tryOffer" in ticked { implicit ticker => - constructor(10).flatMap { q => - take(q).background.use { took => - IO.sleep(1.second) *> tryOffer(q, 1) *> took *> size(q) - } - } must completeAs(0) + ticked(s"$name - should return the queue size when take precedes tryOffer") { + implicit ticker => + assertCompleteAs( + constructor(10).flatMap { q => + take(q).background.use { took => + IO.sleep(1.second) *> tryOffer(q, 1) *> took *> size(q) + } + }, + 0) } } } diff --git a/tests/shared/src/test/scala/cats/effect/std/RandomSuite.scala b/tests/shared/src/test/scala/cats/effect/std/RandomSuite.scala index dc325e7b8c..1cc6700a65 100644 --- a/tests/shared/src/test/scala/cats/effect/std/RandomSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/RandomSuite.scala @@ -17,26 +17,21 @@ package cats.effect package std -import org.specs2.specification.core.Fragments - import scala.annotation.nowarn -class RandomSuite extends BaseSpec { - "scala.util.Random" should { - testRandom(Random.scalaUtilRandom[IO]) - } +class RandomSuite extends BaseSuite { + testRandom("scala.util.Random", Random.scalaUtilRandom[IO]) - "java.util.Random" should { - testRandom(Random.javaUtilRandom[IO](new java.util.Random(System.currentTimeMillis()))) - } + testRandom( + "java.util.Random", + Random.javaUtilRandom[IO](new java.util.Random(System.currentTimeMillis()))) - "java.security.SecureRandom" should { - testRandom(Random.javaSecuritySecureRandom[IO]): @nowarn("cat=deprecation") - } + testRandom("java.security.SecureRandom", Random.javaSecuritySecureRandom[IO]): @nowarn( + "cat=deprecation") - "javaUtilConcurrentThreadLocalRandom" should { - testRandom(IO.pure(Random.javaUtilConcurrentThreadLocalRandom[IO])) - } + testRandom( + "javaUtilConcurrentThreadLocalRandom", + IO.pure(Random.javaUtilConcurrentThreadLocalRandom[IO])) /** * It verifies the correctness of generating random numbers and other random elements within @@ -45,256 +40,221 @@ class RandomSuite extends BaseSpec { * @param randomGen * An IO-wrapped Random[IO] instance used for running random number generation tests */ - private def testRandom(randomGen: IO[Random[IO]]): Fragments = { - "betweenDouble" >> { - "generate a random double within a range" in real { - val min: Double = 0.0 - val max: Double = 1.0 - val numIterations: Int = 1000 - for { - random <- randomGen - randDoubles <- random.betweenDouble(min, max).replicateA(numIterations) - } yield randDoubles.forall(randDouble => randDouble >= min && randDouble <= max) - } + private def testRandom(name: String, randomGen: IO[Random[IO]]) = { + real(s"$name - betweenDouble - generate a random double within a range") { + val min: Double = 0.0 + val max: Double = 1.0 + val numIterations: Int = 1000 + for { + random <- randomGen + randDoubles <- random.betweenDouble(min, max).replicateA(numIterations) + } yield randDoubles.forall(randDouble => randDouble >= min && randDouble <= max) } - "betweenFloat" >> { - "generate a random float within a range" in real { - val min: Float = 0.0f - val max: Float = 1.0f - val numIterations: Int = 1000 - for { - random <- randomGen - randFloats <- random.betweenFloat(min, max).replicateA(numIterations) - } yield randFloats.forall(randFloat => randFloat >= min && randFloat <= max) - } + real(s"$name - betweenFloat - generate a random float within a range") { + val min: Float = 0.0f + val max: Float = 1.0f + val numIterations: Int = 1000 + for { + random <- randomGen + randFloats <- random.betweenFloat(min, max).replicateA(numIterations) + } yield randFloats.forall(randFloat => randFloat >= min && randFloat <= max) } - "betweenInt" >> { - "generate a random integer within a range" in real { - val min: Integer = 0 - val max: Integer = 10 - val numIterations: Int = 1000 - for { - random <- randomGen - randInts <- random.betweenInt(min, max).replicateA(numIterations) - } yield randInts.forall(randInt => randInt >= min && randInt <= max) - } + real(s"$name - betweenInt - generate a random integer within a range") { + val min: Integer = 0 + val max: Integer = 10 + val numIterations: Int = 1000 + for { + random <- randomGen + randInts <- random.betweenInt(min, max).replicateA(numIterations) + } yield randInts.forall(randInt => randInt >= min && randInt <= max) } - "betweenLong" >> { - "generate a random long within a range" in real { - val min: Long = 0L - val max: Long = 100L - val numIterations: Int = 1000 - for { - random <- randomGen - randLongs <- random.betweenLong(min, max).replicateA(numIterations) - } yield randLongs.forall(randLong => randLong >= min && randLong <= max) - } + real(s"$name - betweenLong - generate a random long within a range") { + val min: Long = 0L + val max: Long = 100L + val numIterations: Int = 1000 + for { + random <- randomGen + randLongs <- random.betweenLong(min, max).replicateA(numIterations) + } yield randLongs.forall(randLong => randLong >= min && randLong <= max) } - "nextAlphaNumeric" >> { - "generate random alphanumeric characters" in real { - val alphaNumeric: Set[Char] = (('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9')).toSet - val numIterations: Int = 1000 - for { - random <- randomGen - randomChars <- random.nextAlphaNumeric.replicateA(numIterations) - } yield randomChars.forall(randomChar => alphaNumeric.contains(randomChar)) - } + real(s"$name - nextAlphaNumeric - generate random alphanumeric characters") { + val alphaNumeric: Set[Char] = (('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9')).toSet + val numIterations: Int = 1000 + for { + random <- randomGen + randomChars <- random.nextAlphaNumeric.replicateA(numIterations) + } yield randomChars.forall(randomChar => alphaNumeric.contains(randomChar)) } - "nextBoolean" >> { - "generate random boolean values" in real { - for { - random <- randomGen - randomBoolean <- random.nextBoolean - } yield randomBoolean must beOneOf(true, false) - } + real(s"$name - nextBoolean - generate random boolean values") { + for { + random <- randomGen + randomBoolean <- random.nextBoolean + } yield assert(randomBoolean || !randomBoolean) } - "nextBytes" >> { - "securely generate random bytes" in real { - for { - random1 <- randomGen - bytes1 <- random1.nextBytes(128) - random2 <- randomGen - bytes2 <- random2.nextBytes(256) - } yield bytes1.length == 128 && bytes2.length == 256 - } - - "prevent array reference from leaking in ThreadLocalRandom.nextBytes impl" in real { - randomGen.flatMap { random => - val nextBytes = random.nextBytes(128) - for { - bytes1 <- nextBytes - bytes2 <- nextBytes - } yield bytes1 ne bytes2 - } - } + real(s"$name - nextBytes - securely generate random bytes") { + for { + random1 <- randomGen + bytes1 <- random1.nextBytes(128) + random2 <- randomGen + bytes2 <- random2.nextBytes(256) + } yield bytes1.length == 128 && bytes2.length == 256 + } - "prevent array reference from leaking in ScalaRandom.nextBytes impl" in real { + real( + s"$name - nextBytes - prevent array reference from leaking in ThreadLocalRandom.nextBytes impl") { + randomGen.flatMap { random => + val nextBytes = random.nextBytes(128) for { - random <- randomGen - nextBytes = random.nextBytes(128) bytes1 <- nextBytes bytes2 <- nextBytes } yield bytes1 ne bytes2 } } - "nextDouble" >> { - "generate random double values between 0.0 and 1.0" in real { - val numIterations: Int = 1000 - for { - random <- randomGen - randomDoubles <- random.nextDouble.replicateA(numIterations) - } yield randomDoubles.forall(double => double >= 0.0 && double < 1.0) - } + real( + s"$name - nextBytes - prevent array reference from leaking in ScalaRandom.nextBytes impl") { + for { + random <- randomGen + nextBytes = random.nextBytes(128) + bytes1 <- nextBytes + bytes2 <- nextBytes + } yield bytes1 ne bytes2 } - "nextFloat" >> { - "generate random float values between 0.0 and 1.0" in real { - val numIterations: Int = 1000 - for { - random <- randomGen - randomFloats <- random.nextFloat.replicateA(numIterations) - } yield randomFloats.forall(float => float >= 0.0f && float < 1.0f) - } + real(s"$name - nextDouble - generate random double values between 0.0 and 1.0") { + val numIterations: Int = 1000 + for { + random <- randomGen + randomDoubles <- random.nextDouble.replicateA(numIterations) + } yield randomDoubles.forall(double => double >= 0.0 && double < 1.0) } - "nextGaussian" >> { - "generate random Gaussian distributed double values with mean 0.0 and standard deviation 1.0" in real { - val sampleSize = 1000 - for { - random <- randomGen - gaussians <- random.nextGaussian.replicateA(sampleSize) - mean = gaussians.sum / sampleSize - variance = gaussians.map(x => math.pow(x - mean, 2)).sum / sampleSize - stddev = math.sqrt(variance) - } yield java.lang.Double.isFinite(mean) && java.lang.Double.isFinite(stddev) - } + real(s"$name - nextFloat - generate random float values between 0.0 and 1.0") { + val numIterations: Int = 1000 + for { + random <- randomGen + randomFloats <- random.nextFloat.replicateA(numIterations) + } yield randomFloats.forall(float => float >= 0.0f && float < 1.0f) } - "nextInt" >> { - "generate random int value" in real { - for { - random <- randomGen - int <- random.nextInt - } yield int must beBetween(Int.MinValue, Int.MaxValue) - } + real( + s"$name - nextGaussian - generate random Gaussian distributed double values with mean 0.0 and standard deviation 1.0") { + val sampleSize = 1000 + for { + random <- randomGen + gaussians <- random.nextGaussian.replicateA(sampleSize) + mean = gaussians.sum / sampleSize + variance = gaussians.map(x => math.pow(x - mean, 2)).sum / sampleSize + stddev = math.sqrt(variance) + } yield java.lang.Double.isFinite(mean) && java.lang.Double.isFinite(stddev) } - "nextIntBounded" >> { - "generate random int values within specified bounds" in real { - val bound: Int = 100 - val numIterations: Int = 1000 - for { - random <- randomGen - randomInts <- random.nextIntBounded(bound).replicateA(numIterations) - } yield randomInts.forall(int => int >= 0 && int < bound) - } + real(s"$name - nextInt - generate random int value") { + for { + random <- randomGen + int <- random.nextInt + } yield assert(int >= Int.MinValue && int <= Int.MaxValue) } - "nextLong" >> { - "generate random long value" in real { - for { - random <- randomGen - long <- random.nextLong - } yield long must beBetween(Long.MinValue, Long.MaxValue) - } + real(s"$name - nextIntBounded - generate random int values within specified bounds") { + val bound: Int = 100 + val numIterations: Int = 1000 + for { + random <- randomGen + randomInts <- random.nextIntBounded(bound).replicateA(numIterations) + } yield randomInts.forall(int => int >= 0 && int < bound) } - "nextLongBounded" >> { - "generate random long values within specified bounds" in real { - val bound: Long = 100L - val numIterations: Int = 1000 - for { - random <- randomGen - randomLongs <- random.nextLongBounded(bound).replicateA(numIterations) - } yield randomLongs.forall(long => long >= 0L && long < bound) - } + real(s"$name - nextLong - generate random long value") { + for { + random <- randomGen + long <- random.nextLong + } yield assert(long >= Long.MinValue && long <= Long.MaxValue) } - "nextPrintableChar" >> { - "generate random printable characters" in real { - val printableChars: Set[Char] = ((' ' to '~') ++ ('\u00A0' to '\u00FF')).toSet - val numIterations: Int = 1000 - for { - random <- randomGen - randomChars <- random.nextPrintableChar.replicateA(numIterations) - } yield randomChars.forall(char => printableChars.contains(char)) - } + real(s"$name - nextLongBounded - generate random long values within specified bounds") { + val bound: Long = 100L + val numIterations: Int = 1000 + for { + random <- randomGen + randomLongs <- random.nextLongBounded(bound).replicateA(numIterations) + } yield randomLongs.forall(long => long >= 0L && long < bound) } - "nextString" >> { - "generate a random string with the specified length" in real { - val length: Int = 100 - val numIterations: Int = 1000 - for { - random <- randomGen - randomStrings <- random.nextString(length).replicateA(numIterations) - } yield { - randomStrings.forall(_.length == length) - } - } + real(s"$name - nextPrintableChar - generate random printable characters") { + val printableChars: Set[Char] = ((' ' to '~') ++ ('\u00A0' to '\u00FF')).toSet + val numIterations: Int = 1000 + for { + random <- randomGen + randomChars <- random.nextPrintableChar.replicateA(numIterations) + } yield randomChars.forall(char => printableChars.contains(char)) } - "shuffleList" >> { - "shuffle a list" in real { - val sampleSize: Int = - 10000 // In case of modification, consider the probability of error - val list: List[Int] = (1 to sampleSize).toList - for { - random <- randomGen - shuffled <- random.shuffleList(list) - } yield shuffled != list && shuffled.sorted == list.sorted + real(s"$name - nextString - generate a random string with the specified length") { + val length: Int = 100 + val numIterations: Int = 1000 + for { + random <- randomGen + randomStrings <- random.nextString(length).replicateA(numIterations) + } yield { + randomStrings.forall(_.length == length) } } - "shuffleVector" >> { - "shuffle a vector" in real { - val sampleSize: Int = 10000 - val vector: Vector[Int] = (1 to sampleSize).toVector - for { - random <- randomGen - shuffled <- random.shuffleVector(vector) - } yield shuffled != vector && shuffled.sorted == vector.sorted - } + real(s"$name - shuffleList - shuffle a list") { + val sampleSize: Int = + 10000 // In case of modification, consider the probability of error + val list: List[Int] = (1 to sampleSize).toList + for { + random <- randomGen + shuffled <- random.shuffleList(list) + } yield shuffled != list && shuffled.sorted == list.sorted } - "oneOf" >> { - "return the only value provided" in real { - for { - random <- randomGen - chosen <- random.oneOf(42) - } yield chosen == 42 - } + real(s"$name - shuffleVector - shuffle a vector") { + val sampleSize: Int = 10000 + val vector: Vector[Int] = (1 to sampleSize).toVector + for { + random <- randomGen + shuffled <- random.shuffleVector(vector) + } yield shuffled != vector && shuffled.sorted == vector.sorted + } - "eventually choose all the given values at least once" in real { - val values = List(1, 2, 3, 4, 5) - def chooseAndAccumulate(random: Random[IO], ref: Ref[IO, Set[Int]]): IO[Set[Int]] = - random.oneOf(values.head, values.tail: _*).flatMap(x => ref.updateAndGet(_ + x)) - def haveChosenAllValues(ref: Ref[IO, Set[Int]]): IO[Boolean] = - ref.get.map(_ == values.toSet) + real(s"$name - oneOf - return the only value provided") { + for { + random <- randomGen + chosen <- random.oneOf(42) + } yield chosen == 42 + } - for { - random <- randomGen - ref <- Ref.of[IO, Set[Int]](Set.empty) - _ <- chooseAndAccumulate(random, ref).untilM_(haveChosenAllValues(ref)) - success <- haveChosenAllValues(ref) - } yield success - } + real(s"$name - oneOf - eventually choose all the given values at least once") { + val values = List(1, 2, 3, 4, 5) + def chooseAndAccumulate(random: Random[IO], ref: Ref[IO, Set[Int]]): IO[Set[Int]] = + random.oneOf(values.head, values.tail: _*).flatMap(x => ref.updateAndGet(_ + x)) + def haveChosenAllValues(ref: Ref[IO, Set[Int]]): IO[Boolean] = + ref.get.map(_ == values.toSet) + + for { + random <- randomGen + ref <- Ref.of[IO, Set[Int]](Set.empty) + _ <- chooseAndAccumulate(random, ref).untilM_(haveChosenAllValues(ref)) + success <- haveChosenAllValues(ref) + } yield success + } - "not select any value outside the provided list" in real { - val list: List[Int] = List(1, 2, 3, 4, 5) - val numIterations: Int = 1000 - for { - random <- randomGen - chosenValues <- random.oneOf(list.head, list.tail: _*).replicateA(numIterations) - } yield chosenValues.forall(list.contains) - } + real(s"$name - oneOf - not select any value outside the provided list") { + val list: List[Int] = List(1, 2, 3, 4, 5) + val numIterations: Int = 1000 + for { + random <- randomGen + chosenValues <- random.oneOf(list.head, list.tail: _*).replicateA(numIterations) + } yield chosenValues.forall(list.contains) } elementOfTests[Int, List[Int]]( @@ -343,44 +303,42 @@ class RandomSuite extends BaseSpec { emptyCollection: C, nonEmptyCollection: C, randomGen: IO[Random[IO]] - ): Fragments = { - - s"elementOf ($collectionType)" >> { - "reject an empty collection" in real { - for { - random <- randomGen - result <- random.elementOf(emptyCollection).attempt - } yield result.isLeft - } + ) = { - "eventually choose all elements of the given collection at least once" in real { - val xs = nonEmptyCollection - def chooseAndAccumulate(random: Random[IO], ref: Ref[IO, Set[A]]): IO[Set[A]] = - random.elementOf(xs).flatMap(x => ref.updateAndGet(_ + x)) - def haveChosenAllElements(ref: Ref[IO, Set[A]]): IO[Boolean] = - ref.get.map(_ == xs.toSet) + real(s"elementOf ($collectionType) - reject an empty collection") { + for { + random <- randomGen + result <- random.elementOf(emptyCollection).attempt + } yield result.isLeft + } - for { - random <- randomGen - ref <- Ref.of[IO, Set[A]](Set.empty) - _ <- chooseAndAccumulate(random, ref).untilM_(haveChosenAllElements(ref)) - success <- haveChosenAllElements(ref) - } yield success - } + real( + s"elementOf ($collectionType) - eventually choose all elements of the given collection at least once") { + val xs = nonEmptyCollection + def chooseAndAccumulate(random: Random[IO], ref: Ref[IO, Set[A]]): IO[Set[A]] = + random.elementOf(xs).flatMap(x => ref.updateAndGet(_ + x)) + def haveChosenAllElements(ref: Ref[IO, Set[A]]): IO[Boolean] = + ref.get.map(_ == xs.toSet) + + for { + random <- randomGen + ref <- Ref.of[IO, Set[A]](Set.empty) + _ <- chooseAndAccumulate(random, ref).untilM_(haveChosenAllElements(ref)) + success <- haveChosenAllElements(ref) + } yield success + } - "not select any value outside the provided collection" in real { - val numIterations: Int = 1000 - for { - random <- randomGen - chosenValues <- random.elementOf(nonEmptyCollection).replicateA(numIterations) - } yield { - val collectionVector: Vector[A] = nonEmptyCollection.toVector - chosenValues.forall(collectionVector.contains(_)) - } + real( + s"elementOf ($collectionType) - not select any value outside the provided collection") { + val numIterations: Int = 1000 + for { + random <- randomGen + chosenValues <- random.elementOf(nonEmptyCollection).replicateA(numIterations) + } yield { + val collectionVector: Vector[A] = nonEmptyCollection.toVector + chosenValues.forall(collectionVector.contains(_)) } - } - } } diff --git a/tests/shared/src/test/scala/cats/effect/std/SecureRandomSuite.scala b/tests/shared/src/test/scala/cats/effect/std/SecureRandomSuite.scala index 0320314979..61b4ce5007 100644 --- a/tests/shared/src/test/scala/cats/effect/std/SecureRandomSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/SecureRandomSuite.scala @@ -17,27 +17,25 @@ package cats.effect package std -class SecureRandomSuite extends BaseSpec { +class SecureRandomSuite extends BaseSuite { - "SecureRandom" should { - "securely generate random bytes" in real { - for { - random1 <- SecureRandom.javaSecuritySecureRandom[IO] - bytes1 <- random1.nextBytes(128) - random2 <- SecureRandom.javaSecuritySecureRandom[IO](2) - bytes2 <- random2.nextBytes(256) - bytes3 <- random2.nextBytes(1024) - } yield bytes1.length == 128 && bytes2.length == 256 && bytes3.length == 1024 - } + real("securely generate random bytes") { + for { + random1 <- SecureRandom.javaSecuritySecureRandom[IO] + bytes1 <- random1.nextBytes(128) + random2 <- SecureRandom.javaSecuritySecureRandom[IO](2) + bytes2 <- random2.nextBytes(256) + bytes3 <- random2.nextBytes(1024) + } yield bytes1.length == 128 && bytes2.length == 256 && bytes3.length == 1024 + } - "overrides nextInt" in real { - for { - secureRandom <- SecureRandom.javaSecuritySecureRandom[IO] - secureInts <- secureRandom.nextInt.replicateA(3) - insecureRandom <- Random.scalaUtilRandomSeedInt[IO](0) - insecureInts <- insecureRandom.nextInt.replicateA(3) - } yield secureInts != insecureInts - } + real("overrides nextInt") { + for { + secureRandom <- SecureRandom.javaSecuritySecureRandom[IO] + secureInts <- secureRandom.nextInt.replicateA(3) + insecureRandom <- Random.scalaUtilRandomSeedInt[IO](0) + insecureInts <- insecureRandom.nextInt.replicateA(3) + } yield secureInts != insecureInts } } diff --git a/tests/shared/src/test/scala/cats/effect/std/SemaphoreSuite.scala b/tests/shared/src/test/scala/cats/effect/std/SemaphoreSuite.scala index 6651b8585f..d23378fc49 100644 --- a/tests/shared/src/test/scala/cats/effect/std/SemaphoreSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/SemaphoreSuite.scala @@ -21,48 +21,38 @@ package std import cats.arrow.FunctionK import cats.syntax.all._ -import org.specs2.specification.core.Fragments - import scala.concurrent.duration._ -class SemaphoreSuite extends BaseSpec { outer => - - "Semaphore" should { - tests(n => Semaphore[IO](n)) - } - - "Semaphore with dual constructors" should { - tests(n => Semaphore.in[IO, IO](n)) - } +class SemaphoreSuite extends BaseSuite { outer => - "MapK'd semaphore" should { - tests(n => Semaphore[IO](n).map(_.mapK[IO](FunctionK.id))) - } + tests("Semaphore", n => Semaphore[IO](n)) + tests("Semaphore with dual constructors", n => Semaphore.in[IO, IO](n)) + tests("MapK'd semaphore", n => Semaphore[IO](n).map(_.mapK[IO](FunctionK.id))) - def tests(sc: Long => IO[Semaphore[IO]]): Fragments = { - "throw on negative n" in real { + def tests(name: String, sc: Long => IO[Semaphore[IO]]) = { + real(s"$name throw on negative n") { val op = IO(sc(-42)) op.mustFailWith[IllegalArgumentException] } - "block if no permits available" in ticked { implicit ticker => - sc(0).flatMap { sem => sem.permit.surround(IO.unit) } must nonTerminate + ticked(s"$name block if no permits available") { implicit ticker => + assertNonTerminate(sc(0).flatMap { sem => sem.permit.surround(IO.unit) }) } - "execute action if permit is available for it" in real { + real(s"$name execute action if permit is available for it") { sc(1).flatMap { sem => sem.permit.surround(IO.unit).mustEqual(()) } } - "tryPermit returns true if permit is available for it" in real { + real(s"$name tryPermit returns true if permit is available for it") { sc(1).flatMap { sem => sem.tryPermit.use(IO.pure).mustEqual(true) } } - "tryPermit returns false if permit is not available for it" in real { + real(s"$name tryPermit returns false if permit is not available for it") { sc(0).flatMap { sem => sem.tryPermit.use(IO.pure).mustEqual(false) } } - "unblock when permit is released" in ticked { implicit ticker => + ticked(s"$name unblock when permit is released") { implicit ticker => val p = for { sem <- sc(1) @@ -73,10 +63,10 @@ class SemaphoreSuite extends BaseSpec { outer => v <- ref.get } yield v - p must completeAs(true) + assertCompleteAs(p, true) } - "release permit if permit errors" in real { + real(s"$name release permit if permit errors") { for { sem <- sc(1) _ <- sem.permit.surround(IO.raiseError(new Exception)).attempt @@ -84,7 +74,7 @@ class SemaphoreSuite extends BaseSpec { outer => } yield res } - "release permit if tryPermit errors" in real { + real(s"$name release permit if tryPermit errors") { for { sem <- sc(1) _ <- sem.tryPermit.surround(IO.raiseError(new Exception)).attempt @@ -92,7 +82,7 @@ class SemaphoreSuite extends BaseSpec { outer => } yield res } - "release permit if permit completes" in real { + real(s"$name release permit if permit completes") { for { sem <- sc(1) _ <- sem.permit.surround(IO.unit) @@ -100,7 +90,7 @@ class SemaphoreSuite extends BaseSpec { outer => } yield res } - "release permit if tryPermit completes" in real { + real(s"$name release permit if tryPermit completes") { for { sem <- sc(1) _ <- sem.tryPermit.surround(IO.unit) @@ -108,7 +98,7 @@ class SemaphoreSuite extends BaseSpec { outer => } yield res } - "not release permit if tryPermit completes without acquiring a permit" in ticked { + ticked(s"$name not release permit if tryPermit completes without acquiring a permit") { implicit ticker => val p = for { sem <- sc(0) @@ -116,10 +106,10 @@ class SemaphoreSuite extends BaseSpec { outer => res <- sem.permit.surround(IO.unit) } yield res - p must nonTerminate + assertNonTerminate(p) } - "release permit if action gets canceled" in ticked { implicit ticker => + ticked(s"$name release permit if action gets canceled") { implicit ticker => val p = for { sem <- sc(1) @@ -129,10 +119,10 @@ class SemaphoreSuite extends BaseSpec { outer => _ <- sem.permit.surround(IO.unit) } yield () - p must completeAs(()) + assertCompleteAs(p, ()) } - "release tryPermit if action gets canceled" in ticked { implicit ticker => + ticked(s"$name release tryPermit if action gets canceled") { implicit ticker => val p = for { sem <- sc(1) @@ -142,10 +132,10 @@ class SemaphoreSuite extends BaseSpec { outer => _ <- sem.permit.surround(IO.unit) } yield () - p must completeAs(()) + assertCompleteAs(p, ()) } - "allow cancelation if blocked waiting for permit" in ticked { implicit ticker => + ticked(s"$name allow cancelation if blocked waiting for permit") { implicit ticker => val p = for { sem <- sc(0) ref <- IO.ref(false) @@ -155,20 +145,20 @@ class SemaphoreSuite extends BaseSpec { outer => v <- ref.get } yield v - p must completeAs(true) + assertCompleteAs(p, true) } - "not release permit when an acquire gets canceled" in ticked { implicit ticker => + ticked(s"$name not release permit when an acquire gets canceled") { implicit ticker => val p = for { sem <- sc(0) _ <- sem.permit.surround(IO.unit).timeout(1.second).attempt _ <- sem.permit.surround(IO.unit) } yield () - p must nonTerminate + assertNonTerminate(p) } - "acquire n synchronously" in real { + real(s"$name acquire n synchronously") { val n = 20 val op = sc(20).flatMap { s => (0 until n).toList.traverse(_ => s.acquire).void *> s.available @@ -177,7 +167,7 @@ class SemaphoreSuite extends BaseSpec { outer => op.mustEqual(0L) } - "acquireN does not leak permits upon cancelation" in ticked { implicit ticker => + ticked(s"$name acquireN does not leak permits upon cancelation") { implicit ticker => val op = Semaphore[IO](1L).flatMap { s => // acquireN(2L) gets one permit, then blocks waiting for another one // upon timeout, if it leaked and didn't release the permit it had, @@ -185,7 +175,7 @@ class SemaphoreSuite extends BaseSpec { outer => s.acquireN(2L).timeout(1.second).attempt *> s.acquire } - op must completeAs(()) + assertCompleteAs(op, ()) } def withLock[T](n: Long, s: Semaphore[IO], check: IO[T]): IO[(Long, T)] = @@ -196,7 +186,7 @@ class SemaphoreSuite extends BaseSpec { outer => .flatMap(t => check.tupleLeft(t)) } - "available with no available permits" in real { + real(s"$name available with no available permits") { val n = 20L val op = sc(n).flatMap { s => for { @@ -209,7 +199,7 @@ class SemaphoreSuite extends BaseSpec { outer => op.mustEqual(-1L -> 0L) } - "tryAcquire with available permits" in real { + real(s"$name tryAcquire with available permits") { val n = 20 val op = sc(30).flatMap { s => for { @@ -221,7 +211,7 @@ class SemaphoreSuite extends BaseSpec { outer => op.mustEqual(true) } - "tryAcquire with no available permits" in real { + real(s"$name tryAcquire with no available permits") { val n = 20 val op = sc(20).flatMap { s => for { @@ -233,7 +223,7 @@ class SemaphoreSuite extends BaseSpec { outer => op.mustEqual(false) } - "tryAcquireN all available permits" in real { + real(s"$name tryAcquireN all available permits") { val n = 20 val op = sc(20).flatMap { s => for { @@ -244,7 +234,7 @@ class SemaphoreSuite extends BaseSpec { outer => op.mustEqual(true) } - "offsetting acquires/releases - acquires parallel with releases" in real { + real(s"$name offsetting acquires/releases - acquires parallel with releases") { val permits: Vector[Long] = Vector(1, 0, 20, 4, 0, 5, 2, 1, 1, 3) val op = sc(0).flatMap { s => ( @@ -256,7 +246,7 @@ class SemaphoreSuite extends BaseSpec { outer => op.mustEqual(0L) } - "offsetting acquires/releases - individual acquires/increment in parallel" in real { + real(s"$name offsetting acquires/releases - individual acquires/increment in parallel") { val permits: Vector[Long] = Vector(1, 0, 20, 4, 0, 5, 2, 1, 1, 3) val op = sc(0).flatMap { s => ( @@ -268,7 +258,7 @@ class SemaphoreSuite extends BaseSpec { outer => op.mustEqual(0L) } - "available with available permits" in real { + real(s"$name available with available permits") { val op = sc(20).flatMap { s => for { _ <- s.acquireN(19) @@ -279,7 +269,7 @@ class SemaphoreSuite extends BaseSpec { outer => op.mustEqual(1L) } - "available with 0 available permits" in real { + real(s"$name available with 0 available permits") { val op = sc(20).flatMap { s => for { _ <- s.acquireN(20).void @@ -290,7 +280,7 @@ class SemaphoreSuite extends BaseSpec { outer => op.mustEqual(0L) } - "count with available permits" in real { + real(s"$name count with available permits") { val n = 18 val op = sc(20).flatMap { s => for { @@ -302,11 +292,11 @@ class SemaphoreSuite extends BaseSpec { outer => op.flatMap { case (available, count) => - IO(available mustEqual count) + IO(assertEquals(available, count)) } } - "count with no available permits" in real { + real(s"$name count with no available permits") { val n: Long = 8 val op = sc(n).flatMap { s => @@ -317,7 +307,7 @@ class SemaphoreSuite extends BaseSpec { outer => op.mustEqual(-n) } - "count with 0 available permits" in real { + real(s"$name count with 0 available permits") { val op = sc(20).flatMap { s => s.acquireN(20) >> s.count } op.mustEqual(0L) diff --git a/tests/shared/src/test/scala/cats/effect/std/SupervisorSuite.scala b/tests/shared/src/test/scala/cats/effect/std/SupervisorSuite.scala index 5edb00906f..1ed2bfe908 100644 --- a/tests/shared/src/test/scala/cats/effect/std/SupervisorSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/SupervisorSuite.scala @@ -19,54 +19,45 @@ package std import cats.syntax.all._ -import org.specs2.specification.core.Fragments - import scala.concurrent.duration._ -class SupervisorSuite extends BaseSpec with DetectPlatform { - - "Supervisor" should { - "concurrent" >> { - supervisorTests(Supervisor.applyForConcurrent) - } +class SupervisorSuite extends BaseSuite with DetectPlatform { - "async" >> { - supervisorTests(Supervisor.applyForAsync) - } - } + supervisorTests("concurrent", Supervisor.applyForConcurrent) + supervisorTests("async", Supervisor.applyForAsync) private def supervisorTests( + name: String, constructor: ( Boolean, - Option[Outcome[IO, Throwable, _] => Boolean]) => Resource[IO, Supervisor[IO]]) - : Fragments = { + Option[Outcome[IO, Throwable, _] => Boolean]) => Resource[IO, Supervisor[IO]]) = { - "start a fiber that completes successfully" in ticked { implicit ticker => + ticked(s"$name - start a fiber that completes successfully") { implicit ticker => val test = constructor(false, None).use { supervisor => supervisor.supervise(IO(1)).flatMap(_.join) } - test must completeAs(Outcome.succeeded[IO, Throwable, Int](IO.pure(1))) + assertCompleteAs(test, Outcome.succeeded[IO, Throwable, Int](IO.pure(1))) } - "start a fiber that raises an error" in ticked { implicit ticker => + ticked(s"$name - start a fiber that raises an error") { implicit ticker => val t = new Throwable("failed") val test = constructor(false, None).use { supervisor => supervisor.supervise(IO.raiseError[Unit](t)).flatMap(_.join) } - test must completeAs(Outcome.errored[IO, Throwable, Unit](t)) + assertCompleteAs(test, Outcome.errored[IO, Throwable, Unit](t)) } - "start a fiber that self-cancels" in ticked { implicit ticker => + ticked(s"$name - start a fiber that self-cancels") { implicit ticker => val test = constructor(false, None).use { supervisor => supervisor.supervise(IO.canceled).flatMap(_.join) } - test must completeAs(Outcome.canceled[IO, Throwable, Unit]) + assertCompleteAs(test, Outcome.canceled[IO, Throwable, Unit]) } - "cancel active fibers when supervisor exits" in ticked { implicit ticker => + ticked(s"$name - cancel active fibers when supervisor exits") { implicit ticker => val test = for { fiber <- constructor(false, None).use { supervisor => supervisor.supervise(IO.never[Unit]) @@ -74,36 +65,39 @@ class SupervisorSuite extends BaseSpec with DetectPlatform { outcome <- fiber.join } yield outcome - test must completeAs(Outcome.canceled[IO, Throwable, Unit]) + assertCompleteAs(test, Outcome.canceled[IO, Throwable, Unit]) } - "await active fibers when supervisor exits with await = true" in ticked { implicit ticker => - val test = constructor(true, None).use { supervisor => - supervisor.supervise(IO.never[Unit]).void - } + ticked(s"$name - await active fibers when supervisor exits with await = true") { + implicit ticker => + val test = constructor(true, None).use { supervisor => + supervisor.supervise(IO.never[Unit]).void + } - test must nonTerminate + assertNonTerminate(test) } - "await active fibers when supervisor with restarter exits with await = true" in ticked { + ticked( + s"$name - await active fibers when supervisor with restarter exits with await = true") { implicit ticker => val test = constructor(true, Some(_ => true)) use { supervisor => supervisor.supervise(IO.never[Unit]).void } - test must nonTerminate + assertNonTerminate(test) } - "await active fibers through a fiber when supervisor with restarter exits with await = true" in ticked { + ticked( + s"$name - await active fibers through a fiber when supervisor with restarter exits with await = true") { implicit ticker => val test = constructor(true, Some(_ => true)) use { supervisor => supervisor.supervise(IO.never[Unit]).void } - test.start.flatMap(_.join).void must nonTerminate + assertNonTerminate(test.start.flatMap(_.join).void) } - "stop restarting fibers when supervisor exits with await = true" in ticked { + ticked(s"$name - stop restarting fibers when supervisor exits with await = true") { implicit ticker => val test = for { counter <- IO.ref(0) @@ -127,20 +121,20 @@ class SupervisorSuite extends BaseSpec with DetectPlatform { _ <- done.get completed1 <- fiber.join.as(true).timeoutTo(200.millis, IO.pure(false)) - _ <- IO(completed1 must beFalse) + _ <- IO(assert(!completed1)) _ <- signal.release completed2 <- fiber.join.as(true).timeoutTo(200.millis, IO.pure(false)) - _ <- IO(completed2 must beTrue) + _ <- IO(assert(completed2)) count <- counter.get - _ <- IO(count mustEqual 3) + _ <- IO(assertEquals(count, 3)) } yield () - test must completeAs(()) + assertCompleteAs(test, ()) } - "cancel awaited fibers when exiting with error" in ticked { implicit ticker => + ticked(s"$name - cancel awaited fibers when exiting with error") { implicit ticker => case object TestException extends RuntimeException val test = IO.deferred[Unit] flatMap { latch => @@ -154,10 +148,10 @@ class SupervisorSuite extends BaseSpec with DetectPlatform { } } - test must failAs(TestException) + assertFailAs(test, TestException) } - "cancel awaited fibers when canceled" in ticked { implicit ticker => + ticked(s"$name - cancel awaited fibers when canceled") { implicit ticker => val test = IO.deferred[Unit] flatMap { latch => IO.deferred[Unit] flatMap { canceled => val supervision = constructor(true, None) use { supervisor => @@ -169,10 +163,10 @@ class SupervisorSuite extends BaseSpec with DetectPlatform { } } - test must selfCancel + assertSelfCancel(test) } - "check restart a fiber if it produces an error" in ticked { implicit ticker => + ticked(s"$name - check restart a fiber if it produces an error") { implicit ticker => case object TestException extends RuntimeException { override def printStackTrace(): Unit = () // this is an orphan error; we suppress the printing @@ -185,14 +179,14 @@ class SupervisorSuite extends BaseSpec with DetectPlatform { constructor(true, Some(_.fold(false, _ => true, _ => false))).use { supervisor => supervisor.supervise(action).flatMap(_.joinWithNever) - } <* counterR.get.flatMap(count => IO(count mustEqual 2)) + } <* counterR.get.flatMap(count => IO(assertEquals(count, 2))) } } - test must completeAs(42) + assertCompleteAs(test, 42) } - "check restart a fiber if it cancels" in ticked { implicit ticker => + ticked(s"$name - check restart a fiber if it cancels") { implicit ticker => val test = IO.ref(true) flatMap { raiseR => IO.ref(0) flatMap { counterR => val flipCancel = raiseR.set(false) >> IO.canceled.as(1) @@ -200,14 +194,14 @@ class SupervisorSuite extends BaseSpec with DetectPlatform { constructor(true, Some(_.fold(true, _ => false, _ => false))).use { supervisor => supervisor.supervise(action).flatMap(_.joinWithNever) - } <* counterR.get.flatMap(count => IO(count mustEqual 2)) + } <* counterR.get.flatMap(count => IO(assertEquals(count, 2))) } } - test must completeAs(42) + assertCompleteAs(test, 42) } - "cancel inner fiber and ignore restart if outer canceled" in real { + real(s"$name - cancel inner fiber and ignore restart if outer canceled") { val test = IO.deferred[Unit] flatMap { latch => constructor(true, Some(_.fold(true, _ => false, _ => false))).use { supervisor => supervisor.supervise(latch.complete(()) >> IO.canceled) >> latch.get >> IO.canceled @@ -215,10 +209,10 @@ class SupervisorSuite extends BaseSpec with DetectPlatform { } // if this doesn't work properly, the test will hang - test.start.flatMap(_.join).as(ok).timeoutTo(4.seconds, IO(false must beTrue)) + test.start.flatMap(_.join).timeoutTo(4.seconds, IO(sys.error("err"))) } - "cancel inner fiber and ignore restart if outer errored" in real { + real(s"$name - cancel inner fiber and ignore restart if outer errored") { case object TestException extends RuntimeException val test = IO.deferred[Unit] flatMap { latch => @@ -229,14 +223,14 @@ class SupervisorSuite extends BaseSpec with DetectPlatform { } // if this doesn't work properly, the test will hang - test.start.flatMap(_.join).as(ok).timeoutTo(4.seconds, IO(false must beTrue)) + test.start.flatMap(_.join).timeoutTo(4.seconds, IO(sys.error("err"))) } - "supervise / finalize race" in real { + real(s"$name - supervise / finalize race") { superviseFinalizeRace(constructor(false, None), IO.never[Unit]) } - "supervise / finalize race with checkRestart" in real { + real(s"$name - supervise / finalize race with checkRestart") { superviseFinalizeRace(constructor(false, Some(_ => true)), IO.canceled) } @@ -247,13 +241,13 @@ class SupervisorSuite extends BaseSpec with DetectPlatform { supervisor.supervise(IO.never[Unit]).replicateA(100).flatMap { fibers => val tryFork = supervisor.supervise(task).map(Some(_)).recover { case ex: IllegalStateException => - ex.getMessage mustEqual "supervisor already shutdown" + assertEquals(ex.getMessage, "supervisor already shutdown") None } IO.both(tryFork, close).flatMap { case (maybeFiber, _) => def joinAndCheck(fib: Fiber[IO, Throwable, Unit]) = - fib.join.flatMap { oc => IO(oc.isCanceled must beTrue) } + fib.join.flatMap { oc => IO(assert(oc.isCanceled)) } poll(fibers.traverse(joinAndCheck) *> { maybeFiber match { case None => @@ -267,32 +261,37 @@ class SupervisorSuite extends BaseSpec with DetectPlatform { } } } - tsk.parReplicateA_(if (isJVM) 700 else 1).as(ok) + tsk.parReplicateA_(if (isJVM) 700 else 1) } - "submit to closed supervisor" in real { + real(s"$name - submit to closed supervisor") { constructor(false, None).use(IO.pure(_)).flatMap { leaked => leaked.supervise(IO.unit).attempt.flatMap { r => - IO(r must beLeft(beAnInstanceOf[IllegalStateException])) + IO { + r match { + case Left(e) => assert(e.isInstanceOf[IllegalStateException]) + case Right(v) => fail(s"Expected Left, got $v") + } + } } } } - "restart / cancel race" in real { + real(s"$name - restart / cancel race") { val tsk = constructor(false, Some(_ => true)).use { supervisor => IO.ref(0).flatMap { counter => supervisor.supervise(counter.update(_ + 1) *> IO.canceled).flatMap { adaptedFiber => IO.sleep(100.millis) *> adaptedFiber.cancel *> adaptedFiber.join *> ( (counter.get, IO.sleep(100.millis) *> counter.get).flatMapN { case (v1, v2) => - IO(v1 mustEqual v2) + IO(assertEquals(v1, v2)) } ) } } } - tsk.parReplicateA_(if (isJVM) 1000 else 1).as(ok) + tsk.parReplicateA_(if (isJVM) 1000 else 1) } } } diff --git a/tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSuite.scala b/tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSuite.scala index 1c7b585242..225bb71865 100644 --- a/tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/SystemPropertiesSuite.scala @@ -17,23 +17,22 @@ package cats.effect package std -class SystemPropertiesSuite extends BaseSpec { +class SystemPropertiesSuite extends BaseSuite { - "SystemProperties" should { - "retrieve a property just set" in real { - Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => - SystemProperties[IO].set(key, "bar") *> - SystemProperties[IO].get(key).flatMap(x => IO(x mustEqual Some("bar"))) - } + real("retrieve a property just set") { + Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => + SystemProperties[IO].set(key, "bar") *> + SystemProperties[IO].get(key).flatMap(x => IO(assertEquals(x, Some("bar")))) } - "return none for a non-existent property" in real { - SystemProperties[IO].get("MADE_THIS_UP").flatMap(x => IO(x must beNone)) - } - "clear" in real { - Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => - SystemProperties[IO].set(key, "bar") *> SystemProperties[IO].clear(key) *> - SystemProperties[IO].get(key).flatMap(x => IO(x must beNone)) - } + } + real("return none for a non-existent property") { + SystemProperties[IO].get("MADE_THIS_UP").flatMap(x => IO(assertEquals(x, None))) + } + real("clear") { + Random.javaUtilConcurrentThreadLocalRandom[IO].nextString(12).flatMap { key => + SystemProperties[IO].set(key, "bar") *> SystemProperties[IO].clear(key) *> + SystemProperties[IO].get(key).flatMap(x => IO(assertEquals(x, None))) } } + } diff --git a/tests/shared/src/test/scala/cats/effect/std/UUIDGenSuite.scala b/tests/shared/src/test/scala/cats/effect/std/UUIDGenSuite.scala index 1d769e5f6a..f185ce0317 100644 --- a/tests/shared/src/test/scala/cats/effect/std/UUIDGenSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/UUIDGenSuite.scala @@ -17,19 +17,21 @@ package cats.effect package std -class UUIDGenSuite extends BaseSpec { +class UUIDGenSuite extends BaseSuite { - "UUIDGen" should { - "securely generate UUIDs" in real { - for { - left <- UUIDGen.randomUUID[IO] - right <- UUIDGen.randomUUID[IO] - } yield left != right - } - "use the correct variant and version" in real { - for { - uuid <- UUIDGen.randomUUID[IO] - } yield (uuid.variant should be_==(2)) and (uuid.version should be_==(4)) + real("securely generate UUIDs") { + for { + left <- UUIDGen.randomUUID[IO] + right <- UUIDGen.randomUUID[IO] + } yield left != right + } + + real("use the correct variant and version") { + for { + uuid <- UUIDGen.randomUUID[IO] + } yield { + assertEquals(uuid.variant, 2) + assertEquals(uuid.version, 4) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSuite.scala b/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSuite.scala index 425e3d7419..88bfcafb93 100644 --- a/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSuite.scala @@ -21,74 +21,68 @@ import cats.syntax.all._ import scala.concurrent.duration._ -class UnsafeBoundedSuite extends BaseSpec { +class UnsafeBoundedSuite extends BaseSuite { import Queue.UnsafeBounded override def executionTimeout = 30.seconds - "unsafe bounded queue" should { - "enqueue max items and dequeue in order" >> { - // NB: emperically, it seems this needs to be > availableProcessors() to be effective - val length = 1000 + // NB: emperically, it seems this needs to be > availableProcessors() to be effective + val length = 1000 - "sequential all" >> { - val q = new UnsafeBounded[Int](length) + test("sequential all") { + val q = new UnsafeBounded[Int](length) - 0.until(length).foreach(q.put(_)) - 0.until(length).map(_ => q.take()).toList mustEqual 0.until(length).toList - } + 0.until(length).foreach(q.put(_)) + assertEquals(0.until(length).map(_ => q.take()).toList, 0.until(length).toList) + } - "parallel put, parallel take" >> real { - val q = new UnsafeBounded[Int](length) + real("enqueue max items and dequeue in order - parallel put, parallel take") { + val q = new UnsafeBounded[Int](length) - val test = for { - _ <- 0.until(length).toList.parTraverse_(i => IO(q.put(i))) - results <- 0.until(length).toList.parTraverse(_ => IO(q.take())) - _ <- IO(results.toList must containTheSameElementsAs(0.until(length))) - } yield ok + val test = for { + _ <- 0.until(length).toList.parTraverse_(i => IO(q.put(i))) + results <- 0.until(length).toList.parTraverse(_ => IO(q.take())) + _ <- IO(assertEquals(results.sorted, 0.until(length).toList)) + } yield () - test.timeoutTo(16.seconds, IO(false must beTrue)) - } + test.timeoutTo(16.seconds, IO(assert(false))) + } - "parallel put and take" >> real { - val q = new UnsafeBounded[Int](length) + real("enqueue max items and dequeue in order - parallel put and take") { + val q = new UnsafeBounded[Int](length) - // retry forever until canceled - def retry[A](ioa: IO[A]): IO[A] = - ioa.handleErrorWith(_ => IO.cede *> retry(ioa)) + // retry forever until canceled + def retry[A](ioa: IO[A]): IO[A] = + ioa.handleErrorWith(_ => IO.cede *> retry(ioa)) - val puts = 1.to(length * 2).toList.parTraverse_(i => retry(IO(q.put(i)))) - val takes = 1.to(length * 2).toList.parTraverse(_ => retry(IO(q.take()))) + val puts = 1.to(length * 2).toList.parTraverse_(i => retry(IO(q.put(i)))) + val takes = 1.to(length * 2).toList.parTraverse(_ => retry(IO(q.take()))) - val test = for { - results <- puts &> takes - _ <- IO(q.size() mustEqual 0) - _ <- IO(results.toList must containTheSameElementsAs(1.to(length * 2))) - } yield ok + val test = for { + results <- puts &> takes + _ <- IO(assertEquals(q.size(), 0)) + _ <- IO(assertEquals(results.sorted, 1.to(length * 2).toList)) + } yield () - test.timeoutTo(30.seconds, IO(false must beTrue)) - } - } + test.timeoutTo(30.seconds, IO(assert(false))) + } - "produce failure when putting over bound" in { - val q = new UnsafeBounded[Unit](10) - 0.until(11).foreach(_ => q.put(())) must throwAn[Exception] - } + test("produce failure when putting over bound") { + val q = new UnsafeBounded[Unit](10) + intercept[Exception](0.until(11).foreach(_ => q.put(()))) + } - "produce failure when taking while empty" >> { - "without changes" >> { - val q = new UnsafeBounded[Unit](10) - q.take() must throwAn[Exception] - } + test("produce failure when taking while empty - without changes") { + val q = new UnsafeBounded[Unit](10) + intercept[Exception](q.take()) + } - "after put and take" >> { - val q = new UnsafeBounded[Unit](10) + test("produce failure when taking while empty - after put and take") { + val q = new UnsafeBounded[Unit](10) - 0.until(5).foreach(_ => q.put(())) - 0.until(5).foreach(_ => q.take()) + 0.until(5).foreach(_ => q.put(())) + 0.until(5).foreach(_ => q.take()) - q.take() must throwAn[Exception] - } - } + intercept[Exception](q.take()) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSuite.scala b/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSuite.scala index 755e97920d..386f88a84a 100644 --- a/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSuite.scala @@ -19,63 +19,60 @@ package std import cats.syntax.all._ -class UnsafeUnboundedSuite extends BaseSpec { +class UnsafeUnboundedSuite extends BaseSuite { - "unsafe unbounded queue" should { - val length = 1000 + val length = 1000 - "put and take in order" in { - val q = new UnsafeUnbounded[Int]() + test("put and take in order") { + val q = new UnsafeUnbounded[Int]() - 0.until(length).foreach(q.put(_)) - 0.until(length).map(_ => q.take()) mustEqual 0.until(length) - } + 0.until(length).foreach(q.put(_)) + assertEquals(0.until(length).map(_ => q.take()), 0.until(length)) + } - "produce an error when taking from empty" >> { - "always empty" >> { - new UnsafeUnbounded[Unit]().take() must throwAn[Exception] - } + test("produce an error when taking from empty - always empty") { + intercept[Exception](new UnsafeUnbounded[Unit]().take()) + } - "emptied" >> { - val q = new UnsafeUnbounded[Unit]() + test("produce an error when taking from empty - emptied") { + val q = new UnsafeUnbounded[Unit]() - q.put(()) - q.put(()) - q.take() - q.take() + q.put(()) + q.put(()) + q.take() + q.take() - q.take() must throwAn[Exception] - } - } + intercept[Exception](q.take()) + } - "put three times, clear one, then take" in { - val q = new UnsafeUnbounded[String]() + test("put three times, clear one, then take") { + val q = new UnsafeUnbounded[String]() - q.put("1") - val clear = q.put("2") - q.put("3") + q.put("1") + val clear = q.put("2") + q.put("3") - clear() + clear() - q.take() mustEqual "1" - q.take() mustEqual null - q.take() mustEqual "3" - } + assertEquals(q.take(), "1") + assertEquals(q.take(), null) + assertEquals(q.take(), "3") + } - "put and take in parallel" in real { - val q = new UnsafeUnbounded[Int]() + real("put and take in parallel") { + val q = new UnsafeUnbounded[Int]() - // retry forever until canceled - def retry[A](ioa: IO[A]): IO[A] = - ioa.handleErrorWith(_ => IO.cede *> retry(ioa)) + // retry forever until canceled + def retry[A](ioa: IO[A]): IO[A] = + ioa.handleErrorWith(_ => IO.cede *> retry(ioa)) - val puts = 1.to(length * 2).toList.parTraverse_(i => IO(q.put(i))) - val takes = 1.to(length * 2).toList.parTraverse(_ => retry(IO(q.take()))) + val puts = 1.to(length * 2).toList.parTraverse_(i => IO(q.put(i))) + val takes = 1.to(length * 2).toList.parTraverse(_ => retry(IO(q.take()))) - for { - results <- puts &> takes - _ <- IO(results.toList must containTheSameElementsAs(1.to(length * 2))) - } yield ok - } + for { + results <- puts &> takes + _ <- IO(assertEquals(results.sorted, 1.to(length * 2).toList)) + } yield () } + } diff --git a/tests/shared/src/test/scala/cats/effect/std/internal/BankersQueueSuite.scala b/tests/shared/src/test/scala/cats/effect/std/internal/BankersQueueSuite.scala index 679d07d8a1..e474df5291 100644 --- a/tests/shared/src/test/scala/cats/effect/std/internal/BankersQueueSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/internal/BankersQueueSuite.scala @@ -22,43 +22,47 @@ package cats.effect package std.internal -import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.{Arbitrary, Gen, Prop} import org.scalacheck.Arbitrary.arbitrary -import org.specs2.ScalaCheck -import org.specs2.mutable.Specification -class BankersQueueSuite extends Specification with ScalaCheck { +import munit.ScalaCheckSuite - "bankers queue" should { - /* - * frontLen <= rebalanceConstant * backLen + 1 - * backLen <= rebalanceConstant * = frontLen + 1 - */ - "maintain size invariants" in prop { (ops: List[Op[Int]]) => +class BankersQueueSuite extends ScalaCheckSuite { + + /* + * frontLen <= rebalanceConstant * backLen + 1 + * backLen <= rebalanceConstant * = frontLen + 1 + */ + property("maintain size invariants") { + Prop.forAll { (ops: List[Op[Int]]) => val queue = Op.fold(ops) - queue.frontLen must beLessThanOrEqualTo( - queue.backLen * BankersQueue.rebalanceConstant + 1) - queue.backLen must beLessThanOrEqualTo( - queue.frontLen * BankersQueue.rebalanceConstant + 1) + assert(queue.frontLen <= queue.backLen * BankersQueue.rebalanceConstant + 1) + assert(queue.backLen <= queue.frontLen * BankersQueue.rebalanceConstant + 1) } + } - "dequeue in order from front" in prop { (elems: List[Int]) => + property("dequeue in order from front") { + Prop.forAll { (elems: List[Int]) => val queue = buildQueue(elems) - toListFromFront(queue) must beEqualTo(elems) + assertEquals(toListFromFront(queue), elems) } + } - "dequeue in order from back" in prop { (elems: List[Int]) => + property("dequeue in order from back") { + Prop.forAll { (elems: List[Int]) => val queue = buildQueue(elems) - toListFromBack(queue) must beEqualTo(elems.reverse) + assertEquals(toListFromBack(queue), elems.reverse) } + } - "reverse" in prop { (elems: List[Int]) => + property("reverse") { + Prop.forAll { (elems: List[Int]) => val queue = buildQueue(elems) - toListFromFront(queue.reverse) must beEqualTo(elems.reverse) + assertEquals(toListFromFront(queue.reverse), elems.reverse) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/internal/BinomialHeapSuite.scala b/tests/shared/src/test/scala/cats/effect/std/internal/BinomialHeapSuite.scala index 5cf057e9d0..2d7f8fafe9 100644 --- a/tests/shared/src/test/scala/cats/effect/std/internal/BinomialHeapSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/internal/BinomialHeapSuite.scala @@ -23,54 +23,56 @@ package cats.effect.std.internal import cats.Order -import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.{Arbitrary, Gen, Prop} import org.scalacheck.Arbitrary.arbitrary -import org.specs2.ScalaCheck -import org.specs2.mutable.Specification -class BinomialHeapSuite extends Specification with ScalaCheck { +import munit.ScalaCheckSuite - implicit val orderForInt: Order[Int] = Order.fromLessThan((x, y) => x < y) +class BinomialHeapSuite extends ScalaCheckSuite { - "Binomial heap" should { + implicit val orderForInt: Order[Int] = Order.fromLessThan((x, y) => x < y) - "dequeue by priority" in prop { (elems: List[Int]) => + property("dequeue by priority") { + Prop.forAll { (elems: List[Int]) => val heap = buildHeap(elems) - toList(heap) must beEqualTo(elems.sorted) + assertEquals(toList(heap), elems.sorted) } + } - /** - * The root of a heap must be <= any of its children and all of its children must also be - * heaps - */ - "maintain the heap property" in prop { (ops: List[Op[Int]]) => + /** + * The root of a heap must be <= any of its children and all of its children must also be + * heaps + */ + property("maintain the heap property") { + Prop.forAll { (ops: List[Op[Int]]) => val heap = Op.toHeap(ops) - heap.trees.forall(validHeap(_)) must beTrue + assert(heap.trees.forall(validHeap(_))) } + } - /** - * The rank of the top-level trees should be strictly monotonically increasing, where the - * rank of a tree is defined as the height of the tree ie the number of nodes on the longest - * path from the root to a leaf. There is one binomial tree for each nonzero bit in the - * binary representation of the number of elements n - * - * The children of a top-level tree of rank i should be trees of monotonically decreasing - * rank i-1, i-2, ..., 1 - */ - "maintain correct subtree ranks" in prop { (ops: List[Op[Int]]) => + /** + * The rank of the top-level trees should be strictly monotonically increasing, where the rank + * of a tree is defined as the height of the tree ie the number of nodes on the longest path + * from the root to a leaf. There is one binomial tree for each nonzero bit in the binary + * representation of the number of elements n + * + * The children of a top-level tree of rank i should be trees of monotonically decreasing rank + * i-1, i-2, ..., 1 + */ + property("maintain correct subtree ranks") { + Prop.forAll { (ops: List[Op[Int]]) => val heap = Op.toHeap(ops) var currentRank = 0 heap.trees.forall { t => val r = rank(t) - r must beGreaterThan(currentRank) + assert(r > currentRank) currentRank = r checkRank(r, t) } } - } /** diff --git a/tests/shared/src/test/scala/cats/effect/testkit/TestControlSuite.scala b/tests/shared/src/test/scala/cats/effect/testkit/TestControlSuite.scala index fdfe0ee91b..6fecd4bb9a 100644 --- a/tests/shared/src/test/scala/cats/effect/testkit/TestControlSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/testkit/TestControlSuite.scala @@ -20,12 +20,10 @@ package testkit import cats.Id import cats.syntax.all._ -import org.specs2.matcher.Matcher - import scala.concurrent.CancellationException import scala.concurrent.duration._ -class TestControlSuite extends BaseSpec { +class TestControlSuite extends BaseSuite { val simple = IO.unit @@ -41,185 +39,190 @@ class TestControlSuite extends BaseSpec { val deadlock: IO[Unit] = IO.never - "execute" should { - "run a simple IO" in real { - TestControl.execute(simple) flatMap { control => - for { - r1 <- control.results - _ <- IO(r1 must beNone) + real("execute - run a simple IO") { + TestControl.execute(simple) flatMap { control => + for { + r1 <- control.results + _ <- IO(assertEquals(r1, None)) - _ <- control.tick + _ <- control.tick - r2 <- control.results - _ <- IO(r2 must beSome(beSucceeded(()))) - } yield ok - } + r2 <- control.results + _ <- IO(assertEquals(r2, Some(beSucceeded(())))) + } yield () } + } - "run a ceded IO in a single tick" in real { - TestControl.execute(simple) flatMap { control => - for { - r1 <- control.results - _ <- IO(r1 must beNone) + real("execute - run a ceded IO in a single tick") { + TestControl.execute(simple) flatMap { control => + for { + r1 <- control.results + _ <- IO(assertEquals(r1, None)) - _ <- control.tick + _ <- control.tick - r2 <- control.results - _ <- IO(r2 must beSome(beSucceeded(()))) - } yield ok - } + r2 <- control.results + _ <- IO(assertEquals(r2, Some(beSucceeded(())))) + } yield () } + } - "run an IO with long sleeps" in real { - TestControl.execute(longSleeps) flatMap { control => - for { - r1 <- control.results - _ <- IO(r1 must beNone) + real("execute - run an IO with long sleeps") { + TestControl.execute(longSleeps) flatMap { control => + for { + r1 <- control.results + _ <- IO(assertEquals(r1, None)) - _ <- control.tick - r2 <- control.results - _ <- IO(r2 must beNone) + _ <- control.tick + r2 <- control.results + _ <- IO(assertEquals(r2, None)) - int1 <- control.nextInterval - _ <- IO(int1 mustEqual 1.hour) + int1 <- control.nextInterval + _ <- IO(assertEquals(int1, 1.hour)) - _ <- control.advanceAndTick(1.hour) - r3 <- control.results - _ <- IO(r3 must beNone) + _ <- control.advanceAndTick(1.hour) + r3 <- control.results + _ <- IO(assertEquals(r3, None)) - int2 <- control.nextInterval - _ <- IO(int2 mustEqual 1.day) + int2 <- control.nextInterval + _ <- IO(assertEquals(int2, 1.day)) - _ <- control.advanceAndTick(1.day) + _ <- control.advanceAndTick(1.day) - r4 <- control.results - _ <- IO(r4 must beSome(beSucceeded((0.nanoseconds, 1.hour, 25.hours)))) - } yield ok - } + r4 <- control.results + _ <- IO(assertEquals(r4, Some(beSucceeded((0.nanoseconds, 1.hour, 25.hours))))) + } yield () } + } - "detect a deadlock" in real { - TestControl.execute(deadlock) flatMap { control => - for { - r1 <- control.results - _ <- IO(r1 must beNone) + real("execute - detect a deadlock") { + TestControl.execute(deadlock) flatMap { control => + for { + r1 <- control.results + _ <- IO(assertEquals(r1, None)) - _ <- control.tick - id <- control.isDeadlocked - _ <- IO(id must beTrue) + _ <- control.tick + id <- control.isDeadlocked + _ <- IO(assert(id)) - r2 <- control.results - _ <- IO(r2 must beNone) - } yield ok - } + r2 <- control.results + _ <- IO(assertEquals(r2, None)) + } yield () } + } - "only detect a deadlock when results are unavailable" in real { - TestControl.execute(IO.unit) flatMap { control => - for { - r1 <- control.results - _ <- IO(r1 must beNone) + real("execute - only detect a deadlock when results are unavailable") { + TestControl.execute(IO.unit) flatMap { control => + for { + r1 <- control.results + _ <- IO(assertEquals(r1, None)) - _ <- control.tick - id <- control.isDeadlocked - _ <- IO(id must beFalse) + _ <- control.tick + id <- control.isDeadlocked + _ <- IO(assert(!id)) - r2 <- control.results - _ <- IO(r2 must beSome) - } yield ok - } + r2 <- control.results + _ <- IO(assert(r2.isDefined)) + } yield () } + } - "produce Duration.Zero from nextInterval when no tasks" in real { - TestControl.execute(deadlock) flatMap { control => - for { - _ <- control.tick - i <- control.nextInterval - _ <- IO(i mustEqual Duration.Zero) - } yield ok - } + real("execute - produce Duration.Zero from nextInterval when no tasks") { + TestControl.execute(deadlock) flatMap { control => + for { + _ <- control.tick + i <- control.nextInterval + _ <- IO(assertEquals(i, Duration.Zero)) + } yield () } + } - "tickFor" >> { - "not advance beyond limit" in real { - TestControl.execute(IO.sleep(1.second).as(42)) flatMap { control => - for { - r1 <- control.results - _ <- IO(r1 must beNone) + real("execute - tickFor - not advance beyond limit") { + TestControl.execute(IO.sleep(1.second).as(42)) flatMap { control => + for { + r1 <- control.results + _ <- IO(assertEquals(r1, None)) - _ <- control.tickFor(500.millis) - r2 <- control.results - _ <- IO(r2 must beNone) + _ <- control.tickFor(500.millis) + r2 <- control.results + _ <- IO(assertEquals(r2, None)) - i1 <- control.nextInterval - _ <- IO(i1 mustEqual 500.millis) + i1 <- control.nextInterval + _ <- IO(assertEquals(i1, 500.millis)) - _ <- control.tickFor(250.millis) - r3 <- control.results - _ <- IO(r3 must beNone) + _ <- control.tickFor(250.millis) + r3 <- control.results + _ <- IO(assertEquals(r3, None)) - i2 <- control.nextInterval - _ <- IO(i2 mustEqual 250.millis) + i2 <- control.nextInterval + _ <- IO(assertEquals(i2, 250.millis)) - _ <- control.tickFor(250.millis) - r4 <- control.results - _ <- IO(r4 must beSome(beSucceeded(42))) - } yield ok - } - } + _ <- control.tickFor(250.millis) + r4 <- control.results + _ <- IO(assertEquals(r4, Some(beSucceeded(42)))) + } yield () + } + } - "advance incrementally in minimum steps" in real { - val step = IO.sleep(1.second) *> IO.realTime + real("execute - tickFor - advance incrementally in minimum steps") { + val step = IO.sleep(1.second) *> IO.realTime - TestControl.execute((step, step).tupled) flatMap { control => - for { - _ <- control.tickFor(1.second + 500.millis) - _ <- control.tickAll + TestControl.execute((step, step).tupled) flatMap { control => + for { + _ <- control.tickFor(1.second + 500.millis) + _ <- control.tickAll - r <- control.results - _ <- IO(r must beSome(beSucceeded((1.second, 2.seconds)))) - } yield ok - } - } + r <- control.results + _ <- IO(assertEquals(r, Some(beSucceeded((1.second, 2.seconds))))) + } yield () } } - "executeEmbed" should { - "run a simple IO" in real { - TestControl.executeEmbed(simple) flatMap { r => IO(r mustEqual (())) } - } + real("executeEmbed - run a simple IO") { + TestControl.executeEmbed(simple) flatMap { r => IO(assertEquals(r, ())) } + } - "run an IO with long sleeps" in real { - TestControl.executeEmbed(longSleeps) flatMap { r => - IO(r mustEqual ((0.nanoseconds, 1.hour, 25.hours))) - } + real("executeEmbed - run an IO with long sleeps") { + TestControl.executeEmbed(longSleeps) flatMap { r => + IO(assertEquals(r, (0.nanoseconds, 1.hour, 25.hours))) } + } - "detect a deadlock" in real { - TestControl.executeEmbed(deadlock).attempt flatMap { r => - IO { - r must beLike { case Left(_: TestControl.NonTerminationException) => ok } + real("executeEmbed - detect a deadlock") { + TestControl.executeEmbed(deadlock).attempt flatMap { r => + IO { + r match { + case Left(e) => assert(e.isInstanceOf[TestControl.NonTerminationException]) + case Right(v) => fail(s"Expected Left, got $v") } } } + } - "run an IO which produces an error" in real { - case object TestException extends RuntimeException + real("executeEmbed - run an IO which produces an error") { + case object TestException extends RuntimeException - TestControl.executeEmbed(IO.raiseError[Unit](TestException)).attempt flatMap { r => - IO(r must beLeft(TestException: Throwable)) + TestControl.executeEmbed(IO.raiseError[Unit](TestException)).attempt flatMap { r => + IO { + r match { + case Left(e) => assertEquals(e, TestException) + case Right(v) => fail(s"Expected Left, got $v") + } } } + } - "run an IO which self-cancels" in real { - TestControl.executeEmbed(IO.canceled).attempt flatMap { r => - IO { - r must beLike { case Left(_: CancellationException) => ok } + real("executeEmbed - run an IO which self-cancels") { + TestControl.executeEmbed(IO.canceled).attempt flatMap { r => + IO { + r match { + case Left(e) => assert(e.isInstanceOf[CancellationException]) + case Right(v) => fail(s"Expected Left, got $v") } } } } - private def beSucceeded[A](value: A): Matcher[Outcome[Id, Throwable, A]] = - (_: Outcome[Id, Throwable, A]) == Outcome.succeeded[Id, Throwable, A](value) + private def beSucceeded[A](value: A): Outcome[Id, Throwable, A] = + Outcome.succeeded[Id, Throwable, A](value) } diff --git a/tests/shared/src/test/scala/cats/effect/tracing/TraceSuite.scala b/tests/shared/src/test/scala/cats/effect/tracing/TraceSuite.scala index cc28587a55..0d832f819d 100644 --- a/tests/shared/src/test/scala/cats/effect/tracing/TraceSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/tracing/TraceSuite.scala @@ -16,35 +16,33 @@ package tracing // Get out of the CE package so our traces don't get filtered -import cats.effect.{BaseSpec, DetectPlatform, IO} +import cats.effect.{BaseSuite, DetectPlatform, IO} import cats.effect.testkit.TestInstances // Separate from TracingSpec so it can exist outside of cats.effect package -class TraceSuite extends BaseSpec with TestInstances with DetectPlatform { self => +class TraceSuite extends BaseSuite with TestInstances with DetectPlatform { self => - "IO" should { - if (!isJS || !isWSL) { - "have nice traces" in realWithRuntime { rt => - def loop(i: Int): IO[Int] = - IO.pure(i).flatMap { j => - if (j == 0) - IO.raiseError(new Exception) - else - loop(i - 1) - } - loop(100).attempt.map { - case Left(ex) => - ex.getStackTrace.count { e => - e.getClassName() == s"flatMap @ ${self.getClass().getName()}" && e - .getMethodName() - .startsWith("loop$") - } == rt.config.traceBufferSize - case _ => false + if (!isJS || !isWSL) { + realWithRuntime("have nice traces") { rt => + def loop(i: Int): IO[Int] = + IO.pure(i).flatMap { j => + if (j == 0) + IO.raiseError(new Exception) + else + loop(i - 1) } + loop(100).attempt.map { + case Left(ex) => + ex.getStackTrace.count { e => + e.getClassName() == s"flatMap @ ${self.getClass().getName()}" && e + .getMethodName() + .startsWith("loop$") + } == rt.config.traceBufferSize + case _ => false } - } else { - "have nice traces" in skipped("Scala.js exception unmangling is buggy on WSL") } + } else { +// "have nice traces" in skipped("Scala.js exception unmangling is buggy on WSL") } } diff --git a/tests/shared/src/test/scala/cats/effect/tracing/TracingSuite.scala b/tests/shared/src/test/scala/cats/effect/tracing/TracingSuite.scala index cf997256b0..7535dd9038 100644 --- a/tests/shared/src/test/scala/cats/effect/tracing/TracingSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/tracing/TracingSuite.scala @@ -16,50 +16,46 @@ package cats.effect.tracing -import cats.effect.{Async, BaseSpec, IO} +import cats.effect.{Async, BaseSuite, IO} import cats.effect.testkit.TestInstances -class TracingSuite extends BaseSpec with TestInstances { +class TracingSuite extends BaseSuite with TestInstances { - "IO.delay" should { - "generate identical traces" in { - val f = () => println("foo") - val a = IO(f()) - val b = IO(f()) - (a, b) match { - case (IO.Delay(_, eventA), IO.Delay(_, eventB)) => eventA eq eventB - case _ => false - } + test("IO.delay should generate identical traces") { + val f = () => println("foo") + val a = IO(f()) + val b = IO(f()) + (a, b) match { + case (IO.Delay(_, eventA), IO.Delay(_, eventB)) => assert(eventA eq eventB) + case _ => fail("expected IO.Delay") } + } - "generate unique traces" in { - val a = IO(println("foo")) - val b = IO(println("bar")) - (a, b) match { - case (IO.Delay(_, eventA), IO.Delay(_, eventB)) => eventA ne eventB - case _ => false - } + test("IO.delay should generate unique traces") { + val a = IO(println("foo")) + val b = IO(println("bar")) + (a, b) match { + case (IO.Delay(_, eventA), IO.Delay(_, eventB)) => assert(eventA ne eventB) + case _ => fail("expected IO.Delay") } } - "Async.delay" should { - "generate identical traces" in { - val f = () => println("foo") - val a = Async[IO].delay(f()) - val b = Async[IO].delay(f()) - (a, b) match { - case (IO.Delay(_, eventA), IO.Delay(_, eventB)) => eventA eq eventB - case _ => false - } + test("Async.delay should generate identical traces") { + val f = () => println("foo") + val a = Async[IO].delay(f()) + val b = Async[IO].delay(f()) + (a, b) match { + case (IO.Delay(_, eventA), IO.Delay(_, eventB)) => assert(eventA eq eventB) + case _ => fail("expected IO.Delay") } + } - "generate unique traces" in { - val a = Async[IO].delay(println("foo")) - val b = Async[IO].delay(println("bar")) - (a, b) match { - case (IO.Delay(_, eventA), IO.Delay(_, eventB)) => eventA ne eventB - case _ => false - } + test("Async.delay should generate unique traces") { + val a = Async[IO].delay(println("foo")) + val b = Async[IO].delay(println("bar")) + (a, b) match { + case (IO.Delay(_, eventA), IO.Delay(_, eventB)) => assert(eventA ne eventB) + case _ => fail("expected IO.Delay") } } diff --git a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSuite.scala b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSuite.scala index 804ee8addb..4229a31ba6 100644 --- a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSuite.scala @@ -17,17 +17,15 @@ package cats.effect package unsafe -class IORuntimeBuilderSuite extends BaseSpec with DetectPlatform { +import munit.TestOptions - "IORuntimeBuilder" should { - if (isNative) "configure the failure reporter" in pending - else - "configure the failure reporter" in { - var invoked = false - val rt = IORuntime.builder().setFailureReporter(_ => invoked = true).build() - rt.compute.reportFailure(new Exception) - invoked must beTrue - } +class IORuntimeBuilderSuite extends BaseSuite with DetectPlatform { + + test(("configure the failure reporter": TestOptions).ignoreNative) { + var invoked = false + val rt = IORuntime.builder().setFailureReporter(_ => invoked = true).build() + rt.compute.reportFailure(new Exception) + assert(invoked) } } diff --git a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSuite.scala b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSuite.scala index a66761150f..8dc15f1e98 100644 --- a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSuite.scala @@ -17,43 +17,34 @@ package cats.effect package unsafe -import scala.util.Try +class IORuntimeConfigSuite extends BaseSuite { -class IORuntimeConfigSuite extends BaseSpec { - - "IORuntimeConfig" should { - - "Reject invalid values of cancelation check- and auto yield threshold" in { - Try( - IORuntimeConfig( - cancelationCheckThreshold = -1, - autoYieldThreshold = -1)) must beFailedTry - Try( - IORuntimeConfig( - cancelationCheckThreshold = -1, - autoYieldThreshold = -2)) must beFailedTry - Try( - IORuntimeConfig(cancelationCheckThreshold = 0, autoYieldThreshold = 2)) must beFailedTry - Try( - IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 1)) must beFailedTry - Try( - IORuntimeConfig(cancelationCheckThreshold = 2, autoYieldThreshold = 3)) must beFailedTry - Try( - IORuntimeConfig(cancelationCheckThreshold = 4, autoYieldThreshold = 2)) must beFailedTry - // these are fine: - IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 2) - IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 3) - IORuntimeConfig(cancelationCheckThreshold = 2, autoYieldThreshold = 2) - IORuntimeConfig(cancelationCheckThreshold = 2, autoYieldThreshold = 4) - ok - } + test("Reject invalid values of cancelation check- and auto yield threshold") { + intercept[IllegalArgumentException]( + IORuntimeConfig(cancelationCheckThreshold = -1, autoYieldThreshold = -1)) + intercept[IllegalArgumentException]( + IORuntimeConfig(cancelationCheckThreshold = -1, autoYieldThreshold = -2)) + intercept[IllegalArgumentException]( + IORuntimeConfig(cancelationCheckThreshold = 0, autoYieldThreshold = 2)) + intercept[IllegalArgumentException]( + IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 1)) + intercept[IllegalArgumentException]( + IORuntimeConfig(cancelationCheckThreshold = 2, autoYieldThreshold = 3)) + intercept[IllegalArgumentException]( + IORuntimeConfig(cancelationCheckThreshold = 4, autoYieldThreshold = 2)) + // these are fine: + IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 2) + IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 3) + IORuntimeConfig(cancelationCheckThreshold = 2, autoYieldThreshold = 2) + IORuntimeConfig(cancelationCheckThreshold = 2, autoYieldThreshold = 4) + } - "Reject invalid values even in the copy method" in { - val cfg = IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 2) - Try(cfg.copy(cancelationCheckThreshold = 0)) must beFailedTry - Try(cfg.copy(cancelationCheckThreshold = -1)) must beFailedTry - Try(cfg.copy(autoYieldThreshold = 1)) must beFailedTry - Try(cfg.copy(cancelationCheckThreshold = 2, autoYieldThreshold = 3)) must beFailedTry - } + test("Reject invalid values even in the copy method") { + val cfg = IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 2) + intercept[IllegalArgumentException](cfg.copy(cancelationCheckThreshold = 0)) + intercept[IllegalArgumentException](cfg.copy(cancelationCheckThreshold = -1)) + intercept[IllegalArgumentException](cfg.copy(autoYieldThreshold = 1)) + intercept[IllegalArgumentException]( + cfg.copy(cancelationCheckThreshold = 2, autoYieldThreshold = 3)) } } diff --git a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSuite.scala b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSuite.scala index 8595cd879c..eb2906b194 100644 --- a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSuite.scala @@ -16,23 +16,20 @@ package cats.effect.unsafe -import cats.effect.BaseSpec +import cats.effect.BaseSuite -class IORuntimeSuite extends BaseSpec { +class IORuntimeSuite extends BaseSuite { - "IORuntimeSpec" should { - "cleanup allRuntimes collection on shutdown" in { - val (defaultScheduler, closeScheduler) = Scheduler.createDefaultScheduler() + test("cleanup allRuntimes collection on shutdown") { + val (defaultScheduler, closeScheduler) = Scheduler.createDefaultScheduler() - val runtime = IORuntime(null, null, defaultScheduler, closeScheduler, IORuntimeConfig()) + val runtime = IORuntime(null, null, defaultScheduler, closeScheduler, IORuntimeConfig()) - IORuntime.allRuntimes.unsafeHashtable().find(_ == runtime) must beEqualTo(Some(runtime)) + assertEquals(IORuntime.allRuntimes.unsafeHashtable().find(_ == runtime), Some(runtime)) - val _ = runtime.shutdown() - - IORuntime.allRuntimes.unsafeHashtable().find(_ == runtime) must beEqualTo(None) - } + val _ = runtime.shutdown() + assertEquals(IORuntime.allRuntimes.unsafeHashtable().find(_ == runtime), None) } } From 27a743e1c1cd541bb1ea31e6733d187f2f5e6903 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 27 Dec 2024 21:50:15 +0200 Subject: [PATCH 06/20] Fix executionTimeout --- tests/shared/src/test/scala/cats/effect/Runners.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/shared/src/test/scala/cats/effect/Runners.scala b/tests/shared/src/test/scala/cats/effect/Runners.scala index c6a315e8cf..2c94cd389d 100644 --- a/tests/shared/src/test/scala/cats/effect/Runners.scala +++ b/tests/shared/src/test/scala/cats/effect/Runners.scala @@ -34,6 +34,7 @@ trait Runners extends TestInstances with RunnersPlatform { self: FunSuite => def executionTimeout: FiniteDuration = 20.seconds + override def munitTimeout: Duration = executionTimeout def ticked(options: TestOptions)(body: Ticker => Any)(implicit loc: Location): Unit = test(options)(body(Ticker(TestContext()))) From 9f68a305607d2c56279367435f09d78a298629aa Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 30 Dec 2024 15:47:47 +0200 Subject: [PATCH 07/20] Address feedback --- build.sbt | 2 + .../test/scala/cats/effect/BaseSuite.scala | 10 ++++- .../src/test/scala/cats/effect/IOSuite.scala | 40 ++++++++++--------- .../scala/cats/effect/std/ConsoleSuite.scala | 16 +++++--- .../cats/effect/std/DispatcherSuite.scala | 10 ++--- 5 files changed, 47 insertions(+), 31 deletions(-) diff --git a/build.sbt b/build.sbt index 50c804bb87..82ca8522d5 100644 --- a/build.sbt +++ b/build.sbt @@ -300,6 +300,8 @@ ThisBuild / apiURL := Some(url("https://typelevel.org/cats-effect/api/3.x/")) ThisBuild / autoAPIMappings := true +ThisBuild / Test / testOptions += Tests.Argument("+l") + val CatsVersion = "2.11.0" val CatsMtlVersion = "1.3.1" val ScalaCheckVersion = "1.17.1" diff --git a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala index 2d65c8622f..53396d9887 100644 --- a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala @@ -18,4 +18,12 @@ package cats.effect import munit.FunSuite -trait BaseSuite extends FunSuite with Runners +trait BaseSuite extends FunSuite with Runners { + + override def munitValueTransforms: List[ValueTransform] = + super.munitValueTransforms ++ List( + new ValueTransform("IO", { case _: IO[_] => sys.error("Non-evaluated IO") }), + new ValueTransform("SyncIO", { case _: SyncIO[_] => sys.error("Non-evaluated SyncIO") }) + ) + +} diff --git a/tests/shared/src/test/scala/cats/effect/IOSuite.scala b/tests/shared/src/test/scala/cats/effect/IOSuite.scala index 557c411522..9046709396 100644 --- a/tests/shared/src/test/scala/cats/effect/IOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IOSuite.scala @@ -526,26 +526,28 @@ class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with I assertCompleteAs(fa, 44) } - // format: off - ticked("asyncCheckAttempt - produce a failure when the registration raises an error after result") { implicit ticker => - case object TestException extends RuntimeException + ticked( + "asyncCheckAttempt - produce a failure when the registration raises an error after result" + ) { implicit ticker => + case object TestException extends RuntimeException - assertFailAs(IO.asyncCheckAttempt[Int](_ => IO(Right(42)) - .flatMap(_ => IO.raiseError(TestException))) - .void, TestException) - } - // format: on + assertFailAs( + IO.asyncCheckAttempt[Int](_ => IO(Right(42)).flatMap(_ => IO.raiseError(TestException))) + .void, + TestException) + } - // format: off - ticked("asyncCheckAttempt - produce a failure when the registration raises an error after callback") { implicit ticker => - case object TestException extends RuntimeException + ticked( + "asyncCheckAttempt - produce a failure when the registration raises an error after callback" + ) { implicit ticker => + case object TestException extends RuntimeException - val fa = IO.asyncCheckAttempt[Int](cb => IO(cb(Right(42))) - .flatMap(_ => IO.raiseError(TestException))) - .void - assertFailAs(fa, TestException) - } - // format: on + val fa = IO + .asyncCheckAttempt[Int](cb => + IO(cb(Right(42))).flatMap(_ => IO.raiseError(TestException))) + .void + assertFailAs(fa, TestException) + } ticked("asyncCheckAttempt - ignore asyncCheckAttempt callback") { implicit ticker => case object TestException extends RuntimeException @@ -739,7 +741,7 @@ class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with I ticked("async - calling async callback with null during registration (ticked)") { implicit ticker => - IO.async[Int] { cb => IO(cb(null)).as(None) }.map(_ + 1).attempt.map { e => + val test = IO.async[Int] { cb => IO(cb(null)).as(None) }.map(_ + 1).attempt.map { e => assertCompleteAs( IO { e match { @@ -750,6 +752,8 @@ class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with I () ) } + + assertCompleteAs(test, ()) } ticked("async - calling async callback with null after registration (ticked)") { diff --git a/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala b/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala index 30c988d955..3034062561 100644 --- a/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala @@ -22,15 +22,19 @@ class ConsoleSuite extends BaseSuite { case class Foo(n: Int, b: Boolean) test("select default Show.fromToString (IO)") { - IO.print(Foo(1, true)) // compilation test - IO.println(Foo(1, true)) // compilation test + val _ = ( + IO.print(Foo(1, true)), // compilation test + IO.println(Foo(1, true)) // compilation test + ) } test("select default Show.fromToString (Console[IO])") { - Console[IO].print(Foo(1, true)) // compilation test - Console[IO].println(Foo(1, true)) // compilation test - Console[IO].error(Foo(1, true)) // compilation test - Console[IO].errorln(Foo(1, true)) // compilation test + val _ = ( + Console[IO].print(Foo(1, true)), // compilation test + Console[IO].println(Foo(1, true)), // compilation test + Console[IO].error(Foo(1, true)), // compilation test + Console[IO].errorln(Foo(1, true)) // compilation test + ) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala b/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala index aed00eb189..20ee65a7c6 100644 --- a/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala @@ -94,7 +94,6 @@ class DispatcherSuite extends BaseSuite with DetectPlatform { } } }.replicateA_(if (isJVM) 10000 else 1) - } } @@ -183,7 +182,7 @@ class DispatcherSuite extends BaseSuite with DetectPlatform { case (runner, release) => IO(runner.unsafeRunAndForget(release)) *> IO.sleep(100.millis) *> - IO(intercept[IllegalStateException](runner.unsafeRunAndForget(IO(ko)))) + IO(intercept[IllegalStateException](runner.unsafeRunAndForget(IO(fail("fail"))))) } assertCompleteAs(test.void, ()) @@ -428,7 +427,7 @@ class DispatcherSuite extends BaseSuite with DetectPlatform { real(s"$name - raise an error on leaked runner") { dispatcher.use(IO.pure(_)) flatMap { runner => IO { - intercept[IllegalStateException](runner.unsafeRunAndForget(IO(ko))) + intercept[IllegalStateException](runner.unsafeRunAndForget(IO(fail("fail")))) } } } @@ -451,7 +450,7 @@ class DispatcherSuite extends BaseSuite with DetectPlatform { test .use(t => - IO.fromFutureCancelable(IO((t.future, IO.unit))).timeoutTo(1.second, IO.pure(false))) + IO.fromFutureCancelable(IO((t.future, IO.unit))).timeoutTo(3.second, IO.pure(false))) .flatMap(t => IO(assertEquals(t, true))) } @@ -514,7 +513,7 @@ class DispatcherSuite extends BaseSuite with DetectPlatform { .guarantee(latch2.complete(()).void) _ <- release &> challenge - } yield ko + } yield fail("fail") } } @@ -671,5 +670,4 @@ class DispatcherSuite extends BaseSuite with DetectPlatform { } } - private def ko(implicit loc: munit.Location) = fail("fail") } From ef5d6e4d796d3a9cacdfe993b35d63227aef2f44 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 30 Dec 2024 16:02:38 +0200 Subject: [PATCH 08/20] Simplify tests naming --- ioapp-tests/src/test/scala/IOAppSpec.scala | 50 +++++++++++----------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/ioapp-tests/src/test/scala/IOAppSpec.scala b/ioapp-tests/src/test/scala/IOAppSpec.scala index 0ab5cec144..34d428a4a9 100644 --- a/ioapp-tests/src/test/scala/IOAppSpec.scala +++ b/ioapp-tests/src/test/scala/IOAppSpec.scala @@ -149,13 +149,13 @@ class IOAppSpec extends FunSuite { if (!isWindows) { // these tests have all been emperically flaky on Windows CI builds, so they're disabled - test(s"IOApp (${platform.id}) - evaluate and print hello world") { + test("evaluate and print hello world") { val h = platform("HelloWorld", Nil) assertEquals(h.awaitStatus(), 0) assertEquals(h.stdout(), s"Hello, World!${System.lineSeparator()}") } - test(s"IOApp (${platform.id}) - pass all arguments to child") { + test("pass all arguments to child") { val expected = List("the", "quick", "brown", "fox jumped", "over") val h = platform("Arguments", expected) assertEquals(h.awaitStatus(), 0) @@ -165,59 +165,59 @@ class IOAppSpec extends FunSuite { ) } - test(s"IOApp (${platform.id}) - exit on non-fatal error") { + test("exit on non-fatal error") { val h = platform("NonFatalError", List.empty) assertEquals(h.awaitStatus(), 1) assert(h.stderr().contains("Boom!")) } - test(s"IOApp (${platform.id}) - exit with leaked fibers") { + test("exit with leaked fibers") { val h = platform("LeakedFiber", List.empty) assertEquals(h.awaitStatus(), 0) } - test(s"IOApp (${platform.id}) - exit on fatal error") { + test("exit on fatal error") { val h = platform("FatalError", List.empty) assertEquals(h.awaitStatus(), 1) assert(h.stderr().contains("Boom!")) assert(!h.stdout().contains("sadness")) } - test(s"IOApp (${platform.id}) - exit on fatal error with other unsafe runs") { + test("exit on fatal error with other unsafe runs") { val h = platform("FatalErrorUnsafeRun", List.empty) assertEquals(h.awaitStatus(), 1) assert(h.stderr().contains("Boom!")) } - test(s"IOApp (${platform.id}) - exit on raising a fatal error with attempt") { + test("exit on raising a fatal error with attempt") { val h = platform("RaiseFatalErrorAttempt", List.empty) assertEquals(h.awaitStatus(), 1) assert(h.stderr().contains("Boom!")) assert(!h.stdout().contains("sadness")) } - test(s"IOApp (${platform.id}) - exit on raising a fatal error with handleError") { + test("exit on raising a fatal error with handleError") { val h = platform("RaiseFatalErrorHandle", List.empty) assertEquals(h.awaitStatus(), 1) assert(h.stderr().contains("Boom!")) assert(!h.stdout().contains("sadness")) } - test(s"IOApp (${platform.id}) - exit on raising a fatal error inside a map") { + test("exit on raising a fatal error inside a map") { val h = platform("RaiseFatalErrorMap", List.empty) assertEquals(h.awaitStatus(), 1) assert(h.stderr().contains("Boom!")) assert(!h.stdout().contains("sadness")) } - test(s"IOApp (${platform.id}) - exit on raising a fatal error inside a flatMap") { + test("exit on raising a fatal error inside a flatMap") { val h = platform("RaiseFatalErrorFlatMap", List.empty) assertEquals(h.awaitStatus(), 1) assert(h.stderr().contains("Boom!")) assert(!h.stdout().contains("sadness")) } - test(s"IOApp (${platform.id}) - warn on global runtime collision") { + test("warn on global runtime collision") { val h = platform("GlobalRacingInit", List.empty) assertEquals(h.awaitStatus(), 0) assert( @@ -227,7 +227,7 @@ class IOAppSpec extends FunSuite { assert(!h.stderr().contains("boom")) } - test(s"IOApp (${platform.id}) - reset global runtime on shutdown") { + test("reset global runtime on shutdown") { val h = platform("GlobalShutdown", List.empty) assertEquals(h.awaitStatus(), 0) assert( @@ -238,7 +238,7 @@ class IOAppSpec extends FunSuite { } // TODO reenable this test (#3919) - test(s"IOApp (${platform.id}) - warn on cpu starvation".ignore) { + test("warn on cpu starvation".ignore) { val h = platform("CpuStarvation", List.empty) h.awaitStatus() val err = h.stderr() @@ -253,13 +253,13 @@ class IOAppSpec extends FunSuite { )) } - test(s"IOApp (${platform.id}) - custom runtime installed as global") { + test("custom runtime installed as global") { val h = platform("CustomRuntime", List.empty) assertEquals(h.awaitStatus(), 0) } if (platform != Native) { - test(s"IOApp (${platform.id}) - abort awaiting shutdown hooks") { + test("abort awaiting shutdown hooks") { val h = platform("ShutdownHookImmediateTimeout", List.empty) assertEquals(h.awaitStatus(), 0) } @@ -271,7 +271,7 @@ class IOAppSpec extends FunSuite { // The jvm cannot gracefully terminate processes on Windows, so this // test cannot be carried out properly. Same for testing IOApp in sbt. - test(s"IOApp (${platform.id}) - run finalizers on TERM") { + test("run finalizers on TERM") { import _root_.java.io.{BufferedReader, FileReader} // we have to resort to this convoluted approach because Process#destroy kills listeners before killing the process @@ -308,14 +308,14 @@ class IOAppSpec extends FunSuite { } } else () - test(s"IOApp (${platform.id}) - exit on fatal error without IOApp") { + test("exit on fatal error without IOApp") { val h = platform("FatalErrorRaw", List.empty) h.awaitStatus() assert(!h.stdout().contains("sadness")) assert(!h.stderr().contains("Promise already completed")) } - test(s"IOApp (${platform.id}) - exit on canceled") { + test("exit on canceled") { val h = platform("Canceled", List.empty) assertEquals(h.awaitStatus(), 1) } @@ -323,7 +323,7 @@ class IOAppSpec extends FunSuite { if (!isJava8 && !isWindows && platform != Native) { // JDK 8 does not have free signals for live fiber snapshots // cannot observe signals sent to process termination on Windows - test(s"IOApp (${platform.id}) - live fiber snapshot") { + test("live fiber snapshot") { val h = platform("LiveFiberSnapshot", List.empty) // wait for the application to fully start before trying to send the signal @@ -342,23 +342,23 @@ class IOAppSpec extends FunSuite { } if (platform == JVM) { - test(s"IOApp (${platform.id}) - shutdown on worker thread interruption") { + test("shutdown on worker thread interruption") { val h = platform("WorkerThreadInterrupt", List.empty) assertEquals(h.awaitStatus(), 1) assert(h.stderr().contains("java.lang.InterruptedException")) } - test(s"IOApp (${platform.id}) - support main thread evaluation") { + test("support main thread evaluation") { val h = platform("EvalOnMainThread", List.empty) assertEquals(h.awaitStatus(), 0) } - test(s"IOApp (${platform.id}) - use configurable reportFailure for MainThread") { + test("use configurable reportFailure for MainThread") { val h = platform("MainThreadReportFailure", List.empty) assertEquals(h.awaitStatus(), 0) } - test(s"IOApp (${platform.id}) - warn on blocked threads") { + test("warn on blocked threads") { val h = platform("BlockedThreads", List.empty) h.awaitStatus() val err = h.stderr() @@ -367,7 +367,7 @@ class IOAppSpec extends FunSuite { "[WARNING] A Cats Effect worker thread was detected to be in a blocked state")) } - test(s"IOApp (${platform.id}) - shut down WSTP on fatal error without IOApp") { + test("shut down WSTP on fatal error without IOApp") { val h = platform("FatalErrorShutsDownRt", List.empty) h.awaitStatus() assert(!h.stdout().contains("sadness")) @@ -376,7 +376,7 @@ class IOAppSpec extends FunSuite { } if (platform == Node) { - test(s"IOApp (${platform.id}) - gracefully ignore undefined process.exit") { + test("gracefully ignore undefined process.exit") { val h = platform("UndefinedProcessExit", List.empty) assertEquals(h.awaitStatus(), 0) } From 6dcf4f82f239d613d4af1cb409ec649bc57c7747 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 30 Dec 2024 16:23:29 +0200 Subject: [PATCH 09/20] Print fiber info on errors --- .../test/scala/cats/effect/unsafe/FiberMonitorSuite.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala index 6c2df80c8a..01bdc9e049 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala @@ -38,14 +38,16 @@ class FiberMonitorSuite extends BaseSuite with TestInstances { _ <- IO(assertEquals(snapshot.size, 2)) // root and awaiting fibers fiberSnapshot <- IO(snapshot.filter(_.contains(fiberId))) _ <- IO(assertEquals(fiberSnapshot.size, 1)) // only awaiting fiber - _ <- IO(assert(fiberSnapshot.exists(_.matches(waitingPattern)))) + _ <- IO( + assert(fiberSnapshot.exists(_.matches(waitingPattern)), fiberSnapshot.mkString("\n")) + ) _ <- cdl.release // allow further execution outcome <- fiber.join _ <- IO.sleep(100.millis) _ <- IO(assertEquals(outcome, Outcome.succeeded[IO, Throwable, Unit](IO.unit))) - _ <- IO(assert(fiber.toString.matches(completedPattern))) + _ <- IO(assert(fiber.toString.matches(completedPattern), fiber.toString)) _ <- IO(assertEquals(makeSnapshot(runtime).size, 1)) // only root fiber } yield () } From 8e91848b7ab3169b3a2c03e5394617fd2a015ef7 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Mon, 30 Dec 2024 17:14:17 +0200 Subject: [PATCH 10/20] FiberMonitorSuite: use System.lineSeparator --- .../src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala index 01bdc9e049..500115bc31 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/FiberMonitorSuite.scala @@ -25,7 +25,8 @@ import scala.concurrent.duration._ class FiberMonitorSuite extends BaseSuite with TestInstances { realWithRuntime("show only active fibers in a live snapshot") { (runtime: IORuntime) => - val waitingPattern = raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ WAITING((.|\n)*)" + val newline = System.lineSeparator() + val waitingPattern = raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ WAITING((.|$newline)*)" val completedPattern = raw"cats.effect.IOFiber@[0-9a-f][0-9a-f]+ COMPLETED" for { From a594c5a4034a8440243af0d022685a1e17c8b1bc Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 2 Jan 2025 11:47:18 +0200 Subject: [PATCH 11/20] Deprecate `test`, add `testUnit` --- .../cats/effect/SyncIOPlatformSuite.scala | 2 +- .../effect/unsafe/JSArrayQueueSuite.scala | 4 +- .../cats/effect/SyncIOPlatformSuite.scala | 2 +- .../scala/cats/effect/IOPlatformSuite.scala | 10 +- .../scala/cats/effect/SelectorSuite.scala | 2 +- .../cats/effect/unsafe/SleepersSuite.scala | 6 +- .../cats/effect/unsafe/TimerHeapSuite.scala | 6 +- .../cats/effect/IOImplicitSuite.scala | 8 +- .../not/cats/effect/IOParImplicitSuite.scala | 8 +- .../test/scala/cats/effect/BaseSuite.scala | 18 +- .../cats/effect/CallbackStackSuite.scala | 2 +- .../scala/cats/effect/ExitCodeSuite.scala | 2 +- .../src/test/scala/cats/effect/IOSuite.scala | 16 +- .../scala/cats/effect/ResourceSuite.scala | 768 +++++++++--------- .../test/scala/cats/effect/SyncIOSuite.scala | 58 +- .../test/scala/cats/effect/ThunkSuite.scala | 4 +- .../kernel/DerivationRefinementSuite.scala | 18 +- .../scala/cats/effect/std/ConsoleSuite.scala | 4 +- .../cats/effect/std/UnsafeBoundedSuite.scala | 14 +- .../effect/std/UnsafeUnboundedSuite.scala | 12 +- .../cats/effect/tracing/TracingSuite.scala | 8 +- .../effect/unsafe/IORuntimeBuilderSuite.scala | 2 +- .../effect/unsafe/IORuntimeConfigSuite.scala | 6 +- .../cats/effect/unsafe/IORuntimeSuite.scala | 2 +- 24 files changed, 490 insertions(+), 492 deletions(-) diff --git a/tests/js/src/test/scala/cats/effect/SyncIOPlatformSuite.scala b/tests/js/src/test/scala/cats/effect/SyncIOPlatformSuite.scala index 1b80c7f7bf..747f9d7ae1 100644 --- a/tests/js/src/test/scala/cats/effect/SyncIOPlatformSuite.scala +++ b/tests/js/src/test/scala/cats/effect/SyncIOPlatformSuite.scala @@ -19,7 +19,7 @@ package cats.effect trait SyncIOPlatformSuite { self: BaseSuite => def platformTests() = { - test("realTimeDate should return an Instant constructed from realTime") { + testUnit("realTimeDate should return an Instant constructed from realTime") { // Unfortunately since SyncIO doesn't rely on a controllable // time source, this is the best I can do val op = for { diff --git a/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala b/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala index fac9d79f0c..8b7ac91f00 100644 --- a/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala +++ b/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala @@ -25,7 +25,7 @@ import munit.ScalaCheckSuite class JSArrayQueueSuite extends BaseSuite with ScalaCheckSuite { - test("be fifo") { + property("be fifo") { forAll { (stuff: List[Option[Int]]) => val queue = new JSArrayQueue[Int] val taken = new ListBuffer[Int] @@ -42,7 +42,7 @@ class JSArrayQueueSuite extends BaseSuite with ScalaCheckSuite { } } - test("iterate over contents in foreach") { + property("iterate over contents in foreach") { forAll { (stuff: List[Option[Int]]) => val queue = new JSArrayQueue[Int] val shadow = new Queue[Int] diff --git a/tests/jvm-native/src/test/scala/cats/effect/SyncIOPlatformSuite.scala b/tests/jvm-native/src/test/scala/cats/effect/SyncIOPlatformSuite.scala index 2bfeb07911..087ee09047 100644 --- a/tests/jvm-native/src/test/scala/cats/effect/SyncIOPlatformSuite.scala +++ b/tests/jvm-native/src/test/scala/cats/effect/SyncIOPlatformSuite.scala @@ -19,7 +19,7 @@ package cats.effect trait SyncIOPlatformSuite { self: BaseSuite => def platformTests() = { - test("realTimeInstant should return an Instant constructed from realTime") { + testUnit("realTimeInstant should return an Instant constructed from realTime") { // Unfortunately since SyncIO doesn't use on a controllable // clock source, so a diff best we can do val op = for { diff --git a/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala b/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala index 76d2152a0a..132488abb0 100644 --- a/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala @@ -333,7 +333,7 @@ trait IOPlatformSuite extends DetectPlatform { } } - test("safely detect hard-blocked threads even while blockers are being created") { + testUnit("safely detect hard-blocked threads even while blockers are being created") { val (compute, _, shutdown) = IORuntime.createWorkStealingComputeThreadPool(blockedThreadDetectionEnabled = true) @@ -353,7 +353,7 @@ trait IOPlatformSuite extends DetectPlatform { } // this test ensures that the parkUntilNextSleeper bit works - test("run a timer when parking thread") { + testUnit("run a timer when parking thread") { val (pool, _, shutdown) = IORuntime.createWorkStealingComputeThreadPool(threads = 1) implicit val runtime: IORuntime = IORuntime.builder().setCompute(pool, shutdown).build() @@ -368,7 +368,7 @@ trait IOPlatformSuite extends DetectPlatform { } // this test ensures that we always see the timer, even when it fires just as we're about to park - test("run a timer when detecting just prior to park") { + testUnit("run a timer when detecting just prior to park") { val (pool, _, shutdown) = IORuntime.createWorkStealingComputeThreadPool(threads = 1) implicit val runtime: IORuntime = IORuntime.builder().setCompute(pool, shutdown).build() @@ -439,7 +439,7 @@ trait IOPlatformSuite extends DetectPlatform { } - test("not lose cedeing threads from the bypass when blocker transitioning") { + testUnit("not lose cedeing threads from the bypass when blocker transitioning") { // writing this test in terms of IO seems to not reproduce the issue 0.until(5) foreach { _ => val wstp = new WorkStealingThreadPool[AnyRef]( @@ -485,7 +485,7 @@ trait IOPlatformSuite extends DetectPlatform { () } - test("wake parked thread for polled events") { + testUnit("wake parked thread for polled events") { trait DummyPoller { def poll: IO[Unit] diff --git a/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala b/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala index 7a7ec3d379..20a225b903 100644 --- a/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala @@ -91,7 +91,7 @@ class SelectorSuite extends BaseSuite { } } - test("handles concurrent close") { + testUnit("handles concurrent close") { val (pool, poller, shutdown) = IORuntime.createWorkStealingComputeThreadPool(threads = 1) implicit val runtime: IORuntime = IORuntime.builder().setCompute(pool, shutdown).addPoller(poller, () => ()).build() diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSuite.scala index cfbbc1bca0..9e3c7e930a 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/SleepersSuite.scala @@ -23,7 +23,7 @@ import scala.concurrent.duration._ class SleepersSuite extends BaseSuite { - test("have a trigger time in the future") { + testUnit("have a trigger time in the future") { val sleepers = new TimerHeap val now = 100.millis.toNanos val delay = 500.millis.toNanos @@ -61,7 +61,7 @@ class SleepersSuite extends BaseSuite { new Function1[Right[Nothing, Unit], Unit] { def apply(x: Right[Nothing, Unit]) = () } } - test("be ordered according to the trigger time") { + testUnit("be ordered according to the trigger time") { val sleepers = new TimerHeap val now1 = 100.millis.toNanos @@ -94,7 +94,7 @@ class SleepersSuite extends BaseSuite { assertEquals(ordering, expectedOrdering) } - test("be ordered correctly even if Long overflows") { + testUnit("be ordered correctly even if Long overflows") { val sleepers = new TimerHeap val now1 = Long.MaxValue - 20L diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSuite.scala index cd0645f566..dd9bbf3f8b 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/TimerHeapSuite.scala @@ -34,7 +34,7 @@ class TimerHeapSuite extends BaseSuite { private val cb4 = newCb() private val cb5 = newCb() - test("correctly insert / pollFirstIfTriggered") { + testUnit("correctly insert / pollFirstIfTriggered") { val m = new TimerHeap val out = new Array[Right[Nothing, Unit] => Unit](1) assert(m.pollFirstIfTriggered(Long.MinValue) eq null) @@ -68,7 +68,7 @@ class TimerHeapSuite extends BaseSuite { assert(m.pollFirstIfTriggered(Long.MaxValue) eq null) } - test("correctly insert / remove (cancel)") { + testUnit("correctly insert / remove (cancel)") { val m = new TimerHeap val out = new Array[Right[Nothing, Unit] => Unit](1) val r0 = m.insert(0L, 1L, cb0, out) @@ -114,7 +114,7 @@ class TimerHeapSuite extends BaseSuite { assert(m.pollFirstIfTriggered(Long.MaxValue) eq null) } - test("behave correctly when nanoTime wraps around") { + testUnit("behave correctly when nanoTime wraps around") { val m = new TimerHeap val startFrom = Long.MaxValue - 100L var nanoTime = startFrom diff --git a/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSuite.scala b/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSuite.scala index 40b8004e47..9cf0b2e3cd 100644 --- a/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSuite.scala +++ b/tests/shared/src/test/scala-2.13+/cats/effect/IOImplicitSuite.scala @@ -18,17 +18,17 @@ package cats.effect class IOImplicitSuite extends BaseSuite { - test("Can resolve IO sequence ops without import of cats.syntax.all") { // compilation test + testUnit("Can resolve IO sequence ops without import of cats.syntax.all") { // compilation test for { _ <- List(IO(1)).sequence_ _ <- Option(IO(1)).sequence _ <- Option(IO(1)).sequence_ _ <- List(IO(List(1))).flatSequence } yield () - true + () } - test("Can resolve IO.Par ops without import of cats.syntax.all") { // compilation test + testUnit("Can resolve IO.Par ops without import of cats.syntax.all") { // compilation test for { _ <- Option(IO(1)).parSequence _ <- Option(IO(1)).parSequence_ @@ -48,6 +48,6 @@ class IOImplicitSuite extends BaseSuite { _ <- (IO(1), IO(2), IO(3)).parTupled _ <- (IO(1), IO(2), IO(3)).parFlatMapN { case (x, y, z) => IO.pure(x + y + z) } } yield () - true + () } } diff --git a/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSuite.scala b/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSuite.scala index 92de0eb7a9..ce9125909b 100644 --- a/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSuite.scala +++ b/tests/shared/src/test/scala-2.13+/not/cats/effect/IOParImplicitSuite.scala @@ -22,15 +22,15 @@ import cats.syntax.all._ class IOParImplicitSuite extends BaseSuite { - test("Can resolve CommutativeApplicative instance") { + testUnit("Can resolve CommutativeApplicative instance") { List(1, 2, 3).parUnorderedTraverse(_ => IO.unit) // compilation test - true + () } - test("Can resolve IO.Par instances") { // compilation test + testUnit("Can resolve IO.Par instances") { // compilation test Align[IO.Par] CommutativeApplicative[IO.Par] - true + () } } diff --git a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala index 53396d9887..e2708b3333 100644 --- a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala @@ -16,10 +16,26 @@ package cats.effect -import munit.FunSuite +import munit.{FunSuite, Location, TestOptions} trait BaseSuite extends FunSuite with Runners { + @deprecated("Please use a type safe alternative, such as 'real' or 'ticked'", "3.6.0") + override def test(name: String)(body: => Any)(implicit loc: Location): Unit = + super.test(name)(body) + + @deprecated("Please use a type safe alternative, such as 'real' or 'ticked'", "3.6.0") + override def test(options: TestOptions)(body: => Any)(implicit loc: Location): Unit = + super.test(options)(body) + + @annotation.nowarn("cat=deprecation") + def testUnit(name: String)(body: => Unit)(implicit loc: Location): Unit = + test(name)(body) + + @annotation.nowarn("cat=deprecation") + def testUnit(options: TestOptions)(body: => Unit)(implicit loc: Location): Unit = + test(options)(body) + override def munitValueTransforms: List[ValueTransform] = super.munitValueTransforms ++ List( new ValueTransform("IO", { case _: IO[_] => sys.error("Non-evaluated IO") }), diff --git a/tests/shared/src/test/scala/cats/effect/CallbackStackSuite.scala b/tests/shared/src/test/scala/cats/effect/CallbackStackSuite.scala index 44d1c3e253..05a8cad29e 100644 --- a/tests/shared/src/test/scala/cats/effect/CallbackStackSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/CallbackStackSuite.scala @@ -18,7 +18,7 @@ package cats.effect class CallbackStackSuite extends BaseSuite with DetectPlatform { - test("correctly report the number removed") { + testUnit("correctly report the number removed") { val stack = CallbackStack.of[Unit](null) val handle = stack.push(_ => ()) stack.push(_ => ()) diff --git a/tests/shared/src/test/scala/cats/effect/ExitCodeSuite.scala b/tests/shared/src/test/scala/cats/effect/ExitCodeSuite.scala index d19bd03205..0a068f6fb1 100644 --- a/tests/shared/src/test/scala/cats/effect/ExitCodeSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/ExitCodeSuite.scala @@ -18,7 +18,7 @@ package cats.effect class ExitCodeSuite extends BaseSuite { - test("ExitCode.unapply is exhaustive") { + testUnit("ExitCode.unapply is exhaustive") { ExitCode(0) match { // if not, should be a fatal warning in CI case ExitCode(_) => assert(true) } diff --git a/tests/shared/src/test/scala/cats/effect/IOSuite.scala b/tests/shared/src/test/scala/cats/effect/IOSuite.scala index 9046709396..07dd1c5fd6 100644 --- a/tests/shared/src/test/scala/cats/effect/IOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IOSuite.scala @@ -2006,7 +2006,7 @@ class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with I IO(cancel.run()) *> IO(cancel.run())) } - test("syncStep - run sync IO to completion") { + testUnit("syncStep - run sync IO to completion") { var bool = false val zero = 0 @@ -2036,7 +2036,7 @@ class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with I assertEquals(bool, true) } - test("syncStep - fail synchronously with a throwable") { + testUnit("syncStep - fail synchronously with a throwable") { case object TestException extends RuntimeException val io = IO.raiseError[Unit](TestException) @@ -2093,7 +2093,7 @@ class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with I ) } - test("syncStep - evaluate up to limit and no further") { + testUnit("syncStep - evaluate up to limit and no further") { var first = false var second = false @@ -2158,23 +2158,23 @@ class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with I assertEquals(i, 1) } - test("syncStep - handle uncancelable") { + testUnit("syncStep - handle uncancelable") { val sio = IO.unit.uncancelable.syncStep(Int.MaxValue) assertCompleteAsSync(sio.map(_.bimap(_ => (), _ => ())), Right(())) } - test("syncStep - handle onCancel") { + testUnit("syncStep - handle onCancel") { val sio = IO.unit.onCancel(IO.unit).syncStep(Int.MaxValue) assertCompleteAsSync(sio.map(_.bimap(_ => (), _ => ())), Right(())) } - test("syncStep - synchronously allocate a vanilla resource") { + testUnit("syncStep - synchronously allocate a vanilla resource") { val sio = Resource.make(IO.unit)(_ => IO.unit).allocated.map(_._1).syncStep(Int.MaxValue) assertCompleteAsSync(sio.map(_.bimap(_ => (), _ => ())), Right(())) } - test("syncStep - synchronously allocate a evalMapped resource") { + testUnit("syncStep - synchronously allocate a evalMapped resource") { val sio = Resource .make(IO.unit)(_ => IO.unit) .evalMap(_ => IO.unit) @@ -2200,7 +2200,7 @@ class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with I } yield () } - test("serialize") { + property("serialize") { forAll { (io: IO[Int]) => serializable(io) }( implicitly, arbitraryIOWithoutContextShift, diff --git a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala index f423d727be..f29cb9842a 100644 --- a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala @@ -39,20 +39,19 @@ import munit.DisciplineSuite class ResourceSuite extends BaseSuite with DisciplineSuite { - ticked("Resource[IO, *] - releases resources in reverse order of acquisition") { - implicit ticker => - forAll { (as: List[(Int, Either[Throwable, Unit])]) => - var released: List[Int] = Nil - val r = as.traverse { - case (a, e) => - Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) - } - assertCompleteAs(r.use_.attempt.void, ()) - assertEquals(released, as.map(_._1)) + ticked("releases resources in reverse order of acquisition") { implicit ticker => + forAll { (as: List[(Int, Either[Throwable, Unit])]) => + var released: List[Int] = Nil + val r = as.traverse { + case (a, e) => + Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) } + assertCompleteAs(r.use_.attempt.void, ()) + assertEquals(released, as.map(_._1)) + } } - ticked("Resource[IO, *] - makes acquires non interruptible") { implicit ticker => + ticked("makes acquires non interruptible") { implicit ticker => assertCompleteAs( IO.ref(false).flatMap { interrupted => val fa = IO.sleep(5.seconds).onCancel(interrupted.set(true)) @@ -63,39 +62,36 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { ) } - ticked("Resource[IO, *] - makes acquires non interruptible, overriding uncancelable") { - implicit ticker => - assertCompleteAs( - IO.ref(false).flatMap { interrupted => - val fa = IO.uncancelable { poll => - poll(IO.sleep(5.seconds)).onCancel(interrupted.set(true)) - } + ticked("makes acquires non interruptible, overriding uncancelable") { implicit ticker => + assertCompleteAs( + IO.ref(false).flatMap { interrupted => + val fa = + IO.uncancelable { poll => poll(IO.sleep(5.seconds)).onCancel(interrupted.set(true)) } - Resource.make(fa)(_ => IO.unit).use_.timeout(1.second).attempt >> interrupted.get - }, - false - ) + Resource.make(fa)(_ => IO.unit).use_.timeout(1.second).attempt >> interrupted.get + }, + false + ) } - ticked("Resource[IO, *] - releases resource if interruption happens during use") { - implicit ticker => - val flag = IO.ref(false) + ticked("releases resource if interruption happens during use") { implicit ticker => + val flag = IO.ref(false) - assertCompleteAs( - (flag, flag).tupled.flatMap { - case (acquireFin, resourceFin) => - val action = IO.sleep(1.second).onCancel(acquireFin.set(true)) - val fin = resourceFin.set(true) - val res = Resource.makeFull[IO, Unit](poll => poll(action))(_ => fin) - - res.surround(IO.sleep(5.seconds)).timeout(3.seconds).attempt >> - (acquireFin.get, resourceFin.get).tupled - }, - false -> true - ) + assertCompleteAs( + (flag, flag).tupled.flatMap { + case (acquireFin, resourceFin) => + val action = IO.sleep(1.second).onCancel(acquireFin.set(true)) + val fin = resourceFin.set(true) + val res = Resource.makeFull[IO, Unit](poll => poll(action))(_ => fin) + + res.surround(IO.sleep(5.seconds)).timeout(3.seconds).attempt >> + (acquireFin.get, resourceFin.get).tupled + }, + false -> true + ) } - ticked("Resource[IO, *] - supports interruptible acquires") { implicit ticker => + ticked("supports interruptible acquires") { implicit ticker => val flag = IO.ref(false) assertCompleteAs( @@ -112,31 +108,30 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { ) } - ticked("Resource[IO, *] - supports interruptible acquires, respecting uncancelable") { - implicit ticker => - val flag = IO.ref(false) - val sleep = IO.sleep(1.second) - val timeout = 500.millis + ticked("supports interruptible acquires, respecting uncancelable") { implicit ticker => + val flag = IO.ref(false) + val sleep = IO.sleep(1.second) + val timeout = 500.millis - assertCompleteAs( - (flag, flag, flag, flag).tupled.flatMap { - case (acquireFin, resourceFin, a, b) => - val io = IO.uncancelable { poll => - sleep.onCancel(a.set(true)) >> poll(sleep).onCancel(b.set(true)) - } + assertCompleteAs( + (flag, flag, flag, flag).tupled.flatMap { + case (acquireFin, resourceFin, a, b) => + val io = IO.uncancelable { poll => + sleep.onCancel(a.set(true)) >> poll(sleep).onCancel(b.set(true)) + } - val resource = Resource.makeFull[IO, Unit] { poll => - poll(io).onCancel(acquireFin.set(true)) - }(_ => resourceFin.set(true)) + val resource = Resource.makeFull[IO, Unit] { poll => + poll(io).onCancel(acquireFin.set(true)) + }(_ => resourceFin.set(true)) - resource.use_.timeout(timeout).attempt >> - List(a.get, b.get, acquireFin.get, resourceFin.get).sequence - }, - List(false, true, true, false) - ) + resource.use_.timeout(timeout).attempt >> + List(a.get, b.get, acquireFin.get, resourceFin.get).sequence + }, + List(false, true, true, false) + ) } - ticked("Resource[IO, *] - release is always uninterruptible") { implicit ticker => + ticked("release is always uninterruptible") { implicit ticker => val flag = IO.ref(false) val sleep = IO.sleep(1.second) val timeout = 500.millis @@ -153,11 +148,11 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { ) } - ticked("Resource[IO, *] - eval") { implicit ticker => + ticked("eval") { implicit ticker => forAll { (fa: IO[String]) => Resource.eval(fa).use(IO.pure) eqv fa } } - ticked("Resource[IO, *] - eval - interruption") { implicit ticker => + ticked("eval - interruption") { implicit ticker => def resource(d: Deferred[IO, Int]): Resource[IO, Unit] = for { _ <- Resource.make(IO.unit)(_ => d.complete(1).void) @@ -177,17 +172,17 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assertCompleteAs(p, 1) } - ticked("Resource[IO, *] - eval(fa) <-> liftK.apply(fa)") { implicit ticker => + ticked("eval(fa) <-> liftK.apply(fa)") { implicit ticker => forAll { (fa: IO[String], f: String => IO[Int]) => Resource.eval(fa).use(f) eqv Resource.liftK[IO].apply(fa).use(f) } } - ticked("Resource[IO, *] - evalMap") { implicit ticker => + ticked("evalMap") { implicit ticker => forAll { (f: Int => IO[Int]) => Resource.eval(IO(0)).evalMap(f).use(IO.pure) eqv f(0) } } - ticked("Resource[IO, *] - evalMap with error fails during use") { implicit ticker => + ticked("evalMap with error fails during use") { implicit ticker => case object Foo extends Exception assertFailAs( @@ -195,33 +190,32 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { Foo) } - ticked("Resource[IO, *] - evalTap") { implicit ticker => + ticked("evalTap") { implicit ticker => forAll { (f: Int => IO[Int]) => Resource.eval(IO(0)).evalTap(f).use(IO.pure) eqv f(0).as(0) } } - ticked("Resource[IO, *] - evalTap with error fails during use") { implicit ticker => + ticked("evalTap with error fails during use") { implicit ticker => case object Foo extends Exception assertFailAs(Resource.eval(IO(0)).evalTap(_ => IO.raiseError(Foo)).void.use(IO.pure), Foo) } - ticked("Resource[IO, *] - releases resources that implement AutoCloseable") { - implicit ticker => - var closed = false - val autoCloseable = new AutoCloseable { - override def close(): Unit = closed = true - } + ticked("releases resources that implement AutoCloseable") { implicit ticker => + var closed = false + val autoCloseable = new AutoCloseable { + override def close(): Unit = closed = true + } - assertCompleteAs( - Resource.fromAutoCloseable(IO(autoCloseable)).surround("Hello world".pure[IO]), - "Hello world") + assertCompleteAs( + Resource.fromAutoCloseable(IO(autoCloseable)).surround("Hello world".pure[IO]), + "Hello world") - assert(closed) + assert(closed) } - real("Resource[IO, *] - allocated releases two resources") { + real("allocated releases two resources") { var a = false var b = false @@ -235,40 +229,38 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { } } - ticked("Resource[IO, *] - allocated releases resources in reverse order of acquisition") { - implicit ticker => - forAll { (as: List[(Int, Either[Throwable, Unit])]) => - var released: List[Int] = Nil - val r = as.traverse { - case (a, e) => - Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) - } - assertCompleteAs(r.allocated.flatMap(_._2).attempt.void, ()) - assertEquals(released, as.map(_._1)) + ticked("allocated releases resources in reverse order of acquisition") { implicit ticker => + forAll { (as: List[(Int, Either[Throwable, Unit])]) => + var released: List[Int] = Nil + val r = as.traverse { + case (a, e) => + Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) } + assertCompleteAs(r.allocated.flatMap(_._2).attempt.void, ()) + assertEquals(released, as.map(_._1)) + } } - ticked("Resource[IO, *] - allocated does not release until close is invoked") { - implicit ticker => - val released = new java.util.concurrent.atomic.AtomicBoolean(false) - val release = Resource.make(IO.unit)(_ => IO(released.set(true))) - val resource = Resource.eval(IO.unit) + ticked("allocated does not release until close is invoked") { implicit ticker => + val released = new java.util.concurrent.atomic.AtomicBoolean(false) + val release = Resource.make(IO.unit)(_ => IO(released.set(true))) + val resource = Resource.eval(IO.unit) - // do not inline: it confuses Dotty - val ioa = (release *> resource).allocated + // do not inline: it confuses Dotty + val ioa = (release *> resource).allocated - val prog = for { - res <- ioa - (_, close) = res - _ <- IO(assert(!released.get())) - _ <- close - _ <- IO(assert(released.get())) - } yield () + val prog = for { + res <- ioa + (_, close) = res + _ <- IO(assert(!released.get())) + _ <- close + _ <- IO(assert(released.get())) + } yield () - assertCompleteAs(prog, ()) + assertCompleteAs(prog, ()) } - ticked("Resource[IO, *] - mapK") { implicit ticker => + ticked("mapK") { implicit ticker => forAll { (fa: Kleisli[IO, Int, Int]) => val runWithTwo = new ~>[Kleisli[IO, Int, *], IO] { override def apply[A](fa: Kleisli[IO, Int, A]): IO[A] = fa(2) @@ -277,7 +269,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { } } - real("Resource[IO, *] - attempt on Resource after mapK") { + real("attempt on Resource after mapK") { class Err extends Exception Resource @@ -289,45 +281,44 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { .mustEqual(3) } - ticked("Resource[IO, *] - mapK should preserve ExitCode-specific behaviour") { - implicit ticker => - def sideEffectyResource: (AtomicBoolean, Resource[IO, Unit]) = { - val cleanExit = new java.util.concurrent.atomic.AtomicBoolean(false) - val res = Resource.makeCase(IO.unit) { - case (_, Resource.ExitCase.Succeeded) => - IO { - cleanExit.set(true) - } - case _ => IO.unit - } - (cleanExit, res) + ticked("mapK should preserve ExitCode-specific behaviour") { implicit ticker => + def sideEffectyResource: (AtomicBoolean, Resource[IO, Unit]) = { + val cleanExit = new java.util.concurrent.atomic.AtomicBoolean(false) + val res = Resource.makeCase(IO.unit) { + case (_, Resource.ExitCase.Succeeded) => + IO { + cleanExit.set(true) + } + case _ => IO.unit } + (cleanExit, res) + } - val (clean, res) = sideEffectyResource - assertCompleteAs(res.use(_ => IO.unit).attempt.void, ()) - assert(clean.get()) + val (clean, res) = sideEffectyResource + assertCompleteAs(res.use(_ => IO.unit).attempt.void, ()) + assert(clean.get()) - val (clean1, res1) = sideEffectyResource - assertCompleteAs(res1.use(_ => IO.raiseError(new Throwable("oh no"))).attempt.void, ()) - assert(!clean1.get()) + val (clean1, res1) = sideEffectyResource + assertCompleteAs(res1.use(_ => IO.raiseError(new Throwable("oh no"))).attempt.void, ()) + assert(!clean1.get()) - val (clean2, res2) = sideEffectyResource - assertCompleteAs(res2.mapK(Kleisli.liftK[IO, Int]).use_.run(0).attempt.void, ()) - assert(clean2.get()) + val (clean2, res2) = sideEffectyResource + assertCompleteAs(res2.mapK(Kleisli.liftK[IO, Int]).use_.run(0).attempt.void, ()) + assert(clean2.get()) - val (clean3, res3) = sideEffectyResource - assertCompleteAs( - res3 - .mapK(Kleisli.liftK[IO, Int]) - .use(_ => Kleisli.liftF(IO.raiseError[Unit](new Throwable("oh no")))) - .run(0) - .attempt - .void, - ()) - assert(!clean3.get()) + val (clean3, res3) = sideEffectyResource + assertCompleteAs( + res3 + .mapK(Kleisli.liftK[IO, Int]) + .use(_ => Kleisli.liftF(IO.raiseError[Unit](new Throwable("oh no")))) + .run(0) + .attempt + .void, + ()) + assert(!clean3.get()) } - ticked("Resource[IO, *] - mapK respects interruptible acquires") { implicit ticker => + ticked("mapK respects interruptible acquires") { implicit ticker => val flag = IO.ref(false) val sleep = IO.sleep(1.second) val timeout = 500.millis @@ -349,22 +340,20 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assertCompleteAs(fa, false -> true) } - ticked("Resource[IO, *] - allocated produces the same value as the resource") { - implicit ticker => - forAll { (resource: Resource[IO, Int]) => - val a0 = IO.uncancelable { p => - p(resource.allocated).flatMap { case (b, fin) => fin.as(b) } - } - val a1 = resource.use(IO.pure) - - a0.flatMap(IO.pure).handleErrorWith(IO.raiseError) eqv a1 - .flatMap(IO.pure) - .handleErrorWith(IO.raiseError) + ticked("allocated produces the same value as the resource") { implicit ticker => + forAll { (resource: Resource[IO, Int]) => + val a0 = IO.uncancelable { p => + p(resource.allocated).flatMap { case (b, fin) => fin.as(b) } } + val a1 = resource.use(IO.pure) + + a0.flatMap(IO.pure).handleErrorWith(IO.raiseError) eqv a1 + .flatMap(IO.pure) + .handleErrorWith(IO.raiseError) + } } - ticked( - "Resource[IO, *] - allocated does not release until close is invoked on mapK'd Resources") { + ticked("allocated does not release until close is invoked on mapK'd Resources") { implicit ticker => val released = new java.util.concurrent.atomic.AtomicBoolean(false) @@ -396,7 +385,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assertCompleteAs(prog, ()) } - ticked("Resource[IO, *] - use is stack-safe over binds") { implicit ticker => + ticked("use is stack-safe over binds") { implicit ticker => val r = (1 to 10000) .foldLeft(Resource.eval(IO.unit)) { case (r, _) => @@ -406,7 +395,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { r eqv IO.unit } - real("Resource[IO, *] - use is stack-safe over binds - 2") { + real("use is stack-safe over binds - 2") { val n = 50000 def p(i: Int = 0, n: Int = 50000): Resource[IO, Int] = Resource @@ -422,7 +411,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { p(n = n).use(IO.pure).mustEqual(n) } - ticked("Resource[IO, *] - mapK is stack-safe over binds") { implicit ticker => + ticked("mapK is stack-safe over binds") { implicit ticker => val r = (1 to 10000) .foldLeft(Resource.eval(IO.unit)) { case (r, _) => @@ -438,7 +427,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { r eqv IO.unit } - ticked("Resource[IO, *] - attempt is stack-safe over binds") { implicit ticker => + ticked("attempt is stack-safe over binds") { implicit ticker => val r = (1 to 10000) .foldLeft(Resource.eval(IO.unit)) { case (r, _) => @@ -449,32 +438,31 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assertCompleteAs(r.use_, ()) } - ticked("Resource[IO, *] - safe attempt suspended resource") { implicit ticker => + ticked("safe attempt suspended resource") { implicit ticker => val exception = new Exception("boom!") val suspend = Resource.suspend[IO, Unit](IO.raiseError(exception)) assertFailAs(suspend.use_, exception) } - ticked("Resource[IO, *] - both - releases resources in reverse order of acquisition") { - implicit ticker => - // conceptually asserts that: - // forAll (r: Resource[F, A]) then r <-> r.both(Resource.unit) <-> Resource.unit.both(r) - // needs to be tested manually to assert the equivalence during cleanup as well - forAll { (as: List[(Int, Either[Throwable, Unit])], rhs: Boolean) => - var released: List[Int] = Nil - val r = as.traverse { - case (a, e) => - Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) - } - val unit = ().pure[Resource[IO, *]] - val p = if (rhs) r.both(unit) else unit.both(r) - - assertCompleteAs(p.use_.attempt.void, ()) - assertEquals(released, as.map(_._1)) + ticked("both - releases resources in reverse order of acquisition") { implicit ticker => + // conceptually asserts that: + // forAll (r: Resource[F, A]) then r <-> r.both(Resource.unit) <-> Resource.unit.both(r) + // needs to be tested manually to assert the equivalence during cleanup as well + forAll { (as: List[(Int, Either[Throwable, Unit])], rhs: Boolean) => + var released: List[Int] = Nil + val r = as.traverse { + case (a, e) => + Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) } + val unit = ().pure[Resource[IO, *]] + val p = if (rhs) r.both(unit) else unit.both(r) + + assertCompleteAs(p.use_.attempt.void, ()) + assertEquals(released, as.map(_._1)) + } } - ticked("Resource[IO, *] - both - parallel acquisition and release") { implicit ticker => + ticked("both - parallel acquisition and release") { implicit ticker => var leftAllocated = false var rightAllocated = false var leftReleasing = false @@ -517,197 +505,193 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assert(rightReleased) } - ticked("Resource[IO, *] - both - safety: lhs error during rhs interruptible region") { - implicit ticker => - var leftAllocated = false - var rightAllocated = false - var leftReleasing = false - var rightReleasing = false - var leftReleased = false - var rightReleased = false - - def wait(n: Int) = IO.sleep(n.seconds) - val lhs = for { - _ <- Resource.make(wait(1) >> IO { leftAllocated = true }) { _ => - IO { leftReleasing = true } >> wait(1) >> IO { leftReleased = true } - } - _ <- Resource.eval(wait(1) >> IO.raiseError[Unit](new Exception)) - } yield () + ticked("both - safety: lhs error during rhs interruptible region") { implicit ticker => + var leftAllocated = false + var rightAllocated = false + var leftReleasing = false + var rightReleasing = false + var leftReleased = false + var rightReleased = false - val rhs = for { - _ <- Resource.make(wait(1) >> IO { rightAllocated = true }) { _ => - IO { rightReleasing = true } >> wait(1) >> IO { rightReleased = true } - } - _ <- Resource.eval(wait(2)) - } yield () + def wait(n: Int) = IO.sleep(n.seconds) + val lhs = for { + _ <- Resource.make(wait(1) >> IO { leftAllocated = true }) { _ => + IO { leftReleasing = true } >> wait(1) >> IO { leftReleased = true } + } + _ <- Resource.eval(wait(1) >> IO.raiseError[Unit](new Exception)) + } yield () - lhs.both(rhs).use(_ => IO.unit).handleError(_ => ()).unsafeToFuture() + val rhs = for { + _ <- Resource.make(wait(1) >> IO { rightAllocated = true }) { _ => + IO { rightReleasing = true } >> wait(1) >> IO { rightReleased = true } + } + _ <- Resource.eval(wait(2)) + } yield () - // after 1 second: - // both resources have allocated (concurrency, serially it would happen after 2 seconds) - // resources are still open during `flatMap` (correctness) - ticker.ctx.tick() - ticker.ctx.advanceAndTick(1.second) - assert(leftAllocated) - assert(rightAllocated) - assert(!leftReleasing) - assert(!rightReleasing) + lhs.both(rhs).use(_ => IO.unit).handleError(_ => ()).unsafeToFuture() - // after 2 seconds: - // both resources have started cleanup (interruption, or rhs would start releasing after 3 seconds) - ticker.ctx.advanceAndTick(1.second) - assert(leftReleasing) - assert(rightReleasing) - assert(!leftReleased) - assert(!rightReleased) + // after 1 second: + // both resources have allocated (concurrency, serially it would happen after 2 seconds) + // resources are still open during `flatMap` (correctness) + ticker.ctx.tick() + ticker.ctx.advanceAndTick(1.second) + assert(leftAllocated) + assert(rightAllocated) + assert(!leftReleasing) + assert(!rightReleasing) - // after 3 seconds: - // both resources have terminated cleanup (concurrency, serially it would happen after 4 seconds) - ticker.ctx.advanceAndTick(1.second) - assert(leftReleased) - assert(rightReleased) + // after 2 seconds: + // both resources have started cleanup (interruption, or rhs would start releasing after 3 seconds) + ticker.ctx.advanceAndTick(1.second) + assert(leftReleasing) + assert(rightReleasing) + assert(!leftReleased) + assert(!rightReleased) + + // after 3 seconds: + // both resources have terminated cleanup (concurrency, serially it would happen after 4 seconds) + ticker.ctx.advanceAndTick(1.second) + assert(leftReleased) + assert(rightReleased) } - ticked("Resource[IO, *] - both - safety: rhs error during lhs uninterruptible region") { - implicit ticker => - var leftAllocated = false - var rightAllocated = false - var rightErrored = false - var leftReleasing = false - var rightReleasing = false - var leftReleased = false - var rightReleased = false - - def wait(n: Int) = IO.sleep(n.seconds) - val lhs = Resource.make(wait(3) >> IO { leftAllocated = true }) { _ => - IO { leftReleasing = true } >> wait(1) >> IO { leftReleased = true } + ticked("both - safety: rhs error during lhs uninterruptible region") { implicit ticker => + var leftAllocated = false + var rightAllocated = false + var rightErrored = false + var leftReleasing = false + var rightReleasing = false + var leftReleased = false + var rightReleased = false + + def wait(n: Int) = IO.sleep(n.seconds) + val lhs = Resource.make(wait(3) >> IO { leftAllocated = true }) { _ => + IO { leftReleasing = true } >> wait(1) >> IO { leftReleased = true } + } + val rhs = for { + _ <- Resource.make(wait(1) >> IO { rightAllocated = true }) { _ => + IO { rightReleasing = true } >> wait(1) >> IO { rightReleased = true } } - val rhs = for { - _ <- Resource.make(wait(1) >> IO { rightAllocated = true }) { _ => - IO { rightReleasing = true } >> wait(1) >> IO { rightReleased = true } - } - _ <- Resource.make( - wait(1) >> IO { rightErrored = true } >> IO.raiseError[Unit](new Exception))(_ => - IO.unit) - } yield () + _ <- Resource.make( + wait(1) >> IO { rightErrored = true } >> IO.raiseError[Unit](new Exception))(_ => + IO.unit) + } yield () - lhs.both(rhs).use(_ => wait(1)).handleError(_ => ()).unsafeToFuture() + lhs.both(rhs).use(_ => wait(1)).handleError(_ => ()).unsafeToFuture() - // after 1 second: - // rhs has partially allocated, lhs executing - ticker.ctx.tick() - ticker.ctx.advanceAndTick(1.second) - assert(!leftAllocated) - assert(rightAllocated) - assert(!rightErrored) - assert(!leftReleasing) - assert(!rightReleasing) - - // after 2 seconds: - // rhs has failed, release blocked since lhs is in uninterruptible allocation - ticker.ctx.advanceAndTick(1.second) - assert(!leftAllocated) - assert(rightAllocated) - assert(rightErrored) - assert(!leftReleasing) - assert(!rightReleasing) - - // after 3 seconds: - // lhs completes allocation (concurrency, serially it would happen after 4 seconds) - // both resources have started cleanup (correctness, error propagates to both sides) - ticker.ctx.advanceAndTick(1.second) - assert(leftAllocated) - assert(leftReleasing) - assert(rightReleasing) - assert(!leftReleased) - assert(!rightReleased) - - // after 4 seconds: - // both resource have terminated cleanup (concurrency, serially it would happen after 5 seconds) - ticker.ctx.advanceAndTick(1.second) - assert(leftReleased) - assert(rightReleased) + // after 1 second: + // rhs has partially allocated, lhs executing + ticker.ctx.tick() + ticker.ctx.advanceAndTick(1.second) + assert(!leftAllocated) + assert(rightAllocated) + assert(!rightErrored) + assert(!leftReleasing) + assert(!rightReleasing) + + // after 2 seconds: + // rhs has failed, release blocked since lhs is in uninterruptible allocation + ticker.ctx.advanceAndTick(1.second) + assert(!leftAllocated) + assert(rightAllocated) + assert(rightErrored) + assert(!leftReleasing) + assert(!rightReleasing) + + // after 3 seconds: + // lhs completes allocation (concurrency, serially it would happen after 4 seconds) + // both resources have started cleanup (correctness, error propagates to both sides) + ticker.ctx.advanceAndTick(1.second) + assert(leftAllocated) + assert(leftReleasing) + assert(rightReleasing) + assert(!leftReleased) + assert(!rightReleased) + + // after 4 seconds: + // both resource have terminated cleanup (concurrency, serially it would happen after 5 seconds) + ticker.ctx.advanceAndTick(1.second) + assert(leftReleased) + assert(rightReleased) } - test("Resource[IO, *] - both - propagate the exit case") { - import Resource.ExitCase + import Resource.ExitCase - ticked("use successfully, test left") { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertCompleteAs(r.both(Resource.unit).use(_ => IO.unit), ()) - assertEquals(got, ExitCase.Succeeded) - } + ticked("both - propagate the exit case - use successfully, test left") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertCompleteAs(r.both(Resource.unit).use(_ => IO.unit), ()) + assertEquals(got, ExitCase.Succeeded) + } - ticked("use successfully, test right") { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertCompleteAs(Resource.unit.both(r).use(_ => IO.unit), ()) - assertEquals(got, ExitCase.Succeeded) - } + ticked("both - propagate the exit case - use successfully, test right") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertCompleteAs(Resource.unit.both(r).use(_ => IO.unit), ()) + assertEquals(got, ExitCase.Succeeded) + } - ticked("use errored, test left") { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertFailAs(r.both(Resource.unit).use(_ => IO.raiseError(ex)), ex) - assertEquals(got, ExitCase.Errored(ex)) - } + ticked("both - propagate the exit case - use errored, test left") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertFailAs(r.both(Resource.unit).use(_ => IO.raiseError(ex)), ex) + assertEquals(got, ExitCase.Errored(ex)) + } - ticked("use errored, test right") { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertFailAs(Resource.unit.both(r).use(_ => IO.raiseError(ex)), ex) - assertEquals(got, ExitCase.Errored(ex)) - } + ticked("both - propagate the exit case - use errored, test right") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertFailAs(Resource.unit.both(r).use(_ => IO.raiseError(ex)), ex) + assertEquals(got, ExitCase.Errored(ex)) + } - ticked("right errored, test left") { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertFailAs(r.both(Resource.eval(IO.sleep(1.second) *> IO.raiseError(ex))).use_, ex) - assertEquals(got, ExitCase.Errored(ex)) - } + ticked("both - propagate the exit case - right errored, test left") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertFailAs(r.both(Resource.eval(IO.sleep(1.second) *> IO.raiseError(ex))).use_, ex) + assertEquals(got, ExitCase.Errored(ex)) + } - ticked("left errored, test right") { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertFailAs(Resource.eval(IO.sleep(1.second) *> IO.raiseError(ex)).both(r).use_, ex) - assertEquals(got, ExitCase.Errored(ex)) - } + ticked("both - propagate the exit case - left errored, test right") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertFailAs(Resource.eval(IO.sleep(1.second) *> IO.raiseError(ex)).both(r).use_, ex) + assertEquals(got, ExitCase.Errored(ex)) + } - ticked("use canceled, test left") { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertSelfCancel(r.both(Resource.unit).use(_ => IO.canceled)) - assertEquals(got, ExitCase.Canceled) - } + ticked("both - propagate the exit case - use canceled, test left") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertSelfCancel(r.both(Resource.unit).use(_ => IO.canceled)) + assertEquals(got, ExitCase.Canceled) + } - ticked("use canceled, test right") { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertSelfCancel(Resource.unit.both(r).use(_ => IO.canceled)) - assertEquals(got, ExitCase.Canceled) - } + ticked("both - propagate the exit case - use canceled, test right") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertSelfCancel(Resource.unit.both(r).use(_ => IO.canceled)) + assertEquals(got, ExitCase.Canceled) + } - ticked("right canceled, test left") { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertSelfCancel(r.both(Resource.eval(IO.sleep(1.second) *> IO.canceled)).use_) - assertEquals(got, ExitCase.Canceled) - } + ticked("both - propagate the exit case - right canceled, test left") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertSelfCancel(r.both(Resource.eval(IO.sleep(1.second) *> IO.canceled)).use_) + assertEquals(got, ExitCase.Canceled) + } - ticked("left canceled, test right") { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertSelfCancel(Resource.eval(IO.sleep(1.second) *> IO.canceled).both(r).use_) - assertEquals(got, ExitCase.Canceled) - } + ticked("both - propagate the exit case - left canceled, test right") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertSelfCancel(Resource.eval(IO.sleep(1.second) *> IO.canceled).both(r).use_) + assertEquals(got, ExitCase.Canceled) } - ticked("Resource[IO, *] - releases both resources on combineK") { implicit ticker => + ticked("releases both resources on combineK") { implicit ticker => var acquired: Set[Int] = Set.empty var released: Set[Int] = Set.empty def observe(a: Int) = @@ -718,7 +702,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { } ticked( - "Resource[IO, *] - releases both resources on combineK when using a SemigroupK instance that discards allocated values") { + "releases both resources on combineK when using a SemigroupK instance that discards allocated values") { implicit ticker => implicit val sgk: SemigroupK[IO] = new SemigroupK[IO] { override def combineK[A](x: IO[A], y: IO[A]): IO[A] = x <* y @@ -732,17 +716,16 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assertEquals(released, acquired) } - ticked("Resource[IO, *] - combineK - behave like orElse when underlying effect does") { - implicit ticker => - Prop.forAll { (r1: Resource[IO, Int], r2: Resource[IO, Int]) => - val lhs = r1.orElse(r2) - val rhs = r1 <+> r2 + ticked("combineK - behave like orElse when underlying effect does") { implicit ticker => + Prop.forAll { (r1: Resource[IO, Int], r2: Resource[IO, Int]) => + val lhs = r1.orElse(r2) + val rhs = r1 <+> r2 - lhs eqv rhs - } + lhs eqv rhs + } } - ticked("Resource[IO, *] - combineK - behave like underlying effect") { implicit ticker => + ticked("combineK - behave like underlying effect") { implicit ticker => forAll { (ot1: OptionT[IO, Int], ot2: OptionT[IO, Int]) => val lhs = Resource.eval(ot1 <+> ot2).use(OptionT.pure[IO](_)).value val rhs = (Resource.eval(ot1) <+> Resource.eval(ot2)).use(OptionT.pure[IO](_)).value @@ -751,42 +734,41 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { } } - test("Resource[IO, *] - combineK - propagate the exit case") { - import Resource.ExitCase - - ticked("use successfully, test left") { implicit ticker => + ticked("combineK - propagate the exit case - use successfully, test left") { + implicit ticker => var got: ExitCase = null val r = Resource.onFinalizeCase(ec => IO { got = ec }) assertCompleteAs(r.combineK(Resource.unit).use(_ => IO.unit), ()) assertEquals(got, ExitCase.Succeeded) - } + } - ticked("use errored, test left") { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertFailAs(r.combineK(Resource.unit).use(_ => IO.raiseError(ex)), ex) - assertEquals(got, ExitCase.Errored(ex)) - } + ticked("combineK - propagate the exit case - use errored, test left") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertFailAs(r.combineK(Resource.unit).use(_ => IO.raiseError(ex)), ex) + assertEquals(got, ExitCase.Errored(ex)) + } - ticked("left errored, test left") { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) *> - Resource.eval(IO.raiseError(ex)) - assertCompleteAs(r.combineK(Resource.unit).use_, ()) - assertEquals(got, ExitCase.Succeeded) - } + ticked("combineK - propagate the exit case - left errored, test left") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) *> + Resource.eval(IO.raiseError(ex)) + assertCompleteAs(r.combineK(Resource.unit).use_, ()) + assertEquals(got, ExitCase.Succeeded) + } - ticked("left errored, test right") { implicit ticker => - var got: ExitCase = null - val ex = new Exception - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertCompleteAs(Resource.eval(IO.raiseError(ex)).combineK(r).use_, ()) - assertEquals(got, ExitCase.Succeeded) - } + ticked("combineK - propagate the exit case - left errored, test right") { implicit ticker => + var got: ExitCase = null + val ex = new Exception + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertCompleteAs(Resource.eval(IO.raiseError(ex)).combineK(r).use_, ()) + assertEquals(got, ExitCase.Succeeded) + } - ticked("left errored, use errored, test right") { implicit ticker => + ticked("combineK - propagate the exit case - left errored, use errored, test right") { + implicit ticker => var got: ExitCase = null val ex = new Exception val r = Resource.onFinalizeCase(ec => IO { got = ec }) @@ -794,22 +776,22 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { Resource.eval(IO.raiseError(new Exception)).combineK(r).use(_ => IO.raiseError(ex)), ex) assertEquals(got, ExitCase.Errored(ex)) - } + } - ticked("use canceled, test left") { implicit ticker => - var got: ExitCase = null - val r = Resource.onFinalizeCase(ec => IO { got = ec }) - assertSelfCancel(r.combineK(Resource.unit).use(_ => IO.canceled)) - assertEquals(got, ExitCase.Canceled) - } + ticked("combineK - propagate the exit case - use canceled, test left") { implicit ticker => + var got: ExitCase = null + val r = Resource.onFinalizeCase(ec => IO { got = ec }) + assertSelfCancel(r.combineK(Resource.unit).use(_ => IO.canceled)) + assertEquals(got, ExitCase.Canceled) + } - ticked("left errored, use canceled, test right") { implicit ticker => + ticked("combineK - propagate the exit case - left errored, use canceled, test right") { + implicit ticker => var got: ExitCase = null val r = Resource.onFinalizeCase(ec => IO { got = ec }) assertSelfCancel( Resource.eval(IO.raiseError(new Exception)).combineK(r).use(_ => IO.canceled)) assertEquals(got, ExitCase.Canceled) - } } ticked("surround - wrap an effect in a usage and ignore the value produced by resource") { @@ -822,7 +804,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { } ticked( - "Resource[IO, *] - surroundK - wrap an effect in a usage, ignore the value produced by resource and return FunctionK") { + "surroundK - wrap an effect in a usage, ignore the value produced by resource and return FunctionK") { implicit ticker => val r = Resource.eval(IO.pure(0)) val surroundee = IO("hello") @@ -832,13 +814,12 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { surrounded eqv surroundee } - ticked("Resource[IO, *] - evalOn - run acquire and release on provided ExecutionContext") { - implicit ticker => - forAll { (executionContext: ExecutionContext) => - val assertion = - IO.executionContext.flatMap(ec => IO(assertEquals(ec, executionContext)).void) - Resource.make(assertion)(_ => assertion).evalOn(executionContext).use_.as(true) - } + ticked("evalOn - run acquire and release on provided ExecutionContext") { implicit ticker => + forAll { (executionContext: ExecutionContext) => + val assertion = + IO.executionContext.flatMap(ec => IO(assertEquals(ec, executionContext)).void) + Resource.make(assertion)(_ => assertion).evalOn(executionContext).use_.as(true) + } } { @@ -1197,8 +1178,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { } // TODO enable once `PureConc` finalizer bug is fixed. - test( - "Concurrent[Resource] - does not leak if canceled right after delayed acquire is canceled".ignore) { + testUnit("does not leak if canceled right after delayed acquire is canceled".ignore) { import cats.effect.kernel.testkit.pure._ type F[A] = PureConc[Throwable, A] val F = Concurrent[F] @@ -1217,7 +1197,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { releaseRun <- released.get } yield acquireRun && releaseRun - run(go) == Outcome.succeeded(Some(true)) + assertEquals(run(go), Outcome.succeeded[Option, Throwable, Boolean](Some(true))) } } diff --git a/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala b/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala index 6d801d4937..a5d542ee17 100644 --- a/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala @@ -29,11 +29,11 @@ import munit.DisciplineSuite class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuite { - test("produce a pure value when run") { + testUnit("produce a pure value when run") { assertCompleteAsSync(SyncIO.pure(42), 42) } - test("suspend a side-effect without memoizing") { + testUnit("suspend a side-effect without memoizing") { var i = 42 val ioa = SyncIO { @@ -45,73 +45,73 @@ class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuit assertCompleteAsSync(ioa, 44) } - test("capture errors in suspensions") { + testUnit("capture errors in suspensions") { case object TestException extends RuntimeException assertFailAsSync(SyncIO(throw TestException), TestException) } - test("map results to a new type") { + testUnit("map results to a new type") { assertCompleteAsSync(SyncIO.pure(42).map(_.toString), "42") } - test("flatMap results sequencing both effects") { + testUnit("flatMap results sequencing both effects") { var i = 0 assertCompleteAsSync(SyncIO.pure(42).flatMap(i2 => SyncIO { i = i2 }), ()) assertEquals(i, 42) } - test("raiseError propagates out") { + testUnit("raiseError propagates out") { case object TestException extends RuntimeException assertFailAsSync( SyncIO.raiseError(TestException).void.flatMap(_ => SyncIO.pure(())), TestException) } - test("errors can be handled") { + testUnit("errors can be handled") { case object TestException extends RuntimeException assertCompleteAsSync(SyncIO.raiseError[Unit](TestException).attempt, Left(TestException)) } - test("attempt is redeem with Left(_) for recover and Right(_) for map") { + property("attempt is redeem with Left(_) for recover and Right(_) for map") { forAll { (io: SyncIO[Int]) => io.attempt eqv io.redeem(Left(_), Right(_)) } } - test("attempt is flattened redeemWith") { + property("attempt is flattened redeemWith") { forAll { (io: SyncIO[Int], recover: Throwable => SyncIO[String], bind: Int => SyncIO[String]) => io.attempt.flatMap(_.fold(recover, bind)) eqv io.redeemWith(recover, bind) } } - test("redeem is flattened redeemWith") { + property("redeem is flattened redeemWith") { forAll { (io: SyncIO[Int], recover: Throwable => SyncIO[String], bind: Int => SyncIO[String]) => io.redeem(recover, bind).flatMap(identity) eqv io.redeemWith(recover, bind) } } - test("redeem subsumes handleError") { + property("redeem subsumes handleError") { forAll { (io: SyncIO[Int], recover: Throwable => Int) => io.redeem(recover, identity) eqv io.handleError(recover) } } - test("redeemWith subsumes handleErrorWith") { + property("redeemWith subsumes handleErrorWith") { forAll { (io: SyncIO[Int], recover: Throwable => SyncIO[Int]) => io.redeemWith(recover, SyncIO.pure) eqv io.handleErrorWith(recover) } } - test("redeem correctly recovers from errors") { + property("redeem correctly recovers from errors") { case object TestException extends RuntimeException assertCompleteAsSync(SyncIO.raiseError[Unit](TestException).redeem(_ => 42, _ => 43), 42) } - test("redeem maps successful results") { + testUnit("redeem maps successful results") { assertCompleteAsSync(SyncIO.unit.redeem(_ => 41, _ => 42), 42) } - test("redeem catches exceptions thrown in recovery function") { + testUnit("redeem catches exceptions thrown in recovery function") { case object TestException extends RuntimeException case object ThrownException extends RuntimeException assertCompleteAsSync( @@ -122,14 +122,14 @@ class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuit Left(ThrownException)) } - test("redeem catches exceptions thrown in map function") { + testUnit("redeem catches exceptions thrown in map function") { case object ThrownException extends RuntimeException assertCompleteAsSync( SyncIO.unit.redeem(_ => 41, _ => throw ThrownException).attempt, Left(ThrownException)) } - test("redeemWith correctly recovers from errors") { + testUnit("redeemWith correctly recovers from errors") { case object TestException extends RuntimeException assertCompleteAsSync( SyncIO @@ -138,11 +138,11 @@ class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuit 42) } - test("redeemWith binds successful results") { + testUnit("redeemWith binds successful results") { assertCompleteAsSync(SyncIO.unit.redeemWith(_ => SyncIO.pure(41), _ => SyncIO.pure(42)), 42) } - test("redeemWith catches exceptions throw in recovery function") { + testUnit("redeemWith catches exceptions throw in recovery function") { case object TestException extends RuntimeException case object ThrownException extends RuntimeException assertCompleteAsSync( @@ -153,14 +153,14 @@ class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuit Left(ThrownException)) } - test("redeemWith catches exceptions thrown in bind function") { + testUnit("redeemWith catches exceptions thrown in bind function") { case object ThrownException extends RuntimeException assertCompleteAsSync( SyncIO.unit.redeem(_ => SyncIO.pure(41), _ => throw ThrownException).attempt, Left(ThrownException)) } - test("evaluate 10,000 consecutive map continuations") { + testUnit("evaluate 10,000 consecutive map continuations") { def loop(i: Int): SyncIO[Unit] = if (i < 10000) SyncIO.unit.flatMap(_ => loop(i + 1)).map(u => u) @@ -170,7 +170,7 @@ class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuit assertCompleteAsSync(loop(0), ()) } - test("evaluate 10,000 consecutive handleErrorWith continuations") { + testUnit("evaluate 10,000 consecutive handleErrorWith continuations") { def loop(i: Int): SyncIO[Unit] = if (i < 10000) SyncIO.unit.flatMap(_ => loop(i + 1)).handleErrorWith(SyncIO.raiseError(_)) @@ -180,21 +180,21 @@ class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuit assertCompleteAsSync(loop(0), ()) } - test("catch exceptions thrown in map functions") { + testUnit("catch exceptions thrown in map functions") { case object TestException extends RuntimeException assertCompleteAsSync( SyncIO.unit.map(_ => (throw TestException): Unit).attempt, Left(TestException)) } - test("catch exceptions thrown in flatMap functions") { + testUnit("catch exceptions thrown in flatMap functions") { case object TestException extends RuntimeException assertCompleteAsSync( SyncIO.unit.flatMap(_ => (throw TestException): SyncIO[Unit]).attempt, Left(TestException)) } - test("catch exceptions thrown in handleErrorWith functions") { + testUnit("catch exceptions thrown in handleErrorWith functions") { case object TestException extends RuntimeException case object WrongException extends RuntimeException assertCompleteAsSync( @@ -205,13 +205,13 @@ class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuit Left(TestException)) } - test("preserve monad right identity on uncancelable") { + testUnit("preserve monad right identity on uncancelable") { val fa = MonadCancel[SyncIO].uncancelable(_ => MonadCancel[SyncIO].canceled) assertCompleteAsSync(fa.flatMap(SyncIO.pure(_)), ()) assertCompleteAsSync(fa, ()) } - test("cancel flatMap continuations following a canceled uncancelable block") { + testUnit("cancel flatMap continuations following a canceled uncancelable block") { assertCompleteAsSync( MonadCancel[SyncIO] .uncancelable(_ => MonadCancel[SyncIO].canceled) @@ -219,7 +219,7 @@ class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuit ()) } - test("cancel map continuations following a canceled uncancelable block") { + testUnit("cancel map continuations following a canceled uncancelable block") { assertCompleteAsSync( MonadCancel[SyncIO].uncancelable(_ => MonadCancel[SyncIO].canceled).map(_ => ()), ()) @@ -235,7 +235,7 @@ class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuit } yield res } - test("serialize") { + property("serialize") { forAll { (io: SyncIO[Int]) => serializable(io) } } diff --git a/tests/shared/src/test/scala/cats/effect/ThunkSuite.scala b/tests/shared/src/test/scala/cats/effect/ThunkSuite.scala index bf8a66400a..535834ed3c 100644 --- a/tests/shared/src/test/scala/cats/effect/ThunkSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/ThunkSuite.scala @@ -18,10 +18,10 @@ package cats.effect class ThunkSuite extends BaseSuite { - test("return the same function") { + testUnit("return the same function") { var i = 0 val f = () => i += 1 - IO.delay(f()).asInstanceOf[IO.Delay[Unit]].thunk eq f + assertEquals(IO.delay(f()).asInstanceOf[IO.Delay[Unit]].thunk, f) } } diff --git a/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSuite.scala b/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSuite.scala index 7c32c0f8df..133153cf8a 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/DerivationRefinementSuite.scala @@ -26,7 +26,7 @@ class DerivationRefinementSuite extends BaseSuite { type AsyncStack[F[_], A] = Kleisli[OptionT[EitherT[IorT[F, Int, *], String, *], *], Unit, A] type SyncStack[F[_], A] = StateT[ReaderWriterStateT[F, String, Int, Unit, *], Boolean, A] - test("returns Async for OptionT at runtime if possible") { + testUnit("returns Async for OptionT at runtime if possible") { check[IO, OptionT, Sync, Async] check[IO, OptionT, Temporal, Async] check[IO, OptionT, Concurrent, Async] @@ -34,7 +34,7 @@ class DerivationRefinementSuite extends BaseSuite { check[IO, OptionT, MonadCancelThrow, Async] } - test("returns Async for EitherT at runtime if possible") { + testUnit("returns Async for EitherT at runtime if possible") { type EitherTString[F[_], A] = EitherT[F, String, A] check[IO, EitherTString, Sync, Async] check[IO, EitherTString, Temporal, Async] @@ -43,7 +43,7 @@ class DerivationRefinementSuite extends BaseSuite { check[IO, EitherTString, MonadCancelThrow, Async] } - test("returns Async for Kleisli at runtime if possible") { + testUnit("returns Async for Kleisli at runtime if possible") { type StringKleisli[F[_], A] = Kleisli[F, String, A] check[IO, StringKleisli, Sync, Async] check[IO, StringKleisli, Temporal, Async] @@ -52,7 +52,7 @@ class DerivationRefinementSuite extends BaseSuite { check[IO, StringKleisli, MonadCancelThrow, Async] } - test("returns Async for IorT at runtime if possible") { + testUnit("returns Async for IorT at runtime if possible") { type StringIorT[F[_], A] = IorT[F, String, A] check[IO, StringIorT, Sync, Async] check[IO, StringIorT, Temporal, Async] @@ -61,7 +61,7 @@ class DerivationRefinementSuite extends BaseSuite { check[IO, StringIorT, MonadCancelThrow, Async] } - test("returns Async for WriterT at runtime if possible") { + testUnit("returns Async for WriterT at runtime if possible") { type StringWriterT[F[_], A] = WriterT[F, String, A] check[IO, StringWriterT, Sync, Async] check[IO, StringWriterT, Temporal, Async] @@ -70,19 +70,19 @@ class DerivationRefinementSuite extends BaseSuite { check[IO, StringWriterT, MonadCancelThrow, Async] } - test("returns Sync for StateT at runtime if possible") { + testUnit("returns Sync for StateT at runtime if possible") { type StringStateT[F[_], A] = StateT[F, String, A] check[IO, StringStateT, MonadCancelThrow, Sync] check[SyncIO, StringStateT, MonadCancelThrow, Sync] } - test("returns Sync for ReaderWriterStateT at runtime if possible") { + testUnit("returns Sync for ReaderWriterStateT at runtime if possible") { type TestRWST[F[_], A] = ReaderWriterStateT[F, String, Int, Unit, A] check[IO, TestRWST, MonadCancelThrow, Sync] check[SyncIO, TestRWST, MonadCancelThrow, Sync] } - test("returns Async for stacked transformers at runtime if possible") { + testUnit("returns Async for stacked transformers at runtime if possible") { check[IO, AsyncStack, Sync, Async] check[IO, AsyncStack, Temporal, Async] check[IO, AsyncStack, Concurrent, Async] @@ -90,7 +90,7 @@ class DerivationRefinementSuite extends BaseSuite { check[IO, AsyncStack, MonadCancelThrow, Async] } - test("returns Sync for stacked transformers at runtime if possible") { + testUnit("returns Sync for stacked transformers at runtime if possible") { check[IO, SyncStack, MonadCancelThrow, Sync] check[SyncIO, SyncStack, MonadCancelThrow, Sync] check[SyncIO, AsyncStack, MonadCancelThrow, Sync] diff --git a/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala b/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala index 3034062561..3eacb594e0 100644 --- a/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/ConsoleSuite.scala @@ -21,14 +21,14 @@ class ConsoleSuite extends BaseSuite { case class Foo(n: Int, b: Boolean) - test("select default Show.fromToString (IO)") { + testUnit("select default Show.fromToString (IO)") { val _ = ( IO.print(Foo(1, true)), // compilation test IO.println(Foo(1, true)) // compilation test ) } - test("select default Show.fromToString (Console[IO])") { + testUnit("select default Show.fromToString (Console[IO])") { val _ = ( Console[IO].print(Foo(1, true)), // compilation test Console[IO].println(Foo(1, true)), // compilation test diff --git a/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSuite.scala b/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSuite.scala index 88bfcafb93..3461652376 100644 --- a/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/UnsafeBoundedSuite.scala @@ -29,7 +29,7 @@ class UnsafeBoundedSuite extends BaseSuite { // NB: emperically, it seems this needs to be > availableProcessors() to be effective val length = 1000 - test("sequential all") { + testUnit("sequential all") { val q = new UnsafeBounded[Int](length) 0.until(length).foreach(q.put(_)) @@ -67,22 +67,22 @@ class UnsafeBoundedSuite extends BaseSuite { test.timeoutTo(30.seconds, IO(assert(false))) } - test("produce failure when putting over bound") { + testUnit("produce failure when putting over bound") { val q = new UnsafeBounded[Unit](10) - intercept[Exception](0.until(11).foreach(_ => q.put(()))) + val _ = intercept[Exception](0.until(11).foreach(_ => q.put(()))) } - test("produce failure when taking while empty - without changes") { + testUnit("produce failure when taking while empty - without changes") { val q = new UnsafeBounded[Unit](10) - intercept[Exception](q.take()) + val _ = intercept[Exception](q.take()) } - test("produce failure when taking while empty - after put and take") { + testUnit("produce failure when taking while empty - after put and take") { val q = new UnsafeBounded[Unit](10) 0.until(5).foreach(_ => q.put(())) 0.until(5).foreach(_ => q.take()) - intercept[Exception](q.take()) + val _ = intercept[Exception](q.take()) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSuite.scala b/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSuite.scala index 386f88a84a..9dbe535b2b 100644 --- a/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/UnsafeUnboundedSuite.scala @@ -23,18 +23,18 @@ class UnsafeUnboundedSuite extends BaseSuite { val length = 1000 - test("put and take in order") { + testUnit("put and take in order") { val q = new UnsafeUnbounded[Int]() 0.until(length).foreach(q.put(_)) assertEquals(0.until(length).map(_ => q.take()), 0.until(length)) } - test("produce an error when taking from empty - always empty") { - intercept[Exception](new UnsafeUnbounded[Unit]().take()) + testUnit("produce an error when taking from empty - always empty") { + val _ = intercept[Exception](new UnsafeUnbounded[Unit]().take()) } - test("produce an error when taking from empty - emptied") { + testUnit("produce an error when taking from empty - emptied") { val q = new UnsafeUnbounded[Unit]() q.put(()) @@ -42,10 +42,10 @@ class UnsafeUnboundedSuite extends BaseSuite { q.take() q.take() - intercept[Exception](q.take()) + val _ = intercept[Exception](q.take()) } - test("put three times, clear one, then take") { + testUnit("put three times, clear one, then take") { val q = new UnsafeUnbounded[String]() q.put("1") diff --git a/tests/shared/src/test/scala/cats/effect/tracing/TracingSuite.scala b/tests/shared/src/test/scala/cats/effect/tracing/TracingSuite.scala index 7535dd9038..c17e664b61 100644 --- a/tests/shared/src/test/scala/cats/effect/tracing/TracingSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/tracing/TracingSuite.scala @@ -21,7 +21,7 @@ import cats.effect.testkit.TestInstances class TracingSuite extends BaseSuite with TestInstances { - test("IO.delay should generate identical traces") { + testUnit("IO.delay should generate identical traces") { val f = () => println("foo") val a = IO(f()) val b = IO(f()) @@ -31,7 +31,7 @@ class TracingSuite extends BaseSuite with TestInstances { } } - test("IO.delay should generate unique traces") { + testUnit("IO.delay should generate unique traces") { val a = IO(println("foo")) val b = IO(println("bar")) (a, b) match { @@ -40,7 +40,7 @@ class TracingSuite extends BaseSuite with TestInstances { } } - test("Async.delay should generate identical traces") { + testUnit("Async.delay should generate identical traces") { val f = () => println("foo") val a = Async[IO].delay(f()) val b = Async[IO].delay(f()) @@ -50,7 +50,7 @@ class TracingSuite extends BaseSuite with TestInstances { } } - test("Async.delay should generate unique traces") { + testUnit("Async.delay should generate unique traces") { val a = Async[IO].delay(println("foo")) val b = Async[IO].delay(println("bar")) (a, b) match { diff --git a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSuite.scala b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSuite.scala index 4229a31ba6..d30b151e9a 100644 --- a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeBuilderSuite.scala @@ -21,7 +21,7 @@ import munit.TestOptions class IORuntimeBuilderSuite extends BaseSuite with DetectPlatform { - test(("configure the failure reporter": TestOptions).ignoreNative) { + testUnit(("configure the failure reporter": TestOptions).ignoreNative) { var invoked = false val rt = IORuntime.builder().setFailureReporter(_ => invoked = true).build() rt.compute.reportFailure(new Exception) diff --git a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSuite.scala b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSuite.scala index 8dc15f1e98..aaf2c380a1 100644 --- a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeConfigSuite.scala @@ -19,7 +19,7 @@ package unsafe class IORuntimeConfigSuite extends BaseSuite { - test("Reject invalid values of cancelation check- and auto yield threshold") { + testUnit("Reject invalid values of cancelation check- and auto yield threshold") { intercept[IllegalArgumentException]( IORuntimeConfig(cancelationCheckThreshold = -1, autoYieldThreshold = -1)) intercept[IllegalArgumentException]( @@ -37,14 +37,16 @@ class IORuntimeConfigSuite extends BaseSuite { IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 3) IORuntimeConfig(cancelationCheckThreshold = 2, autoYieldThreshold = 2) IORuntimeConfig(cancelationCheckThreshold = 2, autoYieldThreshold = 4) + () } - test("Reject invalid values even in the copy method") { + testUnit("Reject invalid values even in the copy method") { val cfg = IORuntimeConfig(cancelationCheckThreshold = 1, autoYieldThreshold = 2) intercept[IllegalArgumentException](cfg.copy(cancelationCheckThreshold = 0)) intercept[IllegalArgumentException](cfg.copy(cancelationCheckThreshold = -1)) intercept[IllegalArgumentException](cfg.copy(autoYieldThreshold = 1)) intercept[IllegalArgumentException]( cfg.copy(cancelationCheckThreshold = 2, autoYieldThreshold = 3)) + () } } diff --git a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSuite.scala b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSuite.scala index eb2906b194..f790517f76 100644 --- a/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/unsafe/IORuntimeSuite.scala @@ -20,7 +20,7 @@ import cats.effect.BaseSuite class IORuntimeSuite extends BaseSuite { - test("cleanup allRuntimes collection on shutdown") { + testUnit("cleanup allRuntimes collection on shutdown") { val (defaultScheduler, closeScheduler) = Scheduler.createDefaultScheduler() val runtime = IORuntime(null, null, defaultScheduler, closeScheduler, IORuntimeConfig()) From e4380d796eb7857db3ec808a9f7b2f3539d77ec0 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 2 Jan 2025 12:29:41 +0200 Subject: [PATCH 12/20] Add `tickedProperty` --- .../scala/cats/effect/IOPlatformSuite.scala | 10 +- .../effect/unsafe/JSArrayQueueSuite.scala | 4 +- .../scala/cats/effect/IOPlatformSuite.scala | 15 +- .../scala/cats/effect/ResourceJVMSuite.scala | 5 +- .../scala/cats/effect/IOPlatformSuite.scala | 2 +- .../test/scala/cats/effect/BaseSuite.scala | 10 +- .../src/test/scala/cats/effect/IOSuite.scala | 65 ++++---- .../test/scala/cats/effect/MemoizeSuite.scala | 11 +- .../scala/cats/effect/ResourceSuite.scala | 141 +++++++++--------- .../src/test/scala/cats/effect/Runners.scala | 7 +- .../test/scala/cats/effect/SyncIOSuite.scala | 10 +- 11 files changed, 154 insertions(+), 126 deletions(-) diff --git a/tests/js/src/test/scala/cats/effect/IOPlatformSuite.scala b/tests/js/src/test/scala/cats/effect/IOPlatformSuite.scala index 30f92f8809..776d6ef5d0 100644 --- a/tests/js/src/test/scala/cats/effect/IOPlatformSuite.scala +++ b/tests/js/src/test/scala/cats/effect/IOPlatformSuite.scala @@ -22,22 +22,22 @@ import org.scalacheck.Prop.forAll import scala.scalajs.js -trait IOPlatformSuite { self: BaseSuite with munit.ScalaCheckSuite => +trait IOPlatformSuite { self: BaseScalaCheckSuite => def platformTests() = { - ticked("round trip through js.Promise".ignore) { implicit ticker => + tickedProperty("round trip through js.Promise".ignore) { implicit ticker => forAll { (ioa: IO[Int]) => - ioa.eqv(IO.fromPromise(IO(ioa.unsafeToPromise()))) + assertEqv(ioa, IO.fromPromise(IO(ioa.unsafeToPromise()))) } // "callback scheduling gets in the way here since Promise doesn't use TestContext" } - ticked("round trip through js.Promise via Async".ignore) { implicit ticker => + tickedProperty("round trip through js.Promise via Async".ignore) { implicit ticker => def lossy[F[_]: Async, A](fa: F[A])(f: F[A] => js.Promise[A]): F[A] = Async[F].fromPromise(Sync[F].delay(f(fa))).map(x => x) forAll { (ioa: IO[Int]) => - ioa.eqv(lossy(ioa)(_.unsafeToPromise())) + assertEqv(ioa, lossy(ioa)(_.unsafeToPromise())) } // "callback scheduling gets in the way here since Promise doesn't use TestContext" } diff --git a/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala b/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala index 8b7ac91f00..6f154c13fd 100644 --- a/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala +++ b/tests/js/src/test/scala/cats/effect/unsafe/JSArrayQueueSuite.scala @@ -21,9 +21,7 @@ import org.scalacheck.Prop.forAll import scala.collection.mutable.{ListBuffer, Queue} -import munit.ScalaCheckSuite - -class JSArrayQueueSuite extends BaseSuite with ScalaCheckSuite { +class JSArrayQueueSuite extends BaseScalaCheckSuite { property("be fifo") { forAll { (stuff: List[Option[Int]]) => diff --git a/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala b/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala index 132488abb0..3cc374be86 100644 --- a/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala @@ -44,7 +44,7 @@ import java.util.concurrent.{ import java.util.concurrent.atomic.{AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference} trait IOPlatformSuite extends DetectPlatform { - self: BaseSuite with munit.ScalaCheckSuite => + self: BaseScalaCheckSuite => def platformTests() = { real("shift delay evaluation within evalOn") { @@ -120,11 +120,14 @@ trait IOPlatformSuite extends DetectPlatform { task.replicateA(100) } - ticked("round trip non-canceled through j.u.c.CompletableFuture") { implicit ticker => - forAll { (ioa: IO[Int]) => - val normalized = ioa.onCancel(IO.never) - normalized.eqv(IO.fromCompletableFuture(IO(normalized.unsafeToCompletableFuture()))) - } + tickedProperty("round trip non-canceled through j.u.c.CompletableFuture") { + implicit ticker => + forAll { (ioa: IO[Int]) => + val normalized = ioa.onCancel(IO.never) + assertEqv( + normalized, + IO.fromCompletableFuture(IO(normalized.unsafeToCompletableFuture()))) + } } ticked("canceled through j.u.c.CompletableFuture is errored") { implicit ticker => diff --git a/tests/jvm/src/test/scala/cats/effect/ResourceJVMSuite.scala b/tests/jvm/src/test/scala/cats/effect/ResourceJVMSuite.scala index 71eee9c547..b501d098d2 100644 --- a/tests/jvm/src/test/scala/cats/effect/ResourceJVMSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/ResourceJVMSuite.scala @@ -17,7 +17,6 @@ package cats.effect import cats.arrow.FunctionK -import cats.syntax.eq._ class ResourceJVMSuite extends BaseSuite { @@ -50,7 +49,7 @@ class ResourceJVMSuite extends BaseSuite { r.flatMap(_ => Resource.eval(IO.unit)) } .use_ - r eqv IO.unit + assertEqv(r, IO.unit) } real("verify use is stack-safe over binds - 2") { @@ -79,7 +78,7 @@ class ResourceJVMSuite extends BaseSuite { .mapK(FunctionK.id) .use_ - r eqv IO.unit + assertEqv(r, IO.unit) } ticked("verify attempt is stack-safe over binds") { implicit ticker => diff --git a/tests/native/src/test/scala/cats/effect/IOPlatformSuite.scala b/tests/native/src/test/scala/cats/effect/IOPlatformSuite.scala index bc77537401..61f4b70d17 100644 --- a/tests/native/src/test/scala/cats/effect/IOPlatformSuite.scala +++ b/tests/native/src/test/scala/cats/effect/IOPlatformSuite.scala @@ -16,7 +16,7 @@ package cats.effect -trait IOPlatformSuite { self: BaseSuite with munit.ScalaCheckSuite => +trait IOPlatformSuite { self: BaseScalaCheckSuite => def platformTests() = { ticked("realTimeInstant should return an Instant constructed from realTime") { diff --git a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala index e2708b3333..625b7300ac 100644 --- a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala @@ -16,7 +16,9 @@ package cats.effect -import munit.{FunSuite, Location, TestOptions} +import cats.effect.testkit.TestContext +import munit.{FunSuite, Location, ScalaCheckSuite, TestOptions} +import org.scalacheck.Prop trait BaseSuite extends FunSuite with Runners { @@ -43,3 +45,9 @@ trait BaseSuite extends FunSuite with Runners { ) } + +trait BaseScalaCheckSuite extends BaseSuite with ScalaCheckSuite { + @annotation.nowarn("cat=deprecation") + def tickedProperty(options: TestOptions)(body: Ticker => Prop)(implicit loc: Location): Unit = + test(options)(body(Ticker(TestContext()))) +} diff --git a/tests/shared/src/test/scala/cats/effect/IOSuite.scala b/tests/shared/src/test/scala/cats/effect/IOSuite.scala index 07dd1c5fd6..3813a79134 100644 --- a/tests/shared/src/test/scala/cats/effect/IOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IOSuite.scala @@ -31,11 +31,11 @@ import org.scalacheck.Prop import scala.concurrent.{CancellationException, ExecutionContext, Promise, TimeoutException} import scala.concurrent.duration._ -import munit.{DisciplineSuite, ScalaCheckSuite} +import munit.DisciplineSuite import Prop.forAll -class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with IOPlatformSuite { +class IOSuite extends BaseScalaCheckSuite with DisciplineSuite with IOPlatformSuite { ticked("free monad - produce a pure value when run") { implicit ticker => assertCompleteAs(IO.pure(42), 42) @@ -132,43 +132,45 @@ class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with I TestException2) } - ticked("error handling - attempt is redeem with Left(_) for recover and Right(_) for map") { - implicit ticker => forAll { (io: IO[Int]) => io.attempt eqv io.redeem(Left(_), Right(_)) } + tickedProperty( + "error handling - attempt is redeem with Left(_) for recover and Right(_) for map") { + implicit ticker => + forAll { (io: IO[Int]) => assertEqv(io.attempt, io.redeem(Left(_), Right(_))) } } - ticked("error handling - attempt is flattened redeemWith") { implicit ticker => + tickedProperty("error handling - attempt is flattened redeemWith") { implicit ticker => forAll { (io: IO[Int], recover: Throwable => IO[String], bind: Int => IO[String]) => - io.attempt.flatMap(_.fold(recover, bind)) eqv io.redeemWith(recover, bind) + assertEqv(io.attempt.flatMap(_.fold(recover, bind)), io.redeemWith(recover, bind)) } } - ticked("error handling - attemptTap(f) is an alias for attempt.flatTap(f).rethrow") { + tickedProperty("error handling - attemptTap(f) is an alias for attempt.flatTap(f).rethrow") { implicit ticker => forAll { (io: IO[Int], f: Either[Throwable, Int] => IO[Int]) => - io.attemptTap(f) eqv io.attempt.flatTap(f).rethrow + assertEqv(io.attemptTap(f), io.attempt.flatTap(f).rethrow) } } - ticked("error handling - rethrow is inverse of attempt") { implicit ticker => - forAll { (io: IO[Int]) => io.attempt.rethrow eqv io } + tickedProperty("error handling - rethrow is inverse of attempt") { implicit ticker => + forAll { (io: IO[Int]) => assertEqv(io.attempt.rethrow, io) } } - ticked("error handling - redeem is flattened redeemWith") { implicit ticker => + tickedProperty("error handling - redeem is flattened redeemWith") { implicit ticker => forAll { (io: IO[Int], recover: Throwable => IO[String], bind: Int => IO[String]) => - io.redeem(recover, bind).flatten eqv io.redeemWith(recover, bind) + assertEqv(io.redeem(recover, bind).flatten, io.redeemWith(recover, bind)) } } - ticked("error handling - redeem subsumes handleError") { implicit ticker => + tickedProperty("error handling - redeem subsumes handleError") { implicit ticker => forAll { (io: IO[Int], recover: Throwable => Int) => // we have to workaround functor law weirdness here... again... sigh... because of self-cancelation - io.redeem(recover, identity).flatMap(IO.pure(_)) eqv io.handleError(recover) + assertEqv(io.redeem(recover, identity).flatMap(IO.pure(_)), io.handleError(recover)) } } - ticked("error handling - redeemWith subsumes handleErrorWith") { implicit ticker => + tickedProperty("error handling - redeemWith subsumes handleErrorWith") { implicit ticker => forAll { (io: IO[Int], recover: Throwable => IO[Int]) => - io.redeemWith(recover, IO.pure) eqv io.handleErrorWith(recover) + assertEqv(io.redeemWith(recover, IO.pure), io.handleErrorWith(recover)) } } @@ -1800,23 +1802,26 @@ class IOSuite extends BaseSuite with DisciplineSuite with ScalaCheckSuite with I assertFailAs(tsk, TestException) } - ticked("miscellaneous - round trip non-canceled through s.c.Future") { implicit ticker => - forAll { (ioa: IO[Int]) => - val normalized = ioa.onCancel(IO.never) - normalized eqv IO.fromFuture(IO(normalized.unsafeToFuture())) - } + tickedProperty("miscellaneous - round trip non-canceled through s.c.Future") { + implicit ticker => + forAll { (ioa: IO[Int]) => + val normalized = ioa.onCancel(IO.never) + assertEqv(normalized, IO.fromFuture(IO(normalized.unsafeToFuture()))) + } } - ticked("miscellaneous - round trip cancelable through s.c.Future") { implicit ticker => - forAll { (ioa: IO[Int]) => - ioa eqv IO - .fromFutureCancelable( - IO(ioa.unsafeToFutureCancelable()).map { - case (fut, fin) => (fut, IO.fromFuture(IO(fin()))) - } + tickedProperty("miscellaneous - round trip cancelable through s.c.Future") { + implicit ticker => + forAll { (ioa: IO[Int]) => + assertEqv( + ioa, + IO.fromFutureCancelable( + IO(ioa.unsafeToFutureCancelable()).map { + case (fut, fin) => (fut, IO.fromFuture(IO(fin()))) + } + ).recoverWith { case _: CancellationException => IO.canceled *> IO.never[Int] } ) - .recoverWith { case _: CancellationException => IO.canceled *> IO.never[Int] } - } + } } ticked("miscellaneous - canceled through s.c.Future is errored") { implicit ticker => diff --git a/tests/shared/src/test/scala/cats/effect/MemoizeSuite.scala b/tests/shared/src/test/scala/cats/effect/MemoizeSuite.scala index 1fec65b9d6..4664f0fb5c 100644 --- a/tests/shared/src/test/scala/cats/effect/MemoizeSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/MemoizeSuite.scala @@ -33,7 +33,7 @@ import munit.DisciplineSuite import Prop.forAll -class MemoizeSuite extends BaseSuite with DisciplineSuite { +class MemoizeSuite extends BaseScalaCheckSuite with DisciplineSuite { def tests[F[_]: Concurrent: LiftIO](name: String, lowerK: F ~> IO) = { @@ -103,14 +103,17 @@ class MemoizeSuite extends BaseSuite with DisciplineSuite { assertEquals(result.value, Some(Success((1, 1)))) } - ticked(s"$name Concurrent.memoize and then flatten is identity") { implicit ticker => - forAll { (fa: IO[Int]) => lowerK(Concurrent[F].memoize(liftK(fa)).flatten) eqv fa } + tickedProperty(s"$name Concurrent.memoize and then flatten is identity") { + implicit ticker => + forAll { (fa: IO[Int]) => + assertEqv(lowerK(Concurrent[F].memoize(liftK(fa)).flatten), fa) + } } ticked(s"$name Concurrent.memoize uncancelable canceled and then flatten is identity") { implicit ticker => val fa = Concurrent[F].uncancelable(_ => Concurrent[F].canceled) - lowerK(Concurrent[F].memoize(fa).flatten) eqv lowerK(fa) + assertEqv(lowerK(Concurrent[F].memoize(fa).flatten), lowerK(fa)) } ticked( diff --git a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala index f29cb9842a..05b8bd42d6 100644 --- a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala @@ -16,7 +16,7 @@ package cats.effect -import cats.{~>, SemigroupK} +import cats.{SemigroupK, Show, ~>} import cats.data.{Kleisli, OptionT} import cats.effect.implicits._ import cats.effect.kernel.testkit.TestContext @@ -26,20 +26,20 @@ import cats.kernel.laws.discipline.MonoidTests import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.syntax.all._ - -import org.scalacheck.{Cogen, Prop} +import org.scalacheck.Cogen import org.scalacheck.Prop.forAll import scala.concurrent.ExecutionContext import scala.concurrent.duration._ - import java.util.concurrent.atomic.AtomicBoolean import munit.DisciplineSuite -class ResourceSuite extends BaseSuite with DisciplineSuite { +class ResourceSuite extends BaseScalaCheckSuite with DisciplineSuite { - ticked("releases resources in reverse order of acquisition") { implicit ticker => + private implicit def resourceShow[A]: Show[Resource[IO, A]] = Show.fromToString + + tickedProperty("releases resources in reverse order of acquisition") { implicit ticker => forAll { (as: List[(Int, Either[Throwable, Unit])]) => var released: List[Int] = Nil val r = as.traverse { @@ -148,8 +148,8 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { ) } - ticked("eval") { implicit ticker => - forAll { (fa: IO[String]) => Resource.eval(fa).use(IO.pure) eqv fa } + tickedProperty("eval") { implicit ticker => + forAll { (fa: IO[String]) => assertEqv(Resource.eval(fa).use(IO.pure), fa) } } ticked("eval - interruption") { implicit ticker => @@ -172,14 +172,16 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assertCompleteAs(p, 1) } - ticked("eval(fa) <-> liftK.apply(fa)") { implicit ticker => + tickedProperty("eval(fa) <-> liftK.apply(fa)") { implicit ticker => forAll { (fa: IO[String], f: String => IO[Int]) => - Resource.eval(fa).use(f) eqv Resource.liftK[IO].apply(fa).use(f) + assertEqv(Resource.eval(fa).use(f), Resource.liftK[IO].apply(fa).use(f)) } } - ticked("evalMap") { implicit ticker => - forAll { (f: Int => IO[Int]) => Resource.eval(IO(0)).evalMap(f).use(IO.pure) eqv f(0) } + tickedProperty("evalMap") { implicit ticker => + forAll { (f: Int => IO[Int]) => + assertEqv(Resource.eval(IO(0)).evalMap(f).use(IO.pure), f(0)) + } } ticked("evalMap with error fails during use") { implicit ticker => @@ -190,9 +192,9 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { Foo) } - ticked("evalTap") { implicit ticker => + tickedProperty("evalTap") { implicit ticker => forAll { (f: Int => IO[Int]) => - Resource.eval(IO(0)).evalTap(f).use(IO.pure) eqv f(0).as(0) + assertEqv(Resource.eval(IO(0)).evalTap(f).use(IO.pure), f(0).as(0)) } } @@ -229,16 +231,17 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { } } - ticked("allocated releases resources in reverse order of acquisition") { implicit ticker => - forAll { (as: List[(Int, Either[Throwable, Unit])]) => - var released: List[Int] = Nil - val r = as.traverse { - case (a, e) => - Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) + tickedProperty("allocated releases resources in reverse order of acquisition") { + implicit ticker => + forAll { (as: List[(Int, Either[Throwable, Unit])]) => + var released: List[Int] = Nil + val r = as.traverse { + case (a, e) => + Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) + } + assertCompleteAs(r.allocated.flatMap(_._2).attempt.void, ()) + assertEquals(released, as.map(_._1)) } - assertCompleteAs(r.allocated.flatMap(_._2).attempt.void, ()) - assertEquals(released, as.map(_._1)) - } } ticked("allocated does not release until close is invoked") { implicit ticker => @@ -260,12 +263,12 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assertCompleteAs(prog, ()) } - ticked("mapK") { implicit ticker => + tickedProperty("mapK") { implicit ticker => forAll { (fa: Kleisli[IO, Int, Int]) => val runWithTwo = new ~>[Kleisli[IO, Int, *], IO] { override def apply[A](fa: Kleisli[IO, Int, A]): IO[A] = fa(2) } - Resource.eval(fa).mapK(runWithTwo).use(IO.pure) eqv fa(2) + assertEqv(Resource.eval(fa).mapK(runWithTwo).use(IO.pure), fa(2)) } } @@ -340,16 +343,16 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assertCompleteAs(fa, false -> true) } - ticked("allocated produces the same value as the resource") { implicit ticker => + tickedProperty("allocated produces the same value as the resource") { implicit ticker => forAll { (resource: Resource[IO, Int]) => val a0 = IO.uncancelable { p => p(resource.allocated).flatMap { case (b, fin) => fin.as(b) } } val a1 = resource.use(IO.pure) - a0.flatMap(IO.pure).handleErrorWith(IO.raiseError) eqv a1 - .flatMap(IO.pure) - .handleErrorWith(IO.raiseError) + assertEqv( + a0.flatMap(IO.pure).handleErrorWith(IO.raiseError), + a1.flatMap(IO.pure).handleErrorWith(IO.raiseError)) } } @@ -392,7 +395,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { r.flatMap(_ => Resource.eval(IO.unit)) } .use_ - r eqv IO.unit + assertEqv(r, IO.unit) } real("use is stack-safe over binds - 2") { @@ -424,7 +427,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { } .use_ - r eqv IO.unit + assertEqv(r, IO.unit) } ticked("attempt is stack-safe over binds") { implicit ticker => @@ -444,22 +447,23 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assertFailAs(suspend.use_, exception) } - ticked("both - releases resources in reverse order of acquisition") { implicit ticker => - // conceptually asserts that: - // forAll (r: Resource[F, A]) then r <-> r.both(Resource.unit) <-> Resource.unit.both(r) - // needs to be tested manually to assert the equivalence during cleanup as well - forAll { (as: List[(Int, Either[Throwable, Unit])], rhs: Boolean) => - var released: List[Int] = Nil - val r = as.traverse { - case (a, e) => - Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) - } - val unit = ().pure[Resource[IO, *]] - val p = if (rhs) r.both(unit) else unit.both(r) + tickedProperty("both - releases resources in reverse order of acquisition") { + implicit ticker => + // conceptually asserts that: + // forAll (r: Resource[F, A]) then r <-> r.both(Resource.unit) <-> Resource.unit.both(r) + // needs to be tested manually to assert the equivalence during cleanup as well + forAll { (as: List[(Int, Either[Throwable, Unit])], rhs: Boolean) => + var released: List[Int] = Nil + val r = as.traverse { + case (a, e) => + Resource.make(IO(a))(a => IO { released = a :: released } *> IO.fromEither(e)) + } + val unit = ().pure[Resource[IO, *]] + val p = if (rhs) r.both(unit) else unit.both(r) - assertCompleteAs(p.use_.attempt.void, ()) - assertEquals(released, as.map(_._1)) - } + assertCompleteAs(p.use_.attempt.void, ()) + assertEquals(released, as.map(_._1)) + } } ticked("both - parallel acquisition and release") { implicit ticker => @@ -716,21 +720,22 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assertEquals(released, acquired) } - ticked("combineK - behave like orElse when underlying effect does") { implicit ticker => - Prop.forAll { (r1: Resource[IO, Int], r2: Resource[IO, Int]) => - val lhs = r1.orElse(r2) - val rhs = r1 <+> r2 + tickedProperty("combineK - behave like orElse when underlying effect does") { + implicit ticker => + forAll { (r1: Resource[IO, Int], r2: Resource[IO, Int]) => + val lhs = r1.orElse(r2) + val rhs = r1 <+> r2 - lhs eqv rhs - } + assertEqv(lhs, rhs) + } } - ticked("combineK - behave like underlying effect") { implicit ticker => + tickedProperty("combineK - behave like underlying effect") { implicit ticker => forAll { (ot1: OptionT[IO, Int], ot2: OptionT[IO, Int]) => val lhs = Resource.eval(ot1 <+> ot2).use(OptionT.pure[IO](_)).value val rhs = (Resource.eval(ot1) <+> Resource.eval(ot2)).use(OptionT.pure[IO](_)).value - lhs eqv rhs + assertEqv(lhs, rhs) } } @@ -800,7 +805,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { val surroundee = IO("hello") val surrounded = r.surround(surroundee) - surrounded eqv surroundee + assertEqv(surrounded, surroundee) } ticked( @@ -811,15 +816,16 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { val surround = r.surroundK val surrounded = surround(surroundee) - surrounded eqv surroundee + assertEqv(surrounded, surroundee) } - ticked("evalOn - run acquire and release on provided ExecutionContext") { implicit ticker => - forAll { (executionContext: ExecutionContext) => - val assertion = - IO.executionContext.flatMap(ec => IO(assertEquals(ec, executionContext)).void) - Resource.make(assertion)(_ => assertion).evalOn(executionContext).use_.as(true) - } + tickedProperty("evalOn - run acquire and release on provided ExecutionContext") { + implicit ticker => + forAll { (executionContext: ExecutionContext) => + val assertion = + IO.executionContext.flatMap(ec => IO(assertEquals(ec, executionContext)).void) + Resource.make(assertion)(_ => assertion).evalOn(executionContext).use_.as(true) + } } { @@ -1119,8 +1125,8 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { assert(rightReleased) } - ticked("Concurrent[Resource] - memoize - memoize and then flatten is identity") { - implicit ticker => forAll { (r: Resource[IO, Int]) => r.memoize.flatten eqv r } + tickedProperty("Concurrent[Resource] - memoize - memoize and then flatten is identity") { + implicit ticker => forAll { (r: Resource[IO, Int]) => assertEqv(r.memoize.flatten, r) } } ticked("Concurrent[Resource] - memoize - allocates once and releases at end") { implicit ticker => @@ -1253,7 +1259,8 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { } .uncancelable .use_ - r eqv IO.unit + + assertEqv(r, IO.unit) } ticked("allocatedCase - is stack-safe over binds") { implicit ticker => @@ -1265,8 +1272,8 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { } .allocatedCase .map(_._1) - r eqv IO.unit + assertEqv(r, IO.unit) } ticked("Resource[Resource[IO, *], *] - flatten with finalizers inside-out") { @@ -1290,7 +1297,7 @@ class ResourceSuite extends BaseSuite with DisciplineSuite { } } - r.use_ eqv IO.unit + assertEqv(r.use_, IO.unit) } { diff --git a/tests/shared/src/test/scala/cats/effect/Runners.scala b/tests/shared/src/test/scala/cats/effect/Runners.scala index 2c94cd389d..f60e3286e3 100644 --- a/tests/shared/src/test/scala/cats/effect/Runners.scala +++ b/tests/shared/src/test/scala/cats/effect/Runners.scala @@ -36,7 +36,7 @@ trait Runners extends TestInstances with RunnersPlatform { def executionTimeout: FiniteDuration = 20.seconds override def munitTimeout: Duration = executionTimeout - def ticked(options: TestOptions)(body: Ticker => Any)(implicit loc: Location): Unit = + def ticked(options: TestOptions)(body: Ticker => Unit)(implicit loc: Location): Unit = test(options)(body(Ticker(TestContext()))) def real[A](options: TestOptions)(body: => IO[A])(implicit loc: Location): Unit = @@ -63,6 +63,11 @@ trait Runners extends TestInstances with RunnersPlatform { timeout(fut, cancel, executionTimeout) } + def assertEqv[A: Eq: Show](obtained: A, expected: A)(implicit loc: Location): Unit = { + implicit val comp: munit.Compare[A, A] = (l, r) => Eq[A].eqv(l, r) + assertEquals(obtained, expected, show"$obtained !== $expected") + } + def assertCompleteAs[A: Eq: Show](ioa: IO[A], expected: A)( implicit ticker: Ticker, loc: Location): Unit = diff --git a/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala b/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala index a5d542ee17..7897c5034f 100644 --- a/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/SyncIOSuite.scala @@ -73,32 +73,32 @@ class SyncIOSuite extends BaseSuite with DisciplineSuite with SyncIOPlatformSuit } property("attempt is redeem with Left(_) for recover and Right(_) for map") { - forAll { (io: SyncIO[Int]) => io.attempt eqv io.redeem(Left(_), Right(_)) } + forAll { (io: SyncIO[Int]) => assertEqv(io.attempt, io.redeem(Left(_), Right(_))) } } property("attempt is flattened redeemWith") { forAll { (io: SyncIO[Int], recover: Throwable => SyncIO[String], bind: Int => SyncIO[String]) => - io.attempt.flatMap(_.fold(recover, bind)) eqv io.redeemWith(recover, bind) + assertEqv(io.attempt.flatMap(_.fold(recover, bind)), io.redeemWith(recover, bind)) } } property("redeem is flattened redeemWith") { forAll { (io: SyncIO[Int], recover: Throwable => SyncIO[String], bind: Int => SyncIO[String]) => - io.redeem(recover, bind).flatMap(identity) eqv io.redeemWith(recover, bind) + assertEqv(io.redeem(recover, bind).flatMap(identity), io.redeemWith(recover, bind)) } } property("redeem subsumes handleError") { forAll { (io: SyncIO[Int], recover: Throwable => Int) => - io.redeem(recover, identity) eqv io.handleError(recover) + assertEqv(io.redeem(recover, identity), io.handleError(recover)) } } property("redeemWith subsumes handleErrorWith") { forAll { (io: SyncIO[Int], recover: Throwable => SyncIO[Int]) => - io.redeemWith(recover, SyncIO.pure) eqv io.handleErrorWith(recover) + assertEqv(io.redeemWith(recover, SyncIO.pure), io.handleErrorWith(recover)) } } From 709e87337fda29b7c9e867a8e7d32f0de34fbc6b Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 2 Jan 2025 12:39:49 +0200 Subject: [PATCH 13/20] Run scalafixAll --- tests/shared/src/test/scala/cats/effect/BaseSuite.scala | 4 +++- tests/shared/src/test/scala/cats/effect/ResourceSuite.scala | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala index 625b7300ac..f3b59c652c 100644 --- a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala @@ -17,9 +17,11 @@ package cats.effect import cats.effect.testkit.TestContext -import munit.{FunSuite, Location, ScalaCheckSuite, TestOptions} + import org.scalacheck.Prop +import munit.{FunSuite, Location, ScalaCheckSuite, TestOptions} + trait BaseSuite extends FunSuite with Runners { @deprecated("Please use a type safe alternative, such as 'real' or 'ticked'", "3.6.0") diff --git a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala index 05b8bd42d6..741749ee6f 100644 --- a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala @@ -16,7 +16,7 @@ package cats.effect -import cats.{SemigroupK, Show, ~>} +import cats.{~>, SemigroupK, Show} import cats.data.{Kleisli, OptionT} import cats.effect.implicits._ import cats.effect.kernel.testkit.TestContext @@ -26,11 +26,13 @@ import cats.kernel.laws.discipline.MonoidTests import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.syntax.all._ + import org.scalacheck.Cogen import org.scalacheck.Prop.forAll import scala.concurrent.ExecutionContext import scala.concurrent.duration._ + import java.util.concurrent.atomic.AtomicBoolean import munit.DisciplineSuite From 9186d4bb7f7ba2fa2681c85181e7904264c70137 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 2 Jan 2025 13:33:53 +0200 Subject: [PATCH 14/20] Fix non-evaluated tests --- .../cats/effect/std/ConsoleJSSuite.scala | 4 +- .../cats/effect/unsafe/SchedulerSuite.scala | 4 +- .../scala/cats/effect/IOPlatformSuite.scala | 5 +- .../scala/cats/effect/SelectorSuite.scala | 4 +- .../effect/kernel/AsyncPlatformSuite.scala | 2 +- .../effect/unsafe/HelperThreadParkSuite.scala | 2 +- .../effect/FileDescriptorPollerSuite.scala | 7 ++- .../cats/effect/unsafe/SchedulerSuite.scala | 2 +- .../test/scala/cats/effect/BaseSuite.scala | 23 ++++++--- .../src/test/scala/cats/effect/IOSuite.scala | 4 +- .../test/scala/cats/effect/IorTIOSuite.scala | 2 +- .../scala/cats/effect/ResourceSuite.scala | 2 +- .../src/test/scala/cats/effect/Runners.scala | 11 +++-- .../scala/cats/effect/kernel/AsyncSuite.scala | 6 +-- .../cats/effect/kernel/DeferredSuite.scala | 23 ++++----- .../cats/effect/std/DispatcherSuite.scala | 8 ++-- .../scala/cats/effect/std/MutexSuite.scala | 2 +- .../scala/cats/effect/std/QueueSuite.scala | 12 ++--- .../scala/cats/effect/std/RandomSuite.scala | 48 +++++++++---------- .../cats/effect/std/SecureRandomSuite.scala | 4 +- .../cats/effect/std/SupervisorSuite.scala | 4 +- .../scala/cats/effect/std/UUIDGenSuite.scala | 2 +- .../cats/effect/tracing/TraceSuite.scala | 15 +++--- 23 files changed, 104 insertions(+), 92 deletions(-) diff --git a/tests/js/src/test/scala/cats/effect/std/ConsoleJSSuite.scala b/tests/js/src/test/scala/cats/effect/std/ConsoleJSSuite.scala index f55245e43e..0fc34b7298 100644 --- a/tests/js/src/test/scala/cats/effect/std/ConsoleJSSuite.scala +++ b/tests/js/src/test/scala/cats/effect/std/ConsoleJSSuite.scala @@ -20,10 +20,10 @@ package std class ConsoleJSSuite extends BaseSuite { real("work in any JS environment") { - Console[IO].println("printing") *> Console[IO].errorln("erroring") *> IO(true) + Console[IO].println("printing") *> Console[IO].errorln("erroring") } real("println should not hang for large strings") { - Console[IO].println("foo" * 10000).as(true) + Console[IO].println("foo" * 10000) } } diff --git a/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala b/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala index 7a5b3e7fbd..8bffdde058 100644 --- a/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala +++ b/tests/js/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala @@ -39,7 +39,7 @@ class SchedulerSuite extends BaseSuite { start <- IO.realTime times <- IO.realTime.replicateA(100) deltas = times.map(_ - start) - } yield deltas.exists(_.toMicros % 1000 != 0) + } yield assert(deltas.exists(_.toMicros % 1000 != 0)) } real("correctly calculate real time") { IO.realTime.product(IO(System.currentTimeMillis())).map { @@ -56,7 +56,7 @@ class SchedulerSuite extends BaseSuite { _ <- IO(cancel.run()) _ <- IO.sleep(200.millis) didItCancel <- IO(ref.get()) - } yield didItCancel + } yield assert(didItCancel) } } diff --git a/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala b/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala index 3cc374be86..5d67bd9726 100644 --- a/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/IOPlatformSuite.scala @@ -104,7 +104,7 @@ trait IOPlatformSuite extends DetectPlatform { real("start 1000 fibers in series and await them all") { val input = (0 until 1000).toList - val ioa = input.traverse(i => IO.pure(i).start.flatMap(_.join)) + val ioa = input.traverse_(i => IO.pure(i).start.flatMap(_.join)) ioa } @@ -117,7 +117,7 @@ trait IOPlatformSuite extends DetectPlatform { } } - task.replicateA(100) + task.replicateA_(100) } tickedProperty("round trip non-canceled through j.u.c.CompletableFuture") { @@ -562,6 +562,7 @@ trait IOPlatformSuite extends DetectPlatform { .invoke(Thread.currentThread()) .asInstanceOf[Boolean] }.evalOn(loomEc) + .map(assert(_)) } // else // "block in-place on virtual threads" in skipped("virtual threads not supported") diff --git a/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala b/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala index 20a225b903..1378218435 100644 --- a/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/SelectorSuite.scala @@ -84,8 +84,8 @@ class SelectorSuite extends BaseSuite { // get off the wstp to test async codepaths IO.blocking(()) *> getSelector.flatMap { selector => selector.select(pipe.sink, OP_READ).attempt.map { - case Left(_: IllegalArgumentException) => true - case _ => false + case Left(_: IllegalArgumentException) => () + case other => fail(s"Expected Left(IllegalArgumentException), got $other") } } } diff --git a/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSuite.scala b/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSuite.scala index 86c2717d23..8be5e37a6e 100644 --- a/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/kernel/AsyncPlatformSuite.scala @@ -65,6 +65,6 @@ class AsyncPlatformSuite extends BaseSuite { .as(false) .recover { case _: TestControl.NonTerminationException => true } .replicateA(1000) - .map(_.forall(identity(_))) + .map(r => assert(r.forall(identity(_)))) } } diff --git a/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSuite.scala b/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSuite.scala index 01cae0daba..9146c81510 100644 --- a/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/unsafe/HelperThreadParkSuite.scala @@ -61,7 +61,7 @@ class HelperThreadParkSuite extends BaseSuite { _ <- IO(Await.result(p.future, Duration.Inf)) } yield () - List.fill(10)(io.start).sequence.flatMap(_.traverse(_.joinWithNever)).evalOn(rt.compute) + List.fill(10)(io.start).sequence.flatMap(_.traverse_(_.joinWithNever)).evalOn(rt.compute) } } } diff --git a/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSuite.scala b/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSuite.scala index 42d423d1b2..5f48a70023 100644 --- a/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSuite.scala +++ b/tests/native/src/test/scala/cats/effect/FileDescriptorPollerSuite.scala @@ -123,7 +123,7 @@ class FileDescriptorPollerSuite extends BaseSuite { pipes.foreach { pipe => unistd.write(pipe.writeFd, Array[Byte](42).atUnsafe(0), 1.toULong) } - }.background.surround(latch.await.as(true)) + }.background.surround(latch.await) } } } @@ -135,7 +135,10 @@ class FileDescriptorPollerSuite extends BaseSuite { real("hang if never ready") { mkPipe.use { pipe => - pipe.read(new Array[Byte](1), 0, 1).as(false).timeoutTo(1.second, IO.pure(true)) + pipe + .read(new Array[Byte](1), 0, 1) + .map(_ => fail("shouldn't get there")) + .timeoutTo(1.second, IO.unit) } } diff --git a/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala b/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala index 74c2c6eef8..b34a911fe3 100644 --- a/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala +++ b/tests/native/src/test/scala/cats/effect/unsafe/SchedulerSuite.scala @@ -26,7 +26,7 @@ class SchedulerSuite extends BaseSuite { start <- IO.realTime times <- IO.realTime.replicateA(100) deltas = times.map(_ - start) - } yield deltas.exists(_.toMicros % 1000 != 0) + } yield assert(deltas.exists(_.toMicros % 1000 != 0)) } real("correctly calculate real time") { diff --git a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala index f3b59c652c..8b2d17bff0 100644 --- a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala @@ -24,32 +24,43 @@ import munit.{FunSuite, Location, ScalaCheckSuite, TestOptions} trait BaseSuite extends FunSuite with Runners { - @deprecated("Please use a type safe alternative, such as 'real' or 'ticked'", "3.6.0") + /*@deprecated("Please use a type safe alternative, such as 'real' or 'ticked'", "3.6.0") override def test(name: String)(body: => Any)(implicit loc: Location): Unit = super.test(name)(body) @deprecated("Please use a type safe alternative, such as 'real' or 'ticked'", "3.6.0") override def test(options: TestOptions)(body: => Any)(implicit loc: Location): Unit = - super.test(options)(body) + super.test(options)(body)*/ - @annotation.nowarn("cat=deprecation") def testUnit(name: String)(body: => Unit)(implicit loc: Location): Unit = test(name)(body) - @annotation.nowarn("cat=deprecation") def testUnit(options: TestOptions)(body: => Unit)(implicit loc: Location): Unit = test(options)(body) override def munitValueTransforms: List[ValueTransform] = super.munitValueTransforms ++ List( new ValueTransform("IO", { case _: IO[_] => sys.error("Non-evaluated IO") }), - new ValueTransform("SyncIO", { case _: SyncIO[_] => sys.error("Non-evaluated SyncIO") }) + new ValueTransform("SyncIO", { case _: SyncIO[_] => sys.error("Non-evaluated SyncIO") }), + new ValueTransform( + "Prop", + { + case _: Prop if !munitTestTransforms.exists(_.name == "ScalaCheck Prop") => + sys.error("Non-evaluated Prop.") + } + ), + new ValueTransform( + "Other", + { + case r if !r.isInstanceOf[Unit] && !r.isInstanceOf[Prop] => + sys.error(s"Unexpected value of type ${r.getClass.getName}: $r") + } + ) ) } trait BaseScalaCheckSuite extends BaseSuite with ScalaCheckSuite { - @annotation.nowarn("cat=deprecation") def tickedProperty(options: TestOptions)(body: Ticker => Prop)(implicit loc: Location): Unit = test(options)(body(Ticker(TestContext()))) } diff --git a/tests/shared/src/test/scala/cats/effect/IOSuite.scala b/tests/shared/src/test/scala/cats/effect/IOSuite.scala index 3813a79134..cca069bb1e 100644 --- a/tests/shared/src/test/scala/cats/effect/IOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IOSuite.scala @@ -835,7 +835,7 @@ class IOSuite extends BaseScalaCheckSuite with DisciplineSuite with IOPlatformSu } yield List(r1, r2, r3, r4) test.flatMap { results => - results.traverse { result => + results.traverse_ { result => IO(assert(result.isSuccess)).flatMap { _ => result match { case Outcome.Succeeded(ioa) => @@ -2214,7 +2214,7 @@ class IOSuite extends BaseScalaCheckSuite with DisciplineSuite with IOPlatformSu } real("produce a specialized version of Deferred") { - IO.deferred[Unit].flatMap(d => IO(d.isInstanceOf[IODeferred[_]])) + IO.deferred[Unit].flatMap(d => IO(assert(d.isInstanceOf[IODeferred[_]]))) } platformTests() diff --git a/tests/shared/src/test/scala/cats/effect/IorTIOSuite.scala b/tests/shared/src/test/scala/cats/effect/IorTIOSuite.scala index fa88b3cb58..bf51fc7bae 100644 --- a/tests/shared/src/test/scala/cats/effect/IorTIOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IorTIOSuite.scala @@ -78,7 +78,7 @@ class IorTIOSuite extends BaseSuite with DisciplineSuite { unsafeRun(sbool.value).fold( false, _ => false, - iO => iO.fold(false)(i => i.fold(_ => false, _ => true, (_, _) => false))) + iO => iO.fold(false)(i => i.fold(_ => true, _ => true, (_, _) => false))) ) { diff --git a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala index 741749ee6f..11b313dacc 100644 --- a/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/ResourceSuite.scala @@ -996,7 +996,7 @@ class ResourceSuite extends BaseScalaCheckSuite with DisciplineSuite { } } - TestControl.executeEmbed(go.replicateA_(100)).as(true) + TestControl.executeEmbed(go.replicateA_(100)) } ticked("Async[Resource] - start - runs fibers in parallel") { implicit ticker => diff --git a/tests/shared/src/test/scala/cats/effect/Runners.scala b/tests/shared/src/test/scala/cats/effect/Runners.scala index f60e3286e3..bc7850f436 100644 --- a/tests/shared/src/test/scala/cats/effect/Runners.scala +++ b/tests/shared/src/test/scala/cats/effect/Runners.scala @@ -39,7 +39,7 @@ trait Runners extends TestInstances with RunnersPlatform { def ticked(options: TestOptions)(body: Ticker => Unit)(implicit loc: Location): Unit = test(options)(body(Ticker(TestContext()))) - def real[A](options: TestOptions)(body: => IO[A])(implicit loc: Location): Unit = + def real(options: TestOptions)(body: => IO[Unit])(implicit loc: Location): Unit = test(options) { val (fut, cancel) = body.unsafeToFutureCancelable()(runtime()) timeout(fut, cancel, executionTimeout) @@ -48,14 +48,14 @@ trait Runners extends TestInstances with RunnersPlatform { /* * Hacky implementation of effectful property testing */ - def realProp[A, B](options: TestOptions, gen: Gen[A])(f: A => IO[B])( + def realProp[A](options: TestOptions, gen: Gen[A])(f: A => IO[Unit])( implicit loc: Location): Unit = real(options)(List.range(1, 100).traverse_ { _ => val a = gen.sample.get f(a) }) - def realWithRuntime[A](options: TestOptions)(f: IORuntime => IO[A])( + def realWithRuntime(options: TestOptions)(f: IORuntime => IO[Unit])( implicit loc: Location): Unit = test(options) { val rt = runtime() @@ -115,7 +115,7 @@ trait Runners extends TestInstances with RunnersPlatform { // useful for tests in the `real` context implicit class Assertions[A](fa: IO[A]) { - def mustFailWith[E <: Throwable: ClassTag](implicit loc: Location) = + def mustFailWith[E <: Throwable: ClassTag](implicit loc: Location): IO[Unit] = fa.attempt.flatMap { res => IO { res match { @@ -127,7 +127,8 @@ trait Runners extends TestInstances with RunnersPlatform { } } - def mustEqual(a: A)(implicit loc: Location) = fa.flatMap { res => IO(assertEquals(res, a)) } + def mustEqual(a: A)(implicit loc: Location): IO[Unit] = + fa.flatMap { res => IO(assertEquals(res, a)) } } private def timeout[A]( diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSuite.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSuite.scala index 74c7ed1da5..886001b756 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSuite.scala @@ -66,7 +66,7 @@ class AsyncSuite extends BaseSuite with DisciplineSuite { .as(false) .recover { case _: TestControl.NonTerminationException => true } .replicateA(100) - .map(_.forall(identity(_))) + .map(r => assert(r.forall(identity(_)))) } real("fromFutureCancelable should cancel on fiber cancelation") { @@ -83,7 +83,7 @@ class AsyncSuite extends BaseSuite with DisciplineSuite { res <- IO(assert(canceled.get())) } yield res - TestControl.executeEmbed(go, IORuntimeConfig(1, 2)).replicateA(1000) + TestControl.executeEmbed(go, IORuntimeConfig(1, 2)).replicateA_(1000) } @@ -108,7 +108,7 @@ class AsyncSuite extends BaseSuite with DisciplineSuite { .as(false) .recover { case _: TestControl.NonTerminationException => true } .replicateA(1000) - .map(_.forall(identity(_))) + .map(r => assert(r.forall(identity(_)))) } final class AsyncIO[A](val io: IO[A]) diff --git a/tests/shared/src/test/scala/cats/effect/kernel/DeferredSuite.scala b/tests/shared/src/test/scala/cats/effect/kernel/DeferredSuite.scala index f6fa776107..ec3f5a3a67 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/DeferredSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/DeferredSuite.scala @@ -165,23 +165,20 @@ class DeferredSuite extends BaseSuite with DetectPlatform { outer => IO.race(attemptCompletion(1), attemptCompletion(2)).void, d.get.void ).parSequence - r <- IO { assert(res == List((), ())) } - } yield r + } yield assertEquals(res, List((), ())) } real(s"$name handle lots of canceled gets in parallel") { - List(10, 100, 1000) - .traverse_ { n => - deferredU - .flatMap { d => - (d.get.background.surround(IO.cede).replicateA_(n) *> d - .complete(())).background.surround { - d.get.as(1).parReplicateA(n).map(r => assertEquals(r.sum, n)) - } + List(10, 100, 1000).traverse_ { n => + deferredU + .flatMap { d => + (d.get.background.surround(IO.cede).replicateA_(n) *> d + .complete(())).background.surround { + d.get.as(1).parReplicateA(n).map(r => assertEquals(r.sum, n)) } - .replicateA_(if (isJVM) 100 else 1) - } - .as(true) + } + .replicateA_(if (isJVM) 100 else 1) + } } ticked("handle adversarial cancelations without loss of callbacks") { implicit ticker => diff --git a/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala b/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala index 20ee65a7c6..47d4b07333 100644 --- a/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/DispatcherSuite.scala @@ -39,8 +39,7 @@ class DispatcherSuite extends BaseSuite with DetectPlatform { real("sequential dispatcher (cancelable = false) - await = true - not hang") { D.use(dispatcher => IO(dispatcher.unsafeRunAndForget(IO.unit))) - .replicateA(if (isJS || isNative) 1 else 10000) - .as(true) + .replicateA_(if (isJS || isNative) 1 else 10000) } real( @@ -125,8 +124,7 @@ class DispatcherSuite extends BaseSuite with DetectPlatform { real("sequential dispatcher (cancelable = true) - await = true - not hang") { D.use(dispatcher => IO(dispatcher.unsafeRunAndForget(IO.unit))) - .replicateA(if (isJS || isNative) 1 else 10000) - .as(true) + .replicateA_(if (isJS || isNative) 1 else 10000) } } @@ -427,7 +425,7 @@ class DispatcherSuite extends BaseSuite with DetectPlatform { real(s"$name - raise an error on leaked runner") { dispatcher.use(IO.pure(_)) flatMap { runner => IO { - intercept[IllegalStateException](runner.unsafeRunAndForget(IO(fail("fail")))) + val _ = intercept[IllegalStateException](runner.unsafeRunAndForget(IO(fail("fail")))) } } } diff --git a/tests/shared/src/test/scala/cats/effect/std/MutexSuite.scala b/tests/shared/src/test/scala/cats/effect/std/MutexSuite.scala index 0cf32e89de..c8734e9ff5 100644 --- a/tests/shared/src/test/scala/cats/effect/std/MutexSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/MutexSuite.scala @@ -116,7 +116,7 @@ final class MutexSuite extends BaseSuite with DetectPlatform { } real(s"$name not deadlock when highly contended") { - mutex.flatMap(_.lock.use_.parReplicateA_(10)).replicateA_(10000).as(true) + mutex.flatMap(_.lock.use_.parReplicateA_(10)).replicateA_(10000) } real(s"$name handle cancelled acquire") { diff --git a/tests/shared/src/test/scala/cats/effect/std/QueueSuite.scala b/tests/shared/src/test/scala/cats/effect/std/QueueSuite.scala index 2f78e8b306..4b173a6f4c 100644 --- a/tests/shared/src/test/scala/cats/effect/std/QueueSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/QueueSuite.scala @@ -57,7 +57,7 @@ class BoundedQueueSuite extends BaseSuite with QueueTests[Queue] with DetectPlat } real("BoundedQueue constructor - not OOM") { - Queue.bounded[IO, Unit](Int.MaxValue).as(true) + Queue.bounded[IO, Unit](Int.MaxValue).void } boundedQueueTests("BoundedQueue mapK", Queue.bounded[IO, Int](_).map(_.mapK(FunctionK.id))) @@ -135,7 +135,7 @@ class BoundedQueueSuite extends BaseSuite with QueueTests[Queue] with DetectPlat _ <- IO.race(taker.joinWithNever, q.offer(()).delayBy(500.millis)) } yield () - test.parReplicateA(if (isJS || isNative) 1 else 1000) + test.parReplicateA_(if (isJS || isNative) 1 else 1000) } private def boundedQueueTests(name: String, constructor: Int => IO[Queue[IO, Int]]) = { @@ -591,7 +591,7 @@ trait QueueTests[Q[_[_], _]] { self: BaseSuite => IO.println("did not take any results") } yield () - test.parReplicateA(10) + test.parReplicateA_(10) } real(s"$name - release all offerers when queue is full") { @@ -612,7 +612,7 @@ trait QueueTests[Q[_[_], _]] { self: BaseSuite => _ <- expected.await } yield () - test.parReplicateA(10) + test.parReplicateA_(10) } } @@ -736,7 +736,7 @@ trait QueueTests[Q[_[_], _]] { self: BaseSuite => offer2.join } yield () - test.parReplicateA(16) + test.parReplicateA_(16) } } @@ -887,7 +887,7 @@ trait QueueTests[Q[_[_], _]] { self: BaseSuite => } } yield () - test.parReplicateA(16) + test.parReplicateA_(16) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/RandomSuite.scala b/tests/shared/src/test/scala/cats/effect/std/RandomSuite.scala index 1cc6700a65..35aaa13366 100644 --- a/tests/shared/src/test/scala/cats/effect/std/RandomSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/RandomSuite.scala @@ -48,7 +48,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen randDoubles <- random.betweenDouble(min, max).replicateA(numIterations) - } yield randDoubles.forall(randDouble => randDouble >= min && randDouble <= max) + } yield assert(randDoubles.forall(randDouble => randDouble >= min && randDouble <= max)) } real(s"$name - betweenFloat - generate a random float within a range") { @@ -58,7 +58,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen randFloats <- random.betweenFloat(min, max).replicateA(numIterations) - } yield randFloats.forall(randFloat => randFloat >= min && randFloat <= max) + } yield assert(randFloats.forall(randFloat => randFloat >= min && randFloat <= max)) } real(s"$name - betweenInt - generate a random integer within a range") { @@ -68,7 +68,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen randInts <- random.betweenInt(min, max).replicateA(numIterations) - } yield randInts.forall(randInt => randInt >= min && randInt <= max) + } yield assert(randInts.forall(randInt => randInt >= min && randInt <= max)) } real(s"$name - betweenLong - generate a random long within a range") { @@ -78,7 +78,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen randLongs <- random.betweenLong(min, max).replicateA(numIterations) - } yield randLongs.forall(randLong => randLong >= min && randLong <= max) + } yield assert(randLongs.forall(randLong => randLong >= min && randLong <= max)) } real(s"$name - nextAlphaNumeric - generate random alphanumeric characters") { @@ -87,7 +87,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen randomChars <- random.nextAlphaNumeric.replicateA(numIterations) - } yield randomChars.forall(randomChar => alphaNumeric.contains(randomChar)) + } yield assert(randomChars.forall(randomChar => alphaNumeric.contains(randomChar))) } real(s"$name - nextBoolean - generate random boolean values") { @@ -103,7 +103,7 @@ class RandomSuite extends BaseSuite { bytes1 <- random1.nextBytes(128) random2 <- randomGen bytes2 <- random2.nextBytes(256) - } yield bytes1.length == 128 && bytes2.length == 256 + } yield assert(bytes1.length == 128 && bytes2.length == 256) } real( @@ -113,7 +113,7 @@ class RandomSuite extends BaseSuite { for { bytes1 <- nextBytes bytes2 <- nextBytes - } yield bytes1 ne bytes2 + } yield assert(bytes1 ne bytes2) } } @@ -124,7 +124,7 @@ class RandomSuite extends BaseSuite { nextBytes = random.nextBytes(128) bytes1 <- nextBytes bytes2 <- nextBytes - } yield bytes1 ne bytes2 + } yield assert(bytes1 ne bytes2) } real(s"$name - nextDouble - generate random double values between 0.0 and 1.0") { @@ -132,7 +132,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen randomDoubles <- random.nextDouble.replicateA(numIterations) - } yield randomDoubles.forall(double => double >= 0.0 && double < 1.0) + } yield assert(randomDoubles.forall(double => double >= 0.0 && double < 1.0)) } real(s"$name - nextFloat - generate random float values between 0.0 and 1.0") { @@ -140,7 +140,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen randomFloats <- random.nextFloat.replicateA(numIterations) - } yield randomFloats.forall(float => float >= 0.0f && float < 1.0f) + } yield assert(randomFloats.forall(float => float >= 0.0f && float < 1.0f)) } real( @@ -152,7 +152,7 @@ class RandomSuite extends BaseSuite { mean = gaussians.sum / sampleSize variance = gaussians.map(x => math.pow(x - mean, 2)).sum / sampleSize stddev = math.sqrt(variance) - } yield java.lang.Double.isFinite(mean) && java.lang.Double.isFinite(stddev) + } yield assert(java.lang.Double.isFinite(mean) && java.lang.Double.isFinite(stddev)) } real(s"$name - nextInt - generate random int value") { @@ -168,7 +168,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen randomInts <- random.nextIntBounded(bound).replicateA(numIterations) - } yield randomInts.forall(int => int >= 0 && int < bound) + } yield assert(randomInts.forall(int => int >= 0 && int < bound)) } real(s"$name - nextLong - generate random long value") { @@ -184,7 +184,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen randomLongs <- random.nextLongBounded(bound).replicateA(numIterations) - } yield randomLongs.forall(long => long >= 0L && long < bound) + } yield assert(randomLongs.forall(long => long >= 0L && long < bound)) } real(s"$name - nextPrintableChar - generate random printable characters") { @@ -193,7 +193,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen randomChars <- random.nextPrintableChar.replicateA(numIterations) - } yield randomChars.forall(char => printableChars.contains(char)) + } yield assert(randomChars.forall(char => printableChars.contains(char))) } real(s"$name - nextString - generate a random string with the specified length") { @@ -202,9 +202,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen randomStrings <- random.nextString(length).replicateA(numIterations) - } yield { - randomStrings.forall(_.length == length) - } + } yield assert(randomStrings.forall(_.length == length)) } real(s"$name - shuffleList - shuffle a list") { @@ -214,7 +212,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen shuffled <- random.shuffleList(list) - } yield shuffled != list && shuffled.sorted == list.sorted + } yield assert(shuffled != list && shuffled.sorted == list.sorted) } real(s"$name - shuffleVector - shuffle a vector") { @@ -223,14 +221,14 @@ class RandomSuite extends BaseSuite { for { random <- randomGen shuffled <- random.shuffleVector(vector) - } yield shuffled != vector && shuffled.sorted == vector.sorted + } yield assert(shuffled != vector && shuffled.sorted == vector.sorted) } real(s"$name - oneOf - return the only value provided") { for { random <- randomGen chosen <- random.oneOf(42) - } yield chosen == 42 + } yield assertEquals(chosen, 42) } real(s"$name - oneOf - eventually choose all the given values at least once") { @@ -245,7 +243,7 @@ class RandomSuite extends BaseSuite { ref <- Ref.of[IO, Set[Int]](Set.empty) _ <- chooseAndAccumulate(random, ref).untilM_(haveChosenAllValues(ref)) success <- haveChosenAllValues(ref) - } yield success + } yield assert(success) } real(s"$name - oneOf - not select any value outside the provided list") { @@ -254,7 +252,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen chosenValues <- random.oneOf(list.head, list.tail: _*).replicateA(numIterations) - } yield chosenValues.forall(list.contains) + } yield assert(chosenValues.forall(list.contains)) } elementOfTests[Int, List[Int]]( @@ -309,7 +307,7 @@ class RandomSuite extends BaseSuite { for { random <- randomGen result <- random.elementOf(emptyCollection).attempt - } yield result.isLeft + } yield assert(result.isLeft) } real( @@ -325,7 +323,7 @@ class RandomSuite extends BaseSuite { ref <- Ref.of[IO, Set[A]](Set.empty) _ <- chooseAndAccumulate(random, ref).untilM_(haveChosenAllElements(ref)) success <- haveChosenAllElements(ref) - } yield success + } yield assert(success) } real( @@ -336,7 +334,7 @@ class RandomSuite extends BaseSuite { chosenValues <- random.elementOf(nonEmptyCollection).replicateA(numIterations) } yield { val collectionVector: Vector[A] = nonEmptyCollection.toVector - chosenValues.forall(collectionVector.contains(_)) + assert(chosenValues.forall(collectionVector.contains(_))) } } } diff --git a/tests/shared/src/test/scala/cats/effect/std/SecureRandomSuite.scala b/tests/shared/src/test/scala/cats/effect/std/SecureRandomSuite.scala index 61b4ce5007..086f1adbef 100644 --- a/tests/shared/src/test/scala/cats/effect/std/SecureRandomSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/SecureRandomSuite.scala @@ -26,7 +26,7 @@ class SecureRandomSuite extends BaseSuite { random2 <- SecureRandom.javaSecuritySecureRandom[IO](2) bytes2 <- random2.nextBytes(256) bytes3 <- random2.nextBytes(1024) - } yield bytes1.length == 128 && bytes2.length == 256 && bytes3.length == 1024 + } yield assert(bytes1.length == 128 && bytes2.length == 256 && bytes3.length == 1024) } real("overrides nextInt") { @@ -35,7 +35,7 @@ class SecureRandomSuite extends BaseSuite { secureInts <- secureRandom.nextInt.replicateA(3) insecureRandom <- Random.scalaUtilRandomSeedInt[IO](0) insecureInts <- insecureRandom.nextInt.replicateA(3) - } yield secureInts != insecureInts + } yield assert(secureInts != insecureInts) } } diff --git a/tests/shared/src/test/scala/cats/effect/std/SupervisorSuite.scala b/tests/shared/src/test/scala/cats/effect/std/SupervisorSuite.scala index 1ed2bfe908..4b24a4d93a 100644 --- a/tests/shared/src/test/scala/cats/effect/std/SupervisorSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/SupervisorSuite.scala @@ -209,7 +209,7 @@ class SupervisorSuite extends BaseSuite with DetectPlatform { } // if this doesn't work properly, the test will hang - test.start.flatMap(_.join).timeoutTo(4.seconds, IO(sys.error("err"))) + test.start.flatMap(_.join).timeoutTo(4.seconds, IO(sys.error("err"))).void } real(s"$name - cancel inner fiber and ignore restart if outer errored") { @@ -223,7 +223,7 @@ class SupervisorSuite extends BaseSuite with DetectPlatform { } // if this doesn't work properly, the test will hang - test.start.flatMap(_.join).timeoutTo(4.seconds, IO(sys.error("err"))) + test.start.flatMap(_.join).timeoutTo(4.seconds, IO(sys.error("err"))).void } real(s"$name - supervise / finalize race") { diff --git a/tests/shared/src/test/scala/cats/effect/std/UUIDGenSuite.scala b/tests/shared/src/test/scala/cats/effect/std/UUIDGenSuite.scala index f185ce0317..b18e1670ce 100644 --- a/tests/shared/src/test/scala/cats/effect/std/UUIDGenSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/std/UUIDGenSuite.scala @@ -23,7 +23,7 @@ class UUIDGenSuite extends BaseSuite { for { left <- UUIDGen.randomUUID[IO] right <- UUIDGen.randomUUID[IO] - } yield left != right + } yield assert(left != right) } real("use the correct variant and version") { diff --git a/tests/shared/src/test/scala/cats/effect/tracing/TraceSuite.scala b/tests/shared/src/test/scala/cats/effect/tracing/TraceSuite.scala index 0d832f819d..2c1453998b 100644 --- a/tests/shared/src/test/scala/cats/effect/tracing/TraceSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/tracing/TraceSuite.scala @@ -33,12 +33,15 @@ class TraceSuite extends BaseSuite with TestInstances with DetectPlatform { self } loop(100).attempt.map { case Left(ex) => - ex.getStackTrace.count { e => - e.getClassName() == s"flatMap @ ${self.getClass().getName()}" && e - .getMethodName() - .startsWith("loop$") - } == rt.config.traceBufferSize - case _ => false + assertEquals( + ex.getStackTrace.count { e => + e.getClassName() == s"flatMap @ ${self.getClass().getName()}" && e + .getMethodName() + .startsWith("loop$") + }, + rt.config.traceBufferSize + ) + case other => fail(s"Expected Left, got $other") } } } else { From 545e9b20a77c4a415eb1fed6358744bab48c9df2 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 3 Jan 2025 10:06:37 +0200 Subject: [PATCH 15/20] Backport tests --- ioapp-tests/src/test/scala/IOAppSpec.scala | 5 +++++ .../main/scala/catseffect/examplesplatform.scala | 13 +++++++++++++ .../src/test/scala/cats/effect/BaseSuite.scala | 10 +--------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/ioapp-tests/src/test/scala/IOAppSpec.scala b/ioapp-tests/src/test/scala/IOAppSpec.scala index 34d428a4a9..b19b25a1ec 100644 --- a/ioapp-tests/src/test/scala/IOAppSpec.scala +++ b/ioapp-tests/src/test/scala/IOAppSpec.scala @@ -358,6 +358,11 @@ class IOAppSpec extends FunSuite { assertEquals(h.awaitStatus(), 0) } + test("use configurable reportFailure for runnables on MainThread") { + val h = platform("MainThreadReportFailureRunnable", List.empty) + assertEquals(h.awaitStatus(), 0) + } + test("warn on blocked threads") { val h = platform("BlockedThreads", List.empty) h.awaitStatus() diff --git a/tests/jvm/src/main/scala/catseffect/examplesplatform.scala b/tests/jvm/src/main/scala/catseffect/examplesplatform.scala index c7a6396b8f..882829399f 100644 --- a/tests/jvm/src/main/scala/catseffect/examplesplatform.scala +++ b/tests/jvm/src/main/scala/catseffect/examplesplatform.scala @@ -71,6 +71,19 @@ package examples { } + object MainThreadReportFailureRunnable extends IOApp { + + val exitCode = new AtomicReference[ExitCode](ExitCode.Error) + + override def reportFailure(err: Throwable): IO[Unit] = + IO(exitCode.set(ExitCode.Success)) + + def run(args: List[String]): IO[ExitCode] = + IO(MainThread.execute(() => throw new Exception)) *> + IO.sleep(1.second) *> IO(exitCode.get) + + } + object BlockedThreads extends IOApp.Simple { override protected def blockedThreadDetectionEnabled = true diff --git a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala index 8b2d17bff0..9e21a51c05 100644 --- a/tests/shared/src/test/scala/cats/effect/BaseSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/BaseSuite.scala @@ -24,14 +24,6 @@ import munit.{FunSuite, Location, ScalaCheckSuite, TestOptions} trait BaseSuite extends FunSuite with Runners { - /*@deprecated("Please use a type safe alternative, such as 'real' or 'ticked'", "3.6.0") - override def test(name: String)(body: => Any)(implicit loc: Location): Unit = - super.test(name)(body) - - @deprecated("Please use a type safe alternative, such as 'real' or 'ticked'", "3.6.0") - override def test(options: TestOptions)(body: => Any)(implicit loc: Location): Unit = - super.test(options)(body)*/ - def testUnit(name: String)(body: => Unit)(implicit loc: Location): Unit = test(name)(body) @@ -50,7 +42,7 @@ trait BaseSuite extends FunSuite with Runners { } ), new ValueTransform( - "Other", + "Unexpected test result type", { case r if !r.isInstanceOf[Unit] && !r.isInstanceOf[Prop] => sys.error(s"Unexpected value of type ${r.getClass.getName}: $r") From 27fcf66b17df8ac976bcd421b5d2f26876d7fa21 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Thu, 9 Jan 2025 20:49:14 +0200 Subject: [PATCH 16/20] Rename `*Spec` -> `*Suite` --- .../src/test/scala/{IOAppSpec.scala => IOAppSuite.scala} | 2 +- .../cats/effect/std/{SyntaxSpec.scala => SyntaxSuite.scala} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename ioapp-tests/src/test/scala/{IOAppSpec.scala => IOAppSuite.scala} (99%) rename std/shared/src/test/scala/cats/effect/std/{SyntaxSpec.scala => SyntaxSuite.scala} (97%) diff --git a/ioapp-tests/src/test/scala/IOAppSpec.scala b/ioapp-tests/src/test/scala/IOAppSuite.scala similarity index 99% rename from ioapp-tests/src/test/scala/IOAppSpec.scala rename to ioapp-tests/src/test/scala/IOAppSuite.scala index b19b25a1ec..92c40d5ca8 100644 --- a/ioapp-tests/src/test/scala/IOAppSpec.scala +++ b/ioapp-tests/src/test/scala/IOAppSuite.scala @@ -23,7 +23,7 @@ import java.io.File import munit.FunSuite -class IOAppSpec extends FunSuite { +class IOAppSuite extends FunSuite { abstract class Platform(val id: String) { outer => def builder(proto: String, args: List[String]): ProcessBuilder diff --git a/std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala b/std/shared/src/test/scala/cats/effect/std/SyntaxSuite.scala similarity index 97% rename from std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala rename to std/shared/src/test/scala/cats/effect/std/SyntaxSuite.scala index 178d5271ae..9116cf25b3 100644 --- a/std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala +++ b/std/shared/src/test/scala/cats/effect/std/SyntaxSuite.scala @@ -20,7 +20,7 @@ import cats.effect.kernel.{Async, Concurrent, Deferred, GenConcurrent, Ref, Sync import munit.FunSuite -class SyntaxSpec extends FunSuite { +class SyntaxSuite extends FunSuite { test("concurrent data structure construction syntax") {} From 65dc9ad33917e6a8f7d6b5c2d196451b897a059f Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 10 Jan 2025 10:06:43 +0200 Subject: [PATCH 17/20] Use `ExecutionContext.global` as munit context --- tests/shared/src/test/scala/cats/effect/Runners.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/shared/src/test/scala/cats/effect/Runners.scala b/tests/shared/src/test/scala/cats/effect/Runners.scala index bc7850f436..9ac94788ef 100644 --- a/tests/shared/src/test/scala/cats/effect/Runners.scala +++ b/tests/shared/src/test/scala/cats/effect/Runners.scala @@ -23,7 +23,7 @@ import cats.syntax.all._ import org.scalacheck.Gen -import scala.concurrent.{Future, Promise} +import scala.concurrent.{ExecutionContext, Future, Promise} import scala.concurrent.duration._ import scala.reflect.{classTag, ClassTag} @@ -35,6 +35,7 @@ trait Runners extends TestInstances with RunnersPlatform { def executionTimeout: FiniteDuration = 20.seconds override def munitTimeout: Duration = executionTimeout + override def munitExecutionContext: ExecutionContext = ExecutionContext.global def ticked(options: TestOptions)(body: Ticker => Unit)(implicit loc: Location): Unit = test(options)(body(Ticker(TestContext()))) From ce3f8dd91c495bbb7645a448c46f3608571474b5 Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 10 Jan 2025 10:13:53 +0200 Subject: [PATCH 18/20] Use `ExecutionContext.global` as munit context (jvm) --- tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala | 4 ++++ tests/shared/src/test/scala/cats/effect/Runners.scala | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala b/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala index a10838bf24..8df1f33fb1 100644 --- a/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala +++ b/tests/jvm/src/test/scala/cats/effect/RunnersPlatform.scala @@ -18,10 +18,14 @@ package cats.effect import cats.effect.unsafe.{IORuntime, IORuntimeConfig} +import scala.concurrent.ExecutionContext + trait RunnersPlatform { self: munit.Suite => private[this] var runtime0: IORuntime = _ + override def munitExecutionContext: ExecutionContext = ExecutionContext.global + protected def runtime(): IORuntime = runtime0 override def beforeAll(): Unit = { diff --git a/tests/shared/src/test/scala/cats/effect/Runners.scala b/tests/shared/src/test/scala/cats/effect/Runners.scala index 9ac94788ef..bc7850f436 100644 --- a/tests/shared/src/test/scala/cats/effect/Runners.scala +++ b/tests/shared/src/test/scala/cats/effect/Runners.scala @@ -23,7 +23,7 @@ import cats.syntax.all._ import org.scalacheck.Gen -import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.concurrent.{Future, Promise} import scala.concurrent.duration._ import scala.reflect.{classTag, ClassTag} @@ -35,7 +35,6 @@ trait Runners extends TestInstances with RunnersPlatform { def executionTimeout: FiniteDuration = 20.seconds override def munitTimeout: Duration = executionTimeout - override def munitExecutionContext: ExecutionContext = ExecutionContext.global def ticked(options: TestOptions)(body: Ticker => Unit)(implicit loc: Location): Unit = test(options)(body(Ticker(TestContext()))) From e72d87cff87662ad09b0090d0d16210a679b84ba Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Fri, 10 Jan 2025 10:26:13 +0200 Subject: [PATCH 19/20] Fix before/after in DeferredJVMSuite --- .../jvm/src/test/scala/cats/effect/std/DeferredJVMSuite.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSuite.scala b/tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSuite.scala index ff612136d5..bf9bf3d913 100644 --- a/tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSuite.scala +++ b/tests/jvm/src/test/scala/cats/effect/std/DeferredJVMSuite.scala @@ -44,7 +44,7 @@ abstract class BaseDeferredJVMTests(parallelism: Int) extends FunSuite { implicit val runtime: IORuntime = IORuntime.global - def before: Any = + override def beforeEach(context: BeforeEach): Unit = service = Executors.newFixedThreadPool( parallelism, new ThreadFactory { @@ -58,7 +58,7 @@ abstract class BaseDeferredJVMTests(parallelism: Int) extends FunSuite { } ) - def after: Any = { + override def afterEach(context: AfterEach): Unit = { service.shutdown() assert(service.awaitTermination(60, TimeUnit.SECONDS), "has active threads") } From d13efdc1e42d22280c095d4fc56117680aad145c Mon Sep 17 00:00:00 2001 From: Maksym Ochenashko Date: Sat, 11 Jan 2025 20:42:56 +0200 Subject: [PATCH 20/20] Fix formatting --- .../src/test/scala/cats/effect/IOSuite.scala | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/tests/shared/src/test/scala/cats/effect/IOSuite.scala b/tests/shared/src/test/scala/cats/effect/IOSuite.scala index cca069bb1e..85b0fb1048 100644 --- a/tests/shared/src/test/scala/cats/effect/IOSuite.scala +++ b/tests/shared/src/test/scala/cats/effect/IOSuite.scala @@ -686,15 +686,14 @@ class IOSuite extends BaseScalaCheckSuite with DisciplineSuite with IOPlatformSu assertCompleteAs(IO.async[Int](cb => IO(cb(Right(42))).as(None)).map(_ + 2), 44) } - // format: off - ticked("async - produce a failure when the registration raises an error after callback") { implicit ticker => - case object TestException extends RuntimeException + ticked("async - produce a failure when the registration raises an error after callback") { + implicit ticker => + case object TestException extends RuntimeException - assertFailAs(IO.async[Int](cb => IO(cb(Right(42))) - .flatMap(_ => IO.raiseError(TestException))) - .void, TestException) - } - // format: on + assertFailAs( + IO.async[Int](cb => IO(cb(Right(42))).flatMap(_ => IO.raiseError(TestException))).void, + TestException) + } ticked("async - repeated async callback") { implicit ticker => case object TestException extends RuntimeException @@ -1576,35 +1575,33 @@ class IOSuite extends BaseScalaCheckSuite with DisciplineSuite with IOPlatformSu assert(success) } - // format: off - ticked("finalization - not finalize after uncancelable with suppressed cancelation (succeeded)") { implicit ticker => - var finalized = false + ticked( + "finalization - not finalize after uncancelable with suppressed cancelation (succeeded)") { + implicit ticker => + var finalized = false - val test = - IO.uncancelable(_ => IO.canceled >> IO.pure(42)) - .onCancel(IO { finalized = true }) - .void + val test = + IO.uncancelable(_ => IO.canceled >> IO.pure(42)).onCancel(IO { finalized = true }).void - assertSelfCancel(test ) - assert(!finalized) - } - // format: on + assertSelfCancel(test) + assert(!finalized) + } - // format: off - ticked("finalization - not finalize after uncancelable with suppressed cancelation (errored)") { implicit ticker => - case object TestException extends RuntimeException + ticked( + "finalization - not finalize after uncancelable with suppressed cancelation (errored)") { + implicit ticker => + case object TestException extends RuntimeException - var finalized = false + var finalized = false - val test = - IO.uncancelable(_ => IO.canceled >> IO.raiseError(TestException)) - .onCancel(IO { finalized = true }) - .void + val test = + IO.uncancelable(_ => IO.canceled >> IO.raiseError(TestException)) + .onCancel(IO { finalized = true }) + .void - assertSelfCancel(test ) - assert(!finalized) - } - // format: on + assertSelfCancel(test) + assert(!finalized) + } ticked("finalization - finalize on uncaught errors in bracket use clauses") { implicit ticker =>