Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comprehensive review of Test.Parameters. #522

Merged
merged 8 commits into from
Aug 28, 2019
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
43 changes: 43 additions & 0 deletions jvm/src/test/scala/org/scalacheck/TestSpecification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,47 @@ object TestSpecification extends Properties("Test") {
}
}

property("disabling shrinking works") = {

object Bogus {
val gen: Gen[Bogus] =
Gen.choose(Int.MinValue, Int.MaxValue).map(Bogus(_))

var shrunk: Boolean = false

implicit def shrinkBogus: Shrink[Bogus] = {
Shrink { (b: Bogus) => shrunk = true; Stream.empty }
}
}

case class Bogus(x: Int)

val prop = Prop.forAll[Bogus, Prop](Bogus.gen) { b => Prop(false) }
val prms = Test.Parameters.default.disableLegacyShrinking
val res = Test.check(prms, prop)
Prop(!res.passed && !Bogus.shrunk)
}

property("Properties.overrideParameters overrides Test.Parameters") = {

val seed0 = rng.Seed.fromBase64("aaaaa_mr05Z_DCbd2PyUolC0h93iH1MQwIdnH2UuI4L=").get
val seed1 = rng.Seed.fromBase64("zzzzz_mr05Z_DCbd2PyUolC0h93iH1MQwIdnH2UuI4L=").get

val myProps = new Properties("MyProps") {
override def overrideParameters(prms: Test.Parameters): Test.Parameters =
prms.withInitialSeed(Some(seed1))
property("initial seed matches") =
Prop { prms =>
val ok = prms.initialSeed == Some(seed1)
Prop.Result(status = if (ok) Prop.Proof else Prop.False)
}
}

Prop {
val prms = Test.Parameters.default.withInitialSeed(Some(seed0))
val results = Test.checkProperties(prms, myProps)
val ok = results.forall { case (_, res) => res.passed }
Prop.Result(status = if (ok) Prop.Proof else Prop.False)
}
}
}
38 changes: 32 additions & 6 deletions src/main/scala/org/scalacheck/Gen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ object Gen extends GenArities with GenVersionSpecific {
//// Public interface ////

/** Generator parameters, used by [[org.scalacheck.Gen.apply]] */
sealed abstract class Parameters extends Serializable {
sealed abstract class Parameters extends Serializable { outer =>

/**
* The size of the generated value. Generator implementations are
Expand All @@ -268,34 +268,60 @@ object Gen extends GenArities with GenVersionSpecific {
*/
val size: Int

private[this] def cpy(
size0: Int = outer.size,
initialSeed0: Option[Seed] = outer.initialSeed,
useLegacyShrinking0: Boolean = outer.useLegacyShrinking
): Parameters =
new Parameters {
val size: Int = size0
val initialSeed: Option[Seed] = initialSeed0
override val useLegacyShrinking: Boolean = useLegacyShrinking0
}

/**
* Create a copy of this [[Gen.Parameters]] instance with
* [[Gen.Parameters.size]] set to the specified value.
*/
def withSize(size: Int): Parameters =
cp(size = size)
cpy(size0 = size)

/**
*
*/
val initialSeed: Option[Seed]

def withInitialSeed(o: Option[Seed]): Parameters =
cpy(initialSeed0 = o)

def withInitialSeed(seed: Seed): Parameters =
cp(initialSeed = Some(seed))
cpy(initialSeed0 = Some(seed))

def withInitialSeed(n: Long): Parameters =
cp(initialSeed = Some(Seed(n)))
cpy(initialSeed0 = Some(Seed(n)))

def withNoInitialSeed: Parameters =
cp(initialSeed = None)
cpy(initialSeed0 = None)

def useInitialSeed[A](seed: Seed)(f: (Parameters, Seed) => A): A =
initialSeed match {
case Some(s) => f(this.withNoInitialSeed, s)
case None => f(this, seed)
}

// private since we can't guarantee binary compatibility for this one
val useLegacyShrinking: Boolean = true

def disableLegacyShrinking: Parameters =
withLegacyShrinking(false)

def enableLegacyShrinking: Parameters =
withLegacyShrinking(true)

def withLegacyShrinking(b: Boolean): Parameters =
cpy(useLegacyShrinking0 = b)

// no longer used, but preserved for binary compatibility
non marked this conversation as resolved.
Show resolved Hide resolved
@deprecated("cp is deprecated. use cpy.", "1.14.1")
private case class cp(size: Int = size, initialSeed: Option[Seed] = None) extends Parameters
}

Expand Down
27 changes: 12 additions & 15 deletions src/main/scala/org/scalacheck/Prop.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ sealed class PropFromFun(f: Gen.Parameters => Prop.Result) extends Prop {
sealed abstract class Prop extends Serializable { self =>

import Prop.{Result, True, False, Undecided, provedToTrue, mergeRes}
import Gen.Parameters

def apply(prms: Parameters): Result
def apply(prms: Gen.Parameters): Result

def viewSeed(name: String): Prop =
Prop { prms0 =>
Expand All @@ -46,7 +45,7 @@ sealed abstract class Prop extends Serializable { self =>
def useSeed(name: String, seed: Seed): Prop =
Prop(prms0 => self(prms0.withInitialSeed(seed)))

def contramap(f: Parameters => Parameters): Prop =
def contramap(f: Gen.Parameters => Gen.Parameters): Prop =
new PropFromFun(params => apply(f(params)))

def map(f: Result => Result): Prop = Prop(prms => f(this(prms)))
Expand Down Expand Up @@ -166,9 +165,7 @@ sealed abstract class Prop extends Serializable { self =>

object Prop {

import Gen.{fail, Parameters}
import Arbitrary.{arbitrary}
import Shrink.{shrink}

// Types

Expand Down Expand Up @@ -302,7 +299,7 @@ object Prop {
}

/** Create a new property from the given function. */
def apply(f: Parameters => Result): Prop = new PropFromFun(prms =>
def apply(f: Gen.Parameters => Result): Prop = new PropFromFun(prms =>
try f(prms) catch {
case e: Throwable => Result(status = Exception(e))
}
Expand Down Expand Up @@ -437,11 +434,11 @@ object Prop {

/** A property that holds if at least one of the given generators
* fails generating a value */
def someFailing[T](gs: Seq[Gen[T]]) = atLeastOne(gs.map(_ == fail):_*)
def someFailing[T](gs: Seq[Gen[T]]) = atLeastOne(gs.map(_ == Gen.fail):_*)

/** A property that holds iff none of the given generators
* fails generating a value */
def noneFailing[T](gs: Seq[Gen[T]]) = all(gs.map(_ !== fail):_*)
def noneFailing[T](gs: Seq[Gen[T]]) = all(gs.map(_ !== Gen.fail):_*)

/** Returns true if the given statement throws an exception
* of the specified type */
Expand Down Expand Up @@ -512,7 +509,7 @@ object Prop {
* seed. In that case you should use `slideSeed` to update the
* parameters.
*/
def startSeed(prms: Parameters): (Parameters, Seed) =
def startSeed(prms: Gen.Parameters): (Gen.Parameters, Seed) =
prms.initialSeed match {
case Some(seed) => (prms.withNoInitialSeed, seed)
case None => (prms, Seed.random())
Expand All @@ -521,7 +518,7 @@ object Prop {
/**
*
*/
def slideSeed(prms: Parameters): Parameters =
def slideSeed(prms: Gen.Parameters): Gen.Parameters =
prms.initialSeed match {
case Some(seed) => prms.withInitialSeed(seed.slide)
case None => prms
Expand Down Expand Up @@ -800,8 +797,8 @@ object Prop {
case None => undecided(prms)
case Some(x) =>
val r = result(x)
if (!r.failure) r.addArg(Arg(labels,x,0,x,pp(x),pp(x)))
else shrinker(x,r,0,x)
if (r.failure && prms.useLegacyShrinking) shrinker(x,r,0,x)
else r.addArg(Arg(labels,x,0,x,pp(x),pp(x)))
}

}
Expand All @@ -814,7 +811,7 @@ object Prop {
p: P => Prop,
s1: Shrink[T1],
pp1: T1 => Pretty
): Prop = forAllShrink[T1,P](g1, shrink[T1])(f)
): Prop = forAllShrink[T1,P](g1, s1.shrink)(f)

/** Universal quantifier for two explicit generators. Shrinks failed arguments
* with the default shrink function for the type */
Expand Down Expand Up @@ -912,7 +909,7 @@ object Prop {
f: A1 => P)(implicit
p: P => Prop,
a1: Arbitrary[A1], s1: Shrink[A1], pp1: A1 => Pretty
): Prop = forAllShrink(arbitrary[A1],shrink[A1])(f andThen p)
): Prop = forAllShrink(arbitrary[A1], s1.shrink)(f andThen p)

/** Converts a function into a universally quantified property */
def forAll[A1,A2,P] (
Expand Down Expand Up @@ -994,7 +991,7 @@ object Prop {
/** Ensures that the property expression passed in completes within the given
* space of time. */
def within(maximumMs: Long)(wrappedProp: => Prop): Prop = {
@tailrec def attempt(prms: Parameters, endTime: Long): Result = {
@tailrec def attempt(prms: Gen.Parameters, endTime: Long): Result = {
val result = wrappedProp.apply(prms)
if (System.currentTimeMillis > endTime) {
(if(result.failure) result else Result(status = False)).label("Timeout")
Expand Down
86 changes: 44 additions & 42 deletions src/main/scala/org/scalacheck/ScalaCheckFramework.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ private abstract class ScalaCheckRunner extends Runner {

def deserializeTask(task: String, deserializer: String => TaskDef): BaseTask = {
val taskDef = deserializer(task)
val countTestSelectors = taskDef.selectors.toSeq.count {
case _:TestSelector => true
case _ => false
}
if (countTestSelectors == 0) rootTask(taskDef)
if (taskDef.selectors.isEmpty) rootTask(taskDef)
else checkPropTask(taskDef, single = true)
}

Expand All @@ -41,34 +37,40 @@ private abstract class ScalaCheckRunner extends Runner {

def tasks(taskDefs: Array[TaskDef]): Array[Task] = {
val isForked = taskDefs.exists(_.fingerprint().getClass.getName.contains("ForkMain"))
taskDefs.map { taskDef =>
if (isForked) checkPropTask(taskDef, single = false)
else rootTask(taskDef)
}
taskDefs.map(t => if (isForked) checkPropTask(t, single = false) else rootTask(t))
}

protected def sbtSetup(loader: ClassLoader): Parameters => Parameters =
_.withTestCallback(new Test.TestCallback {}).withCustomClassLoader(Some(loader))

abstract class BaseTask(override val taskDef: TaskDef) extends Task {
val tags: Array[String] = Array()

val props: collection.Seq[(String,Prop)] = {
val loaded: Either[Prop, Properties] = {
val fp = taskDef.fingerprint.asInstanceOf[SubclassFingerprint]
val obj = if (fp.isModule) Platform.loadModule(taskDef.fullyQualifiedName,loader)
else Platform.newInstance(taskDef.fullyQualifiedName, loader, Seq())(Seq())
obj match {
case props: Properties => props.properties
case prop: Prop => Seq("" -> prop)
case props: Properties => Right(props)
case prop: Prop => Left(prop)
}
}

// TODO copypasted from props val
val properties: Option[Properties] = {
val fp = taskDef.fingerprint.asInstanceOf[SubclassFingerprint]
val obj = if (fp.isModule) Platform.loadModule(taskDef.fullyQualifiedName,loader)
else Platform.newInstance(taskDef.fullyQualifiedName, loader, Seq())(Seq())
obj match {
case props: Properties => Some(props)
case prop: Prop => None
}
val props: collection.Seq[(String, Prop)] = loaded match {
case Right(ps) => ps.properties
case Left(prop) => Seq("" -> prop)
}

val properties: Option[Properties] =
loaded.right.toOption

val params: Parameters = {
// apply global parameters first, then allow properties to
// override them. the other order does not work because
// applyCmdParams will unconditionally set parameters to default
// values, even when they were overridden.
val ps = applyCmdParams(Parameters.default)
properties.fold(ps)(_.overrideParameters(ps))
}

def log(loggers: Array[Logger], ok: Boolean, msg: String) =
Expand All @@ -84,7 +86,7 @@ private abstract class ScalaCheckRunner extends Runner {
): Unit = continuation(execute(handler,loggers))
}

def rootTask(td: TaskDef) = new BaseTask(td) {
def rootTask(td: TaskDef): BaseTask = new BaseTask(td) {
def execute(handler: EventHandler, loggers: Array[Logger]): Array[Task] =
props.map(_._1).toSet.toArray map { name =>
checkPropTask(new TaskDef(td.fullyQualifiedName, td.fingerprint,
Expand All @@ -93,23 +95,25 @@ private abstract class ScalaCheckRunner extends Runner {
}
}

def checkPropTask(taskDef: TaskDef, single: Boolean) = new BaseTask(taskDef) {
def checkPropTask(taskDef: TaskDef, single: Boolean): BaseTask = new BaseTask(taskDef) {
def execute(handler: EventHandler, loggers: Array[Logger]): Array[Task] = {
val params = applyCmdParams(properties.foldLeft(Parameters.default)((params, props) => props.overrideParameters(params)))
val propertyFilter = params.propFilter.map(_.r)

if (single) {
val names = taskDef.selectors flatMap {
case ts: TestSelector => Array(ts.testName)
case _ => Array.empty[String]
}
names foreach { name =>
for ((`name`, prop) <- props)
executeInternal(prop, name, handler, loggers, propertyFilter)
val mprops: Map[String, Prop] = props.toMap
taskDef.selectors.foreach {
case ts: TestSelector =>
val name = ts.testName
mprops.get(name).foreach { prop =>
executeInternal(prop, name, handler, loggers, propertyFilter)
}
case _ =>
()
}
} else {
for ((name, prop) <- props)
props.foreach { case (name, prop) =>
executeInternal(prop, name, handler, loggers, propertyFilter)
}
}
Array.empty[Task]
}
Expand All @@ -118,7 +122,6 @@ private abstract class ScalaCheckRunner extends Runner {
if (propertyFilter.isEmpty || propertyFilter.exists(matchPropFilter(name, _))) {

import util.Pretty.{pretty, Params}
val params = applyCmdParams(properties.foldLeft(Parameters.default)((params, props) => props.overrideParameters(params)))
val result = Test.check(params, prop)

val event = new Event {
Expand Down Expand Up @@ -193,10 +196,7 @@ final class ScalaCheckFramework extends Framework {
val remoteArgs = _remoteArgs
val loader = _loader
val (prms,unknownArgs) = Test.CmdLineParser.parseParams(args)
val applyCmdParams = prms.andThen {
p => p.withTestCallback(new Test.TestCallback {})
.withCustomClassLoader(Some(loader))
}
val applyCmdParams = prms.andThen(sbtSetup(loader))

def receiveMessage(msg: String): Option[String] = msg(0) match {
case 'd' =>
Expand Down Expand Up @@ -224,18 +224,20 @@ final class ScalaCheckFramework extends Framework {
val args = _args
val remoteArgs = _remoteArgs
val loader = _loader
val applyCmdParams = Test.CmdLineParser.parseParams(args)._1.andThen {
p => p.withTestCallback(new Test.TestCallback {})
.withCustomClassLoader(Some(loader))

val (prms,unknownArgs) = Test.CmdLineParser.parseParams(args)

if (unknownArgs.nonEmpty) {
println(s"Warning: Unknown ScalaCheck args provided: ${unknownArgs.mkString(" ")}")
}

val applyCmdParams = prms.andThen(sbtSetup(loader))

def receiveMessage(msg: String) = None

def done = {
send(s"d$testCount,$successCount,$failureCount,$errorCount")
""
}

}

}
Loading