Skip to content

Instantly share code, notes, and snippets.

@Baccata
Last active June 4, 2025 16:22
Show Gist options
  • Save Baccata/bc05593d1c1f55ea4f9fdb53fbd0cc8d to your computer and use it in GitHub Desktop.
Save Baccata/bc05593d1c1f55ea4f9fdb53fbd0cc8d to your computer and use it in GitHub Desktop.
Something to make it easier to write scalacheck
package xen
import org.scalacheck.*
import org.scalacheck.util.Pretty
import munit.ScalaCheckSuite
import munit.TestOptions
/**
* This constructs aims at allowing to lift `Gen` instances whilst capturing the associated `Shrink` and `Pretty`
* at the time of lifting. This allows to interpret into `Prop` easily, and thus makes it easier to write tests
* with complex sequential generators chains.
*
* Before :
*
* {{
* val gen = for {
* a <- someGenA
* b <- someGenB(a)
* c <- someGenC(b)
* } yield (a, b, c)
*
* Prop.forAll(gen){ case (a, b, c) =
* assertOn(a, b, c)
* }
* }}
*
* After :
*
* {{
* val xen = for {
* a <- someGenA.xen
* b <- someGenB(a).xen
* c <- someGenC(b).xen
* } yield {
* assertOn(a, b, c)
* }
*
* Xen.toProp(xen)
* }}
*
*/
sealed trait Xen[A] extends Product with Serializable {
final def flatMap[B](f: A => Xen[B]): Xen[B] = Xen.FlatMap(this, f)
final def map[B](f: A => B): Xen[B] = Xen.FlatMap(this, (a: A) => Xen.Pure(f(a)))
}
object Xen {
trait Suite extends ScalaCheckSuite {
implicit def toOps[A](genA: Gen[A]): GenOps[A] = new GenOps(genA)
def test[P](name: String)(xen: Xen[P])(implicit ev: P => Prop): Unit =
super.test(TestOptions(name))(toProp(xen))
def test[P](options: TestOptions)(xen: Xen[P])(implicit ev: P => Prop): Unit =
super.test(options)(toProp(xen))
}
class GenOps[A](private val gen: Gen[A]) extends AnyVal {
def xen(implicit shrink: Shrink[A], pretty: A => Pretty): Xen[A] = Lifted(gen, shrink, pretty)
}
private final case class Lifted[A](gen: Gen[A], shrink: Shrink[A], pretty: A => Pretty) extends Xen[A]
private final case class Pure[A](a: A) extends Xen[A]
private final case class FlatMap[A, B](fa: Xen[A], f: A => Xen[B]) extends Xen[B]
def toProp[P](xen: Xen[P])(implicit prop: P => Prop): Prop = {
def aux[A](xen: Xen[A])(toProp: A => Prop): Prop = xen match {
case Lifted(gen, shrink, pretty) => Prop.forAll(gen)(identity)(toProp, shrink, pretty)
case Pure(a) => toProp(a)
case FlatMap(fa, f) => aux(fa)(aa => aux(f(aa))(toProp))
}
aux(xen)(prop)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment