tinycheck: A lightweight enumeration-based property testing library

[ bsd3, library, unclassified ] [ Propose Tags ] [ Report a vulnerability ]

Tinycheck is a deterministic property testing library. Instead of random generation, test cases are produced by exhaustive, fairly-interleaved enumeration. It integrates with the Tasty test framework via Test.Tasty.TinyCheck.


[Skip to Readme]

Flags

Manual Flags

NameDescriptionDefault
dev

Enable stricter warnings for development

Disabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0
Change log CHANGELOG.md
Dependencies base (>=4.17 && <4.22), generics-sop (>=0.5 && <0.6), tagged (>=0.8 && <0.9), tasty (>=1.5 && <1.6) [details]
Tested with ghc ==9.4, ghc ==9.6, ghc ==9.8, ghc ==9.10, ghc ==9.12
License BSD-3-Clause
Author Manuel Bärenz
Maintainer programming@manuelbaerenz.de
Uploaded by turion at 2026-04-09T08:51:20Z
Downloads 0 total (0 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2026-04-09 [all 1 reports]

Readme for tinycheck-0.1.0.0

[back to package description]

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.