From abab2f1093491dddf8756309da533f47983737d6 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Tue, 31 Jan 2023 12:12:10 -0600 Subject: [PATCH] introduce InMemorySuite.LocalSuite and use it to test a `def testProgram[F[_]: MonadCancelThrow](implicit L: Local[F, Span[F]]): F[Unit]` --- build.sbt | 2 +- .../shared/src/test/scala/InMemorySuite.scala | 153 +++++++++++++----- .../src/test/scala/LocalTraceSpec.scala | 30 ++++ .../src/test/scala/SpanCoalesceTest.scala | 2 +- .../src/test/scala/SpanPropagationTest.scala | 2 +- 5 files changed, 150 insertions(+), 39 deletions(-) create mode 100644 modules/core-tests/shared/src/test/scala/LocalTraceSpec.scala diff --git a/build.sbt b/build.sbt index 3229dc9d..9ab89362 100644 --- a/build.sbt +++ b/build.sbt @@ -110,7 +110,7 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) lazy val coreTests = crossProject(JSPlatform, JVMPlatform, NativePlatform) .in(file("modules/core-tests")) - .dependsOn(core, testkit) + .dependsOn(core, mtl, testkit) .enablePlugins(AutomateHeaderPlugin, NoPublishPlugin) .settings(commonSettings) diff --git a/modules/core-tests/shared/src/test/scala/InMemorySuite.scala b/modules/core-tests/shared/src/test/scala/InMemorySuite.scala index 3610057b..2f8c97bb 100644 --- a/modules/core-tests/shared/src/test/scala/InMemorySuite.scala +++ b/modules/core-tests/shared/src/test/scala/InMemorySuite.scala @@ -4,56 +4,137 @@ package natchez +import cats.Applicative +import cats.syntax.all._ import cats.data.Kleisli -import cats.effect.{IO, MonadCancelThrow} +import cats.effect.{IO, IOLocal, MonadCancelThrow} +import cats.mtl.Local import munit.CatsEffectSuite import natchez.InMemory.Lineage.defaultRootName +import natchez.mtl._ trait InMemorySuite extends CatsEffectSuite { type Lineage = InMemory.Lineage val Lineage = InMemory.Lineage type NatchezCommand = InMemory.NatchezCommand val NatchezCommand = InMemory.NatchezCommand +} - trait TraceTest { - def program[F[_]: MonadCancelThrow: Trace]: F[Unit] - def expectedHistory: List[(Lineage, NatchezCommand)] - } +object InMemorySuite { + trait TraceSuite extends InMemorySuite { + trait TraceTest { + def program[F[_]: MonadCancelThrow: Trace]: F[Unit] + + def expectedHistory: List[(Lineage, NatchezCommand)] + } - def traceTest(name: String, tt: TraceTest): Unit = { - test(s"$name - Kleisli")( - testTraceKleisli(tt.program[Kleisli[IO, Span[IO], *]](implicitly, _), tt.expectedHistory) + def traceTest(name: String, tt: TraceTest): Unit = { + test(s"$name - Kleisli")( + testTraceKleisli(tt.program[Kleisli[IO, Span[IO], *]](implicitly, _), tt.expectedHistory) + ) + test(s"$name - IOLocal")(testTraceIoLocal(tt.program[IO](implicitly, _), tt.expectedHistory)) + } + + def testTraceKleisli( + traceProgram: Trace[Kleisli[IO, Span[IO], *]] => Kleisli[IO, Span[IO], Unit], + expectedHistory: List[(Lineage, NatchezCommand)] + ): IO[Unit] = testTrace[Kleisli[IO, Span[IO], *]]( + traceProgram, + root => IO.pure(Trace[Kleisli[IO, Span[IO], *]] -> (k => k.run(root))), + expectedHistory ) - test(s"$name - IOLocal")(testTraceIoLocal(tt.program[IO](implicitly, _), tt.expectedHistory)) - } - def testTraceKleisli( - traceProgram: Trace[Kleisli[IO, Span[IO], *]] => Kleisli[IO, Span[IO], Unit], - expectedHistory: List[(Lineage, NatchezCommand)] - ): IO[Unit] = testTrace[Kleisli[IO, Span[IO], *]]( - traceProgram, - root => IO.pure(Trace[Kleisli[IO, Span[IO], *]] -> (k => k.run(root))), - expectedHistory - ) - - def testTraceIoLocal( - traceProgram: Trace[IO] => IO[Unit], - expectedHistory: List[(Lineage, NatchezCommand)] - ): IO[Unit] = testTrace[IO](traceProgram, Trace.ioTrace(_).map(_ -> identity), expectedHistory) - - def testTrace[F[_]]( - traceProgram: Trace[F] => F[Unit], - makeTraceAndResolver: Span[IO] => IO[(Trace[F], F[Unit] => IO[Unit])], - expectedHistory: List[(Lineage, NatchezCommand)] - ): IO[Unit] = - InMemory.EntryPoint.create[IO].flatMap { ep => - val traced = ep.root(defaultRootName).use { r => - makeTraceAndResolver(r).flatMap { case (traceInstance, resolve) => - resolve(traceProgram(traceInstance)) + def testTraceIoLocal( + traceProgram: Trace[IO] => IO[Unit], + expectedHistory: List[(Lineage, NatchezCommand)] + ): IO[Unit] = testTrace[IO](traceProgram, Trace.ioTrace(_).map(_ -> identity), expectedHistory) + + def testTrace[F[_]]( + traceProgram: Trace[F] => F[Unit], + makeTraceAndResolver: Span[IO] => IO[(Trace[F], F[Unit] => IO[Unit])], + expectedHistory: List[(Lineage, NatchezCommand)] + ): IO[Unit] = + InMemory.EntryPoint.create[IO].flatMap { ep => + val traced = ep.root(defaultRootName).use { r => + makeTraceAndResolver(r).flatMap { case (traceInstance, resolve) => + resolve(traceProgram(traceInstance)) + } + } + traced *> ep.ref.get.map { history => + assertEquals(history.toList, expectedHistory) } } - traced *> ep.ref.get.map { history => - assertEquals(history.toList, expectedHistory) - } + } + + trait LocalSuite extends InMemorySuite { + trait LocalTest { + def program[F[_]: MonadCancelThrow](implicit L: Local[F, Span[F]]): F[Unit] + def expectedHistory: List[(Lineage, NatchezCommand)] } + + def localTest(name: String, tt: LocalTest): Unit = { + test(s"$name - Kleisli")( + testLocalKleisli(tt.program[Kleisli[IO, Span[IO], *]](implicitly, _), tt.expectedHistory) + ) + test(s"$name - IOLocal")(testLocalIoLocal(tt.program[IO](implicitly, _), tt.expectedHistory)) + } + + def testLocalKleisli( + localProgram: Local[Kleisli[IO, Span[IO], *], Span[Kleisli[IO, Span[IO], *]]] => Kleisli[ + IO, + Span[IO], + Unit + ], + expectedHistory: List[(Lineage, NatchezCommand)] + ): IO[Unit] = testTraceViaLocal[Kleisli[IO, Span[IO], *]]( + localProgram, + root => + IO.pure( + implicitly[Local[Kleisli[IO, Span[IO], *], Span[Kleisli[IO, Span[IO], *]]]] -> (k => + k.run(root) + ) + ), + expectedHistory + ) + + def testLocalIoLocal( + localProgram: Local[IO, Span[IO]] => IO[Unit], + expectedHistory: List[(Lineage, NatchezCommand)] + ): IO[Unit] = + testTraceViaLocal[IO]( + localProgram, + IOLocal(_) + .map { ioLocal => + new Local[IO, Span[IO]] { + override def local[A](fa: IO[A])(f: Span[IO] => Span[IO]): IO[A] = + ioLocal.get.flatMap { initial => + ioLocal.set(f(initial)) >> fa.guarantee(ioLocal.set(initial)) + } + + override def applicative: Applicative[IO] = implicitly + + override def ask[E2 >: Span[IO]]: IO[E2] = ioLocal.get + } + } + .tupleRight(identity[IO[Unit]]), + expectedHistory + ) + + def testTraceViaLocal[F[_]]( + localProgram: Local[F, Span[F]] => F[Unit], + makeTraceAndResolver: Span[IO] => IO[(Local[F, Span[F]], F[Unit] => IO[Unit])], + expectedHistory: List[(Lineage, NatchezCommand)] + ): IO[Unit] = + InMemory.EntryPoint.create[IO].flatMap { ep => + val traced = ep.root(defaultRootName).use { r => + makeTraceAndResolver(r).flatMap { case (localInstance, resolve) => + resolve(localProgram(localInstance)) + } + } + traced *> ep.ref.get.map { history => + assertEquals(history.toList, expectedHistory) + } + } + } + } diff --git a/modules/core-tests/shared/src/test/scala/LocalTraceSpec.scala b/modules/core-tests/shared/src/test/scala/LocalTraceSpec.scala new file mode 100644 index 00000000..64bc8cc6 --- /dev/null +++ b/modules/core-tests/shared/src/test/scala/LocalTraceSpec.scala @@ -0,0 +1,30 @@ +// Copyright (c) 2019-2020 by Rob Norris and Contributors +// This software is licensed under the MIT License (MIT). +// For more information see LICENSE or https://opensource.org/licenses/MIT + +package natchez +package mtl + +import cats.effect.{MonadCancelThrow, Trace => _} +import cats.mtl._ +import natchez.InMemory.Lineage.defaultRootName + +class LocalTraceSpec extends InMemorySuite.LocalSuite { + + private def useTrace[F[_]: Trace]: F[Unit] = Trace[F].log("hello world") + + localTest( + "should compile with", + new LocalTest { + override def program[F[_]: MonadCancelThrow](implicit L: Local[F, Span[F]]): F[Unit] = + useTrace[F] + + override def expectedHistory: List[(Lineage, NatchezCommand)] = List( + Lineage.Root -> NatchezCommand + .CreateRootSpan(defaultRootName, Kernel(Map()), Span.Options.Defaults), + Lineage.Root(defaultRootName) -> NatchezCommand.LogEvent("hello world"), + Lineage.Root -> NatchezCommand.ReleaseRootSpan(defaultRootName) + ) + } + ) +} diff --git a/modules/core-tests/shared/src/test/scala/SpanCoalesceTest.scala b/modules/core-tests/shared/src/test/scala/SpanCoalesceTest.scala index b6fbb889..1a383793 100644 --- a/modules/core-tests/shared/src/test/scala/SpanCoalesceTest.scala +++ b/modules/core-tests/shared/src/test/scala/SpanCoalesceTest.scala @@ -6,7 +6,7 @@ package natchez import cats.effect.MonadCancelThrow -class SpanCoalesceTest extends InMemorySuite { +class SpanCoalesceTest extends InMemorySuite.TraceSuite { traceTest( "suppress - nominal", diff --git a/modules/core-tests/shared/src/test/scala/SpanPropagationTest.scala b/modules/core-tests/shared/src/test/scala/SpanPropagationTest.scala index bef20cef..ec622694 100644 --- a/modules/core-tests/shared/src/test/scala/SpanPropagationTest.scala +++ b/modules/core-tests/shared/src/test/scala/SpanPropagationTest.scala @@ -7,7 +7,7 @@ package natchez import cats.effect.MonadCancelThrow import cats.syntax.all._ -class SpanPropagationTest extends InMemorySuite { +class SpanPropagationTest extends InMemorySuite.TraceSuite { traceTest( "propagation",