facile-it/paraunit
Paraunit speeds up PHPUnit test suites by running tests in parallel across multiple PHPUnit processes. Built on Symfony components, it can collect coverage in parallel and supports many PHPUnit and Symfony versions. Install it via Composer as a dev dependency.
Architecture fit (against a Laravel codebase)
vendor/bin/paraunit run. There is no service provider, no Artisan command, no config/ publish, and no awareness of Laravel's ParallelTesting machinery. Architecturally it sits below Laravel's own test tooling.php artisan test --parallel (Laravel's Paratest-based wrapper) and therefore bypasses Laravel's per-process test-database provisioning (ParallelTesting::setUpTestDatabase, TestDatabases / DatabaseMigrations traits, ParallelTesting::setUpProcess callbacks). Any DB, cache, queue, or filesystem isolation that artisan test --parallel gives us for free must be rebuilt for Paraunit runs.phpunit.xml gets mutated — a config file that developers, CI, and artisan test all read.--coverage path does not parallelize cleanly.Integration feasibility
composer require --dev facile-it/paraunit, one binary, one PHPUnit config addition. Low engineering effort, no application-code changes.Technical risk
| Risk | Severity | Notes |
|---|---|---|
| Duplicate parallelization mechanism | High | Laravel already ships --parallel; adopting Paraunit means two parallel models, two isolation stories, and split documentation/CI conventions. |
| Missing Laravel isolation hooks | High | DB-per-process, unique storage paths, and cache namespacing are not provisioned. Evidence in changelog that DB behavior is a real concern: PostgreSQL deadlock detection was added in 1.1.1. |
| Version-drift churn with PHPUnit | Medium | Release history is dense with compat patches (2.2.2, 2.3.1, 2.3.4, 2.7.1, 2.8.0 chasing PHPUnit 10.4/11/11.3/12/13). Every Laravel major implies a Paraunit bump. |
| Shared-config mutation | Medium | The bootstrap extension lives in phpunit.xml; verify it does not alter behavior of plain vendor/bin/phpunit and artisan test runs. Mitigation: a separate phpunit.paraunit.xml. |
| Ecosystem validation is thin | Medium | 145 stars, 0 dependents, single-vendor (facile-it) maintenance, Apache-2.0 with no documented commercial SLA. Bus-factor exposure. |
| Interactive first-run prompt | Low–Medium | Auto-add-extension prompt is hostile to non-interactive CI; requires pre-provisioned config. |
| Flake amplification | Medium–High | Parallel execution surfaces latent shared-state coupling as newly "flaky" failures — cost lands on the team, not the package. |
| Resource pressure on CI runners | Medium | N concurrent Laravel boots × memory, plus coverage driver overhead (Pcov preferred, Xdebug fallback is materially slower). Risk of OOM/timeout on constrained containers. |
| Process-lifecycle edge cases | Low | 2.7.0 improved abnormal-termination detection; 2.11.0 demoted ext-pcntl to suggested, so signal handling/cleanup of orphaned children on non-pcntl platforms needs validation. |
Key questions
phpunit.xml <php>/<env> blocks and env vars to child processes exactly as PHPUnit does? Laravel's test config depends on this.TEST_TOKEN) injected into children, or must DB isolation be solved by the bootstrap extension / --pass-through? Without this, DB-per-process isolation is bespoke work.artisan test and IDE test runners remain untouched?--stop-on-* semantics when defects occur in different parallel processes (fail-fast correctness for PR gates)?--chunk-size heuristics for Laravel-sized suites, and memory ceiling per worker?Stack fit
ParallelTesting integration, no Artisan surface, no awareness of Laravel's per-process DB lifecycle. Treat it as a PHPUnit-level replacement for the runner, and re-implement isolation deliberately.Migration path
artisan test and artisan test --parallel. Without this the value case is unprovable.phpunit.paraunit.xml as a copy of phpunit.xml plus the Paraunit bootstrap extension; leave the canonical phpunit.xml unmodified so artisan test, IDE runners, and existing CI jobs are unaffected. Pre-provision the extension to avoid the interactive prompt.--exclude-testsuite to carve out unsafe groups while the rest parallelizes.vendor/bin/paraunit coverage --cobertura=... (Cobertura is explicitly supported and GitLab-friendly) once correctness under parallelism is trusted.Compatibility
phpunit to catch bootstrap-extension regressions early.ext-pcntl is now optional/suggested and real parallelism may depend on it.Sequencing (by risk, not by enthusiasm)
| Phase | Scope | Exit criterion |
|---|---|---|
| 0 | Measurement + value case | Documented minutes saved per PR and per merge |
| 1 | Spike on branch, unit suites only, separate XML config | Suite green, no regression in non-Paraunit runs |
| 2 | Isolation engineering (DB/cache/storage/queue) | Two consecutive full-suite parallel runs with zero new flakes |
| 3 | Extend to feature/DB suites, tune workers + --chunk-size |
Stable 10-run flake baseline |
| 4 | Parallel coverage in the PR gate | Coverage parity vs. serial baseline, stage time reduced |
| 5 | Decide: standardize, or retire in favor of artisan test --parallel |
Written decision record |
Crucially, keep artisan test --parallel as the documented fallback at every phase so the exit cost stays near zero.
Maintenance
Support
composer remove plus a CI job revert. This is a low-blast-radius dependency.Scaling
--chunk-size is the primary tuning lever; --sort=random is a useful flake-detection tool but should not be the default in the PR gate.Failure modes
ext-pcntl is absent; 2.7.0 improved abnormal-termination detection but process cleanup should be verified.--stop-on-* now supported, confirm fail-fast semantics across processes so PR gates fail for the right reason and reporting remains legible.Ramp-up
artisan test --parallel already meets the latency target, the incremental value reduces to parallel coverage alone — evaluate on that narrower justification, and expect the honest outcome to be "keep one parallelization story, not two."How can I help you explore Laravel packages today?