Skip to content

Foundation for native parallel test execution#6753

Draft
sebastianbergmann wants to merge 4 commits into
mainfrom
persistent-worker-foundation
Draft

Foundation for native parallel test execution#6753
sebastianbergmann wants to merge 4 commits into
mainfrom
persistent-worker-foundation

Conversation

@sebastianbergmann

@sebastianbergmann sebastianbergmann commented Jun 17, 2026

Copy link
Copy Markdown
Owner

These are the first steps towards native parallel test execution in PHPUnit. Rather than introducing a new subsystem, this pull request generalizes the existing process isolation implementation which already runs a test in a separate process and faithfully reconstitutes its outcome in the parent, from "one synchronous child per test" toward "many concurrent, reusable workers."

This PR does not wire anything into the CLI. There is no scheduler, worker pool, or --parallel option here. Sequential execution remains the only behavior a user can trigger, and process isolation is unchanged.

What's included

1. JobRunner made async-capable (src/Util/PHP/)

  • New RunningJob handle that separates spawning (proc_open) from reaping (proc_close). It exposes readableStreams() + non-blocking consume() so a single thread of control can multiplex several worker processes through one stream_select() loop, plus a blocking wait() for the synchronous case.
  • JobRunner::run() is reimplemented on top of this split and is behaviorally identical to before.
  • New JobRunner::start() spawns a long-lived process whose stdin stays open as a control channel, the building block for the worker (and a future pool of workers).

2. Persistent worker (src/Runner/Parallel/)

  • PersistentWorker + templates/worker.tpl: boots PHPUnit once, then loops reading newline-delimited JSON commands from its control channel, running each test via the same initForIsolation() event-collection path that process isolation uses, and writing the existing nonce-prefixed result envelope to a file.
  • Results are fed straight into the unchanged ChildProcessResultProcessor, so events, results, assertions, and code coverage reconstitute exactly as they do under process isolation. A worker that dies mid-test is reported as an error.

Design notes / trade-offs

  • Tests run by one worker share a process and therefore do not get per-test global-state isolation; this is what allows the bootstrap cost to be amortized
  • Code coverage is cleared per command; each envelope ships only that test's coverage, which the parent merges
  • PassedTests is shipped cumulatively, which is harmless because its import is idempotent
  • Concurrency uses proc_open() + stream_select() only, so there is no new runtime requirement beyond what process isolation already needs

Extract a RunningJob handle that splits process spawning (proc_open()) from reaping (proc_close()), so that a single thread of control can drive several worker processes concurrently via stream_select().
JobRunner::run() keeps its existing synchronous behavior.
A new JobRunner::start() spawns a long-running process whose standard input stays open as a control channel.
Generalize process isolation into a worker that boots PHPUnit once and then runs an arbitrary number of tests, each in response to a command on its control channel.
Results are transported back using the same serialized envelope as process isolation, so ChildProcessResultProcessor reconstitutes them unchanged.
Tests run by one worker share a process and therefore do not get per-test global-state isolation.
@sebastianbergmann sebastianbergmann self-assigned this Jun 17, 2026
@sebastianbergmann sebastianbergmann added type/refactoring A refactoring that should be applied to make the code easier to understand and maintain feature/test-runner CLI test runner labels Jun 17, 2026
@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.67%. Comparing base (43c2cd7) to head (bcfb967).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6753      +/-   ##
============================================
+ Coverage     97.66%   97.67%   +0.01%     
- Complexity     8967     9011      +44     
============================================
  Files           881      883       +2     
  Lines         27551    27707     +156     
============================================
+ Hits          26907    27063     +156     
  Misses          644      644              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature/test-runner CLI test runner type/refactoring A refactoring that should be applied to make the code easier to understand and maintain

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant