From 89016d585b532a2a5f506742e95d1f54fcfe14db Mon Sep 17 00:00:00 2001 From: "A. B. M. Mahmudul Hasan" Date: Mon, 4 May 2026 16:46:14 +0600 Subject: [PATCH] initializer --- bin/run-task.php | 4 ++- composer.json | 1 + src/Composer/CiCommand.php | 2 +- src/Composer/CommandProvider.php | 3 +- src/Composer/InfocyphCommand.php | 3 +- src/Support/Cli.php | 2 +- src/Support/Runner.php | 14 ++++++++-- tests/Support/RunnerTest.php | 47 ++++++++++++++++++++++++++++++++ 8 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 tests/Support/RunnerTest.php diff --git a/bin/run-task.php b/bin/run-task.php index e30b0da..30da8b5 100644 --- a/bin/run-task.php +++ b/bin/run-task.php @@ -27,7 +27,8 @@ return; } -$exitCode = (new Runner($output))->run(taskCommands($task)); +$failFast = !in_array($task, ['tests', 'tests:details'], true); +$exitCode = (new Runner($output, $failFast))->run(taskCommands($task)); if ($exitCode !== 0) { throw new RuntimeException(sprintf('Task "%s" failed with exit code %d.', $task, $exitCode)); @@ -53,6 +54,7 @@ function taskCommands(string $task): array 'test:static' => TaskCatalog::staticAnalysis(), 'test:syntax' => TaskCatalog::syntax(), 'tests' => TaskCatalog::testAll(), + 'tests:details' => TaskCatalog::testDetails(), default => throw new InvalidArgumentException(sprintf('Unknown task "%s".', $task)), }; } diff --git a/composer.json b/composer.json index 082c3bf..08379af 100644 --- a/composer.json +++ b/composer.json @@ -99,6 +99,7 @@ "ic:test:syntax": "@php bin/run-task.php test:syntax", "ic:tests": "@php bin/run-task.php tests", "ic:tests:all": "@ic:tests", + "ic:tests:details": "@php bin/run-task.php tests:details", "ic:tests:parallel": "@php bin/run-task.php tests:parallel" } } diff --git a/src/Composer/CiCommand.php b/src/Composer/CiCommand.php index 556bf07..d86a9c5 100644 --- a/src/Composer/CiCommand.php +++ b/src/Composer/CiCommand.php @@ -28,7 +28,7 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { if ((bool) $input->getOption('prefer-lowest')) { - return (new Runner($output))->run(TaskCatalog::ci(true)); + return (new Runner($output, false))->run(TaskCatalog::ci(true)); } return (new ParallelRunner($output))->run(TaskCatalog::syntax(), TaskCatalog::testParallel()); diff --git a/src/Composer/CommandProvider.php b/src/Composer/CommandProvider.php index eb1f4de..fda8b80 100644 --- a/src/Composer/CommandProvider.php +++ b/src/Composer/CommandProvider.php @@ -79,10 +79,11 @@ private function commandRows(): array private function infocyphCommands(string $group): array { $commands = []; + $failFast = $group !== 'quality'; foreach ($this->commandRows() as [$rowGroup, $name, $description, $taskMethod]) { if ($rowGroup === $group) { - $commands[] = new InfocyphCommand($name, $description, $this->tasks($taskMethod)); + $commands[] = new InfocyphCommand($name, $description, $this->tasks($taskMethod), false, [], $failFast); } } diff --git a/src/Composer/InfocyphCommand.php b/src/Composer/InfocyphCommand.php index 4ce974a..9897348 100644 --- a/src/Composer/InfocyphCommand.php +++ b/src/Composer/InfocyphCommand.php @@ -22,6 +22,7 @@ public function __construct( private readonly array $tasks, private readonly bool $parallel = false, private readonly array $preflightTasks = [], + private readonly bool $failFast = true, ) { parent::__construct($commandName); } @@ -40,6 +41,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int return (new ParallelRunner($output))->run($this->preflightTasks, $this->tasks); } - return (new Runner($output))->run($this->tasks); + return (new Runner($output, $this->failFast))->run($this->tasks); } } diff --git a/src/Support/Cli.php b/src/Support/Cli.php index c594685..632df76 100644 --- a/src/Support/Cli.php +++ b/src/Support/Cli.php @@ -37,7 +37,7 @@ private function ci(array $args): int return (new ParallelRunner($output))->run(TaskCatalog::syntax(), TaskCatalog::testParallel()); } - return (new Runner($output))->run(TaskCatalog::ci(true)); + return (new Runner($output, false))->run(TaskCatalog::ci(true)); } private function help(): int diff --git a/src/Support/Runner.php b/src/Support/Runner.php index 9392a6d..c248900 100644 --- a/src/Support/Runner.php +++ b/src/Support/Runner.php @@ -13,6 +13,7 @@ final class Runner public function __construct( private readonly OutputInterface $output, + private readonly bool $failFast = true, ) {} /** @@ -24,6 +25,7 @@ public function run(array $tasks): int $isFirstTask = true; $results = []; + $failureExitCode = 0; foreach ($tasks as $task) { $result = $this->runTask($task, $isFirstTask); @@ -31,15 +33,21 @@ public function run(array $tasks): int $results[] = $result; if ($result['status'] === 'FAIL') { - QualitySummary::write($results); + if ($failureExitCode === 0) { + $failureExitCode = $result['exit_code']; + } - return $result['exit_code']; + if ($this->failFast) { + QualitySummary::write($results); + + return $result['exit_code']; + } } } QualitySummary::write($results); - return 0; + return $failureExitCode; } private function detectComposerVersion(): string diff --git a/tests/Support/RunnerTest.php b/tests/Support/RunnerTest.php new file mode 100644 index 0000000..06882e3 --- /dev/null +++ b/tests/Support/RunnerTest.php @@ -0,0 +1,47 @@ +run([ + [PHP_BINARY, '-r', 'fwrite(STDERR, "first failed\n"); exit(3);'], + [PHP_BINARY, '-r', 'file_put_contents('.var_export($marker, true).', "ran");'], + ]); + + expect($exitCode)->toBe(3) + ->and(is_file($marker))->toBeTrue(); + + if (is_file($marker)) { + unlink($marker); + } +}); + +it('stops at first failure when fail-fast is enabled', function (): void { + $marker = runnerTempPath('stopped.txt'); + $output = new BufferedOutput(); + $runner = new Runner($output, true); + + $exitCode = $runner->run([ + [PHP_BINARY, '-r', 'fwrite(STDERR, "first failed\n"); exit(4);'], + [PHP_BINARY, '-r', 'file_put_contents('.var_export($marker, true).', "ran");'], + ]); + + expect($exitCode)->toBe(4) + ->and(is_file($marker))->toBeFalse(); + + if (is_file($marker)) { + unlink($marker); + } +});