Skip to content

Fixes before 1.0.0 - #7

Merged
ondrejmirtes merged 8 commits into
1.xfrom
prepare-1.0
Nov 14, 2026
Merged

ondrejmirtes merged 8 commits into
1.xfrom
prepare-1.0

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

The fixes and polish to make before tagging 1.0.0, one commit each:

  • Kill timed-out steps without pcntl. ProcessKiller used pcntl's SIGKILL constant, so without pcntl a timeout crashed. It now sends signal 9 directly, and composer.json requires ext-posix, which it calls.
  • UnifiedDiff takes time and memory proportional to the changed lines. Before, it built a table of every old line against every new line. A 3000-line file ran out of memory at 128M; now it takes 0.004 s and 5 MiB. The output matched the old implementation on 45,000 random inputs.
  • DirectorySuiteLoader collects cases once instead of copying the list for every method.
  • Workers load only the file of their case's class, not the whole suite and every other file's data providers.
  • The package leaves out repo-only files (.agents, CLAUDE.md, infection.json5, and the build's own PHPStan rules, stubs, and scripts).
  • build/tag-release.sh tags release/1.x only when it was generated from the given 1.x commit, and prints the push command instead of pushing.
  • Each benchmark file is tokenized once, and its edits are applied in one pass. For 500 cases in one 3000-line file, locating went from 2.9 s to 0.017 s and planning from 3.0 s to 0.008 s. This changes the @api ExpectationSource: rewrite() becomes edits(), which returns CodeEdits. It is done before 1.0 so it won't need a major version later. A case whose edits overlap another case's now fails instead of corrupting the file, and a file that changes after Tally loaded it fails with a message to run again.

The benchmark numbers will be accepted from this PR's Benchmarks job: UnifiedDiff and ExpectationChanges are much faster, and ExpectedAttributeRewriterBench has new benchIndex and benchEdits cases.

SIGKILL is a constant of the pcntl extension, which PHP builds such as the official
Docker images lack, while posix_kill() comes with posix. A case that ran too long
then failed tally run with an undefined constant instead of being reported. The
test's grandchild ignores the neighbouring signals, so only SIGKILL kills it.

Composer now checks for the POSIX extension the README asks for.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1d9bf-d458-7626-96ba-576978f6ab79
…ween them

The shortest edit script was computed from a table of every old line against every
new line between the first and the last change. An import added at the top of a
long benchmark file and an attribute added at its end put the whole file in that
table: 3000 lines ran out of the default 128M memory limit, at the end of
tally run, after counting every case.

Myers' search from the end finds, for each number of edits, the first line of each
diagonal from which the rest takes that many edits, which is all the walk from
the start needs to choose the same lines as before: a common line, else removing
or adding, whichever leaves fewer edits, removing on a tie. The output is the same
as the table's, compared on random inputs. The hunks count the lines before them
once in total instead of once per hunk.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1d9bf-d458-7626-96ba-576978f6ab79
Spreading the cases found so far into a new array for every benchmark method made
loading a suite quadratic in its cases, and every declared class was looked up in
the list of files.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1d9bf-d458-7626-96ba-576978f6ab79
Each of the two workers of a case loaded the whole suite and ran every data
provider, so starting a worker grew with the suite: with 2000 cases in 40 files,
loading took 0.5 s of a counted worker's 1.5 to 2 s under Valgrind, and a slow
data provider slowed every case of the run. The instructions do not depend on it.

A case now knows the file that declares its class, and the workers get it instead
of the paths of tally run. The data providers of that file still run in its
workers.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1d9bf-d458-7626-96ba-576978f6ab79
Composer's archives shipped .agents/, CLAUDE.md, infection.json5, the PHPStan rules
and stubs, and the release and coverage scripts, which only this repository uses.
ext/ and the build scripts of CodSpeed's Valgrind and tally_hooks stay, for the
README's build of tally_hooks for another PHP.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1d9bf-d458-7626-96ba-576978f6ab79
CI force-pushes release/1.x after every push to 1.x, so a tag made by hand could
land on the builds of an older 1.x commit while CI still runs for the newer one.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1d9bf-d458-7626-96ba-576978f6ab79
Locating and rewriting expectations tokenized the whole file again for every
case, and accept re-tokenized the growing result per case, so a file with many
cases was quadratic: 500 cases in one 3000-line file took 2.9 s to locate and
3.0 s to plan, now 0.017 s and 0.008 s.

Discovery indexes each declaring file's attributes once, and an
ExpectationSource now returns the edits to make instead of the rewritten code,
which ExpectationChanges sorts and applies per file. Edits that are equal, like
the same import needed by two cases, are made once; a case whose edit overlaps
another one fails instead of corrupting the file.

This changes the @api ExpectationSource (rewrite() becomes edits()), done
before 1.0 so that it does not need a major version later.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1d9bf-d458-7626-96ba-576978f6ab79
Planning expectations and diffing are now linear in the changed lines, and the
new index and edits benchmarks get their first numbers. WarmupBench::benchCold
counts the first use of PCRE in its worker now: before, the worker also loaded
the scripts example, whose data provider had already called preg_match.

Amp-Thread-ID: https://ampcode.com/threads/T-01a1d9bf-d458-7626-96ba-576978f6ab79
@ondrejmirtes
ondrejmirtes merged commit 7508062 into 1.x Nov 14, 2026
27 checks passed
@ondrejmirtes
ondrejmirtes deleted the prepare-1.0 branch November 14, 2026 12:11
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