Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1126 bin console task sf #1138

Merged
merged 11 commits into from
Jun 14, 2024
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ feel free to do this, but remember to follow this few simple rules:

## Branching strategy

- __Always__ base your changes on the `master` branch (all new development happens here),
- When you create Pull Request, always select `master` branch as target, otherwise it
- __Always__ base your changes on the latest version `v<version>.x` branch (all new development happens here),
- When you create Pull Request, always select `v<version>.x` branch as target, otherwise it
will be closed (this is selected by default).

## Coverage
Expand Down
2 changes: 2 additions & 0 deletions doc/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ grumphp:
securitychecker_symfony: ~
shell: ~
stylelint: ~
symfony_console: ~
tester: ~
twigcs: ~
twigcsfixer: ~
Expand Down Expand Up @@ -128,6 +129,7 @@ Every task has its own default configuration. It is possible to overwrite the pa
- [Symfony](tasks/securitychecker/symfony.md)
- [Shell](tasks/shell.md)
- [Stylelint](tasks/stylelint.md)
- [Symfony Console](tasks/symfony_console.md)
- [Tester](tasks/tester.md)
- [TwigCs](tasks/twigcs.md)
- [Twig-CS-Fixer](tasks/twigcsfixer.md)
Expand Down
47 changes: 47 additions & 0 deletions doc/tasks/symfony_console.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Symfony Console

Run a symfony console command.

## Composer

Requires the Symfony Console component: [Console Component](https://symfony.com/components/Console)

```bash
composer require symfony/console
```

## Config

The task lives under the `symfony_console` namespace and has following configurable parameters:

```yaml
# grumphp.yml
grumphp:
tasks:
symfony_console:
command: [ "lint:container", "-vvv" ]
```

**command**

Specify the symfony command with defined options and arguments.
Verify the installed console component version for available commands `./bin/console list`

## Note: Multiple Console command tasks

[Run the same task twice with different configuration](../tasks.md#run-the-same-task-twice-with-different-configuration)

Specific running multiple symfony console commands:

```yaml
# grumphp.yml
grumphp:
lint-container:
command: [ "lint:container", "-vvv"]
metadata:
task: symfony_console
lint-yaml:
command: [ "lint:yaml", "path/to/yaml"]
metadata:
task: symfony_console
```
7 changes: 7 additions & 0 deletions resources/config/tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,13 @@ services:
tags:
- {name: grumphp.task, task: stylelint}

GrumPHP\Task\SymfonyConsole:
arguments:
- '@process_builder'
- '@formatter.raw_process'
tags:
- {name: grumphp.task, task: symfony_console}

GrumPHP\Task\Tester:
class:
arguments:
Expand Down
64 changes: 64 additions & 0 deletions src/Task/SymfonyConsole.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace GrumPHP\Task;

use GrumPHP\Formatter\ProcessFormatterInterface;
use GrumPHP\Runner\TaskResult;
use GrumPHP\Runner\TaskResultInterface;
use GrumPHP\Task\Config\ConfigOptionsResolver;
use GrumPHP\Task\Context\ContextInterface;
use GrumPHP\Task\Context\GitPreCommitContext;
use GrumPHP\Task\Context\RunContext;
use Symfony\Component\OptionsResolver\OptionsResolver;

/** @extends AbstractExternalTask<ProcessFormatterInterface> */
class SymfonyConsole extends AbstractExternalTask
{
public static function getConfigurableOptions(): ConfigOptionsResolver
{
return ConfigOptionsResolver::fromOptionsResolver(
(new OptionsResolver())
->setDefaults([
'bin' => './bin/console',
'command' => [],
])
->addAllowedTypes('command', ['string[]'])
->setRequired('command')
);
}

public function canRunInContext(ContextInterface $context): bool
{
return ($context instanceof GitPreCommitContext || $context instanceof RunContext);
}

public function run(ContextInterface $context): TaskResultInterface
{
$config = $this->getConfig()->getOptions();
if (0 === \count($context->getFiles())) {
return TaskResult::createSkipped($this, $context);
}

if (0 === \count($config['command'])) {
return TaskResult::createNonBlockingFailed(
$this,
$context,
'Missing "command" configuration for task "symfony_console".'
);
}

$arguments = $this->processBuilder->createArgumentsForCommand($config['bin']);
$arguments->addArgumentArray('%s', $config['command']);

$process = $this->processBuilder->buildProcess($arguments);
$process->run();

if (!$process->isSuccessful()) {
return TaskResult::createFailed($this, $context, $this->formatter->format($process));
}

return TaskResult::createPassed($this, $context);
}
}
194 changes: 194 additions & 0 deletions test/Unit/Task/SymfonyConsoleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<?php

declare(strict_types=1);

namespace GrumPHPTest\Unit\Task;

use GrumPHP\Runner\TaskResultInterface;
use GrumPHP\Task\Context\ContextInterface;
use GrumPHP\Task\Context\GitPreCommitContext;
use GrumPHP\Task\Context\RunContext;
use GrumPHP\Task\SymfonyConsole;
use GrumPHP\Task\TaskInterface;
use GrumPHP\Test\Task\AbstractExternalTaskTestCase;

final class SymfonyConsoleTest extends AbstractExternalTaskTestCase
{
protected function provideTask(): TaskInterface
{
return new SymfonyConsole(
$this->processBuilder->reveal(),
$this->formatter->reveal()
);
}

public function provideConfigurableOptions(): iterable
{
yield 'default' => [
[],
[
'bin' => './bin/console',
'command' => [],
]
];

yield 'single-command' => [
[
'command' => ['task:run'],
],
[
'bin' => './bin/console',
'command' => [
'task:run'
],
]
];

yield 'array-command' => [
[
'command' => ['task:run', '--env', 'dev', '-vvv'],
],
[
'bin' => './bin/console',
'command' => [
'task:run',
'--env',
'dev',
'-vvv'
],
]
];
}

public function provideRunContexts(): iterable
{
yield 'run-context' => [
true,
$this->mockContext(RunContext::class)
];

yield 'pre-commit-context' => [
true,
$this->mockContext(GitPreCommitContext::class)
];

yield 'other' => [
false,
$this->mockContext()
];
}

public function provideFailsOnStuff(): iterable
{
yield 'exitCode1' => [
[
'command' => ['--version']
],
$this->mockContext(RunContext::class, ['hello.php', 'hello2.php']),
function() {
$process = $this->mockProcess(1);
$this->mockProcessBuilder('./bin/console', $process);
$this->formatter->format($process)->willReturn('nope');
},
'nope'
];
}

public function providePassesOnStuff(): iterable
{
yield 'exitCode0' => [
[
'command' => ['--version']
],
$this->mockContext(RunContext::class, ['hello.php', 'hello2.php']),
function() {
$this->mockProcessBuilder('./bin/console', $this->mockProcess());
}
];
}

public function provideSkipsOnStuff(): iterable
{
yield 'no-files' => [
[],
$this->mockContext(RunContext::class),
function() {
}
];
}

public function provideExternalTaskRuns(): iterable
{
yield 'single-command' => [
[
'command' => ['lint:container']
],
$this->mockContext(RunContext::class, ['hello.php', 'hello2.php']),
'./bin/console',
[
'lint:container',
]
];

yield 'array-command' => [
[
'command' => [
'task:run',
'--env',
'dev',
'-vvv'
]
],
$this->mockContext(RunContext::class, ['hello.php', 'hello2.php']),
'./bin/console',
[
'task:run',
'--env',
'dev',
'-vvv'
]
];
}

/**
* @test
* @dataProvider provideFailsNonBlockingOnStuff
*/
public function it_fails_non_blocking_on_stuff(
array $config,
ContextInterface $context,
callable $configurator,
string $expectedErrorMessage,
): void {
$task = $this->configureTask($config);
\Closure::bind($configurator, $this)($task->getConfig()->getOptions(), $context);
$result = $task->run($context);

self::assertInstanceOf(TaskResultInterface::class, $result);
self::assertSame(TaskResultInterface::NONBLOCKING_FAILED, $result->getResultCode());
self::assertSame($task, $result->getTask());
self::assertSame($context, $result->getContext());
self::assertSame($expectedErrorMessage, $result->getMessage());
}

public function provideFailsNonBlockingOnStuff(): iterable
{
yield 'no-command' => [
[
// missing command
],
$this->mockContext(RunContext::class, ['hello.php', 'hello2.php']),
function() {},
'Missing "command" configuration for task "symfony_console".'
];

yield 'missing-command-data' => [
[
'command' => [], // missing command config
],
$this->mockContext(RunContext::class, ['hello.php', 'hello2.php']),
function() {},
'Missing "command" configuration for task "symfony_console".'
];
}
}