Skip to content

2.4.0: in-body dependent draw (Gen::draw) via a replay tape#10

Merged
rasuvaeff merged 3 commits into
masterfrom
2.4.0-in-body-draw
Jul 11, 2026
Merged

2.4.0: in-body dependent draw (Gen::draw) via a replay tape#10
rasuvaeff merged 3 commits into
masterfrom
2.4.0-in-body-draw

Conversation

@rasuvaeff

Copy link
Copy Markdown
Owner

Ships T2.5 — the last scheduled roadmap item. Closes out Wave 6.

What

Gen::draw($arbitrary) — in-body dependent draw for when several dependent values make nested flatMap awkward. The domain may depend on parameters, previous draws, or intermediate results.

Shrink design (replay tape over the tree model)

  • Every draw is recorded as a Shrinkable on a process-local tape (Internal\DrawContext, same static-channel discipline as Classify).
  • Shrinking walks tape positions like extra parameters: each trial re-runs the body with the tape replayed by position; the draws the trial actually used become the next tape on acceptance.
  • Draws past the tape's end (control flow changed under a smaller prefix) generate anew from the run's Random; unreachable tails are dropped. Replayed nodes are NOT re-validated against the new arbitrary (fast-check gen() model).
  • Because an accepted candidate can regrow the tape with fresh trees, accepted steps are capped at 1000 when draws are present (maxShrinks still wins).

Reporting

  • Counterexamples render draws as draw#1, draw#2, ... next to named parameters.
  • PROPERTY_VERBOSE additionally logs each run's draws.
  • Gen::draw() outside a property run throws RuntimeException; explicit examples may draw (dedicated deterministic stream).

Additive, no BC breaks; a property that never draws generates exactly the same values as before (seed sequences untouched).

Verification

  • composer build — PASSED (563 tests, 270378 assertions)
  • composer rector — no findings in changed files (the tape loop is a while on purpose: the bound must be re-read, hoisted-count for would be wrong)
  • composer bc-check -- --from=v2.3.0 — "No backwards-incompatible changes detected"
  • make mutation — MSI 90.42% ≥ minMsi 90 (1283 killed / 136 escaped, 0 not covered)
  • bin/package-audit property-testing — 0 errors, 0 warnings

Downstream interceptors attach per-run attributes to each run's result —
notably Testo codecov's CoverageResult (its interceptor is innermost).
The interceptor returned bare TestResult objects, dropping them: property
tests were absent from per-test coverage (<covered by>), so Infection never
selected them for mutants and mutants on property-only covered lines
escaped silently.

Merge run attributes run-by-run (last write per key wins) and carry them on
all aggregate paths: passed, falsified, coverage violation, failing example.
@rasuvaeff

Copy link
Copy Markdown
Owner Author

Added f6e88d6: aggregate property results now preserve per-run TestResult attributes (all paths: passed / falsified / coverage violation / failing example).

Root cause of a real gap found while mutation-testing rector-datetime-immutable: the interceptor returned bare TestResult objects, dropping attributes attached by downstream interceptors — notably Testo codecov's CoverageResult (its interceptor is innermost, order PHP_INT_MAX, attaches per $next() run). Property tests were therefore absent from per-test coverage ( in coverage XML) while still present in JUnit, so Infection never selected them when running mutants — mutants on lines covered only by property tests escaped silently.

Verified end-to-end via path-repo against rector-datetime-immutable: with this commit the property test appears in for every line it executes.

Branch state: build green (566 tests), mutation gate passed (MSI ~90.7%). Note: composer rector reports 3 pre-existing findings in files untouched by this commit (RegexCompiler.php, ExampleViolationException.php, RegexCompilerTest.php) — likely a newer rector in a fresh install; out of scope here.

@rasuvaeff rasuvaeff merged commit 138bfb3 into master Jul 11, 2026
7 checks passed
@rasuvaeff rasuvaeff deleted the 2.4.0-in-body-draw branch July 11, 2026 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant