summaryrefslogtreecommitdiff
path: root/src/partest-extras/scala/org/scalacheck/Test.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/partest-extras/scala/org/scalacheck/Test.scala')
-rw-r--r--src/partest-extras/scala/org/scalacheck/Test.scala372
1 files changed, 372 insertions, 0 deletions
diff --git a/src/partest-extras/scala/org/scalacheck/Test.scala b/src/partest-extras/scala/org/scalacheck/Test.scala
new file mode 100644
index 0000000000..9a9c62b93f
--- /dev/null
+++ b/src/partest-extras/scala/org/scalacheck/Test.scala
@@ -0,0 +1,372 @@
+/*-------------------------------------------------------------------------*\
+** ScalaCheck **
+** Copyright (c) 2007-2014 Rickard Nilsson. All rights reserved. **
+** http://www.scalacheck.org **
+** **
+** This software is released under the terms of the Revised BSD License. **
+** There is NO WARRANTY. See the file LICENSE for the full text. **
+\*------------------------------------------------------------------------ */
+
+package org.scalacheck
+
+import Prop.Arg
+
+object Test {
+
+ import util.{FreqMap, ConsoleReporter}
+
+ /** Test parameters used by the check methods. Default
+ * parameters are defined by [[Test.Parameters.Default]]. */
+ trait Parameters {
+ /** The minimum number of tests that must succeed for ScalaCheck to
+ * consider a property passed. */
+ val minSuccessfulTests: Int
+
+ /** Create a copy of this [[Test.Parameters]] instance with
+ * [[Test.Parameters.minSuccessfulTests]] set to the specified value. */
+ def withMinSuccessfulTests(minSuccessfulTests: Int): Parameters = cp(
+ minSuccessfulTests = minSuccessfulTests
+ )
+
+ /** The starting size given as parameter to the generators. */
+ val minSize: Int
+
+ /** Create a copy of this [[Test.Parameters]] instance with
+ * [[Test.Parameters.minSize]] set to the specified value. */
+ def withMinSize(minSize: Int): Parameters = cp(
+ minSize = minSize
+ )
+
+ /** The maximum size given as parameter to the generators. */
+ val maxSize: Int
+
+ /** Create a copy of this [[Test.Parameters]] instance with
+ * [[Test.Parameters.maxSize]] set to the specified value. */
+ def withMaxSize(maxSize: Int): Parameters = cp(
+ maxSize = maxSize
+ )
+
+ /** The random number generator used. */
+ val rng: scala.util.Random
+
+ /** Create a copy of this [[Test.Parameters]] instance with
+ * [[Test.Parameters.rng]] set to the specified value. */
+ def withRng(rng: scala.util.Random): Parameters = cp(
+ rng = rng
+ )
+
+ /** The number of tests to run in parallel. */
+ val workers: Int
+
+ /** Create a copy of this [[Test.Parameters]] instance with
+ * [[Test.Parameters.workers]] set to the specified value. */
+ def withWorkers(workers: Int): Parameters = cp(
+ workers = workers
+ )
+
+ /** A callback that ScalaCheck calls each time a test is executed. */
+ val testCallback: TestCallback
+
+ /** Create a copy of this [[Test.Parameters]] instance with
+ * [[Test.Parameters.testCallback]] set to the specified value. */
+ def withTestCallback(testCallback: TestCallback): Parameters = cp(
+ testCallback = testCallback
+ )
+
+ /** The maximum ratio between discarded and passed tests allowed before
+ * ScalaCheck gives up and discards the property. At least
+ * `minSuccesfulTests` will always be run, though. */
+ val maxDiscardRatio: Float
+
+ /** Create a copy of this [[Test.Parameters]] instance with
+ * [[Test.Parameters.maxDiscardRatio]] set to the specified value. */
+ def withMaxDiscardRatio(maxDiscardRatio: Float): Parameters = cp(
+ maxDiscardRatio = maxDiscardRatio
+ )
+
+ /** A custom class loader that should be used during test execution. */
+ val customClassLoader: Option[ClassLoader]
+
+ /** Create a copy of this [[Test.Parameters]] instance with
+ * [[Test.Parameters.customClassLoader]] set to the specified value. */
+ def withCustomClassLoader(customClassLoader: Option[ClassLoader]
+ ): Parameters = cp(
+ customClassLoader = customClassLoader
+ )
+
+ // private since we can't guarantee binary compatibility for this one
+ private case class cp(
+ minSuccessfulTests: Int = minSuccessfulTests,
+ minSize: Int = minSize,
+ maxSize: Int = maxSize,
+ rng: scala.util.Random = rng,
+ workers: Int = workers,
+ testCallback: TestCallback = testCallback,
+ maxDiscardRatio: Float = maxDiscardRatio,
+ customClassLoader: Option[ClassLoader] = customClassLoader
+ ) extends Parameters
+ }
+
+ /** Test parameters used by the check methods. Default
+ * parameters are defined by [[Test.Parameters.Default]]. */
+ object Parameters {
+ /** Default test parameters trait. This can be overriden if you need to
+ * tweak the parameters:
+ *
+ * {{{
+ * val myParams = new Parameters.Default {
+ * override val minSuccesfulTests = 600
+ * override val maxDiscardRatio = 8
+ * }
+ * }}}
+ *
+ * You can also use the withXXX-methods in
+ * [[org.scalacheck.Test.Parameters]] to achieve
+ * the same thing:
+ *
+ * {{{
+ * val myParams = Parameters.default
+ * .withMinSuccessfulTests(600)
+ * .withMaxDiscardRatio(8)
+ * }}} */
+ trait Default extends Parameters {
+ val minSuccessfulTests: Int = 100
+ val minSize: Int = 0
+ val maxSize: Int = Gen.Parameters.default.size
+ val rng: scala.util.Random = Gen.Parameters.default.rng
+ val workers: Int = 1
+ val testCallback: TestCallback = new TestCallback {}
+ val maxDiscardRatio: Float = 5
+ val customClassLoader: Option[ClassLoader] = None
+ }
+
+ /** Default test parameters instance. */
+ val default: Parameters = new Default {}
+
+ /** Verbose console reporter test parameters instance. */
+ val defaultVerbose: Parameters = new Default {
+ override val testCallback = ConsoleReporter(2)
+ }
+ }
+
+ /** Test statistics */
+ case class Result(
+ status: Status,
+ succeeded: Int,
+ discarded: Int,
+ freqMap: FreqMap[Set[Any]],
+ time: Long = 0
+ ) {
+ def passed = status match {
+ case Passed => true
+ case Proved(_) => true
+ case _ => false
+ }
+ }
+
+ /** Test status */
+ sealed trait Status
+
+ /** ScalaCheck found enough cases for which the property holds, so the
+ * property is considered correct. (It is not proved correct, though). */
+ case object Passed extends Status
+
+ /** ScalaCheck managed to prove the property correct */
+ sealed case class Proved(args: List[Arg[Any]]) extends Status
+
+ /** The property was proved wrong with the given concrete arguments. */
+ sealed case class Failed(args: List[Arg[Any]], labels: Set[String]) extends Status
+
+ /** The property test was exhausted, it wasn't possible to generate enough
+ * concrete arguments satisfying the preconditions to get enough passing
+ * property evaluations. */
+ case object Exhausted extends Status
+
+ /** An exception was raised when trying to evaluate the property with the
+ * given concrete arguments. If an exception was raised before or during
+ * argument generation, the argument list will be empty. */
+ sealed case class PropException(args: List[Arg[Any]], e: Throwable,
+ labels: Set[String]) extends Status
+
+ /** An exception was raised when trying to generate concrete arguments
+ * for evaluating the property.
+ * @deprecated Not used. The type PropException is used for all exceptions.
+ */
+ @deprecated("Not used. The type PropException is used for all exceptions.", "1.11.2")
+ sealed case class GenException(e: Throwable) extends Status
+
+ trait TestCallback { self =>
+ /** Called each time a property is evaluated */
+ def onPropEval(name: String, threadIdx: Int, succeeded: Int,
+ discarded: Int): Unit = ()
+
+ /** Called whenever a property has finished testing */
+ def onTestResult(name: String, result: Result): Unit = ()
+
+ def chain(testCallback: TestCallback) = new TestCallback {
+ override def onPropEval(name: String, threadIdx: Int,
+ succeeded: Int, discarded: Int
+ ): Unit = {
+ self.onPropEval(name,threadIdx,succeeded,discarded)
+ testCallback.onPropEval(name,threadIdx,succeeded,discarded)
+ }
+
+ override def onTestResult(name: String, result: Result): Unit = {
+ self.onTestResult(name,result)
+ testCallback.onTestResult(name,result)
+ }
+ }
+ }
+
+ private def assertParams(prms: Parameters) = {
+ import prms._
+ if(
+ minSuccessfulTests <= 0 ||
+ maxDiscardRatio <= 0 ||
+ minSize < 0 ||
+ maxSize < minSize ||
+ workers <= 0
+ ) throw new IllegalArgumentException("Invalid test parameters")
+ }
+
+ private def secure[T](x: => T): Either[T,Throwable] =
+ try { Left(x) } catch { case e: Throwable => Right(e) }
+
+ def parseParams(args: Array[String]): Option[Parameters] = {
+ var params = Parameters.default
+ args.grouped(2).filter(_.size > 1).map(a => (a(0), a(1))).foreach {
+ case ("-workers" | "-w", n) => params = params.withWorkers(n.toInt)
+ case ("-minSize" | "-n", n) => params = params.withMinSize(n.toInt)
+ case ("-maxSize" | "-x", n) => params = params.withMaxSize(n.toInt)
+ case ("-verbosity" | "-v", n) => params = params.withTestCallback(ConsoleReporter(n.toInt))
+ case ("-maxDiscardRatio" | "-r", n) => params = params.withMaxDiscardRatio(n.toFloat)
+ case ("-minSuccessfulTests" | "-s", n) => params = params.withMinSuccessfulTests(n.toInt)
+ case _ =>
+ }
+ Some(params)
+ }
+
+ /** Tests a property with parameters that are calculated by applying
+ * the provided function to [[Test.Parameters.default]].
+ * Example use:
+ *
+ * {{{
+ * Test.check(p) { _.
+ * withMinSuccessfulTests(80000).
+ * withWorkers(4)
+ * }
+ * }}}
+ */
+ def check(p: Prop)(f: Parameters => Parameters): Result =
+ check(f(Parameters.default), p)
+
+ /** Tests a property with the given testing parameters, and returns
+ * the test results. */
+ def check(params: Parameters, p: Prop): Result = {
+ import params._
+ import concurrent._
+
+ assertParams(params)
+ if(workers > 1) {
+ assert(!p.isInstanceOf[Commands], "Commands cannot be checked multi-threaded")
+ }
+
+ val iterations = math.ceil(minSuccessfulTests / (workers: Double))
+ val sizeStep = (maxSize-minSize) / (iterations*workers)
+ var stop = false
+ val genPrms = new Gen.Parameters.Default { override val rng = params.rng }
+ val tp = java.util.concurrent.Executors.newFixedThreadPool(workers)
+ implicit val ec = ExecutionContext.fromExecutor(tp)
+
+ def workerFun(workerIdx: Int): Result = {
+ var n = 0 // passed tests
+ var d = 0 // discarded tests
+ var res: Result = null
+ var fm = FreqMap.empty[Set[Any]]
+ while(!stop && res == null && n < iterations) {
+ val size = (minSize: Double) + (sizeStep * (workerIdx + (workers*(n+d))))
+ val propRes = p(genPrms.withSize(size.round.toInt))
+ fm = if(propRes.collected.isEmpty) fm else fm + propRes.collected
+ propRes.status match {
+ case Prop.Undecided =>
+ d += 1
+ testCallback.onPropEval("", workerIdx, n, d)
+ // The below condition is kind of hacky. We have to have
+ // some margin, otherwise workers might stop testing too
+ // early because they have been exhausted, but the overall
+ // test has not.
+ if (n+d > minSuccessfulTests && 1+workers*maxDiscardRatio*n < d)
+ res = Result(Exhausted, n, d, fm)
+ case Prop.True =>
+ n += 1
+ testCallback.onPropEval("", workerIdx, n, d)
+ case Prop.Proof =>
+ n += 1
+ res = Result(Proved(propRes.args), n, d, fm)
+ stop = true
+ case Prop.False =>
+ res = Result(Failed(propRes.args,propRes.labels), n, d, fm)
+ stop = true
+ case Prop.Exception(e) =>
+ res = Result(PropException(propRes.args,e,propRes.labels), n, d, fm)
+ stop = true
+ }
+ }
+ if (res == null) {
+ if (maxDiscardRatio*n > d) Result(Passed, n, d, fm)
+ else Result(Exhausted, n, d, fm)
+ } else res
+ }
+
+ def mergeResults(r1: Result, r2: Result): Result = {
+ val Result(st1, s1, d1, fm1, _) = r1
+ val Result(st2, s2, d2, fm2, _) = r2
+ if (st1 != Passed && st1 != Exhausted)
+ Result(st1, s1+s2, d1+d2, fm1++fm2, 0)
+ else if (st2 != Passed && st2 != Exhausted)
+ Result(st2, s1+s2, d1+d2, fm1++fm2, 0)
+ else {
+ if (s1+s2 >= minSuccessfulTests && maxDiscardRatio*(s1+s2) >= (d1+d2))
+ Result(Passed, s1+s2, d1+d2, fm1++fm2, 0)
+ else
+ Result(Exhausted, s1+s2, d1+d2, fm1++fm2, 0)
+ }
+ }
+
+ try {
+ val start = System.currentTimeMillis
+ val r =
+ if(workers < 2) workerFun(0)
+ else {
+ val fs = List.range(0,workers) map (idx => Future {
+ params.customClassLoader.map(
+ Thread.currentThread.setContextClassLoader(_)
+ )
+ blocking { workerFun(idx) }
+ })
+ val zeroRes = Result(Passed,0,0,FreqMap.empty[Set[Any]],0)
+ val res = Future.fold(fs)(zeroRes)(mergeResults)
+ Await.result(res, concurrent.duration.Duration.Inf)
+ }
+ val timedRes = r.copy(time = System.currentTimeMillis-start)
+ params.testCallback.onTestResult("", timedRes)
+ timedRes
+ } finally {
+ stop = true
+ tp.shutdown()
+ }
+ }
+
+ /** Check a set of properties. */
+ def checkProperties(prms: Parameters, ps: Properties): Seq[(String,Result)] =
+ ps.properties.map { case (name,p) =>
+ val testCallback = new TestCallback {
+ override def onPropEval(n: String, t: Int, s: Int, d: Int) =
+ prms.testCallback.onPropEval(name,t,s,d)
+ override def onTestResult(n: String, r: Result) =
+ prms.testCallback.onTestResult(name,r)
+ }
+ val res = check(prms.withTestCallback(testCallback), p)
+ (name,res)
+ }
+}