Small amount of updates for docs and trace support.
This commit is contained in:
parent
f4f2462d15
commit
96f0fab473
6 changed files with 125 additions and 94 deletions
|
@ -1,19 +1,25 @@
|
|||
package gs.test.v0.api
|
||||
|
||||
import cats.~>
|
||||
import cats.Applicative
|
||||
import cats.data.EitherT
|
||||
import cats.effect.kernel.Async
|
||||
import cats.effect.kernel.Ref
|
||||
import cats.effect.kernel.Resource
|
||||
import cats.effect.syntax.all.*
|
||||
import cats.syntax.all.*
|
||||
import java.net.URI
|
||||
import gs.test.v0.api.trace.GsTestTrace
|
||||
import natchez.*
|
||||
|
||||
/** Represents some function that may produce an error, intended to run within a
|
||||
* traced context.
|
||||
*/
|
||||
sealed trait UnitOfWork[F[_]]:
|
||||
|
||||
def work(
|
||||
/** Execute the work defined by this function in the context of the given
|
||||
* span.
|
||||
*
|
||||
* @param span
|
||||
* The root span for tracing this work.
|
||||
* @return
|
||||
* An effect producing either a failure, or any arbitrary value.
|
||||
*/
|
||||
def doWork(
|
||||
span: Span[F]
|
||||
): F[Either[TestFailure, Any]]
|
||||
|
||||
|
@ -36,91 +42,33 @@ object UnitOfWork:
|
|||
): UnitOfWork[F] =
|
||||
new UnitOfWork[F] {
|
||||
|
||||
override def work(span: Span[F]): F[Either[TestFailure, Any]] =
|
||||
makeInternalTrace[F](span).flatMap { trace =>
|
||||
override def doWork(span: Span[F]): F[Either[TestFailure, Any]] =
|
||||
GsTestTrace.initialize[F](span).flatMap { trace =>
|
||||
given Trace[F] = trace
|
||||
unitOfWork
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Instantiate a new [[UnitOfWork]] with the given function that requires a
|
||||
* `Span[F]` as input (EitherT support).
|
||||
*
|
||||
* @param uow
|
||||
* The unit of work implementation.
|
||||
* @return
|
||||
* The new [[UnitOfWork]] instance.
|
||||
*/
|
||||
def applyT[F[_]: Async](
|
||||
unitOfWork: TracedT[F]
|
||||
): UnitOfWork[F] =
|
||||
new UnitOfWork[F] {
|
||||
|
||||
override def work(span: Span[F]): F[Either[TestFailure, Any]] =
|
||||
makeInternalTrace[F](span).flatMap { trace =>
|
||||
override def doWork(span: Span[F]): F[Either[TestFailure, Any]] =
|
||||
GsTestTrace.initialize[F](span).flatMap { trace =>
|
||||
given Trace[F] = trace
|
||||
unitOfWork.value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def makeInternalTrace[F[_]: Async](sourceSpan: Span[F])
|
||||
: F[natchez.Trace[F]] =
|
||||
Ref.of[F, Span[F]](sourceSpan).map(src => new InternalTrace[F](src))
|
||||
|
||||
// Copied from the Natchez ioTrace
|
||||
private class InternalTrace[F[_]: Async](
|
||||
val src: Ref[F, Span[F]]
|
||||
) extends natchez.Trace[F]:
|
||||
|
||||
def put(fields: (String, TraceValue)*): F[Unit] =
|
||||
src.get.flatMap(_.put(fields*))
|
||||
|
||||
def log(fields: (String, TraceValue)*): F[Unit] =
|
||||
src.get.flatMap(_.log(fields*))
|
||||
|
||||
def log(event: String): F[Unit] =
|
||||
src.get.flatMap(_.log(event))
|
||||
|
||||
def attachError(
|
||||
err: Throwable,
|
||||
fields: (String, TraceValue)*
|
||||
): F[Unit] =
|
||||
src.get.flatMap(_.attachError(err, fields*))
|
||||
|
||||
def kernel: F[Kernel] = src.get.flatMap(_.kernel)
|
||||
|
||||
def spanR(
|
||||
name: String,
|
||||
options: Span.Options = Span.Options.Defaults
|
||||
): Resource[F, F ~> F] =
|
||||
for {
|
||||
parent <- Resource.eval(src.get)
|
||||
child <- parent.span(name, options)
|
||||
} yield new (F ~> F) {
|
||||
def apply[A](fa: F[A]): F[A] =
|
||||
src.get.flatMap { old =>
|
||||
src
|
||||
.set(child)
|
||||
.bracket(_ => fa.onError { case e => child.attachError(e) })(_ =>
|
||||
src.set(old)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def span[A](
|
||||
name: String,
|
||||
options: Span.Options = Span.Options.Defaults
|
||||
)(
|
||||
k: F[A]
|
||||
): F[A] =
|
||||
spanR(name, options).use(_(k))
|
||||
|
||||
def traceId: F[Option[String]] =
|
||||
src.get.flatMap(_.traceId)
|
||||
|
||||
override def spanId(
|
||||
implicit
|
||||
F: Applicative[F]
|
||||
): F[Option[String]] =
|
||||
src.get.flatMap(_.spanId)
|
||||
|
||||
override def traceUri: F[Option[URI]] = src.get.flatMap(_.traceUri)
|
||||
|
||||
end InternalTrace
|
||||
|
||||
end UnitOfWork
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package gs.test.v0.api.trace
|
||||
|
||||
import cats.~>
|
||||
import cats.Applicative
|
||||
import cats.effect.kernel.Async
|
||||
import cats.effect.kernel.Ref
|
||||
import cats.effect.kernel.Resource
|
||||
import cats.effect.syntax.all.*
|
||||
import cats.syntax.all.*
|
||||
import java.net.URI
|
||||
import natchez.*
|
||||
|
||||
/** Essentially copied from the "IOTrace" implementation within the excellent
|
||||
* Natchez, but updated to work with `Ref`.
|
||||
*/
|
||||
final class GsTestTrace[F[_]: Async] private (
|
||||
src: Ref[F, Span[F]]
|
||||
) extends natchez.Trace[F]:
|
||||
|
||||
def put(fields: (String, TraceValue)*): F[Unit] =
|
||||
src.get.flatMap(_.put(fields*))
|
||||
|
||||
def log(fields: (String, TraceValue)*): F[Unit] =
|
||||
src.get.flatMap(_.log(fields*))
|
||||
|
||||
def log(event: String): F[Unit] =
|
||||
src.get.flatMap(_.log(event))
|
||||
|
||||
def attachError(
|
||||
err: Throwable,
|
||||
fields: (String, TraceValue)*
|
||||
): F[Unit] =
|
||||
src.get.flatMap(_.attachError(err, fields*))
|
||||
|
||||
def kernel: F[Kernel] = src.get.flatMap(_.kernel)
|
||||
|
||||
def spanR(
|
||||
name: String,
|
||||
options: Span.Options = Span.Options.Defaults
|
||||
): Resource[F, F ~> F] =
|
||||
for {
|
||||
parent <- Resource.eval(src.get)
|
||||
child <- parent.span(name, options)
|
||||
} yield new (F ~> F) {
|
||||
def apply[A](fa: F[A]): F[A] =
|
||||
src.get.flatMap { old =>
|
||||
src
|
||||
.set(child)
|
||||
.bracket(_ => fa.onError { case e => child.attachError(e) })(_ =>
|
||||
src.set(old)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def span[A](
|
||||
name: String,
|
||||
options: Span.Options = Span.Options.Defaults
|
||||
)(
|
||||
k: F[A]
|
||||
): F[A] =
|
||||
spanR(name, options).use(_(k))
|
||||
|
||||
def traceId: F[Option[String]] =
|
||||
src.get.flatMap(_.traceId)
|
||||
|
||||
override def spanId(
|
||||
implicit
|
||||
F: Applicative[F]
|
||||
): F[Option[String]] =
|
||||
src.get.flatMap(_.spanId)
|
||||
|
||||
override def traceUri: F[Option[URI]] = src.get.flatMap(_.traceUri)
|
||||
|
||||
object GsTestTrace:
|
||||
|
||||
/** Initialize a new `natchez.Trace` instance based on a given Span.
|
||||
*/
|
||||
def initialize[F[_]: Async](
|
||||
sourceSpan: Span[F]
|
||||
): F[natchez.Trace[F]] =
|
||||
Ref.of[F, Span[F]](sourceSpan).map(src => new GsTestTrace[F](src))
|
||||
|
||||
end GsTestTrace
|
|
@ -58,7 +58,7 @@ class SourcePositionTests extends IOSuite:
|
|||
case t1 :: Nil =>
|
||||
ep.root("unit-test")
|
||||
.use { span =>
|
||||
t1.unitOfWork.work(span).map {
|
||||
t1.unitOfWork.doWork(span).map {
|
||||
case Left(TestFailure.AssertionFailed(_, _, _, pos)) =>
|
||||
assertEquals(pos.file.endsWith(SourceFileName), true)
|
||||
assertEquals(pos.line, line)
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
package gs.test.v0.runtime
|
||||
|
||||
import cats.Show
|
||||
import gs.test.v0.definition.Marker
|
||||
import gs.test.v0.definition.PermanentId
|
||||
import gs.test.v0.definition.Tag
|
||||
import gs.test.v0.definition.TestFailure
|
||||
import gs.test.v0.definition.pos.SourcePosition
|
||||
import gs.test.v0.api.Marker
|
||||
import gs.test.v0.api.PermanentId
|
||||
import gs.test.v0.api.SourcePosition
|
||||
import gs.test.v0.api.Tag
|
||||
import gs.test.v0.api.TestFailure
|
||||
import gs.uuid.v0.UUID
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
/** Represents a single _Test Execution_. Each _Test Execution_ represents
|
||||
* evaluating the unit of work for some
|
||||
* [[gs.test.v0.definition.TestDefinition]] exactly once. It describes the
|
||||
* result of the test.
|
||||
* evaluating the unit of work for some [[gs.test.v0.api.TestDefinition]]
|
||||
* exactly once. It describes the result of the test.
|
||||
*
|
||||
* @param id
|
||||
* The unique identifier for this execution.
|
||||
* @param permanentId
|
||||
* The [[gs.test.v0.definition.PermanentId]] for the
|
||||
* [[gs.test.v0.definition.TestDefinition]] that was executed.
|
||||
* The [[gs.test.v0.api.PermanentId]] for the
|
||||
* [[gs.test.v0.api.TestDefinition]] that was executed.
|
||||
* @param documentation
|
||||
* Documentation for the test that was executed.
|
||||
* @param tags
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package gs.test.v0.runtime.engine
|
||||
|
||||
import gs.test.v0.definition.TestGroupDefinition
|
||||
import gs.test.v0.api.TestGroupDefinition
|
||||
import gs.test.v0.runtime.TestExecution
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ package gs.test.v0.runtime.engine
|
|||
|
||||
import cats.effect.Async
|
||||
import cats.syntax.all.*
|
||||
import gs.test.v0.definition.TestDefinition
|
||||
import gs.test.v0.definition.TestFailure
|
||||
import gs.test.v0.definition.TestGroupDefinition
|
||||
import gs.test.v0.definition.TestSuite
|
||||
import gs.test.v0.api.TestDefinition
|
||||
import gs.test.v0.api.TestFailure
|
||||
import gs.test.v0.api.TestGroupDefinition
|
||||
import gs.test.v0.api.TestSuite
|
||||
import gs.test.v0.runtime.SuiteExecution
|
||||
import gs.test.v0.runtime.TestExecution
|
||||
import gs.timing.v0.Timing
|
||||
|
@ -149,7 +149,7 @@ final class TestEngine[F[_]: Async](
|
|||
// TODO: Constants
|
||||
_ <- span.put("test_execution_id" -> testExecutionId.show)
|
||||
_ <- span.put("test_name" -> test.name.show)
|
||||
result <- test.unitOfWork.work(span)
|
||||
result <- test.unitOfWork.doWork(span)
|
||||
yield result
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue