Skip to content

Commit 60fc13a

Browse files
authored
Merge pull request #2 from codebtech/feat/merge-reports
Add BadgeComposer class and update existing files
2 parents 654809e + b36aa02 commit 60fc13a

File tree

9 files changed

+400
-58
lines changed

9 files changed

+400
-58
lines changed

Includes/BadgeComposer.php

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
3+
namespace BadgeManager\Includes;
4+
5+
use Exception;
6+
7+
/**
8+
* Class BadgeComposer
9+
*
10+
* This class is responsible for generating a coverage badge based on the input files provided.
11+
*
12+
* @package BadgeManager
13+
*/
14+
class BadgeComposer
15+
{
16+
private array $inputFiles;
17+
private string $outputFile;
18+
private string $coverageName;
19+
public int $totalCoverage = 0;
20+
private int $totalElements = 0;
21+
private int $checkedElements = 0;
22+
23+
/**
24+
* @throws Exception
25+
*/
26+
public function __construct(string $inputFiles, string $outputFile, string $coverageName = 'coverage')
27+
{
28+
$this->inputFiles = explode(',', $inputFiles);
29+
$this->outputFile = $outputFile;
30+
$this->coverageName = $coverageName;
31+
32+
$this->validateFiles($this->inputFiles, $this->outputFile);
33+
}
34+
35+
/**
36+
* Validates the input files and output file.
37+
*
38+
* This method checks if the input files exist and if the output file is provided.
39+
*
40+
* @param array $inputFiles The array of input files to validate.
41+
* @param string $outputFile The output file to validate.
42+
*
43+
* @return void
44+
* @throws Exception If any of the input files do not exist or if the output file is not provided.
45+
*/
46+
private function validateFiles(array $inputFiles, string $outputFile): void
47+
{
48+
foreach ($inputFiles as $inputFile) {
49+
if (!file_exists($inputFile)) {
50+
throw new \Exception("input file does not exist: " . $inputFile);
51+
}
52+
}
53+
54+
if (empty($outputFile)) {
55+
throw new \Exception("output file name is mandatory");
56+
}
57+
}
58+
59+
/**
60+
* Runs the coverage process for each input file.
61+
*
62+
* This method iterates over the input files array and processes each file using the `processFile` method.
63+
* After processing all input files, the coverage is finalized using the `finalizeCoverage` method.
64+
*
65+
* @return void
66+
* @throws Exception
67+
*/
68+
public function run(): void
69+
{
70+
foreach ($this->inputFiles as $inputFile) {
71+
$this->processFile($inputFile);
72+
}
73+
$this->finalizeCoverage();
74+
}
75+
76+
/**
77+
* Process a file and update the total and checked elements count.
78+
*
79+
* @param string $inputFile The path to the XML file to process.
80+
*
81+
* @return void
82+
* @throws Exception When there is an error processing the file.
83+
*
84+
*/
85+
private function processFile(string $inputFile): void
86+
{
87+
try {
88+
$xml = new \SimpleXMLElement(file_get_contents($inputFile));
89+
$metrics = $xml->xpath('//metrics');
90+
foreach ($metrics as $metric) {
91+
$this->totalElements += (int) $metric['elements'];
92+
$this->checkedElements += (int) $metric['coveredelements'];
93+
}
94+
95+
$coverage = round(($this->totalElements === 0) ? 0 : ($this->checkedElements / $this->totalElements) * 100);
96+
$this->totalCoverage += $coverage;
97+
} catch (Exception $e) {
98+
throw new Exception('Error processing file: ' . $inputFile);
99+
}
100+
}
101+
102+
/**
103+
* Finalize the coverage report by generating a badge with the average coverage across all input files.
104+
*
105+
* @return void
106+
* @throws Exception If there is an error generating the badge.
107+
*/
108+
private function finalizeCoverage(): void
109+
{
110+
$totalCoverage = $this->totalCoverage / count($this->inputFiles); // Average coverage across all files
111+
$template = file_get_contents(__DIR__ . '/../badge-templates/badge.svg');
112+
113+
$template = str_replace('{{ total }}', $totalCoverage, $template);
114+
115+
$template = str_replace('{{ coverage }}', $this->coverageName, $template);
116+
117+
$color = '#a4a61d'; // Yellow-Green
118+
if ($totalCoverage < 40) {
119+
$color = '#e05d44'; // Red
120+
} elseif ($totalCoverage < 60) {
121+
$color = '#fe7d37'; // Orange
122+
} elseif ($totalCoverage < 75) {
123+
$color = '#dfb317'; // Yellow
124+
} elseif ($totalCoverage < 95) {
125+
$color = '#97CA00'; // Green
126+
} elseif ($totalCoverage <= 100) {
127+
$color = '#4c1'; // Bright Green
128+
}
129+
130+
$template = str_replace('{{ total }}', $totalCoverage, $template);
131+
$template = str_replace('{{ color }}', $color, $template);
132+
133+
file_put_contents($this->outputFile, $template);
134+
}
135+
}

README.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ Composer
88

99
`composer require codebtech/coveragebadge --dev`
1010

11+
## Features
12+
- Generate coverage badge from PHPUnit Clover XML file
13+
- Generate coverage badge based on multiple Clover XML files, and it merges the coverage percentage automatically
14+
- Can accept coverage name and the badge will be generated with the coverage name
15+
1116

1217
## Usage
1318

14-
1. Generate [XML Code Coverage](https://phpunit.de/manual/current/en/logging.html#logging.codecoverage.xml) using [PHPUnit](https://phpunit.de/manual/current/en/appendixes.configuration.html#appendixes.configuration.logging)
15-
2. Run `vendor/bin/coverage-badge /path/to/clover.xml /path/to/badge/destination.svg type_of_test`
16-
* e.g. `vendor/bin/php-coverage-badge build/clover.xml report/coverage.svg unit-test`
19+
1. Generate [XML Code Coverage](https://docs.phpunit.de/en/11.1/code-coverage.html) and generate [Clover](https://docs.phpunit.de/en/11.1/configuration.html#the-report-element) XML files.
20+
2. Run `vendor/bin/coverage-badge /path/to/clover.xml /path/to/badge/destination.svg test-name`
21+
3. To merge multiple clover files provide the input XML files `comma` separated run `vendor/bin/coverage-badge /path/to/clover.xml,/path/to/clover2.xml /path/to/badge/destination.svg test-name`

Tests/BadgeComposerTest.php

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
namespace BadgeManager\Tests;
4+
5+
use Exception;
6+
use ReflectionException;
7+
use PHPUnit\Framework\TestCase;
8+
use BadgeManager\Includes\BadgeComposer;
9+
10+
class BadgeComposerTest extends TestCase
11+
{
12+
private BadgeComposer $badgeComposer;
13+
14+
private string $inputFile = __DIR__ . "/test-input1.xml";
15+
private string $inputFile2 = __DIR__ . "/test-input2.xml";
16+
private string $outputFile = "output.svg";
17+
private string $coverageName = "unit";
18+
19+
/**
20+
* @throws Exception
21+
*/
22+
public function setUp(): void
23+
{
24+
$this->badgeComposer = new BadgeComposer($this->inputFile, $this->outputFile, $this->coverageName);
25+
}
26+
27+
/**
28+
* @throws ReflectionException
29+
*/
30+
public function validateFiles(array $files, string $output)
31+
{
32+
$method = (new \ReflectionClass($this->badgeComposer))->getMethod('validateFiles');
33+
34+
return $method->invoke($this->badgeComposer, $files, $output);
35+
}
36+
37+
/**
38+
* Check if an exception is thrown when trying to validate files that do not exist.
39+
* @throws ReflectionException
40+
*/
41+
public function testErrorIsThrownWhenInputFileDoesNotExist(): void
42+
{
43+
$this->expectException(\Exception::class);
44+
$this->expectExceptionMessage('input file does not exist: file_does_not_exist.xml');
45+
46+
$this->validateFiles(
47+
["file_does_not_exist.xml"],
48+
"output.svg"
49+
);
50+
}
51+
52+
/**
53+
* Check if an exception is thrown when the output file is not specified.
54+
* @throws ReflectionException
55+
*/
56+
public function testErrorIsThrownWhenOutputFileDoesNotExist(): void
57+
{
58+
$this->expectException(\Exception::class);
59+
$this->expectExceptionMessage('output file name is mandatory');
60+
61+
$this->validateFiles(
62+
[__DIR__ . "/test-input1.xml"],
63+
"" // Empty name to simulate missing output file
64+
);
65+
}
66+
67+
/**
68+
* @throws ReflectionException
69+
*/
70+
public function processFile(string $inputFile)
71+
{
72+
$method = (new \ReflectionClass($this->badgeComposer))->getMethod('processFile');
73+
74+
return $method->invoke($this->badgeComposer, $inputFile);
75+
}
76+
77+
/**
78+
* @throws ReflectionException
79+
*/
80+
public function testProcessTheCloverFileAndCalculateTheCoverage(): void
81+
{
82+
$this->processFile($this->inputFile);
83+
84+
$this->assertEquals(51, $this->badgeComposer->totalCoverage);
85+
}
86+
87+
/**
88+
* @throws ReflectionException
89+
*/
90+
public function testProcessMultipleCloverFilesAndCalculateTheCoverage(): void
91+
{
92+
$this->processFile($this->inputFile);
93+
$this->processFile($this->inputFile2);
94+
95+
$this->assertEquals(94, $this->badgeComposer->totalCoverage);
96+
}
97+
}

Tests/test-input1.xml

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<coverage generated="1717008513">
3+
<project timestamp="1717008513">
4+
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Base/ScriptsEnqueue.php">
5+
<class name="NUK\WP\Inc\Base\ScriptsEnqueue" namespace="global">
6+
<metrics complexity="2" methods="2" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="10" coveredstatements="0" elements="12" coveredelements="12"/>
7+
</class>
8+
<line num="28" type="method" name="register" visibility="public" complexity="1" crap="2" count="0"/>
9+
<line num="29" type="stmt" count="0"/>
10+
<line num="37" type="method" name="enqueue_admin_scripts" visibility="public" complexity="1" crap="2" count="0"/>
11+
<line num="38" type="stmt" count="0"/>
12+
<line num="39" type="stmt" count="0"/>
13+
<line num="41" type="stmt" count="0"/>
14+
<line num="42" type="stmt" count="0"/>
15+
<line num="43" type="stmt" count="0"/>
16+
<line num="44" type="stmt" count="0"/>
17+
<line num="45" type="stmt" count="0"/>
18+
<line num="46" type="stmt" count="0"/>
19+
<line num="47" type="stmt" count="0"/>
20+
<metrics loc="50" ncloc="27" classes="1" methods="2" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="10" coveredstatements="0" elements="12" coveredelements="4"/>
21+
</file>
22+
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Init.php">
23+
<class name="NUK\WP\Inc\Init" namespace="global">
24+
<metrics complexity="9" methods="5" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="15" coveredstatements="0" elements="20" coveredelements="12"/>
25+
</class>
26+
<line num="32" type="method" name="get_services" visibility="public" complexity="1" crap="2" count="0"/>
27+
<line num="33" type="stmt" count="0"/>
28+
<line num="34" type="stmt" count="0"/>
29+
<line num="35" type="stmt" count="0"/>
30+
<line num="43" type="method" name="register_services" visibility="public" complexity="2" crap="6" count="0"/>
31+
<line num="44" type="stmt" count="0"/>
32+
<line num="45" type="stmt" count="0"/>
33+
<line num="46" type="stmt" count="0"/>
34+
<line num="55" type="method" name="deactivate" visibility="public" complexity="3" crap="12" count="0"/>
35+
<line num="56" type="stmt" count="0"/>
36+
<line num="57" type="stmt" count="0"/>
37+
<line num="58" type="stmt" count="0"/>
38+
<line num="59" type="stmt" count="0"/>
39+
<line num="71" type="method" name="register" visibility="private" complexity="1" crap="2" count="0"/>
40+
<line num="72" type="stmt" count="0"/>
41+
<line num="83" type="method" name="instantiate" visibility="private" complexity="2" crap="6" count="0"/>
42+
<line num="84" type="stmt" count="0"/>
43+
<line num="86" type="stmt" count="0"/>
44+
<line num="88" type="stmt" count="0"/>
45+
<line num="91" type="stmt" count="0"/>
46+
<metrics loc="94" ncloc="50" classes="1" methods="5" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="15" coveredstatements="0" elements="20" coveredelements="5"/>
47+
</file>
48+
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Interfaces/Registrable.php">
49+
<metrics loc="29" ncloc="11" classes="0" methods="0" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="0" coveredstatements="0" elements="0" coveredelements="4"/>
50+
</file>
51+
<metrics files="3" loc="173" ncloc="88" classes="2" methods="7" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="25" coveredstatements="0" elements="32" coveredelements="12"/>
52+
</project>
53+
</coverage>

Tests/test-input2.xml

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<coverage generated="1717008513">
3+
<project timestamp="1717008513">
4+
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Base/ScriptsEnqueue.php">
5+
<class name="NUK\WP\Inc\Base\ScriptsEnqueue" namespace="global">
6+
<metrics complexity="2" methods="2" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="10" coveredstatements="0" elements="12" coveredelements="33"/>
7+
</class>
8+
<line num="28" type="method" name="register" visibility="public" complexity="1" crap="2" count="0"/>
9+
<line num="29" type="stmt" count="0"/>
10+
<line num="37" type="method" name="enqueue_admin_scripts" visibility="public" complexity="1" crap="2" count="0"/>
11+
<line num="38" type="stmt" count="0"/>
12+
<line num="39" type="stmt" count="0"/>
13+
<line num="41" type="stmt" count="0"/>
14+
<line num="42" type="stmt" count="0"/>
15+
<line num="43" type="stmt" count="0"/>
16+
<line num="44" type="stmt" count="0"/>
17+
<line num="45" type="stmt" count="0"/>
18+
<line num="46" type="stmt" count="0"/>
19+
<line num="47" type="stmt" count="0"/>
20+
<metrics loc="50" ncloc="27" classes="1" methods="2" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="10" coveredstatements="0" elements="12" coveredelements="0"/>
21+
</file>
22+
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Init.php">
23+
<class name="NUK\WP\Inc\Init" namespace="global">
24+
<metrics complexity="9" methods="5" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="15" coveredstatements="0" elements="20" coveredelements="0"/>
25+
</class>
26+
<line num="32" type="method" name="get_services" visibility="public" complexity="1" crap="2" count="0"/>
27+
<line num="33" type="stmt" count="0"/>
28+
<line num="34" type="stmt" count="0"/>
29+
<line num="35" type="stmt" count="0"/>
30+
<line num="43" type="method" name="register_services" visibility="public" complexity="2" crap="6" count="0"/>
31+
<line num="44" type="stmt" count="0"/>
32+
<line num="45" type="stmt" count="0"/>
33+
<line num="46" type="stmt" count="0"/>
34+
<line num="55" type="method" name="deactivate" visibility="public" complexity="3" crap="12" count="0"/>
35+
<line num="56" type="stmt" count="0"/>
36+
<line num="57" type="stmt" count="0"/>
37+
<line num="58" type="stmt" count="0"/>
38+
<line num="59" type="stmt" count="0"/>
39+
<line num="71" type="method" name="register" visibility="private" complexity="1" crap="2" count="0"/>
40+
<line num="72" type="stmt" count="0"/>
41+
<line num="83" type="method" name="instantiate" visibility="private" complexity="2" crap="6" count="0"/>
42+
<line num="84" type="stmt" count="0"/>
43+
<line num="86" type="stmt" count="0"/>
44+
<line num="88" type="stmt" count="0"/>
45+
<line num="91" type="stmt" count="0"/>
46+
<metrics loc="94" ncloc="50" classes="1" methods="5" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="15" coveredstatements="0" elements="20" coveredelements="0"/>
47+
</file>
48+
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Interfaces/Registrable.php">
49+
<metrics loc="29" ncloc="11" classes="0" methods="0" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="0" coveredstatements="0" elements="0" coveredelements="0"/>
50+
</file>
51+
<metrics files="3" loc="173" ncloc="88" classes="2" methods="7" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="25" coveredstatements="0" elements="32" coveredelements="0"/>
52+
</project>
53+
</coverage>
File renamed without changes.

composer.json

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codebtech/coveragebadge",
3-
"description": "Creates code coverage badge from a Clover XML file.",
3+
"description": "Creates code coverage badge from Clover XML files.",
44
"type": "library",
55
"license": "MIT",
66
"homepage": "https://github.com/codebtech/coveragebadge",
@@ -10,7 +10,26 @@
1010
"homepage": "https://github.com/m0hanraj"
1111
}
1212
],
13+
"autoload": {
14+
"psr-4": {
15+
"BadgeManager\\": [""]
16+
}
17+
},
18+
"autoload-dev": {
19+
"psr-4": {
20+
"BadgeManager\\Tests\\": "Tests/"
21+
}
22+
},
1323
"bin": [
1424
"bin/coverage-badge"
15-
]
25+
],
26+
"require-dev": {
27+
"squizlabs/php_codesniffer": "^3.10",
28+
"phpunit/phpunit": "^11.1"
29+
},
30+
"scripts": {
31+
"lint": "phpcs --ignore=/vendor/* --standard=PSR12 .",
32+
"lint:fix": "phpcbf --ignore=/vendor/* --standard=PSR12 .",
33+
"test": "phpunit --testdox"
34+
}
1635
}

0 commit comments

Comments
 (0)