mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 07:47:22 +01:00
Removes parallel classes.
This commit is contained in:
10
bin/pest
10
bin/pest
@ -3,10 +3,11 @@
|
|||||||
|
|
||||||
use NunoMaduro\Collision\Provider;
|
use NunoMaduro\Collision\Provider;
|
||||||
use ParaTest\Console\Commands\ParaTestCommand;
|
use ParaTest\Console\Commands\ParaTestCommand;
|
||||||
|
use Pest\Actions\InteractsWithPlugins;
|
||||||
use Pest\Actions\LoadStructure;
|
use Pest\Actions\LoadStructure;
|
||||||
use Pest\Actions\MapArguments;
|
|
||||||
use Pest\Actions\ValidatesEnvironment;
|
use Pest\Actions\ValidatesEnvironment;
|
||||||
use Pest\Console\Command;
|
use Pest\Console\Command;
|
||||||
|
use Pest\Support\Arr;
|
||||||
use Pest\Support\Container;
|
use Pest\Support\Container;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
@ -54,13 +55,16 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$_SERVER['argv'] = InteractsWithPlugins::handleArguments($_SERVER['argv']);
|
||||||
|
|
||||||
if ($argv->hasParameterOption('--parallel')) {
|
if ($argv->hasParameterOption('--parallel')) {
|
||||||
LoadStructure::in($testSuite->rootPath);
|
LoadStructure::in($testSuite->rootPath);
|
||||||
MapArguments::toParatest($testSuite);
|
|
||||||
exit(ParaTestCommand::applicationFactory($testSuite->rootPath)->run(new ArgvInput()));
|
exit(ParaTestCommand::applicationFactory($testSuite->rootPath)->run(new ArgvInput()));
|
||||||
}
|
}
|
||||||
|
|
||||||
MapArguments::toPest($testSuite);
|
if (Arr::get($_SERVER, 'PARATEST', false) !== false) {
|
||||||
|
TestSuite::getInstance()->isInParallel = true;
|
||||||
|
}
|
||||||
|
|
||||||
exit($container->get(Command::class)->run($_SERVER['argv']));
|
exit($container->get(Command::class)->run($_SERVER['argv']));
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
"php": "^7.3 || ^8.0",
|
"php": "^7.3 || ^8.0",
|
||||||
"nunomaduro/collision": "^5.4.0",
|
"nunomaduro/collision": "^5.4.0",
|
||||||
"pestphp/pest-plugin": "^1.0.0",
|
"pestphp/pest-plugin": "^1.0.0",
|
||||||
|
"pestphp/pest-plugin-template": "1.x-dev",
|
||||||
"phpunit/phpunit": "^9.5.5"
|
"phpunit/phpunit": "^9.5.5"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@ -44,7 +45,8 @@
|
|||||||
"illuminate/console": "^8.47.0",
|
"illuminate/console": "^8.47.0",
|
||||||
"illuminate/support": "^8.47.0",
|
"illuminate/support": "^8.47.0",
|
||||||
"laravel/dusk": "^6.15.0",
|
"laravel/dusk": "^6.15.0",
|
||||||
"pestphp/pest-dev-tools": "dev-master"
|
"pestphp/pest-dev-tools": "dev-master",
|
||||||
|
"pestphp/pest-plugin-parallel": "1.x-dev"
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
@ -85,5 +87,11 @@
|
|||||||
"Pest\\Laravel\\PestServiceProvider"
|
"Pest\\Laravel\\PestServiceProvider"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "path",
|
||||||
|
"url": "../pest-plugin-parallel"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/Actions/InteractsWithPlugins.php
Normal file
33
src/Actions/InteractsWithPlugins.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Actions;
|
||||||
|
|
||||||
|
use Pest\Contracts\Plugins\HandlesArguments;
|
||||||
|
use Pest\Plugin\Loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class InteractsWithPlugins
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the input arguments by passing it to the relevant plugins.
|
||||||
|
*
|
||||||
|
* @param array<string> $argv
|
||||||
|
*
|
||||||
|
* @return array<string>
|
||||||
|
*/
|
||||||
|
public static function handleArguments(array $argv): array
|
||||||
|
{
|
||||||
|
$plugins = Loader::getPlugins(HandlesArguments::class);
|
||||||
|
|
||||||
|
/** @var HandlesArguments $plugin */
|
||||||
|
foreach ($plugins as $plugin) {
|
||||||
|
$argv = $plugin->handleArguments($argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $argv;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,112 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Pest\Actions;
|
|
||||||
|
|
||||||
use Pest\Console\Paratest\Runner;
|
|
||||||
use Pest\Contracts\Plugins\HandlesArguments;
|
|
||||||
use Pest\Plugin\Loader;
|
|
||||||
use Pest\Support\Container;
|
|
||||||
use Pest\Support\Coverage;
|
|
||||||
use Pest\TestSuite;
|
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
|
|
||||||
final class MapArguments
|
|
||||||
{
|
|
||||||
public static function toParatest(TestSuite $testSuite): void
|
|
||||||
{
|
|
||||||
self::registerPlugins();
|
|
||||||
self::coverage();
|
|
||||||
self::parallel();
|
|
||||||
self::color();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function toPest(TestSuite $testSuite): void
|
|
||||||
{
|
|
||||||
self::inParallel($testSuite);
|
|
||||||
// we could add coverage here too, so we stop before even running tests if there is no coverage driver
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function registerPlugins(): void
|
|
||||||
{
|
|
||||||
$plugins = Loader::getPlugins(HandlesArguments::class);
|
|
||||||
|
|
||||||
/** @var HandlesArguments $plugin */
|
|
||||||
foreach ($plugins as $plugin) {
|
|
||||||
$_SERVER['argv'] = $plugin->handleArguments($_SERVER['argv']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function parallel(): void
|
|
||||||
{
|
|
||||||
if (self::unsetArgument('--parallel')) {
|
|
||||||
self::setArgument('--runner', Runner::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function inParallel(TestSuite $testSuite): void
|
|
||||||
{
|
|
||||||
if (self::unsetArgument('--isInParallel')) {
|
|
||||||
$testSuite->isInParallel = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function color(): void
|
|
||||||
{
|
|
||||||
$argv = new ArgvInput();
|
|
||||||
$isDecorated = $argv->getParameterOption('--colors', 'always') !== 'never';
|
|
||||||
|
|
||||||
self::unsetArgument('--colors');
|
|
||||||
//refactor later
|
|
||||||
self::unsetArgument('--colors=always');
|
|
||||||
self::unsetArgument('--colors=auto');
|
|
||||||
self::unsetArgument('--colors=never');
|
|
||||||
|
|
||||||
if ($isDecorated) {
|
|
||||||
self::setArgument('--colors');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function coverage(): void
|
|
||||||
{
|
|
||||||
if (self::needsCoverage() && ! Coverage::isAvailable()) {
|
|
||||||
Container::getInstance()->get(OutputInterface::class)->writeln(
|
|
||||||
"\n <fg=white;bg=red;options=bold> ERROR </> No code coverage driver is available.</>",
|
|
||||||
);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function needsCoverage(): bool
|
|
||||||
{
|
|
||||||
foreach ($_SERVER['argv'] as $argument) {
|
|
||||||
if(strpos($argument, '--coverage',) === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function unsetArgument(string $argument): bool
|
|
||||||
{
|
|
||||||
if (($key = array_search($argument, $_SERVER['argv'])) !== false) {
|
|
||||||
unset($_SERVER['argv'][$key]);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function setArgument(string $argument, string $value = null): void
|
|
||||||
{
|
|
||||||
$_SERVER['argv'][] = $argument;
|
|
||||||
|
|
||||||
if ($value !== null) {
|
|
||||||
$_SERVER['argv'][] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -9,7 +9,6 @@ use Pest\Actions\AddsTests;
|
|||||||
use Pest\Actions\LoadStructure;
|
use Pest\Actions\LoadStructure;
|
||||||
use Pest\Actions\ValidatesConfiguration;
|
use Pest\Actions\ValidatesConfiguration;
|
||||||
use Pest\Contracts\Plugins\AddsOutput;
|
use Pest\Contracts\Plugins\AddsOutput;
|
||||||
use Pest\Contracts\Plugins\HandlesArguments;
|
|
||||||
use Pest\Plugin\Loader;
|
use Pest\Plugin\Loader;
|
||||||
use Pest\Plugins\Version;
|
use Pest\Plugins\Version;
|
||||||
use Pest\Support\Container;
|
use Pest\Support\Container;
|
||||||
@ -57,23 +56,10 @@ final class Command extends BaseCommand
|
|||||||
*/
|
*/
|
||||||
protected function handleArguments(array $argv): void
|
protected function handleArguments(array $argv): void
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
* First, let's call all plugins that want to handle arguments
|
|
||||||
*/
|
|
||||||
$plugins = Loader::getPlugins(HandlesArguments::class);
|
|
||||||
|
|
||||||
/** @var HandlesArguments $plugin */
|
|
||||||
foreach ($plugins as $plugin) {
|
|
||||||
$argv = $plugin->handleArguments($argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Next, as usual, let's send the console arguments to PHPUnit.
|
|
||||||
*/
|
|
||||||
parent::handleArguments($argv);
|
parent::handleArguments($argv);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Finally, let's validate the configuration. Making
|
* Let's validate the configuration. Making
|
||||||
* sure all options are yet supported by Pest.
|
* sure all options are yet supported by Pest.
|
||||||
*/
|
*/
|
||||||
ValidatesConfiguration::in($this->arguments);
|
ValidatesConfiguration::in($this->arguments);
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Pest\Console\Paratest;
|
|
||||||
|
|
||||||
use ParaTest\Runners\PHPUnit\ExecutableTest;
|
|
||||||
|
|
||||||
class ExecutablePestTest extends ExecutableTest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The number of tests in this file.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
private $testCount;
|
|
||||||
|
|
||||||
public function __construct(string $path, int $testCount, bool $needsCoverage, bool $needsTeamcity, string $tmpDir)
|
|
||||||
{
|
|
||||||
parent::__construct($path, $needsCoverage, $needsTeamcity, $tmpDir);
|
|
||||||
$this->testCount = $testCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTestCount(): int
|
|
||||||
{
|
|
||||||
return $this->testCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function prepareOptions(array $options): array
|
|
||||||
{
|
|
||||||
return $options;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,165 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Pest\Console\Paratest;
|
|
||||||
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use function array_merge;
|
|
||||||
use const DIRECTORY_SEPARATOR;
|
|
||||||
use ParaTest\Runners\PHPUnit\ExecutableTest;
|
|
||||||
use ParaTest\Runners\PHPUnit\Options;
|
|
||||||
use ParaTest\Runners\PHPUnit\WorkerCrashedException;
|
|
||||||
use RuntimeException;
|
|
||||||
use function strlen;
|
|
||||||
use Symfony\Component\Process\PhpExecutableFinder;
|
|
||||||
use Symfony\Component\Process\Process;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
final class PestRunnerWorker
|
|
||||||
{
|
|
||||||
/** @var ExecutableTest */
|
|
||||||
private $executableTest;
|
|
||||||
|
|
||||||
/** @var Process */
|
|
||||||
private $process;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var OutputInterface
|
|
||||||
*/
|
|
||||||
private $output;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<string>
|
|
||||||
*/
|
|
||||||
public static $additionalOutput = [];
|
|
||||||
|
|
||||||
public function __construct(OutputInterface $output, ExecutableTest $executableTest, Options $options, int $token)
|
|
||||||
{
|
|
||||||
$this->output = $output;
|
|
||||||
$this->executableTest = $executableTest;
|
|
||||||
|
|
||||||
$phpFinder = new PhpExecutableFinder();
|
|
||||||
$args = [$phpFinder->find(false)];
|
|
||||||
$args = array_merge($args, $phpFinder->findArguments());
|
|
||||||
|
|
||||||
if (($passthruPhp = $options->passthruPhp()) !== null) {
|
|
||||||
$args = array_merge($args, $passthruPhp);
|
|
||||||
}
|
|
||||||
|
|
||||||
$args = array_merge(
|
|
||||||
$args,
|
|
||||||
$this->executableTest->commandArguments(
|
|
||||||
$this->getPestBinary($options),
|
|
||||||
$options->filtered(),
|
|
||||||
$options->passthru()
|
|
||||||
),
|
|
||||||
['--isInParallel'],
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->process = new Process($args, $options->cwd(), $options->fillEnvWithTokens($token));
|
|
||||||
|
|
||||||
$cmd = $this->process->getCommandLine();
|
|
||||||
$this->assertValidCommandLineLength($cmd);
|
|
||||||
$this->executableTest->setLastCommand($cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExecutableTest(): ExecutableTest
|
|
||||||
{
|
|
||||||
return $this->executableTest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the test by creating a separate process.
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
$this->process->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the process has terminated.
|
|
||||||
*/
|
|
||||||
public function isRunning(): bool
|
|
||||||
{
|
|
||||||
return $this->process->isRunning();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the process and return it's
|
|
||||||
* exit code.
|
|
||||||
*/
|
|
||||||
public function stop(): ?int
|
|
||||||
{
|
|
||||||
$exitCode = $this->process->stop();
|
|
||||||
$this->handleOutput($this->process->getOutput());
|
|
||||||
return $exitCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleOutput(string $output)
|
|
||||||
{
|
|
||||||
$matches = [];
|
|
||||||
preg_match_all("/^\\n/m", $output, $matches, PREG_OFFSET_CAPTURE);
|
|
||||||
|
|
||||||
$overview = substr($output, 0, $matches[0][1][1]);
|
|
||||||
$this->output->write($overview);
|
|
||||||
|
|
||||||
if (count($matches[0]) > 3) {
|
|
||||||
$summarySectionIndex = count($matches[0]) - 2;
|
|
||||||
|
|
||||||
static::$additionalOutput[] = substr(
|
|
||||||
$output,
|
|
||||||
$matches[0][1][1],
|
|
||||||
$matches[0][$summarySectionIndex][1] - $matches[0][1][1],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert that command line length is valid.
|
|
||||||
*
|
|
||||||
* In some situations process command line can became too long when combining different test
|
|
||||||
* cases in single --filter arguments so it's better to show error regarding that to user
|
|
||||||
* and propose him to decrease max batch size.
|
|
||||||
*
|
|
||||||
* @param string $cmd Command line
|
|
||||||
*
|
|
||||||
* @throws RuntimeException on too long command line
|
|
||||||
*
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
*/
|
|
||||||
private function assertValidCommandLineLength(string $cmd): void
|
|
||||||
{
|
|
||||||
if (DIRECTORY_SEPARATOR !== '\\') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// symfony's process wrapper
|
|
||||||
$cmd = 'cmd /V:ON /E:ON /C "(' . $cmd . ')';
|
|
||||||
if (strlen($cmd) > 32767) {
|
|
||||||
throw new RuntimeException('Command line is too long, try to decrease max batch size');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getPestBinary(Options $options): string
|
|
||||||
{
|
|
||||||
$paths = [
|
|
||||||
implode(DIRECTORY_SEPARATOR, [$options->cwd(), 'bin', 'pest']),
|
|
||||||
implode(DIRECTORY_SEPARATOR, [$options->cwd(), 'vendor', 'bin', 'pest']),
|
|
||||||
];
|
|
||||||
|
|
||||||
return file_exists($paths[0]) ? $paths[0] : $paths[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getWorkerCrashedException(?Throwable $previousException = null): WorkerCrashedException
|
|
||||||
{
|
|
||||||
return WorkerCrashedException::fromProcess(
|
|
||||||
$this->process,
|
|
||||||
$this->process->getCommandLine(),
|
|
||||||
$previousException
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,361 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Pest\Console\Paratest;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use ParaTest\Coverage\CoverageMerger;
|
|
||||||
use ParaTest\Coverage\CoverageReporter;
|
|
||||||
use ParaTest\Logging\JUnit\Writer;
|
|
||||||
use ParaTest\Logging\LogInterpreter;
|
|
||||||
use ParaTest\Runners\PHPUnit\EmptyLogFileException;
|
|
||||||
use ParaTest\Runners\PHPUnit\ExecutableTest;
|
|
||||||
use ParaTest\Runners\PHPUnit\Options;
|
|
||||||
use ParaTest\Runners\PHPUnit\ResultPrinter;
|
|
||||||
use ParaTest\Runners\PHPUnit\RunnerInterface;
|
|
||||||
use ParaTest\Runners\PHPUnit\SuiteLoader;
|
|
||||||
use Pest\Factories\TestCaseFactory;
|
|
||||||
use Pest\Support\Coverage;
|
|
||||||
use Pest\TestSuite;
|
|
||||||
use PHPUnit\TextUI\TestRunner;
|
|
||||||
use SebastianBergmann\Timer\Timer;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
|
|
||||||
final class Runner implements RunnerInterface
|
|
||||||
{
|
|
||||||
private const CYCLE_SLEEP = 10000;
|
|
||||||
|
|
||||||
/** @var Options */
|
|
||||||
private $options;
|
|
||||||
|
|
||||||
/** @var ResultPrinter */
|
|
||||||
private $printer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of ExecutableTest objects that have processes
|
|
||||||
* currently running.
|
|
||||||
*
|
|
||||||
* @var PestRunnerWorker[]
|
|
||||||
*/
|
|
||||||
private $running = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of pending ExecutableTest objects that have
|
|
||||||
* yet to run.
|
|
||||||
*
|
|
||||||
* @var ExecutableTest[]
|
|
||||||
*/
|
|
||||||
private $pending = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A tallied exit code that returns the highest exit
|
|
||||||
* code returned out of the entire collection of tests.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
private $exitcode = -1;
|
|
||||||
|
|
||||||
/** @var OutputInterface */
|
|
||||||
private $output;
|
|
||||||
|
|
||||||
/** @var LogInterpreter */
|
|
||||||
private $interpreter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CoverageMerger to hold track of the accumulated coverage.
|
|
||||||
*
|
|
||||||
* @var CoverageMerger|null
|
|
||||||
*/
|
|
||||||
private $coverage = null;
|
|
||||||
|
|
||||||
public function __construct(Options $options, OutputInterface $output)
|
|
||||||
{
|
|
||||||
$this->options = $options;
|
|
||||||
$this->output = $output;
|
|
||||||
$this->interpreter = new LogInterpreter();
|
|
||||||
$this->printer = new ResultPrinter($this->interpreter, $output, $options);
|
|
||||||
|
|
||||||
if (!$this->options->hasCoverage()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->coverage = new CoverageMerger($this->options->coverageTestLimit());
|
|
||||||
}
|
|
||||||
|
|
||||||
final public function run(): void
|
|
||||||
{
|
|
||||||
$this->load(new SuiteLoader($this->options, $this->output));
|
|
||||||
// $this->printer->start();
|
|
||||||
|
|
||||||
$this->doRun();
|
|
||||||
|
|
||||||
$this->complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the collection of pending ExecutableTest objects
|
|
||||||
* to run. If functional mode is enabled $this->pending will
|
|
||||||
* contain a collection of TestMethod objects instead of Suite
|
|
||||||
* objects.
|
|
||||||
*/
|
|
||||||
private function load(SuiteLoader $loader): void
|
|
||||||
{
|
|
||||||
$this->beforeLoadChecks();
|
|
||||||
|
|
||||||
$loader->load();
|
|
||||||
$this->pending = $loader->getSuites();
|
|
||||||
|
|
||||||
$this->loadPestSuite();
|
|
||||||
|
|
||||||
$this->sortPending();
|
|
||||||
|
|
||||||
foreach ($this->pending as $pending) {
|
|
||||||
// $this->printer->addTest($pending);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function sortPending(): void
|
|
||||||
{
|
|
||||||
if ($this->options->orderBy() === Options::ORDER_RANDOM) {
|
|
||||||
mt_srand($this->options->randomOrderSeed());
|
|
||||||
shuffle($this->pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->options->orderBy() !== Options::ORDER_REVERSE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->pending = array_reverse($this->pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finalizes the run process. This method
|
|
||||||
* prints all results, rewinds the log interpreter,
|
|
||||||
* logs any results to JUnit, and cleans up temporary
|
|
||||||
* files.
|
|
||||||
*/
|
|
||||||
private function complete(): void
|
|
||||||
{
|
|
||||||
foreach(PestRunnerWorker::$additionalOutput as $output) {
|
|
||||||
$this->output->write($output);
|
|
||||||
}
|
|
||||||
PestRunnerWorker::$additionalOutput = [];
|
|
||||||
// $this->printer->printResults();
|
|
||||||
$this->log();
|
|
||||||
$this->logCoverage();
|
|
||||||
$readers = $this->interpreter->getReaders();
|
|
||||||
foreach ($readers as $reader) {
|
|
||||||
$reader->removeLog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the highest exit code encountered
|
|
||||||
* throughout the course of test execution.
|
|
||||||
*/
|
|
||||||
final public function getExitCode(): int
|
|
||||||
{
|
|
||||||
return $this->exitcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write output to JUnit format if requested.
|
|
||||||
*/
|
|
||||||
private function log(): void
|
|
||||||
{
|
|
||||||
if (($logJunit = $this->options->logJunit()) === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = $this->options->path() ?? '';
|
|
||||||
|
|
||||||
$writer = new Writer($this->interpreter, $name);
|
|
||||||
$writer->write($logJunit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write coverage to file if requested.
|
|
||||||
*/
|
|
||||||
private function logCoverage(): void
|
|
||||||
{
|
|
||||||
if (!$this->hasCoverage()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$coverageMerger = $this->getCoverage();
|
|
||||||
assert($coverageMerger !== null);
|
|
||||||
$codeCoverage = $coverageMerger->getCodeCoverageObject();
|
|
||||||
assert($codeCoverage !== null);
|
|
||||||
$codeCoverageConfiguration = null;
|
|
||||||
if (($configuration = $this->options->configuration()) !== null) {
|
|
||||||
$codeCoverageConfiguration = $configuration->codeCoverage();
|
|
||||||
}
|
|
||||||
|
|
||||||
$reporter = new CoverageReporter($codeCoverage, $codeCoverageConfiguration);
|
|
||||||
|
|
||||||
$this->output->writeln('');
|
|
||||||
$this->output->write('Generating code coverage report ... ');
|
|
||||||
|
|
||||||
$timer = new Timer();
|
|
||||||
$timer->start();
|
|
||||||
|
|
||||||
if (($coverageClover = $this->options->coverageClover()) !== null) {
|
|
||||||
$reporter->clover($coverageClover);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($coverageCobertura = $this->options->coverageCobertura()) !== null) {
|
|
||||||
$reporter->cobertura($coverageCobertura);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($coverageCrap4j = $this->options->coverageCrap4j()) !== null) {
|
|
||||||
$reporter->crap4j($coverageCrap4j);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($coverageHtml = $this->options->coverageHtml()) !== null) {
|
|
||||||
$reporter->html($coverageHtml);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($coverageText = $this->options->coverageText()) !== null) {
|
|
||||||
if ($coverageText === '') {
|
|
||||||
$this->output->write($reporter->text());
|
|
||||||
} else {
|
|
||||||
file_put_contents($coverageText, $reporter->text());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($coverageXml = $this->options->coverageXml()) !== null) {
|
|
||||||
$reporter->xml($coverageXml);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (($coveragePhp = $this->options->coveragePhp()) !== null) {
|
|
||||||
$reporter->php($coveragePhp);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->output->writeln(
|
|
||||||
sprintf('done [%s]', $timer->stop()->asString())
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($this->options->coveragePhp() && file_exists(Coverage::getPath())) {
|
|
||||||
Coverage::report($this->output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function hasCoverage(): bool
|
|
||||||
{
|
|
||||||
return $this->options->hasCoverage();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getCoverage(): ?CoverageMerger
|
|
||||||
{
|
|
||||||
return $this->coverage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function doRun(): void
|
|
||||||
{
|
|
||||||
$availableTokens = range(1, $this->options->processes());
|
|
||||||
while (count($this->running) > 0 || count($this->pending) > 0) {
|
|
||||||
$this->fillRunQueue($availableTokens);
|
|
||||||
usleep(self::CYCLE_SLEEP);
|
|
||||||
|
|
||||||
$availableTokens = [];
|
|
||||||
foreach ($this->running as $token => $test) {
|
|
||||||
if ($this->testIsStillRunning($test)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
unset($this->running[$token]);
|
|
||||||
$availableTokens[] = $token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fillRunQueue(array $availableTokens)
|
|
||||||
{
|
|
||||||
while (
|
|
||||||
count($this->pending) > 0
|
|
||||||
&& count($this->running) < $this->options->processes()
|
|
||||||
&& ($token = array_shift($availableTokens)) !== null
|
|
||||||
) {
|
|
||||||
$executableTest = array_shift($this->pending);
|
|
||||||
|
|
||||||
$this->running[$token] = new PestRunnerWorker($this->output, $executableTest, $this->options, $token);
|
|
||||||
$this->running[$token]->run();
|
|
||||||
|
|
||||||
if ($this->options->verbosity() < Options::VERBOSITY_VERY_VERBOSE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$cmd = $this->running[$token];
|
|
||||||
$this->output->write("\nExecuting test via: {$cmd->getExecutableTest()->getLastCommand()}\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether or not a test has finished being
|
|
||||||
* executed. If it has, this method also halts a test process - optionally
|
|
||||||
* throwing an exception if a fatal error has occurred -
|
|
||||||
* prints feedback, and updates the overall exit code.
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private function testIsStillRunning(PestRunnerWorker $worker)
|
|
||||||
{
|
|
||||||
if ($worker->isRunning()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->exitcode = max($this->exitcode, (int) $worker->stop());
|
|
||||||
if ($this->options->stopOnFailure() && $this->exitcode > 0) {
|
|
||||||
$this->pending = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
$this->exitcode > 0
|
|
||||||
&& $this->exitcode !== TestRunner::FAILURE_EXIT
|
|
||||||
&& $this->exitcode !== TestRunner::EXCEPTION_EXIT
|
|
||||||
) {
|
|
||||||
throw $worker->getWorkerCrashedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$executableTest = $worker->getExecutableTest();
|
|
||||||
try {
|
|
||||||
// $this->printer->printFeedback($executableTest);
|
|
||||||
} catch (EmptyLogFileException $emptyLogFileException) {
|
|
||||||
throw $worker->getWorkerCrashedException($emptyLogFileException);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->hasCoverage()) {
|
|
||||||
$coverageMerger = $this->getCoverage();
|
|
||||||
assert($coverageMerger !== null);
|
|
||||||
$coverageMerger->addCoverageFromFile($executableTest->getCoverageFileName());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function beforeLoadChecks(): void
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private function loadPestSuite(): void
|
|
||||||
{
|
|
||||||
$pestTestSuite = TestSuite::getInstance();
|
|
||||||
|
|
||||||
$files = array_values(array_map(function (TestCaseFactory $factory): string {
|
|
||||||
return $factory->filename;
|
|
||||||
}, $pestTestSuite->tests->state));
|
|
||||||
|
|
||||||
$occurrences = array_count_values($files);
|
|
||||||
|
|
||||||
$tests = array_values(array_map(function (int $occurrences, string $file) {
|
|
||||||
return new ExecutablePestTest(
|
|
||||||
$file,
|
|
||||||
$occurrences,
|
|
||||||
$this->options->hasCoverage(),
|
|
||||||
$this->options->hasLogTeamcity(),
|
|
||||||
$this->options->tmpDir(),
|
|
||||||
);
|
|
||||||
}, $occurrences, array_keys($occurrences)));
|
|
||||||
|
|
||||||
$this->pending = array_merge($this->pending, $tests);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -74,7 +74,7 @@ final class TestSuite
|
|||||||
public $testPath;
|
public $testPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if this test is running in parallel.
|
* Whether this test is running as part of a parallel suite.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -8,7 +8,7 @@ global $globalHook;
|
|||||||
// HACK: we have to determine our $globalHook->calls baseline. This is because
|
// HACK: we have to determine our $globalHook->calls baseline. This is because
|
||||||
// two other tests are executed before this one due to filename ordering.
|
// two other tests are executed before this one due to filename ordering.
|
||||||
$args = $_SERVER['argv'] ?? [];
|
$args = $_SERVER['argv'] ?? [];
|
||||||
$single = isset($args[1]) && Str::endsWith(__FILE__, $args[1]) || TestSuite::getInstance()->isInParallel;
|
$single = (isset($args[1]) && Str::endsWith(__FILE__, $args[1])) || TestSuite::getInstance()->isInParallel;
|
||||||
$offset = $single ? 0 : 2;
|
$offset = $single ? 0 : 2;
|
||||||
|
|
||||||
uses()->beforeAll(function () use ($globalHook, $offset) {
|
uses()->beforeAll(function () use ($globalHook, $offset) {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Pest\TestSuite;
|
||||||
|
|
||||||
test('visual snapshot of test suite on success', function () {
|
test('visual snapshot of test suite on success', function () {
|
||||||
$testsPath = dirname(__DIR__);
|
$testsPath = dirname(__DIR__);
|
||||||
$snapshot = implode(DIRECTORY_SEPARATOR, [
|
$snapshot = implode(DIRECTORY_SEPARATOR, [
|
||||||
@ -35,4 +37,5 @@ test('visual snapshot of test suite on success', function () {
|
|||||||
expect(implode("\n", $output))->toContain(file_get_contents($snapshot));
|
expect(implode("\n", $output))->toContain(file_get_contents($snapshot));
|
||||||
}
|
}
|
||||||
})->skip(!getenv('REBUILD_SNAPSHOTS') && getenv('EXCLUDE'))
|
})->skip(!getenv('REBUILD_SNAPSHOTS') && getenv('EXCLUDE'))
|
||||||
|
->skip(TestSuite::getInstance()->isInParallel)
|
||||||
->skip(PHP_OS_FAMILY === 'Windows');
|
->skip(PHP_OS_FAMILY === 'Windows');
|
||||||
|
|||||||
Reference in New Issue
Block a user