Skip to content

Commit 3708c55

Browse files
authored
Merge pull request #32 from 123inkt/Allow-stdout-as-output-format
Allow stdout as output format
2 parents 43be471 + cab2dfe commit 3708c55

File tree

13 files changed

+469
-54
lines changed

13 files changed

+469
-54
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
run: php -dpcov.enabled=1 -dpcov.exclude="~vendor~" vendor/bin/phpunit --testsuite unit --coverage-clover ./.coverage/coverage.xml
5555

5656
- name: Check coverage
57-
run: php bin/phpfci inspect ./.coverage/coverage.xml ./.coverage/phpfci.xml --exit-code-on-failure
57+
run: php bin/phpfci inspect ./.coverage/coverage.xml --exit-code-on-failure
5858

5959
quality:
6060
name: Quality checks

README.md

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,45 @@ The base directory will be subtracted from the filepaths in coverage.xml
6666

6767
Checkstyle format:
6868
```shell script
69-
php vendor/bin/phpfci inspect coverage.xml reports/checkstyle.xml --report=checkstyle
69+
php vendor/bin/phpfci inspect coverage.xml --reportCheckstyle=reports/checkstyle.xml
7070
```
7171

7272
Gitlab format:
7373
```shell script
74-
php vendor/bin/phpfci inspect coverage.xml reports/gitlab.errors.json --report=gitlab
74+
php vendor/bin/phpfci inspect coverage.xml --reportGitlab=reports/gitlab.errors.json
75+
```
76+
77+
Gitlab format to file and text output to stdout:
78+
```shell script
79+
php vendor/bin/phpfci inspect coverage.xml --reportGitlab=reports/gitlab.errors.json --reportText
80+
```
81+
82+
Text format to stdout:
83+
```shell script
84+
php vendor/bin/phpfci inspect coverage.xml
85+
```
86+
```shell script
87+
php vendor/bin/phpfci inspect coverage.xml --reportText
7588
```
7689

7790
## Command line arguments
7891

79-
| Option | Values | Description |
80-
|---------------------------|------------------------------------------|-------------------------------------------------------------------------|
81-
| `argument 1` | `inspect`, `baseline` | the command to execute. |
82-
| `argument 2` | `coverage.xml` | the phpunit clover coverage input file. |
83-
| `argument 3` | `phpfci.xml` | the output file to write to. |
84-
| `--report=<report-style>` | `gitlab`, `checkstyle` | the output format. If absent will default to console. |
85-
| `--config=<path-to-file>` | `phpfci.xml` | the path to the config file. |
86-
| `--baseDir=<path>` | defaults to directory of the output file | The root directory of the project, will be used to make paths relative. |
87-
| `--exit-code-on-failure` | - | Set exit code to `1` when there are failures. |
92+
| Option | Values | Description |
93+
|-------------------------------|------------------------------------------|-------------------------------------------------------------------------|
94+
| `argument 1` | `inspect`, `baseline` | the command to execute. |
95+
| `argument 2` | `coverage.xml` | the phpunit clover coverage input file. |
96+
| `--reportGitlab=[<file>]` | filepath or if absent stdout | the file (or stdout) to write the gitlab format to. |
97+
| `--reportCheckstyle=[<file>]` | filepath or if absent stdout | the file (or stdout) to write the checkstyle format to. |
98+
| `--reportText=[<file>]` | filepath or if absent stdout | the file (or stdout) to write the checkstyle format to. |
99+
| `--config=<path-to-file>` | `phpfci.xml` | the path to the config file. |
100+
| `--baseDir=<path>` | defaults to directory of the output file | The root directory of the project, will be used to make paths relative. |
101+
| `--exit-code-on-failure` | - | Set exit code to `1` when there are failures. |
102+
103+
Note: if no `--reportGitlab`, `--reportCheckstyle` or `--reportText` is set, it will default to `--reportText=php://stdout`
104+
105+
## Migrating from 1 to 2
106+
The third required argument and `--report` has been removed, and should be replaced by:
107+
`--reportGitlab=<file>`, `--reportCheckstyle=<file>` or `--reportText=<file>`
88108

89109
## About us
90110

src/Command/BaselineCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6666
$failures = (new MetricsAnalyzer($metrics, $config))->analyze();
6767

6868
// write to file
69-
FileUtil::writeFile($outputPath, (new ConfigFileRenderer())->render($failures, $config));
69+
FileUtil::writeTo($outputPath->getPathname(), (new ConfigFileRenderer())->render($failures, $config));
7070

7171
$output->writeln('Config successfully written to: ' . $outputPath->getPathname());
7272

src/Command/InspectCommand.php

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
namespace DigitalRevolution\CodeCoverageInspection\Command;
55

6+
use DigitalRevolution\CodeCoverageInspection\Lib\Config\ConfigFactory;
7+
use DigitalRevolution\CodeCoverageInspection\Lib\Config\ConfigViolation;
68
use DigitalRevolution\CodeCoverageInspection\Lib\IO\DOMDocumentFactory;
79
use DigitalRevolution\CodeCoverageInspection\Lib\IO\InspectionConfigFactory;
810
use DigitalRevolution\CodeCoverageInspection\Lib\IO\MetricsFactory;
@@ -22,17 +24,26 @@
2224
*/
2325
class InspectCommand extends Command
2426
{
25-
private const CONFIG_FILES = ['phpfci.xml', 'phpfci.xml.dist'];
27+
private ConfigFactory $configFactory;
28+
private string $schemaPath;
29+
30+
public function __construct(string $name = null)
31+
{
32+
parent::__construct($name);
33+
$this->configFactory = new ConfigFactory();
34+
$this->schemaPath = dirname(__DIR__, 2) . '/resources/phpfci.xsd';
35+
}
2636

2737
protected function configure(): void
2838
{
2939
$this->setName("inspect")
3040
->setDescription("PHPUnit code coverage inspection")
3141
->addArgument('coverage', InputOption::VALUE_REQUIRED, 'Path to phpunit\'s coverage.xml')
32-
->addArgument('output', InputOption::VALUE_REQUIRED, 'Path to write inspections report file to')
3342
->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Path to configuration file. Optional')
3443
->addOption('baseDir', '', InputOption::VALUE_REQUIRED, 'Base directory from where to determine the relative config paths')
35-
->addOption('report', '', InputOption::VALUE_REQUIRED, 'output format, either checkstyle or gitlab', 'checkstyle')
44+
->addOption('reportGitlab', '', InputOption::VALUE_OPTIONAL, 'Gitlab output format. To file or if absent to stdout', false)
45+
->addOption('reportCheckstyle', '', InputOption::VALUE_OPTIONAL, 'Checkstyle output format. To file or if absent to stdout', false)
46+
->addOption('reportText', '', InputOption::VALUE_OPTIONAL, 'User-friendly text output format. To file or if absent to stdout', false)
3647
->addOption('exit-code-on-failure', '', InputOption::VALUE_NONE, 'If failures, exit with failure exit code');
3748
}
3849

@@ -41,25 +52,20 @@ protected function configure(): void
4152
*/
4253
protected function execute(InputInterface $input, OutputInterface $output): int
4354
{
44-
$configPath = FileUtil::getExistingFile($input->getOption('config') ?? FileUtil::findFilePath((string)getcwd(), self::CONFIG_FILES));
45-
$baseDir = $input->getOption('baseDir') ?? $configPath->getPath();
46-
$coverageFilePath = FileUtil::getExistingFile($input->getArgument('coverage'));
47-
$outputFilePath = FileUtil::getFile($input->getArgument('output'));
48-
$schema = dirname(__DIR__, 2) . '/resources/phpfci.xsd';
49-
50-
if (is_string($baseDir) === false) {
51-
$output->writeln("--baseDir argument is not valid. Expecting string argument");
55+
$inputConfig = $this->configFactory->createInspectConfig($input);
56+
if ($inputConfig instanceof ConfigViolation) {
57+
$output->writeln($inputConfig->getMessage());
5258

5359
return Command::FAILURE;
5460
}
5561

5662
// gather data
57-
$domConfig = DOMDocumentFactory::getValidatedDOMDocument($configPath, $schema);
58-
$config = InspectionConfigFactory::fromDOMDocument($baseDir, $domConfig);
59-
$metrics = MetricsFactory::getFileMetrics(DOMDocumentFactory::getDOMDocument($coverageFilePath));
63+
$domConfig = DOMDocumentFactory::getValidatedDOMDocument($inputConfig->getConfigPath(), $this->schemaPath);
64+
$config = InspectionConfigFactory::fromDOMDocument($inputConfig->getBaseDir(), $domConfig);
65+
$metrics = MetricsFactory::getFileMetrics(DOMDocumentFactory::getDOMDocument($inputConfig->getCoverageFilepath()));
6066

6167
if (count($metrics) === 0) {
62-
$output->writeln("No metrics found in coverage file: " . $coverageFilePath->getPathname());
68+
$output->writeln("No metrics found in coverage file: " . $inputConfig->getCoverageFilepath());
6369

6470
return Command::FAILURE;
6571
}
@@ -68,20 +74,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6874
$failures = (new MetricsAnalyzer($metrics, $config))->analyze();
6975

7076
// write output
71-
switch ($input->getOption('report')) {
72-
case 'checkstyle':
73-
FileUtil::writeFile($outputFilePath, (new CheckStyleRenderer())->render($config, $failures));
74-
break;
75-
case 'gitlab':
76-
FileUtil::writeFile($outputFilePath, (new GitlabErrorRenderer())->render($config, $failures));
77-
break;
78-
default:
79-
$output->write((new TextRenderer())->render($config, $failures));
80-
break;
77+
if ($inputConfig->getReportGitlab() !== null) {
78+
FileUtil::writeTo($inputConfig->getReportGitlab(), (new GitlabErrorRenderer())->render($config, $failures));
79+
}
80+
if ($inputConfig->getReportCheckstyle() !== null) {
81+
FileUtil::writeTo($inputConfig->getReportCheckstyle(), (new CheckStyleRenderer())->render($config, $failures));
82+
}
83+
if ($inputConfig->getReportText() !== null) {
84+
FileUtil::writeTo($inputConfig->getReportText(), (new TextRenderer())->render($config, $failures));
8185
}
8286

8387
// raise exit code on failure
84-
if (count($failures) > 0 && $input->getOption('exit-code-on-failure') !== false) {
88+
if (count($failures) > 0 && $inputConfig->isExitCodeOnFailure()) {
8589
return Command::FAILURE;
8690
}
8791

src/Lib/Config/ConfigFactory.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DigitalRevolution\CodeCoverageInspection\Lib\Config;
5+
6+
use DigitalRevolution\CodeCoverageInspection\Lib\Utility\FileUtil;
7+
use Symfony\Component\Console\Input\InputInterface;
8+
9+
class ConfigFactory
10+
{
11+
private const CONFIG_FILES = ['phpfci.xml', 'phpfci.xml.dist'];
12+
13+
/**
14+
* @return ConfigViolation|InspectConfig
15+
*/
16+
public function createInspectConfig(InputInterface $input)
17+
{
18+
$configPath = FileUtil::getExistingFile($input->getOption('config') ?? FileUtil::findFilePath((string)getcwd(), self::CONFIG_FILES));
19+
$coverageFilepath = FileUtil::getExistingFile($input->getArgument('coverage'));
20+
$baseDir = $input->getOption('baseDir') ?? $configPath->getPath();
21+
22+
if (is_string($baseDir) === false) {
23+
return new ConfigViolation('--base-dir expecting a value string as argument');
24+
}
25+
26+
$reportGitlab = $this->getReport($input, 'reportGitlab');
27+
$reportCheckstyle = $this->getReport($input, 'reportCheckstyle');
28+
$reportText = $this->getReport($input, 'reportText');
29+
30+
if ($reportGitlab instanceof ConfigViolation) {
31+
return $reportGitlab;
32+
}
33+
if ($reportCheckstyle instanceof ConfigViolation) {
34+
return $reportCheckstyle;
35+
}
36+
if ($reportText instanceof ConfigViolation) {
37+
return $reportText;
38+
}
39+
40+
$reports = array_filter([$reportGitlab, $reportCheckstyle, $reportText]);
41+
if (count($reports) === 0) {
42+
$reportText = 'php://stdout';
43+
} elseif ($reports !== array_unique($reports)) {
44+
return new ConfigViolation('Two or more reports output to the same destination');
45+
}
46+
47+
$exitCodeOnFailure = $input->getOption('exit-code-on-failure') !== false;
48+
49+
return new InspectConfig($coverageFilepath, $configPath, $baseDir, $reportGitlab, $reportCheckstyle, $reportText, $exitCodeOnFailure);
50+
}
51+
52+
/**
53+
* @return ConfigViolation|string|null
54+
*/
55+
private function getReport(InputInterface $input, string $option)
56+
{
57+
$report = $input->getOption($option) ?? 'php://stdout';
58+
if ($report !== false && is_string($report) === false) {
59+
return new ConfigViolation('--' . $option . ' expecting the value to absent or string argument');
60+
}
61+
62+
return $report === false ? null : $report;
63+
}
64+
}

src/Lib/Config/ConfigViolation.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DigitalRevolution\CodeCoverageInspection\Lib\Config;
5+
6+
class ConfigViolation
7+
{
8+
private string $message;
9+
10+
public function __construct(string $message)
11+
{
12+
$this->message = $message;
13+
}
14+
15+
public function getMessage(): string
16+
{
17+
return $this->message;
18+
}
19+
}

src/Lib/Config/InspectConfig.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace DigitalRevolution\CodeCoverageInspection\Lib\Config;
5+
6+
use SplFileInfo;
7+
8+
class InspectConfig
9+
{
10+
private SplFileInfo $coverageFilepath;
11+
private SplFileInfo $configPath;
12+
private string $baseDir;
13+
private ?string $reportGitlab;
14+
private ?string $reportCheckstyle;
15+
private ?string $reportText;
16+
private bool $exitCodeOnFailure;
17+
18+
public function __construct(
19+
SplFileInfo $coverageFilepath,
20+
SplFileInfo $configPath,
21+
string $baseDir,
22+
?string $reportGitlab,
23+
?string $reportCheckstyle,
24+
?string $reportText,
25+
bool $exitCodeOnFailure
26+
) {
27+
$this->coverageFilepath = $coverageFilepath;
28+
$this->configPath = $configPath;
29+
$this->baseDir = $baseDir;
30+
$this->reportGitlab = $reportGitlab;
31+
$this->reportCheckstyle = $reportCheckstyle;
32+
$this->reportText = $reportText;
33+
$this->exitCodeOnFailure = $exitCodeOnFailure;
34+
}
35+
36+
public function getCoverageFilepath(): SplFileInfo
37+
{
38+
return $this->coverageFilepath;
39+
}
40+
41+
public function getConfigPath(): SplFileInfo
42+
{
43+
return $this->configPath;
44+
}
45+
46+
public function getBaseDir(): string
47+
{
48+
return $this->baseDir;
49+
}
50+
51+
public function getReportGitlab(): ?string
52+
{
53+
return $this->reportGitlab;
54+
}
55+
56+
public function getReportCheckstyle(): ?string
57+
{
58+
return $this->reportCheckstyle;
59+
}
60+
61+
public function getReportText(): ?string
62+
{
63+
return $this->reportText;
64+
}
65+
66+
public function isExitCodeOnFailure(): bool
67+
{
68+
return $this->exitCodeOnFailure;
69+
}
70+
}

src/Lib/Utility/FileUtil.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,19 @@ public static function getExistingFile($path): SplFileInfo
6363
return $fileInfo;
6464
}
6565

66-
public static function writeFile(SplFileInfo $file, string $content): void
66+
public static function writeTo(string $filepath, string $content): void
6767
{
68-
$dir = $file->getPath();
69-
if ($dir !== '' && !is_dir($dir) && !mkdir($dir, 0777, true) && !is_dir($dir)) {
68+
$dir = dirname($filepath);
69+
if (str_starts_with($filepath, "php://") === false && $dir !== '' && !is_dir($dir) && !mkdir($dir, 0777, true) && !is_dir($dir)) {
7070
// @codeCoverageIgnoreStart
7171
throw new RuntimeException(sprintf('Failed to create directory "%s".', $dir));
7272
// @codeCoverageIgnoreEnd
7373
}
7474

75-
$success = @file_put_contents($file->getPathname(), $content);
75+
$success = @file_put_contents($filepath, $content);
7676
if ($success === false) {
7777
// @codeCoverageIgnoreStart
78-
throw new RuntimeException(sprintf('Failed to write to file "%s".', $file->getPathname()));
78+
throw new RuntimeException(sprintf('Failed to write to file "%s".', $filepath));
7979
// @codeCoverageIgnoreEnd
8080
}
8181
}

tests/Functional/Command/InspectCommand/InspectCommandTest.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use org\bovigo\vfs\vfsStreamFile;
1111
use PHPUnit\Framework\TestCase;
1212
use Symfony\Component\Console\Command\Command;
13+
use Symfony\Component\Console\Exception\ExceptionInterface;
1314
use Symfony\Component\Console\Input\ArgvInput;
1415
use Symfony\Component\Console\Output\ConsoleOutput;
1516

@@ -24,9 +25,11 @@ protected function setUp(): void
2425
}
2526

2627
/**
28+
* @param string[] $flags
29+
*
2730
* @coversNothing
2831
* @dataProvider dataProvider
29-
* @throws Exception
32+
* @throws Exception|ExceptionInterface
3033
*/
3134
public function testInspectCommand(array $flags, int $exitStatus): void
3235
{
@@ -39,7 +42,9 @@ public function testInspectCommand(array $flags, int $exitStatus): void
3942

4043
// prepare command
4144
$command = new InspectCommand();
42-
$input = new ArgvInput(array_merge(['phpfci', '--config', $configPath, '--baseDir', $baseDir, $coveragePath, $output], $flags));
45+
$input = new ArgvInput(
46+
array_merge(['phpfci', '--config', $configPath, '--baseDir', $baseDir, $coveragePath, '--reportCheckstyle', $output], $flags)
47+
);
4348
$output = new ConsoleOutput();
4449

4550
// run test case
@@ -54,6 +59,9 @@ public function testInspectCommand(array $flags, int $exitStatus): void
5459
static::assertSame($expected, $result);
5560
}
5661

62+
/**
63+
* @return array<string, array{0: string[], 1:int}>
64+
*/
5765
public function dataProvider(): array
5866
{
5967
return [

0 commit comments

Comments
 (0)