Skip to content

Instantly share code, notes, and snippets.

@bodik
Created June 26, 2022 18:31
Show Gist options
  • Save bodik/0f03c4e0a2c1d87245af5dccfface4ac to your computer and use it in GitHub Desktop.
Save bodik/0f03c4e0a2c1d87245af5dccfface4ac to your computer and use it in GitHub Desktop.
Symfony coverage generator
<?php // phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols
/**
* coverage-checker2.php, improved version
* https://ocramius.github.io/blog/automated-code-coverage-check-for-github-pull-requests-with-travis
*/
namespace CoverageChecker;
use InvalidArgumentException;
use SimpleXmlElement;
/**
* coverage checker inspired by pycoverage output
*/
class CoverageChecker
{
/**
* https://stackoverflow.com/questions/13592547/how-to-group-numbers-in-ranges-using-php
*/
public static function formatSequence(array $numbers): string
{
$groups = [];
$result = [];
sort($numbers);
for ($i = 0; $i < count($numbers); $i++) {
if ($i > 0 && ($numbers[$i - 1] === $numbers[$i] - 1)) {
array_push($groups[count($groups) - 1], $numbers[$i]);
} else {
/* First value or no match, create a new group */
array_push($groups, [$numbers[$i]]);
}
}
foreach ($groups as $group) {
if (count($group) === 1) {
/* Single value */
array_push($result, $group[0]);
} else {
/* Range of values, minimum in [0], maximum in [count($group) - 1] */
array_push($result, "${group[0]}-${group[count($group) - 1]}");
}
}
return implode(',', $result);
}
/**
* return overall coverage statistic
*/
public static function overallCoverage(SimpleXMLElement $xml): float
{
$metrics = $xml->xpath('//metrics');
$totalElements = 0;
$checkedElements = 0;
foreach ($metrics as $metric) {
$totalElements += (int) $metric['elements'];
$checkedElements += (int) $metric['coveredelements'];
}
return ($checkedElements / $totalElements) * 100;
}
/**
* return coverage statistics array for single file
*/
public static function fileCoverage(SimpleXMLElement $fileElem): array
{
$name = (string) $fileElem['name'];
$metrics = $fileElem->xpath('./metrics')[0];
/* elements = conditionals + statements */
$elements = (int) $metrics['elements'];
$coveredelements = (int) $metrics['coveredelements'];
$miss = $elements - $coveredelements;
if ($elements === 0) {
$coverage = 100;
} else {
$coverage = ($coveredelements / $elements) * 100;
}
$missedLines = array_map(
function ($item) {
return (int) $item['num'];
},
$fileElem->xpath('./line[@count="0"]')
);
$missing = self::formatSequence($missedLines);
return [$name, $elements, $miss, $coverage, $missing];
}
/**
* main
*/
public static function main(string $inputFile, int $percentage): int
{
if (!file_exists($inputFile)) {
throw new InvalidArgumentException('Invalid input file provided');
}
if (($percentage < 0) || ($percentage > 100)) {
throw new InvalidArgumentException('Invalid pecrentage');
}
$xmldata = new SimpleXMLElement(file_get_contents($inputFile));
$covdata = array_map(['self', 'fileCoverage'], $xmldata->xpath('//file'));
$filenames = array_map(
function ($item) {
return $item[0];
},
$covdata
);
$longestFilename = max(array_map('strlen', $filenames));
$coverage = self::overallCoverage($xmldata);
$fmt = "%-${longestFilename}s %5s %5s %5s%% %s\n";
vprintf($fmt, ['Filename', 'Stmts', 'Miss', 'Cover', 'Missing']);
printf("%s\n", str_repeat('-', $longestFilename + 34));
foreach ($covdata as $item) {
vprintf($fmt, $item);
}
printf("%s\n", str_repeat('-', $longestFilename + 34));
vprintf($fmt, ['TOTAL', '', '', sprintf('%.2f', $coverage), '']);
if ($coverage < $percentage) {
return 1;
}
return 0;
}
}
CoverageChecker::main($argv[1], (int) $argv[2]);
coverage:
vendor/bin/simple-phpunit --stop-on-error --coverage-clove .coverage.clove
php bin/coverage_checker.php .coverage.clove 100 | tee .coverage.log
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment