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