Repository navigation
v0.10.0
The contract freeze ahead of 1.0. Everything here is something that could not
be changed after 1.0.0 without a major, so it is changed now. The new
Compatibility policy says what the numbers
will promise from then on.
- Breaking:
CounterExample::$skipsis now$discards, and$skipsis a
real, separate counter — the runs the environment refused. The two were one
word for two quantities: the field heldAssume::that()discards, while
RunStatistics::$skips(0.9.0) held environmental skips, in the same@api.
toArray()/toJson()gain adiscardskey besideskips. The corpus is
untouched —CorpusDocumentnever wrote the fulltoArray(). - Breaking:
FilesystemCorpus::fromEnv()is removed. It readPROPERTY_DB
and builtnew self($directory)unconditionally, soPROPERTY_DB=redis://…
created a directory literally namedredis:/host:port/…and emitted four
E_WARNINGs per call from the stream-wrapper lookup — a silent fall back to
the filesystem, which is exactly what the family forbids. Use
CorpusFactory::fromDsn(EnvironmentOverrides::string(getenv('PROPERTY_DB')) ?? …),
which cannot return a backend the value did not name. Nothing insrc/calls
getenv()any more. - Breaking:
GenerationExhaustedisGenerationExhaustedExceptionand
StateMachine\PostconditionViolationis
StateMachine\PostconditionViolationException, matching the seven public
exceptions that already carried the suffix. No aliases: 0.x is where a rename
costs a line in an upgrade note rather than a major. - Breaking: every
Gen::*factory returnsArbitraryInterface<T>instead
of a concrete arbitrary. The concrete classes stay@apiand unchanged —
they are simply no longer part of the facade's signature, so an
implementation can be replaced without a major.Gen::enum()bounds its
template at\UnitEnumandGen::intRange()declares the honest
array{int, int}it produces. - Breaking: a flag environment variable is off for
0,false,offand
no(case-insensitive, trimmed), not only0and''.
PROPERTY_VERBOSE=falseturning verbose output on was 2.8-compatible and
indefensible. - Breaking: left implicit, the skip budget is
runsrather than the
discard budget'sruns * 10. A property skipped on every run gives up after
runs + 1attempts instead of10 * runs + 1— under Testo that is a
#[BeforeTest]hook executed 101 times rather than 1001 for aruns: 100
property. An explicitmaxDiscardsstill governs both budgets; transient
skips still do not end a property early. RunDiscardedcarriesbool $skipped, so a listener can tell an
environmental skip from anAssume::that()discard. Both adapters'
VerboseListenercounted them as one.DistributionReportcarriesint $skipsand reports it intoArray().
discardPercent()keeps dividing byattempts— that denominator is the
point of the name.GaveUpExceptioncarries?int $maxSkips, the cap the skip budget was
measured against; the skip message names it. Null means the caller did not
tell the two budgets apart, and the message falls back to$maxDiscards.- Redis
AUTHis reachable:RedisDsnandCorpusFactory::fromDsn()take an
optional password,LazyPhpRedisCorpusClientauthenticates beforeSELECT,
andtoPredisParameters()carries it. Adapters readPROPERTY_DB_PASSWORD.
The DSN's userinfo is still refused —PROPERTY_DBis echoed in diagnostics
and lands in CI logs. RedisCorpusrefuses an empty prefix and a negativemaxValues/maxSeeds.
A negative cap madeCorpusDocument::cap()drop every entry in silence.composer rectoris green:PropertyRunnerandRegexCompilername two
literal arguments. It had been red since 0.9.0, which iscomposer release-checkred —composer builddoes not run rector.- Documentation is part of the contract, and this release closes 37 places
where it disagreed with the code: the split Configuration table in both
READMEs,forAll($generators, $id)(the id comes from->id()),
floatBetween's interval (half-open, and it had no description at all), the
four-of-eleven examples tables, the four-of-nine environment variables page,
the eight-of-fifteen attribute table,Command::shrinks()andminLen,
PSR-20 as the adapters' clock,path"when it ships in 0.2",CorpusFromEnv,
ext-tokenizermissing from the requirements, and Redis absent from the
regression-corpus page entirely. The site gained a Compatibility policy
page,roadmap.mdno longer promises a seed sequence that 0.6.0 changed, and
the API reference reflects traits — so the PHPUnit adapter's single entry
point, thePropertyTestingtrait, has a page for the first time. docs/.api-workspacefollows the adapters again (^0.9/^0.7; it sat at
^0.6/^0.5, and a caret on0.xpins a minor, so the weekly docs rebuild
had been re-reflecting the same stale release). Keeping it in step is now in
the release checklist besidebuild.yml's pin. At release timebuild.yml
moves to0.10.0and the workspace to^0.9/^0.8, but the workspace's
path version stays0.9.0for now: the published Testo adapter still
constrains core to^0.9(its^0.9 || ^0.10bridge is merged, not
tagged), and a0.10.0path version would make the workspace uninstallable.- The Compatibility policy gains the point the 1.0 review asked for and the
freeze first left out: how data is reached is frozen as it stands. The
accessor style is not uniform — results, events and most exceptions expose
publicreadonlyproperties, whilePropertyViolationException,
ExampleViolationException,PathViolationExceptionand
RegressionViolationExceptionexpose getters — and it stays that way. Unifying it would break every consumer for a cosmetic
gain, and the split is at least stable: a type either has the field or the
getter, and one never becomes the other. Written down rather than left to be
rediscovered. ValueCodecrefuses an int-flagged array key whose text is not a decimal
integer the platform holds: a bare(int)cast saturated9223372036854775808
toPHP_INT_MAXand collapsedabcto0, replaying a key the property was
never called with. The lenient007→7reading stays (#119).ValueCodecreads a finite float back only in the shapevar_export()writes
it:1e999(cast toINF),1.0E-400(cast to0.0),01.0,.5and
whitespace-padded texts were accepted byis_numeric()and decoded to a
value the encoder could never have stored (#120).FrequencyArbitraryreports weights whose sum passesPHP_INT_MAXas an
InvalidArgumentException; the float sum used to reach the typed property
and fail with aTypeError(#121).PropertyDefinitionrejects a repeated parameter name — the runner combined
names and draws by name, so one draw was dropped without a word and the
body received fewer arguments than declared (#122).DateTimeArbitraryrefuses a bound outside the microsecond range of a
64-bit integer (about 292 000 years either side of the epoch) with an
InvalidArgumentException; the overflowed product used to fail theint
return type with aTypeError(#123).