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