hasArgument('--shard', $arguments)) { return $arguments; } // @phpstan-ignore-next-line $input = new ArgvInput($arguments); ['index' => $index, 'total' => $total] = self::getShard($input); $arguments = $this->popArgument("--shard=$index/$total", $this->popArgument('--shard', $this->popArgument( "$index/$total", $arguments, ))); /** @phpstan-ignore-next-line */ $tests = $this->allTests($arguments); $testsToRun = (array_chunk($tests, max(1, (int) ceil(count($tests) / $total))))[$index - 1] ?? []; return [...$arguments, '--filter', $this->buildFilterArgument($testsToRun)]; } /** * Returns all tests that the test suite would run. * * @param list $arguments * @return list */ private function allTests(array $arguments): array { $output = (new Process([ 'php', ...$this->removeParallelArguments($arguments), '--list-tests', ]))->mustRun()->getOutput(); preg_match_all('/ - (?:P\\\\)?(Tests\\\\[^:]+)::/', $output, $matches); return array_values(array_unique($matches[1])); } /** * @param array $arguments * @return array */ private function removeParallelArguments(array $arguments): array { return array_filter($arguments, fn (string $argument): bool => ! in_array($argument, ['--parallel', '-p'], strict: true)); } /** * Builds the filter argument for the given tests to run. */ private function buildFilterArgument(mixed $testsToRun): string { return addslashes(implode('|', $testsToRun)); } /** * Returns the shard information. * * @return array{index: int, total: int} */ public static function getShard(InputInterface $input): array { if ($input->hasParameterOption('--'.self::SHARD_OPTION)) { $shard = $input->getParameterOption('--'.self::SHARD_OPTION); } else { $shard = null; } if (! is_string($shard) || ! preg_match('/^\d+\/\d+$/', $shard)) { throw new InvalidOption('The [--shard] option must be in the format "index/total".'); } [$index, $total] = explode('/', $shard); if (! is_numeric($index) || ! is_numeric($total)) { throw new InvalidOption('The [--shard] option must be in the format "index/total".'); } if ($index <= 0 || $total <= 0 || $index > $total) { throw new InvalidOption('The [--shard] option index must be a non-negative integer less than the total number of shards.'); } $index = (int) $index; $total = (int) $total; return [ 'index' => $index, 'total' => $total, ]; } }