mirror of
https://github.com/pestphp/pest.git
synced 2026-07-25 19:10:02 +02:00
Compare commits
8 Commits
afb582616d
...
3c8bae5f05
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c8bae5f05 | |||
| b2998bc69e | |||
| 3876093cd2 | |||
| 932f8bcc07 | |||
| d393799d2a | |||
| 0d7814ca16 | |||
| 8467c64c22 | |||
| 97714a7088 |
+1
-1
@@ -59,7 +59,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"mrpunyapal/peststan": "^0.2.10",
|
||||
"laravel/pao": "^1.1.0",
|
||||
"laravel/pao": "^1.1.1",
|
||||
"pestphp/pest-dev-tools": "^5.0.0",
|
||||
"pestphp/pest-plugin-browser": "^5.0.0",
|
||||
"pestphp/pest-plugin-type-coverage": "^5.0.0",
|
||||
|
||||
@@ -50,11 +50,14 @@ trait HandleArguments
|
||||
*/
|
||||
public function popArgument(string $argument, array $arguments): array
|
||||
{
|
||||
$arguments = array_flip($arguments);
|
||||
$key = array_search($argument, $arguments, true);
|
||||
|
||||
unset($arguments[$argument]);
|
||||
while ($key !== false) {
|
||||
unset($arguments[$key]);
|
||||
$key = array_search($argument, $arguments, true);
|
||||
}
|
||||
|
||||
return array_values(array_flip($arguments));
|
||||
return array_values($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,8 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
*/
|
||||
final class Coverage implements AddsOutput, HandlesArguments
|
||||
{
|
||||
use Concerns\HandleArguments;
|
||||
|
||||
private const string COVERAGE_OPTION = 'coverage';
|
||||
|
||||
private const string MIN_OPTION = 'min';
|
||||
@@ -77,11 +79,9 @@ final class Coverage implements AddsOutput, HandlesArguments
|
||||
return false;
|
||||
}))];
|
||||
|
||||
$originals = array_flip($originals);
|
||||
foreach ($arguments as $argument) {
|
||||
unset($originals[$argument]);
|
||||
$originals = $this->popArgument($argument, $originals);
|
||||
}
|
||||
$originals = array_flip($originals);
|
||||
|
||||
$inputs = [];
|
||||
$inputs[] = new InputOption(self::COVERAGE_OPTION, null, InputOption::VALUE_NONE);
|
||||
|
||||
+69
-4
@@ -27,6 +27,13 @@ final class Shard implements AddsOutput, HandlesArguments, Terminable
|
||||
|
||||
private const string SHARD_OPTION = 'shard';
|
||||
|
||||
/**
|
||||
* The maximum length allowed for the filter argument.
|
||||
* While ARG_MAX can be 2MB, individual arguments are often limited to 128KB (MAX_ARG_STRLEN).
|
||||
* Practical limits in CI environments (like Docker or pipeline runners) can be even lower.
|
||||
*/
|
||||
private const int MAX_FILTER_LENGTH = 32768;
|
||||
|
||||
/**
|
||||
* The shard index and total number of shards.
|
||||
*
|
||||
@@ -132,7 +139,8 @@ final class Shard implements AddsOutput, HandlesArguments, Terminable
|
||||
self::$timeBalanced = true;
|
||||
self::$shardsOutdated = $newTests !== [];
|
||||
} else {
|
||||
$testsToRun = (array_chunk($tests, max(1, (int) ceil(count($tests) / $total))))[$index - 1] ?? [];
|
||||
$isInCurrentShard = fn (int $key): bool => $key % $total === ($index - 1);
|
||||
$testsToRun = array_values(array_filter($tests, $isInCurrentShard, ARRAY_FILTER_USE_KEY));
|
||||
}
|
||||
|
||||
self::$shard = [
|
||||
@@ -146,7 +154,11 @@ final class Shard implements AddsOutput, HandlesArguments, Terminable
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
return [...$arguments, '--filter', $this->buildFilterArgument($testsToRun)];
|
||||
$filter = $this->buildFilterArgument($testsToRun);
|
||||
|
||||
$this->ensureFilterLengthIsSafe($filter);
|
||||
|
||||
return [...$arguments, '--filter', $filter];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,10 +221,63 @@ final class Shard implements AddsOutput, HandlesArguments, Terminable
|
||||
|
||||
/**
|
||||
* Builds the filter argument for the given tests to run.
|
||||
*
|
||||
* @param array<int, string> $testsToRun
|
||||
*/
|
||||
private function buildFilterArgument(mixed $testsToRun): string
|
||||
private function buildFilterArgument(array $testsToRun): string
|
||||
{
|
||||
return addslashes(implode('|', $testsToRun));
|
||||
if ($testsToRun === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $tree */
|
||||
$tree = [];
|
||||
foreach ($testsToRun as $class) {
|
||||
$parts = explode('\\', $class);
|
||||
$current = &$tree;
|
||||
foreach ($parts as $part) {
|
||||
if (! isset($current[$part])) {
|
||||
$current[$part] = [];
|
||||
}
|
||||
$current = &$current[$part];
|
||||
}
|
||||
}
|
||||
|
||||
$buildRegex = function (array $tree) use (&$buildRegex): string {
|
||||
$parts = [];
|
||||
foreach ($tree as $key => $sub) {
|
||||
$subRegex = $buildRegex($sub);
|
||||
if ($subRegex === '') {
|
||||
$parts[] = preg_quote($key, '/');
|
||||
} else {
|
||||
$parts[] = preg_quote($key, '/').'\\\\'.(count($sub) > 1 ? '('.$subRegex.')' : $subRegex);
|
||||
}
|
||||
}
|
||||
|
||||
return implode('|', $parts);
|
||||
};
|
||||
|
||||
return $buildRegex($tree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the filter length is safe for the current environment.
|
||||
*
|
||||
* @throws InvalidOption
|
||||
*/
|
||||
private function ensureFilterLengthIsSafe(string $filter): void
|
||||
{
|
||||
$maxLength = (int) (getenv('PEST_SHARD_MAX_FILTER_LENGTH') ?: self::MAX_FILTER_LENGTH);
|
||||
|
||||
if (strlen($filter) > $maxLength) {
|
||||
throw new InvalidOption(sprintf(
|
||||
'The generated filter for this shard is too long (%d characters). '.
|
||||
'This can cause issues with some environments (limit is %d characters). '.
|
||||
'Please increase the number of shards (e.g., use 1/4 instead of 1/2) to reduce the filter length.',
|
||||
strlen($filter),
|
||||
$maxLength
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1716,6 +1716,8 @@
|
||||
✓ method hasArgument with ('someValue', true)
|
||||
✓ method hasArgument with ('--a', false)
|
||||
✓ method hasArgument with ('--undefined-argument', false)
|
||||
✓ popArgument preserves duplicate values when removing a missing argument
|
||||
✓ popArgument preserves duplicate values when removing an existing argument
|
||||
|
||||
PASS Tests\Unit\Plugins\Environment
|
||||
✓ environment is set to CI when --ci option is used
|
||||
@@ -1724,6 +1726,40 @@
|
||||
PASS Tests\Unit\Plugins\Retry
|
||||
✓ it orders by defects and stop on defects if when --retry is used
|
||||
|
||||
PASS Tests\Unit\Plugins\Shard
|
||||
✓ getShard → it parses valid shard format with ('1/2', 1, 2)
|
||||
✓ getShard → it parses valid shard format with ('2/2', 2, 2)
|
||||
✓ getShard → it parses valid shard format with ('1/4', 1, 4)
|
||||
✓ getShard → it parses valid shard format with ('4/4', 4, 4)
|
||||
✓ getShard → it parses valid shard format with ('1/10', 1, 10)
|
||||
✓ getShard → it parses valid shard format with ('10/10', 10, 10)
|
||||
✓ getShard → it parses valid shard format with ('5/100', 5, 100)
|
||||
✓ getShard → it throws exception for invalid format with (['test', '--shard', 'invalid'])
|
||||
✓ getShard → it throws exception for invalid format with (['test', '--shard', '1'])
|
||||
✓ getShard → it throws exception for invalid format with (['test', '--shard', '1/'])
|
||||
✓ getShard → it throws exception for invalid format with (['test', '--shard', '/2'])
|
||||
✓ getShard → it throws exception for invalid format with (['test', '--shard', 'a/b'])
|
||||
✓ getShard → it throws exception for invalid format with (['test', '--shard', '1.5/2'])
|
||||
✓ getShard → it throws exception for invalid index or total values with (['test', '--shard', '0/2'])
|
||||
✓ getShard → it throws exception for invalid index or total values with (['test', '--shard', '1/0'])
|
||||
✓ getShard → it throws exception for invalid index or total values with (['test', '--shard', '3/2'])
|
||||
✓ getShard → it throws exception for invalid index or total values with (['test', '--shard', '5/4'])
|
||||
✓ buildFilterArgument → it generates compact filter for single test
|
||||
✓ buildFilterArgument → it generates compact filter for multiple tests with common prefix
|
||||
✓ buildFilterArgument → it generates compact filter for tests with different namespaces
|
||||
✓ buildFilterArgument → it returns empty string for empty test list
|
||||
✓ buildFilterArgument → it generates compact filter for deeply nested namespaces
|
||||
✓ buildFilterArgument → it handles mix of nested and flat namespaces
|
||||
✓ ensureFilterLengthIsSafe → it accepts filter within length limit
|
||||
✓ ensureFilterLengthIsSafe → it throws exception when filter exceeds default limit
|
||||
✓ ensureFilterLengthIsSafe → it respects custom limit from environment variable
|
||||
✓ ensureFilterLengthIsSafe → it accepts filter within custom limit
|
||||
✓ handleArguments → it returns original arguments when shard option is not present
|
||||
✓ handleArguments → it removes parallel arguments from test discovery
|
||||
✓ addOutput → it displays shard information after test execution
|
||||
✓ addOutput → it uses singular form for single test file
|
||||
✓ addOutput → it returns original exit code when shard is not set
|
||||
|
||||
PASS Tests\Unit\Plugins\Tia\ContentHash
|
||||
✓ of() → it returns false when file does not exist
|
||||
✓ of() → it hashes an existing file
|
||||
@@ -1913,6 +1949,7 @@
|
||||
✓ parallel
|
||||
✓ a parallel test can extend another test with same name
|
||||
✓ parallel reports invalid datasets as failures
|
||||
✓ parallel can have multiple exclude-groups
|
||||
|
||||
PASS Tests\Visual\ParallelNestedDatasets
|
||||
✓ parallel loads nested datasets from nested directories
|
||||
@@ -1946,4 +1983,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, 1335 passed (3024 assertions)
|
||||
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1370 passed (3068 assertions)
|
||||
@@ -24,3 +24,27 @@ test('method hasArgument', function (string $argument, bool $expectedResult) {
|
||||
['--a', false],
|
||||
['--undefined-argument', false],
|
||||
]);
|
||||
|
||||
test('popArgument preserves duplicate values when removing a missing argument', function () {
|
||||
$obj = new class
|
||||
{
|
||||
use HandleArguments;
|
||||
};
|
||||
|
||||
$arguments = ['--verbose', '--exclude-group', 'firstGroup', '--exclude-group', 'secondGroup', '--filter=MyTest'];
|
||||
$result = $obj->popArgument('--missingitem', $arguments);
|
||||
|
||||
expect($result)->toBe($arguments);
|
||||
});
|
||||
|
||||
test('popArgument preserves duplicate values when removing an existing argument', function () {
|
||||
$obj = new class
|
||||
{
|
||||
use HandleArguments;
|
||||
};
|
||||
|
||||
$arguments = ['--verbose', '--exclude-group', 'firstGroup', '--exclude-group', 'secondGroup', '--filter=MyTest'];
|
||||
$result = $obj->popArgument('--verbose', $arguments);
|
||||
|
||||
expect($result)->toBe(['--exclude-group', 'firstGroup', '--exclude-group', 'secondGroup', '--filter=MyTest']);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
use Pest\Exceptions\InvalidOption;
|
||||
use Pest\Plugins\Shard;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
|
||||
describe('getShard', function () {
|
||||
it('parses valid shard format', function (string $format, int $expectedIndex, int $expectedTotal) {
|
||||
$input = new ArgvInput(['test', '--shard', $format]);
|
||||
|
||||
$result = Shard::getShard($input);
|
||||
|
||||
expect($result)->toBe([
|
||||
'index' => $expectedIndex,
|
||||
'total' => $expectedTotal,
|
||||
]);
|
||||
})->with([
|
||||
['1/2', 1, 2],
|
||||
['2/2', 2, 2],
|
||||
['1/4', 1, 4],
|
||||
['4/4', 4, 4],
|
||||
['1/10', 1, 10],
|
||||
['10/10', 10, 10],
|
||||
['5/100', 5, 100],
|
||||
]);
|
||||
|
||||
it('throws exception for invalid format', function (array $arguments) {
|
||||
$input = new ArgvInput($arguments);
|
||||
|
||||
Shard::getShard($input);
|
||||
})->with([
|
||||
[['test', '--shard', 'invalid']],
|
||||
[['test', '--shard', '1']],
|
||||
[['test', '--shard', '1/']],
|
||||
[['test', '--shard', '/2']],
|
||||
[['test', '--shard', 'a/b']],
|
||||
[['test', '--shard', '1.5/2']],
|
||||
])->throws(InvalidOption::class);
|
||||
|
||||
it('throws exception for invalid index or total values', function (array $arguments) {
|
||||
$input = new ArgvInput($arguments);
|
||||
|
||||
Shard::getShard($input);
|
||||
})->with([
|
||||
[['test', '--shard', '0/2']],
|
||||
[['test', '--shard', '1/0']],
|
||||
[['test', '--shard', '3/2']],
|
||||
[['test', '--shard', '5/4']],
|
||||
])->throws(InvalidOption::class);
|
||||
});
|
||||
|
||||
describe('buildFilterArgument', function () {
|
||||
it('generates compact filter for single test', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$method = $reflection->getMethod('buildFilterArgument');
|
||||
|
||||
$filter = $method->invoke($shard, ['Tests\\Unit\\ExampleTest']);
|
||||
|
||||
expect($filter)->toBe('Tests\\\\Unit\\\\ExampleTest');
|
||||
});
|
||||
|
||||
it('generates compact filter for multiple tests with common prefix', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$method = $reflection->getMethod('buildFilterArgument');
|
||||
|
||||
$filter = $method->invoke($shard, [
|
||||
'Tests\\Unit\\Foo\\BarTest',
|
||||
'Tests\\Unit\\Foo\\BazTest',
|
||||
]);
|
||||
|
||||
expect($filter)->toBe('Tests\\\\Unit\\\\Foo\\\\(BarTest|BazTest)');
|
||||
});
|
||||
|
||||
it('generates compact filter for tests with different namespaces', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$method = $reflection->getMethod('buildFilterArgument');
|
||||
|
||||
$filter = $method->invoke($shard, [
|
||||
'Tests\\Unit\\FooTest',
|
||||
'Tests\\Feature\\BarTest',
|
||||
]);
|
||||
|
||||
expect($filter)->toBe('Tests\\\\(Unit\\\\FooTest|Feature\\\\BarTest)');
|
||||
});
|
||||
|
||||
it('returns empty string for empty test list', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$method = $reflection->getMethod('buildFilterArgument');
|
||||
|
||||
$filter = $method->invoke($shard, []);
|
||||
|
||||
expect($filter)->toBe('');
|
||||
});
|
||||
|
||||
it('generates compact filter for deeply nested namespaces', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$method = $reflection->getMethod('buildFilterArgument');
|
||||
|
||||
$filter = $method->invoke($shard, [
|
||||
'Tests\\Unit\\Plugins\\Concerns\\Foo',
|
||||
'Tests\\Unit\\Plugins\\Concerns\\Bar',
|
||||
'Tests\\Unit\\Plugins\\Concerns\\Baz',
|
||||
]);
|
||||
|
||||
expect($filter)->toBe('Tests\\\\Unit\\\\Plugins\\\\Concerns\\\\(Foo|Bar|Baz)');
|
||||
});
|
||||
|
||||
it('handles mix of nested and flat namespaces', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$method = $reflection->getMethod('buildFilterArgument');
|
||||
|
||||
$tests = [
|
||||
'Tests\\Unit\\SimpleTest',
|
||||
'Tests\\Unit\\Plugins\\Concerns\\HandleArguments',
|
||||
'Tests\\Unit\\Plugins\\Concerns\\Validation',
|
||||
'Tests\\Unit\\Another\\Deep\\Nested\\Test',
|
||||
];
|
||||
|
||||
$filter = $method->invoke($shard, $tests);
|
||||
|
||||
expect($filter)
|
||||
->toBe(addslashes('Tests\\Unit\\(SimpleTest|Plugins\\Concerns\\(HandleArguments|Validation)|Another\\Deep\\Nested\\Test)'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureFilterLengthIsSafe', function () {
|
||||
it('accepts filter within length limit', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$method = $reflection->getMethod('ensureFilterLengthIsSafe');
|
||||
|
||||
$filter = str_repeat('a', 1000);
|
||||
|
||||
$method->invoke($shard, $filter);
|
||||
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
|
||||
it('throws exception when filter exceeds default limit', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$method = $reflection->getMethod('ensureFilterLengthIsSafe');
|
||||
|
||||
$filter = str_repeat('a', 32769);
|
||||
|
||||
$method->invoke($shard, $filter);
|
||||
})->throws(InvalidOption::class, 'The generated filter for this shard is too long');
|
||||
|
||||
it('respects custom limit from environment variable', function () {
|
||||
putenv('PEST_SHARD_MAX_FILTER_LENGTH=1000');
|
||||
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$method = $reflection->getMethod('ensureFilterLengthIsSafe');
|
||||
|
||||
$filter = str_repeat('a', 1001);
|
||||
|
||||
try {
|
||||
$method->invoke($shard, $filter);
|
||||
expect(false)->toBeTrue('Should have thrown exception');
|
||||
} catch (InvalidOption $e) {
|
||||
expect($e->getMessage())->toContain('1001 characters')
|
||||
->and($e->getMessage())->toContain('limit is 1000 characters');
|
||||
} finally {
|
||||
putenv('PEST_SHARD_MAX_FILTER_LENGTH');
|
||||
}
|
||||
});
|
||||
|
||||
it('accepts filter within custom limit', function () {
|
||||
putenv('PEST_SHARD_MAX_FILTER_LENGTH=1000');
|
||||
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$method = $reflection->getMethod('ensureFilterLengthIsSafe');
|
||||
|
||||
$filter = str_repeat('a', 999);
|
||||
|
||||
try {
|
||||
$method->invoke($shard, $filter);
|
||||
expect(true)->toBeTrue();
|
||||
} catch (InvalidOption) {
|
||||
expect(false)->toBeTrue('Should not have thrown exception');
|
||||
} finally {
|
||||
putenv('PEST_SHARD_MAX_FILTER_LENGTH');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleArguments', function () {
|
||||
it('returns original arguments when shard option is not present', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$arguments = ['bin/pest', 'tests/', '--parallel'];
|
||||
|
||||
$result = $shard->handleArguments($arguments);
|
||||
|
||||
expect($result)->toBe($arguments);
|
||||
});
|
||||
|
||||
it('removes parallel arguments from test discovery', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$method = $reflection->getMethod('removeParallelArguments');
|
||||
|
||||
$arguments = ['bin/pest', '--parallel', 'tests/', '-p'];
|
||||
|
||||
$result = $method->invoke($shard, $arguments);
|
||||
|
||||
expect($result)->toBe([0 => 'bin/pest', 2 => 'tests/']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addOutput', function () {
|
||||
it('displays shard information after test execution', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$property = $reflection->getProperty('shard');
|
||||
$property->setValue(null, [
|
||||
'index' => 2,
|
||||
'total' => 4,
|
||||
'testsRan' => 25,
|
||||
'testsCount' => 100,
|
||||
]);
|
||||
|
||||
$exitCode = $shard->addOutput(0);
|
||||
$outputText = $output->fetch();
|
||||
|
||||
expect($exitCode)->toBe(0)
|
||||
->and($outputText)->toContain('Shard:')
|
||||
->and($outputText)->toContain('2 of 4')
|
||||
->and($outputText)->toContain('25 files ran')
|
||||
->and($outputText)->toContain('out of 100');
|
||||
});
|
||||
|
||||
it('uses singular form for single test file', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$property = $reflection->getProperty('shard');
|
||||
$property->setValue(null, [
|
||||
'index' => 1,
|
||||
'total' => 4,
|
||||
'testsRan' => 1,
|
||||
'testsCount' => 100,
|
||||
]);
|
||||
|
||||
$shard->addOutput(0);
|
||||
$outputText = $output->fetch();
|
||||
|
||||
expect($outputText)->toContain('1 file ran')
|
||||
->and($outputText)->not->toContain('1 files');
|
||||
});
|
||||
|
||||
it('returns original exit code when shard is not set', function () {
|
||||
$output = new BufferedOutput;
|
||||
$shard = new Shard($output);
|
||||
|
||||
$reflection = new ReflectionClass($shard);
|
||||
$property = $reflection->getProperty('shard');
|
||||
$property->setValue(null, null);
|
||||
|
||||
$exitCode = $shard->addOutput(1);
|
||||
$outputText = $output->fetch();
|
||||
|
||||
expect($exitCode)->toBe(1)
|
||||
->and($outputText)->not->toContain('Shard:');
|
||||
});
|
||||
});
|
||||
@@ -24,13 +24,13 @@ test('parallel', function () use ($run) {
|
||||
$file = file_get_contents(__FILE__);
|
||||
$file = preg_replace(
|
||||
'/\$expected = \'.*?\';/',
|
||||
"\$expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1319 passed (2973 assertions)';",
|
||||
"\$expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1353 passed (3015 assertions)';",
|
||||
$file,
|
||||
);
|
||||
file_put_contents(__FILE__, $file);
|
||||
}
|
||||
|
||||
$expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1319 passed (2973 assertions)';
|
||||
$expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1353 passed (3015 assertions)';
|
||||
|
||||
expect($output)
|
||||
->toContain("Tests: {$expected}")
|
||||
@@ -47,3 +47,14 @@ test('parallel reports invalid datasets as failures', function () use ($run) {
|
||||
->toContain('Tests: 1 failed, 1 passed (1 assertions)')
|
||||
->toContain('Parallel: 3 processes');
|
||||
})->skipOnWindows();
|
||||
|
||||
test('parallel can have multiple exclude-groups', function () use ($run) {
|
||||
$singleExclude = $run('--exclude-group=integration');
|
||||
$doubleExclude = $run('--exclude-group=integration', '--exclude-group=container');
|
||||
|
||||
preg_match('/(\d+) passed/', $singleExclude, $singleMatch);
|
||||
preg_match('/(\d+) passed/', $doubleExclude, $doubleMatch);
|
||||
|
||||
expect((int) $doubleMatch[1])->toBeLessThan((int) $singleMatch[1]);
|
||||
expect($doubleExclude)->toContain('Parallel: 3 processes');
|
||||
})->skipOnWindows();
|
||||
|
||||
Reference in New Issue
Block a user