Utility classes and objects for property-based testing and law verification in cats-laws.
import cats.laws.discipline._
import org.scalacheck.{Arbitrary, Gen, Cogen}
import org.scalacheck.Prop._The core type for expressing law equations.
// Type alias from cats.kernel.laws
type IsEq[A] = cats.kernel.laws.IsEq[A]
// Value reference
val IsEq = cats.kernel.laws.IsEq
// Implicit class for creating IsEq instances
implicit final class IsEqArrow[A](private val lhs: A) extends AnyVal {
def <->(rhs: A): IsEq[A] = IsEq(lhs, rhs)
}Trait for exhaustive property checking with finite domains.
trait ExhaustiveCheck[A] {
def allValues: List[A]
}
object ExhaustiveCheck {
// Instances for common types
implicit val booleanExhaustiveCheck: ExhaustiveCheck[Boolean]
implicit def tuple2ExhaustiveCheck[A: ExhaustiveCheck, B: ExhaustiveCheck]: ExhaustiveCheck[(A, B)]
implicit def tuple3ExhaustiveCheck[A: ExhaustiveCheck, B: ExhaustiveCheck, C: ExhaustiveCheck]: ExhaustiveCheck[(A, B, C)]
implicit def eitherExhaustiveCheck[A: ExhaustiveCheck, B: ExhaustiveCheck]: ExhaustiveCheck[Either[A, B]]
implicit def optionExhaustiveCheck[A: ExhaustiveCheck]: ExhaustiveCheck[Option[A]]
implicit def setExhaustiveCheck[A: ExhaustiveCheck]: ExhaustiveCheck[Set[A]]
}A small domain integer type for efficient property testing. Similar to Int, but with a 4-bit domain (-8 to 7) that provides integer overflow characteristics for fast testing.
final class MiniInt private (val intBits: Int) extends AnyVal with Serializable {
def toInt: Int
def +(o: MiniInt): MiniInt
def *(o: MiniInt): MiniInt
def |(o: MiniInt): MiniInt
def /(o: MiniInt): MiniInt
def unary_- : MiniInt
}
object MiniInt {
val bitCount: Int = 4
val minIntValue: Int = -8
val maxIntValue: Int = 7
val minValue: MiniInt
val maxValue: MiniInt
val zero: MiniInt
val one: MiniInt
val negativeOne: MiniInt
val allValues: List[MiniInt]
def isInDomain(i: Int): Boolean
def fromInt(i: Int): Option[MiniInt]
def wrapped(intBits: Int): MiniInt
def unsafeFromInt(i: Int): MiniInt
// Algebra instances for MiniInt
val miniIntAddition: CommutativeGroup[MiniInt]
val miniIntMultiplication: CommutativeMonoid[MiniInt]
val miniIntOr: BoundedSemilattice[MiniInt]
// Typeclass instances
implicit val catsLawsEqInstancesForMiniInt: Order[MiniInt] with Hash[MiniInt]
implicit val catsLawsExhaustiveCheckForMiniInt: ExhaustiveCheck[MiniInt]
}Comprehensive collection of Eq instances for higher-order types and functions.
object eq {
// Function equality instances
implicit def function1Eq[A, B](implicit A: ExhaustiveCheck[A], B: Eq[B]): Eq[A => B]
implicit def function2Eq[A, B, C](implicit A: ExhaustiveCheck[A], B: ExhaustiveCheck[B], C: Eq[C]): Eq[(A, B) => C]
// Kleisli arrow equality
implicit def kleisliEq[F[_], A, B](implicit FA: ExhaustiveCheck[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]]
// Cokleisli arrow equality
implicit def cokleisliEq[F[_], A, B](implicit FB: ExhaustiveCheck[F[B]], A: Eq[A]): Eq[Cokleisli[F, A, B]]
// Type class instances equality
implicit def semigroupEq[A](implicit A: ExhaustiveCheck[A], eq: Eq[A]): Eq[Semigroup[A]]
implicit def monoidEq[A](implicit A: ExhaustiveCheck[A], eq: Eq[A]): Eq[Monoid[A]]
implicit def groupEq[A](implicit A: ExhaustiveCheck[A], eq: Eq[A]): Eq[Group[A]]
// Higher-order type class equality
implicit def functorEq[F[_]](implicit F: ExhaustiveCheck[Int => F[Int]]): Eq[Functor[F]]
implicit def applicativeEq[F[_]](implicit F: ExhaustiveCheck[Int => F[Int]], FA: ExhaustiveCheck[F[Int => Int]]): Eq[Applicative[F]]
implicit def monadEq[F[_]](implicit arbFInt: ExhaustiveCheck[F[Int]], eqFInt: Eq[F[Int]]): Eq[Monad[F]]
// Many more instances for cats data types...
}Comprehensive collection of Arbitrary and Cogen instances for ScalaCheck.
object arbitrary {
// Basic cats.data types
implicit def evalArbitrary[A: Arbitrary]: Arbitrary[Eval[A]]
implicit def evalCogen[A: Cogen]: Cogen[Eval[A]]
implicit def nonEmptyListArbitrary[A: Arbitrary]: Arbitrary[NonEmptyList[A]]
implicit def nonEmptyListCogen[A: Cogen]: Cogen[NonEmptyList[A]]
implicit def nonEmptyVectorArbitrary[A: Arbitrary]: Arbitrary[NonEmptyVector[A]]
implicit def nonEmptyVectorCogen[A: Cogen]: Cogen[NonEmptyVector[A]]
implicit def chainArbitrary[A: Arbitrary]: Arbitrary[Chain[A]]
implicit def chainCogen[A: Cogen]: Cogen[Chain[A]]
implicit def nonEmptyChainArbitrary[A: Arbitrary]: Arbitrary[NonEmptyChain[A]]
implicit def nonEmptyChainCogen[A: Cogen]: Cogen[NonEmptyChain[A]]
// Monad transformers
implicit def optionTArbitrary[F[_]: Arbitrary, A: Arbitrary]: Arbitrary[OptionT[F, A]]
implicit def optionTCogen[F[_]: Cogen, A: Cogen]: Cogen[OptionT[F, A]]
implicit def eitherTArbitrary[F[_]: Arbitrary, A: Arbitrary, B: Arbitrary]: Arbitrary[EitherT[F, A, B]]
implicit def eitherTCogen[F[_]: Cogen, A: Cogen, B: Cogen]: Cogen[EitherT[F, A, B]]
implicit def kleisliArbitrary[F[_]: Arbitrary, A: Arbitrary, B: Arbitrary]: Arbitrary[Kleisli[F, A, B]]
implicit def kleisliCogen[F[_]: Cogen, A: Cogen, B: Cogen]: Cogen[Kleisli[F, A, B]]
implicit def cokleisliArbitrary[F[_]: Arbitrary, A: Arbitrary, B: Arbitrary]: Arbitrary[Cokleisli[F, A, B]]
implicit def cokleisliCogen[F[_]: Cogen, A: Cogen, B: Cogen]: Cogen[Cokleisli[F, A, B]]
// Validated and Ior
implicit def validatedArbitrary[A: Arbitrary, B: Arbitrary]: Arbitrary[Validated[A, B]]
implicit def validatedCogen[A: Cogen, B: Cogen]: Cogen[Validated[A, B]]
implicit def iorArbitrary[A: Arbitrary, B: Arbitrary]: Arbitrary[Ior[A, B]]
implicit def iorCogen[A: Cogen, B: Cogen]: Cogen[Ior[A, B]]
// And many more for all cats.data types...
}The fundamental type for organizing property-based tests.
trait RuleSet {
def name: String
def bases: Seq[(String, RuleSet)]
def props: Seq[(String, Prop)]
}
// Common implementations
class DefaultRuleSet(
val name: String,
val parent: Option[RuleSet],
val props: (String, Prop)*
) extends RuleSet {
val bases: Seq[(String, RuleSet)] = parent.toSeq.map(p => (p.name, p))
}
class SimpleRuleSet(
val name: String,
val props: (String, Prop)*
) extends RuleSet {
val bases: Seq[(String, RuleSet)] = Seq.empty
}Version-specific arbitrary instances for different Scala versions.
object ScalaVersionSpecific {
implicit def zipStreamArbitrary[A: Arbitrary]: Arbitrary[ZipStream[A]]
implicit def zipStreamCogen[A: Cogen]: Cogen[ZipStream[A]]
}object ScalaVersionSpecific {
implicit def zipLazyListArbitrary[A: Arbitrary]: Arbitrary[ZipLazyList[A]]
implicit def zipLazyListCogen[A: Cogen]: Cogen[ZipLazyList[A]]
implicit def nonEmptyLazyListArbitrary[A: Arbitrary]: Arbitrary[NonEmptyLazyList[A]]
implicit def nonEmptyLazyListCogen[A: Cogen]: Cogen[NonEmptyLazyList[A]]
}import cats.laws.discipline.ExhaustiveCheck
// Get all boolean values for testing
val allBooleans = ExhaustiveCheck[Boolean].allValues
// List(true, false)
// Test all combinations of Either[Boolean, Boolean]
val allEithers = ExhaustiveCheck[Either[Boolean, Boolean]].allValues
// List(Left(true), Left(false), Right(true), Right(false))import cats.laws.discipline.MiniInt
import cats.laws.discipline.eq._
// Test ring laws with small domain
class MiniIntRingTest extends AnyFunSuite with Checkers {
checkAll("MiniInt.RingLaws", RingTests[MiniInt].ring)
}import cats.laws.discipline.{RuleSet, DefaultRuleSet}
import org.scalacheck.Prop._
def customLaws[F[_]: Functor]: RuleSet = new DefaultRuleSet(
name = "custom functor laws",
parent = Some(FunctorTests[F].functor[Int, String, Double]),
"custom property" -> forAll { (fa: F[Int]) =>
// Custom property test
fa.map(identity) == fa
}
)import cats.laws.discipline.catsLawsIsEqToProp
val law: IsEq[List[Int]] = List(1, 2).map(identity) <-> List(1, 2)
val prop: Prop = law // Automatic conversion via implicit