Skip to content

Instantly share code, notes, and snippets.

@davidallsopp
Last active January 30, 2024 13:25
Show Gist options
  • Save davidallsopp/f65d73fea8b5e5165fc3 to your computer and use it in GitHub Desktop.
Save davidallsopp/f65d73fea8b5e5165fc3 to your computer and use it in GitHub Desktop.
Solutions to the ScalaCheck problem that shrinking failing values may generate invalid values, because the constraints of the generator are not respected. This is for using ScalaCheck from within ScalaTest.
import org.scalatest._
import prop._
import org.scalacheck.Arbitrary._
import org.scalacheck.Gen
/**
* Solutions to the ScalaCheck problem that shrinking failing values may generate
* invalid values, because the constraints of the generator are not respected.
*
* See also http://stackoverflow.com/questions/20037900/scalacheck-wont-properly-report-the-failing-case
* and http://code.google.com/p/scalatest/issues/detail?id=14
*/
class Shrinking extends PropSpec with PropertyChecks with ShouldMatchers {
def odd(i: Int) = i % 2 == 1
// Old solution - use whenever{} clause within the property
val odds: Gen[Int] = for {
n <- arbitrary[Int]
if (odd(n))
} yield n
property("fails with big numbers") {
forAll(odds) { n =>
whenever(odd(n)) {
if (n % 2 == 0) throw new Exception("Argh") // should never happen
if (n > 1000) (n / 2 + n / 2 should be(n)) else n should be(n)
}
}
}
// New solution - use suchThat() postcondition on the generator
// See https://github.com/rickynils/scalacheck/commit/2d92eb6
val odds2: Gen[Int] = arbitrary[Int].suchThat(odd)
property("fails with big numbers v2") {
forAll(odds2) { n =>
if (n % 2 == 0) throw new Exception("Argh") // should never happen
if (n > 1000) (n / 2 + n / 2 should be(n)) else n should be(n)
}
}
// Alternative solution - disable shrinking entirely (for Ints)
// From http://blog.knutwalker.de/2014/01/fun-with-scalatests-propertychecks.html
// In pure ScalaCheck you can use forAllNoShrink() but this is not exposed in ScalaTest
{
import org.scalacheck.Shrink
implicit val noShrink: Shrink[Int] = Shrink.shrinkAny
property("fails with big numbers, shrinking disabled") {
forAll(odds) { n =>
if (n % 2 == 0) throw new Exception("Argh") // should never happen
if (n > 1000) (n / 2 + n / 2 should be(n)) else n should be(n)
}
}
}
}
@mcarolan
Copy link

mcarolan commented Feb 2, 2018

:) thanks for this, frequently end up at this gist in the pits of frustration.

there is also the nuclear option of disable shrinking for all the things ever:

      implicit def noShrink[T]: Shrink[T] = Shrink.shrinkAny

whacking this here for the next time I end up reading this!

@borice
Copy link

borice commented Apr 12, 2018

@mcarolan thanks! it's sad we have to do this... like you, i end up here frequently

@ArthurAttout
Copy link

forAllNoShrink in ScalaTest would have been soooo much useful. I don't like having to disable the shrink for all the tests in a suite, nor to write a suchThat that would be 15 lines long ..

@michaelahlers
Copy link

Incidentally, org.scalacheck.ShrinkLowPriority.shinkAny is already marked implicit, so there's no need to create a definition for it. Simply import Shink.shinkAny into scope wherever you want to change the behavior of forAll. It's not the most granular mechanism, but it is simple and effective enough.

@OndrejSpanel
Copy link

OndrejSpanel commented Sep 9, 2020

import Shink.shinkAny

Just a typo fix: should be Shrink.shrinkAny

@oschrenk
Copy link

Ended up after a long journey. It is not really intuitive that

forAll {
      Gen.nonEmptyListOf(...)
} { ... } 

will fail because it will create empty lists.

I'm using the nuclear option, as stated above import org.scalacheck.Shrink.shrinkAny

@vadzim-marchanka
Copy link

Thank you so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment