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
|
package gs.test.v0.api
|
||||||
|
|
||||||
import cats.~>
|
|
||||||
import cats.Applicative
|
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
import cats.effect.kernel.Async
|
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 cats.syntax.all.*
|
||||||
import java.net.URI
|
import gs.test.v0.api.trace.GsTestTrace
|
||||||
import natchez.*
|
import natchez.*
|
||||||
|
|
||||||
|
/** Represents some function that may produce an error, intended to run within a
|
||||||
|
* traced context.
|
||||||
|
*/
|
||||||
sealed trait UnitOfWork[F[_]]:
|
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]
|
span: Span[F]
|
||||||
): F[Either[TestFailure, Any]]
|
): F[Either[TestFailure, Any]]
|
||||||
|
|
||||||
|
@ -36,91 +42,33 @@ object UnitOfWork:
|
||||||
): UnitOfWork[F] =
|
): UnitOfWork[F] =
|
||||||
new UnitOfWork[F] {
|
new UnitOfWork[F] {
|
||||||
|
|
||||||
override def work(span: Span[F]): F[Either[TestFailure, Any]] =
|
override def doWork(span: Span[F]): F[Either[TestFailure, Any]] =
|
||||||
makeInternalTrace[F](span).flatMap { trace =>
|
GsTestTrace.initialize[F](span).flatMap { trace =>
|
||||||
given Trace[F] = trace
|
given Trace[F] = trace
|
||||||
unitOfWork
|
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](
|
def applyT[F[_]: Async](
|
||||||
unitOfWork: TracedT[F]
|
unitOfWork: TracedT[F]
|
||||||
): UnitOfWork[F] =
|
): UnitOfWork[F] =
|
||||||
new UnitOfWork[F] {
|
new UnitOfWork[F] {
|
||||||
|
|
||||||
override def work(span: Span[F]): F[Either[TestFailure, Any]] =
|
override def doWork(span: Span[F]): F[Either[TestFailure, Any]] =
|
||||||
makeInternalTrace[F](span).flatMap { trace =>
|
GsTestTrace.initialize[F](span).flatMap { trace =>
|
||||||
given Trace[F] = trace
|
given Trace[F] = trace
|
||||||
unitOfWork.value
|
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
|
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 =>
|
case t1 :: Nil =>
|
||||||
ep.root("unit-test")
|
ep.root("unit-test")
|
||||||
.use { span =>
|
.use { span =>
|
||||||
t1.unitOfWork.work(span).map {
|
t1.unitOfWork.doWork(span).map {
|
||||||
case Left(TestFailure.AssertionFailed(_, _, _, pos)) =>
|
case Left(TestFailure.AssertionFailed(_, _, _, pos)) =>
|
||||||
assertEquals(pos.file.endsWith(SourceFileName), true)
|
assertEquals(pos.file.endsWith(SourceFileName), true)
|
||||||
assertEquals(pos.line, line)
|
assertEquals(pos.line, line)
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
package gs.test.v0.runtime
|
package gs.test.v0.runtime
|
||||||
|
|
||||||
import cats.Show
|
import cats.Show
|
||||||
import gs.test.v0.definition.Marker
|
import gs.test.v0.api.Marker
|
||||||
import gs.test.v0.definition.PermanentId
|
import gs.test.v0.api.PermanentId
|
||||||
import gs.test.v0.definition.Tag
|
import gs.test.v0.api.SourcePosition
|
||||||
import gs.test.v0.definition.TestFailure
|
import gs.test.v0.api.Tag
|
||||||
import gs.test.v0.definition.pos.SourcePosition
|
import gs.test.v0.api.TestFailure
|
||||||
import gs.uuid.v0.UUID
|
import gs.uuid.v0.UUID
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
|
|
||||||
/** Represents a single _Test Execution_. Each _Test Execution_ represents
|
/** Represents a single _Test Execution_. Each _Test Execution_ represents
|
||||||
* evaluating the unit of work for some
|
* evaluating the unit of work for some [[gs.test.v0.api.TestDefinition]]
|
||||||
* [[gs.test.v0.definition.TestDefinition]] exactly once. It describes the
|
* exactly once. It describes the result of the test.
|
||||||
* result of the test.
|
|
||||||
*
|
*
|
||||||
* @param id
|
* @param id
|
||||||
* The unique identifier for this execution.
|
* The unique identifier for this execution.
|
||||||
* @param permanentId
|
* @param permanentId
|
||||||
* The [[gs.test.v0.definition.PermanentId]] for the
|
* The [[gs.test.v0.api.PermanentId]] for the
|
||||||
* [[gs.test.v0.definition.TestDefinition]] that was executed.
|
* [[gs.test.v0.api.TestDefinition]] that was executed.
|
||||||
* @param documentation
|
* @param documentation
|
||||||
* Documentation for the test that was executed.
|
* Documentation for the test that was executed.
|
||||||
* @param tags
|
* @param tags
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package gs.test.v0.runtime.engine
|
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 gs.test.v0.runtime.TestExecution
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ package gs.test.v0.runtime.engine
|
||||||
|
|
||||||
import cats.effect.Async
|
import cats.effect.Async
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
import gs.test.v0.definition.TestDefinition
|
import gs.test.v0.api.TestDefinition
|
||||||
import gs.test.v0.definition.TestFailure
|
import gs.test.v0.api.TestFailure
|
||||||
import gs.test.v0.definition.TestGroupDefinition
|
import gs.test.v0.api.TestGroupDefinition
|
||||||
import gs.test.v0.definition.TestSuite
|
import gs.test.v0.api.TestSuite
|
||||||
import gs.test.v0.runtime.SuiteExecution
|
import gs.test.v0.runtime.SuiteExecution
|
||||||
import gs.test.v0.runtime.TestExecution
|
import gs.test.v0.runtime.TestExecution
|
||||||
import gs.timing.v0.Timing
|
import gs.timing.v0.Timing
|
||||||
|
@ -149,7 +149,7 @@ final class TestEngine[F[_]: Async](
|
||||||
// TODO: Constants
|
// TODO: Constants
|
||||||
_ <- span.put("test_execution_id" -> testExecutionId.show)
|
_ <- span.put("test_execution_id" -> testExecutionId.show)
|
||||||
_ <- span.put("test_name" -> test.name.show)
|
_ <- span.put("test_name" -> test.name.show)
|
||||||
result <- test.unitOfWork.work(span)
|
result <- test.unitOfWork.doWork(span)
|
||||||
yield result
|
yield result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue