Passed
Push — master ( 92d243...5a69a1 )
by Eric
12:07
created

CoverageCheckCommand::execute()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 43
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 20
nc 8
nop 2
dl 0
loc 43
rs 9.2888
c 1
b 0
f 0
ccs 19
cts 19
cp 1
crap 5
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of PHPUnit Coverage Check.
7
 *
8
 * (c) Eric Sizemore <[email protected]>
9
 * (c) Richard Regeer <[email protected]>
10
 *
11
 * This source file is subject to the MIT license. For the full copyright,
12
 * license information, and credits/acknowledgements, please view the LICENSE
13
 * and README files that were distributed with this source code.
14
 */
15
16
namespace Esi\CoverageCheck\Command;
17
18
use Esi\CoverageCheck\Application;
19
use Esi\CoverageCheck\CoverageCheck;
20
use Esi\CoverageCheck\Style\CoverageCheckStyle;
21
use Esi\CoverageCheck\Utils;
22
use Symfony\Component\Console\Attribute\AsCommand;
23
use Symfony\Component\Console\Command\Command;
24
use Symfony\Component\Console\Helper\TableCell;
25
use Symfony\Component\Console\Helper\TableCellStyle;
26
use Symfony\Component\Console\Helper\TableSeparator;
27
use Symfony\Component\Console\Input\InputArgument;
28
use Symfony\Component\Console\Input\InputInterface;
29
use Symfony\Component\Console\Input\InputOption;
30
use Symfony\Component\Console\Output\OutputInterface;
31
use Throwable;
32
33
use function sprintf;
34
35
/**
36
 * @see \Esi\CoverageCheck\Tests\Command\CoverageCheckCommandTest
37
 */
38
#[AsCommand(name: Application::COMMAND_NAME, description: Application::APPLICATION_DESCRIPTION)]
39
class CoverageCheckCommand extends Command
40
{
41
    private CoverageCheckStyle $coverageCheckStyle;
42
43 15
    public function __construct(private readonly CoverageCheck $coverageCheck)
44
    {
45 15
        parent::__construct();
46
    }
47
48
    /**
49
     * @see Command
50
     */
51 15
    #[\Override]
52
    protected function configure(): void
53
    {
54 15
        $this
55 15
            ->setDefinition([
56 15
                new InputArgument('cloverfile', InputArgument::REQUIRED, 'The location of the clover xml file that is generated by PHPUnit.'),
57 15
                new InputArgument('threshold', InputArgument::REQUIRED, 'The coverage threshold that is acceptable. Min = 1, Max = 100'),
58 15
                new InputOption('--only-percentage', '-O', InputOption::VALUE_NONE, 'Only return the resulting coverage percentage'),
59 15
                new InputOption('--show-files', '-F', InputOption::VALUE_NONE, 'Show a breakdown of coverage by file'),
60 15
            ])
61 15
            ->setHelp(
62 15
                <<<'EOF'
63
                    The <info>%command.name%</info> command calculates coverage score for the provided clover xml report.
64
65
                    You must also pass a coverage threshold that is acceptable. <info>Min = 1, Max = 100</info>:
66
67
                    <info>php %command.full_name% /path/to/clover.xml 100</info>
68
69
                    You may also choose to only return the resulting coverage percentage by using the <info>--only-percentage</info> option:
70
71
                    <info>php %command.full_name% /path/to/clover.xml 100 --only-percentage</info>
72
73
                    You may also choose to show a breakdown of coverage by file by using the <info>--show-files</info> option:
74
75
                    <info>php %command.full_name% /path/to/clover.xml 100 --show-files</info>
76 15
                    EOF
77 15
            )
78 15
        ;
79
    }
80
81
    /**
82
     * @see Command
83
     * @see CoverageCheck
84
     * @see CoverageCheckStyle
85
     */
86 15
    #[\Override]
87
    protected function execute(InputInterface $input, OutputInterface $output): int
88
    {
89 15
        $this->coverageCheckStyle = new CoverageCheckStyle($input, $output);
90
91
        /** @var string $cloverFile */
92 15
        $cloverFile = $input->getArgument('cloverfile');
93
94
        /** @var string $threshold */
95 15
        $threshold = $input->getArgument('threshold');
96
97
        /** @var bool $onlyPercentage */
98 15
        $onlyPercentage = $input->getOption('only-percentage');
99
100
        /** @var bool $showFiles */
101 15
        $showFiles = $input->getOption('show-files');
102
103 15
        $this->coverageCheck->setCloverFile($cloverFile)
104 15
            ->setThreshold((int) $threshold)
105 15
            ->setOnlyPercentage($onlyPercentage);
106
107
        try {
108 12
            $result = $showFiles ? $this->coverageCheck->processByFile() : $this->coverageCheck->process();
109 3
        } catch (Throwable $throwable) {
110 3
            $this->coverageCheckStyle->error($throwable->getMessage());
111
112 3
            return Command::INVALID;
113
        }
114
115
        // No metrics
116 9
        if ($result === false) {
117 3
            $this->coverageCheckStyle->error('Insufficient data for calculation. Please add more code.');
118
119 3
            return Command::FAILURE;
120
        }
121
122
        // --show-files
123 6
        if (\is_array($result)) {
1 ignored issue
show
introduced by
The condition is_array($result) is always true.
Loading history...
124 2
            return $this->getFileTable($result);
125
        }
126
127
        // Standard output
128 4
        return $this->getResultOutput($result);
129
    }
130
131
    /**
132
     * @param array{
133
     *     fileMetrics: array<string, array{coveredMetrics: int, totalMetrics: int, percentage: float|int}>,
134
     *     totalCoverage: float|int
135
     * } $result
136
     */
137 2
    private function getFileTable(array $result): int
138
    {
139 2
        $threshold     = $this->coverageCheck->getThreshold();
140 2
        $tableRows     = [];
141 2
        $totalElements = ['coveredMetrics' => 0, 'totalMetrics' => 0];
142 2
        $metrics       = $result['fileMetrics'];
143 2
        $totalCoverage = $result['totalCoverage'];
144
145 2
        unset($result);
146
147 2
        foreach ($metrics as $name => $file) {
148 2
            $tableRows[] = [
149 2
                $name,
150 2
                sprintf('%d/%d', $file['coveredMetrics'], $file['totalMetrics']),
151 2
                new TableCell(
152 2
                    Utils::formatCoverage($file['percentage']),
153 2
                    [
154 2
                        'style' => new TableCellStyle(
155 2
                            [
156 2
                                'cellFormat' => ($file['percentage'] < $threshold) ? '<error>%s</error>' : '<info>%s</info>',
157 2
                            ]
158 2
                        ),
159 2
                    ]
160 2
                ),
161 2
            ];
162
163 2
            $totalElements['coveredMetrics'] += $file['coveredMetrics'];
164 2
            $totalElements['totalMetrics']   += $file['totalMetrics'];
165
        }
166
167 2
        unset($metrics);
168
169 2
        $tableRows[] = new TableSeparator();
170 2
        $tableRows[] = [
171 2
            'Overall Totals',
172 2
            sprintf('%d/%d', $totalElements['coveredMetrics'], $totalElements['totalMetrics']),
173 2
            new TableCell(
174 2
                Utils::formatCoverage($totalCoverage),
175 2
                ['style' => new TableCellStyle(['cellFormat' => ($totalCoverage < $threshold) ? '<error>%s</error>' : '<info>%s</info>',])]
176 2
            ),
177 2
        ];
178
179 2
        $this->coverageCheckStyle->table(
180 2
            ['File', 'Elements (Covered/Total)', 'Coverage'],
181 2
            $tableRows
182 2
        );
183
184 2
        unset($tableRows);
185
186 2
        if ($totalCoverage < $threshold) {
187 1
            return Command::FAILURE;
188
        }
189
190 1
        return Command::SUCCESS;
191
    }
192
193 4
    private function getResultOutput(float $result): int
194
    {
195 4
        $threshold         = $this->coverageCheck->getThreshold();
196 4
        $onlyPercentage    = $this->coverageCheck->getOnlyPercentage();
197 4
        $formattedCoverage = Utils::formatCoverage($result);
198 4
        $belowThreshold    = $result < $threshold;
199
200
        // Only display the percentage?
201 4
        if ($onlyPercentage) {
202
            // ... below the accepted threshold
203 2
            if ($belowThreshold) {
204 1
                $this->coverageCheckStyle->error($formattedCoverage, true);
205
206 1
                return Command::FAILURE;
207
            }
208
209
            // all good, we meet or exceed the threshold
210 1
            $this->coverageCheckStyle->success($formattedCoverage, true);
211
212 1
            return Command::SUCCESS;
213
        }
214
215
        // We want the full message…
216 2
        if ($belowThreshold) {
217
            // ... below the accepted threshold
218 1
            $this->coverageCheckStyle->error(
219 1
                sprintf('Total code coverage is %s which is below the accepted %d%%', $formattedCoverage, $threshold)
220 1
            );
221
222 1
            return Command::FAILURE;
223
        }
224
225
        // all good, we meet or exceed the threshold
226 1
        $this->coverageCheckStyle->success(sprintf('Total code coverage is %s', $formattedCoverage));
227
228 1
        return Command::SUCCESS;
229
    }
230
}
231