Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/Plugins/Parallel/Paratest/WrapperRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,18 @@ private function complete(TestResult $testResultSum): int
/** @var list<AfterLastTestMethodFailed> $failedEvents */
$failedEvents = array_merge_recursive($testResultSum->testFailedEvents(), $testResult->testFailedEvents());

// A worker's `hasTests()` only reflects the *last* file it executed:
// PHPUnit's Collector overwrites `numberOfTests` on every
// `executionStarted` event. When a worker's final file has all of
// its tests excluded by `--group`/`--exclude-group`, that worker
// reports `hasTests() === false` even though it ran plenty of
// tests. If every worker happens to end on such a file, the merged
// result claims the whole suite was empty and the run exits 1
// (PHPUnit enables `failOnEmptyTestSuite` implicitly for explicit
// test selections) — despite a fully green summary. Treating a
// worker with executed tests as "has tests" restores the truth.
$testResultSum = new TestResult(
(int) $testResultSum->hasTests() + (int) $testResult->hasTests(),
(int) ($testResultSum->hasTests() || $testResult->hasTests() || $testResult->numberOfTestsRun() > 0),
$testResultSum->numberOfTestsRun() + $testResult->numberOfTestsRun(),
$testResultSum->numberOfAssertions() + $testResult->numberOfAssertions(),
array_merge_recursive($testResultSum->testErroredEvents(), $testResult->testErroredEvents()),
Expand Down
5 changes: 5 additions & 0 deletions tests/.tests/ParallelGroupFilteredLastFile/ATest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

test('a test inside the filtered group', function () {
expect(true)->toBeTrue();
})->group('filtered-group');
5 changes: 5 additions & 0 deletions tests/.tests/ParallelGroupFilteredLastFile/ZTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

test('a test outside the filtered group', function () {
expect(true)->toBeTrue();
});
35 changes: 35 additions & 0 deletions tests/Visual/Parallel.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,38 @@
->toContain('Tests: 1 failed, 1 passed (1 assertions)')
->toContain('Parallel: 3 processes');
})->skipOnWindows();

test('parallel group-filtered run is not considered empty when workers end on fully filtered files', function () {
// Regression: a worker's TestResult only carries `numberOfTests` of its
// LAST executed file (PHPUnit's Collector overwrites it per execution).
// With `--processes=1` the single worker deterministically ends on
// ZTest.php, whose only test is excluded by the group filter — before the
// fix the merged result claimed "no tests" and exited 1 despite the green
// summary (PHPUnit enables failOnEmptyTestSuite implicitly for --group).
$process = new Process(
['php', 'bin/pest', '--parallel', '--processes=1', '--group=filtered-group', 'tests/.tests/ParallelGroupFilteredLastFile'],
dirname(__DIR__, 2),
['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'],
);

$process->run();

expect(removeAnsiEscapeSequences($process->getOutput()))
->toContain('Tests: 1 passed (1 assertions)');

expect($process->getExitCode())->toBe(0);
})->skipOnWindows();

test('parallel group-filtered run still fails when the selection matches no tests at all', function () {
// Companion guard: a genuinely empty selection must keep exiting non-zero
// (failOnEmptyTestSuite) — the fix must not mask real "0 tests" runs.
$process = new Process(
['php', 'bin/pest', '--parallel', '--processes=3', '--group=group-that-does-not-exist', 'tests/.tests/ParallelGroupFilteredLastFile'],
dirname(__DIR__, 2),
['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'],
);

$process->run();

expect($process->getExitCode())->not->toBe(0);
})->skipOnWindows();