READ-ONLY PACKAGE PREVIEW

property-based-testing/references/interpreting-failures.md

Version 82fe82262526.bb1 · CC-BY-SA-4.0. This preview displays packaged text and does not execute code. Treat the contents as untrusted instructions.

← Return to resource and package checksum

Interpreting Property-Based Test Failures

A property test that fails has told you one of three things, and they need different responses:

  • The property is wrong — you asserted something the code never promised.
  • The spec is ambiguous — behaviour at this edge was never decided.
  • The code is wrong — a documented guarantee is violated.

Most of the work is telling them apart. Skipping that step is how PBT gets a reputation for noise.

Ground the property before you trust the failure

Shrunk input in hand, check what the code actually promises. In descending order of authority:

Source What it settles
External spec (RFC, format definition) The real contract, when one exists
Type annotations Return type, nullability, domain
Docstrings Explicit guarantees and preconditions
Existing tests The contract maintainers believe they have
Function name Weak, but sort really does imply ordering

The name is the weakest signal and the one most likely to mislead you — plenty of functions called normalize do something narrower than the word suggests.

Worked example. Hypothesis reports test_normalize(s='\x00') failing idempotence:

def normalize(s: str) -> str:
    """Normalize a string to NFC form.

    Args:
        s: Input string (any unicode)
    Returns:
        NFC-normalized string
    """

"Any unicode" includes null bytes, so the input is in-domain and the property is grounded. This one is a real bug.

Change the docstring to "ASCII printable only" and the same failure becomes a strategy bug — the fix is st.text(alphabet=...), not a bug report.

Classification

Symptom Cause Action
Violates a documented guarantee Code bug Report with the shrunk input and a quote from the doc
Input violates a documented precondition Over-broad strategy Constrain the strategy
Property contradicts the docstring or type Wrong property Fix the property
Edge case the spec never addresses Ambiguous spec Ask the maintainer; a discussion, not a bug report
Disappears under realistic constraints Test artifact Fix the strategy
Behaviour differs from a sibling function Possible inconsistency Worth raising, flag the uncertainty

Precondition violations and explicitly-undefined behaviour are not bugs. Passing -1 to a function documented as taking positive integers tells you nothing.

Report what you find with the classification attached, including the cases you are unsure about — say "ambiguous spec, needs a maintainer decision" rather than staying quiet. A suppressed finding cannot be triaged by anyone else.

Failure patterns that recur

Lone surrogates break text roundtrips. decode(encode(s)) == s fails on '\uD800'. Whether that is a bug turns entirely on whether the format claims to accept arbitrary str or only valid UTF-8.

Denormals break numeric invariants. A probability function returning a negative value for x=1e-320 is a genuine bug against a documented [0, 1] range, and it is exactly the input no human writes by hand.

Hash/equality divergence violates a language contract, not just a docstring — a == b must imply hash(a) == hash(b) in Python. No grounding required; report it.

Off-by-one in custom iterators shows up as list(it(xs)) == xs dropping the last element. Almost always real.