Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,21 @@ case class CheckConfig private (
/**
* The proportion of values discarded by the generator allowed before the test
* is considered failed.
*
* A value is considered discarded if the generator outputs `None`. The
* generator must not discard more values than the number of successful runs,
* so this ratio is a proportion of [[minimumSuccessful]].
*/
def withMaximumDiscardRatio(maximumDiscardRatio: Int) = copy(
maximumDiscardRatio = maximumDiscardRatio
)

/** The [[org.scalacheck.Gen.Parameters.size]] of the generator. */
def withMaximumGeneratorSize(maximumGeneratorSize: Int) = copy(
maximumGeneratorSize = maximumGeneratorSize
)

/** The number of concurrent runs. */
def withPerPropertyParallelism(perPropertyParallelism: Int) = copy(
perPropertyParallelism = perPropertyParallelism
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import scala.util.control.NoStackTrace
import org.scalacheck.rng.Seed
import org.scalacheck.{ Arbitrary, Gen }
import cats.data.Validated
import cats.effect.Concurrent
import cats.MonadThrow

trait Checkers {
self: EffectSuiteAux =>
Expand All @@ -20,7 +22,7 @@ trait Checkers {
f andThen (b => Prop[F, B].lift(b))
}

// Configuration for property-based tests
/** Configuration for all property-based tests in this suite. */
def checkConfig: CheckConfig = CheckConfig.default

class PartiallyAppliedForall(config: CheckConfig) {
Expand All @@ -43,9 +45,6 @@ trait Checkers {
B: PropF](
f: (A1, A2, A3) => B)(
implicit loc: SourceLocation): F[Expectations] = {
implicit val tuple3Show: Show[(A1, A2, A3)] = {
case (a1, a2, a3) => s"(${a1.show},${a2.show},${a3.show})"
}
forall(implicitly[Arbitrary[(A1, A2, A3)]].arbitrary)(liftProp(
f.tupled))
}
Expand All @@ -58,10 +57,6 @@ trait Checkers {
B: PropF
](f: (A1, A2, A3, A4) => B)(
implicit loc: SourceLocation): F[Expectations] = {
implicit val tuple3Show: Show[(A1, A2, A3, A4)] = {
case (a1, a2, a3, a4) =>
s"(${a1.show},${a2.show},${a3.show},${a4.show})"
}
forall(implicitly[Arbitrary[(A1, A2, A3, A4)]].arbitrary)(
liftProp(f.tupled))
}
Expand All @@ -75,10 +70,6 @@ trait Checkers {
B: PropF
](f: (A1, A2, A3, A4, A5) => B)(
implicit loc: SourceLocation): F[Expectations] = {
implicit val tuple3Show: Show[(A1, A2, A3, A4, A5)] = {
case (a1, a2, a3, a4, a5) =>
s"(${a1.show},${a2.show},${a3.show},${a4.show},${a5.show})"
}
forall(implicitly[Arbitrary[(A1, A2, A3, A4, A5)]].arbitrary)(
liftProp(f.tupled))
}
Expand All @@ -93,10 +84,6 @@ trait Checkers {
B: PropF
](f: (A1, A2, A3, A4, A5, A6) => B)(
implicit loc: SourceLocation): F[Expectations] = {
implicit val tuple3Show: Show[(A1, A2, A3, A4, A5, A6)] = {
case (a1, a2, a3, a4, a5, a6) =>
s"(${a1.show},${a2.show},${a3.show},${a4.show},${a5.show},${a6.show})"
}
forall(implicitly[Arbitrary[(A1, A2, A3, A4, A5, A6)]].arbitrary)(
liftProp(f.tupled))
}
Expand All @@ -106,6 +93,59 @@ trait Checkers {
forall_(gen, liftProp(f))

private def forall_[A: Show](gen: Gen[A], f: A => F[Expectations])(
implicit loc: SourceLocation): F[Expectations] =
Helpers.forall(config, gen, f)
}

object forall extends PartiallyAppliedForall(checkConfig) {

/** Configuration for this specific property assertion. */
def withConfig(config: CheckConfig) = new PartiallyAppliedForall(config)
}
}

object Checkers {
// These allow us to define functions that go from F[Expectations]
trait Prop[F[_], A] {
def lift(a: A): F[Expectations]
}

object Prop {
def apply[F[_], B](implicit ev: Prop[F, B]): Prop[F, B] = ev

implicit def wrap[F[_]: Applicative]: Prop[F, Expectations] =
new Prop[F, Expectations] {
def lift(a: Expectations): F[Expectations] = Applicative[F].pure(a)
}

implicit def unwrapped[F[_], FE](
implicit ev: FE <:< F[Expectations]): Prop[F, FE] =
new Prop[F, FE] {
def lift(a: FE): F[Expectations] = ev(a)
}
}

private def failureMessage(ith: Int, seed: Seed, input: String): String =
s"""Property test failed on try $ith with seed ${seed} and input $input.
|You can reproduce this by adding the following override to your suite:
|
|override def checkConfig = super.checkConfig.withInitialSeed($seed.toOption)""".stripMargin

private[scalacheck] class PropertyTestError(
ith: Int,
seed: Seed,
input: String,
cause: Throwable)
extends RuntimeException(failureMessage(ith, seed, input), cause)
with NoStackTrace

object Helpers {

/** Runs assertions in a property test concurrently. */
def forall[F[_]: Defer: Concurrent, A: Show](
config: CheckConfig,
gen: Gen[A],
f: A => F[Expectations])(
implicit loc: SourceLocation): F[Expectations] = {
val params = Gen.Parameters.default.withNoInitialSeed.withSize(
config.maximumGeneratorSize)
Expand All @@ -130,37 +170,33 @@ trait Checkers {
.map { status => status.endResult(config) }
}

private def seedStream(initial: Seed): fs2.Stream[F, Seed] =
fs2.Stream.iterate[F, Seed](initial)(_.slide)
}

object forall extends PartiallyAppliedForall(checkConfig) {
def withConfig(config: CheckConfig) = new PartiallyAppliedForall(config)
}

private def testOne[T: Show](
gen: Gen[T],
f: T => F[Expectations])(
params: Gen.Parameters,
seed: Seed): F[TestResult] = {
Defer[F](self.effect).defer {
gen(params, seed)
.traverse(x => f(x).attempt.map(x -> _))
.map { (x: Option[(T, Either[Throwable, Expectations])]) =>
x match {
case Some((_, Right(ex))) if ex.run.isValid => TestResult.Success
case Some((t, Right(ex))) => TestResult.Failure(t.show, ex)
case Some((t, Left(exception: ExpectationFailed))) =>
TestResult.Failure(t.show,
Expectations(Validated.invalidNel(exception)))
case Some((t, Left(other))) => TestResult.Exception(t.show, other)
case None => TestResult.Discard
private def testOne[F[_]: Defer: MonadThrow, T: Show](
gen: Gen[T],
f: T => F[Expectations])(
params: Gen.Parameters,
seed: Seed): F[TestResult] = {
Defer[F].defer {
gen(params, seed)
.traverse(x => f(x).attempt.map(x -> _))
.map { (x: Option[(T, Either[Throwable, Expectations])]) =>
x match {
case Some((_, Right(ex))) if ex.run.isValid => TestResult.Success
case Some((t, Right(ex))) => TestResult.Failure(t.show, ex)
case Some((t, Left(exception: ExpectationFailed))) =>
TestResult.Failure(
t.show,
Expectations(Validated.invalidNel(exception)))
case Some((t, Left(other))) => TestResult.Exception(t.show, other)
case None => TestResult.Discard
}
}
}
}
}
}

private[scalacheck] case class Status[T](
private def seedStream[F[_]](initial: Seed): fs2.Stream[F, Seed] =
fs2.Stream.iterate[F, Seed](initial)(_.slide)
}
private case class Status[T](
succeeded: Int,
discarded: Int,
failure: Option[Expectations]
Expand Down Expand Up @@ -198,28 +234,6 @@ trait Checkers {
def start[T] = Status[T](0, 0, None)
}

}

object Checkers {
trait Prop[F[_], A] {
def lift(a: A): F[Expectations]
}

object Prop {
def apply[F[_], B](implicit ev: Prop[F, B]): Prop[F, B] = ev

implicit def wrap[F[_]: Applicative]: Prop[F, Expectations] =
new Prop[F, Expectations] {
def lift(a: Expectations): F[Expectations] = Applicative[F].pure(a)
}

implicit def unwrapped[F[_], FE](
implicit ev: FE <:< F[Expectations]): Prop[F, FE] =
new Prop[F, FE] {
def lift(a: FE): F[Expectations] = ev(a)
}
}

private sealed trait TestResult
private object TestResult {
case object Success extends TestResult
Expand All @@ -229,17 +243,4 @@ object Checkers {
case class Exception(input: String, error: Throwable) extends TestResult
}

private def failureMessage(ith: Int, seed: Seed, input: String): String =
s"""Property test failed on try $ith with seed ${seed} and input $input.
|You can reproduce this by adding the following override to your suite:
|
|override def checkConfig = super.checkConfig.withInitialSeed($seed.toOption)""".stripMargin

private class PropertyTestError(
ith: Int,
seed: Seed,
input: String,
cause: Throwable)
extends RuntimeException(failureMessage(ith, seed, input), cause)
with NoStackTrace
}