Skip to content

Simplifying Failed Inputs (Shrinking)

New to property-based testing? Start with the Walkthrough for a step-by-step guide. Shrinking happens automatically when a property fails—you don't need to configure it. This page explains how it works and when it helps.

When a property fails, cppproptest finds a counterexample—an input that disproves the property. Shrinking then simplifies that counterexample to make debugging easier. All built-in generators and combinators support shrinking out of the box.

 

Overview

Concept Description
Counterexample An input that causes the property to fail
Shrinking The process of finding a simpler counterexample that still fails
Shrinkable A wrapper that carries a value and its shrink candidates (created internally by generators)

Example: Assume a property fails with a = -13680124, b = 7524.68454e-14, c = true. After shrinking, you might get a = 0, b = 0, c = true—if a and b have no relation to the failure condition and only c matters.

 

How Shrinking Works

When forAll detects a failure, it runs a shrinking phase. It repeatedly tries simpler variants of the failing input (while keeping the constraint given when it was generated). If a simpler variant still fails the test, the framework keeps it and continues until there is no more that fails. The result is a minimal counterexample that still reproduces the failure.

This automates the manual debugging step of finding which arguments matter and simplifying them to isolate the root cause as much as possible.

 

Shrinkability by Type

cppproptest uses type-specific heuristics for what counts as "simpler". For example, simplifying some basic types can have following approaches:

Type Simpler means
Booleans false is simpler than true
Integers Smaller absolute value; remove sign (e.g., -3434, 163841024)
Floats Smaller exponent; fewer digits (e.g., 12e5512e20, -29.5134-29)
Strings Fewer characters, simpler characters. (e.g., "Hello world!""Hello")
Containers Fewer elements, simpler elements. (e.g., [0,1,2,3,4,5][0,0,0])

 

When Shrinking Helps

  • Debugging — A minimal counterexample is easier to reason about than a large random one. It's often a single parameter that's causing the failure while the rest are irrelevant.
  • Test design
  • Reproducibility — The shrunk counterexample is reported with the failure; you can use it to reproduce the bug locally.

 

Custom Generators and Shrinking

When building custom generators, you wrap values with make_shrinkable<T>(value). The default make_shrinkable provides no shrink candidates (the value is already minimal). For custom shrinking behavior, you can provide alternative shrink candidates—see the CustomGenerator and shrinker APIs for advanced use.

 

Shrinking with Flaky Tests (Retry)

By default, shrinking assumes deterministic tests: one run per candidate. But for (potentially) flaky tests (e.g., a test that includes a component with random or external factor), a shrink candidate might succeed once and fail next time even though all input parameters are the same. Use retry mode with following configuration parameters:

Config Description
setShrinkMaxRetries(n) Max retries per candidate. Total trials = 1 + n (e.g. 10 → 11 trials). 0 = deterministic, 1 trial only (default).
setShrinkTimeoutMs(ms) Total shrink phase timeout. 0 = no limit.
setShrinkRetryTimeoutMs(ms) Per-candidate timeout. 0 = no limit.

When shrinkMaxRetries > 0, the framework runs an assessment phase on each failure: it re-runs the failing input multiple times to measure reproduction rate (e.g., reproduction: 5/10 in 0.12s). This informs adaptive retry budgets during shrinking.

Reproduction stats — Access programmatically via: - getLastReproductionStats() — Returns optional<ReproductionStats> (populated only after a failure that triggered shrink with retry). - setOnReproductionStats(callback) — Callback invoked after each assessment.

ReproductionStats fields: numReproduced, totalRuns, elapsedSec, argsAsString.

Stateful testsStatefulProperty propagates shrink config to its inner property. Use the same setters before .go().

See also: Property API — Shrinking with Retry

 


  • Walkthrough - Step-by-step guide for creating property tests
  • Property API - Using forAll and property configuration
  • Generators - Built-in generators (all support shrinking)
  • Combinators - Generator combinators (all support shrinking)
  • CustomGenerator - Building custom generators with make_shrinkable