chore: adds support for PhpUnit@10.2.2

This commit is contained in:
Nuno Maduro
2023-06-15 12:02:00 +02:00
parent 2122e57990
commit 0e5470b192
6 changed files with 82 additions and 72 deletions

View File

@ -31,6 +31,7 @@ $bootPest = (static function (): void {
$getopt = getopt('', [ $getopt = getopt('', [
'status-file:', 'status-file:',
'progress-file:', 'progress-file:',
'unexpected-output-file:',
'testresult-file:', 'testresult-file:',
'teamcity-file:', 'teamcity-file:',
'testdox-file:', 'testdox-file:',
@ -58,6 +59,7 @@ $bootPest = (static function (): void {
assert(is_resource($statusFile)); assert(is_resource($statusFile));
assert(isset($getopt['progress-file']) && is_string($getopt['progress-file'])); assert(isset($getopt['progress-file']) && is_string($getopt['progress-file']));
assert(isset($getopt['unexpected-output-file']) && is_string($getopt['unexpected-output-file']));
assert(isset($getopt['testresult-file']) && is_string($getopt['testresult-file'])); assert(isset($getopt['testresult-file']) && is_string($getopt['testresult-file']));
assert(! isset($getopt['teamcity-file']) || is_string($getopt['teamcity-file'])); assert(! isset($getopt['teamcity-file']) || is_string($getopt['teamcity-file']));
assert(! isset($getopt['testdox-file']) || is_string($getopt['testdox-file'])); assert(! isset($getopt['testdox-file']) || is_string($getopt['testdox-file']));
@ -73,6 +75,7 @@ $bootPest = (static function (): void {
$application = new ApplicationForWrapperWorker( $application = new ApplicationForWrapperWorker(
$phpunitArgv, $phpunitArgv,
$getopt['progress-file'], $getopt['progress-file'],
$getopt['unexpected-output-file'],
$getopt['testresult-file'], $getopt['testresult-file'],
$getopt['teamcity-file'] ?? null, $getopt['teamcity-file'] ?? null,
$getopt['testdox-file'] ?? null, $getopt['testdox-file'] ?? null,
@ -88,10 +91,10 @@ $bootPest = (static function (): void {
$testPath = fgets(STDIN); $testPath = fgets(STDIN);
if ($testPath === false || $testPath === WrapperWorker::COMMAND_EXIT) { if ($testPath === false || $testPath === WrapperWorker::COMMAND_EXIT) {
$application->end(); $application->end();
exit; exit;
} }
// It must be a 1 byte string to ensure filesize() is equal to the number of tests executed
$exitCode = $application->runTest(realpath(trim($testPath))); $exitCode = $application->runTest(realpath(trim($testPath)));
fwrite($statusFile, (string) $exitCode); fwrite($statusFile, (string) $exitCode);

View File

@ -18,16 +18,16 @@
], ],
"require": { "require": {
"php": "^8.1.0", "php": "^8.1.0",
"brianium/paratest": "^7.1.4", "brianium/paratest": "^7.2.0",
"nunomaduro/collision": "^7.5.2", "nunomaduro/collision": "^7.5.2",
"nunomaduro/termwind": "^1.15.1", "nunomaduro/termwind": "^1.15.1",
"pestphp/pest-plugin": "^2.0.1", "pestphp/pest-plugin": "^2.0.1",
"pestphp/pest-plugin-arch": "^2.2.0", "pestphp/pest-plugin-arch": "^2.2.0",
"phpunit/phpunit": "^10.2.1" "phpunit/phpunit": "^10.2.2"
}, },
"conflict": { "conflict": {
"webmozart/assert": "<1.11.0", "webmozart/assert": "<1.11.0",
"phpunit/phpunit": ">10.2.1" "phpunit/phpunit": ">10.2.2"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -12,20 +12,20 @@ use function fread;
use function fseek; use function fseek;
use function ftell; use function ftell;
use function fwrite; use function fwrite;
use NunoMaduro\Collision\Adapters\Phpunit\State;
use ParaTest\Options; use ParaTest\Options;
use Pest\Plugins\Parallel\Support\CompactPrinter; use Pest\Plugins\Parallel\Support\CompactPrinter;
use Pest\Support\StateGenerator; use Pest\Support\StateGenerator;
use PHPUnit\TestRunner\TestResult\TestResult; use PHPUnit\TestRunner\TestResult\TestResult;
use PHPUnit\TextUI\Output\Printer; use PHPUnit\TextUI\Output\Printer;
use function preg_replace;
use SebastianBergmann\Timer\Duration; use SebastianBergmann\Timer\Duration;
use SplFileInfo; use SplFileInfo;
use function strlen; use function strlen;
use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
/** @internal */ /**
* @internal
*/
final class ResultPrinter final class ResultPrinter
{ {
/** /**
@ -51,7 +51,7 @@ final class ResultPrinter
/** @var resource|null */ /** @var resource|null */
private $teamcityLogFileHandle; private $teamcityLogFileHandle;
/** @var array<string, int> */ /** @var array<non-empty-string, int> */
private array $tailPositions; private array $tailPositions;
public function __construct( public function __construct(
@ -74,6 +74,7 @@ final class ResultPrinter
if (str_starts_with($buffer, 'done [')) { if (str_starts_with($buffer, 'done [')) {
return; return;
} }
$this->output->write(OutputFormatter::escape($buffer)); $this->output->write(OutputFormatter::escape($buffer));
} }
@ -93,9 +94,12 @@ final class ResultPrinter
$this->teamcityLogFileHandle = $teamcityLogFileHandle; $this->teamcityLogFileHandle = $teamcityLogFileHandle;
} }
/** @param array<int, SplFileInfo> $teamcityFiles */ /** @param list<SplFileInfo> $teamcityFiles */
public function printFeedback(SplFileInfo $progressFile, array $teamcityFiles): void public function printFeedback(
{ SplFileInfo $progressFile,
SplFileInfo $outputFile,
array $teamcityFiles
): void {
if ($this->options->needsTeamcity) { if ($this->options->needsTeamcity) {
$teamcityProgress = $this->tailMultiple($teamcityFiles); $teamcityProgress = $this->tailMultiple($teamcityFiles);
@ -115,6 +119,16 @@ final class ResultPrinter
return; return;
} }
$unexpectedOutput = $this->tail($outputFile);
if ($unexpectedOutput !== '') {
// if unexpected output only contains the letter "T", like "T", or "TT", or "TTT", etc, then ignore it.
if (preg_match('/^T+$/', $unexpectedOutput) !== false) {
return;
}
$this->output->write($unexpectedOutput);
}
$feedbackItems = $this->tail($progressFile); $feedbackItems = $this->tail($progressFile);
if ($feedbackItems === '') { if ($feedbackItems === '') {
return; return;
@ -129,8 +143,8 @@ final class ResultPrinter
} }
/** /**
* @param array<int, SplFileInfo> $teamcityFiles * @param list<SplFileInfo> $teamcityFiles
* @param array<int, SplFileInfo> $testdoxFiles * @param list<SplFileInfo> $testdoxFiles
*/ */
public function printResults(TestResult $testResult, array $teamcityFiles, array $testdoxFiles, Duration $duration): void public function printResults(TestResult $testResult, array $teamcityFiles, array $testdoxFiles, Duration $duration): void
{ {
@ -183,7 +197,7 @@ final class ResultPrinter
$this->compactPrinter->descriptionItem($item); $this->compactPrinter->descriptionItem($item);
} }
/** @param array<int, SplFileInfo> $files */ /** @param list<SplFileInfo> $files */
private function tailMultiple(array $files): string private function tailMultiple(array $files): string
{ {
$content = ''; $content = '';
@ -201,6 +215,7 @@ final class ResultPrinter
private function tail(SplFileInfo $file): string private function tail(SplFileInfo $file): string
{ {
$path = $file->getPathname(); $path = $file->getPathname();
assert($path !== '');
$handle = fopen($path, 'r'); $handle = fopen($path, 'r');
assert($handle !== false); assert($handle !== false);
$fseek = fseek($handle, $this->tailPositions[$path] ?? 0); $fseek = fseek($handle, $this->tailPositions[$path] ?? 0);

View File

@ -48,36 +48,39 @@ final class WrapperRunner implements RunnerInterface
private readonly Timer $timer; private readonly Timer $timer;
/** @var array<int, string> */ /** @var list<non-empty-string> */
private array $pending = []; private array $pending = [];
private int $exitCode = -1; private int $exitcode = -1;
/** @var array<int,WrapperWorker> */ /** @var array<positive-int,WrapperWorker> */
private array $workers = []; private array $workers = [];
/** @var array<int,int> */ /** @var array<int,int> */
private array $batches = []; private array $batches = [];
/** @var array<int, SplFileInfo> */ /** @var list<SplFileInfo> */
private array $unexpectedOutputFiles = [];
/** @var list<SplFileInfo> */
private array $testresultFiles = []; private array $testresultFiles = [];
/** @var array<int, SplFileInfo> */ /** @var list<SplFileInfo> */
private array $coverageFiles = []; private array $coverageFiles = [];
/** @var array<int, SplFileInfo> */ /** @var list<SplFileInfo> */
private array $junitFiles = []; private array $junitFiles = [];
/** @var array<int, SplFileInfo> */ /** @var list<SplFileInfo> */
private array $teamcityFiles = []; private array $teamcityFiles = [];
/** @var array<int, SplFileInfo> */ /** @var list<SplFileInfo> */
private array $testdoxFiles = []; private array $testdoxFiles = [];
/** @var array<int, string> */ /** @var non-empty-string[] */
private readonly array $parameters; private readonly array $parameters;
private readonly CodeCoverageFilterRegistry $codeCoverageFilterRegistry; private CodeCoverageFilterRegistry $codeCoverageFilterRegistry;
public function __construct( public function __construct(
private readonly Options $options, private readonly Options $options,
@ -86,11 +89,10 @@ final class WrapperRunner implements RunnerInterface
$this->printer = new ResultPrinter($output, $options); $this->printer = new ResultPrinter($output, $options);
$this->timer = new Timer(); $this->timer = new Timer();
$worker = realpath( $wrapper = realpath(
dirname(__DIR__, 4).DIRECTORY_SEPARATOR.'bin'.DIRECTORY_SEPARATOR.'worker.php', dirname(__DIR__, 4).DIRECTORY_SEPARATOR.'bin'.DIRECTORY_SEPARATOR.'worker.php',
); );
assert($wrapper !== false);
assert($worker !== false);
$phpFinder = new PhpExecutableFinder(); $phpFinder = new PhpExecutableFinder();
$phpBin = $phpFinder->find(false); $phpBin = $phpFinder->find(false);
assert($phpBin !== false); assert($phpBin !== false);
@ -101,7 +103,7 @@ final class WrapperRunner implements RunnerInterface
$parameters = array_merge($parameters, $options->passthruPhp); $parameters = array_merge($parameters, $options->passthruPhp);
} }
$parameters[] = $worker; $parameters[] = $wrapper;
$this->parameters = $parameters; $this->parameters = $parameters;
$this->codeCoverageFilterRegistry = new CodeCoverageFilterRegistry(); $this->codeCoverageFilterRegistry = new CodeCoverageFilterRegistry();
@ -110,14 +112,15 @@ final class WrapperRunner implements RunnerInterface
public function run(): int public function run(): int
{ {
$directory = dirname(__DIR__); $directory = dirname(__DIR__);
assert(strlen($directory) > 0); assert($directory !== '');
ExcludeList::addDirectory($directory); ExcludeList::addDirectory($directory);
TestResultFacade::init(); TestResultFacade::init();
EventFacade::instance()->seal(); EventFacade::instance()->seal();
$suiteLoader = new SuiteLoader(
$suiteLoader = new SuiteLoader($this->options, $this->output, $this->codeCoverageFilterRegistry); $this->options,
$this->output,
$this->codeCoverageFilterRegistry,
);
$this->pending = $this->getTestFiles($suiteLoader); $this->pending = $this->getTestFiles($suiteLoader);
$result = TestResultFacade::result(); $result = TestResultFacade::result();
@ -142,7 +145,7 @@ final class WrapperRunner implements RunnerInterface
{ {
$batchSize = $this->options->maxBatchSize; $batchSize = $this->options->maxBatchSize;
while ($this->pending !== [] && $this->workers !== []) { while (count($this->pending) > 0 && count($this->workers) > 0) {
foreach ($this->workers as $token => $worker) { foreach ($this->workers as $token => $worker) {
if (! $worker->isRunning()) { if (! $worker->isRunning()) {
throw $worker->getWorkerCrashedException(); throw $worker->getWorkerCrashedException();
@ -160,13 +163,11 @@ final class WrapperRunner implements RunnerInterface
} }
if ( if (
$this->exitCode > 0 $this->exitcode > 0
&& $this->options->configuration->stopOnFailure() && $this->options->configuration->stopOnFailure()
) { ) {
$this->pending = []; $this->pending = [];
} elseif (($pending = array_shift($this->pending)) !== null) { } elseif (($pending = array_shift($this->pending)) !== null) {
$this->debug(sprintf('Assigning %s to worker %d', $pending, $token));
$worker->assign($pending); $worker->assign($pending);
$this->batches[$token]++; $this->batches[$token]++;
} }
@ -178,9 +179,10 @@ final class WrapperRunner implements RunnerInterface
private function flushWorker(WrapperWorker $worker): void private function flushWorker(WrapperWorker $worker): void
{ {
$this->exitCode = max($this->exitCode, $worker->getExitCode()); $this->exitcode = max($this->exitcode, $worker->getExitCode());
$this->printer->printFeedback( $this->printer->printFeedback(
$worker->progressFile, $worker->progressFile,
$worker->unexpectedOutputFile,
$this->teamcityFiles, $this->teamcityFiles,
); );
$worker->reset(); $worker->reset();
@ -189,10 +191,10 @@ final class WrapperRunner implements RunnerInterface
private function waitForAllToFinish(): void private function waitForAllToFinish(): void
{ {
$stopped = []; $stopped = [];
while ($this->workers !== []) { while (count($this->workers) > 0) {
foreach ($this->workers as $index => $worker) { foreach ($this->workers as $index => $worker) {
if ($worker->isRunning()) { if ($worker->isRunning()) {
if (! array_key_exists($index, $stopped) && $worker->isFree()) { if (! isset($stopped[$index]) && $worker->isFree()) {
$worker->stop(); $worker->stop();
$stopped[$index] = true; $stopped[$index] = true;
} }
@ -212,22 +214,19 @@ final class WrapperRunner implements RunnerInterface
} }
} }
/** @param positive-int $token */
private function startWorker(int $token): WrapperWorker private function startWorker(int $token): WrapperWorker
{ {
/** @var array<non-empty-string> $parameters */
$parameters = $this->parameters;
$worker = new WrapperWorker( $worker = new WrapperWorker(
$this->output, $this->output,
$this->options, $this->options,
$parameters, $this->parameters,
$token, $token,
); );
$worker->start(); $worker->start();
$this->batches[$token] = 0; $this->batches[$token] = 0;
$this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile;
$this->testresultFiles[] = $worker->testresultFile; $this->testresultFiles[] = $worker->testresultFile;
if (isset($worker->junitFile)) { if (isset($worker->junitFile)) {
@ -330,15 +329,16 @@ final class WrapperRunner implements RunnerInterface
$this->generateCodeCoverageReports(); $this->generateCodeCoverageReports();
$this->generateLogs(); $this->generateLogs();
$exitCode = Result::exitCode($this->options->configuration, $testResultSum); $exitcode = Result::exitCode($this->options->configuration, $testResultSum);
$this->clearFiles($this->unexpectedOutputFiles);
$this->clearFiles($this->testresultFiles); $this->clearFiles($this->testresultFiles);
$this->clearFiles($this->coverageFiles); $this->clearFiles($this->coverageFiles);
$this->clearFiles($this->junitFiles); $this->clearFiles($this->junitFiles);
$this->clearFiles($this->teamcityFiles); $this->clearFiles($this->teamcityFiles);
$this->clearFiles($this->testdoxFiles); $this->clearFiles($this->testdoxFiles);
return $exitCode; return $exitcode;
} }
private function generateCodeCoverageReports(): void private function generateCodeCoverageReports(): void
@ -348,10 +348,11 @@ final class WrapperRunner implements RunnerInterface
} }
$coverageManager = new CodeCoverage(); $coverageManager = new CodeCoverage();
$coverageManager->init(
// @phpstan-ignore-next-line $this->options->configuration,
is_bool(true) && $coverageManager->init($this->options->configuration, $this->codeCoverageFilterRegistry, true); $this->codeCoverageFilterRegistry,
false,
);
$coverageMerger = new CoverageMerger($coverageManager->codeCoverage()); $coverageMerger = new CoverageMerger($coverageManager->codeCoverage());
foreach ($this->coverageFiles as $coverageFile) { foreach ($this->coverageFiles as $coverageFile) {
$coverageMerger->addCoverageFromFile($coverageFile); $coverageMerger->addCoverageFromFile($coverageFile);
@ -376,7 +377,7 @@ final class WrapperRunner implements RunnerInterface
); );
} }
/** @param array<int, SplFileInfo> $files */ /** @param list<SplFileInfo> $files */
private function clearFiles(array $files): void private function clearFiles(array $files): void
{ {
foreach ($files as $file) { foreach ($files as $file) {
@ -391,31 +392,19 @@ final class WrapperRunner implements RunnerInterface
/** /**
* Returns the test files to be executed. * Returns the test files to be executed.
* *
* @return array<int, string> * @return array<int, non-empty-string>
*/ */
private function getTestFiles(SuiteLoader $suiteLoader): array private function getTestFiles(SuiteLoader $suiteLoader): array
{ {
$this->debug(sprintf('Found %d test file%s', count($suiteLoader->files), count($suiteLoader->files) === 1 ? '' : 's')); /** @var array<string, non-empty-string> $files */
$files = [
/** @var array<string, string> $files */
$files = $suiteLoader->files;
return [
...array_values(array_filter( ...array_values(array_filter(
$files, $suiteLoader->tests,
fn (string $filename): bool => ! str_ends_with($filename, "eval()'d code") fn (string $filename): bool => ! str_ends_with($filename, "eval()'d code")
)), )),
...TestSuite::getInstance()->tests->getFilenames(), ...TestSuite::getInstance()->tests->getFilenames(),
]; ];
}
/** return $files; // @phpstan-ignore-line
* Prints a debug message.
*/
private function debug(string $message): void
{
if ($this->options->verbose) {
$this->output->writeln(" <fg=blue>{$message}</> ");
}
} }
} }

View File

@ -932,6 +932,9 @@
✓ it ensures the given closures reports the correct class name ✓ it ensures the given closures reports the correct class name
✓ it ensures the given closures reports the correct class name and suggests the [uses()] function ✓ it ensures the given closures reports the correct class name and suggests the [uses()] function
PASS Tests\Unit\Support\HigherOrderMessage
✓ undefined method exceptions
PASS Tests\Unit\Support\Reflection PASS Tests\Unit\Support\Reflection
✓ it gets file name from closure ✓ it gets file name from closure
✓ it gets property values ✓ it gets property values
@ -1035,4 +1038,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, 8 todos, 17 skipped, 715 passed (1729 assertions) Tests: 2 deprecated, 3 warnings, 4 incomplete, 1 notice, 8 todos, 17 skipped, 716 passed (1733 assertions)

View File

@ -18,7 +18,7 @@ $run = function () {
test('parallel', function () use ($run) { test('parallel', function () use ($run) {
expect($run('--exclude-group=integration')) expect($run('--exclude-group=integration'))
->toContain('Tests: 1 deprecated, 3 warnings, 4 incomplete, 1 notice, 8 todos, 14 skipped, 704 passed (1714 assertions)') ->toContain('Tests: 1 deprecated, 3 warnings, 4 incomplete, 1 notice, 8 todos, 14 skipped, 705 passed (1718 assertions)')
->toContain('Parallel: 3 processes'); ->toContain('Parallel: 3 processes');
})->skipOnWindows(); })->skipOnWindows();