@hackage tinycheck0.2.0

A lightweight enumeration-based property testing library

  • Installation

  • Dependencies (5)

  • Dependents (0)

  • Package Flags

      dev
       (off by default)

      Enable stricter warnings for development

      dump-core
       (off by default)

      Emit GHC Core output (.dump-simpl files) for inspection

tinycheck

A lightweight, deterministic property testing library for Haskell.

Instead of generating random inputs, tinycheck enumerates test cases from a canonical, fairly-interleaved ordering. Tests are reproducible, require no seeds, and cover small values first — no shrinking required.

Quick start

import Test.Tasty
import Test.Tasty.TinyCheck

main :: IO ()
main = defaultMain $ testGroup "my suite"
  [ testProperty "reverse . reverse == id" $
      \(xs :: [Int]) -> reverse (reverse xs) == xs
  , testProperty "abs x >= 0" $
      \(x :: Int) -> abs x >= 0
  ]

Run with cabal test.

How it works

The core type is TestCases a — a newtype over [a] whose Semigroup, Applicative, and Monad instances use fair interleaving instead of concatenation and cartesian product.

TestCases [1,2,3] <> TestCases [10,20,30]  ==  TestCases [1,10,2,20,3,30]

With infinite generators this ensures neither side is starved:

(Left <$> TestCases [1..]) <> (Right <$> TestCases [1..])
  ==  TestCases [Left 1, Right 1, Left 2, Right 2, ...]

Applicative interleaves function-argument pairs, so all parts of the input space are explored immediately rather than exhausting one argument before moving to the next.

Use interleaveN to interleave any number of generators fairly:

interleaveN [TestCases [1,2,3], TestCases [10,20,30], TestCases [100,200,300]]
  ==  TestCases [1,10,100, 2,20,200, 3,30,300]

Defining generators

Implement Arbitrary for your types, or derive it via Generically:

{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics (Generic, Generically (..))
import Data.TestCases (Arbitrary)

data Colour = Red | Green | Blue
  deriving stock (Show, Generic)
  deriving (Arbitrary) via Generically Colour

Newtype wrappers are provided for common patterns:

Wrapper Suitable for
SignedArbitrary Num + Enum (e.g. Int, Integer)
BoundedArbitrary Bounded + Enum (e.g. Bool, Word8)
RealFracArbitrary Fractional + Enum (e.g. Float, Double)
... ...

Preconditions

Use ==> to skip inputs that don't satisfy a precondition:

testProperty "n > 0 implies n * 2 > 0" $
  \(n :: Int) -> (n > 0) ==> property (n * 2 > 0)

Development

Modules

Module Purpose
Data.TestCases Core TestCases type, Arbitrary, CoArbitrary
Test.Tasty.TinyCheck Tasty integration (testProperty, testPropertyWith, …)

examples/

Two standalone examples for defining Arbitrary instances for your own types are provided here.

AI usage

The central pieces are developed by a human, @turion. Many details (tasty integration, boilerplate, test cases, longer docs) have been generated by AI (Github Copilot with Claude Opus & Sonnet 4.6) and are thoroughly checked by myself.