Figured out trace syntax and passing context bounds to lambdas.
This commit is contained in:
parent
3f41e23478
commit
ddb977b80c
4 changed files with 151 additions and 51 deletions
|
@ -1,12 +1,10 @@
|
|||
package gs.test.v0.definition
|
||||
|
||||
import cats.data.EitherT
|
||||
import cats.data.Kleisli
|
||||
import cats.effect.Async
|
||||
import cats.syntax.all.*
|
||||
import gs.test.v0.definition.pos.SourcePosition
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import natchez.Span
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.jdk.CollectionConverters.*
|
||||
|
||||
|
@ -266,15 +264,16 @@ object TestGroup:
|
|||
* The function this test will execute.
|
||||
*/
|
||||
def pure(unitOfWork: => Either[TestFailure, Unit]): Unit =
|
||||
effectful(Kleisli(_ => Async[F].pure(unitOfWork)))
|
||||
effectful(Async[F].pure(unitOfWork))
|
||||
|
||||
/** Finalize and register this test with an effectful unit of work.
|
||||
*
|
||||
* @param unitOfWork
|
||||
* The function this test will execute.
|
||||
*/
|
||||
def effectful(unitOfWork: => Kleisli[F, Span[F], Either[TestFailure, Any]])
|
||||
: Unit =
|
||||
def effectful(
|
||||
unitOfWork: natchez.Trace[F] ?=> F[Either[TestFailure, Any]]
|
||||
): Unit =
|
||||
registry.register(
|
||||
new TestDefinition[F](
|
||||
name = name,
|
||||
|
@ -288,16 +287,14 @@ object TestGroup:
|
|||
)
|
||||
)
|
||||
|
||||
/** Helper type for representing `span => EitherT[F, TestFailure, Any]`
|
||||
*/
|
||||
type ET[A] = EitherT[F, TestFailure, A]
|
||||
|
||||
/** Finalize and register this test with an effectful unit of work.
|
||||
*
|
||||
* @param unitOfWork
|
||||
* The function this test will execute.
|
||||
*/
|
||||
def apply(unitOfWork: => Kleisli[ET, Span[F], Any]): Unit =
|
||||
def apply(
|
||||
unitOfWork: natchez.Trace[F] ?=> EitherT[F, TestFailure, Any]
|
||||
): Unit =
|
||||
registry.register(
|
||||
new TestDefinition[F](
|
||||
name = name,
|
||||
|
@ -306,7 +303,7 @@ object TestGroup:
|
|||
tags = tags.distinct.toList,
|
||||
markers = markers.distinct.toList,
|
||||
iterations = iterations,
|
||||
unitOfWork = UnitOfWork[F].apply(unitOfWork.mapF(_.value)),
|
||||
unitOfWork = UnitOfWork.applyT(unitOfWork),
|
||||
sourcePosition = pos
|
||||
)
|
||||
)
|
||||
|
@ -406,7 +403,20 @@ object TestGroup:
|
|||
* The function this test will execute.
|
||||
*/
|
||||
def pure(unitOfWork: Input => Either[TestFailure, Unit]): Unit =
|
||||
effectful(input => Kleisli(_ => Async[F].pure(unitOfWork(input))))
|
||||
registry.register(
|
||||
new TestDefinition[F](
|
||||
name = name,
|
||||
permanentId = permanentId,
|
||||
documentation = documentation,
|
||||
tags = tags.distinct.toList,
|
||||
markers = markers.distinct.toList,
|
||||
iterations = iterations,
|
||||
unitOfWork = UnitOfWork.apply(
|
||||
inputFunction.map(input => unitOfWork(input))
|
||||
),
|
||||
sourcePosition = pos
|
||||
)
|
||||
)
|
||||
|
||||
/** Finalize and register this test with an effectful unit of work.
|
||||
*
|
||||
|
@ -414,7 +424,7 @@ object TestGroup:
|
|||
* The function this test will execute.
|
||||
*/
|
||||
def effectful(
|
||||
unitOfWork: Input => Kleisli[F, Span[F], Either[TestFailure, Any]]
|
||||
unitOfWork: natchez.Trace[F] ?=> Input => F[Either[TestFailure, Any]]
|
||||
): Unit =
|
||||
registry.register(
|
||||
new TestDefinition[F](
|
||||
|
@ -425,24 +435,20 @@ object TestGroup:
|
|||
markers = markers.distinct.toList,
|
||||
iterations = iterations,
|
||||
unitOfWork = UnitOfWork.apply(
|
||||
Kleisli(span =>
|
||||
inputFunction.flatMap(input => unitOfWork(input).run(span))
|
||||
)
|
||||
inputFunction.flatMap(input => unitOfWork(input))
|
||||
),
|
||||
sourcePosition = pos
|
||||
)
|
||||
)
|
||||
|
||||
/** Helper type for representing `span => EitherT[F, TestFailure, Any]`
|
||||
*/
|
||||
type ET[A] = EitherT[F, TestFailure, A]
|
||||
|
||||
/** Finalize and register this test with an effectful unit of work.
|
||||
*
|
||||
* @param unitOfWork
|
||||
* The function this test will execute.
|
||||
*/
|
||||
def apply(unitOfWork: => Input => Kleisli[ET, Span[F], Any]): Unit =
|
||||
def apply(
|
||||
unitOfWork: natchez.Trace[F] ?=> Input => EitherT[F, TestFailure, Any]
|
||||
): Unit =
|
||||
registry.register(
|
||||
new TestDefinition[F](
|
||||
name = name,
|
||||
|
@ -451,12 +457,8 @@ object TestGroup:
|
|||
tags = tags.distinct.toList,
|
||||
markers = markers.distinct.toList,
|
||||
iterations = iterations,
|
||||
unitOfWork = UnitOfWork[F].apply(
|
||||
Kleisli(span =>
|
||||
inputFunction.flatMap { input =>
|
||||
unitOfWork(input).mapF(_.value).run(span)
|
||||
}
|
||||
)
|
||||
unitOfWork = UnitOfWork.applyT(
|
||||
EitherT.liftF(inputFunction).flatMap(unitOfWork)
|
||||
),
|
||||
sourcePosition = pos
|
||||
)
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
package gs.test.v0.definition
|
||||
|
||||
import cats.data.Kleisli
|
||||
import natchez.Span
|
||||
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 natchez.*
|
||||
|
||||
trait UnitOfWork[F[_]]:
|
||||
sealed trait UnitOfWork[F[_]]:
|
||||
|
||||
def work(
|
||||
span: Span[F]
|
||||
|
@ -11,6 +19,10 @@ trait UnitOfWork[F[_]]:
|
|||
|
||||
object UnitOfWork:
|
||||
|
||||
type Traced[F[_]] = natchez.Trace[F] ?=> F[Either[TestFailure, Any]]
|
||||
|
||||
type TracedT[F[_]] = natchez.Trace[F] ?=> EitherT[F, TestFailure, Any]
|
||||
|
||||
/** Instantiate a new [[UnitOfWork]] with the given function that requires a
|
||||
* `Span[F]` as input.
|
||||
*
|
||||
|
@ -19,13 +31,96 @@ object UnitOfWork:
|
|||
* @return
|
||||
* The new [[UnitOfWork]] instance.
|
||||
*/
|
||||
def apply[F[_]](
|
||||
uow: Kleisli[F, Span[F], Either[TestFailure, Any]]
|
||||
): UnitOfWork[F] = new UnitOfWork[F] {
|
||||
def apply[F[_]: Async](
|
||||
unitOfWork: Traced[F]
|
||||
): UnitOfWork[F] =
|
||||
new UnitOfWork[F] {
|
||||
|
||||
override def work(span: Span[F]): F[Either[TestFailure, Any]] =
|
||||
uow.apply(span)
|
||||
makeInternalTrace[F](span).flatMap { trace =>
|
||||
given Trace[F] = trace
|
||||
unitOfWork
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 =>
|
||||
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
|
||||
|
|
|
@ -6,7 +6,6 @@ import cats.effect.IO
|
|||
import gs.test.v0.definition.{Tag => GsTag}
|
||||
import munit.*
|
||||
import natchez.Span
|
||||
import natchez.Trace
|
||||
|
||||
class GroupImplementationTests extends FunSuite:
|
||||
import GroupImplementationTests.*
|
||||
|
@ -90,15 +89,16 @@ object GroupImplementationTests:
|
|||
val T1: PermanentId = pid"t1"
|
||||
val T2: PermanentId = pid"t2"
|
||||
val T3: PermanentId = pid"t3"
|
||||
val T4: PermanentId = pid"t4"
|
||||
|
||||
end Ids
|
||||
|
||||
class G1[F[_]: Async: Trace] extends TestGroup[F]:
|
||||
class G1[F[_]: Async] extends TestGroup[F]:
|
||||
override def name: String = "G1"
|
||||
test(Ids.T1, "simple").pure(Right(()))
|
||||
end G1
|
||||
|
||||
class G2[F[_]: Async: Trace] extends TestGroup[F]:
|
||||
class G2[F[_]: Async] extends TestGroup[F]:
|
||||
|
||||
override def name: String =
|
||||
"G2"
|
||||
|
@ -120,7 +120,7 @@ object GroupImplementationTests:
|
|||
test(Ids.T2, "inherit from group").pure(Right(()))
|
||||
end G2
|
||||
|
||||
class G3[F[_]: Async: Trace] extends TestGroup[F]:
|
||||
class G3[F[_]: Async] extends TestGroup[F]:
|
||||
override def name: String = "G3"
|
||||
|
||||
test(Ids.T3, "configure test")
|
||||
|
@ -132,4 +132,12 @@ object GroupImplementationTests:
|
|||
|
||||
end G3
|
||||
|
||||
class G4[F[_]: Async] extends TestGroup[F]:
|
||||
override def name: String = "G4"
|
||||
|
||||
// TODO: Make test entrypoint and test Trace[F]
|
||||
test(Ids.T4, "Effectful test").effectful {
|
||||
???
|
||||
}
|
||||
|
||||
end GroupImplementationTests
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package gs.test.v0.definition.pos
|
||||
|
||||
import cats.data.Kleisli
|
||||
import cats.effect.IO
|
||||
import cats.effect.kernel.Resource
|
||||
import gs.test.v0.IOSuite
|
||||
|
@ -10,8 +9,6 @@ import natchez.EntryPoint
|
|||
import natchez.Kernel
|
||||
import natchez.Span
|
||||
import natchez.Span.Options
|
||||
import natchez.Trace
|
||||
import natchez.Trace.Implicits.noop
|
||||
|
||||
/** These tests are sensitive to changes, even in formatting! They are looking
|
||||
* for specific line numbers in this source code, so any sort of newline that
|
||||
|
@ -45,15 +42,15 @@ class SourcePositionTests extends IOSuite:
|
|||
}
|
||||
|
||||
test("should provide the source position of a failed check") {
|
||||
lookForSourcePosition(new G1, 73)
|
||||
lookForSourcePosition(new G1, 81)
|
||||
}
|
||||
|
||||
test("should provide the source position of an explicit failure") {
|
||||
lookForSourcePosition(new G2, 82)
|
||||
lookForSourcePosition(new G2, 90)
|
||||
}
|
||||
|
||||
private def lookForSourcePosition(
|
||||
groupDef: TestGroup[TracedIO],
|
||||
groupDef: TestGroup[IO],
|
||||
line: Int
|
||||
): Unit =
|
||||
val group = groupDef.compile()
|
||||
|
@ -61,7 +58,7 @@ class SourcePositionTests extends IOSuite:
|
|||
case t1 :: Nil =>
|
||||
ep.root("unit-test")
|
||||
.use { span =>
|
||||
t1.unitOfWork.run(span).map {
|
||||
t1.unitOfWork.work(span).map {
|
||||
case Left(TestFailure.AssertionFailed(_, _, _, pos)) =>
|
||||
assertEquals(pos.file.endsWith(SourceFileName), true)
|
||||
assertEquals(pos.line, line)
|
||||
|
@ -77,9 +74,7 @@ class SourcePositionTests extends IOSuite:
|
|||
|
||||
object SourcePositionTests:
|
||||
|
||||
type TracedIO[A] = Kleisli[IO, Span[IO], A]
|
||||
|
||||
class G1 extends TestGroup[TracedIO]:
|
||||
class G1 extends TestGroup[IO]:
|
||||
override def name: String = "G1"
|
||||
|
||||
test(pid"t1", "pos").pure {
|
||||
|
@ -88,7 +83,7 @@ object SourcePositionTests:
|
|||
|
||||
end G1
|
||||
|
||||
class G2 extends TestGroup[TracedIO]:
|
||||
class G2 extends TestGroup[IO]:
|
||||
override def name: String = "G2"
|
||||
|
||||
test(pid"t2", "pos").pure {
|
||||
|
|
Loading…
Add table
Reference in a new issue