From eb55d25e7fea9399d49a4eeb9dd8d56e872c9bc1 Mon Sep 17 00:00:00 2001 From: Amund Murstad Date: Wed, 7 Jul 2021 17:41:34 +0200 Subject: [PATCH] Add IOForEachSuite as a way of getting a global resource, but then transforming it and using the transformed value individually for each of the individual tests --- modules/core/cats/src/weaver/Suites.scala | 4 ++ modules/core/cats/src/weaver/package.scala | 1 + modules/core/src/weaver/suites.scala | 65 ++++++++++++++------ modules/framework/cats/test/src/Global.scala | 44 ++++++++++++- 4 files changed, 93 insertions(+), 21 deletions(-) diff --git a/modules/core/cats/src/weaver/Suites.scala b/modules/core/cats/src/weaver/Suites.scala index ad540641..98a8d605 100644 --- a/modules/core/cats/src/weaver/Suites.scala +++ b/modules/core/cats/src/weaver/Suites.scala @@ -23,6 +23,10 @@ abstract class MutableIOSuite with BaseIOSuite with Expectations.Helpers +abstract class MutableForEachIOSuite extends MutableForEachSuite[IO] + with BaseIOSuite + with Expectations.Helpers + abstract class SimpleMutableIOSuite extends MutableIOSuite { type Res = Unit def sharedResource: Resource[IO, Unit] = Resource.pure[IO, Unit](()) diff --git a/modules/core/cats/src/weaver/package.scala b/modules/core/cats/src/weaver/package.scala index dc84043a..ed80ded4 100644 --- a/modules/core/cats/src/weaver/package.scala +++ b/modules/core/cats/src/weaver/package.scala @@ -3,6 +3,7 @@ import cats.effect.IO package object weaver { type IOSuite = MutableIOSuite + type IOForEachSuite = MutableForEachIOSuite type SimpleIOSuite = SimpleMutableIOSuite type GlobalResource = IOGlobalResource type GlobalRead = GlobalResourceF.Read[IO] diff --git a/modules/core/src/weaver/suites.scala b/modules/core/src/weaver/suites.scala index fb6ef72e..7704a19b 100644 --- a/modules/core/src/weaver/suites.scala +++ b/modules/core/src/weaver/suites.scala @@ -58,10 +58,9 @@ abstract class RunnableSuite[F[_]] extends EffectSuite[F] { effectCompat.sync(run(args)(outcome => effectCompat.effect.delay(report(outcome)))) } -abstract class MutableFSuite[F[_]] extends RunnableSuite[F] { +abstract class MutableBaseFSuite[F[_]] extends RunnableSuite[F] { type Res - def sharedResource : Resource[F, Res] def maxParallelism : Int = 10000 @@ -81,37 +80,63 @@ abstract class MutableFSuite[F[_]] extends RunnableSuite[F] { def apply(run : (Res, Log[F]) => F[Expectations]) : Unit = registerTest(name)(res => Test(name.name, log => run(res, log))) } + private[this] var testSeq = Seq.empty[(TestName, Res => F[TestOutcome])] + + def plan: List[TestName] = testSeq.map(_._1).toList + + private[this] var isInitialized = false + + private[this] def initError() = + new AssertionError( + "Cannot define new tests after TestSuite was initialized" + ) + + + private[weaver] def getTests(args: List[String]) = { + if (!isInitialized) isInitialized = true + val argsFilter = Filters.filterTests(this.name)(args) + val filteredTests = if (testSeq.exists(_._1.tags(TestName.Tags.only))){ + testSeq.filter(_._1.tags(TestName.Tags.only)).map { case (_, test) => (res: Res) => test(res)} + } else testSeq.collect { + case (name, test) if argsFilter(name) => (res : Res) => test(res) + } + val parallism = math.max(1, maxParallelism) + + (filteredTests, parallism) + } +} + +abstract class MutableFSuite[F[_]] extends MutableBaseFSuite[F]{ + def sharedResource : Resource[F, Res] override def spec(args: List[String]) : Stream[F, TestOutcome] = synchronized { - if (!isInitialized) isInitialized = true - val argsFilter = Filters.filterTests(this.name)(args) - val filteredTests = if (testSeq.exists(_._1.tags(TestName.Tags.only))){ - testSeq.filter(_._1.tags(TestName.Tags.only)).map { case (_, test) => (res: Res) => test(res)} - } else testSeq.collect { - case (name, test) if argsFilter(name) => (res : Res) => test(res) - } - val parallism = math.max(1, maxParallelism) + val (filteredTests, parallism) = getTests(args) if (filteredTests.isEmpty) Stream.empty // no need to allocate resources else for { resource <- Stream.resource(sharedResource) tests = filteredTests.map(_.apply(resource)) testStream = Stream.emits(tests).lift[F](effectCompat.effect) result <- if (parallism > 1 ) testStream.parEvalMap(parallism)(identity)(effectCompat.effect) - else testStream.evalMap(identity) + else testStream.evalMap(identity) } yield result } +} +abstract class MutableForEachSuite[F[_]] extends MutableBaseFSuite[F]{ + def uniqueResource : Resource[F, Res] - private[this] var testSeq = Seq.empty[(TestName, Res => F[TestOutcome])] - - def plan: List[TestName] = testSeq.map(_._1).toList - - private[this] var isInitialized = false + override def spec(args: List[String]) : Stream[F, TestOutcome] = + synchronized { + val (filteredTests, parallism) = getTests(args) - private[this] def initError() = - new AssertionError( - "Cannot define new tests after TestSuite was initialized" - ) + if (filteredTests.isEmpty) Stream.empty // no need to allocate resources + else { + val testStream = Stream.emits(filteredTests).lift[F](effectCompat.effect) + if (parallism > 1 ) + testStream.parEvalMap(parallism)(test => uniqueResource.use(test(_)))(effectCompat.effect) + else testStream.evalMap(test => uniqueResource.use(test(_))) + } + } } trait FunSuiteAux { diff --git a/modules/framework/cats/test/src/Global.scala b/modules/framework/cats/test/src/Global.scala index d0a7d6f9..dab514fa 100644 --- a/modules/framework/cats/test/src/Global.scala +++ b/modules/framework/cats/test/src/Global.scala @@ -2,13 +2,19 @@ package weaver package framework package test -import cats.effect.{ IO, Resource } +import cats.effect.{ IO, Resource} object SharedResources extends IOGlobalResource { + class RefFactory(init: Int){ + val generate: Resource[IO, CECompat.Ref[IO, Int]] = Resource.eval(CECompat.Ref.of[IO, Int](init)) + } + def sharedResources(global: GlobalResourceF.Write[IO]): Resource[IO, Unit] = for { foo <- Resource.pure[IO, String]("hello world!") + bar <- Resource.pure[IO, RefFactory](new RefFactory(0)) _ <- global.putR(foo) + _ <- global.putR(bar) } yield () } @@ -34,3 +40,39 @@ class OtherResourceSharingSuite(globalResources: GlobalResourceF.Read[IO]) } } + +class UniqueResourceSuite(global: GlobalRead) extends IOForEachSuite{ + type Res = CECompat.Ref[IO, Int] + def uniqueResource = global.getOrFailR[SharedResources.RefFactory]().flatMap(_.generate) + + override val maxParallelism = 1 + + test("start value is set to 0") { refIO => + refIO.get.map(i => expect(i == 0)) + } + test("value is updated locally to 1") { refIO=> + refIO.update(_ + 1) *> + refIO.get.map(i => expect(i == 1)) + } + test("value is still 0 in another test") { refIO=> + refIO.get.map(i => expect(i == 0)) + } +} + +class SharedResourceSuite(global: GlobalRead) extends IOSuite{ + type Res = CECompat.Ref[IO, Int] + def sharedResource = global.getOrFailR[SharedResources.RefFactory]().flatMap(_.generate) + + override val maxParallelism = 1 + + test("start value is set to 0") { refIO => + refIO.get.map(i => expect(i == 0)) + } + test("value is updated in whole suit to 1") { refIO=> + refIO.update(_ + 1) *> + refIO.get.map(i => expect(i == 1)) + } + test("value is changed to 1 in another test") { refIO=> + refIO.get.map(i => expect(i == 1)) + } +} \ No newline at end of file