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
87 changes: 83 additions & 4 deletions src/Plugins/Parallel/Paratest/WrapperRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@
use function array_merge;
use function array_merge_recursive;
use function array_shift;
use function array_unique;
use function array_values;
use function assert;
use function count;
use function dirname;
use function file_get_contents;
use function max;
use function realpath;
use function str_contains;
use function str_starts_with;
use function unlink;
use function unserialize;
Expand Down Expand Up @@ -107,6 +110,11 @@ final class WrapperRunner implements RunnerInterface
/** @var non-empty-string[] */
private readonly array $parameters;

/** @var list<string>|null */
private ?array $stopOutcomeChars = null;

private bool $stopRequested = false;

/**
* The code coverage filter registry.
*/
Expand Down Expand Up @@ -210,10 +218,7 @@ private function assignAllPendingTests(): void
$worker = $this->startWorker($token);
}

if (
$this->exitcode > 0
&& $this->options->configuration->stopOnFailure()
) {
if ($this->shouldStop()) {
$this->pending = [];
} elseif (($pending = array_shift($this->pending)) !== null) {
$worker->assign($pending);
Expand All @@ -225,6 +230,80 @@ private function assignAllPendingTests(): void
}
}

private function shouldStop(): bool
{
if ($this->stopRequested) {
return true;
}

$outcomes = $this->stopOutcomeChars();
if ($outcomes === []) {
return false;
}

foreach ($this->workers as $worker) {
$progress = file_get_contents($worker->progressFile->getPathname());
if ($progress === false || $progress === '') {
continue;
}

foreach ($outcomes as $outcome) {
if (str_contains($progress, $outcome)) {
return $this->stopRequested = true;
}
}
}

return false;
}

/**
* @return list<string>
*/
private function stopOutcomeChars(): array
{
if ($this->stopOutcomeChars !== null) {
return $this->stopOutcomeChars;
}

$configuration = $this->options->configuration;
$outcomes = [];

if ($configuration->stopOnFailure() || $configuration->stopOnDefect()) {
$outcomes[] = 'F';
}

if ($configuration->stopOnError() || $configuration->stopOnDefect()) {
$outcomes[] = 'E';
}

if ($configuration->stopOnRisky() || $configuration->stopOnDefect()) {
$outcomes[] = 'R';
}

if ($configuration->stopOnIncomplete() || $configuration->stopOnDefect()) {
$outcomes[] = 'I';
}

if ($configuration->stopOnWarning()) {
$outcomes[] = 'W';
}

if ($configuration->stopOnNotice()) {
$outcomes[] = 'N';
}

if ($configuration->stopOnDeprecation()) {
$outcomes[] = 'D';
}

if ($configuration->stopOnSkipped()) {
$outcomes[] = 'S';
}

return $this->stopOutcomeChars = array_values(array_unique($outcomes));
}

private function flushWorker(WrapperWorker $worker): void
{
$this->exitcode = max($this->exitcode, $worker->getExitCode());
Expand Down
19 changes: 18 additions & 1 deletion tests/.snapshots/success.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1908,6 +1908,23 @@
✓ parallel
✓ a parallel test can extend another test with same name
✓ parallel reports invalid datasets as failures
✓ parallel stop-on-* fixture runs every case without a stop flag with ('fails')
✓ parallel stop-on-* fixture runs every case without a stop flag with ('errors')
✓ parallel stop-on-* fixture runs every case without a stop flag with ('is risky')
✓ parallel stop-on-* fixture runs every case without a stop flag with ('is incomplete')
✓ parallel stop-on-* fixture runs every case without a stop flag with ('warns')
✓ parallel stop-on-* fixture runs every case without a stop flag with ('notices')
✓ parallel stop-on-* fixture runs every case without a stop flag with ('deprecates')
✓ parallel stop-on-* fixture runs every case without a stop flag with ('is skipped')
✓ parallel honors --stop-on-* with dataset "failure"
✓ parallel honors --stop-on-* with dataset "defect"
✓ parallel honors --stop-on-* with dataset "error"
✓ parallel honors --stop-on-* with dataset "risky"
✓ parallel honors --stop-on-* with dataset "incomplete"
✓ parallel honors --stop-on-* with dataset "warning"
✓ parallel honors --stop-on-* with dataset "notice"
✓ parallel honors --stop-on-* with dataset "deprecation"
✓ parallel honors --stop-on-* with dataset "skipped"

PASS Tests\Visual\ParallelNestedDatasets
✓ parallel loads nested datasets from nested directories
Expand Down Expand Up @@ -1941,4 +1958,4 @@
✓ pass with dataset with ('my-datas-set-value')
✓ within describe → pass with dataset with ('my-datas-set-value')

Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1330 passed (3010 assertions)
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1347 passed (3027 assertions)
7 changes: 7 additions & 0 deletions tests/.tests/ParallelStopOn/HasDeprecationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

it('deprecates', function () {
trigger_error('user deprecation', E_USER_DEPRECATED);

expect(true)->toBeTrue();
});
5 changes: 5 additions & 0 deletions tests/.tests/ParallelStopOn/HasErrorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

it('errors', function () {
throw new RuntimeException('boom');
});
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/HasFailureTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('fails')->assertTrue(false);
5 changes: 5 additions & 0 deletions tests/.tests/ParallelStopOn/HasIncompleteTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

it('is incomplete', function () {
$this->markTestIncomplete('work in progress');
});
7 changes: 7 additions & 0 deletions tests/.tests/ParallelStopOn/HasNoticeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

it('notices', function () {
trigger_error('user notice', E_USER_NOTICE);

expect(true)->toBeTrue();
});
5 changes: 5 additions & 0 deletions tests/.tests/ParallelStopOn/HasRiskyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

it('is risky', function () {
// no assertions
});
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/HasSkippedTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('is skipped')->skip('skipping');
7 changes: 7 additions & 0 deletions tests/.tests/ParallelStopOn/HasWarningTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

it('warns', function () {
trigger_error('user warning', E_USER_WARNING);

expect(true)->toBeTrue();
});
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/Passing/Passing01Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('passes 01')->assertTrue(true);
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/Passing/Passing02Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('passes 02')->assertTrue(true);
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/Passing/Passing03Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('passes 03')->assertTrue(true);
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/Passing/Passing04Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('passes 04')->assertTrue(true);
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/Passing/Passing05Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('passes 05')->assertTrue(true);
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/Passing/Passing06Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('passes 06')->assertTrue(true);
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/Passing/Passing07Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('passes 07')->assertTrue(true);
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/Passing/Passing08Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('passes 08')->assertTrue(true);
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/Passing/Passing09Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('passes 09')->assertTrue(true);
3 changes: 3 additions & 0 deletions tests/.tests/ParallelStopOn/Passing/Passing10Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

it('passes 10')->assertTrue(true);
30 changes: 30 additions & 0 deletions tests/Visual/Parallel.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,33 @@
->toContain('Tests: 1 failed, 1 passed (1 assertions)')
->toContain('Parallel: 3 processes');
})->skipOnWindows();

test('parallel stop-on-* fixture runs every case without a stop flag', function (string $testName) use ($run) {
$output = $run('--filter', "($testName|passes)", 'tests/.tests/ParallelStopOn');
expect($output)->toContain('10 passed');
})->with([
'fails',
'errors',
'is risky',
'is incomplete',
'warns',
'notices',
'deprecates',
'is skipped',
])->skipOnWindows();

test('parallel honors --stop-on-*', function (string $flag, string $testName) use ($run) {
$output = $run('--filter', "($testName|passes)", $flag, 'tests/.tests/ParallelStopOn');
preg_match('/(\d+) passed/', $output, $matches);
expect((int) ($matches[1] ?? 0))->toBeLessThan(10);
})->with([
'failure' => ['--stop-on-failure', 'fails'],
'defect' => ['--stop-on-defect', 'fails'],
'error' => ['--stop-on-error', 'errors'],
'risky' => ['--stop-on-risky', 'is risky'],
'incomplete' => ['--stop-on-incomplete', 'is incomplete'],
'warning' => ['--stop-on-warning', 'warns'],
'notice' => ['--stop-on-notice', 'notices'],
'deprecation' => ['--stop-on-deprecation', 'deprecates'],
'skipped' => ['--stop-on-skipped', 'is skipped'],
])->skipOnWindows();