master

laravel/framework

Last updated at: 29/12/2023 09:25

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();
    }
}