provide a list of spans so we can test more correctly

This commit is contained in:
Pat Garrity 2025-09-15 22:00:00 -05:00
parent 49e9ccae41
commit 984427fdc9
Signed by: pfm
GPG key ID: 5CA5D21BAB7F3A76
4 changed files with 89 additions and 62 deletions

View file

@ -18,15 +18,16 @@ class TestEngineTests extends IOSuite:
iotest("should run an engine with no tests") { iotest("should run an engine with no tests") {
newEngine().use { obs => newEngine().use { obs =>
val spanDb = obs.entryPoint.spanDb
for for
suiteExecution <- obs.engine.runSuite( suiteExecution <- obs.engine.runSuite(
suite = Generators.testSuite(), suite = Generators.testSuite(),
tests = emptyStream[TestGroupDefinition[IO]] tests = emptyStream[TestGroupDefinition[IO]]
) )
rootSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.RootSpan) rootSpan <- spanDb.get(EngineConstants.Tracing.RootSpan)
results <- obs.reporter.terminateAndGetResults() results <- obs.reporter.terminateAndGetResults()
yield yield
assertEquals(rootSpan.isDefined, true) assertEquals(rootSpan.size, 1)
assertEquals(results.isEmpty, true) assertEquals(results.isEmpty, true)
assertEquals(suiteExecution.seen, 0L) assertEquals(suiteExecution.seen, 0L)
assertEquals(suiteExecution.passed, 0L) assertEquals(suiteExecution.passed, 0L)
@ -36,41 +37,43 @@ class TestEngineTests extends IOSuite:
iotest("should run an engine with a single passing test") { iotest("should run an engine with a single passing test") {
newEngine().use { obs => newEngine().use { obs =>
val spanDb = obs.entryPoint.spanDb
val g1 = new G1 val g1 = new G1
val group = g1.compile() val group = g1.compile()
for for
suiteExecution <- obs.engine.runSuite( suiteExecution <- obs.engine.runSuite(
suite = Generators.testSuite(), suite = Generators.testSuite(),
tests = fs2.Stream.apply(group) tests = fs2.Stream.apply(group)
) )
rootSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.RootSpan) rootSpan <- spanDb.get(EngineConstants.Tracing.RootSpan)
groupSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.FullGroup) groupSpan <- spanDb.get(EngineConstants.Tracing.FullGroup)
beforeGroupSpan <- obs.entryPoint.getSpan( beforeGroupSpan <- spanDb.get(
EngineConstants.Tracing.BeforeGroup EngineConstants.Tracing.BeforeGroup
) )
afterGroupSpan <- obs.entryPoint.getSpan( afterGroupSpan <- spanDb.get(
EngineConstants.Tracing.AfterGroup EngineConstants.Tracing.AfterGroup
) )
inGroupSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.InGroup) inGroupSpan <- spanDb.get(EngineConstants.Tracing.InGroup)
fullTestSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.FullTest) fullTestSpan <- spanDb.get(EngineConstants.Tracing.FullTest)
beforeTestSpan <- obs.entryPoint.getSpan( beforeTestSpan <- spanDb.get(
EngineConstants.Tracing.BeforeTest EngineConstants.Tracing.BeforeTest
) )
afterTestSpan <- obs.entryPoint.getSpan( afterTestSpan <- spanDb.get(
EngineConstants.Tracing.AfterTest EngineConstants.Tracing.AfterTest
) )
testSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.TestSpan) testSpan <- spanDb.get(EngineConstants.Tracing.TestSpan)
results <- obs.reporter.terminateAndGetResults() results <- obs.reporter.terminateAndGetResults()
yield yield
assertEquals(rootSpan.isDefined, true) assertEquals(rootSpan.size, 1)
assertEquals(groupSpan.isDefined, true) assertEquals(groupSpan.size, 1)
assertEquals(beforeGroupSpan.isDefined, true) assertEquals(beforeGroupSpan.size, 1)
assertEquals(afterGroupSpan.isDefined, true) assertEquals(afterGroupSpan.size, 1)
assertEquals(inGroupSpan.isDefined, true) assertEquals(inGroupSpan.size, 1)
assertEquals(fullTestSpan.isDefined, true) assertEquals(fullTestSpan.size, 1)
assertEquals(beforeTestSpan.isDefined, true) assertEquals(beforeTestSpan.size, 1)
assertEquals(afterTestSpan.isDefined, true) assertEquals(afterTestSpan.size, 1)
assertEquals(testSpan.isDefined, true) assertEquals(testSpan.size, 1)
assertEquals(results.size, 1) assertEquals(results.size, 1)
assertEquals(suiteExecution.seen, 1L) assertEquals(suiteExecution.seen, 1L)
assertEquals(suiteExecution.passed, 1L) assertEquals(suiteExecution.passed, 1L)
@ -80,6 +83,7 @@ class TestEngineTests extends IOSuite:
iotest("should run an engine with a single failing test") { iotest("should run an engine with a single failing test") {
newEngine().use { obs => newEngine().use { obs =>
val spanDb = obs.entryPoint.spanDb
val g2 = new G2 val g2 = new G2
val group = g2.compile() val group = g2.compile()
for for
@ -87,34 +91,35 @@ class TestEngineTests extends IOSuite:
suite = Generators.testSuite(), suite = Generators.testSuite(),
tests = fs2.Stream.apply(group) tests = fs2.Stream.apply(group)
) )
rootSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.RootSpan) rootSpan <- spanDb.get(EngineConstants.Tracing.RootSpan)
groupSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.FullGroup) groupSpan <- spanDb.get(EngineConstants.Tracing.FullGroup)
beforeGroupSpan <- obs.entryPoint.getSpan( beforeGroupSpan <- spanDb.get(
EngineConstants.Tracing.BeforeGroup EngineConstants.Tracing.BeforeGroup
) )
afterGroupSpan <- obs.entryPoint.getSpan( afterGroupSpan <- spanDb.get(
EngineConstants.Tracing.AfterGroup EngineConstants.Tracing.AfterGroup
) )
inGroupSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.InGroup) inGroupSpan <- spanDb.get(EngineConstants.Tracing.InGroup)
fullTestSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.FullTest) fullTestSpan <- spanDb.get(EngineConstants.Tracing.FullTest)
beforeTestSpan <- obs.entryPoint.getSpan( beforeTestSpan <- spanDb.get(
EngineConstants.Tracing.BeforeTest EngineConstants.Tracing.BeforeTest
) )
afterTestSpan <- obs.entryPoint.getSpan( afterTestSpan <- spanDb.get(
EngineConstants.Tracing.AfterTest EngineConstants.Tracing.AfterTest
) )
testSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.TestSpan) testSpan <- spanDb.get(EngineConstants.Tracing.TestSpan)
results <- obs.reporter.terminateAndGetResults() results <- obs.reporter.terminateAndGetResults()
yield yield
assertEquals(rootSpan.isDefined, true) // TODO rip out a validation function for a full set of stuff.
assertEquals(groupSpan.isDefined, true) assertEquals(rootSpan.size, 1)
assertEquals(beforeGroupSpan.isDefined, true) assertEquals(groupSpan.size, 1)
assertEquals(afterGroupSpan.isDefined, true) assertEquals(beforeGroupSpan.size, 1)
assertEquals(inGroupSpan.isDefined, true) assertEquals(afterGroupSpan.size, 1)
assertEquals(fullTestSpan.isDefined, true) assertEquals(inGroupSpan.size, 1)
assertEquals(beforeTestSpan.isDefined, true) assertEquals(fullTestSpan.size, 1)
assertEquals(afterTestSpan.isDefined, true) assertEquals(beforeTestSpan.size, 1)
assertEquals(testSpan.isDefined, true) assertEquals(afterTestSpan.size, 1)
assertEquals(testSpan.size, 1)
assertEquals(results.size, 1) assertEquals(results.size, 1)
assertEquals(suiteExecution.seen, 1L) assertEquals(suiteExecution.seen, 1L)
assertEquals(suiteExecution.passed, 0L) assertEquals(suiteExecution.passed, 0L)

View file

@ -0,0 +1,27 @@
package support
import cats.effect.IO
import cats.effect.std.MapRef
final class SpanDb(
db: MapRef[IO, String, Option[List[TestSpan]]]
):
def get(spanName: String): IO[List[TestSpan]] =
db(spanName).get.map(_.getOrElse(Nil))
def putSpan(
spanName: String,
span: TestSpan
): IO[Unit] =
db(spanName).update {
case None => Some(List(span))
case Some(spans) => Some(span :: spans)
}
object SpanDb:
def initialize(): IO[SpanDb] =
MapRef[IO, String, List[TestSpan]].map(db => new SpanDb(db))
end SpanDb

View file

@ -2,27 +2,22 @@ package support
import cats.effect.IO import cats.effect.IO
import cats.effect.kernel.Resource import cats.effect.kernel.Resource
import cats.effect.std.MapRef
import natchez.EntryPoint import natchez.EntryPoint
import natchez.Kernel import natchez.Kernel
import natchez.Span import natchez.Span
import natchez.Span.Options import natchez.Span.Options
// TODO: This doesn't account for multiple spans with the same name.
final class TestEntryPoint private ( final class TestEntryPoint private (
spans: MapRef[IO, String, Option[TestSpan]] val spanDb: SpanDb
) extends EntryPoint[IO]: ) extends EntryPoint[IO]:
def getSpan(name: String): IO[Option[TestSpan]] =
spans(name).get
override def root( override def root(
name: String, name: String,
options: Options options: Options
): Resource[IO, Span[IO]] = ): Resource[IO, Span[IO]] =
TestSpan TestSpan
.provisionRoot(name, spans) .provisionRoot(name, spanDb)
.evalTap(span => spans.setKeyValue(name, span)) .evalTap(span => spanDb.putSpan(name, span))
override def continue( override def continue(
name: String, name: String,
@ -41,7 +36,7 @@ final class TestEntryPoint private (
object TestEntryPoint: object TestEntryPoint:
def initialize(): IO[TestEntryPoint] = def initialize(): IO[TestEntryPoint] =
MapRef.apply[IO, String, TestSpan].map(spans => new TestEntryPoint(spans)) SpanDb.initialize().map(db => new TestEntryPoint(db))
def provision(): Resource[IO, TestEntryPoint] = def provision(): Resource[IO, TestEntryPoint] =
Resource.make(initialize())(_ => IO.unit) Resource.make(initialize())(_ => IO.unit)

View file

@ -15,7 +15,7 @@ final class TestSpan private (
val rawTraceId: String, val rawTraceId: String,
val rawSpanId: String, val rawSpanId: String,
baggage: MapRef[IO, String, Option[TraceValue]], baggage: MapRef[IO, String, Option[TraceValue]],
spans: MapRef[IO, String, Option[TestSpan]] spanDb: SpanDb
) extends Span[IO]: ) extends Span[IO]:
override def put(fields: (String, TraceValue)*): IO[Unit] = override def put(fields: (String, TraceValue)*): IO[Unit] =
@ -37,8 +37,8 @@ final class TestSpan private (
options: Options options: Options
): Resource[IO, Span[IO]] = ): Resource[IO, Span[IO]] =
TestSpan TestSpan
.provision(name, rawTraceId, TestSpan.makeSpanId(), spans) .provision(name, rawTraceId, TestSpan.makeSpanId(), spanDb)
.evalTap(span => spans.setKeyValue(name, span)) .evalTap(span => spanDb.putSpan(name, span))
override def traceId: IO[Option[String]] = IO(Some(rawTraceId)) override def traceId: IO[Option[String]] = IO(Some(rawTraceId))
@ -50,15 +50,15 @@ object TestSpan:
def initializeRoot( def initializeRoot(
name: String, name: String,
spans: MapRef[IO, String, Option[TestSpan]] spanDb: SpanDb
): IO[TestSpan] = ): IO[TestSpan] =
initialize(name, makeTraceId(), makeSpanId(), spans) initialize(name, makeTraceId(), makeSpanId(), spanDb)
def initialize( def initialize(
name: String, name: String,
traceId: String, traceId: String,
spanId: String, spanId: String,
spans: MapRef[IO, String, Option[TestSpan]] spanDb: SpanDb
): IO[TestSpan] = ): IO[TestSpan] =
MapRef.apply[IO, String, TraceValue].map { baggage => MapRef.apply[IO, String, TraceValue].map { baggage =>
new TestSpan( new TestSpan(
@ -66,23 +66,23 @@ object TestSpan:
rawTraceId = traceId, rawTraceId = traceId,
rawSpanId = spanId, rawSpanId = spanId,
baggage = baggage, baggage = baggage,
spans = spans spanDb = spanDb
) )
} }
def provisionRoot( def provisionRoot(
name: String, name: String,
spans: MapRef[IO, String, Option[TestSpan]] spanDb: SpanDb
): Resource[IO, TestSpan] = ): Resource[IO, TestSpan] =
provision(name, makeTraceId(), makeSpanId(), spans) provision(name, makeTraceId(), makeSpanId(), spanDb)
def provision( def provision(
name: String, name: String,
traceId: String, traceId: String,
spanId: String, spanId: String,
spans: MapRef[IO, String, Option[TestSpan]] spanDb: SpanDb
): Resource[IO, TestSpan] = ): Resource[IO, TestSpan] =
Resource.make(initialize(name, traceId, spanId, spans))(_ => IO.unit) Resource.make(initialize(name, traceId, spanId, spanDb))(_ => IO.unit)
private def makeTraceId(): String = private def makeTraceId(): String =
UUID.randomUUID().toString().filterNot(_ == '-') UUID.randomUUID().toString().filterNot(_ == '-')