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..6edcabbec 100644 --- a/spec/Collection/TasksCollectionSpec.php +++ b/spec/Collection/TasksCollectionSpec.php @@ -62,6 +62,33 @@ 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->filterByTaskName($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->filterByTaskName($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..53024a401 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) + { + $tasks = ["task_1"]; + $this->beConstructedWith($context, null, $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/src/Collection/TasksCollection.php b/src/Collection/TasksCollection.php index 9be604bde..5481c47ba 100644 --- a/src/Collection/TasksCollection.php +++ b/src/Collection/TasksCollection.php @@ -39,6 +39,21 @@ public function filterByTestSuite(TestSuiteInterface $testSuite = null) }); } + /** + * @param string[] $tasks + * + * @return TasksCollection + */ + public function filterByTaskName($tasks) + { + return $this->filter(function (TaskInterface $task) use ($tasks) { + if (empty($tasks)) { + return true; + } + return in_array($task->getName(), $tasks); + }); + } + /** * This method sorts the tasks by highest priority first. * diff --git a/src/Console/Command/RunCommand.php b/src/Console/Command/RunCommand.php index 3161121f4..da2bfba7f 100644 --- a/src/Console/Command/RunCommand.php +++ b/src/Console/Command/RunCommand.php @@ -53,6 +53,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 + ); } /** @@ -66,9 +73,12 @@ public function execute(InputInterface $input, OutputInterface $output) $files = $this->getRegisteredFiles(); $testSuites = $this->grumPHP->getTestSuites(); + $tasks = $this->parseCommaSeparatedOption($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); @@ -97,4 +107,25 @@ protected function paths() { return $this->getHelper(PathsHelper::HELPER_NAME); } + + /** + * Split $value on ",", trim the individual parts and + * de-deduplicate the remaining values + * + * @param string $value + * @return string[] + */ + protected function parseCommaSeparatedOption($value) + { + $stringValues = explode(",", $value); + $parsedValues = []; + foreach ($stringValues as $k => $v) { + $v = trim($v); + if (empty($v)) { + continue; + } + $parsedValues[$v] = $v; + } + return $parsedValues; + } } diff --git a/src/Runner/TaskRunner.php b/src/Runner/TaskRunner.php index 5c8d5bf71..314d450c2 100644 --- a/src/Runner/TaskRunner.php +++ b/src/Runner/TaskRunner.php @@ -78,6 +78,7 @@ public function run(TaskRunnerContext $runnerContext) $tasks = $this->tasks ->filterByContext($runnerContext->getTaskContext()) ->filterByTestSuite($runnerContext->getTestSuite()) + ->filterByTaskName($runnerContext->getTasks()) ->sortByPriority($this->grumPHP); $taskResults = new TaskResultCollection(); diff --git a/src/Runner/TaskRunnerContext.php b/src/Runner/TaskRunnerContext.php index 7a964e583..5e24ec06c 100644 --- a/src/Runner/TaskRunnerContext.php +++ b/src/Runner/TaskRunnerContext.php @@ -22,16 +22,23 @@ class TaskRunnerContext */ private $testSuite = null; + /** + * @var string[] + */ + private $tasks = null; + /** * TaskRunnerContext constructor. * - * @param ContextInterface $taskContext + * @param ContextInterface $taskContext * @param TestSuiteInterface $testSuite + * @param string[]|null $tasks */ - public function __construct(ContextInterface $taskContext, TestSuiteInterface $testSuite = null) + public function __construct(ContextInterface $taskContext, TestSuiteInterface $testSuite = null, $tasks = null) { $this->taskContext = $taskContext; $this->testSuite = $testSuite; + $this->tasks = $tasks ?? []; } /** @@ -81,4 +88,20 @@ public function setTestSuite(TestSuiteInterface $testSuite) { $this->testSuite = $testSuite; } + + /** + * @return string[] + */ + public function getTasks() + { + return $this->tasks; + } + + /** + * @return bool + */ + public function hasTasks() + { + return !empty($this->tasks); + } } diff --git a/test/Console/Command/RunCommandTest.php b/test/Console/Command/RunCommandTest.php new file mode 100644 index 000000000..1f9ad4d03 --- /dev/null +++ b/test/Console/Command/RunCommandTest.php @@ -0,0 +1,78 @@ +createMock(GrumPHP::class); + /** + * @var RegisteredFiles $registeredFiles + */ + $registeredFiles = $this->createMock(RegisteredFiles::class); + + $command = new RunCommand($grumPhp, $registeredFiles); + $method = new \ReflectionMethod($command, "parseCommaSeparatedOption"); + $method->setAccessible(true); + + $actual = $method->invoke($command, $valueString); + + $this->assertEquals($expected, $actual); + } + + + public function parses_comma_separated_options_dataProvider() + { + return [ + "default" => [ + "valueString" => "foo,bar", + "expected" => [ + "foo" => "foo", + "bar" => "bar" + ], + ], + "trims values" => [ + "valueString" => "foo , bar", + "expected" => [ + "foo" => "foo", + "bar" => "bar" + ], + ], + "deduplicates values" => [ + "valueString" => "foo,bar,bar", + "expected" => [ + "foo" => "foo", + "bar" => "bar" + ], + ], + "null" => [ + "valueString" => null, + "expected" => [], + ], + "empty" => [ + "valueString" => "", + "expected" => [], + ], + "empty after trim" => [ + "valueString" => " ", + "expected" => [], + ], + ]; + } +}