From c2a155ceabcc6279192e046575352995db95ef98 Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Thu, 31 Jul 2025 07:08:00 -0500 Subject: [PATCH] Minor model refactoring and more base implementations. --- .../scala/gs/test/v0/api/GroupResult.scala | 5 +-- .../gs/test/v0/reporting/NoopReporter.scala | 30 ++++++++++++++ .../v0/reporting/NoopResultFormatter.scala | 19 +++++++++ .../scala/gs/test/v0/reporting/Reporter.scala | 26 +++++++++++-- .../test/v0/reporting/ResultFormatter.scala | 39 +++++++++++++++++++ .../test/v0/runtime/engine/TestEngine.scala | 34 +++++++++------- 6 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 modules/reporting/src/main/scala/gs/test/v0/reporting/NoopReporter.scala create mode 100644 modules/reporting/src/main/scala/gs/test/v0/reporting/NoopResultFormatter.scala create mode 100644 modules/reporting/src/main/scala/gs/test/v0/reporting/ResultFormatter.scala diff --git a/modules/api/src/main/scala/gs/test/v0/api/GroupResult.scala b/modules/api/src/main/scala/gs/test/v0/api/GroupResult.scala index 6731c5b..64abe7d 100644 --- a/modules/api/src/main/scala/gs/test/v0/api/GroupResult.scala +++ b/modules/api/src/main/scala/gs/test/v0/api/GroupResult.scala @@ -16,8 +16,6 @@ import scala.concurrent.duration.FiniteDuration * The number of tests which passed. * @param failed * The number of tests which failed. - * @param testExecutions - * List of test results. */ final class GroupResult( val name: TestGroupDefinition.Name, @@ -25,6 +23,5 @@ final class GroupResult( val duration: FiniteDuration, val seen: Long, val passed: Long, - val failed: Long, - val testExecutions: List[TestExecution] + val failed: Long ) diff --git a/modules/reporting/src/main/scala/gs/test/v0/reporting/NoopReporter.scala b/modules/reporting/src/main/scala/gs/test/v0/reporting/NoopReporter.scala new file mode 100644 index 0000000..24742b0 --- /dev/null +++ b/modules/reporting/src/main/scala/gs/test/v0/reporting/NoopReporter.scala @@ -0,0 +1,30 @@ +package gs.test.v0.reporting + +import cats.Applicative +import gs.test.v0.api.GroupResult +import gs.test.v0.api.SuiteExecution +import gs.test.v0.api.TestExecution + +/** No-op implementation of [[Reporter]]. + */ +final class NoopReporter[F[_]: Applicative] extends Reporter[F]: + + /** @inheritDocs + */ + override def beginReporting(): F[Unit] = Applicative[F].unit + + /** @inheritDocs + */ + override def reportGroup( + groupResult: GroupResult, + testExecutions: List[TestExecution] + ): F[Unit] = Applicative[F].unit + + /** @inheritDocs + */ + override def reportSuite(suiteExecution: SuiteExecution): F[Unit] = + Applicative[F].unit + + /** @inheritDocs + */ + override def endReporting(): F[Unit] = Applicative[F].unit diff --git a/modules/reporting/src/main/scala/gs/test/v0/reporting/NoopResultFormatter.scala b/modules/reporting/src/main/scala/gs/test/v0/reporting/NoopResultFormatter.scala new file mode 100644 index 0000000..a52578b --- /dev/null +++ b/modules/reporting/src/main/scala/gs/test/v0/reporting/NoopResultFormatter.scala @@ -0,0 +1,19 @@ +package gs.test.v0.reporting + +import gs.test.v0.api.GroupResult +import gs.test.v0.api.SuiteExecution +import gs.test.v0.api.TestExecution + +final class NoopResultFormatter extends ResultFormatter: + + /** @inheritDocs + */ + override def formatGroupResult(groupResult: GroupResult): String = "" + + /** @inheritDocs + */ + override def formatTestExecution(testExecution: TestExecution): String = "" + + /** @inheritDocs + */ + override def formatSuiteExecution(suiteExecution: SuiteExecution): String = "" diff --git a/modules/reporting/src/main/scala/gs/test/v0/reporting/Reporter.scala b/modules/reporting/src/main/scala/gs/test/v0/reporting/Reporter.scala index 3ff2451..e4a8866 100644 --- a/modules/reporting/src/main/scala/gs/test/v0/reporting/Reporter.scala +++ b/modules/reporting/src/main/scala/gs/test/v0/reporting/Reporter.scala @@ -1,7 +1,9 @@ package gs.test.v0.reporting +import cats.Applicative import gs.test.v0.api.GroupResult import gs.test.v0.api.SuiteExecution +import gs.test.v0.api.TestExecution /** Interface for reporters - implementations that report on test results. * @@ -10,8 +12,10 @@ import gs.test.v0.api.SuiteExecution * * ## Order of Operations * - * 1. `beginReporting()` 2. `reportGroup` for each group executed 3. - * `reportSuite` 4. `endReporting()` + * - `beginReporting()` + * - `reportGroup` for each group executed + * - `reportSuite` + * - `endReporting()` */ trait Reporter[F[_]]: /** Hook for the beginning of the reporting lifecycle. This allows @@ -23,11 +27,16 @@ trait Reporter[F[_]]: /** Report the results of a single group. * * @param groupResult - * The [[GroupResult]] that describes results. + * The [[GroupResult]] that describes the group level summary. + * @param testExecutions + * The list of [[TestExecution]] describing the result of each test. * @return * Side-effect that describes the reporting operation. */ - def reportGroup(groupResult: GroupResult): F[Unit] + def reportGroup( + groupResult: GroupResult, + testExecutions: List[TestExecution] + ): F[Unit] /** Report the results of an entire suite. * @@ -43,3 +52,12 @@ trait Reporter[F[_]]: * footer. */ def endReporting(): F[Unit] + +object Reporter: + + /** @return + * New instance of the no-op Reporter implementation. + */ + def noop[F[_]: Applicative]: Reporter[F] = new NoopReporter[F] + +end Reporter diff --git a/modules/reporting/src/main/scala/gs/test/v0/reporting/ResultFormatter.scala b/modules/reporting/src/main/scala/gs/test/v0/reporting/ResultFormatter.scala new file mode 100644 index 0000000..18c3351 --- /dev/null +++ b/modules/reporting/src/main/scala/gs/test/v0/reporting/ResultFormatter.scala @@ -0,0 +1,39 @@ +package gs.test.v0.reporting + +import gs.test.v0.api.GroupResult +import gs.test.v0.api.SuiteExecution +import gs.test.v0.api.TestExecution + +/** Interface for formatters - implementations that transform test results into + * string representations. + * + * Example implementations include producing plain text or JSON + * representations. + */ +trait ResultFormatter: + /** Format a single [[GroupResult]] as a string. + * + * @param groupResult + * The result to format. + * @return + * The string rendition. + */ + def formatGroupResult(groupResult: GroupResult): String + + /** Format a single [[TestExecution]] as a string. + * + * @param testExecution + * The result to format. + * @return + * The string rendition. + */ + def formatTestExecution(testExecution: TestExecution): String + + /** Format a single [[SuiteExecution]] as a string. + * + * @param suiteExecution + * The result to format. + * @return + * The string rendition. + */ + def formatSuiteExecution(suiteExecution: SuiteExecution): String diff --git a/modules/runtime/src/main/scala/gs/test/v0/runtime/engine/TestEngine.scala b/modules/runtime/src/main/scala/gs/test/v0/runtime/engine/TestEngine.scala index c657c8e..050cb76 100644 --- a/modules/runtime/src/main/scala/gs/test/v0/runtime/engine/TestEngine.scala +++ b/modules/runtime/src/main/scala/gs/test/v0/runtime/engine/TestEngine.scala @@ -76,17 +76,24 @@ final class TestEngine[F[_]: Async]( // TODO: Just do telemetry for the whole damn thing. _ <- tests .mapAsync(configuration.groupConcurrency.toInt())(runGroup) - .evalTap(groupResult => - for - // Update the overall statistics based on this group. - _ <- stats.updateForGroup( - duration = groupResult.duration, - testExecutions = groupResult.testExecutions - ) + .evalTap( + ( + groupResult, + testExecutions + ) => + for + // Update the overall statistics based on this group. + _ <- stats.updateForGroup( + duration = groupResult.duration, + testExecutions = testExecutions + ) - // Report group level results for this group. - _ <- reporter.reportGroup(groupResult) - yield () + // Report group level results for this group. + _ <- reporter.reportGroup( + groupResult = groupResult, + testExecutions = testExecutions + ) + yield () ) .compile .drain @@ -103,7 +110,7 @@ final class TestEngine[F[_]: Async]( def runGroup( group: TestGroupDefinition[F] - ): F[GroupResult] = + ): F[(GroupResult, List[TestExecution])] = entryPoint.root(EngineConstants.Tracing.RootSpan).use { rootSpan => for groupStats <- EngineStats.initialize[F] @@ -150,9 +157,8 @@ final class TestEngine[F[_]: Async]( duration = elapsed.duration, seen = seen, passed = passed, - failed = failed, - testExecutions = testExecutions - ) + failed = failed + ) -> testExecutions } private def executeGroupTests(