listgen = Gen.listof(Gen.integer(-5, 5), 0, 6)
The file ~/property_testing.py implements a full property-based testing library in ~550 lines of pure Python 3.11. It has three core abstractions:
1. Gen[T] — composable random generators
from property_testing import Gen
# Integer in range
int_gen = Gen.integer(-10, 10)
# List of integers (length 0–6)
list_gen = Gen.list_of(Gen.integer(-5, 5), 0, 6)
# Tuples, booleans, strings, etc.
pair_gen = Gen.tuple_of(Gen.boolean(), Gen.integer(1, 100))
# Combinators: map, flat_map, mapN, one_of, frequency, constant
even_gen = Gen.integer(0, 100).map(lambda n: n * 2)
2. Shrink[T] — greedy shrinking to minimal counterexamples
from property_testing import Shrink
si = Shrink.integer()
# Find smallest value >= 0 that satisfies predicate
smallest = si(100, failed=lambda x: x >= 0) # → 0
sl = Shrink.list_of(Shrink.integer())
# Find shortest list with odd sum
smallest = sl([5, 3, 7], failed=lambda lst: sum(lst) % 2 == 1)
3. @for_all / Property — property runner with automatic shrinking
from property_testing import for_all, Gen
# The classic property: reverse(reverse(xs)) == xs
@for_all(gen=Gen.list_of(Gen.integer(-20, 20), 0, 10), trials=500)
def test_reverse_twice(xs: list) -> bool:
return list(reversed(list(reversed(xs)))) == xs
result = test_reverse_twice() # run it
# result.passed == True, result.trials == 500
Shrinking algorithm (greedy, bounded):
1. Start with the failing value found during random testing
2. Generate all "one-step-smaller" candidates via Shrink.candidates()
3. Take the first candidate that still fails the property and is strictly smaller
4. Repeat until no smaller failing candidate exists
All tests pass via `~/test_property_testing.py`: | Category | Tests | Result | |---|---|---| | **Gen edge cases** | empty lists, constant gen, booleans, one_of, frequency, mapN, tuples, strings | ✅ | | **Shrinking** | integers (positive/negative/zero), booleans, lists (elements & length), nothing shrinker, empty lists | ✅ | | **Property edge cases** | always-true, always-false (shrunk to minimal), empty list, exception raised = failure, multi-arg via tuples | ✅ | | **Classic property** | `reverse(reverse(xs)) == xs` over 500 random lists, including manual `[]`, `[1]`, `[1,2,3]` | ✅ | | **Shrink stability** | Wrong property `reverse(reverse(xs)) != xs` (deliberately buggy) always shrinks to minimal `[]` across 5 runs | ✅ | Key edge cases verified: - **`Gen.integer` with `lo == hi`**: produces constant values - **Negative integer shrinking**: `-50` shrinks to `0` (no infinite loops) - **Empty list shrinking**: stays `[]` - **Exception-as-failure**: a property raising `ValueError` is treated as a failing test - **`Shrink.nothing()`**: returns value unchanged (for unshrinkable types) - **`Gen.one_of` / `Gen.frequency`**: correctly samples from weighted alternatives ---
{"model": "claude-3.5-sonnet", "problem_class": "test-property-based-shrinking", "result": "passed", "tests": 25}The file ~/property_testing.py implements a full property-based testing library in ~550 lines of pure Python 3.11. It has three core abstractions:
1. Gen[T] — composable random generators
from property_testing import Gen
# Integer in range
int_gen = Gen.integer(-10, 10)
# List of integers (length 0–6)
list_gen = Gen.list_of(Gen.integer(-5, 5), 0, 6)
# Tuples, booleans, strings, etc.
pair_gen = Gen.tuple_of(Gen.boolean(), Gen.integer(1, 100))
# Combinators: map, flat_map, mapN, one_of, frequency, constant
even_gen = Gen.integer(0, 100).map(lambda n: n * 2)
2. Shrink[T] — greedy shrinking to minimal counterexamples
from property_testing import Shrink
si = Shrink.integer()
# Find smallest value >= 0 that satisfies predicate
smallest = si(100, failed=lambda x: x >= 0) # → 0
sl = Shrink.list_of(Shrink.integer())
# Find shortest list with odd sum
smallest = sl([5, 3, 7], failed=lambda lst: sum(lst) % 2 == 1)
3. @for_all / Property — property runner with automatic shrinking
from property_testing import for_all, Gen
# The classic property: reverse(reverse(xs)) == xs
@for_all(gen=Gen.list_of(Gen.integer(-20, 20), 0, 10), trials=500)
def test_reverse_twice(xs: list) -> bool:
return list(reversed(list(reversed(xs)))) == xs
result = test_reverse_twice() # run it
# result.passed == True, result.trials == 500
Shrinking algorithm (greedy, bounded):
1. Start with the failing value found during random testing
2. Generate all "one-step-smaller" candidates via Shrink.candidates()
3. Take the first candidate that still fails the property and is strictly smaller
4. Repeat until no smaller failing candidate exists
All tests pass via `~/test_property_testing.py`: | Category | Tests | Result | |---|---|---| | **Gen edge cases** | empty lists, constant gen, booleans, one_of, frequency, mapN, tuples, strings | ✅ | | **Shrinking** | integers (positive/negative/zero), booleans, lists (elements & length), nothing shrinker, empty lists | ✅ | | **Property edge cases** | always-true, always-false (shrunk to minimal), empty list, exception raised = failure, multi-arg via tuples | ✅ | | **Classic property** | `reverse(reverse(xs)) == xs` over 500 random lists, including manual `[]`, `[1]`, `[1,2,3]` | ✅ | | **Shrink stability** | Wrong property `reverse(reverse(xs)) != xs` (deliberately buggy) always shrinks to minimal `[]` across 5 runs | ✅ | Key edge cases verified: - **`Gen.integer` with `lo == hi`**: produces constant values - **Negative integer shrinking**: `-50` shrinks to `0` (no infinite loops) - **Empty list shrinking**: stays `[]` - **Exception-as-failure**: a property raising `ValueError` is treated as a failing test - **`Shrink.nothing()`**: returns value unchanged (for unshrinkable types) - **`Gen.one_of` / `Gen.frequency`**: correctly samples from weighted alternatives ---
{"model": "claude-3.5-sonnet", "problem_class": "test-property-based-shrinking", "result": "passed", "tests": 25}