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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"sebastian/phpcpd": "Lets GrumPHP find duplicated code.",
"squizlabs/php_codesniffer": "Lets GrumPHP sniff on your code.",
"sstalle/php7cc": "Lets GrumPHP check PHP 5.3 - 5.6 code compatibility with PHP 7.",
"symfony/console": "Lets GrumPHP run Symfony Console commands.",
"symfony/phpunit-bridge": "Lets GrumPHP run your unit tests with the phpunit-bridge of Symfony.",
"symplify/easy-coding-standard": "Lets GrumPHP check coding standard.",
"vimeo/psalm": "Lets GrumPHP discover errors in your code without running it.",
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
51 changes: 51 additions & 0 deletions doc/tasks/symfony_console.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 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" ]
```

**Note:** GrumPHP will use the default Symfony console path `./bin/console` from the project root.

### Parameters

**command**

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

## 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 ('' === \implode('', $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);
}
}
3 changes: 1 addition & 2 deletions src/Test/Task/AbstractTaskTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use GrumPHP\Collection\FilesCollection;
use GrumPHP\Runner\TaskResult;
use GrumPHP\Runner\TaskResultInterface;
use GrumPHP\Task\Config\EmptyTaskConfig;
use GrumPHP\Task\Config\Metadata;
use GrumPHP\Task\Config\TaskConfig;
Expand Down Expand Up @@ -47,7 +46,7 @@ protected function setUp(): void
public function it_contains_configurable_options(array $input, ?array $output): void
{
if (!$output) {
self::expectException(ExceptionInterface::class);
$this->expectException(ExceptionInterface::class);
}

$resolver = $this->task::getConfigurableOptions();
Expand Down
203 changes: 203 additions & 0 deletions test/Unit/Task/SymfonyConsoleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<?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 'missing-command' => [
[
// missing command
],
$this->mockContext(RunContext::class, ['hello.php', 'hello2.php']),
function() {},
'Missing "command" configuration for task "symfony_console".'
];

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

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