/*-------------------------------------------------------------------------*\
** ScalaCheck **
** Copyright (c) 2007-2010 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
object Test {
import util.FreqMap
import scala.collection.immutable
import Prop.FM
import util.CmdLineParser
/** Test parameters */
case class Params(
minSuccessfulTests: Int = 100,
maxDiscardedTests: Int = 500,
minSize: Int = 0,
maxSize: Int = Gen.Params().size,
rng: java.util.Random = Gen.Params().rng,
workers: Int = 1,
testCallback: TestCallback = new TestCallback {}
)
/** Test statistics */
case class Result(status: Status, succeeded: Int, discarded: Int, freqMap: FM) {
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: Prop.Args) extends Status
/** The property was proved wrong with the given concrete arguments. */
sealed case class Failed(args: Prop.Args, 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. */
sealed case class PropException(args: Prop.Args, e: Throwable,
labels: Set[String]) extends Status
/** An exception was raised when trying to generate concrete arguments
* for evaluating the property. */
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: Params) = {
import prms._
if(
minSuccessfulTests <= 0 ||
maxDiscardedTests < 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 => Right(e) }
private[scalacheck] lazy val cmdLineParser = new CmdLineParser {
object OptMinSuccess extends IntOpt {
val default = Test.Params().minSuccessfulTests
val names = Set("minSuccessfulTests", "s")
val help = "Number of tests that must succeed in order to pass a property"
}
object OptMaxDiscarded extends IntOpt {
val default = Test.Params().maxDiscardedTests
val names = Set("maxDiscardedTests", "d")
val help =
"Number of tests that can be discarded before ScalaCheck stops " +
"testing a property"
}
object OptMinSize extends IntOpt {
val default = Test.Params().minSize
val names = Set("minSize", "n")
val help = "Minimum data generation size"
}
object OptMaxSize extends IntOpt {
val default = Test.Params().maxSize
val names = Set("maxSize", "x")
val help = "Maximum data generation size"
}
object OptWorkers extends IntOpt {
val default = Test.Params().workers
val names = Set("workers", "w")
val help = "Number of threads to execute in parallel for testing"
}
object OptVerbosity extends IntOpt {
val default = 1
val names = Set("verbosity", "v")
val help = "Verbosity level"
}
val opts = Set[Opt[_]](
OptMinSuccess, OptMaxDiscarded, OptMinSize,
OptMaxSize, OptWorkers, OptVerbosity
)
def parseParams(args: Array[String]) = parseArgs(args) {
optMap => Test.Params(
optMap(OptMinSuccess),
optMap(OptMaxDiscarded),
optMap(OptMinSize),
optMap(OptMaxSize),
Test.Params().rng,
optMap(OptWorkers),
ConsoleReporter(optMap(OptVerbosity))
)
}
}
/** Tests a property with the given testing parameters, and returns
* the test results. */
def check(prms: Params, p: Prop): Result = {
import prms._
import actors.Futures.future
//import scala.concurrent.ops.future
assertParams(prms)
if(workers > 1)
assert(!p.isInstanceOf[Commands], "Commands cannot be checked multi-threaded")
val iterations = minSuccessfulTests / workers
val sizeStep = (maxSize-minSize) / (minSuccessfulTests: Float)
var stop = false
def worker(workerdIdx: Int) = future {
var n = 0
var d = 0
var size = workerdIdx*sizeStep
var res: Result = null
var fm = FreqMap.empty[immutable.Set[Any]]
while(!stop && res == null && n < iterations) {
val propPrms = Prop.Params(Gen.Params(size.round, prms.rng), fm)
secure(p(propPrms)) match {
case Right(e) => res =
Result(GenException(e), n, d, FreqMap.empty[immutable.Set[Any]])
case Left(propRes) =>
fm =
if(propRes.collected.isEmpty) fm
else fm + propRes.collected
propRes.status match {
case Prop.Undecided =>
d += 1
testCallback.onPropEval("", workerdIdx, n, d)
if(d >= maxDiscardedTests) res = Result(Exhausted, n, d, fm)
case Prop.True =>
n += 1
testCallback.onPropEval("", workerdIdx, n, d)
case Prop.Proof =>
n += 1
res = Result(Proved(propRes.args), n, d, fm)
case Prop.False => res =
Result(Failed(propRes.args, propRes.labels), n, d, fm)
case Prop.Exception(e) => res =
Result(PropException(propRes.args, e, propRes.labels), n, d, fm)
}
}
size += sizeStep
}
if(res != null) stop = true
else res = Result(Passed, n, d, fm)
res
}
def mergeResults(r1: () => Result, r2: () => Result) = r1() match {
case Result(Passed, s1, d1, fm1) => r2() match {
case Result(Passed, s2, d2, fm2) if d1+d2 >= maxDiscardedTests =>
() => Result(Exhausted, s1+s2, d1+d2, fm1++fm2)
case Result(st, s2, d2, fm2) =>
() => Result(st, s1+s2, d1+d2, fm1++fm2)
}
case r => () => r
}
val results = for(i <- 0 until workers) yield worker(i)
val r = results.reduceLeft(mergeResults)()
stop = true
results foreach (_.apply())
prms.testCallback.onTestResult("", r)
r
}
def checkProperties(prms: Params, 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 copy (testCallback = testCallback), p)
(name,res)
}
// Deprecated methods //
/** Default testing parameters
* @deprecated Use Test.Params()
instead */
@deprecated("Use Test.Params() instead")
val defaultParams = Params()
/** Property evaluation callback. Takes number of passed and
* discarded tests, respectively */
@deprecated("(v1.8)")
type PropEvalCallback = (Int,Int) => Unit
/** Property evaluation callback. Takes property name, and number of passed
* and discarded tests, respectively */
@deprecated("(v1.8)")
type NamedPropEvalCallback = (String,Int,Int) => Unit
/** Test callback. Takes property name, and test results. */
@deprecated("(v1.8)")
type TestResCallback = (String,Result) => Unit
/** @deprecated (v1.8) Use check(prms.copy(testCallback = myCallback), p)
instead. */
@deprecated("(v1.8) Use check(prms.copy(testCallback = myCallback), p) instead")
def check(prms: Params, p: Prop, propCallb: PropEvalCallback): Result = {
val testCallback = new TestCallback {
override def onPropEval(n: String, t: Int, s: Int, d: Int) = propCallb(s,d)
}
check(prms copy (testCallback = testCallback), p)
}
/** Tests a property and prints results to the console. The
* maxDiscarded
parameter specifies how many
* discarded tests that should be allowed before ScalaCheck
* @deprecated (v1.8) Use check(Params(maxDiscardedTests = n, testCallback = ConsoleReporter()), p)
instead. */
@deprecated("(v1.8) Use check(Params(maxDiscardedTests = n, testCallback = ConsoleReporter()), p) instead.")
def check(p: Prop, maxDiscarded: Int): Result =
check(Params(maxDiscardedTests = maxDiscarded, testCallback = ConsoleReporter()), p)
/** Tests a property and prints results to the console
* @deprecated (v1.8) Use check(Params(testCallback = ConsoleReporter()), p)
instead. */
@deprecated("(v1.8) Use check(Params(testCallback = ConsoleReporter()), p) instead.")
def check(p: Prop): Result = check(Params(testCallback = ConsoleReporter()), p)
/** Tests all properties with the given testing parameters, and returns
* the test results. f
is a function which is called each
* time a property is evaluted. g
is a function called each
* time a property has been fully tested.
* @deprecated (v1.8) Use checkProperties(prms.copy(testCallback = myCallback), ps)
instead. */
@deprecated("(v1.8) Use checkProperties(prms.copy(testCallback = myCallback), ps) instead.")
def checkProperties(ps: Properties, prms: Params,
propCallb: NamedPropEvalCallback, testCallb: TestResCallback
): Seq[(String,Result)] = {
val testCallback = new TestCallback {
override def onPropEval(n: String, t: Int, s: Int, d: Int) = propCallb(n,s,d)
override def onTestResult(n: String, r: Result) = testCallb(n,r)
}
checkProperties(prms copy (testCallback = testCallback), ps)
}
/** Tests all properties with the given testing parameters, and returns
* the test results.
* @deprecated (v1.8) Use checkProperties(prms, ps) instead */
@deprecated("(v1.8) Use checkProperties(prms, ps) instead")
def checkProperties(ps: Properties, prms: Params): Seq[(String,Result)] =
checkProperties(ps, prms, (n,s,d) => (), (n,s) => ())
/** Tests all properties with default testing parameters, and returns
* the test results. The results are also printed on the console during
* testing.
* @deprecated (v1.8) Use checkProperties(Params(), ps)
instead. */
@deprecated("(v1.8) Use checkProperties(Params(), ps) instead.")
def checkProperties(ps: Properties): Seq[(String,Result)] =
checkProperties(Params(), ps)
}