Skip to content

Commit

Permalink
JUnit format improvements (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
janedbal authored Jul 17, 2024
1 parent 213c157 commit db52364
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 108 deletions.
139 changes: 72 additions & 67 deletions src/Result/JunitFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@

namespace ShipMonk\ComposerDependencyAnalyser\Result;

use DOMDocument;
use ShipMonk\ComposerDependencyAnalyser\CliOptions;
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore;
use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedSymbolIgnore;
use ShipMonk\ComposerDependencyAnalyser\Printer;
use ShipMonk\ComposerDependencyAnalyser\SymbolKind;
use function array_fill_keys;
use function array_reduce;
use function count;
use function extension_loaded;
use function htmlspecialchars;
use function implode;
use function sprintf;
use function strlen;
use function strpos;
use function substr;
use function trim;
use const ENT_COMPAT;
use const ENT_XML1;
use const LIBXML_NOEMPTYTAG;
use const PHP_INT_MAX;

class JunitFormatter implements ResultFormatter
Expand Down Expand Up @@ -120,9 +123,13 @@ public function format(
$xml .= $this->createUnusedIgnoresTestSuite($unusedIgnores);
}

if ($hasError) {
$xml .= sprintf('<!-- %s -->', $this->getUsagesComment($maxShownUsages));
}

$xml .= '</testsuites>';

$this->printer->print($xml);
$this->printer->print($this->prettyPrintXml($xml));

if ($hasError) {
return 255;
Expand Down Expand Up @@ -154,30 +161,18 @@ private function createSymbolBasedTestSuite(string $title, array $errors, int $m
foreach ($errors as $symbol => $usages) {
$xml .= sprintf('<testcase name="%s">', $this->escape($symbol));

if ($maxShownUsages > 1) {
$failureUsage = [];
$failureUsage = [];

foreach ($usages as $index => $usage) {
$failureUsage[] = $this->relativizeUsage($usage);
foreach ($usages as $index => $usage) {
$failureUsage[] = $this->relativizeUsage($usage);

if ($index === $maxShownUsages - 1) {
$restUsagesCount = count($usages) - $index - 1;

if ($restUsagesCount > 0) {
$failureUsage[] = "+ {$restUsagesCount} more";
break;
}
}
if ($index === $maxShownUsages) {
break;
}

$xml .= sprintf('<failure>%s</failure>', $this->escape(implode('\n', $failureUsage)));
} else {
$firstUsage = $usages[0];
$restUsagesCount = count($usages) - 1;
$rest = $restUsagesCount > 0 ? " (+ {$restUsagesCount} more)" : '';
$xml .= sprintf('<failure>in %s%s</failure>', $this->escape($this->relativizeUsage($firstUsage)), $rest);
}

$xml .= sprintf('<failure>%s</failure>', $this->escape(implode("\n", $failureUsage)));

$xml .= '</testcase>';
}

Expand All @@ -195,7 +190,24 @@ private function createPackageBasedTestSuite(string $title, array $errors, int $

foreach ($errors as $packageName => $usagesPerClassname) {
$xml .= sprintf('<testcase name="%s">', $this->escape($packageName));
$xml .= sprintf('<failure>%s</failure>', $this->escape(implode('\n', $this->createUsages($usagesPerClassname, $maxShownUsages))));

$printedSymbols = 0;

foreach ($usagesPerClassname as $symbol => $usages) {
$printedSymbols++;
$xml .= sprintf(
'<failure message="%s">%s</failure>',
$symbol,
$this->escape(
implode("\n", $this->createUsages($usages, $maxShownUsages))
)
);

if ($printedSymbols === $maxShownUsages) {
break;
}
}

$xml .= '</testcase>';
}

Expand All @@ -205,59 +217,19 @@ private function createPackageBasedTestSuite(string $title, array $errors, int $
}

/**
* @param array<string, list<SymbolUsage>> $usagesPerSymbol
* @param list<SymbolUsage> $usages
* @return list<string>
*/
private function createUsages(array $usagesPerSymbol, int $maxShownUsages): array
private function createUsages(array $usages, int $maxShownUsages): array
{
$usageMessages = [];

if ($maxShownUsages === 1) {
$countOfAllUsages = array_reduce(
$usagesPerSymbol,
static function (int $carry, array $usages): int {
return $carry + count($usages);
},
0
);
foreach ($usages as $index => $usage) {
$usageMessages[] = $this->relativizeUsage($usage);

foreach ($usagesPerSymbol as $symbol => $usages) {
$firstUsage = $usages[0];
$restUsagesCount = $countOfAllUsages - 1;
$rest = $countOfAllUsages > 1 ? " (+ {$restUsagesCount} more)" : '';
$usageMessages[] = "e.g. {$symbol} in {$this->relativizeUsage($firstUsage)}$rest";
if ($index === $maxShownUsages - 1) {
break;
}
} else {
$classnamesPrinted = 0;

foreach ($usagesPerSymbol as $symbol => $usages) {
$classnamesPrinted++;

$usageMessages[] = $symbol;

foreach ($usages as $index => $usage) {
$usageMessages[] = " {$this->relativizeUsage($usage)}";

if ($index === $maxShownUsages - 1) {
$restUsagesCount = count($usages) - $index - 1;

if ($restUsagesCount > 0) {
$usageMessages[] = " + {$restUsagesCount} more";
break;
}
}
}

if ($classnamesPrinted === $maxShownUsages) {
$restSymbolsCount = count($usagesPerSymbol) - $classnamesPrinted;

if ($restSymbolsCount > 0) {
$usageMessages[] = " + {$restSymbolsCount} more symbol" . ($restSymbolsCount > 1 ? 's' : '');
break;
}
}
}
}

return $usageMessages;
Expand Down Expand Up @@ -323,4 +295,37 @@ private function escape(string $string): string
return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT, 'UTF-8');
}

private function prettyPrintXml(string $inputXml): string
{
if (!extension_loaded('dom') || !extension_loaded('libxml')) {
return $inputXml;
}

$dom = new DOMDocument();
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($inputXml);

$outputXml = $dom->saveXML(null, LIBXML_NOEMPTYTAG);

if ($outputXml === false) {
return $inputXml;
}

return trim($outputXml);
}

private function getUsagesComment(int $maxShownUsages): string
{
if ($maxShownUsages === PHP_INT_MAX) {
return 'showing all failure usages';
}

if ($maxShownUsages === 1) {
return 'showing only first example failure usage';
}

return sprintf('showing only first %d example failure usages', $maxShownUsages);
}

}
2 changes: 1 addition & 1 deletion tests/BinTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function test(): void

$usingConfig = 'Using config';

$junitOutput = '<?xml version="1.0" encoding="UTF-8"?><testsuites></testsuites>';
$junitOutput = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testsuites></testsuites>";

$this->runCommand('php bin/composer-dependency-analyser', $rootDir, 0, $okOutput, $usingConfig);
$this->runCommand('php bin/composer-dependency-analyser --verbose', $rootDir, 0, $okOutput, $usingConfig);
Expand Down
63 changes: 23 additions & 40 deletions tests/JunitFormatterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@

namespace ShipMonk\ComposerDependencyAnalyser;

use DOMDocument;
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;
use ShipMonk\ComposerDependencyAnalyser\Config\Ignore\UnusedErrorIgnore;
use ShipMonk\ComposerDependencyAnalyser\Result\AnalysisResult;
use ShipMonk\ComposerDependencyAnalyser\Result\JunitFormatter;
use ShipMonk\ComposerDependencyAnalyser\Result\ResultFormatter;
use ShipMonk\ComposerDependencyAnalyser\Result\SymbolUsage;
use function trim;
use const LIBXML_NOEMPTYTAG;

class JunitFormatterTest extends FormatterTest
{
Expand Down Expand Up @@ -39,11 +36,12 @@ public function testPrintResult(): void
<failure>'shadow-dependency' was globally ignored, but it was never applied.</failure>
</testcase>
</testsuite>
<!-- showing only first example failure usage -->
</testsuites>
OUT;

self::assertSame($this->normalizeEol($expectedNoIssuesOutput), $this->prettyPrintXml($noIssuesOutput));
self::assertSame($this->normalizeEol($expectedNoIssuesButWarningsOutput), $this->prettyPrintXml($noIssuesButUnusedIgnores));
self::assertSame($this->normalizeEol($expectedNoIssuesOutput), $noIssuesOutput);
self::assertSame($this->normalizeEol($expectedNoIssuesButWarningsOutput), $noIssuesButUnusedIgnores);

$analysisResult = new AnalysisResult(
10,
Expand Down Expand Up @@ -87,37 +85,34 @@ public function testPrintResult(): void
<testsuites>
<testsuite name="unknown classes" failures="1">
<testcase name="Unknown\Thing">
<failure>in app/init.php:1093</failure>
<failure>app/init.php:1093</failure>
</testcase>
</testsuite>
<testsuite name="unknown functions" failures="1">
<testcase name="Unknown\function">
<failure>in app/foo.php:51</failure>
<failure>app/foo.php:51</failure>
</testcase>
</testsuite>
<testsuite name="shadow dependencies" failures="2">
<testcase name="shadow/another">
<failure>e.g. Another\Controller in src/bootstrap.php:173</failure>
<failure message="Another\Controller">src/bootstrap.php:173</failure>
</testcase>
<testcase name="shadow/package">
<failure>e.g. Forth\Provider in src/bootstrap.php:873 (+ 6 more)</failure>
<failure message="Forth\Provider">src/bootstrap.php:873</failure>
</testcase>
</testsuite>
<testsuite name="dev dependencies in production code" failures="1">
<testcase name="some/package">
<failure>e.g. Another\Command in src/ProductGenerator.php:28</failure>
<failure message="Another\Command">src/ProductGenerator.php:28</failure>
</testcase>
</testsuite>
<testsuite name="prod dependencies used only in dev paths" failures="1">
<testcase name="misplaced/package">
<failure></failure>
</testcase>
<testcase name="misplaced/package"></testcase>
</testsuite>
<testsuite name="unused dependencies" failures="1">
<testcase name="dead/package">
<failure></failure>
</testcase>
<testcase name="dead/package"></testcase>
</testsuite>
<!-- showing only first example failure usage -->
</testsuites>
OUT;
$expectedVerboseOutput = <<<'OUT'
Expand All @@ -135,48 +130,36 @@ public function testPrintResult(): void
</testsuite>
<testsuite name="shadow dependencies" failures="2">
<testcase name="shadow/another">
<failure>Another\Controller\n src/bootstrap.php:173</failure>
<failure message="Another\Controller">src/bootstrap.php:173</failure>
</testcase>
<testcase name="shadow/package">
<failure>Forth\Provider\n src/bootstrap.php:873\nShadow\Comparator\n src/Printer.php:25\nShadow\Utils\n src/Utils.php:19\n src/Utils.php:22\n src/Application.php:128\n + 1 more\n + 1 more symbol</failure>
<failure message="Forth\Provider">src/bootstrap.php:873</failure>
<failure message="Shadow\Comparator">src/Printer.php:25</failure>
<failure message="Shadow\Utils">src/Utils.php:19
src/Utils.php:22
src/Application.php:128</failure>
</testcase>
</testsuite>
<testsuite name="dev dependencies in production code" failures="1">
<testcase name="some/package">
<failure>Another\Command\n src/ProductGenerator.php:28</failure>
<failure message="Another\Command">src/ProductGenerator.php:28</failure>
</testcase>
</testsuite>
<testsuite name="prod dependencies used only in dev paths" failures="1">
<testcase name="misplaced/package">
<failure></failure>
</testcase>
<testcase name="misplaced/package"></testcase>
</testsuite>
<testsuite name="unused dependencies" failures="1">
<testcase name="dead/package">
<failure></failure>
</testcase>
<testcase name="dead/package"></testcase>
</testsuite>
<!-- showing only first 3 example failure usages -->
</testsuites>
OUT;

self::assertSame($this->normalizeEol($expectedRegularOutput), $this->prettyPrintXml($regularOutput));
self::assertSame($this->normalizeEol($expectedVerboseOutput), $this->prettyPrintXml($verboseOutput));
self::assertSame($this->normalizeEol($expectedRegularOutput), $regularOutput);
self::assertSame($this->normalizeEol($expectedVerboseOutput), $verboseOutput);
// editorconfig-checker-enable
}

private function prettyPrintXml(string $inputXml): string
{
$dom = new DOMDocument();
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($inputXml);

$outputXml = $dom->saveXML(null, LIBXML_NOEMPTYTAG);
self::assertNotFalse($outputXml);

return trim($outputXml);
}

protected function createFormatter(Printer $printer): ResultFormatter
{
return new JunitFormatter('/app', $printer);
Expand Down

0 comments on commit db52364

Please sign in to comment.