Skip to content

Commit e8852d2

Browse files
authored
Merge pull request #6 from Aeliot-Tm/beautify_report
Beautify clover baseline comparing report
2 parents 185a9f4 + c3f826c commit e8852d2

16 files changed

+902
-18
lines changed

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ Usage
2828
```
2929
It accepts options:
3030

31-
| Full name | Short name | Description | Default value |
32-
|--------------|------------|---------------------------|--------------------------------|
33-
| `--baseline` | `-b` | Path to the baseline | `phpunit.clover.baseline.json` |
34-
| `--clover` | `-c` | Path to the Clover report | `build/coverage/clover.xml` |
31+
| Full name | Short name | Description | Default value |
32+
|--------------|------------|----------------------------------------------|--------------------------------|
33+
| `--baseline` | `-b` | Path to the baseline | `phpunit.clover.baseline.json` |
34+
| `--clover` | `-c` | Path to the Clover report | `build/coverage/clover.xml` |
35+
| `--verbose` | `-v` | Generates verbose report. Accepts value: `v` | |
3536

3637

3738
### Build baseline for Clover report
@@ -41,4 +42,9 @@ Usage
4142
```shell
4243
vendor/bin/pccb_clover_build_baseline
4344
```
44-
It accepts same options like previous command.
45+
It accepts options:
46+
47+
| Full name | Short name | Description | Default value |
48+
|--------------|------------|---------------------------|--------------------------------|
49+
| `--baseline` | `-b` | Path to the baseline | `phpunit.clover.baseline.json` |
50+
| `--clover` | `-c` | Path to the Clover report | `build/coverage/clover.xml` |

bin/pccb_clover_compare

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use Aeliot\PHPUnitCodeCoverageBaseline\Comparator;
77
use Aeliot\PHPUnitCodeCoverageBaseline\Console\GetOpt;
88
use Aeliot\PHPUnitCodeCoverageBaseline\Console\OptionsConfig;
99
use Aeliot\PHPUnitCodeCoverageBaseline\Console\OptionsReader;
10+
use Aeliot\PHPUnitCodeCoverageBaseline\Model\ComparingRow;
11+
use Aeliot\PHPUnitCodeCoverageBaseline\Model\ConsoleTable;
1012
use Aeliot\PHPUnitCodeCoverageBaseline\Reader\BaselineReader;
1113
use Aeliot\PHPUnitCodeCoverageBaseline\Reader\CloverReader;
1214

@@ -15,19 +17,47 @@ require __DIR__ . '/include_autoloader.php';
1517
$optionsConfig = new OptionsConfig();
1618
$optionsConfig->add('baseline', 'b', 'phpunit.clover.baseline.json');
1719
$optionsConfig->add('clover', 'c', 'build/coverage/clover.xml');
20+
$optionsConfig->add('verbose', 'v');
1821

1922
/** @var array{ baseline: string, clover: string } $options */
2023
$options = (new OptionsReader($optionsConfig, new GetOpt()))->read();
24+
$isVerbose = false;
25+
if (isset($options['verbose'])) {
26+
if ('v' !== $options['verbose']) {
27+
throw new \InvalidArgumentException('Invalid verbosity option');
28+
}
29+
$isVerbose = ('v' === $options['verbose']);
30+
}
2131

2232
try {
2333
$comparator = new Comparator(new BaselineReader($options['baseline']), new CloverReader($options['clover']));
24-
$regressedTypes = $comparator->compare();
34+
$results = $comparator->compare();
2535
} catch (\Exception $exception) {
2636
echo sprintf('[ERROR] %s%s', $exception->getMessage(), PHP_EOL);
2737
exit(2);
2838
}
2939

30-
if ($regressedTypes) {
40+
if ($isVerbose) {
41+
echo 'Clover baseline comparing results:' . PHP_EOL;
42+
43+
$table = new ConsoleTable([
44+
'name' => 'Metrics',
45+
'old' => 'Old coverage',
46+
'new' => 'New coverage',
47+
'progress' => 'Progress',
48+
]);
49+
50+
$rows = $results->getRows();
51+
array_walk($rows, static fn (ComparingRow $x) => $table->addComparingRow($x));
52+
53+
echo $table->getContent();
54+
}
55+
56+
if ($regressedTypes = $results->getRegressedNames()) {
3157
echo sprintf('[ERROR] There is detected regress of code coverage on types: %s.%s', implode(', ', $regressedTypes), PHP_EOL);
3258
exit(1);
3359
}
60+
61+
if ($isVerbose && $results->hasImprovement()) {
62+
echo 'Good job! You improved code coverage. Update baseline.' . PHP_EOL;
63+
}

docs/clover_verbose_report.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
PHPUnit code coverage comparing report
2+
======================================
3+
4+
Add option `--verbose=v` or `-vv` to console command then you will get report:
5+
```
6+
Clover baseline comparing results:
7+
|--------------|--------------|--------------|-----------|
8+
| Metrics | Old coverage | New coverage | Progress |
9+
|--------------|--------------|--------------|-----------|
10+
| methods | 5.00 % | 50.00 % | +45.00 % |
11+
| conditionals | 50.01 % | 45.12 % | -4.89 % |
12+
| statements | 40.00 % | 50.00 % | +10.00 % |
13+
| elements | 50.19 % | 50.19 % | 0.00 % |
14+
|--------------|--------------|--------------|-----------|
15+
```
16+
17+
If you have some progress and don't have any regress then you get suggestion after the table to update your baseline.
18+
```
19+
Good job! You improved code coverage. Update baseline.
20+
```

src/Comparator.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
namespace Aeliot\PHPUnitCodeCoverageBaseline;
66

77
use Aeliot\PHPUnitCodeCoverageBaseline\Enum\SupportedType;
8+
use Aeliot\PHPUnitCodeCoverageBaseline\Model\ComparingResult;
9+
use Aeliot\PHPUnitCodeCoverageBaseline\Model\ComparingRow;
810
use Aeliot\PHPUnitCodeCoverageBaseline\Reader\BaselineReader;
911
use Aeliot\PHPUnitCodeCoverageBaseline\Reader\CloverReader;
1012

@@ -19,11 +21,11 @@ public function __construct(BaselineReader $baselineReader, CloverReader $clover
1921
$this->cloverReader = $cloverReader;
2022
}
2123

22-
public function compare(): array
24+
public function compare(): ComparingResult
2325
{
2426
$baseline = $this->baselineReader->read();
2527
$cloverData = $this->cloverReader->read();
26-
$regressedTypes = [];
28+
$result = new ComparingResult();
2729

2830
foreach (SupportedType::getCoveredTypes() as $type => $typeCover) {
2931
if (!isset($baseline[$type])) {
@@ -33,20 +35,18 @@ public function compare(): array
3335
$currentProgress = 0.0;
3436

3537
$typeValue = $cloverData[$type];
36-
$typeCoverValue = $cloverData[$typeCover];
3738
if ($typeValue) {
39+
$typeCoverValue = $cloverData[$typeCover];
3840
$currentProgress = $typeCoverValue / $typeValue;
3941
}
4042

4143
if ($baseline[$type]) {
4244
$baselineProgress = ($baseline[$typeCover] ?? 0) / $baseline[$type];
4345
}
4446

45-
if ($currentProgress < $baselineProgress) {
46-
$regressedTypes[] = $type;
47-
}
47+
$result->addRow(new ComparingRow($type, $baselineProgress, $currentProgress));
4848
}
4949

50-
return $regressedTypes;
50+
return $result;
5151
}
5252
}

src/Console/OptionsConfig.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ final class OptionsConfig
1212
private array $aliases = [];
1313

1414
/**
15-
* @var array<string,array{ longName: string, shortName: string, defaultValue: string }>
15+
* @var array<string,array{ longName: string, shortName: string, defaultValue: string|null }>
1616
*/
1717
private array $options = [];
1818

1919
public function add(
2020
string $longName,
2121
string $shortName,
22-
string $defaultValue
22+
string $defaultValue = null
2323
): void {
2424
$this->validateNames($longName, $shortName);
2525

@@ -43,7 +43,7 @@ public function getAliases(): array
4343
}
4444

4545
/**
46-
* @return array<string,array{ longName: string, shortName: string, defaultValue: string }>
46+
* @return array<string,array{ longName: string, shortName: string, defaultValue: string|null }>
4747
*/
4848
public function getOptions(): array
4949
{

src/Model/ComparingResult.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Aeliot\PHPUnitCodeCoverageBaseline\Model;
6+
7+
final class ComparingResult
8+
{
9+
private bool $hasImprovement = false;
10+
11+
/**
12+
* @var ComparingRow[]
13+
*/
14+
private array $rows = [];
15+
16+
/**
17+
* @var string[]
18+
*/
19+
private array $regressedNames = [];
20+
21+
public function addRow(ComparingRow $row): void
22+
{
23+
$this->rows[] = $row;
24+
if ($row->hasRegress()) {
25+
$this->regressedNames[] = $row->getName();
26+
} elseif ($row->hasProgress()) {
27+
$this->hasImprovement = true;
28+
}
29+
}
30+
31+
/**
32+
* @return ComparingRow[]
33+
*/
34+
public function getRows(): array
35+
{
36+
return $this->rows;
37+
}
38+
39+
/**
40+
* @return string[]
41+
*/
42+
public function getRegressedNames(): array
43+
{
44+
return $this->regressedNames;
45+
}
46+
47+
public function hasImprovement(): bool
48+
{
49+
return $this->hasImprovement;
50+
}
51+
}

src/Model/ComparingRow.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Aeliot\PHPUnitCodeCoverageBaseline\Model;
6+
7+
final class ComparingRow
8+
{
9+
private string $name;
10+
private float $old;
11+
private float $new;
12+
private float $progress;
13+
14+
public function __construct(string $name, float $old, float $new)
15+
{
16+
$this->name = $name;
17+
$this->old = round($old * 100, 2);
18+
$this->new = round($new * 100, 2);
19+
$this->progress = $this->new - $this->old;
20+
}
21+
22+
public function getName(): string
23+
{
24+
return $this->name;
25+
}
26+
27+
public function hasProgress(): bool
28+
{
29+
return 0 < $this->progress;
30+
}
31+
32+
public function hasRegress(): bool
33+
{
34+
return 0 > $this->progress;
35+
}
36+
37+
public function getValues(): array
38+
{
39+
$progressPrefix = (0 < $this->progress) ? '+' : '';
40+
41+
return [
42+
'name' => $this->name,
43+
'old' => str_pad(number_format($this->old, 2), 6, ' ', \STR_PAD_LEFT) . ' %',
44+
'new' => str_pad(number_format($this->new, 2), 6, ' ', \STR_PAD_LEFT) . ' %',
45+
'progress' => str_pad($progressPrefix . number_format($this->progress, 2), 7, ' ', \STR_PAD_LEFT) . ' %',
46+
];
47+
}
48+
}

src/Model/ConsoleTable.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Aeliot\PHPUnitCodeCoverageBaseline\Model;
6+
7+
final class ConsoleTable
8+
{
9+
/**
10+
* @var string[]
11+
*/
12+
private array $columns;
13+
14+
private int $columnsCount;
15+
16+
/**
17+
* @var array<int,int|string>
18+
*/
19+
private array $columnsKeys;
20+
21+
private array $columnsWidth;
22+
23+
/**
24+
* @var array<int,array<string>>
25+
*/
26+
private array $values = [];
27+
28+
/**
29+
* @param string[] $columns
30+
*/
31+
public function __construct(array $columns)
32+
{
33+
$this->columns = $columns;
34+
$this->columnsCount = count($columns);
35+
$this->columnsKeys = array_keys($this->columns);
36+
$this->columnsWidth = array_map('strlen', array_values($columns));
37+
}
38+
39+
public function addComparingRow(ComparingRow $row): void
40+
{
41+
$data = $row->getValues();
42+
$values = [];
43+
foreach ($this->columnsKeys as $key) {
44+
if (!array_key_exists($key, $data)) {
45+
throw new \InvalidArgumentException(sprintf('Row does not contain column "%s"', $key));
46+
}
47+
48+
$values[] = $data[$key];
49+
unset($data[$key]);
50+
}
51+
52+
if ($data) {
53+
$message = sprintf('Row contain extra column(s): %s', implode(', ', array_keys($data)));
54+
throw new \InvalidArgumentException($message);
55+
}
56+
57+
$this->addLine(...$values);
58+
}
59+
60+
public function addLine(string ...$values): void
61+
{
62+
if (count($values) !== $this->columnsCount) {
63+
throw new \InvalidArgumentException('Invalid values count');
64+
}
65+
66+
$this->values[] = $values;
67+
$this->updateWidth($values);
68+
}
69+
70+
public function getContent(): string
71+
{
72+
$content = [];
73+
$content[] = $this->buildSeparateLine();
74+
$content[] = $this->buildTableLine(array_values($this->columns));
75+
$content[] = $this->buildSeparateLine();
76+
foreach ($this->values as $values) {
77+
$content[] = $this->buildTableLine($values);
78+
}
79+
$content[] = $this->buildSeparateLine();
80+
81+
return implode(PHP_EOL, $content) . PHP_EOL;
82+
}
83+
84+
private function buildSeparateLine(): string
85+
{
86+
return $this->buildTableLine(array_fill(0, count($this->columns), ''), '-');
87+
}
88+
89+
private function buildTableLine(array $values, string $filler = ' '): string
90+
{
91+
foreach ($values as $index => $value) {
92+
$values[$index] = str_pad($value, $this->columnsWidth[$index], $filler);
93+
}
94+
95+
return '|' . $filler . implode($filler . '|' . $filler, $values) . $filler . '|';
96+
}
97+
98+
/**
99+
* @param string[] $values
100+
*/
101+
private function updateWidth(array $values): void
102+
{
103+
$widths = array_map('strlen', $values);
104+
foreach ($this->columnsWidth as $index => $width) {
105+
$this->columnsWidth[$index] = max($width, $widths[$index]);
106+
}
107+
}
108+
}

tests/Unit/ComparatorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function testPositiveFlow(array $expected, array $baseline, array $clover
2222
$cloverReader->method('read')->willReturn($cloverData);
2323
$comparator = new Comparator($baselineReader, $cloverReader);
2424

25-
self::assertSame($expected, $comparator->compare());
25+
self::assertSame($expected, $comparator->compare()->getRegressedNames());
2626
}
2727

2828
public function getDataForTestPositiveFlow(): iterable

0 commit comments

Comments
 (0)