mirror of
https://github.com/pestphp/pest.git
synced 2026-03-10 01:37:21 +01:00
chore: different refactors
This commit is contained in:
@ -4,11 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Plugins;
|
||||
|
||||
use JsonException;
|
||||
use ParaTest\ParaTestCommand;
|
||||
use Pest\Contracts\Plugins\HandlesArguments;
|
||||
use Pest\Plugins\Actions\CallsAddsOutput;
|
||||
use Pest\Plugins\Concerns\HandleArguments;
|
||||
use Pest\Plugins\Parallel\Contracts\HandlesSubprocessArguments;
|
||||
use Pest\Plugins\Parallel\Contracts\HandlersWorkerArguments;
|
||||
use Pest\Plugins\Parallel\Paratest\CleanConsoleOutput;
|
||||
use Pest\Support\Arr;
|
||||
use Pest\Support\Container;
|
||||
@ -31,7 +32,11 @@ final class Parallel implements HandlesArguments
|
||||
|
||||
public static function isInParallelProcess(): bool
|
||||
{
|
||||
return (int) Arr::get($_SERVER, 'PARATEST') === 1;
|
||||
$argvValue = Arr::get($_SERVER, 'PARATEST');
|
||||
|
||||
assert(is_string($argvValue) || is_int($argvValue) || is_null($argvValue));
|
||||
|
||||
return ((int) $argvValue) === 1;
|
||||
}
|
||||
|
||||
public function handleArguments(array $arguments): array
|
||||
@ -41,12 +46,15 @@ final class Parallel implements HandlesArguments
|
||||
}
|
||||
|
||||
if (self::isInParallelProcess()) {
|
||||
return $this->runSubprocessHandlers($arguments);
|
||||
return $this->runWorkersHandlers($arguments);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $arguments
|
||||
*/
|
||||
private function argumentsContainParallelFlags(array $arguments): bool
|
||||
{
|
||||
if ($this->hasArgument('--parallel', $arguments)) {
|
||||
@ -56,6 +64,11 @@ final class Parallel implements HandlesArguments
|
||||
return $this->hasArgument('-p', $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $arguments
|
||||
*
|
||||
* @throws JsonException
|
||||
*/
|
||||
private function runTestSuiteInParallel(array $arguments): int
|
||||
{
|
||||
if (! class_exists(ParaTestCommand::class)) {
|
||||
@ -64,16 +77,16 @@ final class Parallel implements HandlesArguments
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$_ENV['PEST_PARALLEL_ARGV'] = json_encode($_SERVER['argv']);
|
||||
$_ENV['PEST_PARALLEL_ARGV'] = json_encode($_SERVER['argv'], JSON_THROW_ON_ERROR);
|
||||
|
||||
$handlers = array_filter(
|
||||
array_map(fn ($handler) => Container::getInstance()->get($handler), self::HANDLERS),
|
||||
fn ($handler) => $handler instanceof HandlesArguments,
|
||||
array_map(fn ($handler): object|string => Container::getInstance()->get($handler), self::HANDLERS),
|
||||
fn ($handler): bool => $handler instanceof HandlesArguments,
|
||||
);
|
||||
|
||||
$filteredArguments = array_reduce(
|
||||
$handlers,
|
||||
fn ($arguments, HandlesArguments $handler) => $handler->handleArguments($arguments),
|
||||
fn ($arguments, HandlesArguments $handler): array => $handler->handleArguments($arguments),
|
||||
$arguments
|
||||
);
|
||||
|
||||
@ -82,23 +95,30 @@ final class Parallel implements HandlesArguments
|
||||
return (new CallsAddsOutput())($exitCode);
|
||||
}
|
||||
|
||||
private function runSubprocessHandlers(array $arguments): array
|
||||
/**
|
||||
* @param array<int, string> $arguments
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function runWorkersHandlers(array $arguments): array
|
||||
{
|
||||
$handlers = array_filter(
|
||||
array_map(fn ($handler) => Container::getInstance()->get($handler), self::HANDLERS),
|
||||
fn ($handler) => $handler instanceof HandlesSubprocessArguments,
|
||||
array_map(fn ($handler): object|string => Container::getInstance()->get($handler), self::HANDLERS),
|
||||
fn ($handler): bool => $handler instanceof HandlersWorkerArguments,
|
||||
);
|
||||
|
||||
return array_reduce(
|
||||
$handlers,
|
||||
fn ($arguments, HandlesSubprocessArguments $handler) => $handler->handleSubprocessArguments($arguments),
|
||||
fn ($arguments, HandlersWorkerArguments $handler): array => $handler->handleWorkerArguments($arguments),
|
||||
$arguments
|
||||
);
|
||||
}
|
||||
|
||||
private function askUserToInstallParatest(): void
|
||||
{
|
||||
Container::getInstance()->get(OutputInterface::class)->writeln([
|
||||
/** @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</>.',
|
||||
]);
|
||||
@ -106,7 +126,10 @@ final class Parallel implements HandlesArguments
|
||||
|
||||
private function paratestCommand(): Application
|
||||
{
|
||||
$command = ParaTestCommand::applicationFactory(TestSuite::getInstance()->rootPath);
|
||||
/** @var non-empty-string $rootPath */
|
||||
$rootPath = TestSuite::getInstance()->rootPath;
|
||||
|
||||
$command = ParaTestCommand::applicationFactory($rootPath);
|
||||
$command->setAutoExit(false);
|
||||
$command->setName('Pest');
|
||||
$command->setVersion(version());
|
||||
|
||||
14
src/Plugins/Parallel/Contracts/HandlersWorkerArguments.php
Normal file
14
src/Plugins/Parallel/Contracts/HandlersWorkerArguments.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Plugins\Parallel\Contracts;
|
||||
|
||||
interface HandlersWorkerArguments
|
||||
{
|
||||
/**
|
||||
* @param array<int, string> $arguments
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function handleWorkerArguments(array $arguments): array;
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Plugins\Parallel\Contracts;
|
||||
|
||||
interface HandlesSubprocessArguments
|
||||
{
|
||||
public function handleSubprocessArguments(array $arguments): array;
|
||||
}
|
||||
@ -11,8 +11,6 @@ use ParaTest\RunnerInterface;
|
||||
use Pest\Contracts\Plugins\HandlesArguments;
|
||||
use Pest\Plugins\Concerns\HandleArguments;
|
||||
use Pest\Plugins\Parallel\Paratest\WrapperRunner;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
@ -22,13 +20,6 @@ final class Laravel implements HandlesArguments
|
||||
{
|
||||
use HandleArguments;
|
||||
|
||||
public function __construct(
|
||||
private readonly OutputInterface $output,
|
||||
private readonly InputInterface $input,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function handleArguments(array $arguments): array
|
||||
{
|
||||
if (! self::isALaravelApplication()) {
|
||||
@ -44,22 +35,22 @@ final class Laravel implements HandlesArguments
|
||||
|
||||
private function setLaravelParallelRunner(): void
|
||||
{
|
||||
if (! method_exists(ParallelRunner::class, 'resolveRunnerUsing')) {
|
||||
$this->output->writeln(' <fg=red>Using parallel with Pest requires Laravel v8.55.0 or higher.</>');
|
||||
exit(Command::FAILURE);
|
||||
}
|
||||
|
||||
ParallelRunner::resolveRunnerUsing(fn (Options $options, OutputInterface $output): RunnerInterface => new WrapperRunner($options, $output));
|
||||
ParallelRunner::resolveRunnerUsing( // @phpstan-ignore-line
|
||||
fn (Options $options, OutputInterface $output): RunnerInterface => new WrapperRunner($options, $output)
|
||||
);
|
||||
}
|
||||
|
||||
private static function isALaravelApplication(): bool
|
||||
{
|
||||
return InstalledVersions::isInstalled('laravel/framework', false)
|
||||
&& ! class_exists(\Orchestra\Testbench\TestCase::class);
|
||||
if (! InstalledVersions::isInstalled('laravel/framework', false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! class_exists(\Orchestra\Testbench\TestCase::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $arguments
|
||||
* @param array<int, string> $arguments
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function setEnvironmentVariables(array $arguments): array
|
||||
@ -75,17 +66,18 @@ final class Laravel implements HandlesArguments
|
||||
}
|
||||
|
||||
$arguments = $this->popArgument('--recreate-databases', $arguments);
|
||||
|
||||
return $this->popArgument('--drop-databases', $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $arguments
|
||||
* @param array<int, string> $arguments
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function useLaravelRunner(array $arguments): array
|
||||
{
|
||||
foreach ($arguments as $value) {
|
||||
if (str_starts_with((string)$value, '--runner')) {
|
||||
if (str_starts_with($value, '--runner')) {
|
||||
$arguments = $this->popArgument($value, $arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Plugins\Parallel\Handlers;
|
||||
|
||||
use Pest\Contracts\Plugins\HandlesArguments;
|
||||
use Pest\Plugins\Concerns\HandleArguments;
|
||||
use Pest\Plugins\Parallel\Contracts\HandlesSubprocessArguments;
|
||||
use Pest\Plugins\Parallel\Contracts\HandlersWorkerArguments;
|
||||
use Pest\Plugins\Retry;
|
||||
|
||||
final class Pest implements HandlesArguments, HandlesSubprocessArguments
|
||||
final class Pest implements HandlesArguments, HandlersWorkerArguments
|
||||
{
|
||||
use HandleArguments;
|
||||
|
||||
@ -20,7 +22,7 @@ final class Pest implements HandlesArguments, HandlesSubprocessArguments
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
public function handleSubprocessArguments(array $arguments): array
|
||||
public function handleWorkerArguments(array $arguments): array
|
||||
{
|
||||
$_SERVER['PEST_PARALLEL'] = '1';
|
||||
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Plugins\Parallel\Paratest;
|
||||
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
|
||||
class CleanConsoleOutput extends ConsoleOutput
|
||||
final class CleanConsoleOutput extends ConsoleOutput
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doWrite(string $message, bool $newline): void
|
||||
{
|
||||
|
||||
@ -37,7 +37,9 @@ use function unlink;
|
||||
use function unserialize;
|
||||
use function usleep;
|
||||
|
||||
/** @internal */
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class WrapperRunner implements RunnerInterface
|
||||
{
|
||||
private const CYCLE_SLEEP = 10000;
|
||||
@ -46,36 +48,36 @@ final class WrapperRunner implements RunnerInterface
|
||||
|
||||
private readonly Timer $timer;
|
||||
|
||||
/** @var non-empty-string[] */
|
||||
/** @var array<int, string> */
|
||||
private array $pending = [];
|
||||
|
||||
private int $exitcode = -1;
|
||||
private int $exitCode = -1;
|
||||
|
||||
/** @var array<positive-int,WrapperWorker> */
|
||||
/** @var array<int,WrapperWorker> */
|
||||
private array $workers = [];
|
||||
|
||||
/** @var array<int,int> */
|
||||
private array $batches = [];
|
||||
|
||||
/** @var list<SplFileInfo> */
|
||||
/** @var array<int, SplFileInfo> */
|
||||
private array $testresultFiles = [];
|
||||
|
||||
/** @var list<SplFileInfo> */
|
||||
/** @var array<int, SplFileInfo> */
|
||||
private array $coverageFiles = [];
|
||||
|
||||
/** @var list<SplFileInfo> */
|
||||
/** @var array<int, SplFileInfo> */
|
||||
private array $junitFiles = [];
|
||||
|
||||
/** @var list<SplFileInfo> */
|
||||
/** @var array<int, SplFileInfo> */
|
||||
private array $teamcityFiles = [];
|
||||
|
||||
/** @var list<SplFileInfo> */
|
||||
/** @var array<int, SplFileInfo> */
|
||||
private array $testdoxFiles = [];
|
||||
|
||||
/** @var non-empty-string[] */
|
||||
/** @var array<int, string> */
|
||||
private readonly array $parameters;
|
||||
|
||||
private CodeCoverageFilterRegistry $codeCoverageFilterRegistry;
|
||||
private readonly CodeCoverageFilterRegistry $codeCoverageFilterRegistry;
|
||||
|
||||
public function __construct(
|
||||
private readonly Options $options,
|
||||
@ -84,11 +86,11 @@ final class WrapperRunner implements RunnerInterface
|
||||
$this->printer = new ResultPrinter($output, $options);
|
||||
$this->timer = new Timer();
|
||||
|
||||
$wrapper = realpath(
|
||||
dirname(__DIR__, 4).DIRECTORY_SEPARATOR.'bin'.DIRECTORY_SEPARATOR.'pest-wrapper.php',
|
||||
$worker = realpath(
|
||||
dirname(__DIR__, 4).DIRECTORY_SEPARATOR.'bin'.DIRECTORY_SEPARATOR.'worker.php',
|
||||
);
|
||||
|
||||
assert($wrapper !== false);
|
||||
assert($worker !== false);
|
||||
$phpFinder = new PhpExecutableFinder();
|
||||
$phpBin = $phpFinder->find(false);
|
||||
assert($phpBin !== false);
|
||||
@ -99,7 +101,7 @@ final class WrapperRunner implements RunnerInterface
|
||||
$parameters = array_merge($parameters, $options->passthruPhp);
|
||||
}
|
||||
|
||||
$parameters[] = $wrapper;
|
||||
$parameters[] = $worker;
|
||||
|
||||
$this->parameters = $parameters;
|
||||
$this->codeCoverageFilterRegistry = new CodeCoverageFilterRegistry();
|
||||
@ -113,6 +115,7 @@ final class WrapperRunner implements RunnerInterface
|
||||
|
||||
TestResultFacade::init();
|
||||
EventFacade::seal();
|
||||
|
||||
$suiteLoader = new SuiteLoader($this->options, $this->output, $this->codeCoverageFilterRegistry);
|
||||
$this->pending = $this->getTestFiles($suiteLoader);
|
||||
|
||||
@ -159,7 +162,7 @@ final class WrapperRunner implements RunnerInterface
|
||||
}
|
||||
|
||||
if (
|
||||
$this->exitcode > 0
|
||||
$this->exitCode > 0
|
||||
&& $this->options->configuration->stopOnFailure()
|
||||
) {
|
||||
$this->pending = [];
|
||||
@ -177,7 +180,7 @@ final class WrapperRunner implements RunnerInterface
|
||||
|
||||
private function flushWorker(WrapperWorker $worker): void
|
||||
{
|
||||
$this->exitcode = max($this->exitcode, $worker->getExitCode());
|
||||
$this->exitCode = max($this->exitCode, $worker->getExitCode());
|
||||
$this->printer->printFeedback(
|
||||
$worker->progressFile,
|
||||
$this->teamcityFiles,
|
||||
@ -191,7 +194,7 @@ final class WrapperRunner implements RunnerInterface
|
||||
while ($this->workers !== []) {
|
||||
foreach ($this->workers as $index => $worker) {
|
||||
if ($worker->isRunning()) {
|
||||
if (! isset($stopped[$index]) && $worker->isFree()) {
|
||||
if (! array_key_exists($index, $stopped) && $worker->isFree()) {
|
||||
$worker->stop();
|
||||
$stopped[$index] = true;
|
||||
}
|
||||
@ -213,16 +216,22 @@ final class WrapperRunner implements RunnerInterface
|
||||
|
||||
private function startWorker(int $token): WrapperWorker
|
||||
{
|
||||
/** @var array<non-empty-string> $parameters */
|
||||
$parameters = $this->parameters;
|
||||
|
||||
$worker = new WrapperWorker(
|
||||
$this->output,
|
||||
$this->options,
|
||||
$this->parameters,
|
||||
$parameters,
|
||||
$token,
|
||||
);
|
||||
|
||||
$worker->start();
|
||||
|
||||
$this->batches[$token] = 0;
|
||||
|
||||
$this->testresultFiles[] = $worker->testresultFile;
|
||||
|
||||
if (isset($worker->junitFile)) {
|
||||
$this->junitFiles[] = $worker->junitFile;
|
||||
}
|
||||
@ -349,7 +358,7 @@ final class WrapperRunner implements RunnerInterface
|
||||
);
|
||||
}
|
||||
|
||||
/** @param list<SplFileInfo> $files */
|
||||
/** @param array<int, SplFileInfo> $files */
|
||||
private function clearFiles(array $files): void
|
||||
{
|
||||
foreach ($files as $file) {
|
||||
@ -362,23 +371,29 @@ final class WrapperRunner implements RunnerInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* We are doing this because the SuiteLoader returns filenames incorrectly
|
||||
* for Pest tests. Ideally we should find a cleaner solution.
|
||||
* Returns the test files to be executed.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function getTestFiles(SuiteLoader $suiteLoader): array
|
||||
{
|
||||
$this->debug(sprintf('Found %d test file%s', count($suiteLoader->files), count($suiteLoader->files) === 1 ? '' : 's'));
|
||||
|
||||
$phpunitTests = array_filter(
|
||||
$suiteLoader->files,
|
||||
fn (string $filename): bool => ! str_ends_with($filename, "eval()'d code")
|
||||
);
|
||||
/** @var array<string, string> $files */
|
||||
$files = $suiteLoader->files;
|
||||
|
||||
$pestTests = TestSuite::getInstance()->tests->getFilenames();
|
||||
|
||||
return [...$phpunitTests, ...$pestTests];
|
||||
return [
|
||||
...array_values(array_filter(
|
||||
$files,
|
||||
fn (string $filename): bool => ! str_ends_with($filename, "eval()'d code")
|
||||
)),
|
||||
...TestSuite::getInstance()->tests->getFilenames(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a debug message.
|
||||
*/
|
||||
private function debug(string $message): void
|
||||
{
|
||||
if ($this->options->verbose) {
|
||||
|
||||
Reference in New Issue
Block a user