RunsInParallel.php
TLDR
The RunsInParallel
trait is used for running tests in parallel. It provides methods to create a new test runner instance, set the application and runner resolvers, run the test suite, get the exit code, and create the application.
Methods
__construct($options, OutputInterface $output)
Creates a new test runner instance. The method takes two arguments: $options
and $output
. It initializes the options
property, checks the type of $output
, resolves the runner class, and creates a new runner instance.
resolveApplicationUsing($resolver)
Set the application resolver callback. The method takes a closure argument $resolver
and sets it to the applicationResolver
property.
resolveRunnerUsing($resolver)
Set the runner resolver callback. The method takes a closure argument $resolver
and sets it to the runnerResolver
property.
execute(): int
Runs the test suite. It handles the PHP configuration, calls the setup process callbacks, runs the test using the runner, and calls the teardown process callbacks. It returns the exit code.
getExitCode(): int
Returns the highest exit code encountered throughout the course of test execution.
forEachProcess($callback)
Applies the given callback for each process. The method takes a callable argument $callback
and applies it to each process defined in the options.
createApplication()
Creates the application. It resolves the application resolver callback and returns the created application. If the trait \Tests\CreatesApplication
exists, it uses it to create the application. Otherwise, it searches for the bootstrap/app.php
or .laravel/app.php
file and creates the application using it.
<?php
namespace Illuminate\Testing\Concerns;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Facades\ParallelTesting;
use Illuminate\Testing\ParallelConsoleOutput;
use RuntimeException;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
trait RunsInParallel
{
/**
* The application resolver callback.
*
* @var \Closure|null
*/
protected static $applicationResolver;
/**
* The runner resolver callback.
*
* @var \Closure|null
*/
protected static $runnerResolver;
/**
* The original test runner options.
*
* @var \ParaTest\Runners\PHPUnit\Options|\ParaTest\Options
*/
protected $options;
/**
* The output instance.
*
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output;
/**
* The original test runner.
*
* @var \ParaTest\Runners\PHPUnit\RunnerInterface|\ParaTest\RunnerInterface
*/
protected $runner;
/**
* Creates a new test runner instance.
*
* @param \ParaTest\Runners\PHPUnit\Options|\ParaTest\Options $options
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return void
*/
public function __construct($options, OutputInterface $output)
{
$this->options = $options;
if ($output instanceof ConsoleOutput) {
$output = new ParallelConsoleOutput($output);
}
$runnerResolver = static::$runnerResolver ?: function ($options, OutputInterface $output) {
$wrapperRunnerClass = class_exists(\ParaTest\WrapperRunner\WrapperRunner::class)
? \ParaTest\WrapperRunner\WrapperRunner::class
: \ParaTest\Runners\PHPUnit\WrapperRunner::class;
return new $wrapperRunnerClass($options, $output);
};
$this->runner = $runnerResolver($options, $output);
}
/**
* Set the application resolver callback.
*
* @param \Closure|null $resolver
* @return void
*/
public static function resolveApplicationUsing($resolver)
{
static::$applicationResolver = $resolver;
}
/**
* Set the runner resolver callback.
*
* @param \Closure|null $resolver
* @return void
*/
public static function resolveRunnerUsing($resolver)
{
static::$runnerResolver = $resolver;
}
/**
* Runs the test suite.
*
* @return int
*/
public function execute(): int
{
$phpHandlerClass = class_exists(\PHPUnit\TextUI\Configuration\PhpHandler::class)
? \PHPUnit\TextUI\Configuration\PhpHandler::class
: \PHPUnit\TextUI\XmlConfiguration\PhpHandler::class;
$configuration = $this->options instanceof \ParaTest\Options
? $this->options->configuration
: $this->options->configuration();
(new $phpHandlerClass)->handle($configuration->php());
$this->forEachProcess(function () {
ParallelTesting::callSetUpProcessCallbacks();
});
try {
$potentialExitCode = $this->runner->run();
} finally {
$this->forEachProcess(function () {
ParallelTesting::callTearDownProcessCallbacks();
});
}
return $potentialExitCode === null
? $this->getExitCode()
: $potentialExitCode;
}
/**
* Returns the highest exit code encountered throughout the course of test execution.
*
* @return int
*/
public function getExitCode(): int
{
return $this->runner->getExitCode();
}
/**
* Apply the given callback for each process.
*
* @param callable $callback
* @return void
*/
protected function forEachProcess($callback)
{
$processes = $this->options instanceof \ParaTest\Options
? $this->options->processes
: $this->options->processes();
collect(range(1, $processes))->each(function ($token) use ($callback) {
tap($this->createApplication(), function ($app) use ($callback, $token) {
ParallelTesting::resolveTokenUsing(fn () => $token);
$callback($app);
})->flush();
});
}
/**
* Creates the application.
*
* @return \Illuminate\Contracts\Foundation\Application
*
* @throws \RuntimeException
*/
protected function createApplication()
{
$applicationResolver = static::$applicationResolver ?: function () {
if (trait_exists(\Tests\CreatesApplication::class)) {
$applicationCreator = new class
{
use \Tests\CreatesApplication;
};
return $applicationCreator->createApplication();
} elseif (file_exists($path = getcwd().'/bootstrap/app.php') ||
file_exists($path = getcwd().'/.laravel/app.php')) {
$app = require $path;
$app->make(Kernel::class)->bootstrap();
return $app;
}
throw new RuntimeException('Parallel Runner unable to resolve application.');
};
return $applicationResolver();
}
}