From 51723502ab2eb22ff2b440dd71af048c88fed5db Mon Sep 17 00:00:00 2001 From: Milan van der Meer <5628925+milanvdm@users.noreply.github.com> Date: Sun, 24 May 2020 10:46:31 +0200 Subject: [PATCH 1/4] Add new CatsEffect module --- build.sbt | 53 ++++++++++++++----- .../scala/munit/CatsEffectFunFixture.scala | 5 ++ 2 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixture.scala diff --git a/build.sbt b/build.sbt index e9ede917..bce80df9 100644 --- a/build.sbt +++ b/build.sbt @@ -206,6 +206,23 @@ lazy val plugin = project ) ) +lazy val munitCatsEffect = crossProject(JSPlatform, JVMPlatform) + .in(file("munit-cats-effect")) + .dependsOn(munit) + .settings( + moduleName := "munit-cats-effect", + sharedSettings, + libraryDependencies += ("org.typelevel" %%% "cats-effect" % "2.1.3") + .withDottyCompat(scalaVersion.value) + ) + .jvmSettings( + sharedJVMSettings, + skip in publish := customScalaJSVersion.isDefined + ) + .jsSettings(sharedJSSettings) +lazy val munitCatsEffectJVM = munitCatsEffect.jvm +lazy val munitCatsEffectJS = munitCatsEffect.js + lazy val munitScalacheck = crossProject(JSPlatform, JVMPlatform, NativePlatform) .in(file("munit-scalacheck")) .dependsOn(munit) @@ -229,33 +246,43 @@ lazy val munitScalacheckJVM = munitScalacheck.jvm lazy val munitScalacheckJS = munitScalacheck.js lazy val munitScalacheckNative = munitScalacheck.native -lazy val tests = crossProject(JSPlatform, JVMPlatform, NativePlatform) - .dependsOn(munit, munitScalacheck) +lazy val testsSettings = List( + buildInfoPackage := "munit", + buildInfoKeys := Seq[BuildInfoKey]( + "sourceDirectory" -> + baseDirectory.in(ThisBuild).value / "tests" / "shared" / "src" / "main", + scalaVersion + ), + skip in publish := true +) +lazy val tests = crossProject(JSPlatform, JVMPlatform) + .dependsOn(munit, munitCatsEffect, munitScalacheck) .enablePlugins(BuildInfoPlugin) .settings( sharedSettings, - buildInfoPackage := "munit", - buildInfoKeys := Seq[BuildInfoKey]( - "sourceDirectory" -> - baseDirectory.in(ThisBuild).value / "tests" / "shared" / "src" / "main", - scalaVersion - ), - skip in publish := true + testsSettings ) - .nativeConfigure(sharedNativeConfigure) - .nativeSettings(sharedNativeSettings) .jsSettings(sharedJSSettings) .jvmSettings( sharedJVMSettings, fork := true ) +lazy val testsN = crossProject(NativePlatform) + .dependsOn(munit, munitScalacheck) + .enablePlugins(BuildInfoPlugin) + .settings( + sharedSettings, + testsSettings + ) + .nativeConfigure(sharedNativeConfigure) + .nativeSettings(sharedNativeSettings) lazy val testsJVM = tests.jvm lazy val testsJS = tests.js -lazy val testsNative = tests.native +lazy val testsNative = testsN.native lazy val docs = project .in(file("munit-docs")) - .dependsOn(munitJVM, munitScalacheckJVM) + .dependsOn(munitJVM, munitCatsEffectJVM, munitScalacheckJVM) .enablePlugins(MdocPlugin, MUnitReportPlugin, DocusaurusPlugin) .settings( sharedSettings, diff --git a/munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixture.scala b/munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixture.scala new file mode 100644 index 00000000..c657eb9e --- /dev/null +++ b/munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixture.scala @@ -0,0 +1,5 @@ +package munit + +trait CatsEffectFunFixture extends FunFixtures { self: FunSuite => + +} From d5b964d50515900a1346658c36b7d8736c8ff22d Mon Sep 17 00:00:00 2001 From: Milan van der Meer <5628925+milanvdm@users.noreply.github.com> Date: Mon, 25 May 2020 08:50:55 +0200 Subject: [PATCH 2/4] Add CatsEffectSuite --- .../main/scala/munit/CatsEffectSuite.scala | 16 ++++++++++ tests/jvm/src/test/scala/munit/IOSuite.scala | 30 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 munit-cats-effect/shared/src/main/scala/munit/CatsEffectSuite.scala create mode 100644 tests/jvm/src/test/scala/munit/IOSuite.scala diff --git a/munit-cats-effect/shared/src/main/scala/munit/CatsEffectSuite.scala b/munit-cats-effect/shared/src/main/scala/munit/CatsEffectSuite.scala new file mode 100644 index 00000000..d4fe4be5 --- /dev/null +++ b/munit-cats-effect/shared/src/main/scala/munit/CatsEffectSuite.scala @@ -0,0 +1,16 @@ +package munit + +import cats.effect.{ContextShift, IO} + +abstract class CatsEffectSuite extends FunSuite { + + def munitContextShift: ContextShift[IO] = + IO.contextShift(munitExecutionContext) + + override def munitValueTransforms: List[ValueTransform] = + super.munitValueTransforms ++ List(munitIOTransform) + + final def munitIOTransform: ValueTransform = + new ValueTransform("IO", { case e: IO[_] => e.unsafeToFuture() }) + +} diff --git a/tests/jvm/src/test/scala/munit/IOSuite.scala b/tests/jvm/src/test/scala/munit/IOSuite.scala new file mode 100644 index 00000000..7bac9246 --- /dev/null +++ b/tests/jvm/src/test/scala/munit/IOSuite.scala @@ -0,0 +1,30 @@ +package munit + +import cats.effect.IO + +class IOSuite extends CatsEffectSuite { + test("nested".fail) { + IO { + Thread.sleep(2) + IO { + Thread.sleep(2) + IO { + Thread.sleep(2) + ??? + } + } + } + } + test("nested success") { + IO { + Thread.sleep(2) + IO { + Thread.sleep(2) + IO { + Thread.sleep(2) + assertEquals(true, true) + } + } + } + } +} From c82b6ea465ce2971aaf5b154485a7c66aea8e4c4 Mon Sep 17 00:00:00 2001 From: Milan van der Meer <5628925+milanvdm@users.noreply.github.com> Date: Mon, 25 May 2020 08:51:05 +0200 Subject: [PATCH 3/4] Add CatsEffectFunFictures --- .../scala/munit/CatsEffectFunFixture.scala | 5 -- .../scala/munit/CatsEffectFunFixtures.scala | 55 +++++++++++++++++ .../src/test/scala/munit/IOFixtureSuite.scala | 61 +++++++++++++++++++ 3 files changed, 116 insertions(+), 5 deletions(-) delete mode 100644 munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixture.scala create mode 100644 munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixtures.scala create mode 100644 tests/jvm/src/test/scala/munit/IOFixtureSuite.scala diff --git a/munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixture.scala b/munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixture.scala deleted file mode 100644 index c657eb9e..00000000 --- a/munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixture.scala +++ /dev/null @@ -1,5 +0,0 @@ -package munit - -trait CatsEffectFunFixture extends FunFixtures { self: FunSuite => - -} diff --git a/munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixtures.scala b/munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixtures.scala new file mode 100644 index 00000000..d959f5d5 --- /dev/null +++ b/munit-cats-effect/shared/src/main/scala/munit/CatsEffectFunFixtures.scala @@ -0,0 +1,55 @@ +package munit + +import cats.effect.{IO, Resource} +import cats.syntax.flatMap._ + +import scala.concurrent.Promise + +trait CatsEffectFunFixtures extends FunFixtures { self: CatsEffectSuite => + + object CatsEffectFixture { + + def fromResource[T]( + resource: Resource[IO, T] + ): FunFixture[T] = fromResource( + resource, + (_, _) => IO.pure(()), + _ => IO.pure(()) + ) + + def fromResource[T]( + resource: Resource[IO, T], + setup: (TestOptions, T) => IO[Unit], + teardown: T => IO[Unit] + ): FunFixture[T] = { + val promise = Promise[IO[Unit]]() + + FunFixture.async( + setup = { testOptions => + implicit val ec = munitExecutionContext + + val resourceEffect = resource.allocated + val setupEffect = + resourceEffect + .map { + case (t, release) => + promise.success(release) + t + } + .flatTap(t => setup(testOptions, t)) + + setupEffect.unsafeToFuture() + }, + teardown = { argument: T => + implicit val cs = munitContextShift + + teardown(argument) + .flatMap(_ => IO.fromFuture(IO(promise.future)).flatten) + .unsafeToFuture() + } + ) + } + + } + +} diff --git a/tests/jvm/src/test/scala/munit/IOFixtureSuite.scala b/tests/jvm/src/test/scala/munit/IOFixtureSuite.scala new file mode 100644 index 00000000..69cc932d --- /dev/null +++ b/tests/jvm/src/test/scala/munit/IOFixtureSuite.scala @@ -0,0 +1,61 @@ +package munit + +import cats.effect.{IO, Resource} + +import scala.concurrent.{Future, Promise} + +class IOFixtureSuite extends CatsEffectSuite with CatsEffectFunFixtures { + val latch: Promise[Unit] = Promise[Unit] + var completedFromTest: Option[Boolean] = None + var completedFromTeardown: Option[Boolean] = None + + var completedFromResourceAcquire: Option[Boolean] = None + var completedFromResourceRelease: Option[Boolean] = None + + val latchOnTeardown: FunFixture[String] = + CatsEffectFixture.fromResource[String]( + resource = Resource.make[IO, String]( + IO { + completedFromResourceAcquire = Some(true) + "test" + } + )(_ => + IO { + completedFromResourceRelease = Some(true) + } + ), + setup = { (_: TestOptions, _: String) => + IO { + completedFromResourceAcquire = Some(false) + } + }, + teardown = { _: String => + IO { + completedFromResourceRelease = Some(false) + completedFromTeardown = Some(latch.trySuccess(())); + } + } + ) + + override def afterAll(): Unit = { + // resource was created before setup + assertEquals(completedFromResourceAcquire, Some(false)) + // resource was released after teardown + assertEquals(completedFromResourceRelease, Some(true)) + // promise was completed first by the test + assertEquals(completedFromTest, Some(true)) + // and then there was a completion attempt by the teardown + assertEquals(completedFromTeardown, Some(false)) + } + + latchOnTeardown.test("teardown runs only after test completes") { _ => + import scala.concurrent.ExecutionContext.Implicits.global + Future { + // Simulate some work here, which increases the certainty that this test + // will fail by design and not by lucky scheduling if the happens-before + // relationship between the test and teardown is removed. + Thread.sleep(50) + completedFromTest = Some(latch.trySuccess(())) + } + } +} From 31598108425555960350e467791c2fde0299cd42 Mon Sep 17 00:00:00 2001 From: Milan van der Meer <5628925+milanvdm@users.noreply.github.com> Date: Mon, 25 May 2020 09:02:29 +0200 Subject: [PATCH 4/4] Add Timer --- .../main/scala/munit/CatsEffectSuite.scala | 5 ++- tests/jvm/src/test/scala/munit/IOSuite.scala | 41 ++++++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/munit-cats-effect/shared/src/main/scala/munit/CatsEffectSuite.scala b/munit-cats-effect/shared/src/main/scala/munit/CatsEffectSuite.scala index d4fe4be5..67ff1f77 100644 --- a/munit-cats-effect/shared/src/main/scala/munit/CatsEffectSuite.scala +++ b/munit-cats-effect/shared/src/main/scala/munit/CatsEffectSuite.scala @@ -1,12 +1,15 @@ package munit -import cats.effect.{ContextShift, IO} +import cats.effect.{ContextShift, IO, Timer} abstract class CatsEffectSuite extends FunSuite { def munitContextShift: ContextShift[IO] = IO.contextShift(munitExecutionContext) + def munitTimer: Timer[IO] = + IO.timer(munitExecutionContext) + override def munitValueTransforms: List[ValueTransform] = super.munitValueTransforms ++ List(munitIOTransform) diff --git a/tests/jvm/src/test/scala/munit/IOSuite.scala b/tests/jvm/src/test/scala/munit/IOSuite.scala index 7bac9246..05dc7fe4 100644 --- a/tests/jvm/src/test/scala/munit/IOSuite.scala +++ b/tests/jvm/src/test/scala/munit/IOSuite.scala @@ -2,29 +2,40 @@ package munit import cats.effect.IO +import scala.concurrent.duration._ + class IOSuite extends CatsEffectSuite { - test("nested".fail) { + + test("nested fail".fail) { IO { - Thread.sleep(2) - IO { - Thread.sleep(2) - IO { - Thread.sleep(2) - ??? + IO.sleep(2.millis)(munitTimer) + .flatMap { _ => + IO { + IO.sleep(2.millis)(munitTimer) + .flatMap { _ => + IO { + IO.sleep(2.millis)(munitTimer) + .map(_ => assertEquals(false, true)) + } + } + } } - } } } test("nested success") { IO { - Thread.sleep(2) - IO { - Thread.sleep(2) - IO { - Thread.sleep(2) - assertEquals(true, true) + IO.sleep(2.millis)(munitTimer) + .flatMap { _ => + IO { + IO.sleep(2.millis)(munitTimer) + .flatMap { _ => + IO { + IO.sleep(2.millis)(munitTimer) + .map(_ => assertEquals(true, true)) + } + } + } } - } } } }