|
20 | 20 | * status. The resulting badge message is "<passed>/<total> (<pct>%)". |
21 | 21 | */ |
22 | 22 |
|
23 | | -if (4 !== $argc) { |
24 | | - fwrite(\STDERR, "Usage: php score.php <results-dir> <label> <output-file>\n"); |
25 | | - exit(1); |
26 | | -} |
| 23 | +use Symfony\Component\Console\Command\Command; |
| 24 | +use Symfony\Component\Console\Input\InputArgument; |
| 25 | +use Symfony\Component\Console\Input\InputInterface; |
| 26 | +use Symfony\Component\Console\Output\OutputInterface; |
| 27 | +use Symfony\Component\Console\SingleCommandApplication; |
| 28 | +use Symfony\Component\Console\Style\SymfonyStyle; |
| 29 | + |
| 30 | +require_once dirname(__DIR__, 3).'/vendor/autoload.php'; |
| 31 | + |
| 32 | +(new SingleCommandApplication()) |
| 33 | + ->setName('conformance-score') |
| 34 | + ->setDescription('Generates a shields.io endpoint badge from a conformance results directory') |
| 35 | + ->addArgument('results-dir', InputArgument::REQUIRED, 'Directory produced by `conformance --output-dir`') |
| 36 | + ->addArgument('label', InputArgument::REQUIRED, 'Badge label, e.g. "server conformance"') |
| 37 | + ->addArgument('output-file', InputArgument::REQUIRED, 'Path the badge JSON is written to') |
| 38 | + ->setCode(static function (InputInterface $input, OutputInterface $output): int { |
| 39 | + $io = new SymfonyStyle($input, $output); |
| 40 | + |
| 41 | + $resultsDir = $input->getArgument('results-dir'); |
| 42 | + $label = $input->getArgument('label'); |
| 43 | + $outputFile = $input->getArgument('output-file'); |
| 44 | + |
| 45 | + if (!is_dir($resultsDir)) { |
| 46 | + $io->error(sprintf('Results directory "%s" does not exist.', $resultsDir)); |
| 47 | + |
| 48 | + return Command::FAILURE; |
| 49 | + } |
| 50 | + |
| 51 | + $checkFiles = new RegexIterator( |
| 52 | + new RecursiveIteratorIterator( |
| 53 | + new RecursiveDirectoryIterator($resultsDir, FilesystemIterator::SKIP_DOTS), |
| 54 | + ), |
| 55 | + '/checks\.json$/', |
| 56 | + ); |
| 57 | + |
| 58 | + $total = 0; |
| 59 | + $passed = 0; |
| 60 | + $failures = []; |
| 61 | + |
| 62 | + foreach ($checkFiles as $file) { |
| 63 | + $checks = json_decode((string) file_get_contents($file), true); |
27 | 64 |
|
28 | | -[, $resultsDir, $label, $outputFile] = $argv; |
| 65 | + if (!is_array($checks)) { |
| 66 | + $io->warning(sprintf('Skipping unreadable result file "%s".', $file)); |
29 | 67 |
|
30 | | -if (!is_dir($resultsDir)) { |
31 | | - fwrite(\STDERR, sprintf("Results directory \"%s\" does not exist.\n", $resultsDir)); |
32 | | - exit(1); |
33 | | -} |
| 68 | + continue; |
| 69 | + } |
34 | 70 |
|
35 | | -$checkFiles = new RegexIterator( |
36 | | - new RecursiveIteratorIterator( |
37 | | - new RecursiveDirectoryIterator($resultsDir, FilesystemIterator::SKIP_DOTS), |
38 | | - ), |
39 | | - '/checks\.json$/', |
40 | | -); |
| 71 | + ++$total; |
41 | 72 |
|
42 | | -$total = 0; |
43 | | -$passed = 0; |
| 73 | + foreach ($checks as $check) { |
| 74 | + if ('FAILURE' === ($check['status'] ?? null)) { |
| 75 | + $failures[] = basename($file->getPath()); |
44 | 76 |
|
45 | | -foreach ($checkFiles as $file) { |
46 | | - $checks = json_decode((string) file_get_contents($file), true); |
| 77 | + continue 2; |
| 78 | + } |
| 79 | + } |
47 | 80 |
|
48 | | - if (!is_array($checks)) { |
49 | | - fwrite(\STDERR, sprintf("Skipping unreadable result file \"%s\".\n", $file)); |
50 | | - continue; |
51 | | - } |
| 81 | + ++$passed; |
| 82 | + } |
52 | 83 |
|
53 | | - ++$total; |
| 84 | + $pct = $total > 0 ? (int) round($passed / $total * 100) : 0; |
| 85 | + |
| 86 | + $badge = [ |
| 87 | + 'schemaVersion' => 1, |
| 88 | + 'label' => $label, |
| 89 | + 'message' => $total > 0 ? sprintf('%d/%d (%d%%)', $passed, $total, $pct) : 'no data', |
| 90 | + 'color' => match (true) { |
| 91 | + 0 === $total => 'lightgrey', |
| 92 | + $pct >= 95 => 'brightgreen', |
| 93 | + $pct >= 80 => 'green', |
| 94 | + $pct >= 60 => 'yellow', |
| 95 | + default => 'orange', |
| 96 | + }, |
| 97 | + ]; |
| 98 | + |
| 99 | + if (false === file_put_contents($outputFile, json_encode($badge, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n")) { |
| 100 | + $io->error(sprintf('Could not write badge file "%s".', $outputFile)); |
| 101 | + |
| 102 | + return Command::FAILURE; |
| 103 | + } |
54 | 104 |
|
55 | | - foreach ($checks as $check) { |
56 | | - if ('FAILURE' === ($check['status'] ?? null)) { |
57 | | - continue 2; |
| 105 | + if ($failures && $io->isVerbose()) { |
| 106 | + $io->section('Failing scenarios'); |
| 107 | + $io->listing($failures); |
58 | 108 | } |
59 | | - } |
60 | | - |
61 | | - ++$passed; |
62 | | -} |
63 | | - |
64 | | -$pct = $total > 0 ? (int) round($passed / $total * 100) : 0; |
65 | | - |
66 | | -$color = match (true) { |
67 | | - 0 === $total => 'lightgrey', |
68 | | - $pct >= 95 => 'brightgreen', |
69 | | - $pct >= 80 => 'green', |
70 | | - $pct >= 60 => 'yellow', |
71 | | - default => 'orange', |
72 | | -}; |
73 | | - |
74 | | -$badge = [ |
75 | | - 'schemaVersion' => 1, |
76 | | - 'label' => $label, |
77 | | - 'message' => $total > 0 ? sprintf('%d/%d (%d%%)', $passed, $total, $pct) : 'no data', |
78 | | - 'color' => $color, |
79 | | -]; |
80 | | - |
81 | | -if (false === file_put_contents($outputFile, json_encode($badge, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n")) { |
82 | | - fwrite(\STDERR, sprintf("Could not write badge file \"%s\".\n", $outputFile)); |
83 | | - exit(1); |
84 | | -} |
85 | | - |
86 | | -printf("%s: %s\n", $label, $badge['message']); |
| 109 | + |
| 110 | + $io->success(sprintf('%s: %s', $label, $badge['message'])); |
| 111 | + |
| 112 | + return Command::SUCCESS; |
| 113 | + }) |
| 114 | + ->run(); |
0 commit comments