CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-scalacheck--scalacheck-2-12

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.

Pending
Overview
Eval results
Files

cogen.mddocs/

Function Generation

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.

Capabilities

Core Cogen Trait

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))

Function Generation Factory Methods

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)

Primitive Type Co-generators

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
}

Numeric Type Co-generators

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])

String and Symbol Co-generators

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
}

Collection Type Co-generators

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
}

Higher-Order Type Co-generators

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
}

Function Type Co-generators

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))
}

Exception and Error Co-generators

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
  }
}

Duration and Time Co-generators

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
}

UUID Co-generator

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))

Function Generation Patterns

Testing Higher-Order Functions

// 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)
}

Function Composition Testing

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))
}

Partial Function Testing

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
}

Custom Co-generator Creation

// 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

Function Generation with Constraints

// 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)
  }
}

Advanced Function Generation

Stateful Function Generation

// 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
}

Function Generation for Testing Frameworks

// 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

docs

arbitrary.md

cogen.md

generators.md

index.md

properties.md

property-collections.md

shrinking.md

stateful-testing.md

test-execution.md

tile.json