Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ abstract class BaseTestTask(
protected def run(eventHandler: EventHandler): ZIO[TestLogger with Clock, Throwable, Unit] =
for {
spec <- specInstance.runSpec(FilteredSpec(specInstance.spec, args))
summary <- SummaryBuilder.buildSummary(spec)
summary = SummaryBuilder.buildSummary(spec)
_ <- sendSummary.provide(summary)
events <- ZTestEvent.from(spec, taskDef.fullyQualifiedName, taskDef.fingerprint)
events = ZTestEvent.from(spec, taskDef.fullyQualifiedName, taskDef.fingerprint)
_ <- ZIO.foreach(events)(e => ZIO.effect(eventHandler.handle(e)))
} yield ()

Expand Down
16 changes: 6 additions & 10 deletions test-sbt/shared/src/main/scala/zio/test/sbt/ZTestEvent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ package zio.test.sbt

import sbt.testing._

import zio.UIO
import zio.test.{ ExecutedSpec, Spec, TestFailure, TestSuccess }
import zio.test.{ ExecutedSpec, TestFailure, TestSuccess }

final case class ZTestEvent(
fullyQualifiedName: String,
Expand All @@ -21,14 +20,11 @@ object ZTestEvent {
executedSpec: ExecutedSpec[E],
fullyQualifiedName: String,
fingerprint: Fingerprint
): UIO[Seq[ZTestEvent]] =
executedSpec.fold[UIO[Seq[ZTestEvent]]] {
case Spec.SuiteCase(_, results, _) =>
results.use(UIO.collectAll(_).map(_.flatten))
case Spec.TestCase(label, result, _) =>
result.map { result =>
Seq(ZTestEvent(fullyQualifiedName, new TestSelector(label), toStatus(result), None, 0, fingerprint))
}
): Seq[ZTestEvent] =
executedSpec.fold[Seq[ZTestEvent]] {
case ExecutedSpec.SuiteCase(_, results) => results.flatten
case ExecutedSpec.TestCase(label, result, _) =>
Seq(ZTestEvent(fullyQualifiedName, new TestSelector(label), toStatus(result), None, 0, fingerprint))
}

private def toStatus[E](result: Either[TestFailure[E], TestSuccess]) = result match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ object ReportingTestUtils {
.provideLayer[Nothing, TestEnvironment, TestLogger with Clock](
TestLogger.fromConsole ++ TestClock.default
)
actualSummary <- SummaryBuilder.buildSummary(results)
actualSummary = SummaryBuilder.buildSummary(results)
} yield actualSummary.summary

private[this] def TestTestRunner(testEnvironment: Layer[Nothing, TestEnvironment]) =
Expand Down
10 changes: 8 additions & 2 deletions test-tests/shared/src/test/scala/zio/test/SpecSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ object SpecSpec extends ZIOBaseSpec {
).provideLayerShared(ZLayer.succeed(43))
for {
executedSpec <- execute(spec)
successes <- executedSpec.countTests(_.isRight).useNow
failures <- executedSpec.countTests(_.isLeft).useNow
successes = executedSpec.fold[Int] {
case ExecutedSpec.SuiteCase(_, counts) => counts.sum
case ExecutedSpec.TestCase(_, test, _) => if (test.isRight) 1 else 0
}
failures = executedSpec.fold[Int] {
case ExecutedSpec.SuiteCase(_, counts) => counts.sum
case ExecutedSpec.TestCase(_, test, _) => if (test.isLeft) 1 else 0
}
} yield assert(successes)(equalTo(1)) && assert(failures)(equalTo(2))
}
),
Expand Down
38 changes: 18 additions & 20 deletions test-tests/shared/src/test/scala/zio/test/TestUtils.scala
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
package zio.test

import zio.test.environment.TestEnvironment
import zio.{ ExecutionStrategy, UIO, ZIO }
import zio.{ ExecutionStrategy, UIO }

object TestUtils {

def execute[E](spec: ZSpec[TestEnvironment, E]): UIO[ExecutedSpec[E]] =
TestExecutor.default(environment.testEnvironment).run(spec, ExecutionStrategy.Sequential)

def forAllTests[E](
execSpec: UIO[ExecutedSpec[E]]
)(f: Either[TestFailure[E], TestSuccess] => Boolean): UIO[Boolean] =
execSpec.flatMap { results =>
results.forall {
case Spec.TestCase(_, test, _) => test.map(r => f(r))
case _ => ZIO.succeed(true)
}.useNow
execSpec: ExecutedSpec[E]
)(f: Either[TestFailure[E], TestSuccess] => Boolean): Boolean =
execSpec.forall {
case ExecutedSpec.TestCase(_, test, _) => f(test)
case _ => true
}

def isIgnored[E](spec: ZSpec[environment.TestEnvironment, E]): UIO[Boolean] = {
val execSpec = execute(spec)
forAllTests(execSpec) {
case Right(TestSuccess.Ignored) => true
case _ => false
def isIgnored[E](spec: ZSpec[environment.TestEnvironment, E]): UIO[Boolean] =
execute(spec).map { executedSpec =>
forAllTests(executedSpec) {
case Right(TestSuccess.Ignored) => true
case _ => false
}
}
}

def succeeded[E](spec: ZSpec[environment.TestEnvironment, E]): UIO[Boolean] = {
val execSpec = execute(spec)
forAllTests(execSpec) {
case Right(TestSuccess.Succeeded(_)) => true
case _ => false
def succeeded[E](spec: ZSpec[environment.TestEnvironment, E]): UIO[Boolean] =
execute(spec).map { executedSpec =>
forAllTests(executedSpec) {
case Right(TestSuccess.Succeeded(_)) => true
case _ => false
}
}
}
}
133 changes: 58 additions & 75 deletions test/shared/src/main/scala/zio/test/DefaultTestReporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,103 +29,86 @@ import zio.test.RenderedResult.Status._
import zio.test.RenderedResult.{ CaseType, Status }
import zio.test.mock.Expectation
import zio.test.mock.internal.{ InvalidCall, MockException }
import zio.{ Cause, Has, UIO, URIO }
import zio.{ Cause, Has }

object DefaultTestReporter {

def render[E](
executedSpec: ExecutedSpec[E],
testAnnotationRenderer: TestAnnotationRenderer
): UIO[Seq[RenderedResult[String]]] = {
): Seq[RenderedResult[String]] = {
def loop(
executedSpec: ExecutedSpec[E],
depth: Int,
ancestors: List[TestAnnotationMap]
): UIO[Seq[RenderedResult[String]]] =
): Seq[RenderedResult[String]] =
executedSpec.caseValue match {
case c @ Spec.SuiteCase(label, executedSpecs, _) =>
for {
specs <- executedSpecs.useNow
failures <- UIO.foreach(specs) { specs =>
specs.exists {
case Spec.TestCase(_, test, _) => test.map(_.isLeft)
case _ => UIO.succeedNow(false)
}.useNow
}
annotations <- Spec(c).fold[UIO[TestAnnotationMap]] {
case Spec.SuiteCase(_, specs, _) =>
specs.use(UIO.collectAll(_).map(_.foldLeft(TestAnnotationMap.empty)(_ ++ _)))
case Spec.TestCase(_, _, annotations) => UIO.succeedNow(annotations)
}
hasFailures = failures.exists(identity)
status = if (hasFailures) Failed else Passed
renderedLabel = if (specs.isEmpty) Seq.empty
case ExecutedSpec.SuiteCase(label, specs) =>
val hasFailures = executedSpec.exists {
case ExecutedSpec.TestCase(_, test, _) => test.isLeft
case _ => false
}
val annotations = executedSpec.fold[TestAnnotationMap] {
case ExecutedSpec.SuiteCase(_, annotations) => annotations.foldLeft(TestAnnotationMap.empty)(_ ++ _)
case ExecutedSpec.TestCase(_, _, annotations) => annotations
}
val status = if (hasFailures) Failed else Passed
val renderedLabel =
if (specs.isEmpty) Seq.empty
else if (hasFailures) Seq(renderFailureLabel(label, depth))
else Seq(renderSuccessLabel(label, depth))
renderedAnnotations = testAnnotationRenderer.run(ancestors, annotations)
rest <- UIO.foreach(specs)(loop(_, depth + tabSize, annotations :: ancestors)).map(_.flatten)
} yield rendered(Suite, label, status, depth, (renderedLabel): _*)
.withAnnotations(renderedAnnotations) +: rest
case Spec.TestCase(label, result, annotations) =>
result.map { result =>
val renderedAnnotations = testAnnotationRenderer.run(ancestors, annotations)
val renderedResult = result match {
case Right(TestSuccess.Succeeded(_)) =>
rendered(Test, label, Passed, depth, withOffset(depth)(green("+") + " " + label))
case Right(TestSuccess.Ignored) =>
rendered(Test, label, Ignored, depth)
case Left(TestFailure.Assertion(result)) =>
result.fold(details => rendered(Test, label, Failed, depth, renderFailure(label, depth, details): _*))(
_ && _,
_ || _,
!_
)
case Left(TestFailure.Runtime(cause)) =>
rendered(
Test,
label,
Failed,
depth,
(Seq(renderFailureLabel(label, depth)) ++ Seq(renderCause(cause, depth))): _*
)
}
Seq(renderedResult.withAnnotations(renderedAnnotations))
val renderedAnnotations = testAnnotationRenderer.run(ancestors, annotations)
val rest = specs.flatMap(loop(_, depth + tabSize, annotations :: ancestors))
rendered(Suite, label, status, depth, (renderedLabel): _*).withAnnotations(renderedAnnotations) +: rest
case ExecutedSpec.TestCase(label, result, annotations) =>
val renderedAnnotations = testAnnotationRenderer.run(ancestors, annotations)
val renderedResult = result match {
case Right(TestSuccess.Succeeded(_)) =>
rendered(Test, label, Passed, depth, withOffset(depth)(green("+") + " " + label))
case Right(TestSuccess.Ignored) =>
rendered(Test, label, Ignored, depth)
case Left(TestFailure.Assertion(result)) =>
result.fold(details => rendered(Test, label, Failed, depth, renderFailure(label, depth, details): _*))(
_ && _,
_ || _,
!_
)
case Left(TestFailure.Runtime(cause)) =>
rendered(
Test,
label,
Failed,
depth,
(Seq(renderFailureLabel(label, depth)) ++ Seq(renderCause(cause, depth))): _*
)
}
Seq(renderedResult.withAnnotations(renderedAnnotations))
}
loop(executedSpec, 0, List.empty)
}

def apply[E](testAnnotationRenderer: TestAnnotationRenderer): TestReporter[E] = {
(duration: Duration, executedSpec: ExecutedSpec[E]) =>
for {
rendered <- render(executedSpec, testAnnotationRenderer).map(_.flatMap(_.rendered))
stats <- logStats(duration, executedSpec)
_ <- TestLogger.logLine((rendered ++ Seq(stats)).mkString("\n"))
} yield ()
val rendered = render(executedSpec, testAnnotationRenderer).flatMap(_.rendered)
val stats = logStats(duration, executedSpec)
TestLogger.logLine((rendered ++ Seq(stats)).mkString("\n"))
}

private def logStats[E](duration: Duration, executedSpec: ExecutedSpec[E]): URIO[TestLogger, String] = {
def loop(executedSpec: ExecutedSpec[E]): UIO[(Int, Int, Int)] =
executedSpec.caseValue match {
case Spec.SuiteCase(_, executedSpecs, _) =>
for {
specs <- executedSpecs.useNow
stats <- UIO.foreach(specs)(loop)
} yield stats.foldLeft((0, 0, 0)) {
case ((x1, x2, x3), (y1, y2, y3)) => (x1 + y1, x2 + y2, x3 + y3)
}
case Spec.TestCase(_, result, _) =>
result.map {
case Left(_) => (0, 0, 1)
case Right(TestSuccess.Succeeded(_)) => (1, 0, 0)
case Right(TestSuccess.Ignored) => (0, 1, 0)
}
}
for {
stats <- loop(executedSpec)
(success, ignore, failure) = stats
total = success + ignore + failure
} yield cyan(
private def logStats[E](duration: Duration, executedSpec: ExecutedSpec[E]): String = {
val (success, ignore, failure) = executedSpec.fold[(Int, Int, Int)] {
case ExecutedSpec.SuiteCase(_, stats) =>
stats.foldLeft((0, 0, 0)) {
case ((x1, x2, x3), (y1, y2, y3)) => (x1 + y1, x2 + y2, x3 + y3)
}
case ExecutedSpec.TestCase(_, result, _) =>
result match {
case Left(_) => (0, 0, 1)
case Right(TestSuccess.Succeeded(_)) => (1, 0, 0)
case Right(TestSuccess.Ignored) => (0, 1, 0)
}
}
val total = success + ignore + failure
cyan(
s"Ran $total test${if (total == 1) "" else "s"} in ${duration.render}: $success succeeded, $ignore ignored, $failure failed"
)
}
Expand Down
107 changes: 107 additions & 0 deletions test/shared/src/main/scala/zio/test/ExecutedSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package zio.test

import zio.test.ExecutedSpec._

/**
* An `ExecutedSpec` is a spec that has been run to produce test results.
*/
final case class ExecutedSpec[+E](caseValue: SpecCase[E, ExecutedSpec[E]]) { self =>

/**
* Determines if any node in the spec is satisfied by the given predicate.
*/
def exists(f: SpecCase[E, Boolean] => Boolean): Boolean =
fold[Boolean] {
case c @ SuiteCase(_, specs) => specs.exists(identity) || f(c)
case c @ TestCase(_, _, _) => f(c)
}

/**
* Folds over all nodes to produce a final result.
*/
def fold[Z](f: SpecCase[E, Z] => Z): Z =
caseValue match {
case SuiteCase(label, specs) => f(SuiteCase(label, specs.map(_.fold(f))))
case t @ TestCase(_, _, _) => f(t)
}

/**
* Determines if all nodes in the spec are satisfied by the given predicate.
*/
def forall(f: SpecCase[E, Boolean] => Boolean): Boolean =
fold[Boolean] {
case c @ SuiteCase(_, specs) => specs.forall(identity) && f(c)
case c @ TestCase(_, _, _) => f(c)
}

/**
* Computes the size of the spec, i.e. the number of tests in the spec.
*/
def size: Int =
fold[Int] {
case SuiteCase(_, counts) => counts.sum
case TestCase(_, _, _) => 1
}

/**
* Transforms the spec one layer at a time.
*/
def transform[E1](f: SpecCase[E, ExecutedSpec[E1]] => SpecCase[E1, ExecutedSpec[E1]]): ExecutedSpec[E1] =
caseValue match {
case SuiteCase(label, specs) => ExecutedSpec(f(SuiteCase(label, specs.map(_.transform(f)))))
case t @ TestCase(_, _, _) => ExecutedSpec(f(t))
}

/**
* Transforms the spec statefully, one layer at a time.
*/
def transformAccum[E1, Z](
z0: Z
)(f: (Z, SpecCase[E, ExecutedSpec[E1]]) => (Z, SpecCase[E1, ExecutedSpec[E1]])): (Z, ExecutedSpec[E1]) =
caseValue match {
case SuiteCase(label, specs) =>
val (z, specs1) =
specs.foldLeft(z0 -> Vector.empty[ExecutedSpec[E1]]) {
case ((z, vector), spec) =>
val (z1, spec1) = spec.transformAccum(z)(f)

z1 -> (vector :+ spec1)
}

val (z1, caseValue) = f(z, SuiteCase(label, specs1))

z1 -> ExecutedSpec(caseValue)
case t @ TestCase(_, _, _) =>
val (z, caseValue) = f(z0, t)
z -> ExecutedSpec(caseValue)
}
}

object ExecutedSpec {

trait SpecCase[+E, +A] { self =>
def map[B](f: A => B): SpecCase[E, B] =
self match {
case SuiteCase(label, specs) => SuiteCase(label, specs.map(f))
case TestCase(label, test, annotations) => TestCase(label, test, annotations)
}
}

final case class SuiteCase[+A](label: String, specs: Vector[A]) extends SpecCase[Nothing, A]

final case class TestCase[+E](
label: String,
test: Either[TestFailure[E], TestSuccess],
annotations: TestAnnotationMap
) extends SpecCase[E, Nothing]

def suite[E](label: String, specs: Vector[ExecutedSpec[E]]): ExecutedSpec[E] =
ExecutedSpec(SuiteCase(label, specs))

def test[E](
label: String,
test: Either[TestFailure[E], TestSuccess],
annotations: TestAnnotationMap
): ExecutedSpec[E] =
ExecutedSpec(TestCase(label, test, annotations))
}
Loading