diff --git a/build.sbt b/build.sbt index dd9cfef..c1aab55 100644 --- a/build.sbt +++ b/build.sbt @@ -16,7 +16,7 @@ name := "cats-effect-testing" -ThisBuild / baseVersion := "1.0" +ThisBuild / baseVersion := "1.2" ThisBuild / strictSemVer := true ThisBuild / organization := "org.typelevel" @@ -61,14 +61,14 @@ lazy val root = project lazy val core = crossProject(JSPlatform, JVMPlatform) .in(file("core")) - .settings(libraryDependencies += "org.typelevel" %% "cats-effect" % CatsEffectVersion) + .settings(libraryDependencies += "org.typelevel" %%% "cats-effect" % CatsEffectVersion) lazy val specs2 = crossProject(JSPlatform, JVMPlatform) .in(file("specs2")) .dependsOn(core) .settings( name := "cats-effect-testing-specs2", - libraryDependencies += ("org.specs2" %% "specs2-core" % "4.12.3").cross(CrossVersion.for3Use2_13)) + libraryDependencies += ("org.specs2" %%% "specs2-core" % "4.12.3").cross(CrossVersion.for3Use2_13)) lazy val scalatest = crossProject(JSPlatform, JVMPlatform) .in(file("scalatest")) @@ -77,7 +77,7 @@ lazy val scalatest = crossProject(JSPlatform, JVMPlatform) name := "cats-effect-testing-scalatest", libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "3.2.9")) + "org.scalatest" %%% "scalatest" % "3.2.9")) lazy val utest = crossProject(JSPlatform, JVMPlatform) .in(file("utest")) @@ -88,8 +88,8 @@ lazy val utest = crossProject(JSPlatform, JVMPlatform) testFrameworks += new TestFramework("utest.runner.Framework"), libraryDependencies ++= Seq( - "org.typelevel" %% "cats-effect-testkit" % CatsEffectVersion, - "com.lihaoyi" %% "utest" % "0.7.10")) + "org.typelevel" %%% "cats-effect-testkit" % CatsEffectVersion, + "com.lihaoyi" %%% "utest" % "0.7.10")) lazy val minitest = crossProject(JSPlatform, JVMPlatform) .in(file("minitest")) @@ -99,5 +99,5 @@ lazy val minitest = crossProject(JSPlatform, JVMPlatform) testFrameworks += new TestFramework("minitest.runner.Framework"), libraryDependencies ++= Seq( - "org.typelevel" %% "cats-effect-testkit" % CatsEffectVersion, - "io.monix" %% "minitest" % "2.9.6")) + "org.typelevel" %%% "cats-effect-testkit" % CatsEffectVersion, + "io.monix" %%% "minitest" % "2.9.6")) diff --git a/core/js/src/main/scala/cats/effect/testing/RuntimePlatform.scala b/core/js/src/main/scala/cats/effect/testing/RuntimePlatform.scala new file mode 100644 index 0000000..73f7484 --- /dev/null +++ b/core/js/src/main/scala/cats/effect/testing/RuntimePlatform.scala @@ -0,0 +1,30 @@ +/* + * Copyright 2020-2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect +package testing + +import scala.concurrent.ExecutionContext + +private[testing] trait RuntimePlatform { + private[testing] def createIORuntime(delegate: ExecutionContext): unsafe.IORuntime = + unsafe.IORuntime( + delegate, + unsafe.IORuntime.global.blocking, + unsafe.IORuntime.global.scheduler, + unsafe.IORuntime.global.shutdown, + unsafe.IORuntime.global.config) +} diff --git a/core/jvm/src/main/scala/cats/effect/testing/RuntimePlatform.scala b/core/jvm/src/main/scala/cats/effect/testing/RuntimePlatform.scala new file mode 100644 index 0000000..8a864e4 --- /dev/null +++ b/core/jvm/src/main/scala/cats/effect/testing/RuntimePlatform.scala @@ -0,0 +1,28 @@ +/* + * Copyright 2020-2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect +package testing + +import scala.concurrent.ExecutionContext + +private[testing] trait RuntimePlatform { + private[testing] def createIORuntime(ec: ExecutionContext): unsafe.IORuntime = { + val (blocking, blockingSD) = unsafe.IORuntime.createDefaultBlockingExecutionContext() + val (scheduler, schedulerSD) = unsafe.IORuntime.createDefaultScheduler() + unsafe.IORuntime(ec, blocking, scheduler, { () => blockingSD(); schedulerSD(); }, unsafe.IORuntimeConfig()) + } +} diff --git a/minitest/shared/src/main/scala/cats/effect/testing/minitest/IOTestSuite.scala b/minitest/shared/src/main/scala/cats/effect/testing/minitest/IOTestSuite.scala index 11a6a5c..c7228b5 100644 --- a/minitest/shared/src/main/scala/cats/effect/testing/minitest/IOTestSuite.scala +++ b/minitest/shared/src/main/scala/cats/effect/testing/minitest/IOTestSuite.scala @@ -17,24 +17,22 @@ package cats.effect.testing.minitest import cats.effect.{unsafe, IO} +import cats.effect.testing.RuntimePlatform import minitest.api._ import scala.concurrent.ExecutionContext import scala.concurrent.duration._ -abstract class IOTestSuite extends BaseIOTestSuite[ExecutionContext] { +abstract class IOTestSuite extends BaseIOTestSuite[ExecutionContext] with RuntimePlatform { protected def makeExecutionContext(): ExecutionContext = DefaultExecutionContext protected def timeout: FiniteDuration = 10.seconds protected[effect] def mkSpec(name: String, ec: ExecutionContext, io: => IO[Unit]): TestSpec[Unit, Unit] = { TestSpec.async[Unit](name, { _ => - val (blocking, blockingSD) = unsafe.IORuntime.createDefaultBlockingExecutionContext() - val (scheduler, schedulerSD) = unsafe.IORuntime.createDefaultScheduler() - implicit val runtime: unsafe. IORuntime = - unsafe.IORuntime(ec, blocking, scheduler, { () => blockingSD(); schedulerSD(); }, unsafe.IORuntimeConfig()) - + // TODO cleanup + implicit val runtime: unsafe.IORuntime = createIORuntime(ec) io.timeout(timeout).unsafeToFuture() }) } diff --git a/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/AsyncIOSpec.scala b/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/AsyncIOSpec.scala index 509a3e1..d4a6c46 100644 --- a/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/AsyncIOSpec.scala +++ b/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/AsyncIOSpec.scala @@ -17,19 +17,17 @@ package cats.effect.testing.scalatest import cats.effect.IO -import cats.effect.unsafe.{IORuntime, IORuntimeConfig} +import cats.effect.testing.RuntimePlatform +import cats.effect.unsafe.IORuntime + import org.scalactic.source.Position import org.scalatest.AsyncTestSuite import org.scalatest.enablers.Retrying import org.scalatest.time.Span -trait AsyncIOSpec extends AssertingSyntax with EffectTestSupport { asyncTestSuite: AsyncTestSuite => - - implicit val ioRuntime: IORuntime = { - val (scheduler, sd) = IORuntime.createDefaultScheduler() +trait AsyncIOSpec extends AssertingSyntax with EffectTestSupport with RuntimePlatform { asyncTestSuite: AsyncTestSuite => - IORuntime(executionContext, executionContext, scheduler, sd, IORuntimeConfig()) - } + implicit val ioRuntime: IORuntime = createIORuntime(executionContext) implicit def ioRetrying[T]: Retrying[IO[T]] = new Retrying[IO[T]] { override def retry(timeout: Span, interval: Span, pos: Position)(fun: => IO[T]): IO[T] = diff --git a/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/CatsResource.scala b/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/CatsResource.scala index a27295e..359dbf7 100644 --- a/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/CatsResource.scala +++ b/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/CatsResource.scala @@ -22,7 +22,6 @@ import cats.syntax.all._ import org.scalatest.{BeforeAndAfterAll, FixtureAsyncTestSuite, FutureOutcome, Outcome} -import scala.concurrent.Await import scala.concurrent.duration._ trait CatsResource[F[_], A] extends BeforeAndAfterAll { this: FixtureAsyncTestSuite => @@ -74,7 +73,7 @@ trait CatsResource[F[_], A] extends BeforeAndAfterAll { this: FixtureAsyncTestSu } override def afterAll(): Unit = { - Await.result(UnsafeRun[F].unsafeToFuture(shutdown, finiteResourceTimeout), ResourceTimeout) + UnsafeRun[F].unsafeToFuture(shutdown, finiteResourceTimeout) gate = None value = None diff --git a/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/CatsResourceIO.scala b/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/CatsResourceIO.scala index 84dc28c..ff299a7 100644 --- a/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/CatsResourceIO.scala +++ b/scalatest/shared/src/main/scala/cats/effect/testing/scalatest/CatsResourceIO.scala @@ -18,10 +18,27 @@ package cats.effect.testing package scalatest import cats.effect.{Async, IO} +import cats.effect.unsafe.IORuntime import org.scalatest.FixtureAsyncTestSuite -trait CatsResourceIO[A] extends CatsResource[IO, A] { this: FixtureAsyncTestSuite => +import scala.concurrent.Future +import scala.concurrent.duration._ + +trait CatsResourceIO[A] extends CatsResource[IO, A] with RuntimePlatform { this: FixtureAsyncTestSuite => + final def ResourceAsync = Async[IO] - final def ResourceUnsafeRun = UnsafeRun[IO] + + final def ResourceUnsafeRun = _ResourceUnsafeRun + + private lazy val _ResourceUnsafeRun = + new UnsafeRun[IO] { + private implicit val runtime: IORuntime = createIORuntime(executionContext) + + override def unsafeToFuture[B](ioa: IO[B]): Future[B] = + unsafeToFuture(ioa, None) + + override def unsafeToFuture[B](ioa: IO[B], timeout: Option[FiniteDuration]): Future[B] = + timeout.fold(ioa)(ioa.timeout).unsafeToFuture() + } } diff --git a/specs2/js/src/test/scala/cats/effect/testing/specs2/CatsEffectSpecsPlatform.scala b/specs2/js/src/test/scala/cats/effect/testing/specs2/CatsEffectSpecsPlatform.scala new file mode 100644 index 0000000..4fa556c --- /dev/null +++ b/specs2/js/src/test/scala/cats/effect/testing/specs2/CatsEffectSpecsPlatform.scala @@ -0,0 +1,21 @@ +/* + * Copyright 2020-2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect.testing.specs2 + +trait CatsEffectSpecsPlatform { this: CatsEffectSpecs => + def platformSpecs = "really execute effects" in skipped +} diff --git a/specs2/jvm/src/test/scala/cats/effect/testing/specs2/CatsEffectSpecsPlatform.scala b/specs2/jvm/src/test/scala/cats/effect/testing/specs2/CatsEffectSpecsPlatform.scala new file mode 100644 index 0000000..bf1cbbe --- /dev/null +++ b/specs2/jvm/src/test/scala/cats/effect/testing/specs2/CatsEffectSpecsPlatform.scala @@ -0,0 +1,35 @@ +/* + * Copyright 2020-2021 Typelevel + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cats.effect.testing.specs2 + +import cats.effect.IO + +trait CatsEffectSpecsPlatform { this: CatsEffectSpecs => + def platformSpecs = { + "really execute effects" in { + var gate = false + + "forcibly attempt to get the deferred value" in { + IO.cede.untilM_(IO(gate)).as(ok) + } + + "complete the deferred value inside IO context" in { + (IO { gate = true }).as(ok) + } + } + } +} diff --git a/specs2/shared/src/main/scala/cats/effect/testing/specs2/CatsResource.scala b/specs2/shared/src/main/scala/cats/effect/testing/specs2/CatsResource.scala index 448ba03..4edcd97 100644 --- a/specs2/shared/src/main/scala/cats/effect/testing/specs2/CatsResource.scala +++ b/specs2/shared/src/main/scala/cats/effect/testing/specs2/CatsResource.scala @@ -22,7 +22,6 @@ import cats.syntax.all._ import org.specs2.specification.BeforeAfterAll -import scala.concurrent.Await import scala.concurrent.duration._ abstract class CatsResource[F[_]: Async: UnsafeRun, A] extends BeforeAfterAll with CatsEffect { @@ -66,7 +65,7 @@ abstract class CatsResource[F[_]: Async: UnsafeRun, A] extends BeforeAfterAll wi } override def afterAll(): Unit = { - Await.result(UnsafeRun[F].unsafeToFuture(shutdown, finiteResourceTimeout), ResourceTimeout) + UnsafeRun[F].unsafeToFuture(shutdown, finiteResourceTimeout) gate = None value = None diff --git a/specs2/shared/src/test/scala/cats/effect/testing/specs2/CatsEffectSpecs.scala b/specs2/shared/src/test/scala/cats/effect/testing/specs2/CatsEffectSpecs.scala index fd45019..d0582d5 100644 --- a/specs2/shared/src/test/scala/cats/effect/testing/specs2/CatsEffectSpecs.scala +++ b/specs2/shared/src/test/scala/cats/effect/testing/specs2/CatsEffectSpecs.scala @@ -20,7 +20,7 @@ import cats.effect.{IO, Ref, Resource} import cats.implicits._ import org.specs2.mutable.Specification -class CatsEffectSpecs extends Specification with CatsEffect { +class CatsEffectSpecs extends Specification with CatsEffect with CatsEffectSpecsPlatform { "cats effect specifications" should { "run a non-effectful test" in { @@ -42,17 +42,7 @@ class CatsEffectSpecs extends Specification with CatsEffect { } } - "really execute effects" in { - var gate = false - - "forcibly attempt to get the deferred value" in { - IO.cede.untilM_(IO(gate)).as(ok) - } - - "complete the deferred value inside IO context" in { - (IO { gate = true }).as(ok) - } - } + platformSpecs // "timeout a failing test" in (IO.never: IO[Boolean]) }