A comprehensive property-based testing library for Scala and Java applications that enables developers to specify program properties as testable assertions and automatically generates test cases to verify these properties.
—
ScalaCheck's Cogen (co-generator) framework enables the generation of functions for testing higher-order functions and functional programming patterns. Co-generators work by using input values to perturb random seeds, creating deterministic function generation based on the input space.
The fundamental co-generator abstraction that perturbs random seeds based on input values.
sealed trait Cogen[T] {
def perturb(seed: Seed, t: T): Seed
def cogen[A](t: T, g: Gen[A]): Gen[A]
def contramap[S](f: S => T): Cogen[S]
}
object Cogen {
def apply[T](implicit ev: Cogen[T]): Cogen[T]
def apply[T](f: T => Long): Cogen[T]
def apply[T](f: (Seed, T) => Seed): Cogen[T]
def perturb[T](seed: Seed, t: T)(implicit c: Cogen[T]): Seed
}Usage Examples:
// Custom co-generator from simple function
case class UserId(id: Long)
implicit val cogenUserId: Cogen[UserId] = Cogen(_.id)
// Co-generator from seed transformation
case class Coordinate(x: Double, y: Double)
implicit val cogenCoordinate: Cogen[Coordinate] = Cogen { (seed, coord) =>
seed.reseed(coord.x.toLong).reseed(coord.y.toLong)
}
// Use co-generator to create deterministic perturbation
val seed = Seed.random()
val perturbedSeed = Cogen.perturb(seed, UserId(42))Utilities for creating co-generators from various function types.
def it[T, U](f: T => Iterator[U])(implicit U: Cogen[U]): Cogen[T]
def perturbPair[A, B](seed: Seed, ab: (A, B))(implicit ca: Cogen[A], cb: Cogen[B]): Seed
def perturbArray[A](seed: Seed, as: Array[A])(implicit ca: Cogen[A]): Seed
def domainOf[A, B](f: A => B)(implicit cb: Cogen[B]): Cogen[A]Usage Examples:
// Co-generator from iterator function
case class WordList(words: List[String])
implicit val cogenWordList: Cogen[WordList] = Cogen.it(_.words.iterator)
// Co-generator from function composition
val stringLengthCogen: Cogen[String] = Cogen.domainOf((s: String) => s.length)Co-generators for basic Scala and Java primitive types.
implicit val cogenUnit: Cogen[Unit]
implicit val cogenBoolean: Cogen[Boolean]
implicit val cogenByte: Cogen[Byte]
implicit val cogenShort: Cogen[Short]
implicit val cogenChar: Cogen[Char]
implicit val cogenInt: Cogen[Int]
implicit val cogenLong: Cogen[Long]
implicit val cogenFloat: Cogen[Float]
implicit val cogenDouble: Cogen[Double]Usage Examples:
// Generate functions using primitive co-generators
val intToStringFuncs: Gen[Int => String] = Gen.function1(Arbitrary.arbitrary[String])
val boolPredicates: Gen[Double => Boolean] = Gen.function1(Arbitrary.arbitrary[Boolean])
// Test higher-order functions
val mapProp = forAll { (f: Int => String, list: List[Int]) =>
list.map(f).length == list.length
}Co-generators for big numeric types and mathematical structures.
implicit val cogenBigInt: Cogen[BigInt]
implicit val cogenBigDecimal: Cogen[BigDecimal]Usage Examples:
val bigIntFunctions: Gen[BigInt => Boolean] = Gen.function1(Gen.prob(0.5))
val bigDecimalTransforms: Gen[BigDecimal => BigDecimal] =
Gen.function1(Arbitrary.arbitrary[BigDecimal])Co-generators for textual and symbolic types.
implicit val cogenString: Cogen[String]
implicit val cogenSymbol: Cogen[Symbol]Usage Examples:
// Generate string processing functions
val stringProcessors: Gen[String => Int] = Gen.function1(Arbitrary.arbitrary[Int])
val symbolMappers: Gen[Symbol => String] = Gen.function1(Gen.alphaStr)
// Test string operations
val stringConcatProp = forAll { (f: String => String, s: String) =>
val result = f(s)
result.length >= 0 // Functions can return any string
}Co-generators for various collection types, enabling function generation over collections.
implicit val cogenBitSet: Cogen[BitSet]
implicit def cogenArray[A](implicit ca: Cogen[A]): Cogen[Array[A]]
implicit def cogenList[A](implicit ca: Cogen[A]): Cogen[List[A]]
implicit def cogenVector[A](implicit ca: Cogen[A]): Cogen[Vector[A]]
implicit def cogenStream[A](implicit ca: Cogen[A]): Cogen[Stream[A]]
implicit def cogenSeq[CC[x] <: Seq[x], A](implicit ca: Cogen[A]): Cogen[CC[A]]
implicit def cogenSet[A](implicit ca: Cogen[A], ord: Ordering[A]): Cogen[Set[A]]
implicit def cogenSortedSet[A](implicit ca: Cogen[A]): Cogen[SortedSet[A]]
implicit def cogenMap[K, V](implicit ck: Cogen[K], cv: Cogen[V], ord: Ordering[K]): Cogen[Map[K, V]]
implicit def cogenSortedMap[K, V](implicit ck: Cogen[K], cv: Cogen[V]): Cogen[SortedMap[K, V]]Usage Examples:
// Generate functions over collections
val listProcessors: Gen[List[Int] => Boolean] = Gen.function1(Gen.prob(0.3))
val mapTransformers: Gen[Map[String, Int] => Int] = Gen.function1(Gen.posNum[Int])
// Test collection operations
val filterProp = forAll { (predicate: Int => Boolean, list: List[Int]) =>
val filtered = list.filter(predicate)
filtered.length <= list.length
}
val mapProp = forAll { (transform: String => Int, map: Map[String, String]) =>
val transformed = map.mapValues(transform)
transformed.keySet == map.keySet
}Co-generators for Option, Either, Try, and other wrapper types.
implicit def cogenOption[A](implicit ca: Cogen[A]): Cogen[Option[A]]
implicit def cogenEither[A, B](implicit ca: Cogen[A], cb: Cogen[B]): Cogen[Either[A, B]]
implicit def cogenTry[A](implicit ca: Cogen[A]): Cogen[Try[A]]Usage Examples:
// Generate functions over optional values
val optionProcessors: Gen[Option[String] => Int] = Gen.function1(Gen.choose(0, 100))
val eitherHandlers: Gen[Either[String, Int] => Boolean] = Gen.function1(Gen.prob(0.5))
// Test optional operations
val optionMapProp = forAll { (f: String => Int, opt: Option[String]) =>
opt.map(f).isDefined == opt.isDefined
}
val eitherFoldProp = forAll { (left: String => Int, right: Int => Int, e: Either[String, Int]) =>
val result = e.fold(left, right)
result >= 0 || result < 0 // Any integer is valid
}Co-generators for function values and partial functions.
implicit def cogenFunction0[Z](implicit cz: Cogen[Z]): Cogen[() => Z]
implicit def cogenPartialFunction[A, B](
implicit aa: Arbitrary[A],
cb: Cogen[B]
): Cogen[PartialFunction[A, B]]Usage Examples:
// Generate higher-order functions
val thunkProcessors: Gen[(() => Int) => String] = Gen.function1(Gen.alphaStr)
val partialFunctionCombiners: Gen[PartialFunction[Int, String] => Boolean] =
Gen.function1(Gen.prob(0.6))
// Test function composition
val compositionProp = forAll { (f: Int => String, g: String => Boolean, x: Int) =>
val composed = g.compose(f)
composed(x) == g(f(x))
}Co-generators for exception hierarchy types.
implicit val cogenException: Cogen[Exception]
implicit val cogenThrowable: Cogen[Throwable]Usage Examples:
val exceptionHandlers: Gen[Exception => String] = Gen.function1(Gen.alphaStr)
val errorProcessors: Gen[Throwable => Int] = Gen.function1(Gen.choose(-100, 100))
// Test exception handling
val catchProp = forAll { (handler: Exception => String, ex: Exception) =>
try {
throw ex
} catch {
case e: Exception => handler(e).length >= 0
}
}Co-generators for temporal types.
implicit val cogenFiniteDuration: Cogen[FiniteDuration]
implicit val cogenDuration: Cogen[Duration]Usage Examples:
val durationTransforms: Gen[FiniteDuration => Long] = Gen.function1(Gen.posNum[Long])
val timeoutHandlers: Gen[Duration => Boolean] = Gen.function1(Gen.prob(0.4))
// Test duration operations
val durationProp = forAll { (f: FiniteDuration => Long, d: FiniteDuration) =>
val result = f(d)
result >= Long.MinValue && result <= Long.MaxValue
}Co-generator for universally unique identifiers.
implicit val cogenUUID: Cogen[UUID]Usage Examples:
val uuidProcessors: Gen[UUID => String] = Gen.function1(Gen.alphaNumStr)
val uuidPredicates: Gen[UUID => Boolean] = Gen.function1(Gen.prob(0.5))// Test map operation on lists
val mapProperty = forAll { (f: Int => String, list: List[Int]) =>
val mapped = list.map(f)
mapped.length == list.length &&
(list.isEmpty ==> mapped.isEmpty) &&
(list.nonEmpty ==> mapped.nonEmpty)
}
// Test filter operation
val filterProperty = forAll { (predicate: String => Boolean, list: List[String]) =>
val filtered = list.filter(predicate)
filtered.length <= list.length &&
filtered.forall(predicate) &&
filtered.forall(list.contains)
}
// Test fold operations
val foldProperty = forAll { (f: (Int, String) => Int, zero: Int, list: List[String]) =>
val result = list.foldLeft(zero)(f)
list.isEmpty ==> (result == zero)
}val compositionProperty = forAll {
(f: Int => String, g: String => Boolean, h: Boolean => Double, x: Int) =>
val composed = h.compose(g).compose(f)
val stepwise = h(g(f(x)))
composed(x) == stepwise
}
val andThenProperty = forAll { (f: Int => String, g: String => Boolean, x: Int) =>
(f andThen g)(x) == g(f(x))
}val partialFunctionProperty = forAll {
(pf: PartialFunction[Int, String], x: Int) =>
pf.isDefinedAt(x) ==> {
val result = pf(x)
result != null // Partial functions should not return null when defined
}
}
val liftProperty = forAll { (pf: PartialFunction[String, Int], s: String) =>
val lifted = pf.lift
pf.isDefinedAt(s) ==> lifted(s).isDefined
}// Complex custom type with co-generator
case class Person(name: String, age: Int, emails: List[String])
implicit val cogenPerson: Cogen[Person] = Cogen { (seed, person) =>
val seed1 = Cogen.perturb(seed, person.name)
val seed2 = Cogen.perturb(seed1, person.age)
Cogen.perturb(seed2, person.emails)
}
// Test functions over custom types
val personProperty = forAll { (f: Person => Boolean, p: Person) =>
val result = f(p)
result == true || result == false // Boolean functions return booleans
}
// Recursive data structure co-generator
sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]
implicit def cogenTree[A](implicit ca: Cogen[A]): Cogen[Tree[A]] = Cogen {
case Leaf(value) => Cogen { (seed, _) => Cogen.perturb(seed, value) }
case Branch(left, right) => Cogen { (seed, _) =>
val seed1 = cogenTree[A].perturb(seed, left)
cogenTree[A].perturb(seed1, right)
}
}.perturb// Generate only pure functions (deterministic)
def pureFunction[A, B](implicit ca: Cogen[A], ab: Arbitrary[B]): Gen[A => B] = {
Gen.function1[A, B](ab.arbitrary)(ca, ab)
}
// Generate functions with specific properties
def monotonicIntFunction: Gen[Int => Int] = {
// This is a simplified example - real monotonic function generation is complex
Gen.function1[Int, Int](Gen.choose(0, 1000))
.suchThat { f =>
// Test monotonicity on small sample
val sample = (1 to 10).toList
sample.zip(sample.tail).forall { case (x, y) => f(x) <= f(y) }
}
}
// Generate bijective functions (for small domains)
def bijectiveFunction[A](domain: List[A])(implicit ca: Cogen[A]): Gen[A => A] = {
Gen.oneOf(domain.permutations.toSeq).map { permutation =>
val mapping = domain.zip(permutation).toMap
(a: A) => mapping.getOrElse(a, a)
}
}// Generate functions that maintain internal state
case class Counter(initial: Int) {
private var count = initial
def increment(): Int = { count += 1; count }
def current: Int = count
}
val statefulProperty = forAll { (init: Int) =>
val counter = Counter(init)
val first = counter.increment()
val second = counter.increment()
first == init + 1 && second == init + 2
}// Generate test predicates
val predicateProperty = forAll { (predicate: List[Int] => Boolean, lists: List[List[Int]]) =>
val results = lists.map(predicate)
results.forall(r => r == true || r == false) // All results are booleans
}
// Generate validation functions
case class ValidationResult(isValid: Boolean, errors: List[String])
val validatorProperty = forAll {
(validator: String => ValidationResult, inputs: List[String]) =>
val results = inputs.map(validator)
results.forall(r => r.isValid || r.errors.nonEmpty) // Invalid results have errors
}Install with Tessl CLI
npx tessl i tessl/maven-org-scalacheck--scalacheck-2-12