Skip to content

Commit

Permalink
cleanup extension CodeCoverage
Browse files Browse the repository at this point in the history
  • Loading branch information
jakoch committed Dec 25, 2016
1 parent 9ab8d12 commit 85c29e8
Show file tree
Hide file tree
Showing 19 changed files with 356 additions and 376 deletions.
5 changes: 3 additions & 2 deletions extensions/coverage/autocoverage.php
@@ -1,9 +1,10 @@
<?php

/**
* Include this in any file to start coverage, coverage will automatically end when process dies.
* Include this in any file to start coverage,
* coverage will automatically end when process dies.
*/
require_once dirname(__FILE__) . '/coverage.php';
require_once __DIR__ . '/coverage.php';

if (CodeCoverage::isCoverageOn()) {
$coverage = CodeCoverage::getInstance();
Expand Down
3 changes: 1 addition & 2 deletions extensions/coverage/bin/php-coverage-close.php
Expand Up @@ -2,8 +2,7 @@
/**
* Close code coverage data collection, next step is to generate report
*/
require_once dirname(__FILE__) . '/../coverage.php';
require_once __DIR__ . '/../coverage.php';

$cc = CodeCoverage::getInstance();
$cc->readSettings();
$cc->writeUntouched();
16 changes: 8 additions & 8 deletions extensions/coverage/bin/php-coverage-open.php
Expand Up @@ -7,22 +7,22 @@
# optional arguments:
# --include=<some filepath regexp> these files should be included coverage report
# --exclude=<come filepath regexp> these files should not be included in coverage report
# --maxdepth=2 when considering which file were not touched, scan directories
# --maxdepth=2 when considering which file were not touched, scan directories
#
# Example:
# php-coverage-open.php --include='.*\.php$' --include='.*\.inc$' --exclude='.*/tests/.*'
# Example:
# php-coverage-open.php --include='.*\.php$' --include='.*\.inc$' --exclude='.*/tests/.*'


//include coverage files

require_once dirname(__FILE__) . '/../coverage_utils.php';
require_once dirname(__FILE__) . '/../coverage.php';
require_once __DIR__ . '/../coverage_utils.php';
require_once __DIR__ . '/../coverage.php';

$cc = new CodeCoverage();
$cc->log = 'coverage.sqlite';
$args = CoverageUtils::parseArguments($_SERVER['argv'], true);
$cc->includes = CoverageUtils::issetOr($args['include[]'], array('.*\.php$'));
$cc->excludes = CoverageUtils::issetOr($args['exclude[]']);
$cc->maxDirectoryDepth = (int) CoverageUtils::issetOr($args['maxdepth'], '1');
$cc->includes = CoverageUtils::issetOrDefault($args['include[]'], ['.*\.php$']);
$cc->excludes = CoverageUtils::issetOrDefault($args['exclude[]']);
$cc->maxDirectoryDepth = (int) CoverageUtils::issetOrDefault($args['maxdepth'], '1');
$cc->resetLog();
$cc->writeSettings();
11 changes: 5 additions & 6 deletions extensions/coverage/bin/php-coverage-report.php
Expand Up @@ -7,17 +7,16 @@
# --title='My Coverage Report' title the main page of your report

// include coverage files
require_once dirname(__FILE__) . '/../coverage_utils.php';
require_once dirname(__FILE__) . '/../coverage.php';
require_once dirname(__FILE__) . '/../coverage_reporter.php';
require_once __DIR__ . '/../coverage_utils.php';
require_once __DIR__ . '/../coverage.php';
require_once __DIR__ . '/../coverage_reporter.php';

$cc = CodeCoverage::getInstance();
$cc->readSettings();
$handler = new CoverageDataHandler($cc->log);
$report = new CoverageReporter();
$args = CoverageUtils::parseArguments($_SERVER['argv']);
$report->reportDir = CoverageUtils::issetOr($args['reportDir'], 'coverage-report');
$report->title = CoverageUtils::issetOr($args['title'], 'Simpletest Coverage');
$report->reportDir = CoverageUtils::issetOrDefault($args['reportDir'], 'coverage-report');
$report->title = CoverageUtils::issetOrDefault($args['title'], 'Simpletest Coverage');
$report->coverage = $handler->read();
$report->untouched = $handler->readUntouchedFiles();
$report->generate();
32 changes: 13 additions & 19 deletions extensions/coverage/coverage.php
@@ -1,9 +1,9 @@
<?php

require_once dirname(__FILE__) . '/coverage_data_handler.php';
require_once __DIR__ . '/coverage_data_handler.php';

/**
* SimpleTest Extension - CodeCoverage
* SimpleTest - CodeCoverage
*/
class CodeCoverage
{
Expand All @@ -16,14 +16,14 @@ class CodeCoverage
public $title = 'Code Coverage';

# NOTE: This assumes all code shares the same current working directory.
public $settingsFile = './code-coverage-settings.dat';
public $settingsFile = './coverage-settings.json';

public static $instance;

public function writeUntouched()
{
$touched = array_flip($this->getTouchedFiles());
$untouched = array();
$untouched = [];
$this->getUntouchedFiles($untouched, $touched, '.', '.');
$this->includeUntouchedFiles($untouched);
}
Expand Down Expand Up @@ -67,7 +67,6 @@ public function getUntouchedFiles(&$untouched, $touched, $parentPath, $rootPath,

public function resetLog()
{
error_log('reseting log');
$file = fopen($this->log, 'w');
if (!$file) {
throw new Exception('Could not create ' . $this->log);
Expand Down Expand Up @@ -105,32 +104,29 @@ public function stopCoverage()

public function readSettings()
{
if (file_exists($this->settingsFile)) {
$this->setSettings(file_get_contents($this->settingsFile));
} else {
if (!file_exists($this->settingsFile)) {
error_log('Could not find settings file ' . $this->settingsFile);
}

$this->setSettings(json_decode(file_get_contents($this->settingsFile), true));
}

public function writeSettings()
{
file_put_contents($this->settingsFile, $this->getSettings());
file_put_contents($this->settingsFile, json_encode($this->getSettings(), JSON_PRETTY_PRINT));
}

public function getSettings()
{
$data = array(
return [
'log' => realpath($this->log),
'includes' => $this->includes,
'excludes' => $this->excludes
);

return serialize($data);
];
}

public function setSettings($settings)
public function setSettings($data)
{
$data = unserialize($settings);
$this->log = $data['log'];
$this->includes = $data['includes'];
$this->excludes = $data['excludes'];
Expand Down Expand Up @@ -187,11 +183,9 @@ public function isDirectoryIncluded($dir, $directoryDepth)
public static function isCoverageOn()
{
$coverage = self::getInstance();
$coverage->readSettings();
if (empty($coverage->log) || !file_exists($coverage->log)) {
trigger_error('No coverage log');

return false;
if (empty($coverage->log) || !file_exists($coverage->log)) {
throw new Exception('Could not find the coverage log file.');
}

return true;
Expand Down
75 changes: 43 additions & 32 deletions extensions/coverage/coverage_calculator.php
@@ -1,5 +1,7 @@
<?php

require_once __DIR__ . '/coverage_utils.php';

class CoverageCalculator
{
public function coverageByFileVariables($file, $coverage)
Expand All @@ -8,18 +10,16 @@ public function coverageByFileVariables($file, $coverage)
if (!$hnd) {
throw new Exception("File $file is missing");
}
$lines = array();
$lines = [];
for ($i = 1; !feof($hnd); $i++) {
$line = fgets($hnd);
$lineCoverage = $this->lineCoverageCodeToStyleClass($coverage, $i);
$lines[$i] = array('lineCoverage' => $lineCoverage, 'code' => $line);
$lines[$i] = ['lineCoverage' => $lineCoverage, 'code' => $line];
}

fclose($hnd);

$var = compact('file', 'lines', 'coverage');

return $var;
return ['file' => $file, 'lines' => $lines, 'coverage' => $coverage];
}

public function lineCoverageCodeToStyleClass($coverage, $line)
Expand All @@ -41,61 +41,72 @@ public function lineCoverageCodeToStyleClass($coverage, $line)
return 'covered';
}

public function totalLoc($total, $coverage)
public function totalLinesOfCode($total, $coverage)
{
return $total + sizeof($coverage);
return $total + count($coverage);
}

/**
*
* https://xdebug.org/docs/code_coverage
*
* 1: this line was executed
* -1: this line was not executed
* -2: this line did not have executable code on it
*
* @param type $total
* @param type $line
* @return type
*/
public function lineCoverage($total, $line)
{
# NOTE: counting dead code as covered, as it's almost always an executable line
# strange artifact of xdebug or underlying system
return $total + ($line > 0 || $line == -2 ? 1 : 0);
}

public function totalCoverage($total, $coverage)
{
return $total + array_reduce($coverage, array(&$this, 'lineCoverage'));
return $total + array_reduce($coverage, array($this, 'lineCoverage'));
}

public static function reportFilename($filename)
public function percentCoverageForFile($file, $coverage)
{
return preg_replace('|[/\\\\]|', '_', $filename) . '.html';
}
$fileReport = CoverageUtils::reportFilename($file);

public function percentCoverageByFile($coverage, $file, &$results)
{
$byFileReport = self::reportFilename($file);

$loc = sizeof($coverage);
$loc = count($coverage);
if ($loc == 0) {
return 0;
}
$lineCoverage = array_reduce($coverage, array(&$this, 'lineCoverage'));
$lineCoverage = array_reduce($coverage, array($this, 'lineCoverage'));
$percentage = 100 * ($lineCoverage / $loc);
$results[0][$file] = array('byFileReport' => $byFileReport, 'percentage' => $percentage);
return ['fileReport' => $fileReport, 'percentage' => $percentage];
}

public function variables($coverage, $untouched)
{
$coverageByFile = array();
array_walk($coverage, array(&$this, 'percentCoverageByFile'), array(&$coverageByFile));
$coverageByFile = [];
foreach($coverage as $file => $lineCoverageData) {
$coverageByFile[$file] = $this->percentCoverageForFile($file, $lineCoverageData);
}

$totalLoc = array_reduce($coverage, array(&$this, 'totalLoc'));
$totalLinesOfCode = array_reduce($coverage, [$this, 'totalLinesOfCode']);

if ($totalLoc > 0) {
$totalLinesOfCoverage = array_reduce($coverage, array(&$this, 'totalCoverage'));
$totalPercentCoverage = 100 * ($totalLinesOfCoverage / $totalLoc);
if ($totalLinesOfCode > 0) {
$totalLinesOfCoverage = array_reduce($coverage, array($this, 'totalCoverage'));
$totalPercentCoverage = 100 * ($totalLinesOfCoverage / $totalLinesOfCode);
}

$untouchedPercentageDenominator = sizeof($coverage) + sizeof($untouched);
$untouchedPercentageDenominator = count($coverage) + count($untouched);
if ($untouchedPercentageDenominator > 0) {
$filesTouchedPercentage = 100 * sizeof($coverage) / $untouchedPercentageDenominator;
$filesTouchedPercentage = 100 * count($coverage) / $untouchedPercentageDenominator;
}

$var = compact('coverageByFile', 'totalPercentCoverage', 'totalLoc', 'totalLinesOfCoverage', 'filesTouchedPercentage');
$var['untouched'] = $untouched;

return $var;
return [
'coverageByFile' => $coverageByFile,
'totalPercentCoverage' => $totalPercentCoverage,
'totalLinesOfCode' => $totalLinesOfCode,
'totalLinesOfCoverage' => $totalLinesOfCoverage,
'filesTouchedPercentage' => $filesTouchedPercentage,
'untouched' => $untouched
];
}
}
51 changes: 24 additions & 27 deletions extensions/coverage/coverage_reporter.php
@@ -1,11 +1,11 @@
<?php

require_once dirname(__FILE__) . '/coverage_calculator.php';
require_once dirname(__FILE__) . '/coverage_utils.php';
require_once dirname(__FILE__) . '/simple_coverage_writer.php';
require_once __DIR__ . '/coverage_calculator.php';
require_once __DIR__ . '/coverage_utils.php';
require_once __DIR__ . '/coverage_writer.php';

/**
* Take aggregated coverage data and generate reports from it using smarty templates
* Take aggregated coverage data and generate reports from it.
*/
class CoverageReporter
{
Expand All @@ -15,49 +15,46 @@ class CoverageReporter
public $title = 'Coverage';
public $writer;
public $calculator;
public $summaryFile;

public function __construct()
{
$this->writer = new SimpleCoverageWriter();
$this->writer = new CoverageWriter();
$this->calculator = new CoverageCalculator();
}

public function generateSummaryReport($out)
{
$variables = $this->calculator->variables($this->coverage, $this->untouched);
$variables['title'] = $this->title;
$report = $this->writer->writeSummary($out, $variables);
fwrite($out, $report);
$this->summaryFile = $this->reportDir . '/index.html';
}

public function generate()
{
echo 'Generating Code Coverage Report';

CoverageUtils::mkdir($this->reportDir);

$index = $this->reportDir . '/index.html';
$hnd = fopen($index, 'w');
$this->generateSummaryReport($hnd);
fclose($hnd);
$this->generateSummaryReport();

foreach ($this->coverage as $file => $cov) {
$byFile = $this->reportDir . '/' . self::reportFilename($file);
$byFileHnd = fopen($byFile, 'w');
$this->generateCoverageByFile($byFileHnd, $file, $cov);
fclose($byFileHnd);
$this->generateCoverageByFile($file, $cov);
}

echo "generated report $index\n";
echo "Report generated: $this->summaryFile\n";
}

public function generateCoverageByFile($out, $file, $cov)
public function generateSummaryReport()
{
$variables = $this->calculator->coverageByFileVariables($file, $cov);
$variables['title'] = $this->title . ' - ' . $file;
$this->writer->writeByFile($out, $variables);
$variables = $this->calculator->variables($this->coverage, $this->untouched);
$variables['title'] = $this->title;

$this->writer->writeSummaryReport($this->summaryFile, $variables);
}

public static function reportFilename($filename)
public function generateCoverageByFile($file, $cov)
{
return preg_replace('|[:/\\\\]|i', '_', $filename) . '.html';
$reportFile = $this->reportDir . '/' . CoverageUtils::reportFilename($file);

$variables = $this->calculator->coverageByFileVariables($file, $cov);
$variables['title'] = $this->title . ' - ' . $file;

$this->writer->writeFileReport($reportFile, $variables);
}
}

0 comments on commit 85c29e8

Please sign in to comment.