2026-04-06Β·11 min readΒ·sota.io team

Deploy QuickCheck to Europe β€” Koen Claessen πŸ‡ΈπŸ‡ͺ + John Hughes 🏴󠁧󠁒󠁳󠁣󠁷󠁦󠁿 (Chalmers University of Technology πŸ‡ΈπŸ‡ͺ, ICFP 2000), Property-Based Testing, on EU Infrastructure in 2026

Coverage-guided fuzzing (AFL++) explores program paths by mutating concrete inputs. Symbolic execution (KLEE) enumerates paths exhaustively by treating inputs as symbolic unknowns. A third approach asks a different question entirely: not which paths does the program take, but which properties does the program satisfy? The canonical answer to this question is QuickCheck, a Haskell library that transforms property specifications into test generators, runs thousands of randomly generated inputs against those properties, and β€” when a counterexample is found β€” shrinks it to the minimal failing case.

Koen Claessen πŸ‡ΈπŸ‡ͺ is a professor in the Department of Computer Science and Engineering at Chalmers University of Technology πŸ‡ΈπŸ‡ͺ in Gothenburg, Sweden. John Hughes 🏴󠁧󠁒󠁳󠁣󠁷󠁦󠁿 is a Scottish-born computer scientist who joined Chalmers in 1988 and has been a professor there for over three decades, making Sweden and the EU the centre of his academic career. Together they published "QuickCheck: A Lightweight Tool for Random Testing of Haskell Programs" at the ACM SIGPLAN International Conference on Functional Programming (ICFP) 2000. The paper became one of the most influential in the software testing literature, spawning ports to every major programming language and a commercial product used in safety-critical telecommunications.

Chalmers University of Technology πŸ‡ΈπŸ‡ͺ is a Swedish technical university founded in 1829, consistently ranked among Europe's top institutions for computer science and engineering research. It is publicly funded by the Swedish state and has no data-sharing obligations under the US CLOUD Act or equivalent extraterritorial legislation. Hughes and Claessen's QuickCheck work was supported by Swedish Research Council (VetenskapsrΓ₯det) grants β€” research funded by EU taxpayers, maintained in EU academic institutions, with no proprietary lock-in.

Property-Based Testing: The Core Idea

Where AFL++ asks "what input causes a crash?" and KLEE asks "is there any input that violates this assertion?", QuickCheck asks "does this property hold for all inputs of the right type?"

A property in QuickCheck is a Haskell function from generated input to a boolean (or Property). The QuickCheck engine:

  1. Generates random inputs of the appropriate type using the Arbitrary typeclass
  2. Tests the property on each generated input (default: 100 tests)
  3. If a test fails, shrinks the counterexample β€” recursively attempts smaller/simpler inputs that still fail
  4. Reports the minimal counterexample and the number of tests passed before failure
-- Property: sorting preserves list length
prop_sort_length :: [Int] -> Bool
prop_sort_length xs = length (sort xs) == length xs

-- Property: sorting is idempotent
prop_sort_idempotent :: [Int] -> Bool
prop_sort_idempotent xs = sort (sort xs) == sort xs

-- Property: reversing twice is identity
prop_reverse_involution :: [Int] -> Bool
prop_reverse_involution xs = reverse (reverse xs) == xs

-- Run all properties
main :: IO ()
main = do
  quickCheck prop_sort_length
  quickCheck prop_sort_idempotent
  quickCheck prop_reverse_involution
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.

When a property fails, QuickCheck does not report the first failing input β€” it reports the smallest one:

-- Buggy property (wrong assumption about addition and multiplication)
prop_buggy :: Int -> Int -> Bool
prop_buggy x y = x * y > x + y
*** Failed! Falsified (after 2 tests and 3 shrinks):
0
0

QuickCheck found that x=0, y=0 falsifies 0*0 > 0+0 (i.e., 0 > 0). The shrinking algorithm reduced a larger failing input down to the minimal case automatically β€” the developer sees the simplest possible counterexample, not an accidental large one.

The Arbitrary Typeclass: How Generators Work

The heart of QuickCheck is the Arbitrary typeclass:

class Arbitrary a where
  arbitrary :: Gen a
  shrink    :: a -> [a]  -- candidates smaller than a

The Gen a monad is a random generator parameterised by a size parameter that QuickCheck increases gradually across test runs. For built-in types, instances are provided automatically: Arbitrary Int generates random integers, Arbitrary [a] generates random lists of arbitrary elements, Arbitrary (a, b) generates random pairs.

Custom generators allow domain-specific test data:

-- Generator for valid IPv4 addresses
data IPv4 = IPv4 Word8 Word8 Word8 Word8

instance Arbitrary IPv4 where
  arbitrary = IPv4 <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
  shrink (IPv4 a b c d) = [IPv4 a' b c d | a' <- shrink a]
                       ++ [IPv4 a b' c d | b' <- shrink b]
                       ++ [IPv4 a b c' d | c' <- shrink c]
                       ++ [IPv4 a b c d' | d' <- shrink d]

-- Property: parsing the string representation round-trips
prop_ipv4_roundtrip :: IPv4 -> Bool
prop_ipv4_roundtrip ip = parseIPv4 (showIPv4 ip) == Just ip

The shrink method defines a partial order: shrink x returns candidates smaller than x. QuickCheck's shrinker performs a greedy search β€” it takes the first shrunken candidate that still fails the property, and recurses until no smaller failing input exists.

Conditional Properties and Statistics

Properties can be conditional using the ==> operator:

-- Only test on even numbers
prop_even_div2 :: Int -> Property
prop_even_div2 n = even n ==> (n `div` 2) * 2 == n

This discards test cases where the precondition is false. QuickCheck reports the number of discarded cases separately.

For statistical coverage, QuickCheck provides label, classify, and cover:

prop_sort_coverage :: [Int] -> Property
prop_sort_coverage xs =
  classify (null xs) "empty" $
  classify (length xs == 1) "singleton" $
  classify (length xs > 10) "large" $
  sort xs == sort (sort xs)
+++ OK, passed 100 tests (32% large, 15% singleton, 3% empty).

The cover combinator turns coverage into a mandatory requirement β€” the test suite fails if the specified percentage of inputs does not satisfy a condition:

prop_with_coverage :: [Int] -> Property
prop_with_coverage xs =
  cover 30 (length xs > 5) "non-trivial" $
  sort xs == nub (nub xs)

This enforces that at least 30% of generated lists have more than 5 elements β€” preventing the test suite from passing on trivially easy inputs.

Shrinking: Finding the Minimal Counterexample

Shrinking is arguably QuickCheck's most valuable contribution. When a property fails on a large, complex input, debugging requires understanding the failure mode. A large random input typically contains irrelevant structure that obscures the actual bug.

QuickCheck's shrinking algorithm:

  1. Takes the first failing input x
  2. Computes shrink x β€” a list of candidates smaller than x
  3. Finds the first candidate in shrink x that still fails the property
  4. Recursively shrinks that candidate
  5. Terminates when no smaller failing input exists

The result is the minimal counterexample β€” the smallest input (in the partial order defined by shrink) that exhibits the failure. In practice, this often reduces a 100-element list to a 2-element list, or a complex data structure to a single edge case.

-- Buggy: assumes list is always non-empty
head_or_zero :: [Int] -> Int
head_or_zero [] = error "empty list"
head_or_zero (x:_) = x

prop_head_safe :: [Int] -> Bool
prop_head_safe xs = head_or_zero xs >= 0
*** Failed! Exception: 'empty list' (after 1 test and 1 shrink):
[]

QuickCheck found the empty list immediately and confirmed the minimal case in one shrink step.

The Ericsson Connection: Quviq and Telecom

John Hughes co-founded Quviq πŸ‡ΈπŸ‡ͺ, a Gothenburg-based company, specifically to bring property-based testing to industrial Erlang development. The flagship product, Quviq QuickCheck for Erlang, has been used by Ericsson πŸ‡ΈπŸ‡ͺ to test the Erlang/OTP codebase β€” the runtime underlying BEAM, the engine powering Ericsson's telecom infrastructure, WhatsApp, RabbitMQ, CouchDB, and Ejabberd.

In a widely cited case study, Quviq QuickCheck found a subtle bug in Ericsson's telecom switch software that had gone undetected for years of conventional testing. The property-based approach tested state machine transitions across millions of input sequences β€” exactly the combinatorial space that unit tests cannot cover.

Hughes has documented cases where QuickCheck-based testing found bugs in:

The Riak case study, presented at EUC 2011, found that 14 bugs remained in Riak after all existing tests passed. The QuickCheck model checked multi-node cluster invariants across thousands of randomly generated operation sequences. Shrinking reduced the failing sequences from hundreds of operations to 3-7 operations in each case, making the bugs immediately debuggable.

The EU Language Ecosystem

QuickCheck's influence spread to every major language through reimplementations:

LibraryLanguageAuthorProvenance
HypothesisPythonDavid MacIver πŸ‡¬πŸ‡§UK (European)
fast-checkTypeScript/JSNicolas Dubien πŸ‡«πŸ‡·France πŸ‡«πŸ‡·
ScalaCheckScalaRickard Nilsson πŸ‡ΈπŸ‡ͺSweden πŸ‡ΈπŸ‡ͺ (Chalmers adjacent)
PropErErlangKostis Sagonas πŸ‡¬πŸ‡· (Uppsala Univ πŸ‡ΈπŸ‡ͺ)EU (Greece/Sweden)
proptestRustJason LingleUS
jqwikJavaJohannes Link πŸ‡©πŸ‡ͺGermany πŸ‡©πŸ‡ͺ

fast-check (Nicolas Dubien πŸ‡«πŸ‡·) is notable: it is the most widely used property-based testing library for TypeScript and JavaScript (8M+ npm downloads/month), maintained in France. Hypothesis (David MacIver πŸ‡¬πŸ‡§) has become the standard property-based testing tool for Python, used at Jane Street, Mozilla, and throughout EU financial services firms. PropEr (Kostis Sagonas πŸ‡¬πŸ‡·, Uppsala University πŸ‡ΈπŸ‡ͺ) brings typed stateful testing to Erlang with EU academic backing.

State Machine Testing

Beyond pure function properties, QuickCheck extends to stateful systems via state machine models. This is particularly powerful for testing APIs, databases, and network protocols.

-- Model of a simple counter API
data CounterState = CounterState { count :: Int }

data Command = Increment | Decrement | Reset | GetCount
  deriving (Show)

-- Model transition
nextState :: CounterState -> Command -> CounterState
nextState s Increment = s { count = count s + 1 }
nextState s Decrement = s { count = count s - 1 }
nextState s Reset     = s { count = 0 }
nextState s GetCount  = s

-- Postcondition: actual response matches model
postcondition :: CounterState -> Command -> Int -> Bool
postcondition s GetCount actual = actual == count s
postcondition _ _ _             = True

-- QuickCheck generates random command sequences and checks invariants
prop_counter :: Commands CounterState -> Property
prop_counter cmds = monadicIO $ do
  result <- run (runCounterAPI cmds)
  assert (checkPostconditions cmds result)

This approach tests the actual API implementation against a pure functional model β€” any discrepancy between the model and implementation is a bug. Shrinking finds the minimal command sequence that exposes the divergence.

EU Regulatory Context

CRA 2027 (Cyber Resilience Act):

NIS2 Article 21 β€” Security of Network and Information Systems:

EU AI Act Article 9 β€” Risk Management System for High-Risk AI:

Running QuickCheck on sota.io

sota.io is an EU-native Platform-as-a-Service β€” data centres in Germany and France, GDPR-compliant by infrastructure design, no US CLOUD Act exposure, managed PostgreSQL included in the free tier.

# Dockerfile for a Haskell QuickCheck test suite
FROM haskell:9.8
WORKDIR /app

# Install project dependencies
COPY package.yaml stack.yaml ./
RUN stack setup && stack build --only-dependencies

# Copy source and test suite
COPY . .
RUN stack build --test --no-run-tests

# Run QuickCheck test suite as CI step
CMD ["stack", "test", "--test-arguments", "--quickcheck-tests=1000"]
# sota.io deploy configuration
runtime: haskell
build_command: stack build --test --no-run-tests
start_command: stack test --test-arguments "--quickcheck-tests=1000"
region: eu-central-1   # Frankfurt, Germany

The QuickCheck test suite runs as a CI step in the sota.io build pipeline. Each deployment triggers a full property-based test run β€” 1000 random inputs per property, shrinking on any failure, coverage statistics reported to the build log.

For database-backed tests, sota.io's managed PostgreSQL (accessible at DATABASE_URL) integrates with QuickCheck's monadicIO combinator for stateful database property testing:

-- Property: user creation and lookup are inverse operations
prop_user_roundtrip :: UserId -> UserData -> Property
prop_user_roundtrip uid userData = monadicIO $ do
  result <- run $ do
    createUser uid userData
    lookupUser uid
  assert (result == Just userData)

EU Infrastructure, No Cloud Act

sota.io stores all application data β€” test results, coverage reports, deployment artefacts β€” on servers in Germany and France. Under the US CLOUD Act (2018), US cloud providers can be compelled to produce data stored anywhere in the world, including EU data centres, without notifying the data subject. This creates a structural incompatibility with GDPR Article 48, which prohibits transferring personal data to third countries except under EU-approved mechanisms.

For software teams subject to EU regulations β€” financial services under DORA, healthcare under the Medical Device Regulation, automotive under ISO 26262 β€” this distinction matters: where your CI/CD runs is where your test logs, coverage data, and potentially your source code lives.

QuickCheck's EU provenance chain is complete: the original library (Chalmers University of Technology πŸ‡ΈπŸ‡ͺ, Swedish Research Council funded) + the leading Python port (Hypothesis, UK) + the leading TypeScript port (fast-check, France πŸ‡«πŸ‡·) + the leading Erlang port (PropEr, Greece/Sweden EU) + the primary commercial deployment (Quviq πŸ‡ΈπŸ‡ͺ, Gothenburg) β€” all anchored in EU member states or UK academic institutions with EU research funding.

Running your property-based test suite on sota.io keeps the complete testing infrastructure β€” generators, counterexamples, shrinking logs, coverage statistics β€” on EU-native infrastructure, under GDPR jurisdiction, with no extraterritorial cloud exposure.

Deploy QuickCheck on sota.io β€” free tier, EU data centres, managed PostgreSQL β†’