WIP trying to figure out traces.
This commit is contained in:
parent
3304d5d341
commit
9c8ae5ca9b
10 changed files with 156 additions and 36 deletions
|
@ -1,8 +1,9 @@
|
|||
package gs.test.v0.definition
|
||||
|
||||
import cats.Show
|
||||
import cats.data.Kleisli
|
||||
import gs.test.v0.definition.pos.SourcePosition
|
||||
import natchez.Trace
|
||||
import natchez.Span
|
||||
|
||||
/** Each instance of this class indicates the _definition_ of some test.
|
||||
*
|
||||
|
@ -23,14 +24,14 @@ import natchez.Trace
|
|||
* @param sourcePosition
|
||||
* The location of this test in source code.
|
||||
*/
|
||||
final class TestDefinition[F[_]: Trace](
|
||||
final class TestDefinition[F[_]](
|
||||
val name: TestDefinition.Name,
|
||||
val permanentId: PermanentId,
|
||||
val documentation: Option[String],
|
||||
val tags: List[Tag],
|
||||
val markers: List[Marker],
|
||||
val iterations: TestIterations,
|
||||
val unitOfWork: F[Either[TestFailure, Unit]],
|
||||
val unitOfWork: UnitOfWork[[A] =>> Kleisli[F, Span[F], A]],
|
||||
val sourcePosition: SourcePosition
|
||||
)
|
||||
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package gs.test.v0.definition
|
||||
|
||||
import cats.~>
|
||||
import cats.arrow.FunctionK
|
||||
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 natchez.Trace
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.jdk.CollectionConverters.*
|
||||
|
@ -27,7 +31,7 @@ import scala.jdk.CollectionConverters.*
|
|||
* }
|
||||
* }}}
|
||||
*/
|
||||
abstract class TestGroup[F[_]: Async: Trace]:
|
||||
abstract class TestGroup[F[_]: Async]:
|
||||
/** @return
|
||||
* The display name for this group.
|
||||
*/
|
||||
|
@ -272,15 +276,8 @@ object TestGroup:
|
|||
* @param unitOfWork
|
||||
* The function this test will execute.
|
||||
*/
|
||||
def effectful(unitOfWork: => F[Either[TestFailure, Unit]]): Unit =
|
||||
apply(EitherT(unitOfWork))
|
||||
|
||||
/** Finalize and register this test with an effectful unit of work.
|
||||
*
|
||||
* @param unitOfWork
|
||||
* The function this test will execute.
|
||||
*/
|
||||
def apply(unitOfWork: => EitherT[F, TestFailure, Unit]): Unit =
|
||||
def effectful(unitOfWork: => UnitOfWork[[A] =>> Kleisli[F, Span[F], A]])
|
||||
: Unit =
|
||||
registry.register(
|
||||
new TestDefinition[F](
|
||||
name = name,
|
||||
|
@ -289,7 +286,35 @@ object TestGroup:
|
|||
tags = tags.distinct.toList,
|
||||
markers = markers.distinct.toList,
|
||||
iterations = iterations,
|
||||
unitOfWork = unitOfWork.value,
|
||||
unitOfWork = unitOfWork,
|
||||
sourcePosition = pos
|
||||
)
|
||||
)
|
||||
|
||||
type Foo[A] = EitherT[F, TestFailure, A]
|
||||
|
||||
type Bar[A] = F[Either[TestFailure, A]]
|
||||
|
||||
val FooToBar: FunctionK[Foo, Bar] = new FunctionK[Foo, Bar] {
|
||||
def apply[A](fa: Foo[A]): Bar[A] = fa.value
|
||||
}
|
||||
|
||||
/** Finalize and register this test with an effectful unit of work.
|
||||
*
|
||||
* @param unitOfWork
|
||||
* The function this test will execute.
|
||||
*/
|
||||
def apply(unitOfWork: => UnitOfWork[[A] =>> Kleisli[Foo, Span[Foo], A]])
|
||||
: Unit =
|
||||
registry.register(
|
||||
new TestDefinition[F](
|
||||
name = name,
|
||||
permanentId = permanentId,
|
||||
documentation = documentation,
|
||||
tags = tags.distinct.toList,
|
||||
markers = markers.distinct.toList,
|
||||
iterations = iterations,
|
||||
unitOfWork = UnitOfWork[F](unitOfWork.work.mapK(FooToBar)),
|
||||
sourcePosition = pos
|
||||
)
|
||||
)
|
||||
|
|
|
@ -2,7 +2,6 @@ package gs.test.v0.definition
|
|||
|
||||
import cats.Show
|
||||
import cats.effect.Async
|
||||
import natchez.Trace
|
||||
|
||||
/** Each group is comprised of a list of [[Test]]. This list may be empty.
|
||||
*
|
||||
|
@ -19,7 +18,7 @@ import natchez.Trace
|
|||
* @param tests
|
||||
* The list of tests in this group.
|
||||
*/
|
||||
final class TestGroupDefinition[F[_]: Async: Trace](
|
||||
final class TestGroupDefinition[F[_]: Async](
|
||||
val name: TestGroupDefinition.Name,
|
||||
val documentation: Option[String],
|
||||
val testTags: List[Tag],
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package gs.test.v0.definition
|
||||
|
||||
import cats.data.Kleisli
|
||||
import natchez.Span
|
||||
import natchez.Trace
|
||||
|
||||
trait UnitOfWork[F[_]]:
|
||||
|
||||
def work(
|
||||
using
|
||||
Trace[F]
|
||||
): F[Either[TestFailure, Unit]]
|
||||
|
||||
object UnitOfWork:
|
||||
|
||||
def apply[F[_]](uow: Kleisli[F, Span[F], Either[TestFailure, Unit]]) = uow
|
||||
|
||||
end UnitOfWork
|
|
@ -9,9 +9,9 @@ case class SuiteExecution(
|
|||
name: String,
|
||||
documentation: Option[String],
|
||||
duration: FiniteDuration,
|
||||
countSeen: Int,
|
||||
countSucceeded: Int,
|
||||
countFailed: Int,
|
||||
countIgnored: Int,
|
||||
countSeen: Long,
|
||||
countSucceeded: Long,
|
||||
countFailed: Long,
|
||||
countIgnored: Long,
|
||||
executedAt: Instant
|
||||
)
|
||||
|
|
|
@ -63,15 +63,8 @@ object TestExecution:
|
|||
*/
|
||||
def apply(value: UUID): Id = value
|
||||
|
||||
given UUID.Generator = UUID.Generator.version7()
|
||||
|
||||
given CanEqual[Id, Id] = CanEqual.derived
|
||||
|
||||
/** @return
|
||||
* New ID based on a UUIDv7.
|
||||
*/
|
||||
def generate(): Id = UUID.generate()
|
||||
|
||||
extension (id: Id)
|
||||
/** @return
|
||||
* The underlying UUID.
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
package gs.test.v0.execution.engine
|
||||
|
||||
/** Used to control the behavior of some [[TestEngine]]
|
||||
*
|
||||
* @param groupConcurrency
|
||||
* [[ConcurrencySetting]] for groups; the number of groups allowed to execute
|
||||
* at the same time.
|
||||
* @param testConcurrency
|
||||
* [[ConcurrencySetting]] for tests; the number of tests allowed to execute
|
||||
* at the same time within some group.
|
||||
*/
|
||||
case class EngineConfiguration(
|
||||
groupConcurrency: ConcurrencySetting,
|
||||
testConcurrency: ConcurrencySetting
|
||||
)
|
||||
|
|
|
@ -3,7 +3,6 @@ package gs.test.v0.execution.engine
|
|||
import gs.test.v0.execution.SuiteExecution
|
||||
import gs.test.v0.execution.TestExecution
|
||||
|
||||
final class EngineResult[F[_]](
|
||||
val suiteExecution: SuiteExecution,
|
||||
val testExecutions: fs2.Stream[F, TestExecution]
|
||||
final class EngineResult(
|
||||
val suiteExecution: SuiteExecution
|
||||
)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package gs.test.v0.execution.engine
|
||||
|
||||
import cats.effect.Async
|
||||
import cats.effect.Ref
|
||||
import cats.syntax.all.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
final class EngineStats[F[_]: Async](
|
||||
val overallDuration: Ref[F, FiniteDuration],
|
||||
val countSeen: Ref[F, Long],
|
||||
val countSucceeded: Ref[F, Long],
|
||||
val countFailed: Ref[F, Long],
|
||||
val countIgnored: Ref[F, Long]
|
||||
)
|
||||
|
||||
object EngineStats:
|
||||
|
||||
def initialize[F[_]: Async]: F[EngineStats[F]] =
|
||||
for
|
||||
duration <- Ref.of(FiniteDuration(0L, TimeUnit.NANOSECONDS))
|
||||
seen <- Ref.of(0L)
|
||||
succeeded <- Ref.of(0L)
|
||||
failed <- Ref.of(0L)
|
||||
ignored <- Ref.of(0L)
|
||||
yield new EngineStats[F](
|
||||
overallDuration = duration,
|
||||
countSeen = seen,
|
||||
countSucceeded = succeeded,
|
||||
countFailed = failed,
|
||||
countIgnored = ignored
|
||||
)
|
||||
|
||||
end EngineStats
|
|
@ -1,28 +1,69 @@
|
|||
package gs.test.v0.execution.engine
|
||||
|
||||
import cats.data.Kleisli
|
||||
import cats.effect.Async
|
||||
import cats.syntax.all.*
|
||||
import gs.test.v0.definition.TestGroupDefinition
|
||||
import gs.test.v0.definition.TestSuite
|
||||
import gs.test.v0.execution.SuiteExecution
|
||||
import gs.test.v0.execution.TestExecution
|
||||
import gs.timing.v0.Timing
|
||||
import gs.uuid.v0.UUID
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import natchez.EntryPoint
|
||||
import natchez.Span
|
||||
|
||||
final class TestEngine[F[_]: Async](
|
||||
val configuration: EngineConfiguration,
|
||||
timing: Timing[F],
|
||||
suiteExecutionIdGenerator: UUID.Generator,
|
||||
testExecutionIdGenerator: UUID.Generator,
|
||||
clock: Clock,
|
||||
val entryPoint: EntryPoint[F]
|
||||
):
|
||||
|
||||
def runSuite(
|
||||
suite: TestSuite,
|
||||
tests: fs2.Stream[F, TestGroupDefinition[F]]
|
||||
): EngineResult[F] =
|
||||
EngineResult[F](
|
||||
suiteExecution = ???,
|
||||
testExecutions = tests.mapAsync(4)(group => runGroup(group)).map(_ => ???)
|
||||
): F[SuiteExecution] =
|
||||
for
|
||||
executedAt <- Async[F].delay(Instant.now(clock))
|
||||
stats <- EngineStats.initialize[F]
|
||||
_ <- tests
|
||||
.mapAsync(configuration.groupConcurrency.toInt())(runGroup)
|
||||
.evalTap(updateGroupStats)
|
||||
.evalTap(reportGroup)
|
||||
.flatMap(groupResult => fs2.Stream.emits(groupResult.testExecutions))
|
||||
.evalTap(updateTestStats)
|
||||
.evalMap(reportTestExecution)
|
||||
.compile
|
||||
.drain
|
||||
overallDuration <- stats.overallDuration.get
|
||||
countSeen <- stats.countSeen.get
|
||||
countSucceeded <- stats.countSucceeded.get
|
||||
countFailed <- stats.countFailed.get
|
||||
countIgnored <- stats.countIgnored.get
|
||||
yield SuiteExecution(
|
||||
id = suiteExecutionIdGenerator.next(),
|
||||
name = suite.name,
|
||||
documentation = suite.documentation,
|
||||
duration = overallDuration,
|
||||
countSeen = countSeen,
|
||||
countSucceeded = countSucceeded,
|
||||
countFailed = countFailed,
|
||||
countIgnored = countIgnored,
|
||||
executedAt = executedAt
|
||||
)
|
||||
|
||||
private def updateGroupStats(groupResult: GroupResult): F[Unit] = ???
|
||||
|
||||
private def updateTestStats(testExecution: TestExecution): F[Unit] = ???
|
||||
|
||||
private def reportGroup(groupResult: GroupResult): F[Unit] = ???
|
||||
|
||||
private def reportTestExecution(testExecution: TestExecution): F[Unit] = ???
|
||||
|
||||
def runGroup(
|
||||
group: TestGroupDefinition[F]
|
||||
): F[GroupResult] =
|
||||
|
@ -52,14 +93,14 @@ final class TestEngine[F[_]: Async](
|
|||
.emits(group.tests)
|
||||
.mapAsync(configuration.testConcurrency.toInt()) { test =>
|
||||
for
|
||||
testExecutionId <- Async[F].delay(TestExecution.Id.generate())
|
||||
testExecutionId <- Async[F].delay(testExecutionIdGenerator.next())
|
||||
timer <- timing.start()
|
||||
_ <- group.beforeEachTest.getOrElse(Async[F].unit)
|
||||
result <- test.unitOfWork
|
||||
_ <- group.afterEachTest.getOrElse(Async[F].unit)
|
||||
elapsed <- timer.checkpoint()
|
||||
yield TestExecution(
|
||||
id = testExecutionId,
|
||||
id = TestExecution.Id(testExecutionId),
|
||||
permanentId = test.permanentId,
|
||||
documentation = test.documentation,
|
||||
tags = test.tags,
|
||||
|
|
Loading…
Add table
Reference in a new issue