testz-stdlib
The testz-stdlib
module provides basic testz harnesses using nothing more
than the standard library, testz-core
, testz-resource
, and testz-util
.
It provides two harnesses: PureHarness
and FutureHarness
. Both are built to be
used with testz-runner, despite there being no dependency on testz-runner.
PureHarness
is a harness type for tests which return testz.Result
.
Its Uses[R]
type alias is the implementation type of PureHarness
;
a test group depending on a resource R
in PureHarness
is an
(R, List[String]) => TestOutput
; a function which, given the resource
it needs and the current test group name (and all labels attached to it)
produces a TestOutput
, which describes both how to print the results
of the group and whether any tests failed.
import testz._
import testz.runner.TestOutput
object PureHarness {
type Uses[R] = (R, List[String]) => TestOutput
def makeFromPrinter(
output: (Result, List[String]) => Unit
): Harness[Uses[Unit]] =
ResourceHarness.toHarness(makeFromPrinterR(output))
def makeFromPrinterR(
output: (Result, List[String]) => Unit
): ResourceHarness[Uses] =
new ResourceHarness[Uses] {
override def test[R]
(name: String)
(assertions: R => Result
): Uses[R] =
// note that `assertions(r)` is *already computed* before the
// `() => Unit` is run; this is important to separate phases between
// printing and running tests.
{ (resource, scope) =>
val result = assertions(resource)
new TestOutput(
result ne Succeed(),
() => output(result, name :: scope)
)
}
override def namedSection[R]
(name: String)
(test1: Uses[R], tests: Uses[R]*
): Uses[R] = {
(r, sc) =>
val newScope = name :: sc
val outFirst = test1(r, newScope)
val outRest = tests.map(_(r, newScope))
TestOutput.combineAll1(outFirst, outRest: _*)
}
override def section[R]
(test1: Uses[R], tests: Uses[R]*
): Uses[R] = {
(r, sc) =>
val outFirst = test1(r, sc)
val outRest = tests.map(_(r, sc))
TestOutput.combineAll1(outFirst, outRest: _*)
}
override def allocate[R, I]
(init: () => I)
(tests: ((I, R), List[String]) => TestOutput
): Uses[R] =
(r, sc) => tests((init(), r), sc)
}
}
FutureHarness
is a harness type for tests which return Future[testz.Result]
.
It’s a lot more verbose than PureHarness
, mostly because I’m careful with
ExecutionContext
and because there are several generic utilities missing from
Future
that are very useful in implementing the harness in a clear and concise
way.
import testz._
import testz.runner.TestOutput
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.Try
object FutureHarness {
type Uses[R] = (R, List[String]) => Future[TestOutput]
def makeFromPrinterEff(
output: (Result, List[String]) => Unit
)(
ec: ExecutionContext
): EffectHarness[Future, Uses[Unit]] =
EffectResourceHarness.toEffectHarness(makeFromPrinterEffR(output)(ec))
def makeFromPrinterEffR(
outputTest: (Result, List[String]) => Unit,
)(
ec: ExecutionContext
): EffectResourceHarness[Future, Uses] =
new EffectResourceHarness[Future, Uses] {
// note that `assertions(r)` is *already computed* before we run
// the `() => Unit`.
def test[R](name: String)(assertions: R => Future[Result]): Uses[R] =
(r, sc) => assertions(r).map { result =>
new TestOutput(result ne Succeed(), () => outputTest(result, name :: sc))
}(ec)
def namedSection[R](name: String)(test1: Uses[R], tests: Uses[R]*): Uses[R] = {
(r, sc) =>
val newScope = name :: sc
test1(r, newScope).flatMap { p1 =>
futureUtil.collectIterator(tests.iterator.map(_(r, newScope)))(ec).map { ps =>
TestOutput.combineAll1(p1, ps: _*)
}(ec)
}(ec)
}
def section[R](test1: Uses[R], tests: Uses[R]*): Uses[R] = {
(r, sc) =>
test1(r, sc).flatMap { p1 =>
futureUtil.collectIterator(tests.iterator.map(_(r, sc)))(ec).map { ps =>
TestOutput.combineAll1(p1, ps: _*)
}(ec)
}(ec)
}
// a more powerful version of `Future.transform` that lets you block on
// whatever you make from the inner `Try[A]`, instead of only letting you
// return a `Try`.
// relative monad operation (`rflatMap :: f a -> (g a -> f b) -> f b`)
private def blockingTransform[A, B](fut: Future[A])(f: Try[A] => Future[B])(ec: ExecutionContext): Future[B] = {
val prom = Promise[B]
fut.onComplete {
t => prom.completeWith(f(t))
}(ec)
prom.future
}
private def fromTry[A](t: Try[A]): Future[A] = {
if (t.isInstanceOf[scala.util.Failure[A]])
Future.failed(t.asInstanceOf[scala.util.Failure[A]].exception)
else
Future.successful(t.asInstanceOf[scala.util.Success[A]].value)
}
def bracket[R, I]
(init: () => Future[I])
(cleanup: I => Future[Unit])
(tests: Uses[(I, R)]
): Uses[R] = { (r, sc) =>
init().flatMap { i =>
blockingTransform(
tests((i, r), sc)
)(r =>
cleanup(i).flatMap(_ =>
fromTry(r)
)(ec)
)(ec)
}(ec)
}
}
}