diff --git a/UPGRADE.md b/UPGRADE.md index b094d61..03cd1f7 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -54,6 +54,8 @@ - `::isValueRequiresQuoting()` - `::fetchItems()` can now return an `iterable` instead of `array` - `::getItemName()` can now take `null` for an unknown number of items +- Ensures that if an item is given, then the processing is done in the main + process. An item also cannot be passed to a child process (via the argument). ## New extension points diff --git a/src/Input/ParallelizationInput.php b/src/Input/ParallelizationInput.php index 25f20c5..760a1b9 100644 --- a/src/Input/ParallelizationInput.php +++ b/src/Input/ParallelizationInput.php @@ -19,9 +19,11 @@ use Symfony\Component\Console\Input\InputOption; use Webmozart\Assert\Assert; use Webmozarts\Console\Parallelization\CpuCoreCounter; +use function get_class; use function gettype; use function is_int; use function is_numeric; +use function is_object; use function sprintf; final class ParallelizationInput @@ -70,14 +72,23 @@ public static function fromInput(InputInterface $input): self { /** @var string|null $numberOfProcesses */ $numberOfProcesses = $input->getOption(self::PROCESSES_OPTION); - /** @var string|null $item */ + /** @var mixed|null $item */ $item = $input->getArgument(self::ITEM_ARGUMENT); + $hasItem = null !== $item; /** @var bool $mainProcess */ $mainProcess = $input->getOption(self::MAIN_PROCESS_OPTION); /** @var bool $isChild */ $isChild = $input->getOption(self::CHILD_OPTION); + if ($hasItem) { + $item = self::validateItem($item); + // When there is a single item passed, we do not want to spawn + // child processes. + $mainProcess = true; + } + if ($mainProcess) { + // TODO: add this to the logger $validatedNumberOfProcesses = 1; } else { $validatedNumberOfProcesses = null !== $numberOfProcesses @@ -85,21 +96,6 @@ public static function fromInput(InputInterface $input): self : static fn () => CpuCoreCounter::getNumberOfCpuCores(); } - $hasItem = null !== $item; - - if ($hasItem && !is_numeric($item)) { - // Safeguard in case an invalid type is accidentally passed in tests when invoking the - // command directly - Assert::string( - $item, - sprintf( - 'Invalid item type. Expected a string, got "%s".', - // TODO: change to get_debug_type() once dropping PHP 7.4 - gettype($input), - ), - ); - } - if ($isChild) { Assert::false( $hasItem, @@ -113,7 +109,7 @@ public static function fromInput(InputInterface $input): self return new self( $mainProcess, $validatedNumberOfProcesses, - $hasItem ? (string) $item : null, + $hasItem ? $item : null, $isChild, ); } @@ -178,6 +174,29 @@ public function isChildProcess(): bool return $this->childProcess; } + /** + * @param mixed $item + */ + private static function validateItem($item): string + { + if (is_numeric($item)) { + return (string) $item; + } + + // Safeguard in case an invalid type is accidentally passed in tests when invoking the + // command directly + Assert::string( + $item, + sprintf( + 'Invalid item type. Expected a string, got "%s".', + // TODO: use get_debug_type when dropping PHP 7.4 support + is_object($item) ? get_class($item) : gettype($item), + ), + ); + + return $item; + } + /** * @return positive-int */ diff --git a/tests/Fixtures/Command/NoSubProcessCommand.php b/tests/Fixtures/Command/NoSubProcessCommand.php index 8404642..7470ca5 100644 --- a/tests/Fixtures/Command/NoSubProcessCommand.php +++ b/tests/Fixtures/Command/NoSubProcessCommand.php @@ -91,7 +91,7 @@ protected function runSingleCommand(string $item, InputInterface $input, OutputI protected function getItemName(?int $count): string { - return 0 === $count ? 'item' : 'items'; + return 1 === $count ? 'item' : 'items'; } protected function createLogger(OutputInterface $output): Logger diff --git a/tests/Input/ParallelizationInputTest.php b/tests/Input/ParallelizationInputTest.php index 2f623b3..215c43c 100644 --- a/tests/Input/ParallelizationInputTest.php +++ b/tests/Input/ParallelizationInputTest.php @@ -112,7 +112,7 @@ public function test_it_can_be_instantiated_from_an_input_with_an_invalid_item() self::bindInput($input); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid item type. Expected a string, got "object".'); + $this->expectExceptionMessage('Invalid item type. Expected a string, got "stdClass".'); ParallelizationInput::fromInput($input); } @@ -196,8 +196,8 @@ public static function inputProvider(): iterable yield 'item passed' => [ new StringInput('item15'), new ParallelizationInput( - false, - $findNumberOfProcesses, + true, + 1, 'item15', false, ), @@ -206,8 +206,8 @@ public static function inputProvider(): iterable yield 'integer item passed' => [ new ArrayInput(['item' => 10]), new ParallelizationInput( - false, - $findNumberOfProcesses, + true, + 1, '10', false, ), @@ -216,8 +216,8 @@ public static function inputProvider(): iterable yield 'float item passed' => [ new ArrayInput(['item' => -.5]), new ParallelizationInput( - false, - $findNumberOfProcesses, + true, + 1, '-0.5', false, ), diff --git a/tests/Integration/ParallelizationIntegrationTest.php b/tests/Integration/ParallelizationIntegrationTest.php index 122ba87..9e02262 100644 --- a/tests/Integration/ParallelizationIntegrationTest.php +++ b/tests/Integration/ParallelizationIntegrationTest.php @@ -102,6 +102,36 @@ public function test_it_can_run_the_command_without_sub_processes(): void self::assertSame($expected, $actual, $actual); } + public function test_it_processes_the_item_in_the_main_process_if_an_item_is_passed(): void + { + $commandTester = $this->noSubProcessCommandTester; + + $commandTester->execute( + [ + 'command' => 'test:no-subprocess', + 'item' => 'item0', + ], + ['interactive' => true], + ); + + // TODO: note that the "in 1 process is incorrect here..." + $expected = <<<'EOF' + Processing 1 item, batches of 2, 1 batch + + 0/1 [>---------------------------] 0% 10 secs/10 secs 10.0 MiB + 1/1 [============================] 100% 10 secs/10 secs 10.0 MiB + + Processed 1 item. + + EOF; + + $actual = self::normalizeIntermediateFixedProgressBars( + $this->getOutput($commandTester), + ); + + self::assertSame($expected, $actual, $actual); + } + public function test_it_uses_a_sub_process_if_only_one_process_is_used(): void { $commandTester = $this->noSubProcessCommandTester;