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
|
package gs.test.v0.definition
|
||||||
|
|
||||||
import cats.Show
|
import cats.Show
|
||||||
|
import cats.data.Kleisli
|
||||||
import gs.test.v0.definition.pos.SourcePosition
|
import gs.test.v0.definition.pos.SourcePosition
|
||||||
import natchez.Trace
|
import natchez.Span
|
||||||
|
|
||||||
/** Each instance of this class indicates the _definition_ of some test.
|
/** Each instance of this class indicates the _definition_ of some test.
|
||||||
*
|
*
|
||||||
|
@ -23,14 +24,14 @@ import natchez.Trace
|
||||||
* @param sourcePosition
|
* @param sourcePosition
|
||||||
* The location of this test in source code.
|
* The location of this test in source code.
|
||||||
*/
|
*/
|
||||||
final class TestDefinition[F[_]: Trace](
|
final class TestDefinition[F[_]](
|
||||||
val name: TestDefinition.Name,
|
val name: TestDefinition.Name,
|
||||||
val permanentId: PermanentId,
|
val permanentId: PermanentId,
|
||||||
val documentation: Option[String],
|
val documentation: Option[String],
|
||||||
val tags: List[Tag],
|
val tags: List[Tag],
|
||||||
val markers: List[Marker],
|
val markers: List[Marker],
|
||||||
val iterations: TestIterations,
|
val iterations: TestIterations,
|
||||||
val unitOfWork: F[Either[TestFailure, Unit]],
|
val unitOfWork: UnitOfWork[[A] =>> Kleisli[F, Span[F], A]],
|
||||||
val sourcePosition: SourcePosition
|
val sourcePosition: SourcePosition
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
package gs.test.v0.definition
|
package gs.test.v0.definition
|
||||||
|
|
||||||
|
import cats.~>
|
||||||
|
import cats.arrow.FunctionK
|
||||||
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 natchez.Trace
|
import natchez.Trace
|
||||||
import scala.collection.mutable.ListBuffer
|
import scala.collection.mutable.ListBuffer
|
||||||
import scala.jdk.CollectionConverters.*
|
import scala.jdk.CollectionConverters.*
|
||||||
|
@ -27,7 +31,7 @@ import scala.jdk.CollectionConverters.*
|
||||||
* }
|
* }
|
||||||
* }}}
|
* }}}
|
||||||
*/
|
*/
|
||||||
abstract class TestGroup[F[_]: Async: Trace]:
|
abstract class TestGroup[F[_]: Async]:
|
||||||
/** @return
|
/** @return
|
||||||
* The display name for this group.
|
* The display name for this group.
|
||||||
*/
|
*/
|
||||||
|
@ -272,15 +276,8 @@ object TestGroup:
|
||||||
* @param unitOfWork
|
* @param unitOfWork
|
||||||
* The function this test will execute.
|
* The function this test will execute.
|
||||||
*/
|
*/
|
||||||
def effectful(unitOfWork: => F[Either[TestFailure, Unit]]): Unit =
|
def effectful(unitOfWork: => UnitOfWork[[A] =>> Kleisli[F, Span[F], A]])
|
||||||
apply(EitherT(unitOfWork))
|
: Unit =
|
||||||
|
|
||||||
/** 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 =
|
|
||||||
registry.register(
|
registry.register(
|
||||||
new TestDefinition[F](
|
new TestDefinition[F](
|
||||||
name = name,
|
name = name,
|
||||||
|
@ -289,7 +286,35 @@ object TestGroup:
|
||||||
tags = tags.distinct.toList,
|
tags = tags.distinct.toList,
|
||||||
markers = markers.distinct.toList,
|
markers = markers.distinct.toList,
|
||||||
iterations = iterations,
|
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
|
sourcePosition = pos
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,6 @@ package gs.test.v0.definition
|
||||||
|
|
||||||
import cats.Show
|
import cats.Show
|
||||||
import cats.effect.Async
|
import cats.effect.Async
|
||||||
import natchez.Trace
|
|
||||||
|
|
||||||
/** Each group is comprised of a list of [[Test]]. This list may be empty.
|
/** Each group is comprised of a list of [[Test]]. This list may be empty.
|
||||||
*
|
*
|
||||||
|
@ -19,7 +18,7 @@ import natchez.Trace
|
||||||
* @param tests
|
* @param tests
|
||||||
* The list of tests in this group.
|
* The list of tests in this group.
|
||||||
*/
|
*/
|
||||||
final class TestGroupDefinition[F[_]: Async: Trace](
|
final class TestGroupDefinition[F[_]: Async](
|
||||||
val name: TestGroupDefinition.Name,
|
val name: TestGroupDefinition.Name,
|
||||||
val documentation: Option[String],
|
val documentation: Option[String],
|
||||||
val testTags: List[Tag],
|
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,
|
name: String,
|
||||||
documentation: Option[String],
|
documentation: Option[String],
|
||||||
duration: FiniteDuration,
|
duration: FiniteDuration,
|
||||||
countSeen: Int,
|
countSeen: Long,
|
||||||
countSucceeded: Int,
|
countSucceeded: Long,
|
||||||
countFailed: Int,
|
countFailed: Long,
|
||||||
countIgnored: Int,
|
countIgnored: Long,
|
||||||
executedAt: Instant
|
executedAt: Instant
|
||||||
)
|
)
|
||||||
|
|
|
@ -63,15 +63,8 @@ object TestExecution:
|
||||||
*/
|
*/
|
||||||
def apply(value: UUID): Id = value
|
def apply(value: UUID): Id = value
|
||||||
|
|
||||||
given UUID.Generator = UUID.Generator.version7()
|
|
||||||
|
|
||||||
given CanEqual[Id, Id] = CanEqual.derived
|
given CanEqual[Id, Id] = CanEqual.derived
|
||||||
|
|
||||||
/** @return
|
|
||||||
* New ID based on a UUIDv7.
|
|
||||||
*/
|
|
||||||
def generate(): Id = UUID.generate()
|
|
||||||
|
|
||||||
extension (id: Id)
|
extension (id: Id)
|
||||||
/** @return
|
/** @return
|
||||||
* The underlying UUID.
|
* The underlying UUID.
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
package gs.test.v0.execution.engine
|
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(
|
case class EngineConfiguration(
|
||||||
|
groupConcurrency: ConcurrencySetting,
|
||||||
testConcurrency: ConcurrencySetting
|
testConcurrency: ConcurrencySetting
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package gs.test.v0.execution.engine
|
||||||
import gs.test.v0.execution.SuiteExecution
|
import gs.test.v0.execution.SuiteExecution
|
||||||
import gs.test.v0.execution.TestExecution
|
import gs.test.v0.execution.TestExecution
|
||||||
|
|
||||||
final class EngineResult[F[_]](
|
final class EngineResult(
|
||||||
val suiteExecution: SuiteExecution,
|
val suiteExecution: SuiteExecution
|
||||||
val testExecutions: fs2.Stream[F, TestExecution]
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
package gs.test.v0.execution.engine
|
||||||
|
|
||||||
|
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.TestGroupDefinition
|
import gs.test.v0.definition.TestGroupDefinition
|
||||||
import gs.test.v0.definition.TestSuite
|
import gs.test.v0.definition.TestSuite
|
||||||
|
import gs.test.v0.execution.SuiteExecution
|
||||||
import gs.test.v0.execution.TestExecution
|
import gs.test.v0.execution.TestExecution
|
||||||
import gs.timing.v0.Timing
|
import gs.timing.v0.Timing
|
||||||
|
import gs.uuid.v0.UUID
|
||||||
|
import java.time.Clock
|
||||||
|
import java.time.Instant
|
||||||
import natchez.EntryPoint
|
import natchez.EntryPoint
|
||||||
|
import natchez.Span
|
||||||
|
|
||||||
final class TestEngine[F[_]: Async](
|
final class TestEngine[F[_]: Async](
|
||||||
val configuration: EngineConfiguration,
|
val configuration: EngineConfiguration,
|
||||||
timing: Timing[F],
|
timing: Timing[F],
|
||||||
|
suiteExecutionIdGenerator: UUID.Generator,
|
||||||
|
testExecutionIdGenerator: UUID.Generator,
|
||||||
|
clock: Clock,
|
||||||
val entryPoint: EntryPoint[F]
|
val entryPoint: EntryPoint[F]
|
||||||
):
|
):
|
||||||
|
|
||||||
def runSuite(
|
def runSuite(
|
||||||
suite: TestSuite,
|
suite: TestSuite,
|
||||||
tests: fs2.Stream[F, TestGroupDefinition[F]]
|
tests: fs2.Stream[F, TestGroupDefinition[F]]
|
||||||
): EngineResult[F] =
|
): F[SuiteExecution] =
|
||||||
EngineResult[F](
|
for
|
||||||
suiteExecution = ???,
|
executedAt <- Async[F].delay(Instant.now(clock))
|
||||||
testExecutions = tests.mapAsync(4)(group => runGroup(group)).map(_ => ???)
|
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(
|
def runGroup(
|
||||||
group: TestGroupDefinition[F]
|
group: TestGroupDefinition[F]
|
||||||
): F[GroupResult] =
|
): F[GroupResult] =
|
||||||
|
@ -52,14 +93,14 @@ final class TestEngine[F[_]: Async](
|
||||||
.emits(group.tests)
|
.emits(group.tests)
|
||||||
.mapAsync(configuration.testConcurrency.toInt()) { test =>
|
.mapAsync(configuration.testConcurrency.toInt()) { test =>
|
||||||
for
|
for
|
||||||
testExecutionId <- Async[F].delay(TestExecution.Id.generate())
|
testExecutionId <- Async[F].delay(testExecutionIdGenerator.next())
|
||||||
timer <- timing.start()
|
timer <- timing.start()
|
||||||
_ <- group.beforeEachTest.getOrElse(Async[F].unit)
|
_ <- group.beforeEachTest.getOrElse(Async[F].unit)
|
||||||
result <- test.unitOfWork
|
result <- test.unitOfWork
|
||||||
_ <- group.afterEachTest.getOrElse(Async[F].unit)
|
_ <- group.afterEachTest.getOrElse(Async[F].unit)
|
||||||
elapsed <- timer.checkpoint()
|
elapsed <- timer.checkpoint()
|
||||||
yield TestExecution(
|
yield TestExecution(
|
||||||
id = testExecutionId,
|
id = TestExecution.Id(testExecutionId),
|
||||||
permanentId = test.permanentId,
|
permanentId = test.permanentId,
|
||||||
documentation = test.documentation,
|
documentation = test.documentation,
|
||||||
tags = test.tags,
|
tags = test.tags,
|
||||||
|
|
Loading…
Add table
Reference in a new issue