diff --git a/core/jvm/src/test/scala/org/scalacheck/GenSpecification.scala b/core/jvm/src/test/scala/org/scalacheck/GenSpecification.scala index 45a9dace..c54d5b29 100644 --- a/core/jvm/src/test/scala/org/scalacheck/GenSpecification.scala +++ b/core/jvm/src/test/scala/org/scalacheck/GenSpecification.scala @@ -583,6 +583,8 @@ object GenSpecification extends Properties("Gen") with GenSpecificationVersionSp } val avg = sum / n s"average = $avg" |: avg >= 0.49 && avg <= 0.51 + s"average = $avg".ensuring(false, "eager evaluation") =|= avg >= 0.49 && avg <= 0.51 + avg >= 0.49 && avg <= 0.51 =|= s"average = $avg" } property("uniform long #209") = { diff --git a/core/shared/src/main/scala-2/org/scalacheck/package.scala b/core/shared/src/main/scala-2/org/scalacheck/package.scala new file mode 100644 index 00000000..4da5b7d2 --- /dev/null +++ b/core/shared/src/main/scala-2/org/scalacheck/package.scala @@ -0,0 +1,29 @@ +/* + * ScalaCheck + * Copyright (c) 2007-2021 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 `package` { + implicit class `by-name label :|`(private val prop: Prop) extends AnyVal { + + /** Label this property. + * + * The label is evaluated lazily. The operator name is chosen for its precedence btween boolean operators and + * others. + */ + def =|=(label: => String): Prop = prop.map(_.label(label)) + } + // chained implicit for true =|= label + implicit class `by-name label bool :| label`(private val bool: Boolean) extends AnyVal { + def =|=(label: => String): Prop = (bool: Prop).=|=(label) + } + implicit class `by-name label |: prop`(label: => String) { + def =|=(prop: Prop): Prop = prop.=|=(label) + } +} diff --git a/core/shared/src/main/scala-3/org/scalacheck/package.scala b/core/shared/src/main/scala-3/org/scalacheck/package.scala new file mode 100644 index 00000000..a51f9d39 --- /dev/null +++ b/core/shared/src/main/scala-3/org/scalacheck/package.scala @@ -0,0 +1,21 @@ +/* + * ScalaCheck + * Copyright (c) 2007-2021 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 `package` { + + /** Label this property. The label is evaluated lazily. + * + * The operator name is chosen for its precedence between boolean operators and others. + */ + extension (prop: Prop) def =|=(label: => String): Prop = prop.map(_.label(label)) + + extension (label: => String) def =|=(prop: Prop): Prop = prop.map(_.label(label)) +} diff --git a/core/shared/src/main/scala/org/scalacheck/Prop.scala b/core/shared/src/main/scala/org/scalacheck/Prop.scala index dedb1ec8..4bd25a70 100644 --- a/core/shared/src/main/scala/org/scalacheck/Prop.scala +++ b/core/shared/src/main/scala/org/scalacheck/Prop.scala @@ -9,22 +9,23 @@ package org.scalacheck -import scala.annotation.tailrec - import rng.Seed import util.{Pretty, ConsoleReporter} /** Helper class to satisfy ScalaJS compilation. Do not use this directly, use `Prop.apply` instead. */ sealed class PropFromFun(f: Gen.Parameters => Prop.Result) extends Prop { + + /** Evaluate this property by applying the function. */ def apply(prms: Gen.Parameters) = f(prms) } @Platform.EnableReflectiveInstantiation -sealed abstract class Prop extends Serializable { self => +sealed abstract class Prop extends Serializable { import Prop.{Result, True, False, Undecided, provedToTrue, mergeRes} + /** Evaluate this property. */ def apply(prms: Gen.Parameters): Result def viewSeed(name: String): Prop = @@ -36,7 +37,7 @@ sealed abstract class Prop extends Serializable { self => val sd = Seed.random() (prms0.withInitialSeed(sd), sd) } - val res = self(prms) + val res = apply(prms) if (res.failure) println(s"failing seed for $name is ${seed.toBase64}") res } @@ -46,7 +47,7 @@ sealed abstract class Prop extends Serializable { self => useSeed(seed) def useSeed(seed: Seed): Prop = - Prop(prms0 => self(prms0.withInitialSeed(seed))) + Prop(prms0 => apply(prms0.withInitialSeed(seed))) def contramap(f: Gen.Parameters => Gen.Parameters): Prop = new PropFromFun(params => apply(f(params))) @@ -197,14 +198,12 @@ object Prop { labels: Set[String] = Set.empty ) { def success = status match { - case True => true - case Proof => true + case True | Proof => true case _ => false } def failure = status match { - case False => true - case Exception(_) => true + case False | Exception(_) => true case _ => false } @@ -276,6 +275,10 @@ object Prop { case (Proof, _) => mergeRes(this, r, r.status) case (True, _) => mergeRes(this, r, r.status) } + + def flatMap(f: Result => Result): Result = if (success) f(this) else this + + def recover(f: Result => Result): Result = if (failure) f(this) else this } sealed trait Status @@ -1274,14 +1277,12 @@ 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: Gen.Parameters, endTime: Long): Result = { + 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") - } else { - if (result.success) result - else attempt(prms, endTime) - } + if (System.currentTimeMillis > endTime) + result.flatMap(_ => Result(status = False)).label("Timeout") + else + result.recover(_ => attempt(prms, endTime)) } Prop.apply(prms => attempt(prms, System.currentTimeMillis + maximumMs)) } diff --git a/core/shared/src/test/scala/org/scalacheck/StatsSpecification.scala b/core/shared/src/test/scala/org/scalacheck/StatsSpecification.scala index 0a214693..0e5d4197 100644 --- a/core/shared/src/test/scala/org/scalacheck/StatsSpecification.scala +++ b/core/shared/src/test/scala/org/scalacheck/StatsSpecification.scala @@ -87,7 +87,7 @@ object StatsSpecification extends Properties("Stats") { case class Bounds(min: Double, max: Double) { def contains(x: Double): Prop = - Prop(min <= x && x <= max) :| s"($min <= $x <= $max) was false" + Prop(min <= x && x <= max) =|= s"($min <= $x <= $max) was false" } implicit class MakeBounds(val n: Double) extends AnyVal {