diff --git a/doc/commands.md b/doc/commands.md index 3d5de5e1c..25d16b4c9 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -42,3 +42,12 @@ php ./vendor/bin/grumphp run --testsuite=mytestsuite This command can also be used for continious integration. More information about the testsuites can be found in the [testsuites documentation](testsuites.md). + +If you want to run only a subset of the configured tasks, you can run the command with the `--tasks` option: + +```sh +php ./vendor/bin/grumphp run --tasks=task1,task2 +``` + +The `--tasks` value has to be a comma-separated string of task names that match the keys in the `tasks` section +of the `grumphp.yml` file. See [#580](https://github.com/phpro/grumphp/issues/580) for a more exhaustive explanation. diff --git a/spec/Collection/TasksCollectionSpec.php b/spec/Collection/TasksCollectionSpec.php index f5581e285..db1974e6f 100644 --- a/spec/Collection/TasksCollectionSpec.php +++ b/spec/Collection/TasksCollectionSpec.php @@ -62,6 +62,46 @@ function it_can_filter_by_empty_testsuite(TaskInterface $task1, TaskInterface $t $tasks[1]->shouldBe($task2); } + function it_can_filter_by_task_names(TaskInterface $task1, TaskInterface $task2) + { + $task1->getName()->willReturn('task1'); + $task2->getName()->willReturn('task2'); + $tasks = ['task1']; + + $result = $this->filterByTaskNames($tasks); + $result->shouldBeAnInstanceOf(TasksCollection::class); + $result->count()->shouldBe(1); + $tasks = $result->toArray(); + $tasks[0]->shouldBe($task1); + } + + function it_can_filter_by_duplicate_task_names(TaskInterface $task1, TaskInterface $task2) + { + $task1->getName()->willReturn('task1'); + $task2->getName()->willReturn('task2'); + $tasks = ['task1', 'task1']; + + $result = $this->filterByTaskNames($tasks); + $result->shouldBeAnInstanceOf(TasksCollection::class); + $result->count()->shouldBe(1); + $tasks = $result->toArray(); + $tasks[0]->shouldBe($task1); + } + + function it_can_filter_by_empty_task_names(TaskInterface $task1, TaskInterface $task2) + { + $task1->getName()->willReturn('task1'); + $task2->getName()->willReturn('task2'); + $tasks = []; + + $result = $this->filterByTaskNames($tasks); + $result->shouldBeAnInstanceOf(TasksCollection::class); + $result->count()->shouldBe(2); + $tasks = $result->toArray(); + $tasks[0]->shouldBe($task1); + $tasks[1]->shouldBe($task2); + } + function it_should_sort_on_priority(TaskInterface $task1, TaskInterface $task2, TaskInterface $task3, GrumPHP $grumPHP) { $this->beConstructedWith([$task1, $task2, $task3]); diff --git a/spec/Runner/TaskRunnerContextSpec.php b/spec/Runner/TaskRunnerContextSpec.php index 0f6a2b22a..9ddb8902a 100644 --- a/spec/Runner/TaskRunnerContextSpec.php +++ b/spec/Runner/TaskRunnerContextSpec.php @@ -37,6 +37,20 @@ function it_has_no_test_suite(ContextInterface $context) $this->getTestSuite()->shouldBe(null); } + function it_has_no_tasks() + { + $this->hasTasks()->shouldBe(false); + $this->getTasks()->shouldBe([]); + } + + function it_has_tasks(ContextInterface $context, TestSuiteInterface $testSuite) + { + $tasks = ["task_1"]; + $this->beConstructedWith($context, $testSuite, $tasks); + $this->hasTasks()->shouldBe(true); + $this->getTasks()->shouldBe($tasks); + } + function it_knows_to_skip_the_success_message() { $this->skipSuccessOutput()->shouldBe(false); diff --git a/spec/Runner/TaskRunnerSpec.php b/spec/Runner/TaskRunnerSpec.php index ec6b4db05..ec90f239e 100644 --- a/spec/Runner/TaskRunnerSpec.php +++ b/spec/Runner/TaskRunnerSpec.php @@ -35,6 +35,7 @@ public function let( $runnerContext->getTaskContext()->willReturn($taskContext); $runnerContext->getTestSuite()->willReturn(null); + $runnerContext->getTasks()->willReturn([]); $task1->getName()->willReturn('task1'); $task1->canRunInContext($taskContext)->willReturn(true); diff --git a/spec/Util/StrSpec.php b/spec/Util/StrSpec.php new file mode 100644 index 000000000..fcf1577c8 --- /dev/null +++ b/spec/Util/StrSpec.php @@ -0,0 +1,24 @@ +shouldBe(true); + $this::containsOneOf('a;randomText-written by me', ['Text'])->shouldBe(true); + + $this::containsOneOf('a;randomText-written by me', ['this does not exist'])->shouldBe(false); + $this::containsOneOf('a;randomText-written by me', ['text'])->shouldBe(false); + } + + function it_should_split_a_string_by_a_delimiter_and_result_in_a_unique_list() + { + $this::explodeWithCleanup(',', ' a random,list, of things ')->shouldBe([ + 'a random', 'list', 'of things' + ]); + } +} diff --git a/src/Collection/TasksCollection.php b/src/Collection/TasksCollection.php index f359af68c..84307f8c1 100644 --- a/src/Collection/TasksCollection.php +++ b/src/Collection/TasksCollection.php @@ -26,7 +26,7 @@ public function filterByContext(ContextInterface $context): self public function filterByTestSuite(TestSuiteInterface $testSuite = null): self { if (null === $testSuite) { - return new self($this->toArray()); + return $this; } return $this->filter(function (TaskInterface $task) use ($testSuite) { @@ -34,6 +34,20 @@ public function filterByTestSuite(TestSuiteInterface $testSuite = null): self }); } + /** + * @param string[] $tasks + */ + public function filterByTaskNames(array $tasks): self + { + if (empty($tasks)) { + return $this; + } + + return $this->filter(function (TaskInterface $task) use ($tasks) { + return \in_array($task->getName(), $tasks, true); + }); + } + /** * This method sorts the tasks by highest priority first. */ diff --git a/src/Console/Command/RunCommand.php b/src/Console/Command/RunCommand.php index 97783b414..5e8ee9632 100644 --- a/src/Console/Command/RunCommand.php +++ b/src/Console/Command/RunCommand.php @@ -11,6 +11,7 @@ use GrumPHP\Locator\RegisteredFiles; use GrumPHP\Runner\TaskRunnerContext; use GrumPHP\Task\Context\RunContext; +use GrumPHP\Util\Str; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -51,6 +52,13 @@ protected function configure() 'Specify which testsuite you want to run.', null ); + $this->addOption( + 'tasks', + null, + InputOption::VALUE_REQUIRED, + 'Specify which tasks you want to run (comma separated). Example --tasks=task1,task2', + null + ); } public function execute(InputInterface $input, OutputInterface $output) @@ -58,9 +66,12 @@ public function execute(InputInterface $input, OutputInterface $output) $files = $this->getRegisteredFiles(); $testSuites = $this->grumPHP->getTestSuites(); + $tasks = Str::explodeWithCleanup(',', $input->getOption("tasks") ?? ''); + $context = new TaskRunnerContext( new RunContext($files), - (bool) $input->getOption('testsuite') ? $testSuites->getRequired($input->getOption('testsuite')) : null + (bool) $input->getOption('testsuite') ? $testSuites->getRequired($input->getOption('testsuite')) : null, + $tasks ); return $this->taskRunner()->run($output, $context); diff --git a/src/Runner/TaskRunner.php b/src/Runner/TaskRunner.php index 6eab357e6..99c773e11 100644 --- a/src/Runner/TaskRunner.php +++ b/src/Runner/TaskRunner.php @@ -69,6 +69,7 @@ public function run(TaskRunnerContext $runnerContext): TaskResultCollection $tasks = $this->tasks ->filterByContext($runnerContext->getTaskContext()) ->filterByTestSuite($runnerContext->getTestSuite()) + ->filterByTaskNames($runnerContext->getTasks()) ->sortByPriority($this->grumPHP); $taskResults = new TaskResultCollection(); diff --git a/src/Runner/TaskRunnerContext.php b/src/Runner/TaskRunnerContext.php index 627c2869b..5cbe773c8 100644 --- a/src/Runner/TaskRunnerContext.php +++ b/src/Runner/TaskRunnerContext.php @@ -22,15 +22,21 @@ class TaskRunnerContext /** * @var null|TestSuiteInterface */ - private $testSuite = null; + private $testSuite; /** - * TaskRunnerContext constructor. + * @var string[] */ - public function __construct(ContextInterface $taskContext, TestSuiteInterface $testSuite = null) - { + private $tasks; + + public function __construct( + ContextInterface $taskContext, + TestSuiteInterface $testSuite = null, + array $tasks = [] + ) { $this->taskContext = $taskContext; $this->testSuite = $testSuite; + $this->tasks = $tasks; } public function getTaskContext(): ContextInterface @@ -54,7 +60,7 @@ public function hasTestSuite(): bool } /** - * @return TestSuiteInterface|null + * @return null|TestSuiteInterface */ public function getTestSuite() { @@ -62,10 +68,23 @@ public function getTestSuite() } /** - * @param TestSuiteInterface|null $testSuite + * @param null|TestSuiteInterface $testSuite */ public function setTestSuite(TestSuiteInterface $testSuite) { $this->testSuite = $testSuite; } + + /** + * @return string[] + */ + public function getTasks(): array + { + return $this->tasks; + } + + public function hasTasks(): bool + { + return !empty($this->tasks); + } } diff --git a/src/Util/Str.php b/src/Util/Str.php index 9ec961dd4..aeb198ad4 100644 --- a/src/Util/Str.php +++ b/src/Util/Str.php @@ -2,7 +2,7 @@ namespace GrumPHP\Util; -class Str +final class Str { /** * String contains one of the provided needles @@ -17,4 +17,15 @@ public static function containsOneOf(string $haystack, array $needles): bool return false; } + + /** + * Split $value on ",", trim the individual parts and + * de-deduplicate the remaining values + */ + public static function explodeWithCleanup(string $delimiter, string $value): array + { + return array_unique(array_map(function (string $value) { + return trim($value); + }, explode($delimiter, $value))); + } }