Property
Rasuvaeff\PropertyTesting\Property
Class — Package: property-testing-testo — Source — Version: v1.0.0
Implements: Testo\Pipeline\Attribute\Interceptable
Marks a test method as a property: the PropertyInterceptor takes over, generating random arguments from a generators method until the property has completed $runs successful checks or exhausted its discard budget.
Attribute arguments in PHP must be constant expressions. A provider can be a method name, any callable accepted by an attribute expression, or an invokable provider object; callable providers return array<string, ArbitraryInterface>, keyed by parameter name. When $generators is null the runner falls back to a method named <testMethod>Generators.
With $auto the provider becomes optional: parameters it does not cover are derived from the property's own signature through Gen::forParameters() — the @param psalm type when there is one, the native type otherwise.
Constructor
__construct(
int $runs = 100,
?int $seed = NULL,
callable|array<array-key,mixed>|string|null $generators = NULL,
?int $maxShrinks = NULL,
callable|array<array-key,mixed>|string|null $examples = NULL,
?int $maxDiscards = NULL,
?int $timeoutMs = NULL,
?int $budgetMs = NULL,
?\Runner\ShrinkMode $shrink = NULL,
?int $shrinkBudgetMs = NULL,
?list<\Runner\Phase> $phases = NULL,
bool $derandomize = false,
?string $path = NULL,
\Runner\EdgeCases $edgeCases = Rasuvaeff\PropertyTesting\Runner\EdgeCases::Mixin,
bool $auto = false,
?class-string<\Throwable> $throws = NULL,
bool $exhaustive = false,
int $exhaustiveBudget = 10000,
int $flakyReplays = 2,
int $searchRuns = 0,
)| Parameter | Type | Default | Description |
|---|---|---|---|
$runs | int | 100 | Number of successful random inputs to check. Discarded inputs do not count. |
$seed | ?int | NULL | Fixed seed for reproducibility. Omit to let the runner pick a random one (the failing seed is reported by PropertyViolationException). |
$generators | `callable | array<array-key,mixed> | string |
$maxShrinks | ?int | NULL | Cap on the number of accepted shrink steps. Null (default) means no cap. 0 disables shrinking, reporting the original counterexample unchanged. |
$examples | `callable | array<array-key,mixed> | string |
$maxDiscards | ?int | NULL | Maximum number of discarded inputs before the property gives up. Null (default) uses ten times the resolved run count. |
$timeoutMs | ?int | NULL | Wall-clock deadline for a single run (random or example) in milliseconds. A body that takes longer fails the property with a DeadlineExceededException naming the offending input — protection against pathological inputs (catastrophic regex, deep recursion, unbounded backoff). Measured after the run returns, so a body that never returns cannot be interrupted; shrink trials are not measured. Null (default) disables the deadline. |
$budgetMs | ?int | NULL | Wall-clock budget for the whole random phase in milliseconds. When it runs out before $runs successful checks complete, the property fails with a TimeBudgetExceededException. Null (default) disables the budget. |
$shrink | ?\Runner\ShrinkMode | NULL | How hard to minimise a counterexample: Runner\ShrinkMode::Full (the default), Runner\ShrinkMode::Off to report the input as generated, or Runner\ShrinkMode::Bounded together with $shrinkBudgetMs. |
$shrinkBudgetMs | ?int | NULL | Wall-clock budget for the shrink descent in milliseconds — the one knob here that costs determinism, since how far the descent gets depends on how long the body takes. It answers "the descent hung", not "reproduce this exactly". |
$phases | ?list<\Runner\Phase> | NULL | Stages this property performs, in run order. Null (default) runs all of them; a subset trades coverage for time on purpose. |
$derandomize | bool | false | Derives an unset seed from the property id instead of drawing one, so the same property on the same code always selects the same inputs. An explicit $seed still wins. |
$path | ?string | NULL | A recorded shrink descent (CounterExample::$path) followed instead of searched for again. It needs the $seed of the run that produced it — the steps mean nothing against another one — and it is a debugging aid, not a fixture: editing a generator orphans it, which is what the regression corpus is for. |
$edgeCases | Runner\EdgeCases | Rasuvaeff\PropertyTesting\Runner\EdgeCases::Mixin | Whether the numeric generators keep biasing toward their boundary values (Runner\EdgeCases::Mixin, the default) or generate uniformly (Runner\EdgeCases::None). Turn them off when the edges are what this property cannot use — a body discarding 0, a range end that violates a precondition — so the discard budget stops paying for one run in five. |
$auto | bool | false | Derive a generator from the property's signature for every parameter the provider does not cover — the @param psalm type when there is one (int<1, 300> beats a bare int), the native type otherwise, and an error naming the parameter for anything unreadable. The provider (explicit or conventional) becomes the overrides and may be partial; it may also cover everything, in which case auto derives nothing. Deliberately opt-in and deliberately without an environment knob: the environment dials the suite, while this changes what one property's arguments mean. |
$throws | ?class-string<\Throwable> | NULL | The exception class every run must throw. A run that throws it (or a subclass) passes; one that returns normally fails with Expected <class> to be thrown, but it was not and shrinks like any other counterexample; one that throws another class fails with that throw. A skip and an Assume::that() discard keep their meaning — never a pass earned by throwing. This is the per-run replacement for #[ExpectException], which observes the aggregate result and is refused on a property. The matching throw is recorded as an assertion, so a body that asserts nothing else is not reported as risky. |
$exhaustive | bool | false | Walk the whole parameter domain instead of sampling it, when every generator has a finite domain (Enumerable) and the product fits $exhaustiveBudget; otherwise the phase samples and the report says why. runs is ignored when it walks. PROPERTY_EXHAUSTIVE turns it on for the suite. |
$exhaustiveBudget | int | 10000 | The largest domain $exhaustive walks; at least 1. |
$flakyReplays | int | 2 | Re-executions of the minimised counterexample after the descent; one that passes marks the counterexample flaky (a Flaky: line names the replay). 0 disables the check. |
$searchRuns | int | 0 | Bodies the targeted search may execute after the random phase, for a body that calls Target::maximize()/minimize(): the best-scoring inputs are mutated one parameter at a time. 0 (the default) performs no search. PROPERTY_SEARCH_RUNS overrides it for the suite. |
Properties
| Property | Type | Readonly | Description |
|---|---|---|---|
generators | `\Closure | array<array-key,mixed> | string |
examples | `\Closure | array<array-key,mixed> | string |