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
|
package gs.test.v0.definition
|
||||||
|
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
import cats.data.Kleisli
|
|
||||||
import cats.effect.Async
|
import cats.effect.Async
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
import gs.test.v0.definition.pos.SourcePosition
|
import gs.test.v0.definition.pos.SourcePosition
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import natchez.Span
|
|
||||||
import scala.collection.mutable.ListBuffer
|
import scala.collection.mutable.ListBuffer
|
||||||
import scala.jdk.CollectionConverters.*
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
|
@ -266,15 +264,16 @@ object TestGroup:
|
||||||
* The function this test will execute.
|
* The function this test will execute.
|
||||||
*/
|
*/
|
||||||
def pure(unitOfWork: => Either[TestFailure, Unit]): Unit =
|
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.
|
/** Finalize and register this test with an effectful unit of work.
|
||||||
*
|
*
|
||||||
* @param unitOfWork
|
* @param unitOfWork
|
||||||
* The function this test will execute.
|
* The function this test will execute.
|
||||||
*/
|
*/
|
||||||
def effectful(unitOfWork: => Kleisli[F, Span[F], Either[TestFailure, Any]])
|
def effectful(
|
||||||
: Unit =
|
unitOfWork: natchez.Trace[F] ?=> F[Either[TestFailure, Any]]
|
||||||
|
): Unit =
|
||||||
registry.register(
|
registry.register(
|
||||||
new TestDefinition[F](
|
new TestDefinition[F](
|
||||||
name = name,
|
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.
|
/** Finalize and register this test with an effectful unit of work.
|
||||||
*
|
*
|
||||||
* @param unitOfWork
|
* @param unitOfWork
|
||||||
* The function this test will execute.
|
* 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(
|
registry.register(
|
||||||
new TestDefinition[F](
|
new TestDefinition[F](
|
||||||
name = name,
|
name = name,
|
||||||
|
@ -306,7 +303,7 @@ object TestGroup:
|
||||||
tags = tags.distinct.toList,
|
tags = tags.distinct.toList,
|
||||||
markers = markers.distinct.toList,
|
markers = markers.distinct.toList,
|
||||||
iterations = iterations,
|
iterations = iterations,
|
||||||
unitOfWork = UnitOfWork[F].apply(unitOfWork.mapF(_.value)),
|
unitOfWork = UnitOfWork.applyT(unitOfWork),
|
||||||
sourcePosition = pos
|
sourcePosition = pos
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -406,7 +403,20 @@ object TestGroup:
|
||||||
* The function this test will execute.
|
* The function this test will execute.
|
||||||
*/
|
*/
|
||||||
def pure(unitOfWork: Input => Either[TestFailure, Unit]): Unit =
|
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.
|
/** Finalize and register this test with an effectful unit of work.
|
||||||
*
|
*
|
||||||
|
@ -414,7 +424,7 @@ object TestGroup:
|
||||||
* The function this test will execute.
|
* The function this test will execute.
|
||||||
*/
|
*/
|
||||||
def effectful(
|
def effectful(
|
||||||
unitOfWork: Input => Kleisli[F, Span[F], Either[TestFailure, Any]]
|
unitOfWork: natchez.Trace[F] ?=> Input => F[Either[TestFailure, Any]]
|
||||||
): Unit =
|
): Unit =
|
||||||
registry.register(
|
registry.register(
|
||||||
new TestDefinition[F](
|
new TestDefinition[F](
|
||||||
|
@ -425,24 +435,20 @@ object TestGroup:
|
||||||
markers = markers.distinct.toList,
|
markers = markers.distinct.toList,
|
||||||
iterations = iterations,
|
iterations = iterations,
|
||||||
unitOfWork = UnitOfWork.apply(
|
unitOfWork = UnitOfWork.apply(
|
||||||
Kleisli(span =>
|
inputFunction.flatMap(input => unitOfWork(input))
|
||||||
inputFunction.flatMap(input => unitOfWork(input).run(span))
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
sourcePosition = pos
|
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.
|
/** Finalize and register this test with an effectful unit of work.
|
||||||
*
|
*
|
||||||
* @param unitOfWork
|
* @param unitOfWork
|
||||||
* The function this test will execute.
|
* 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(
|
registry.register(
|
||||||
new TestDefinition[F](
|
new TestDefinition[F](
|
||||||
name = name,
|
name = name,
|
||||||
|
@ -451,12 +457,8 @@ object TestGroup:
|
||||||
tags = tags.distinct.toList,
|
tags = tags.distinct.toList,
|
||||||
markers = markers.distinct.toList,
|
markers = markers.distinct.toList,
|
||||||
iterations = iterations,
|
iterations = iterations,
|
||||||
unitOfWork = UnitOfWork[F].apply(
|
unitOfWork = UnitOfWork.applyT(
|
||||||
Kleisli(span =>
|
EitherT.liftF(inputFunction).flatMap(unitOfWork)
|
||||||
inputFunction.flatMap { input =>
|
|
||||||
unitOfWork(input).mapF(_.value).run(span)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
sourcePosition = pos
|
sourcePosition = pos
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
package gs.test.v0.definition
|
package gs.test.v0.definition
|
||||||
|
|
||||||
import cats.data.Kleisli
|
import cats.~>
|
||||||
import natchez.Span
|
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(
|
def work(
|
||||||
span: Span[F]
|
span: Span[F]
|
||||||
|
@ -11,6 +19,10 @@ trait UnitOfWork[F[_]]:
|
||||||
|
|
||||||
object UnitOfWork:
|
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
|
/** Instantiate a new [[UnitOfWork]] with the given function that requires a
|
||||||
* `Span[F]` as input.
|
* `Span[F]` as input.
|
||||||
*
|
*
|
||||||
|
@ -19,13 +31,96 @@ object UnitOfWork:
|
||||||
* @return
|
* @return
|
||||||
* The new [[UnitOfWork]] instance.
|
* The new [[UnitOfWork]] instance.
|
||||||
*/
|
*/
|
||||||
def apply[F[_]](
|
def apply[F[_]: Async](
|
||||||
uow: Kleisli[F, Span[F], Either[TestFailure, Any]]
|
unitOfWork: Traced[F]
|
||||||
): UnitOfWork[F] = new UnitOfWork[F] {
|
): UnitOfWork[F] =
|
||||||
|
new UnitOfWork[F] {
|
||||||
|
|
||||||
override def work(span: Span[F]): F[Either[TestFailure, Any]] =
|
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
|
end UnitOfWork
|
||||||
|
|
|
@ -6,7 +6,6 @@ import cats.effect.IO
|
||||||
import gs.test.v0.definition.{Tag => GsTag}
|
import gs.test.v0.definition.{Tag => GsTag}
|
||||||
import munit.*
|
import munit.*
|
||||||
import natchez.Span
|
import natchez.Span
|
||||||
import natchez.Trace
|
|
||||||
|
|
||||||
class GroupImplementationTests extends FunSuite:
|
class GroupImplementationTests extends FunSuite:
|
||||||
import GroupImplementationTests.*
|
import GroupImplementationTests.*
|
||||||
|
@ -90,15 +89,16 @@ object GroupImplementationTests:
|
||||||
val T1: PermanentId = pid"t1"
|
val T1: PermanentId = pid"t1"
|
||||||
val T2: PermanentId = pid"t2"
|
val T2: PermanentId = pid"t2"
|
||||||
val T3: PermanentId = pid"t3"
|
val T3: PermanentId = pid"t3"
|
||||||
|
val T4: PermanentId = pid"t4"
|
||||||
|
|
||||||
end Ids
|
end Ids
|
||||||
|
|
||||||
class G1[F[_]: Async: Trace] extends TestGroup[F]:
|
class G1[F[_]: Async] extends TestGroup[F]:
|
||||||
override def name: String = "G1"
|
override def name: String = "G1"
|
||||||
test(Ids.T1, "simple").pure(Right(()))
|
test(Ids.T1, "simple").pure(Right(()))
|
||||||
end G1
|
end G1
|
||||||
|
|
||||||
class G2[F[_]: Async: Trace] extends TestGroup[F]:
|
class G2[F[_]: Async] extends TestGroup[F]:
|
||||||
|
|
||||||
override def name: String =
|
override def name: String =
|
||||||
"G2"
|
"G2"
|
||||||
|
@ -120,7 +120,7 @@ object GroupImplementationTests:
|
||||||
test(Ids.T2, "inherit from group").pure(Right(()))
|
test(Ids.T2, "inherit from group").pure(Right(()))
|
||||||
end G2
|
end G2
|
||||||
|
|
||||||
class G3[F[_]: Async: Trace] extends TestGroup[F]:
|
class G3[F[_]: Async] extends TestGroup[F]:
|
||||||
override def name: String = "G3"
|
override def name: String = "G3"
|
||||||
|
|
||||||
test(Ids.T3, "configure test")
|
test(Ids.T3, "configure test")
|
||||||
|
@ -132,4 +132,12 @@ object GroupImplementationTests:
|
||||||
|
|
||||||
end G3
|
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
|
end GroupImplementationTests
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package gs.test.v0.definition.pos
|
package gs.test.v0.definition.pos
|
||||||
|
|
||||||
import cats.data.Kleisli
|
|
||||||
import cats.effect.IO
|
import cats.effect.IO
|
||||||
import cats.effect.kernel.Resource
|
import cats.effect.kernel.Resource
|
||||||
import gs.test.v0.IOSuite
|
import gs.test.v0.IOSuite
|
||||||
|
@ -10,8 +9,6 @@ import natchez.EntryPoint
|
||||||
import natchez.Kernel
|
import natchez.Kernel
|
||||||
import natchez.Span
|
import natchez.Span
|
||||||
import natchez.Span.Options
|
import natchez.Span.Options
|
||||||
import natchez.Trace
|
|
||||||
import natchez.Trace.Implicits.noop
|
|
||||||
|
|
||||||
/** These tests are sensitive to changes, even in formatting! They are looking
|
/** 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
|
* 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") {
|
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") {
|
test("should provide the source position of an explicit failure") {
|
||||||
lookForSourcePosition(new G2, 82)
|
lookForSourcePosition(new G2, 90)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def lookForSourcePosition(
|
private def lookForSourcePosition(
|
||||||
groupDef: TestGroup[TracedIO],
|
groupDef: TestGroup[IO],
|
||||||
line: Int
|
line: Int
|
||||||
): Unit =
|
): Unit =
|
||||||
val group = groupDef.compile()
|
val group = groupDef.compile()
|
||||||
|
@ -61,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.run(span).map {
|
t1.unitOfWork.work(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)
|
||||||
|
@ -77,9 +74,7 @@ class SourcePositionTests extends IOSuite:
|
||||||
|
|
||||||
object SourcePositionTests:
|
object SourcePositionTests:
|
||||||
|
|
||||||
type TracedIO[A] = Kleisli[IO, Span[IO], A]
|
class G1 extends TestGroup[IO]:
|
||||||
|
|
||||||
class G1 extends TestGroup[TracedIO]:
|
|
||||||
override def name: String = "G1"
|
override def name: String = "G1"
|
||||||
|
|
||||||
test(pid"t1", "pos").pure {
|
test(pid"t1", "pos").pure {
|
||||||
|
@ -88,7 +83,7 @@ object SourcePositionTests:
|
||||||
|
|
||||||
end G1
|
end G1
|
||||||
|
|
||||||
class G2 extends TestGroup[TracedIO]:
|
class G2 extends TestGroup[IO]:
|
||||||
override def name: String = "G2"
|
override def name: String = "G2"
|
||||||
|
|
||||||
test(pid"t2", "pos").pure {
|
test(pid"t2", "pos").pure {
|
||||||
|
|
Loading…
Add table
Reference in a new issue