You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added EdgeCases, the explicit switch for the numeric boundary bias: PropertyConfig(edgeCases: EdgeCases::None) generates uniformly instead of
returning an in-range edge value roughly one draw in five. The bias is right
until the edges are what a property cannot use — a body discarding 0, a
range end that violates a precondition — where it costs one run in five and
the discard budget pays. The roll that chooses an edge is still consumed
under None, so the two modes stay aligned on the same seed: switching
changes which values are edges rather than every draw after the first, which
is what keeps a suite comparable to itself. jqwik's FIRST is deliberately
absent — explicit examples and the corpus already run before the random
phase, with values chosen rather than guessed.
Added Gen::forClass(): a generator built from what a constructor already
declares. Per parameter, in order — an override, the @param psalm type, the
native type. The docblock wins because it says more: int and int<0, 100>
are the same native type and a very different value space, and reading the
narrower one is what keeps a validating constructor from rejecting four
generated values in five. The supported subset is bounded and documented
(int<a, b>, positive-int and friends, non-empty-string, list<T>, array<K, V>, literal unions such as 'draft'|'published', ?T, unions,
enums, DateTimeImmutable, and other classes followed to maxDepth with
cycles refused by name); anything outside it throws naming the parameter
rather than widening to a guess, because a guessed generator fails later, in
somebody else's test. A constructor that rejects a value throws by default —
that is information — and skipInvalid: true discards and redraws through
the same Gen::filter() machinery, discarding exceptions only, never Errors.
Added RedisCorpus: the regression corpus in Redis instead of a directory, so
a falsification found on a laptop replays in CI and one found in CI replays on
the next laptop. The stored document is byte-identical to the filesystem
backend's, so moving a corpus between the two is a copy rather than a
migration — asserted, not assumed. It takes a two-method client seam
(Runner\Redis\CorpusClient), shipped over ext-redis
(PhpRedisCorpusClient) and predis (PredisCorpusClient); a consumer with a
pool or a namespaced wrapper can supply its own. Writes are optimistic — read,
compare-and-set through one Lua script, retry — and give up quietly after RedisCorpus::MAX_ATTEMPTS, because a corpus is memory rather than a ledger
and failing a passing run to record a counterexample is the wrong trade.
Documented the corpus as a CI artifact: the three GitHub Actions steps that
carry a corpus across runs, and why each exists. The combined cache action
declares post-if: success(), so it never saves on the red job that just
recorded the counterexample; run_attempt has to be in the key or a re-run
writes nothing; restore-keys is what actually carries the corpus forward.
Plus the two patterns beyond a cache — a committed fixture, and a store
shared between CI and developers — and what must not be committed or shared.