diff --git a/bin/pest b/bin/pest index bae7b4eb..e99c09bb 100755 --- a/bin/pest +++ b/bin/pest @@ -17,6 +17,7 @@ use Symfony\Component\Console\Output\OutputInterface; $_SERVER['COLLISION_PRINTER'] = 'DefaultPrinter'; $args = $_SERVER['argv']; + $dirty = false; $todo = false; @@ -68,11 +69,11 @@ use Symfony\Component\Console\Output\OutputInterface; // Get $rootPath based on $autoloadPath $rootPath = dirname($autoloadPath, 2); - $argv = new ArgvInput(); + $input = new ArgvInput(); $testSuite = TestSuite::getInstance( $rootPath, - $argv->getParameterOption('--test-directory', (new ConfigLoader($rootPath))->getTestsDirectory()), + $input->getParameterOption('--test-directory', (new ConfigLoader($rootPath))->getTestsDirectory()), ); if ($dirty) { @@ -83,19 +84,13 @@ use Symfony\Component\Console\Output\OutputInterface; $testSuite->tests->addTestCaseMethodFilter(new TodoTestCaseFilter()); } - $isDecorated = $argv->getParameterOption('--colors', 'always') !== 'never'; + $isDecorated = $input->getParameterOption('--colors', 'always') !== 'never'; $output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, $isDecorated); - $container = Container::getInstance(); - $container->add(TestSuite::class, $testSuite); - $container->add(OutputInterface::class, $output); - $container->add(InputInterface::class, $argv); - $container->add(Container::class, $container); + $kernel = Kernel::boot($testSuite, $input, $output); - $kernel = Kernel::boot(); - - $result = $kernel->handle($output, $args); + $result = $kernel->handle($args); $kernel->shutdown(); diff --git a/bin/pest-wrapper.php b/bin/worker.php similarity index 79% rename from bin/pest-wrapper.php rename to bin/worker.php index 7972b206..ca6e84c6 100644 --- a/bin/pest-wrapper.php +++ b/bin/worker.php @@ -7,37 +7,31 @@ use ParaTest\WrapperRunner\WrapperWorker; use Pest\ConfigLoader; use Pest\Kernel; use Pest\Plugins\Actions\CallsHandleArguments; -use Pest\Support\Container; use Pest\TestCaseMethodFilters\TodoTestCaseFilter; use Pest\TestSuite; use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\OutputInterface; $bootPest = (static function (): void { - $argv = new ArgvInput(); - $parentProcessArgv = new ArgvInput(json_decode($_SERVER['PEST_PARALLEL_ARGV'])); + $workerArgv = new ArgvInput(); + $masterArgv = new ArgvInput(json_decode($_SERVER['PEST_PARALLEL_ARGV'])); $rootPath = dirname(PHPUNIT_COMPOSER_INSTALL, 2); - $testSuite = TestSuite::getInstance( - $rootPath, - $argv->getParameterOption('--test-directory', (new ConfigLoader($rootPath))->getTestsDirectory()), - ); + $testSuite = TestSuite::getInstance($rootPath, $workerArgv->getParameterOption( + '--test-directory', + (new ConfigLoader($rootPath))->getTestsDirectory() + )); - if ($parentProcessArgv->hasParameterOption('--todo')) { + if ($masterArgv->hasParameterOption('--todo')) { $testSuite->tests->addTestCaseMethodFilter(new TodoTestCaseFilter()); } + $input = new ArgvInput(); + $output = new ConsoleOutput(OutputInterface::VERBOSITY_NORMAL, true); - $container = Container::getInstance(); - $container->add(TestSuite::class, $testSuite); - $container->add(OutputInterface::class, $output); - $container->add(InputInterface::class, $argv); - $container->add(Container::class, $container); - - Kernel::boot(); + Kernel::boot($testSuite, $input, $output); }); (static function () use ($bootPest): void { @@ -104,10 +98,10 @@ $bootPest = (static function (): void { $testPath = fgets(STDIN); if ($testPath === false || $testPath === WrapperWorker::COMMAND_EXIT) { $application->end(); + exit; } - // It must be a 1 byte string to ensure filesize() is equal to the number of tests executed $exitCode = $application->runTest(trim($testPath)); fwrite($statusFile, (string) $exitCode); diff --git a/composer.json b/composer.json index 66776cef..d4598946 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ ] }, "require-dev": { - "brianium/paratest": "^7.0.4", + "brianium/paratest": "^7.0.5", "pestphp/pest-dev-tools": "^2.4.0", "pestphp/pest-plugin-arch": "^2.0.0", "symfony/process": "^6.2.5" diff --git a/phpstan.neon b/phpstan.neon index cb50f844..a75d68cc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -12,9 +12,10 @@ parameters: reportUnmatchedIgnoredErrors: true ignoreErrors: + - "#Language construct isset\\(\\) should not be used.#" + - "#is not allowed to extend#" - "#with a nullable type declaration#" - "#type mixed is not subtype of native#" - - "#is not allowed to extend#" - "# with null as default value#" - "#has parameter \\$closure with default value.#" - "#has parameter \\$description with default value.#" diff --git a/src/Kernel.php b/src/Kernel.php index 39c0fd66..3914418e 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -11,7 +11,7 @@ use Pest\Plugins\Actions\CallsBoot; use Pest\Plugins\Actions\CallsShutdown; use Pest\Support\Container; use PHPUnit\TextUI\Application; -use PHPUnit\TextUI\Exception; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** @@ -36,7 +36,8 @@ final class Kernel * Creates a new Kernel instance. */ public function __construct( - private readonly Application $application + private readonly Application $application, + private readonly OutputInterface $output, ) { register_shutdown_function(function (): void { if (error_get_last() !== null) { @@ -50,8 +51,16 @@ final class Kernel /** * Boots the Kernel. */ - public static function boot(): self + public static function boot(TestSuite $testSuite, InputInterface $input, OutputInterface $output): self { + $container = Container::getInstance(); + + $container + ->add(TestSuite::class, $testSuite) + ->add(InputInterface::class, $input) + ->add(OutputInterface::class, $output) + ->add(Container::class, $container); + foreach (self::BOOTSTRAPPERS as $bootstrapper) { $bootstrapper = Container::getInstance()->get($bootstrapper); assert($bootstrapper instanceof Bootstrapper); @@ -61,24 +70,25 @@ final class Kernel (new CallsBoot())->__invoke(); - return new self(new Application()); + return new self( + new Application(), + $output, + ); } /** - * Handles the given argv. + * Runs the application, and returns the exit code. * - * @param array $argv - * - * @throws Exception + * @param array $args */ - public function handle(OutputInterface $output, array $argv): int + public function handle(array $args): int { - $argv = (new Plugins\Actions\CallsHandleArguments())->__invoke($argv); + $args = (new Plugins\Actions\CallsHandleArguments())->__invoke($args); try { - $this->application->run($argv); + $this->application->run($args); } catch (NoTestsFound) { - $output->writeln([ + $this->output->writeln([ '', ' INFO No tests found.', '', diff --git a/src/Plugins/Parallel.php b/src/Plugins/Parallel.php index 7a959afe..07a48587 100644 --- a/src/Plugins/Parallel.php +++ b/src/Plugins/Parallel.php @@ -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 $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 $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 $arguments + * @return array + */ + 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([ 'Pest Parallel requires ParaTest to run.', 'Please run 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()); diff --git a/src/Plugins/Parallel/Contracts/HandlersWorkerArguments.php b/src/Plugins/Parallel/Contracts/HandlersWorkerArguments.php new file mode 100644 index 00000000..f953ecb7 --- /dev/null +++ b/src/Plugins/Parallel/Contracts/HandlersWorkerArguments.php @@ -0,0 +1,14 @@ + $arguments + * @return array + */ + public function handleWorkerArguments(array $arguments): array; +} diff --git a/src/Plugins/Parallel/Contracts/HandlesSubprocessArguments.php b/src/Plugins/Parallel/Contracts/HandlesSubprocessArguments.php deleted file mode 100644 index 4fb08457..00000000 --- a/src/Plugins/Parallel/Contracts/HandlesSubprocessArguments.php +++ /dev/null @@ -1,10 +0,0 @@ -output->writeln(' 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 $arguments + * @param array $arguments * @return array */ 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 $arguments + * @param array $arguments * @return array */ 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); } } diff --git a/src/Plugins/Parallel/Handlers/Pest.php b/src/Plugins/Parallel/Handlers/Pest.php index 75c12557..71b24647 100644 --- a/src/Plugins/Parallel/Handlers/Pest.php +++ b/src/Plugins/Parallel/Handlers/Pest.php @@ -1,13 +1,15 @@ */ private array $pending = []; - private int $exitcode = -1; + private int $exitCode = -1; - /** @var array */ + /** @var array */ private array $workers = []; /** @var array */ private array $batches = []; - /** @var list */ + /** @var array */ private array $testresultFiles = []; - /** @var list */ + /** @var array */ private array $coverageFiles = []; - /** @var list */ + /** @var array */ private array $junitFiles = []; - /** @var list */ + /** @var array */ private array $teamcityFiles = []; - /** @var list */ + /** @var array */ private array $testdoxFiles = []; - /** @var non-empty-string[] */ + /** @var array */ 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 $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 $files */ + /** @param array $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 */ 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 $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) { diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index d505b883..d698fbd8 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -54,22 +54,22 @@ final class TestRepository */ public function getFilenames(): array { - $testCases = array_filter($this->testCases, static fn(TestCaseFactory $testCase): bool => $testCase->methodsUsingOnly() !== []); + $testCases = array_filter($this->testCases, static fn (TestCaseFactory $testCase): bool => $testCase->methodsUsingOnly() !== []); if ($testCases === []) { $testCases = $this->testCases; } - return array_values(array_map(static fn(TestCaseFactory $factory): string => $factory->filename, $testCases)); + return array_values(array_map(static fn (TestCaseFactory $factory): string => $factory->filename, $testCases)); } /** * Uses the given `$testCaseClass` on the given `$paths`. * - * @param array $classOrTraits - * @param array $groups - * @param array $paths - * @param array $hooks + * @param array $classOrTraits + * @param array $groups + * @param array $paths + * @param array $hooks */ public function use(array $classOrTraits, array $groups, array $paths, array $hooks): void { @@ -126,18 +126,18 @@ final class TestRepository public function set(TestCaseMethodFactory $method): void { foreach ($this->testCaseFilters as $filter) { - if (!$filter->accept($method->filename)) { + if (! $filter->accept($method->filename)) { return; } } foreach ($this->testCaseMethodFilters as $filter) { - if (!$filter->accept($method)) { + if (! $filter->accept($method)) { return; } } - if (!array_key_exists($method->filename, $this->testCases)) { + if (! array_key_exists($method->filename, $this->testCases)) { $this->testCases[$method->filename] = new TestCaseFactory($method->filename); } @@ -149,12 +149,12 @@ final class TestRepository */ public function makeIfNeeded(string $filename): void { - if (!array_key_exists($filename, $this->testCases)) { + if (! array_key_exists($filename, $this->testCases)) { return; } foreach ($this->testCaseFilters as $filter) { - if (!$filter->accept($filename)) { + if (! $filter->accept($filename)) { return; } } @@ -167,12 +167,12 @@ final class TestRepository */ private function make(TestCaseFactory $testCase): void { - $startsWith = static fn(string $target, string $directory): bool => Str::startsWith($target, $directory . DIRECTORY_SEPARATOR); + $startsWith = static fn (string $target, string $directory): bool => Str::startsWith($target, $directory.DIRECTORY_SEPARATOR); foreach ($this->uses as $path => $uses) { [$classOrTraits, $groups, $hooks] = $uses; - if ((!is_dir($path) && $testCase->filename === $path) || (is_dir($path) && $startsWith($testCase->filename, $path))) { + if ((! is_dir($path) && $testCase->filename === $path) || (is_dir($path) && $startsWith($testCase->filename, $path))) { foreach ($classOrTraits as $class) { /** @var string $class */ if (class_exists($class)) { diff --git a/src/Subscribers/EnsureTeamCityEnabled.php b/src/Subscribers/EnsureTeamCityEnabled.php index 5da91a70..bd6b1bf2 100644 --- a/src/Subscribers/EnsureTeamCityEnabled.php +++ b/src/Subscribers/EnsureTeamCityEnabled.php @@ -21,8 +21,8 @@ final class EnsureTeamCityEnabled implements ConfiguredSubscriber * Creates a new Configured Subscriber instance. */ public function __construct( - private readonly OutputInterface $output, private readonly InputInterface $input, + private readonly OutputInterface $output, private readonly TestSuite $testSuite, ) { } diff --git a/src/Support/Container.php b/src/Support/Container.php index 3afa9818..af15d003 100644 --- a/src/Support/Container.php +++ b/src/Support/Container.php @@ -47,10 +47,14 @@ final class Container /** * Adds the given instance to the container. + * + * @return $this */ - public function add(string $id, object|string $instance): void + public function add(string $id, object|string $instance): self { $this->instances[$id] = $instance; + + return $this; } /** diff --git a/src/Support/StateGenerator.php b/src/Support/StateGenerator.php index 21bd1794..7d06b7b4 100644 --- a/src/Support/StateGenerator.php +++ b/src/Support/StateGenerator.php @@ -23,12 +23,15 @@ final class StateGenerator $state = new State(); foreach ($testResult->testErroredEvents() as $testResultEvent) { - assert($testResultEvent instanceof Errored); - $state->add(\NunoMaduro\Collision\Adapters\Phpunit\TestResult::fromTestCase( - $testResultEvent->test(), - TestResult::FAIL, - $testResultEvent->throwable() - )); + if ($testResultEvent instanceof Errored) { + $state->add(TestResult::fromTestCase( + $testResultEvent->test(), + TestResult::FAIL, + $testResultEvent->throwable() + )); + } else { + $state->add(TestResult::fromBeforeFirstTestMethodErrored($testResultEvent)); + } } foreach ($testResult->testFailedEvents() as $testResultEvent) { diff --git a/tests/.snapshots/Failure.php.inc b/tests/.snapshots/Failure.php.inc index 6e04c2ba..e69de29b 100644 --- a/tests/.snapshots/Failure.php.inc +++ b/tests/.snapshots/Failure.php.inc @@ -1,24 +0,0 @@ -##teamcity[testSuiteStarted name='Tests/tests/Failure' locationHint='file://tests/.tests/Failure.php' flowId='1234'] -##teamcity[testStarted name='it can fail with comparison' locationHint='pest_qn://tests/.tests/Failure.php::it can fail with comparison' flowId='1234'] -##teamcity[testFailed name='it can fail with comparison' message='Failed asserting that true matches expected false.' details='at src/Mixins/Expectation.php:342|nat src/Support/ExpectationPipeline.php:75|nat src/Support/ExpectationPipeline.php:79|nat src/Expectation.php:300|nat tests/.tests/Failure.php:6|nat src/Factories/TestCaseMethodFactory.php:105|nat src/Concerns/Testable.php:262|nat src/Support/ExceptionTrace.php:28|nat src/Concerns/Testable.php:262|nat src/Concerns/Testable.php:217|nat src/Kernel.php:79' type='comparisonFailure' actual='true' expected='false' flowId='1234'] -##teamcity[testFinished name='it can fail with comparison' duration='100000' flowId='1234'] -##teamcity[testStarted name='it can be ignored because of no assertions' locationHint='pest_qn://tests/.tests/Failure.php::it can be ignored because of no assertions' flowId='1234'] -##teamcity[testIgnored name='it can be ignored because of no assertions' message='This test did not perform any assertions' details='' flowId='1234'] -##teamcity[testFinished name='it can be ignored because of no assertions' duration='100000' flowId='1234'] -##teamcity[testStarted name='it can be ignored because it is skipped' locationHint='pest_qn://tests/.tests/Failure.php::it can be ignored because it is skipped' flowId='1234'] -##teamcity[testIgnored name='it can be ignored because it is skipped' message='This test was ignored.' details='' flowId='1234'] -##teamcity[testFinished name='it can be ignored because it is skipped' duration='100000' flowId='1234'] -##teamcity[testStarted name='it can fail' locationHint='pest_qn://tests/.tests/Failure.php::it can fail' flowId='1234'] -##teamcity[testFailed name='it can fail' message='oh noo' details='at tests/.tests/Failure.php:18|nat src/Factories/TestCaseMethodFactory.php:105|nat src/Concerns/Testable.php:262|nat src/Support/ExceptionTrace.php:28|nat src/Concerns/Testable.php:262|nat src/Concerns/Testable.php:217|nat src/Kernel.php:79' flowId='1234'] -##teamcity[testFinished name='it can fail' duration='100000' flowId='1234'] -##teamcity[testStarted name='it is not done yet' locationHint='pest_qn://tests/.tests/Failure.php::it is not done yet' flowId='1234'] -##teamcity[testIgnored name='it is not done yet' message='This test was ignored.' details='' flowId='1234'] -##teamcity[testFinished name='it is not done yet' duration='100000' flowId='1234'] -##teamcity[testStarted name='build this one.' locationHint='pest_qn://tests/.tests/Failure.php::build this one.' flowId='1234'] -##teamcity[testIgnored name='build this one.' message='This test was ignored.' details='' flowId='1234'] -##teamcity[testFinished name='build this one.' duration='100000' flowId='1234'] -##teamcity[testSuiteFinished name='Tests/tests/Failure' flowId='1234'] - - Tests: 2 failed, 1 risky, 2 todos, 1 skipped (2 assertions) - Duration: 1.00s - diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index cce6c952..6ab35fd6 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -896,6 +896,9 @@ PASS Tests\Visual\Help ✓ visual snapshot of help command output + PASS Tests\Visual\Parallel + ✓ parallel + PASS Tests\Visual\SingleTestOrDirectory ✓ allows to run a single test ✓ allows to run a directory @@ -914,4 +917,4 @@ PASS Tests\Visual\Version ✓ visual snapshot of help command output - Tests: 4 incomplete, 4 todos, 18 skipped, 633 passed (1552 assertions) \ No newline at end of file + Tests: 4 incomplete, 4 todos, 18 skipped, 634 passed (1554 assertions) \ No newline at end of file diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php new file mode 100644 index 00000000..5b622870 --- /dev/null +++ b/tests/Visual/Parallel.php @@ -0,0 +1,18 @@ + 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true'], + ); + + $process->run(); + + return preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', $process->getOutput()); +}; + +test('parallel', function () use ($run) { + expect($run())->toContain('Running 650 tests using 3 processes') + ->toContain('Tests: 4 incomplete, 4 todos, 15 skipped, 627 passed (1546 assertions)'); +});