Compare commits

...

22 Commits

Author SHA1 Message Date
6c8970e0a3 release: v2.2.3 2023-03-24 11:26:54 +00:00
2f2b51ce3d chore: bumps dependencies 2023-03-24 11:22:41 +00:00
33f596bcce Merge pull request #703 from pestphp/2.x_parallel_args
[2.x] feat(parallel): Adds support for plugins to filter parallel arguments
2023-03-24 11:22:19 +00:00
50a96dcb8f Merge pull request #736 from fabio-ivona/fix-dataset-mismatch-message
[chore] Remove dataset name from DatasetArgsCountMismatch
2023-03-24 09:45:08 +00:00
d9a4fa33b9 remove dataset name from DatasetArgsCountMismatch 2023-03-24 10:20:20 +01:00
cc6bd59df9 release: v2.2.2 2023-03-23 21:48:55 +00:00
3ce6408195 fix: parallel testing test description 2023-03-23 21:47:10 +00:00
1c673fcff9 feat(parallel): Adds support for plugins to filter parallel arguments 2023-03-22 11:30:53 +00:00
ff82596158 feat(parallel): Adds support for plugins to filter parallel arguments 2023-03-22 11:27:26 +00:00
0539d2ba62 feat(parallel): Adds support for plugins to filter parallel arguments 2023-03-22 11:25:33 +00:00
221ac62f03 release: v2.2.1 2023-03-22 11:20:25 +00:00
4b6c949032 chore: updates snapshots 2023-03-22 11:18:10 +00:00
1915ad368a feat(parallel): Adds support for plugins to filter parallel arguments 2023-03-22 11:17:11 +00:00
1408cffc02 chore: bumps PHPUnit to ^10.0.18 2023-03-22 11:16:31 +00:00
95b5379945 Merge pull request #724 from fabio-ivona/fix-test-names-collision
[chore] Fix underscores in test names
2023-03-22 11:16:24 +00:00
a4833bbfe4 feat(parallel): Adds support for plugins to filter parallel arguments 2023-03-22 11:13:39 +00:00
cb1c777b9b Merge pull request #725 from fabio-ivona/disable-integration-tests-on-PRs
disable integration tests for PR workflows
2023-03-22 11:11:33 +00:00
7433cc5565 feat(parallel): Adds support for plugins to filter parallel arguments 2023-03-22 11:09:53 +00:00
4c769fac66 feat(parallel): Adds support for plugins to filter parallel arguments 2023-03-22 10:58:48 +00:00
176d3efbc6 fix workflow name 2023-03-22 11:54:54 +01:00
d635665c1b disable integration tests for PR workflows 2023-03-22 11:53:33 +01:00
22467d05c8 fix underscores in test names 2023-03-22 11:24:21 +01:00
19 changed files with 130 additions and 58 deletions

41
.github/workflows/integration-tests.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Integration Tests
on:
push:
schedule:
- cron: '0 0 * * *'
jobs:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
php: ['8.1', '8.2']
dependency-version: [prefer-lowest, prefer-stable]
name: PHP ${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none
- name: Setup Problem Matches
run: |
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Install PHP dependencies
run: composer update --${{ matrix.dependency-version }} --no-interaction --no-progress --ansi
- name: Integration Tests
run: composer test:integration

View File

@ -43,6 +43,3 @@ jobs:
- name: Unit Tests in Parallel - name: Unit Tests in Parallel
run: composer test:parallel run: composer test:parallel
if: startsWith(matrix.os, 'windows') != true if: startsWith(matrix.os, 'windows') != true
- name: Integration Tests
run: composer test:integration

View File

@ -2,6 +2,26 @@
## Unreleased ## Unreleased
## [v2.2.3 (2023-03-24)](https://github.com/pestphp/pest/compare/v2.2.2...v2.2.3)
### Fixed
- Unnecessary dataset on dataset arguments missmatch ([#736](https://github.com/pestphp/pest/pull/736))
- Parallel arguments on plugins order ([#703](https://github.com/pestphp/pest/pull/703))
- Arch plugin runtime exceptions on bad phpdocs ([2f2b51c](https://github.com/pestphp/pest/commit/2f2b51ce3d1b000be9d6add0e785fd0044931b3b))
## [v2.2.2 (2023-03-23)](https://github.com/pestphp/pest/compare/v2.2.1...v2.2.2)
### Fixed
- Edge case in parallel executation test description ([3ce6408](https://github.com/pestphp/pest/commit/3ce640819541ca6022b250e000f336d87c3e7889))
## [v2.2.1 (2023-03-22)](https://github.com/pestphp/pest/compare/v2.2.0...v2.2.1)
### Fixed
- Collision between tests names with underscores ([#724](https://github.com/pestphp/pest/pull/724))
### Chore
- Bumps PHPUnit to `^10.0.18` ([1408cff](https://github.com/pestphp/pest/commit/1408cffc028690057e44f00038f9390f776e6bfb))
## [v2.2.0 (2023-03-22)](https://github.com/pestphp/pest/compare/v2.1.0...v2.2.0) ## [v2.2.0 (2023-03-22)](https://github.com/pestphp/pest/compare/v2.1.0...v2.2.0)
### Added ### Added

View File

@ -19,15 +19,15 @@
"require": { "require": {
"php": "^8.1.0", "php": "^8.1.0",
"brianium/paratest": "^7.1.2", "brianium/paratest": "^7.1.2",
"nunomaduro/collision": "^7.3.2", "nunomaduro/collision": "^7.3.3",
"nunomaduro/termwind": "^1.15.1", "nunomaduro/termwind": "^1.15.1",
"pestphp/pest-plugin": "^2.0.0", "pestphp/pest-plugin": "^2.0.1",
"pestphp/pest-plugin-arch": "^2.0.1", "pestphp/pest-plugin-arch": "^2.0.2",
"phpunit/phpunit": "^10.0.17" "phpunit/phpunit": "^10.0.18"
}, },
"conflict": { "conflict": {
"webmozart/assert": "<1.11.0", "webmozart/assert": "<1.11.0",
"phpunit/phpunit": ">10.0.17" "phpunit/phpunit": ">10.0.18"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -2,7 +2,7 @@ ARG PHP=8.1
FROM php:${PHP}-cli-alpine FROM php:${PHP}-cli-alpine
RUN apk update \ RUN apk update \
&& apk add zip libzip-dev icu-dev && apk add zip libzip-dev icu-dev git
RUN docker-php-ext-configure zip RUN docker-php-ext-configure zip
RUN docker-php-ext-install zip RUN docker-php-ext-install zip

View File

@ -241,7 +241,7 @@ trait Testable
continue; continue;
} }
if (in_array($testParameterTypes[$argumentIndex], [\Closure::class, 'callable', 'mixed'])) { if (in_array($testParameterTypes[$argumentIndex], [Closure::class, 'callable', 'mixed'])) {
continue; continue;
} }
@ -255,7 +255,7 @@ trait Testable
return $arguments; return $arguments;
} }
if (in_array($testParameterTypes[0], [\Closure::class, 'callable'])) { if (in_array($testParameterTypes[0], [Closure::class, 'callable'])) {
return $arguments; return $arguments;
} }
@ -291,7 +291,7 @@ trait Testable
return; return;
} }
throw new DatasetArgsCountMismatch($this->dataName(), $requiredParametersCount, $suppliedParametersCount); throw new DatasetArgsCountMismatch($requiredParametersCount, $suppliedParametersCount);
} }
/** /**

View File

@ -10,7 +10,7 @@ namespace Pest\Contracts\Plugins;
interface HandlesArguments interface HandlesArguments
{ {
/** /**
* Adds arguments before of the Test Suite execution. * Adds arguments before the Test Suite execution.
* *
* @param array<int, string> $arguments * @param array<int, string> $arguments
* @return array<int, string> * @return array<int, string>

View File

@ -8,8 +8,8 @@ use Exception;
final class DatasetArgsCountMismatch extends Exception final class DatasetArgsCountMismatch extends Exception
{ {
public function __construct(string $dataName, int $requiredCount, int $suppliedCount) public function __construct(int $requiredCount, int $suppliedCount)
{ {
parent::__construct(sprintf('Test expects %d arguments but dataset [%s] only provides %d', $requiredCount, $dataName, $suppliedCount)); parent::__construct(sprintf('Test expects %d arguments but dataset only provides %d', $requiredCount, $suppliedCount));
} }
} }

View File

@ -6,7 +6,7 @@ namespace Pest;
function version(): string function version(): string
{ {
return '2.2.0'; return '2.2.3';
} }
function testDirectory(string $file = ''): string function testDirectory(string $file = ''): string

View File

@ -14,15 +14,16 @@ use Pest\Support\Arr;
use Pest\Support\Container; use Pest\Support\Container;
use Pest\TestSuite; use Pest\TestSuite;
use function Pest\version; use function Pest\version;
use Stringable;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\OutputInterface;
final class Parallel implements HandlesArguments final class Parallel implements HandlesArguments
{ {
use HandleArguments; use HandleArguments;
private const GLOBAL_PREFIX = 'PEST_PARALLEL_GLOBAL_';
private const HANDLERS = [ private const HANDLERS = [
Parallel\Handlers\Parallel::class, Parallel\Handlers\Parallel::class,
Parallel\Handlers\Pest::class, Parallel\Handlers\Pest::class,
@ -59,6 +60,33 @@ final class Parallel implements HandlesArguments
return ((int) $argvValue) === 1; return ((int) $argvValue) === 1;
} }
/**
* Sets a global value that can be accessed by the parent process and all workers.
*/
public static function setGlobal(string $key, string|int|bool|Stringable $value): void
{
$data = ['value' => $value instanceof Stringable ? $value->__toString() : $value];
$_ENV[self::GLOBAL_PREFIX.$key] = json_encode($data, JSON_THROW_ON_ERROR);
}
/**
* Returns the given global value if one has been set.
*/
public static function getGlobal(string $key): string|int|bool|null
{
$placesToCheck = [$_SERVER, $_ENV];
foreach ($placesToCheck as $location) {
if (array_key_exists(self::GLOBAL_PREFIX.$key, $location)) {
// @phpstan-ignore-next-line
return json_decode((string) $location[self::GLOBAL_PREFIX.$key], true, 512, JSON_THROW_ON_ERROR)['value'] ?? null;
}
}
return null;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -86,12 +114,6 @@ final class Parallel implements HandlesArguments
*/ */
private function runTestSuiteInParallel(array $arguments): int private function runTestSuiteInParallel(array $arguments): int
{ {
if (! class_exists(ParaTestCommand::class)) {
$this->askUserToInstallParatest();
return Command::FAILURE;
}
$handlers = array_filter( $handlers = array_filter(
array_map(fn ($handler): object|string => Container::getInstance()->get($handler), self::HANDLERS), array_map(fn ($handler): object|string => Container::getInstance()->get($handler), self::HANDLERS),
fn ($handler): bool => $handler instanceof HandlesArguments, fn ($handler): bool => $handler instanceof HandlesArguments,
@ -128,20 +150,6 @@ final class Parallel implements HandlesArguments
); );
} }
/**
* Outputs a message to the user asking them to install ParaTest as a dev dependency.
*/
private function askUserToInstallParatest(): void
{
/** @var OutputInterface $output */
$output = Container::getInstance()->get(OutputInterface::class);
$output->writeln([
'<fg=red>Pest Parallel requires ParaTest to run.</>',
'Please run <fg=yellow>composer require --dev brianium/paratest</>.',
]);
}
/** /**
* Builds an instance of the Paratest command. * Builds an instance of the Paratest command.
*/ */

View File

@ -24,7 +24,7 @@ final class StateGenerator
foreach ($testResult->testErroredEvents() as $testResultEvent) { foreach ($testResult->testErroredEvents() as $testResultEvent) {
if ($testResultEvent instanceof Errored) { if ($testResultEvent instanceof Errored) {
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
$testResultEvent->test(), $testResultEvent->test(),
TestResult::FAIL, TestResult::FAIL,
$testResultEvent->throwable() $testResultEvent->throwable()
@ -35,7 +35,7 @@ final class StateGenerator
} }
foreach ($testResult->testFailedEvents() as $testResultEvent) { foreach ($testResult->testFailedEvents() as $testResultEvent) {
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
$testResultEvent->test(), $testResultEvent->test(),
TestResult::FAIL, TestResult::FAIL,
$testResultEvent->throwable() $testResultEvent->throwable()
@ -43,7 +43,7 @@ final class StateGenerator
} }
foreach ($testResult->testMarkedIncompleteEvents() as $testResultEvent) { foreach ($testResult->testMarkedIncompleteEvents() as $testResultEvent) {
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
$testResultEvent->test(), $testResultEvent->test(),
TestResult::INCOMPLETE, TestResult::INCOMPLETE,
$testResultEvent->throwable() $testResultEvent->throwable()
@ -52,7 +52,7 @@ final class StateGenerator
foreach ($testResult->testConsideredRiskyEvents() as $riskyEvents) { foreach ($testResult->testConsideredRiskyEvents() as $riskyEvents) {
foreach ($riskyEvents as $riskyEvent) { foreach ($riskyEvents as $riskyEvent) {
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
$riskyEvent->test(), $riskyEvent->test(),
TestResult::RISKY, TestResult::RISKY,
ThrowableBuilder::from(new TestOutcome($riskyEvent->message())) ThrowableBuilder::from(new TestOutcome($riskyEvent->message()))
@ -62,12 +62,12 @@ final class StateGenerator
foreach ($testResult->testSkippedEvents() as $testResultEvent) { foreach ($testResult->testSkippedEvents() as $testResultEvent) {
if ($testResultEvent->message() === '__TODO__') { if ($testResultEvent->message() === '__TODO__') {
$state->add(TestResult::fromTestCase($testResultEvent->test(), TestResult::TODO)); $state->add(TestResult::fromPestParallelTestCase($testResultEvent->test(), TestResult::TODO));
continue; continue;
} }
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
$testResultEvent->test(), $testResultEvent->test(),
TestResult::SKIPPED, TestResult::SKIPPED,
ThrowableBuilder::from(new SkippedWithMessageException($testResultEvent->message())) ThrowableBuilder::from(new SkippedWithMessageException($testResultEvent->message()))
@ -77,7 +77,7 @@ final class StateGenerator
foreach ($testResult->testTriggeredDeprecationEvents() as $testResultEvent) { foreach ($testResult->testTriggeredDeprecationEvents() as $testResultEvent) {
$testResultEvent = $testResultEvent[0]; $testResultEvent = $testResultEvent[0];
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
$testResultEvent->test(), $testResultEvent->test(),
TestResult::DEPRECATED, TestResult::DEPRECATED,
ThrowableBuilder::from(new TestOutcome($testResultEvent->message())) ThrowableBuilder::from(new TestOutcome($testResultEvent->message()))
@ -87,7 +87,7 @@ final class StateGenerator
foreach ($testResult->testTriggeredPhpDeprecationEvents() as $testResultEvent) { foreach ($testResult->testTriggeredPhpDeprecationEvents() as $testResultEvent) {
$testResultEvent = $testResultEvent[0]; $testResultEvent = $testResultEvent[0];
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
$testResultEvent->test(), $testResultEvent->test(),
TestResult::DEPRECATED, TestResult::DEPRECATED,
ThrowableBuilder::from(new TestOutcome($testResultEvent->message())) ThrowableBuilder::from(new TestOutcome($testResultEvent->message()))
@ -97,7 +97,7 @@ final class StateGenerator
foreach ($testResult->testTriggeredNoticeEvents() as $testResultEvent) { foreach ($testResult->testTriggeredNoticeEvents() as $testResultEvent) {
$testResultEvent = $testResultEvent[0]; $testResultEvent = $testResultEvent[0];
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
$testResultEvent->test(), $testResultEvent->test(),
TestResult::NOTICE, TestResult::NOTICE,
ThrowableBuilder::from(new TestOutcome($testResultEvent->message())) ThrowableBuilder::from(new TestOutcome($testResultEvent->message()))
@ -107,7 +107,7 @@ final class StateGenerator
foreach ($testResult->testTriggeredPhpNoticeEvents() as $testResultEvent) { foreach ($testResult->testTriggeredPhpNoticeEvents() as $testResultEvent) {
$testResultEvent = $testResultEvent[0]; $testResultEvent = $testResultEvent[0];
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
$testResultEvent->test(), $testResultEvent->test(),
TestResult::NOTICE, TestResult::NOTICE,
ThrowableBuilder::from(new TestOutcome($testResultEvent->message())) ThrowableBuilder::from(new TestOutcome($testResultEvent->message()))
@ -117,7 +117,7 @@ final class StateGenerator
foreach ($testResult->testTriggeredWarningEvents() as $testResultEvent) { foreach ($testResult->testTriggeredWarningEvents() as $testResultEvent) {
$testResultEvent = $testResultEvent[0]; $testResultEvent = $testResultEvent[0];
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
$testResultEvent->test(), $testResultEvent->test(),
TestResult::WARN, TestResult::WARN,
ThrowableBuilder::from(new TestOutcome($testResultEvent->message())) ThrowableBuilder::from(new TestOutcome($testResultEvent->message()))
@ -127,7 +127,7 @@ final class StateGenerator
foreach ($testResult->testTriggeredPhpWarningEvents() as $testResultEvent) { foreach ($testResult->testTriggeredPhpWarningEvents() as $testResultEvent) {
$testResultEvent = $testResultEvent[0]; $testResultEvent = $testResultEvent[0];
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
$testResultEvent->test(), $testResultEvent->test(),
TestResult::WARN, TestResult::WARN,
ThrowableBuilder::from(new TestOutcome($testResultEvent->message())) ThrowableBuilder::from(new TestOutcome($testResultEvent->message()))
@ -136,7 +136,7 @@ final class StateGenerator
// for each test that passed, we need to add it to the state // for each test that passed, we need to add it to the state
for ($i = 0; $i < $passedTests; $i++) { for ($i = 0; $i < $passedTests; $i++) {
$state->add(TestResult::fromTestCase( $state->add(TestResult::fromPestParallelTestCase(
new TestMethod( new TestMethod(
"$i", // @phpstan-ignore-line "$i", // @phpstan-ignore-line
'', // @phpstan-ignore-line '', // @phpstan-ignore-line

View File

@ -59,6 +59,8 @@ final class Str
*/ */
public static function evaluable(string $code): string public static function evaluable(string $code): string
{ {
$code = str_replace('_', '__', $code);
$code = self::PREFIX.str_replace(' ', '_', $code); $code = self::PREFIX.str_replace(' ', '_', $code);
// sticks to PHP8.2 function naming rules https://www.php.net/manual/en/functions.user-defined.php // sticks to PHP8.2 function naming rules https://www.php.net/manual/en/functions.user-defined.php

View File

@ -1,7 +1,7 @@
. .
──────────────────────────────────────────────────────────────────────────── ────────────────────────────────────────────────────────────────────────────
FAILED Tests\Fixtures\CollisionTest > success Exception FAILED Tests\Fixtures\CollisionTest > error Exception
error error
at tests/Fixtures/CollisionTest.php:4 at tests/Fixtures/CollisionTest.php:4

View File

@ -1,5 +1,5 @@
Pest Testing Framework 2.2.0. Pest Testing Framework 2.2.3.
USAGE: pest <file> [options] USAGE: pest <file> [options]

View File

@ -909,7 +909,7 @@
PASS Tests\Unit\Support\Str PASS Tests\Unit\Support\Str
✓ it evaluates the code with ('version()', '__pest_evaluable_version__') ✓ it evaluates the code with ('version()', '__pest_evaluable_version__')
✓ it evaluates the code with ('version__ ', '__pest_evaluable_version___') ✓ it evaluates the code with ('version__ ', '__pest_evaluable_version_____')
✓ it evaluates the code with ('version\', '__pest_evaluable_version_') ✓ it evaluates the code with ('version\', '__pest_evaluable_version_')
PASS Tests\Unit\TestName PASS Tests\Unit\TestName
@ -918,6 +918,8 @@
✓ it may start with P with ('P\PPPackages\Foo', 'PPPackages\Foo') ✓ it may start with P with ('P\PPPackages\Foo', 'PPPackages\Foo')
✓ it may start with P with ('PPPackages\Foo', 'PPPackages\Foo') #1 ✓ it may start with P with ('PPPackages\Foo', 'PPPackages\Foo') #1
✓ it may start with P with ('PPPackages\Foo', 'PPPackages\Foo') #2 ✓ it may start with P with ('PPPackages\Foo', 'PPPackages\Foo') #2
✓ test description
✓ test_description
✓ ふ+が+ ✓ ふ+が+
✓ ほげ ✓ ほげ
✓ 卜竹弓一十山 ✓ 卜竹弓一十山
@ -1002,4 +1004,4 @@
PASS Tests\Visual\Version PASS Tests\Visual\Version
✓ visual snapshot of help command output ✓ visual snapshot of help command output
Tests: 2 deprecated, 3 warnings, 4 incomplete, 1 notice, 4 todos, 14 skipped, 701 passed (1698 assertions) Tests: 2 deprecated, 3 warnings, 4 incomplete, 1 notice, 4 todos, 14 skipped, 703 passed (1702 assertions)

View File

@ -1,3 +1,3 @@
Pest Testing Framework 2.2.0. Pest Testing Framework 2.2.3.

View File

@ -8,6 +8,6 @@ it('evaluates the code', function ($evaluatable, $expected) {
expect($code)->toBe($expected); expect($code)->toBe($expected);
})->with([ })->with([
['version()', '__pest_evaluable_version__'], ['version()', '__pest_evaluable_version__'],
['version__ ', '__pest_evaluable_version___'], ['version__ ', '__pest_evaluable_version_____'],
['version\\', '__pest_evaluable_version_'], ['version\\', '__pest_evaluable_version_'],
]); ]);

View File

@ -13,6 +13,8 @@ it('may start with P', function (string $real, string $toBePrinted) {
]); ]);
$names = [ $names = [
'test description' => '__pest_evaluable_test_description',
'test_description' => '__pest_evaluable_test__description',
'ふ+が+' => '__pest_evaluable_ふ_が_', 'ふ+が+' => '__pest_evaluable_ふ_が_',
'ほげ' => '__pest_evaluable_ほげ', 'ほげ' => '__pest_evaluable_ほげ',
'卜竹弓一十山' => '__pest_evaluable_卜竹弓一十山', '卜竹弓一十山' => '__pest_evaluable_卜竹弓一十山',

View File

@ -15,6 +15,6 @@ $run = function () {
}; };
test('parallel', function () use ($run) { test('parallel', function () use ($run) {
expect($run())->toContain('Tests: 2 deprecated, 3 warnings, 4 incomplete, 1 notice, 4 todos, 11 skipped, 690 passed (1684 assertions)') expect($run())->toContain('Tests: 2 deprecated, 3 warnings, 4 incomplete, 1 notice, 4 todos, 11 skipped, 692 passed (1688 assertions)')
->toContain('Parallel: 3 processes'); ->toContain('Parallel: 3 processes');
})->skip(PHP_OS_FAMILY === 'Windows'); })->skip(PHP_OS_FAMILY === 'Windows');