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") {
newEngine().use { obs =>
val spanDb = obs.entryPoint.spanDb
for
suiteExecution <- obs.engine.runSuite(
suite = Generators.testSuite(),
tests = emptyStream[TestGroupDefinition[IO]]
)
rootSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.RootSpan)
rootSpan <- spanDb.get(EngineConstants.Tracing.RootSpan)
results <- obs.reporter.terminateAndGetResults()
yield
assertEquals(rootSpan.isDefined, true)
assertEquals(rootSpan.size, 1)
assertEquals(results.isEmpty, true)
assertEquals(suiteExecution.seen, 0L)
assertEquals(suiteExecution.passed, 0L)
@ -36,41 +37,43 @@ class TestEngineTests extends IOSuite:
iotest("should run an engine with a single passing test") {
newEngine().use { obs =>
val spanDb = obs.entryPoint.spanDb
val g1 = new G1
val group = g1.compile()
for
suiteExecution <- obs.engine.runSuite(
suite = Generators.testSuite(),
tests = fs2.Stream.apply(group)
)
rootSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.RootSpan)
groupSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.FullGroup)
beforeGroupSpan <- obs.entryPoint.getSpan(
rootSpan <- spanDb.get(EngineConstants.Tracing.RootSpan)
groupSpan <- spanDb.get(EngineConstants.Tracing.FullGroup)
beforeGroupSpan <- spanDb.get(
EngineConstants.Tracing.BeforeGroup
)
afterGroupSpan <- obs.entryPoint.getSpan(
afterGroupSpan <- spanDb.get(
EngineConstants.Tracing.AfterGroup
)
inGroupSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.InGroup)
fullTestSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.FullTest)
beforeTestSpan <- obs.entryPoint.getSpan(
inGroupSpan <- spanDb.get(EngineConstants.Tracing.InGroup)
fullTestSpan <- spanDb.get(EngineConstants.Tracing.FullTest)
beforeTestSpan <- spanDb.get(
EngineConstants.Tracing.BeforeTest
)
afterTestSpan <- obs.entryPoint.getSpan(
afterTestSpan <- spanDb.get(
EngineConstants.Tracing.AfterTest
)
testSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.TestSpan)
testSpan <- spanDb.get(EngineConstants.Tracing.TestSpan)
results <- obs.reporter.terminateAndGetResults()
yield
assertEquals(rootSpan.isDefined, true)
assertEquals(groupSpan.isDefined, true)
assertEquals(beforeGroupSpan.isDefined, true)
assertEquals(afterGroupSpan.isDefined, true)
assertEquals(inGroupSpan.isDefined, true)
assertEquals(fullTestSpan.isDefined, true)
assertEquals(beforeTestSpan.isDefined, true)
assertEquals(afterTestSpan.isDefined, true)
assertEquals(testSpan.isDefined, true)
assertEquals(rootSpan.size, 1)
assertEquals(groupSpan.size, 1)
assertEquals(beforeGroupSpan.size, 1)
assertEquals(afterGroupSpan.size, 1)
assertEquals(inGroupSpan.size, 1)
assertEquals(fullTestSpan.size, 1)
assertEquals(beforeTestSpan.size, 1)
assertEquals(afterTestSpan.size, 1)
assertEquals(testSpan.size, 1)
assertEquals(results.size, 1)
assertEquals(suiteExecution.seen, 1L)
assertEquals(suiteExecution.passed, 1L)
@ -80,6 +83,7 @@ class TestEngineTests extends IOSuite:
iotest("should run an engine with a single failing test") {
newEngine().use { obs =>
val spanDb = obs.entryPoint.spanDb
val g2 = new G2
val group = g2.compile()
for
@ -87,34 +91,35 @@ class TestEngineTests extends IOSuite:
suite = Generators.testSuite(),
tests = fs2.Stream.apply(group)
)
rootSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.RootSpan)
groupSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.FullGroup)
beforeGroupSpan <- obs.entryPoint.getSpan(
rootSpan <- spanDb.get(EngineConstants.Tracing.RootSpan)
groupSpan <- spanDb.get(EngineConstants.Tracing.FullGroup)
beforeGroupSpan <- spanDb.get(
EngineConstants.Tracing.BeforeGroup
)
afterGroupSpan <- obs.entryPoint.getSpan(
afterGroupSpan <- spanDb.get(
EngineConstants.Tracing.AfterGroup
)
inGroupSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.InGroup)
fullTestSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.FullTest)
beforeTestSpan <- obs.entryPoint.getSpan(
inGroupSpan <- spanDb.get(EngineConstants.Tracing.InGroup)
fullTestSpan <- spanDb.get(EngineConstants.Tracing.FullTest)
beforeTestSpan <- spanDb.get(
EngineConstants.Tracing.BeforeTest
)
afterTestSpan <- obs.entryPoint.getSpan(
afterTestSpan <- spanDb.get(
EngineConstants.Tracing.AfterTest
)
testSpan <- obs.entryPoint.getSpan(EngineConstants.Tracing.TestSpan)
testSpan <- spanDb.get(EngineConstants.Tracing.TestSpan)
results <- obs.reporter.terminateAndGetResults()
yield
assertEquals(rootSpan.isDefined, true)
assertEquals(groupSpan.isDefined, true)
assertEquals(beforeGroupSpan.isDefined, true)
assertEquals(afterGroupSpan.isDefined, true)
assertEquals(inGroupSpan.isDefined, true)
assertEquals(fullTestSpan.isDefined, true)
assertEquals(beforeTestSpan.isDefined, true)
assertEquals(afterTestSpan.isDefined, true)
assertEquals(testSpan.isDefined, true)
// TODO rip out a validation function for a full set of stuff.
assertEquals(rootSpan.size, 1)
assertEquals(groupSpan.size, 1)
assertEquals(beforeGroupSpan.size, 1)
assertEquals(afterGroupSpan.size, 1)
assertEquals(inGroupSpan.size, 1)
assertEquals(fullTestSpan.size, 1)
assertEquals(beforeTestSpan.size, 1)
assertEquals(afterTestSpan.size, 1)
assertEquals(testSpan.size, 1)
assertEquals(results.size, 1)
assertEquals(suiteExecution.seen, 1L)
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.kernel.Resource
import cats.effect.std.MapRef
import natchez.EntryPoint
import natchez.Kernel
import natchez.Span
import natchez.Span.Options
// TODO: This doesn't account for multiple spans with the same name.
final class TestEntryPoint private (
spans: MapRef[IO, String, Option[TestSpan]]
val spanDb: SpanDb
) extends EntryPoint[IO]:
def getSpan(name: String): IO[Option[TestSpan]] =
spans(name).get
override def root(
name: String,
options: Options
): Resource[IO, Span[IO]] =
TestSpan
.provisionRoot(name, spans)
.evalTap(span => spans.setKeyValue(name, span))
.provisionRoot(name, spanDb)
.evalTap(span => spanDb.putSpan(name, span))
override def continue(
name: String,
@ -41,7 +36,7 @@ final class TestEntryPoint private (
object 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] =
Resource.make(initialize())(_ => IO.unit)

View file

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