Skip to content

Commit d37bcd0

Browse files
authored
Ensure stdout is valid XML for junit format (#129)
1 parent 701b485 commit d37bcd0

9 files changed

+148
-104
lines changed

bin/composer-dependency-analyser

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ spl_autoload_register(static function (string $class) use ($psr4Prefix): void {
2727
/** @var non-empty-string $cwd */
2828
$cwd = getcwd();
2929

30-
$printer = new Printer();
31-
$initializer = new Initializer($cwd, $printer);
30+
$stdOutPrinter = new Printer(STDOUT);
31+
$stdErrPrinter = new Printer(STDERR);
32+
$initializer = new Initializer($cwd, $stdOutPrinter, $stdErrPrinter);
3233
$stopwatch = new Stopwatch();
3334

3435
try {
@@ -49,7 +50,7 @@ try {
4950
InvalidConfigException |
5051
InvalidCliException $e
5152
) {
52-
$printer->printLine("\n<red>{$e->getMessage()}</red>" . PHP_EOL);
53+
$stdErrPrinter->printLine("\n<red>{$e->getMessage()}</red>" . PHP_EOL);
5354
exit(255);
5455
}
5556

src/Initializer.php

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,22 @@ class Initializer
5555
/**
5656
* @var Printer
5757
*/
58-
private $printer;
58+
private $stdOutPrinter;
59+
60+
/**
61+
* @var Printer
62+
*/
63+
private $stdErrPrinter;
5964

6065
public function __construct(
6166
string $cwd,
62-
Printer $printer
67+
Printer $stdOutPrinter,
68+
Printer $stdErrPrinter
6369
)
6470
{
65-
$this->printer = $printer;
6671
$this->cwd = $cwd;
72+
$this->stdOutPrinter = $stdOutPrinter;
73+
$this->stdErrPrinter = $stdErrPrinter;
6774
}
6875

6976
/**
@@ -85,7 +92,7 @@ public function initConfiguration(
8592
}
8693

8794
if (is_file($configPath)) {
88-
$this->printer->printLine('<gray>Using config</gray> ' . $configPath);
95+
$this->stdErrPrinter->printLine('<gray>Using config</gray> ' . $configPath);
8996

9097
try {
9198
$config = (static function () use ($configPath) {
@@ -190,17 +197,17 @@ public function initComposerClassLoaders(): array
190197
$loaders = ClassLoader::getRegisteredLoaders();
191198

192199
if (count($loaders) > 1) {
193-
$this->printer->printLine("\nDetected multiple class loaders:");
200+
$this->stdErrPrinter->printLine("\nDetected multiple class loaders:");
194201

195202
foreach ($loaders as $vendorDir => $_) {
196-
$this->printer->printLine(" • <gray>$vendorDir</gray>");
203+
$this->stdErrPrinter->printLine(" • <gray>$vendorDir</gray>");
197204
}
198205

199-
$this->printer->printLine('');
206+
$this->stdErrPrinter->printLine('');
200207
}
201208

202209
if (count($loaders) === 0) {
203-
$this->printer->printLine("\nNo composer class loader detected!\n");
210+
$this->stdErrPrinter->printLine("\nNo composer class loader detected!\n");
204211
}
205212

206213
return $loaders;
@@ -215,7 +222,7 @@ public function initCliOptions(string $cwd, array $argv): CliOptions
215222
$cliOptions = (new Cli($cwd, $argv))->getProvidedOptions();
216223

217224
if ($cliOptions->help !== null) {
218-
$this->printer->printLine(self::$help);
225+
$this->stdOutPrinter->printLine(self::$help);
219226
throw new InvalidCliException(''); // just exit
220227
}
221228

@@ -229,11 +236,11 @@ public function initFormatter(CliOptions $options): ResultFormatter
229236
{
230237
switch ($options->format) {
231238
case 'junit':
232-
return new JunitFormatter($this->cwd, $this->printer);
239+
return new JunitFormatter($this->cwd, $this->stdOutPrinter);
233240

234241
case 'console':
235242
case null:
236-
return new ConsoleFormatter($this->cwd, $this->printer);
243+
return new ConsoleFormatter($this->cwd, $this->stdOutPrinter);
237244

238245
default:
239246
throw new InvalidConfigException("Invalid format option provided, allowed are 'console' or 'junit'.");

src/Printer.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace ShipMonk\ComposerDependencyAnalyser;
44

5+
use LogicException;
56
use function array_keys;
67
use function array_values;
8+
use function fwrite;
79
use function str_replace;
810
use const PHP_EOL;
911

@@ -21,14 +23,31 @@ class Printer
2123
'</gray>' => "\033[0m",
2224
];
2325

26+
/**
27+
* @var resource
28+
*/
29+
private $resource;
30+
31+
/**
32+
* @param resource $resource
33+
*/
34+
public function __construct($resource)
35+
{
36+
$this->resource = $resource;
37+
}
38+
2439
public function printLine(string $string): void
2540
{
26-
echo $this->colorize($string) . PHP_EOL;
41+
$this->print($string . PHP_EOL);
2742
}
2843

2944
public function print(string $string): void
3045
{
31-
echo $this->colorize($string);
46+
$result = fwrite($this->resource, $this->colorize($string));
47+
48+
if ($result === false) {
49+
throw new LogicException('Could not write to output stream.');
50+
}
3251
}
3352

3453
private function colorize(string $string): string

tests/BinTest.php

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,30 @@ public function test(): void
2323
$okOutput = 'No composer issues found';
2424
$helpOutput = 'Usage:';
2525

26+
$usingConfig = 'Using config';
27+
2628
$junitOutput = '<?xml version="1.0" encoding="UTF-8"?><testsuites></testsuites>';
2729

28-
$this->runCommand('php bin/composer-dependency-analyser', $rootDir, 0, $okOutput);
29-
$this->runCommand('php bin/composer-dependency-analyser --verbose', $rootDir, 0, $okOutput);
30-
$this->runCommand('php ../bin/composer-dependency-analyser', $testsDir, 255, $noComposerJsonError);
30+
$this->runCommand('php bin/composer-dependency-analyser', $rootDir, 0, $okOutput, $usingConfig);
31+
$this->runCommand('php bin/composer-dependency-analyser --verbose', $rootDir, 0, $okOutput, $usingConfig);
32+
$this->runCommand('php ../bin/composer-dependency-analyser', $testsDir, 255, null, $noComposerJsonError);
3133
$this->runCommand('php bin/composer-dependency-analyser --help', $rootDir, 255, $helpOutput);
3234
$this->runCommand('php ../bin/composer-dependency-analyser --help', $testsDir, 255, $helpOutput);
33-
$this->runCommand('php bin/composer-dependency-analyser --composer-json=composer.json', $rootDir, 0, $okOutput);
34-
$this->runCommand('php bin/composer-dependency-analyser --composer-json=composer.lock', $rootDir, 255, $noPackagesError);
35-
$this->runCommand('php bin/composer-dependency-analyser --composer-json=README.md', $rootDir, 255, $parseError);
36-
$this->runCommand('php ../bin/composer-dependency-analyser --composer-json=composer.json', $testsDir, 255, $noComposerJsonError);
37-
$this->runCommand('php ../bin/composer-dependency-analyser --composer-json=../composer.json --config=../composer-dependency-analyser.php', $testsDir, 0, $okOutput);
38-
$this->runCommand('php bin/composer-dependency-analyser --composer-json=composer.json --format=console', $rootDir, 0, $okOutput);
39-
$this->runCommand('php bin/composer-dependency-analyser --composer-json=composer.json --format=junit', $rootDir, 0, $junitOutput);
35+
$this->runCommand('php bin/composer-dependency-analyser --composer-json=composer.json', $rootDir, 0, $okOutput, $usingConfig);
36+
$this->runCommand('php bin/composer-dependency-analyser --composer-json=composer.lock', $rootDir, 255, null, $noPackagesError);
37+
$this->runCommand('php bin/composer-dependency-analyser --composer-json=README.md', $rootDir, 255, null, $parseError);
38+
$this->runCommand('php ../bin/composer-dependency-analyser --composer-json=composer.json', $testsDir, 255, null, $noComposerJsonError);
39+
$this->runCommand('php ../bin/composer-dependency-analyser --composer-json=../composer.json --config=../composer-dependency-analyser.php', $testsDir, 0, $okOutput, $usingConfig);
40+
$this->runCommand('php bin/composer-dependency-analyser --composer-json=composer.json --format=console', $rootDir, 0, $okOutput, $usingConfig);
41+
$this->runCommand('php bin/composer-dependency-analyser --composer-json=composer.json --format=junit', $rootDir, 0, $junitOutput, $usingConfig);
4042
}
4143

4244
private function runCommand(
4345
string $command,
4446
string $cwd,
4547
int $expectedExitCode,
46-
string $expectedOutputContains
48+
?string $expectedOutputContains = null,
49+
?string $expectedErrorContains = null
4750
): void
4851
{
4952
$desc = [
@@ -74,11 +77,21 @@ private function runCommand(
7477
$extraInfo
7578
);
7679

77-
self::assertStringContainsString(
78-
$expectedOutputContains,
79-
$output,
80-
$extraInfo
81-
);
80+
if ($expectedOutputContains !== null) {
81+
self::assertStringContainsString(
82+
$expectedOutputContains,
83+
$output,
84+
$extraInfo
85+
);
86+
}
87+
88+
if ($expectedErrorContains !== null) {
89+
self::assertStringContainsString(
90+
$expectedErrorContains,
91+
$errorOutput,
92+
$extraInfo
93+
);
94+
}
8295
}
8396

8497
}

tests/ConsoleFormatterTest.php

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,24 @@
22

33
namespace ShipMonk\ComposerDependencyAnalyser;
44

5-
use Closure;
6-
use PHPUnit\Framework\TestCase;
75
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
86
use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;
97
use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore;
108
use ShipMonk\ComposerDependencyAnalyser\Result\AnalysisResult;
119
use ShipMonk\ComposerDependencyAnalyser\Result\ConsoleFormatter;
10+
use ShipMonk\ComposerDependencyAnalyser\Result\ResultFormatter;
1211
use ShipMonk\ComposerDependencyAnalyser\Result\SymbolUsage;
13-
use function ob_get_clean;
14-
use function ob_start;
15-
use function preg_replace;
16-
use function str_replace;
1712

18-
class ConsoleFormatterTest extends TestCase
13+
class ConsoleFormatterTest extends FormatterTest
1914
{
2015

2116
public function testPrintResult(): void
2217
{
2318
// editorconfig-checker-disable
24-
$formatter = new ConsoleFormatter('/app', new Printer());
25-
26-
$noIssuesOutput = $this->captureAndNormalizeOutput(static function () use ($formatter): void {
19+
$noIssuesOutput = $this->getFormatterNormalizedOutput(static function (ResultFormatter $formatter): void {
2720
$formatter->format(new AnalysisResult(2, 0.123, [], [], [], [], [], [], [], []), new CliOptions(), new Configuration());
2821
});
29-
$noIssuesButUnusedIgnores = $this->captureAndNormalizeOutput(static function () use ($formatter): void {
22+
$noIssuesButUnusedIgnores = $this->getFormatterNormalizedOutput(static function (ResultFormatter $formatter): void {
3023
$formatter->format(new AnalysisResult(2, 0.123, [], [], [], [], [], [], [], [new UnusedErrorIgnore(ErrorType::SHADOW_DEPENDENCY, null, null)]), new CliOptions(), new Configuration());
3124
});
3225

@@ -79,10 +72,10 @@ public function testPrintResult(): void
7972
[]
8073
);
8174

82-
$regularOutput = $this->captureAndNormalizeOutput(static function () use ($formatter, $analysisResult): void {
75+
$regularOutput = $this->getFormatterNormalizedOutput(static function ($formatter) use ($analysisResult): void {
8376
$formatter->format($analysisResult, new CliOptions(), new Configuration());
8477
});
85-
$verboseOutput = $this->captureAndNormalizeOutput(static function () use ($formatter, $analysisResult): void {
78+
$verboseOutput = $this->getFormatterNormalizedOutput(static function ($formatter) use ($analysisResult): void {
8679
$options = new CliOptions();
8780
$options->verbose = true;
8881
$formatter->format($analysisResult, $options, new Configuration());
@@ -206,24 +199,9 @@ public function testPrintResult(): void
206199
// editorconfig-checker-enable
207200
}
208201

209-
private function removeColors(string $output): string
210-
{
211-
return (string) preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', $output);
212-
}
213-
214-
/**
215-
* @param Closure(): void $closure
216-
*/
217-
private function captureAndNormalizeOutput(Closure $closure): string
218-
{
219-
ob_start();
220-
$closure();
221-
return $this->normalizeEol((string) ob_get_clean());
222-
}
223-
224-
private function normalizeEol(string $string): string
202+
protected function createFormatter(Printer $printer): ResultFormatter
225203
{
226-
return str_replace("\r\n", "\n", $string);
204+
return new ConsoleFormatter('/app', $printer);
227205
}
228206

229207
}

tests/FormatterTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace ShipMonk\ComposerDependencyAnalyser;
4+
5+
use Closure;
6+
use PHPUnit\Framework\TestCase;
7+
use ShipMonk\ComposerDependencyAnalyser\Result\ResultFormatter;
8+
use function fopen;
9+
use function preg_replace;
10+
use function str_replace;
11+
use function stream_get_contents;
12+
13+
abstract class FormatterTest extends TestCase
14+
{
15+
16+
abstract protected function createFormatter(Printer $printer): ResultFormatter;
17+
18+
/**
19+
* @param Closure(ResultFormatter): void $closure
20+
*/
21+
protected function getFormatterNormalizedOutput(Closure $closure): string
22+
{
23+
$stream = fopen('php://memory', 'w');
24+
self::assertNotFalse($stream);
25+
26+
$printer = new Printer($stream);
27+
$formatter = $this->createFormatter($printer);
28+
29+
$closure($formatter);
30+
return $this->normalizeEol((string) stream_get_contents($stream, -1, 0));
31+
}
32+
33+
protected function normalizeEol(string $string): string
34+
{
35+
return str_replace("\r\n", "\n", $string);
36+
}
37+
38+
protected function removeColors(string $output): string
39+
{
40+
return (string) preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', $output);
41+
}
42+
43+
}

tests/InitializerTest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function testInitConfiguration(): void
2626
$options = new CliOptions();
2727
$options->ignoreUnknownClasses = true;
2828

29-
$initializer = new Initializer(__DIR__, $printer);
29+
$initializer = new Initializer(__DIR__, $printer, $printer);
3030
$config = $initializer->initConfiguration($options, $composerJson);
3131

3232
self::assertEquals([new PathToScan(__DIR__, false)], $config->getPathsToScan());
@@ -44,7 +44,7 @@ public function testInitComposerJson(): void
4444
$options = new CliOptions();
4545
$options->composerJson = 'sample.json';
4646

47-
$initializer = new Initializer($cwd, $printer);
47+
$initializer = new Initializer($cwd, $printer, $printer);
4848
$composerJson = $initializer->initComposerJson($options);
4949

5050
self::assertSame(
@@ -70,7 +70,7 @@ public function testInitComposerJsonWithAbsolutePath(): void
7070
$options = new CliOptions();
7171
$options->composerJson = $composerJsonPath;
7272

73-
$initializer = new Initializer($cwd, $printer);
73+
$initializer = new Initializer($cwd, $printer, $printer);
7474
$composerJson = $initializer->initComposerJson($options);
7575

7676
self::assertSame(
@@ -90,7 +90,7 @@ public function testInitCliOptions(): void
9090
{
9191
$printer = $this->createMock(Printer::class);
9292

93-
$initializer = new Initializer(__DIR__, $printer);
93+
$initializer = new Initializer(__DIR__, $printer, $printer);
9494
$options = $initializer->initCliOptions(__DIR__, ['script.php', '--verbose']);
9595

9696
self::assertNull($options->showAllUsages);
@@ -108,7 +108,7 @@ public function testInitCliOptionsHelp(): void
108108
{
109109
$printer = $this->createMock(Printer::class);
110110

111-
$initializer = new Initializer(__DIR__, $printer);
111+
$initializer = new Initializer(__DIR__, $printer, $printer);
112112

113113
$this->expectException(InvalidCliException::class);
114114
$initializer->initCliOptions(__DIR__, ['script.php', '--help']);
@@ -118,7 +118,7 @@ public function testInitFormatter(): void
118118
{
119119
$printer = $this->createMock(Printer::class);
120120

121-
$initializer = new Initializer(__DIR__, $printer);
121+
$initializer = new Initializer(__DIR__, $printer, $printer);
122122

123123
$optionsNoFormat = new CliOptions();
124124
self::assertInstanceOf(ConsoleFormatter::class, $initializer->initFormatter($optionsNoFormat));

0 commit comments

Comments
 (0)