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

Enable lazy construction of label strings #1041

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions core/jvm/src/test/scala/org/scalacheck/GenSpecification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to exercise the idioms in the absence of tests

}

property("uniform long #209") = {
Expand Down
29 changes: 29 additions & 0 deletions core/shared/src/main/scala-2/org/scalacheck/package.scala
Original file line number Diff line number Diff line change
@@ -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))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another part of my PR (#979) was to avoid evaluating the label if it will never be displayed to the user. What do you think about doing the same here?

def =|=(label: => String): Prop =
  prop.map(r =>
    r.status match {
      case Prop.False | Prop.Exception(_) => r.label(l)
      case Prop.Proof | Prop.True | Prop.Undecided => r
    })

}
// 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)
}
}
21 changes: 21 additions & 0 deletions core/shared/src/main/scala-3/org/scalacheck/package.scala
Original file line number Diff line number Diff line change
@@ -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))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does Scala 3 not need an extension for Boolean like the Scala 2 version?


extension (label: => String) def =|=(prop: Prop): Prop = prop.map(_.label(label))
}
33 changes: 17 additions & 16 deletions core/shared/src/main/scala/org/scalacheck/Prop.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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
}
Expand All @@ -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)))
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down