Skip to content

Add BadgeComposer class and update existing files #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions Includes/BadgeComposer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

namespace BadgeManager\Includes;

use Exception;

/**
* Class BadgeComposer
*
* This class is responsible for generating a coverage badge based on the input files provided.
*
* @package BadgeManager
*/
class BadgeComposer
{
private array $inputFiles;
private string $outputFile;
private string $coverageName;
public int $totalCoverage = 0;
private int $totalElements = 0;
private int $checkedElements = 0;

/**
* @throws Exception
*/
public function __construct(string $inputFiles, string $outputFile, string $coverageName = 'coverage')
{
$this->inputFiles = explode(',', $inputFiles);
$this->outputFile = $outputFile;
$this->coverageName = $coverageName;

$this->validateFiles($this->inputFiles, $this->outputFile);
}

/**
* Validates the input files and output file.
*
* This method checks if the input files exist and if the output file is provided.
*
* @param array $inputFiles The array of input files to validate.
* @param string $outputFile The output file to validate.
*
* @return void
* @throws Exception If any of the input files do not exist or if the output file is not provided.
*/
private function validateFiles(array $inputFiles, string $outputFile): void
{
foreach ($inputFiles as $inputFile) {
if (!file_exists($inputFile)) {
throw new \Exception("input file does not exist: " . $inputFile);
}
}

if (empty($outputFile)) {
throw new \Exception("output file name is mandatory");
}
}

/**
* Runs the coverage process for each input file.
*
* This method iterates over the input files array and processes each file using the `processFile` method.
* After processing all input files, the coverage is finalized using the `finalizeCoverage` method.
*
* @return void
* @throws Exception
*/
public function run(): void
{
foreach ($this->inputFiles as $inputFile) {
$this->processFile($inputFile);
}
$this->finalizeCoverage();
}

/**
* Process a file and update the total and checked elements count.
*
* @param string $inputFile The path to the XML file to process.
*
* @return void
* @throws Exception When there is an error processing the file.
*
*/
private function processFile(string $inputFile): void
{
try {
$xml = new \SimpleXMLElement(file_get_contents($inputFile));
$metrics = $xml->xpath('//metrics');
foreach ($metrics as $metric) {
$this->totalElements += (int) $metric['elements'];
$this->checkedElements += (int) $metric['coveredelements'];
}

$coverage = round(($this->totalElements === 0) ? 0 : ($this->checkedElements / $this->totalElements) * 100);
$this->totalCoverage += $coverage;
} catch (Exception $e) {
throw new Exception('Error processing file: ' . $inputFile);
}
}

/**
* Finalize the coverage report by generating a badge with the average coverage across all input files.
*
* @return void
* @throws Exception If there is an error generating the badge.
*/
private function finalizeCoverage(): void
{
$totalCoverage = $this->totalCoverage / count($this->inputFiles); // Average coverage across all files
$template = file_get_contents(__DIR__ . '/../badge-templates/badge.svg');

$template = str_replace('{{ total }}', $totalCoverage, $template);

$template = str_replace('{{ coverage }}', $this->coverageName, $template);

$color = '#a4a61d'; // Yellow-Green
if ($totalCoverage < 40) {
$color = '#e05d44'; // Red
} elseif ($totalCoverage < 60) {
$color = '#fe7d37'; // Orange
} elseif ($totalCoverage < 75) {
$color = '#dfb317'; // Yellow
} elseif ($totalCoverage < 95) {
$color = '#97CA00'; // Green
} elseif ($totalCoverage <= 100) {
$color = '#4c1'; // Bright Green
}

$template = str_replace('{{ total }}', $totalCoverage, $template);
$template = str_replace('{{ color }}', $color, $template);

file_put_contents($this->outputFile, $template);
}
}
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ Composer

`composer require codebtech/coveragebadge --dev`

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


## Usage

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)
2. Run `vendor/bin/coverage-badge /path/to/clover.xml /path/to/badge/destination.svg type_of_test`
* e.g. `vendor/bin/php-coverage-badge build/clover.xml report/coverage.svg unit-test`
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.
2. Run `vendor/bin/coverage-badge /path/to/clover.xml /path/to/badge/destination.svg test-name`
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`
97 changes: 97 additions & 0 deletions Tests/BadgeComposerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace BadgeManager\Tests;

use Exception;
use ReflectionException;
use PHPUnit\Framework\TestCase;
use BadgeManager\Includes\BadgeComposer;

class BadgeComposerTest extends TestCase
{
private BadgeComposer $badgeComposer;

private string $inputFile = __DIR__ . "/test-input1.xml";
private string $inputFile2 = __DIR__ . "/test-input2.xml";
private string $outputFile = "output.svg";
private string $coverageName = "unit";

/**
* @throws Exception
*/
public function setUp(): void
{
$this->badgeComposer = new BadgeComposer($this->inputFile, $this->outputFile, $this->coverageName);
}

/**
* @throws ReflectionException
*/
public function validateFiles(array $files, string $output)
{
$method = (new \ReflectionClass($this->badgeComposer))->getMethod('validateFiles');

return $method->invoke($this->badgeComposer, $files, $output);
}

/**
* Check if an exception is thrown when trying to validate files that do not exist.
* @throws ReflectionException
*/
public function testErrorIsThrownWhenInputFileDoesNotExist(): void
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('input file does not exist: file_does_not_exist.xml');

$this->validateFiles(
["file_does_not_exist.xml"],
"output.svg"
);
}

/**
* Check if an exception is thrown when the output file is not specified.
* @throws ReflectionException
*/
public function testErrorIsThrownWhenOutputFileDoesNotExist(): void
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('output file name is mandatory');

$this->validateFiles(
[__DIR__ . "/test-input1.xml"],
"" // Empty name to simulate missing output file
);
}

/**
* @throws ReflectionException
*/
public function processFile(string $inputFile)
{
$method = (new \ReflectionClass($this->badgeComposer))->getMethod('processFile');

return $method->invoke($this->badgeComposer, $inputFile);
}

/**
* @throws ReflectionException
*/
public function testProcessTheCloverFileAndCalculateTheCoverage(): void
{
$this->processFile($this->inputFile);

$this->assertEquals(51, $this->badgeComposer->totalCoverage);
}

/**
* @throws ReflectionException
*/
public function testProcessMultipleCloverFilesAndCalculateTheCoverage(): void
{
$this->processFile($this->inputFile);
$this->processFile($this->inputFile2);

$this->assertEquals(94, $this->badgeComposer->totalCoverage);
}
}
53 changes: 53 additions & 0 deletions Tests/test-input1.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1717008513">
<project timestamp="1717008513">
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Base/ScriptsEnqueue.php">
<class name="NUK\WP\Inc\Base\ScriptsEnqueue" namespace="global">
<metrics complexity="2" methods="2" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="10" coveredstatements="0" elements="12" coveredelements="12"/>
</class>
<line num="28" type="method" name="register" visibility="public" complexity="1" crap="2" count="0"/>
<line num="29" type="stmt" count="0"/>
<line num="37" type="method" name="enqueue_admin_scripts" visibility="public" complexity="1" crap="2" count="0"/>
<line num="38" type="stmt" count="0"/>
<line num="39" type="stmt" count="0"/>
<line num="41" type="stmt" count="0"/>
<line num="42" type="stmt" count="0"/>
<line num="43" type="stmt" count="0"/>
<line num="44" type="stmt" count="0"/>
<line num="45" type="stmt" count="0"/>
<line num="46" type="stmt" count="0"/>
<line num="47" type="stmt" count="0"/>
<metrics loc="50" ncloc="27" classes="1" methods="2" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="10" coveredstatements="0" elements="12" coveredelements="4"/>
</file>
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Init.php">
<class name="NUK\WP\Inc\Init" namespace="global">
<metrics complexity="9" methods="5" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="15" coveredstatements="0" elements="20" coveredelements="12"/>
</class>
<line num="32" type="method" name="get_services" visibility="public" complexity="1" crap="2" count="0"/>
<line num="33" type="stmt" count="0"/>
<line num="34" type="stmt" count="0"/>
<line num="35" type="stmt" count="0"/>
<line num="43" type="method" name="register_services" visibility="public" complexity="2" crap="6" count="0"/>
<line num="44" type="stmt" count="0"/>
<line num="45" type="stmt" count="0"/>
<line num="46" type="stmt" count="0"/>
<line num="55" type="method" name="deactivate" visibility="public" complexity="3" crap="12" count="0"/>
<line num="56" type="stmt" count="0"/>
<line num="57" type="stmt" count="0"/>
<line num="58" type="stmt" count="0"/>
<line num="59" type="stmt" count="0"/>
<line num="71" type="method" name="register" visibility="private" complexity="1" crap="2" count="0"/>
<line num="72" type="stmt" count="0"/>
<line num="83" type="method" name="instantiate" visibility="private" complexity="2" crap="6" count="0"/>
<line num="84" type="stmt" count="0"/>
<line num="86" type="stmt" count="0"/>
<line num="88" type="stmt" count="0"/>
<line num="91" type="stmt" count="0"/>
<metrics loc="94" ncloc="50" classes="1" methods="5" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="15" coveredstatements="0" elements="20" coveredelements="5"/>
</file>
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Interfaces/Registrable.php">
<metrics loc="29" ncloc="11" classes="0" methods="0" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="0" coveredstatements="0" elements="0" coveredelements="4"/>
</file>
<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"/>
</project>
</coverage>
53 changes: 53 additions & 0 deletions Tests/test-input2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1717008513">
<project timestamp="1717008513">
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Base/ScriptsEnqueue.php">
<class name="NUK\WP\Inc\Base\ScriptsEnqueue" namespace="global">
<metrics complexity="2" methods="2" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="10" coveredstatements="0" elements="12" coveredelements="33"/>
</class>
<line num="28" type="method" name="register" visibility="public" complexity="1" crap="2" count="0"/>
<line num="29" type="stmt" count="0"/>
<line num="37" type="method" name="enqueue_admin_scripts" visibility="public" complexity="1" crap="2" count="0"/>
<line num="38" type="stmt" count="0"/>
<line num="39" type="stmt" count="0"/>
<line num="41" type="stmt" count="0"/>
<line num="42" type="stmt" count="0"/>
<line num="43" type="stmt" count="0"/>
<line num="44" type="stmt" count="0"/>
<line num="45" type="stmt" count="0"/>
<line num="46" type="stmt" count="0"/>
<line num="47" type="stmt" count="0"/>
<metrics loc="50" ncloc="27" classes="1" methods="2" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="10" coveredstatements="0" elements="12" coveredelements="0"/>
</file>
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Init.php">
<class name="NUK\WP\Inc\Init" namespace="global">
<metrics complexity="9" methods="5" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="15" coveredstatements="0" elements="20" coveredelements="0"/>
</class>
<line num="32" type="method" name="get_services" visibility="public" complexity="1" crap="2" count="0"/>
<line num="33" type="stmt" count="0"/>
<line num="34" type="stmt" count="0"/>
<line num="35" type="stmt" count="0"/>
<line num="43" type="method" name="register_services" visibility="public" complexity="2" crap="6" count="0"/>
<line num="44" type="stmt" count="0"/>
<line num="45" type="stmt" count="0"/>
<line num="46" type="stmt" count="0"/>
<line num="55" type="method" name="deactivate" visibility="public" complexity="3" crap="12" count="0"/>
<line num="56" type="stmt" count="0"/>
<line num="57" type="stmt" count="0"/>
<line num="58" type="stmt" count="0"/>
<line num="59" type="stmt" count="0"/>
<line num="71" type="method" name="register" visibility="private" complexity="1" crap="2" count="0"/>
<line num="72" type="stmt" count="0"/>
<line num="83" type="method" name="instantiate" visibility="private" complexity="2" crap="6" count="0"/>
<line num="84" type="stmt" count="0"/>
<line num="86" type="stmt" count="0"/>
<line num="88" type="stmt" count="0"/>
<line num="91" type="stmt" count="0"/>
<metrics loc="94" ncloc="50" classes="1" methods="5" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="15" coveredstatements="0" elements="20" coveredelements="0"/>
</file>
<file name="/var/www/html/wp-content/plugins/nuk-wp-block-plugin-template/inc/Interfaces/Registrable.php">
<metrics loc="29" ncloc="11" classes="0" methods="0" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="0" coveredstatements="0" elements="0" coveredelements="0"/>
</file>
<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"/>
</project>
</coverage>
File renamed without changes
23 changes: 21 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codebtech/coveragebadge",
"description": "Creates code coverage badge from a Clover XML file.",
"description": "Creates code coverage badge from Clover XML files.",
"type": "library",
"license": "MIT",
"homepage": "https://github.com/codebtech/coveragebadge",
Expand All @@ -10,7 +10,26 @@
"homepage": "https://github.com/m0hanraj"
}
],
"autoload": {
"psr-4": {
"BadgeManager\\": [""]
}
},
"autoload-dev": {
"psr-4": {
"BadgeManager\\Tests\\": "Tests/"
}
},
"bin": [
"bin/coverage-badge"
]
],
"require-dev": {
"squizlabs/php_codesniffer": "^3.10",
"phpunit/phpunit": "^11.1"
},
"scripts": {
"lint": "phpcs --ignore=/vendor/* --standard=PSR12 .",
"lint:fix": "phpcbf --ignore=/vendor/* --standard=PSR12 .",
"test": "phpunit --testdox"
}
}
Loading