Runtime Implementation #2

Merged
pfm merged 13 commits from runtime into main 2026-04-05 13:42:38 +00:00
4 changed files with 89 additions and 62 deletions
Showing only changes of commit 984427fdc9 - Show all commits

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 g1 = new G1 val spanDb = obs.entryPoint.spanDb
val group = g1.compile() val g1 = new G1
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,41 +83,43 @@ 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 g2 = new G2 val spanDb = obs.entryPoint.spanDb
val group = g2.compile() val g2 = new G2
val group = g2.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) // 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(_ == '-')