From df6869531c6acab9b2acd19c214f5008e3562364 Mon Sep 17 00:00:00 2001 From: Keith Pinson Date: Tue, 18 May 2021 10:17:50 -0400 Subject: [PATCH 1/7] feat(test): convert `MemoryCacheSpec` to MUnit --- build.sbt | 7 + .../mules/MemoryCacheSpec.scala | 357 +++++++++--------- 2 files changed, 189 insertions(+), 175 deletions(-) diff --git a/build.sbt b/build.sbt index 4c3a371f..2555eaf3 100644 --- a/build.sbt +++ b/build.sbt @@ -83,6 +83,8 @@ val catsCollectionV = "0.9.2" val specs2V = "4.11.0" val disciplineSpecs2V = "1.1.6" +val munitV = "0.7.25" +val munitCEV = "1.0.2" lazy val commonSettings = Seq( scalaVersion := "2.13.5", @@ -91,6 +93,8 @@ lazy val commonSettings = Seq( addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.13.0" cross CrossVersion.full), addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), + testFrameworks += new TestFramework("munit.Framework"), + libraryDependencies ++= Seq( "org.typelevel" %% "cats-core" % catsV, "org.typelevel" %% "cats-effect" % catsEffectV, @@ -98,6 +102,9 @@ lazy val commonSettings = Seq( "org.typelevel" %% "cats-effect-laws" % catsEffectV % Test, "com.codecommit" %% "cats-effect-testing-specs2" % "0.5.3" % Test, + "org.scalameta" %% "munit" % munitV % Test, + "org.scalameta" %% "munit-scalacheck" % munitV % Test, + "org.typelevel" %% "munit-cats-effect-2" % munitCEV % Test, "org.specs2" %% "specs2-core" % specs2V % Test, "org.specs2" %% "specs2-scalacheck" % specs2V % Test, "org.typelevel" %% "discipline-specs2" % disciplineSpecs2V % Test, diff --git a/modules/core/src/test/scala/io/chrisdavenport/mules/MemoryCacheSpec.scala b/modules/core/src/test/scala/io/chrisdavenport/mules/MemoryCacheSpec.scala index ff1d34d0..3ec45ed3 100644 --- a/modules/core/src/test/scala/io/chrisdavenport/mules/MemoryCacheSpec.scala +++ b/modules/core/src/test/scala/io/chrisdavenport/mules/MemoryCacheSpec.scala @@ -1,212 +1,219 @@ package io.chrisdavenport.mules -import org.specs2.mutable.Specification +import cats.effect.laws.util.TestContext import scala.concurrent.duration._ import cats.effect._ import cats.effect.concurrent._ -// import cats.effect.implicits._ -import cats.effect.IO -import cats.effect.laws.util.TestContext -import cats.effect.testing.specs2.CatsIO - -class MemoryCacheSpec extends Specification with CatsIO { - - - "MemoryCache.ofSingleImmutableMap" should { - "get a value in a quicker period than the timeout" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofSingleImmutableMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ = ctx.tick(1.nano) - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== Some(1)) +import munit._ + +class MemoryCacheSpec extends CatsEffectSuite { + test("MemoryCache.ofSingleImmutableMap should get a value in a quicker period than the timeout") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofSingleImmutableMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ = ctx.tick(1.nano) + value <- cache.lookup("Foo") + } yield { + assertEquals(value, Some(1)) } + } - "remove a value after delete" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofSingleImmutableMap[IO, String, Int](None)(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ <- cache.delete("Foo") - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== None) + test("MemoryCache.ofSingleImmutableMap should remove a value after delete") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofSingleImmutableMap[IO, String, Int](None)(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ <- cache.delete("Foo") + value <- cache.lookup("Foo") + } yield { + assertEquals(value, None) } + } - "Remove a value in mass delete" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofSingleImmutableMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ <- Sync[IO].delay(ctx.tick(2.seconds)) - _ <- cache.purgeExpired - value <- cache.lookupNoUpdate("Foo") - } yield value - setup.map(_ must_=== None) + test("MemoryCache.ofSingleImmutableMap should remove a value in mass delete") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofSingleImmutableMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ <- Sync[IO].delay(ctx.tick(2.seconds)) + _ <- cache.purgeExpired + value <- cache.lookupNoUpdate("Foo") + } yield { + assertEquals(value, None) } + } - "Lookup after interval fails to get a value" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofSingleImmutableMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ <- Sync[IO].delay(ctx.tick(2.seconds)) - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== None) + test("MemoryCache.ofSingleImmutableMap should lookup after interval fails to get a value") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofSingleImmutableMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ <- Sync[IO].delay(ctx.tick(2.seconds)) + value <- cache.lookup("Foo") + } yield { + assertEquals(value, None) } + } - "Not Remove an item on lookup No Delete" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - checkWasTouched <- Ref[IO].of(false) - iCache <- MemoryCache.ofSingleImmutableMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - cache = iCache.setOnDelete(_ => checkWasTouched.set(true)) - _ <- cache.insert("Foo", 1) - _ <- Sync[IO].delay(ctx.tick(2.seconds)) - value <- cache.lookupNoUpdate("Foo") - wasTouched <- checkWasTouched.get - } yield (value, wasTouched) - setup.map(_.must_===((Option.empty[Int], false))) + test("MemoryCache.ofSingleImmutableMap should not Remove an item on lookup No Delete") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + checkWasTouched <- Ref[IO].of(false) + iCache <- MemoryCache.ofSingleImmutableMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + cache = iCache.setOnDelete(_ => checkWasTouched.set(true)) + _ <- cache.insert("Foo", 1) + _ <- Sync[IO].delay(ctx.tick(2.seconds)) + value <- cache.lookupNoUpdate("Foo") + wasTouched <- checkWasTouched.get + } yield { + assertEquals(value, Option.empty[Int]) + assert(!wasTouched) } } - "MemoryCache.ofShardedImmutableMap" should { - "get a value in a quicker period than the timeout" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofShardedImmutableMap[IO, String, Int](10, Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ = ctx.tick(1.nano) - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== Some(1)) + test("MemoryCache.ofShardedImmutableMap should get a value in a quicker period than the timeout") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofShardedImmutableMap[IO, String, Int](10, Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ = ctx.tick(1.nano) + value <- cache.lookup("Foo") + } yield { + assertEquals(value, Some(1)) } + } - "remove a value after delete" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofShardedImmutableMap[IO, String, Int](10, Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ <- cache.delete("Foo") - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== None) + test("MemoryCache.ofShardedImmutableMap should remove a value after delete") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofShardedImmutableMap[IO, String, Int](10, Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ <- cache.delete("Foo") + value <- cache.lookup("Foo") + } yield { + assertEquals(value, None) } + } - "Remove a value in mass delete" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofShardedImmutableMap[IO, String, Int](10, Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ <- Sync[IO].delay(ctx.tick(2.seconds)) - _ <- cache.purgeExpired - value <- cache.lookupNoUpdate("Foo") - } yield value - setup.map(_ must_=== None) + test("MemoryCache.ofShardedImmutableMap should remove a value in mass delete") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofShardedImmutableMap[IO, String, Int](10, Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ <- Sync[IO].delay(ctx.tick(2.seconds)) + _ <- cache.purgeExpired + value <- cache.lookupNoUpdate("Foo") + } yield { + assertEquals(value, None) } + } - "Lookup after interval fails to get a value" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofShardedImmutableMap[IO, String, Int](10, Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ <- Sync[IO].delay(ctx.tick(2.seconds)) - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== None) + test("MemoryCache.ofShardedImmutableMap should lookup after interval fails to get a value") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofShardedImmutableMap[IO, String, Int](10, Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ <- Sync[IO].delay(ctx.tick(2.seconds)) + value <- cache.lookup("Foo") + } yield { + assertEquals(value, None) } + } - "Not Remove an item on lookup No Delete" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - checkWasTouched <- Ref[IO].of(false) - iCache <- MemoryCache.ofShardedImmutableMap[IO, String, Int](10, Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - cache = iCache.setOnDelete(_ => checkWasTouched.set(true)) - _ <- cache.insert("Foo", 1) - _ <- Sync[IO].delay(ctx.tick(2.seconds)) - value <- cache.lookupNoUpdate("Foo") - wasTouched <- checkWasTouched.get - } yield (value, wasTouched) - setup.map(_.must_===((Option.empty[Int], false))) + test("MemoryCache.ofShardedImmutableMap should not Remove an item on lookup No Delete") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + checkWasTouched <- Ref[IO].of(false) + iCache <- MemoryCache.ofShardedImmutableMap[IO, String, Int](10, Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + cache = iCache.setOnDelete(_ => checkWasTouched.set(true)) + _ <- cache.insert("Foo", 1) + _ <- Sync[IO].delay(ctx.tick(2.seconds)) + value <- cache.lookupNoUpdate("Foo") + wasTouched <- checkWasTouched.get + } yield { + assertEquals(value, Option.empty) + assert(!wasTouched) } } - "MemoryCache.ofConcurrentHashMap" should { - "get a value in a quicker period than the timeout" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofConcurrentHashMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ = ctx.tick(1.nano) - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== Some(1)) + test("MemoryCache.ofConcurrentHashMap should get a value in a quicker period than the timeout") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofConcurrentHashMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ = ctx.tick(1.nano) + value <- cache.lookup("Foo") + } yield { + assertEquals(value, Some(1)) } + } - "remove a value after delete" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofConcurrentHashMap[IO, String, Int](None)(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ <- cache.delete("Foo") - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== None) + test("MemoryCache.ofConcurrentHashMap should remove a value after delete") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofConcurrentHashMap[IO, String, Int](None)(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ <- cache.delete("Foo") + value <- cache.lookup("Foo") + } yield { + assertEquals(value, None) } + } - "Remove a value in mass delete" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofConcurrentHashMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ <- Sync[IO].delay(ctx.tick(2.seconds)) - _ <- cache.purgeExpired - value <- cache.lookupNoUpdate("Foo") - } yield value - setup.map(_ must_=== None) + test("MemoryCache.ofConcurrentHashMap should Remove a value in mass delete") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofConcurrentHashMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ <- Sync[IO].delay(ctx.tick(2.seconds)) + _ <- cache.purgeExpired + value <- cache.lookupNoUpdate("Foo") + } yield { + assertEquals(value, None) } + } - "Lookup after interval fails to get a value" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - cache <- MemoryCache.ofConcurrentHashMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - _ <- cache.insert("Foo", 1) - _ <- Sync[IO].delay(ctx.tick(2.seconds)) - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== None) + test("MemoryCache.ofConcurrentHashMap should Lookup after interval fails to get a value") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + cache <- MemoryCache.ofConcurrentHashMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + _ <- cache.insert("Foo", 1) + _ <- Sync[IO].delay(ctx.tick(2.seconds)) + value <- cache.lookup("Foo") + } yield { + assertEquals(value, None) } + } - "Not Remove an item on lookup No Delete" in { - val ctx = TestContext() - implicit val testTimer: Timer[IO] = ctx.timer[IO] - val setup = for { - checkWasTouched <- Ref[IO].of(false) - iCache <- MemoryCache.ofConcurrentHashMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) - cache = iCache.setOnDelete(_ => checkWasTouched.set(true)) - _ <- cache.insert("Foo", 1) - _ <- Sync[IO].delay(ctx.tick(2.seconds)) - value <- cache.lookupNoUpdate("Foo") - wasTouched <- checkWasTouched.get - } yield (value, wasTouched) - setup.map(_.must_===((Option.empty[Int], false))) + test("MemoryCache.ofConcurrentHashMap should Not Remove an item on lookup No Delete") { + val ctx = TestContext() + implicit val testTimer: Timer[IO] = ctx.timer[IO] + for { + checkWasTouched <- Ref[IO].of(false) + iCache <- MemoryCache.ofConcurrentHashMap[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)))(Sync[IO], testTimer.clock) + cache = iCache.setOnDelete(_ => checkWasTouched.set(true)) + _ <- cache.insert("Foo", 1) + _ <- Sync[IO].delay(ctx.tick(2.seconds)) + value <- cache.lookupNoUpdate("Foo") + wasTouched <- checkWasTouched.get + } yield { + assertEquals(value, Option.empty[Int]) + assert(!wasTouched) } } -} \ No newline at end of file +} From a9db3bff434e686b3a52e4fa1abca24154972204 Mon Sep 17 00:00:00 2001 From: Keith Pinson Date: Tue, 18 May 2021 10:25:46 -0400 Subject: [PATCH 2/7] feat(test): convert `CaffeineCacheSpec` to MUnit --- .../mules/caffeine/CaffeineCacheSpec.scala | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/modules/caffeine/src/test/scala/io/chrisdavenport/mules/caffeine/CaffeineCacheSpec.scala b/modules/caffeine/src/test/scala/io/chrisdavenport/mules/caffeine/CaffeineCacheSpec.scala index 63f969e8..c14c67be 100644 --- a/modules/caffeine/src/test/scala/io/chrisdavenport/mules/caffeine/CaffeineCacheSpec.scala +++ b/modules/caffeine/src/test/scala/io/chrisdavenport/mules/caffeine/CaffeineCacheSpec.scala @@ -1,47 +1,43 @@ package io.chrisdavenport.mules.caffeine -import org.specs2.mutable.Specification import scala.concurrent.duration._ import cats.effect._ -// import cats.effect.implicits._ -import cats.effect.IO -import cats.effect.testing.specs2.CatsIO +import munit._ import io.chrisdavenport.mules.TimeSpec -class CaffeineCacheSpec extends Specification with CatsIO { - "CaffeineCache" should { - "get a value in a quicker period than the timeout" in { - val setup = for { - cache <- CaffeineCache.build[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)), None, None) - _ <- cache.insert("Foo", 1) - _ <- Timer[IO].sleep(1.milli) - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== Some(1)) +class CaffeineCacheSpec extends CatsEffectSuite { + test("CaffeineCache should get a value in a quicker period than the timeout") { + for { + cache <- CaffeineCache.build[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)), None, None) + _ <- cache.insert("Foo", 1) + _ <- Timer[IO].sleep(1.milli) + value <- cache.lookup("Foo") + } yield { + assertEquals(value, Some(1)) } + } - "remove a value after delete" in { - val setup = for { - cache <- CaffeineCache.build[IO, String, Int](None, None, None) - _ <- cache.insert("Foo", 1) - _ <- cache.delete("Foo") - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== None) + test("CaffeineCache should remove a value after delete") { + for { + cache <- CaffeineCache.build[IO, String, Int](None, None, None) + _ <- cache.insert("Foo", 1) + _ <- cache.delete("Foo") + value <- cache.lookup("Foo") + } yield { + assertEquals(value, None) } + } - "Lookup after interval fails to get a value" in { - val setup = for { - cache <- CaffeineCache.build[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)), None, None) - _ <- cache.insert("Foo", 1) - _ <- Timer[IO].sleep(2.second) - value <- cache.lookup("Foo") - } yield value - setup.map(_ must_=== None) + test("CaffeineCache should Lookup after interval fails to get a value") { + for { + cache <- CaffeineCache.build[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)), None, None) + _ <- cache.insert("Foo", 1) + _ <- Timer[IO].sleep(2.second) + value <- cache.lookup("Foo") + } yield { + assertEquals(value, None) } - - } -} \ No newline at end of file +} From 19824c85c5c14446ed590397f8b656e534008f10 Mon Sep 17 00:00:00 2001 From: Keith Pinson Date: Tue, 18 May 2021 10:30:43 -0400 Subject: [PATCH 3/7] feat(test): convert `DispatchOneCacheSpec` to MUnit --- .../mules/DispatchOneCacheSpec.scala | 151 +++++++++--------- 1 file changed, 73 insertions(+), 78 deletions(-) diff --git a/modules/core/src/test/scala/io/chrisdavenport/mules/DispatchOneCacheSpec.scala b/modules/core/src/test/scala/io/chrisdavenport/mules/DispatchOneCacheSpec.scala index 155e0000..92dd1a68 100644 --- a/modules/core/src/test/scala/io/chrisdavenport/mules/DispatchOneCacheSpec.scala +++ b/modules/core/src/test/scala/io/chrisdavenport/mules/DispatchOneCacheSpec.scala @@ -1,95 +1,90 @@ package io.chrisdavenport.mules -import org.specs2.mutable.Specification import scala.concurrent.duration._ -import cats.implicits._ +import cats.syntax.all._ import cats.effect._ import cats.effect.concurrent._ -// import cats.effect.implicits._ -import cats.effect.IO -import cats.effect.testing.specs2.CatsIO +import munit._ -class DispatchOneCacheSpec extends Specification with CatsIO { - "DispatchOneCache" should { - "only run once" in { - for { - ref <- Ref[IO].of(0) - cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) - action = {_: Unit => Timer[IO].sleep(1.second) >> ref.modify(i => (i+1, i))} - first <- cache.lookupOrLoad((), action).start - second <- cache.lookupOrLoad((), action).start - third <- cache.lookupOrLoad((), action).start - _ <- first.join - _ <- second.join - _ <- third.join - testValue <- ref.get - } yield testValue must_=== 1 - } +class DispatchOneCacheSpec extends CatsEffectSuite { + test("DispatchOneCache should only run once") { + for { + ref <- Ref[IO].of(0) + cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) + action = {_: Unit => Timer[IO].sleep(1.second) >> ref.modify(i => (i+1, i))} + first <- cache.lookupOrLoad((), action).start + second <- cache.lookupOrLoad((), action).start + third <- cache.lookupOrLoad((), action).start + _ <- first.join + _ <- second.join + _ <- third.join + testValue <- ref.get + } yield assertEquals(testValue, 1) + } - "only run till errors cease" in { - for { - ref <- Ref[IO].of(0) - errorFunction = ref.modify(i => (i+1, if (i > 3) i.pure[IO] else Timer[IO].sleep(1.second) >> IO.raiseError(new Throwable("whoopsie")))).flatten - cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) - first <- cache.lookupOrLoad((), { _ => errorFunction}).start - second <- cache.lookupOrLoad((), { _ => errorFunction}).start - third <- cache.lookupOrLoad((), { _ => errorFunction}).start - _ <- first.join - _ <- second.join - _ <- third.join - testValue <- ref.get - } yield testValue must_=== 5 - } + test("DispatchOneCache should only run till errors cease") { + for { + ref <- Ref[IO].of(0) + errorFunction = ref.modify(i => (i+1, if (i > 3) i.pure[IO] else Timer[IO].sleep(1.second) >> IO.raiseError(new Throwable("whoopsie")))).flatten + cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) + first <- cache.lookupOrLoad((), { _ => errorFunction}).start + second <- cache.lookupOrLoad((), { _ => errorFunction}).start + third <- cache.lookupOrLoad((), { _ => errorFunction}).start + _ <- first.join + _ <- second.join + _ <- third.join + testValue <- ref.get + } yield assertEquals(testValue, 5) + } - "insert places a value" in { - for { - cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) - action = {_: Unit => IO.pure(5)} - _ <- cache.insert((), 1) - now <- cache.lookupOrLoad((), action) - } yield { - now must_=== 1 - } + test("DispatchOneCache should insert places a value") { + for { + cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) + action = {_: Unit => IO.pure(5)} + _ <- cache.insert((), 1) + now <- cache.lookupOrLoad((), action) + } yield { + assertEquals(now, 1) } + } - "insert overrides background action for first action" in { - for { - cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) - action = {_: Unit => Timer[IO].sleep(5.seconds).as(5)} - first <- cache.lookupOrLoad((), action).start - _ <- cache.insert((), 1) - value <- first.join - } yield { - value must_=== 1 - } + test("DispatchOneCache should insert overrides background action for first action") { + for { + cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) + action = {_: Unit => Timer[IO].sleep(5.seconds).as(5)} + first <- cache.lookupOrLoad((), action).start + _ <- cache.insert((), 1) + value <- first.join + } yield { + assertEquals(value, 1) } + } - "insert overrides background action for secondary action" in { - for { - cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) - action = {_: Unit => Timer[IO].sleep(5.seconds).as(5)} - first <- cache.lookupOrLoad((),action).start - second <- cache.lookupOrLoad((), action).start - _ <- cache.insert((), 1) - resultSecond <- second.join - _ <- first.cancel.timeout(1.second).attempt.void - } yield { - resultSecond must_=== 1 - } + test("DispatchOneCache should insert overrides background action for secondary action") { + for { + cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) + action = {_: Unit => Timer[IO].sleep(5.seconds).as(5)} + first <- cache.lookupOrLoad((),action).start + second <- cache.lookupOrLoad((), action).start + _ <- cache.insert((), 1) + resultSecond <- second.join + _ <- first.cancel.timeout(1.second).attempt.void + } yield { + assertEquals(resultSecond, 1) } + } - "insert overrides set value" in { - for { - cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) - action = {_: Unit => IO.pure(2)} - first <- cache.lookupOrLoad((), action) - _ <- cache.insert((), 1) - second <- cache.lookupOrLoad((), action) - } yield { - (first,second).must_===((2, 1)) - - } + test("DispatchOneCache should insert overrides set value") { + for { + cache <- DispatchOneCache.ofSingleImmutableMap[IO, Unit, Int](None) + action = {_: Unit => IO.pure(2)} + first <- cache.lookupOrLoad((), action) + _ <- cache.insert((), 1) + second <- cache.lookupOrLoad((), action) + } yield { + assertEquals(first, 2) + assertEquals(second, 1) } } -} \ No newline at end of file +} From 2433bd0aa9ce160fe34d7ddf0963108e497fd4cc Mon Sep 17 00:00:00 2001 From: Keith Pinson Date: Tue, 18 May 2021 11:20:48 -0400 Subject: [PATCH 4/7] feat(test): convert `AutoFetchingCacheSpec` to MUnit --- .../mules/AutoFetchingCacheSpec.scala | 141 ++++++++---------- 1 file changed, 66 insertions(+), 75 deletions(-) diff --git a/modules/reload/src/test/scala/io/chrisdavenport/mules/AutoFetchingCacheSpec.scala b/modules/reload/src/test/scala/io/chrisdavenport/mules/AutoFetchingCacheSpec.scala index 9f90f235..7a52a060 100644 --- a/modules/reload/src/test/scala/io/chrisdavenport/mules/AutoFetchingCacheSpec.scala +++ b/modules/reload/src/test/scala/io/chrisdavenport/mules/AutoFetchingCacheSpec.scala @@ -1,89 +1,80 @@ -package io.chrisdavenport.mules.reload +package io.chrisdavenport.mules +package reload -import cats.effect.IO +import cats.effect._ import cats.effect.concurrent.Ref -import cats.implicits._ -import io.chrisdavenport.mules._ -import org.specs2.mutable.Specification +import cats.syntax.all._ +import munit._ -import scala.concurrent.ExecutionContext import scala.concurrent.duration._ -class AutoFetchingCacheSpec extends Specification { - - implicit val ctx = IO.contextShift(ExecutionContext.global) - implicit val timer = IO.timer(ExecutionContext.Implicits.global) - - "AutoFetchingCache" should { - "get a value in a quicker period than the timeout" in { - val setup = for { - - count <- Ref.of[IO, Int](0) - - cache <- AutoFetchingCache.createCache[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)), None)(_ => - count.update( _ + 1).as(1) - ) - value <- cache.lookupCurrent("Foo") - cValue <- count.get - } yield (cValue, value) - setup.unsafeRunSync() must_=== ((1, 1)) +class AutoFetchingCacheSpec extends CatsEffectSuite { + test("AutoFetchingCache should get a value in a quicker period than the timeout") { + for { + count <- Ref.of[IO, Int](0) + cache <- AutoFetchingCache.createCache[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)), None)(_ => + count.update( _ + 1).as(1) + ) + value <- cache.lookupCurrent("Foo") + cValue <- count.get + } yield { + assertEquals(cValue, 1) + assertEquals(value, 1) } + } - - "refetch value after expiration timeout" in { - val setup = for { - count <- Ref.of[IO, Int](0) - - cache <- AutoFetchingCache.createCache[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)), None)(_ => - count.update( _ + 1).as(1) - ) - _ <- cache.lookupCurrent("Foo") - _ <- timer.sleep(2.seconds) - value <- cache.lookupCurrent("Foo") - cValue <- count.get - - } yield (cValue, value) - setup.unsafeRunSync() must_=== ((2, 1)) + test("AutoFetchingCache should refetch value after expiration timeout") { + for { + count <- Ref.of[IO, Int](0) + + cache <- AutoFetchingCache.createCache[IO, String, Int](Some(TimeSpec.unsafeFromDuration(1.second)), None)(_ => + count.update( _ + 1).as(1) + ) + _ <- cache.lookupCurrent("Foo") + _ <- Timer[IO].sleep(2.seconds) + value <- cache.lookupCurrent("Foo") + cValue <- count.get + } yield { + assertEquals(cValue, 2) + assertEquals(value, 1) } + } - - "refetch value after autoReload timeout" in { - val setup = for { - count <- Ref.of[IO, Int](0) - - cache <- AutoFetchingCache.createCache[IO, String, Int](None, Some(AutoFetchingCache.RefreshConfig(TimeSpec.unsafeFromDuration(500.milliseconds))))(_ => - count.update( _ + 1).as(1) - ) - _ <- cache.lookupCurrent("Foo") - _ <- timer.sleep(2.seconds) - value <- cache.lookupCurrent("Foo") - cValue <- count.get - - } yield (cValue, value) - - val (cValue, value) = setup.unsafeRunSync() - (value must_=== 1).and(cValue >= 4) + test("AutoFetchingCache should refetch value after autoReload timeout") { + for { + count <- Ref.of[IO, Int](0) + + cache <- AutoFetchingCache.createCache[IO, String, Int](None, Some(AutoFetchingCache.RefreshConfig(TimeSpec.unsafeFromDuration(500.milliseconds))))(_ => + count.update( _ + 1).as(1) + ) + _ <- cache.lookupCurrent("Foo") + _ <- Timer[IO].sleep(2.seconds) + value <- cache.lookupCurrent("Foo") + cValue <- count.get + + } yield { + assertEquals(value, 1) + assert(cValue >= 4) } + } - "refetch value after autoReload timeout and before default expiration" in { - val setup = for { - count <- Ref.of[IO, Int](0) - - cache <- AutoFetchingCache.createCache[IO, String, Int]( - TimeSpec.fromDuration(3.second), - Some(AutoFetchingCache.RefreshConfig(TimeSpec.unsafeFromDuration(500.milliseconds))))(_ => - count.update( _ + 1) *> count.get - ) - _ <- cache.lookupCurrent("Foo") - _ <- timer.sleep(2.seconds) - value <- cache.lookupCurrent("Foo") - cValue <- count.get - - } yield (cValue, value) - - val (cValue, value) = setup.unsafeRunSync() - (value must be >= 4).and(cValue >= 4) + test("AutoFetchingCache should refetch value after autoReload timeout and before default expiration") { + for { + count <- Ref.of[IO, Int](0) + + cache <- AutoFetchingCache.createCache[IO, String, Int]( + TimeSpec.fromDuration(3.second), + Some(AutoFetchingCache.RefreshConfig(TimeSpec.unsafeFromDuration(500.milliseconds))))(_ => + count.update( _ + 1) *> count.get + ) + _ <- cache.lookupCurrent("Foo") + _ <- Timer[IO].sleep(2.seconds) + value <- cache.lookupCurrent("Foo") + cValue <- count.get + + } yield { + assert(value >= 4) + assert(cValue >= 4) } - } } From 9109a4214662956d669d3c12a402ba44c81382e7 Mon Sep 17 00:00:00 2001 From: Keith Pinson Date: Tue, 18 May 2021 12:27:54 -0400 Subject: [PATCH 5/7] feat(test): convert `AutoMemoryCacheSpec` to MUnit --- .../mules/AutoMemoryCacheSpec.scala | 171 ++++++++---------- 1 file changed, 74 insertions(+), 97 deletions(-) diff --git a/modules/core/src/test/scala/io/chrisdavenport/mules/AutoMemoryCacheSpec.scala b/modules/core/src/test/scala/io/chrisdavenport/mules/AutoMemoryCacheSpec.scala index 1630cff4..af607faf 100644 --- a/modules/core/src/test/scala/io/chrisdavenport/mules/AutoMemoryCacheSpec.scala +++ b/modules/core/src/test/scala/io/chrisdavenport/mules/AutoMemoryCacheSpec.scala @@ -2,127 +2,104 @@ package io.chrisdavenport.mules import cats.effect.laws.util.TestContext import cats.effect._ -import cats.implicits._ -import org.specs2.mutable.Specification +import cats.syntax.all._ +import munit._ import scala.concurrent.duration._ -class AutoMemoryCacheSpec extends Specification { - +class AutoMemoryCacheSpec extends CatsEffectSuite { val cacheKeyExpiration = TimeSpec.unsafeFromDuration(12.hours) val checkExpirationsEvery = TimeSpec.unsafeFromDuration(10.millis) - "Auto MemoryCache.ofSingleImmutableMap" should { + private val ctx = TestContext() - "expire keys" in WithTestContext { ctx => implicit cs => implicit timer => - val spec = Resource.eval(MemoryCache.ofSingleImmutableMap[IO, Int, String](cacheKeyExpiration.some)) - .flatMap(cache => MemoryCache.liftToAuto(cache, checkExpirationsEvery).as(cache)) - .use(cache => - for { - _ <- cache.insert(1, "foo") - _ <- IO(ctx.tick(5.hours)) - _ <- cache.insert(2, "bar") - a1 <- cache.lookupNoUpdate(1) - b1 <- cache.lookupNoUpdate(2) - _ <- IO { - assert(a1.contains("foo")) - assert(b1.contains("bar")) - } - _ <- IO(ctx.tick(7.hours + 1.second)) // expiration time reached - a2 <- cache.lookupNoUpdate(1) - b2 <- cache.lookupNoUpdate(2) - _ <- IO { - assert(a2.isEmpty) // not here - assert(b2.contains("bar")) - } - } yield () - ) - spec.as(1).unsafeRunSync() must_== 1 - } + implicit override def munitContextShift: ContextShift[IO] = + IO.contextShift(ctx) + implicit override def munitTimer: Timer[IO] = + ctx.timer - "resets expiration" in WithTestContext { ctx => implicit cs => implicit timer => - val spec = Resource.eval(MemoryCache.ofSingleImmutableMap[IO, Int, String](cacheKeyExpiration.some)) - .flatMap(cache => MemoryCache.liftToAuto(cache, checkExpirationsEvery).as(cache)) - .use(cache => + test("Auto MemoryCache.ofSingleImmutableMap should expire keys") { + Resource.eval(MemoryCache.ofSingleImmutableMap[IO, Int, String](cacheKeyExpiration.some)) + .flatMap(cache => MemoryCache.liftToAuto(cache, checkExpirationsEvery).as(cache)) + .use(cache => for { _ <- cache.insert(1, "foo") _ <- IO(ctx.tick(5.hours)) + _ <- cache.insert(2, "bar") a1 <- cache.lookupNoUpdate(1) - _ <- IO { assert(a1.contains("foo")) } - _ <- cache.insert(1, "bar") - _ <- IO(ctx.tick(7.hours + 1.second)) // expiration time reached for first timestamp + b1 <- cache.lookupNoUpdate(2) + _ <- IO(ctx.tick(7.hours + 1.second)) // expiration time reached a2 <- cache.lookupNoUpdate(1) - _ <- IO { assert(a2.contains("bar")) } - _ <- IO(ctx.tick(5.hours)) // expiration time reached for last timestamp - a3 <- cache.lookupNoUpdate(1) - _ <- IO { assert(a3.isEmpty) } - } yield () + b2 <- cache.lookupNoUpdate(2) + } yield { + assert(a1.contains("foo")) + assert(b1.contains("bar")) + assertEquals(a2, None) // not here + assert(b2.contains("bar")) + } ) - spec.as(1).unsafeRunSync() must_== 1 - } - } - "Auto MemoryCache.ofConcurrentHashMap" should { - - "expire keys" in WithTestContext { ctx => implicit cs => implicit timer => - val spec = Resource.eval(MemoryCache.ofConcurrentHashMap[IO, Int, String](cacheKeyExpiration.some)) - .flatMap(cache => MemoryCache.liftToAuto(cache, checkExpirationsEvery).as(cache)) - .use(cache => - for { - _ <- cache.insert(1, "foo") - _ <- IO(ctx.tick(5.hours)) - _ <- cache.insert(2, "bar") - a1 <- cache.lookupNoUpdate(1) - b1 <- cache.lookupNoUpdate(2) - _ <- IO { - assert(a1.contains("foo")) - assert(b1.contains("bar")) - } - _ <- IO(ctx.tick(7.hours + 1.second)) // expiration time reached - a2 <- cache.lookupNoUpdate(1) - b2 <- cache.lookupNoUpdate(2) - _ <- IO { - assert(a2.isEmpty) // not here - assert(b2.contains("bar")) - } - } yield () - ) - spec.as(1).unsafeRunSync() must_== 1 - } + test("Auto MemoryCache.ofSingleImmutableMap should resets expiration") { + Resource.eval(MemoryCache.ofSingleImmutableMap[IO, Int, String](cacheKeyExpiration.some)) + .flatMap(cache => MemoryCache.liftToAuto(cache, checkExpirationsEvery).as(cache)) + .use(cache => + for { + _ <- cache.insert(1, "foo") + _ <- IO(ctx.tick(5.hours)) + a1 <- cache.lookupNoUpdate(1) + _ <- cache.insert(1, "bar") + _ <- IO(ctx.tick(7.hours + 1.second)) // expiration time reached for first timestamp + a2 <- cache.lookupNoUpdate(1) + _ <- IO(ctx.tick(5.hours)) // expiration time reached for last timestamp + a3 <- cache.lookupNoUpdate(1) + } yield { + assert(a1.contains("foo")) + assert(a2.contains("bar")) + assert(a3.isEmpty) + }) + } - "resets expiration" in WithTestContext { ctx => implicit cs => implicit timer => - val spec = Resource.eval(MemoryCache.ofConcurrentHashMap[IO, Int, String](cacheKeyExpiration.some)) - .flatMap(cache => MemoryCache.liftToAuto(cache, checkExpirationsEvery).as(cache)) - .use(cache => + test("Auto MemoryCache.ofConcurrentHashMap should expire keys") { + Resource.eval(MemoryCache.ofConcurrentHashMap[IO, Int, String](cacheKeyExpiration.some)) + .flatMap(cache => MemoryCache.liftToAuto(cache, checkExpirationsEvery).as(cache)) + .use(cache => for { _ <- cache.insert(1, "foo") _ <- IO(ctx.tick(5.hours)) + _ <- cache.insert(2, "bar") a1 <- cache.lookupNoUpdate(1) - _ <- IO { assert(a1.contains("foo")) } - _ <- cache.insert(1, "bar") - _ <- IO(ctx.tick(7.hours + 1.second)) // expiration time reached for first timestamp + b1 <- cache.lookupNoUpdate(2) + _ <- IO(ctx.tick(7.hours + 1.second)) // expiration time reached a2 <- cache.lookupNoUpdate(1) - _ <- IO { assert(a2.contains("bar")) } - _ <- IO(ctx.tick(5.hours)) // expiration time reached for last timestamp - a3 <- cache.lookupNoUpdate(1) - _ <- IO { assert(a3.isEmpty) } - } yield () + b2 <- cache.lookupNoUpdate(2) + } yield { + assert(a1.contains("foo")) + assert(b1.contains("bar")) + assertEquals(a2, None) // not here + assert(b2.contains("bar")) + } ) - spec.as(1).unsafeRunSync() must_== 1 - } - } -} - -object WithTestContext { - - def apply[A](f: TestContext => ContextShift[IO] => Timer[IO] => A): A = { - val ctx = TestContext() - val cs: ContextShift[IO] = IO.contextShift(ctx) - val timer: Timer[IO] = ctx.timer[IO] - f(ctx)(cs)(timer) + test("Auto MemoryCache.ofConcurrentHashMap should resets expiration") { + Resource.eval(MemoryCache.ofConcurrentHashMap[IO, Int, String](cacheKeyExpiration.some)) + .flatMap(cache => MemoryCache.liftToAuto(cache, checkExpirationsEvery).as(cache)) + .use(cache => + for { + _ <- cache.insert(1, "foo") + _ <- IO(ctx.tick(5.hours)) + a1 <- cache.lookupNoUpdate(1) + _ <- cache.insert(1, "bar") + _ <- IO(ctx.tick(7.hours + 1.second)) // expiration time reached for first timestamp + a2 <- cache.lookupNoUpdate(1) + _ <- IO(ctx.tick(5.hours)) // expiration time reached for last timestamp + a3 <- cache.lookupNoUpdate(1) + } yield { + assert(a1.contains("foo")) + assert(a2.contains("bar")) + assertEquals(a3, None) + } + ) } - } From f49682b5c90f5e72fa302e107eadc1baaebf57c4 Mon Sep 17 00:00:00 2001 From: Keith Pinson Date: Tue, 18 May 2021 12:28:25 -0400 Subject: [PATCH 6/7] infra(git): ignore gtags files --- .gitignore | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5363cd17..e19ab90b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,9 @@ tags .bloop .metals project/metals.sbt -.bsp/ \ No newline at end of file +.bsp/ + +# gtags +GPATH +GRTAGS +GTAGS \ No newline at end of file From 4ec655aa770c8ad6cc1d7b8bc45478ffbd78ca40 Mon Sep 17 00:00:00 2001 From: Keith Pinson Date: Tue, 18 May 2021 12:30:24 -0400 Subject: [PATCH 7/7] clean(test): remove all dependences on specs 2 --- build.sbt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/build.sbt b/build.sbt index 2555eaf3..fab21847 100644 --- a/build.sbt +++ b/build.sbt @@ -81,8 +81,6 @@ val catsV = "2.6.1" val catsEffectV = "2.5.1" val catsCollectionV = "0.9.2" -val specs2V = "4.11.0" -val disciplineSpecs2V = "1.1.6" val munitV = "0.7.25" val munitCEV = "1.0.2" @@ -101,13 +99,9 @@ lazy val commonSettings = Seq( "io.chrisdavenport" %% "mapref" % "0.1.1", "org.typelevel" %% "cats-effect-laws" % catsEffectV % Test, - "com.codecommit" %% "cats-effect-testing-specs2" % "0.5.3" % Test, "org.scalameta" %% "munit" % munitV % Test, "org.scalameta" %% "munit-scalacheck" % munitV % Test, "org.typelevel" %% "munit-cats-effect-2" % munitCEV % Test, - "org.specs2" %% "specs2-core" % specs2V % Test, - "org.specs2" %% "specs2-scalacheck" % specs2V % Test, - "org.typelevel" %% "discipline-specs2" % disciplineSpecs2V % Test, ) )