Skip to content

Commit

Permalink
introduce InMemorySuite.LocalSuite and use it to test a `def testProg…
Browse files Browse the repository at this point in the history
…ram[F[_]: MonadCancelThrow](implicit L: Local[F, Span[F]]): F[Unit]`
  • Loading branch information
bpholt committed Jan 31, 2023
1 parent 1389c6b commit abab2f1
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 39 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
153 changes: 117 additions & 36 deletions modules/core-tests/shared/src/test/scala/InMemorySuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

}
30 changes: 30 additions & 0 deletions modules/core-tests/shared/src/test/scala/LocalTraceSpec.scala
Original file line number Diff line number Diff line change
@@ -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)
)
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package natchez

import cats.effect.MonadCancelThrow

class SpanCoalesceTest extends InMemorySuite {
class SpanCoalesceTest extends InMemorySuite.TraceSuite {

traceTest(
"suppress - nominal",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit abab2f1

Please sign in to comment.