adding coverage extension for simpletest

This commit is contained in:
Thomas Mueller 2012-08-26 01:35:25 +02:00
parent bae07faa34
commit 27eb665516
21 changed files with 1361 additions and 0 deletions

View File

@ -0,0 +1,29 @@
<?php
/**
* @package SimpleTest
* @subpackage Extensions
*/
/**
* Include this in any file to start coverage, coverage will automatically end
* when process dies.
*/
require_once(dirname(__FILE__) .'/coverage.php');
if (CodeCoverage::isCoverageOn()) {
$coverage = CodeCoverage::getInstance();
$coverage->startCoverage();
register_shutdown_function("stop_coverage");
}
function stop_coverage() {
# hack until i can think of a way to run tests first and w/o exiting
$autorun = function_exists("run_local_tests");
if ($autorun) {
$result = run_local_tests();
}
CodeCoverage::getInstance()->stopCoverage();
if ($autorun) {
exit($result ? 0 : 1);
}
}
?>

View File

@ -0,0 +1,14 @@
<?php
/**
* Close code coverage data collection, next step is to generate report
* @package SimpleTest
* @subpackage Extensions
*/
/**
* include coverage files
*/
require_once(dirname(__FILE__) . '/../coverage.php');
$cc = CodeCoverage::getInstance();
$cc->readSettings();
$cc->writeUntouched();
?>

View File

@ -0,0 +1,31 @@
<?php
/**
* Initialize code coverage data collection, next step is to run your tests
* with ini setting auto_prepend_file=autocoverage.php ...
*
* @package SimpleTest
* @subpackage Extensions
*/
# 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
#
# Example:
# php-coverage-open.php --include='.*\.php$' --include='.*\.inc$' --exclude='.*/tests/.*'
/**#@+
* include coverage files
*/
require_once(dirname(__FILE__) . '/../coverage_utils.php');
CoverageUtils::requireSqlite();
require_once(dirname(__FILE__) . '/../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->resetLog();
$cc->writeSettings();
?>

View File

@ -0,0 +1,29 @@
<?php
/**
* Generate a code coverage report
*
* @package SimpleTest
* @subpackage Extensions
*/
# optional arguments:
# --reportDir=some/directory the default is ./coverage-report
# --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');
/**#@-*/
$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->coverage = $handler->read();
$report->untouched = $handler->readUntouchedFiles();
$report->generate();
?>

View File

@ -0,0 +1,196 @@
<?php
/**
* @package SimpleTest
* @subpackage Extensions
*/
/**
* load coverage data handle
*/
require_once dirname(__FILE__) . '/coverage_data_handler.php';
/**
* Orchestrates code coverage both in this thread and in subthread under apache
* Assumes this is running on same machine as apache.
* @package SimpleTest
* @subpackage Extensions
*/
class CodeCoverage {
var $log;
var $root;
var $includes;
var $excludes;
var $directoryDepth;
var $maxDirectoryDepth = 20; // reasonable, otherwise arbitrary
var $title = "Code Coverage";
# NOTE: This assumes all code shares the same current working directory.
var $settingsFile = './code-coverage-settings.dat';
static $instance;
function writeUntouched() {
$touched = array_flip($this->getTouchedFiles());
$untouched = array();
$this->getUntouchedFiles($untouched, $touched, '.', '.');
$this->includeUntouchedFiles($untouched);
}
function &getTouchedFiles() {
$handler = new CoverageDataHandler($this->log);
$touched = $handler->getFilenames();
return $touched;
}
function includeUntouchedFiles($untouched) {
$handler = new CoverageDataHandler($this->log);
foreach ($untouched as $file) {
$handler->writeUntouchedFile($file);
}
}
function getUntouchedFiles(&$untouched, $touched, $parentPath, $rootPath, $directoryDepth = 1) {
$parent = opendir($parentPath);
while ($file = readdir($parent)) {
$path = "$parentPath/$file";
if (is_dir($path)) {
if ($file != '.' && $file != '..') {
if ($this->isDirectoryIncluded($path, $directoryDepth)) {
$this->getUntouchedFiles($untouched, $touched, $path, $rootPath, $directoryDepth + 1);
}
}
}
else if ($this->isFileIncluded($path)) {
$relativePath = CoverageDataHandler::ltrim($rootPath .'/', $path);
if (!array_key_exists($relativePath, $touched)) {
$untouched[] = $relativePath;
}
}
}
closedir($parent);
}
function resetLog() {
error_log('reseting log');
$new_file = fopen($this->log, "w");
if (!$new_file) {
throw new Exception("Could not create ". $this->log);
}
fclose($new_file);
if (!chmod($this->log, 0666)) {
throw new Exception("Could not change ownership on file ". $this->log);
}
$handler = new CoverageDataHandler($this->log);
$handler->createSchema();
}
function startCoverage() {
$this->root = getcwd();
if(!extension_loaded("xdebug")) {
throw new Exception("Could not load xdebug extension");
};
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
}
function stopCoverage() {
$cov = xdebug_get_code_coverage();
$this->filter($cov);
$data = new CoverageDataHandler($this->log);
chdir($this->root);
$data->write($cov);
unset($data); // release sqlite connection
xdebug_stop_code_coverage();
// make sure we wind up on same current working directory, otherwise
// coverage handler writer doesn't know what directory to chop off
chdir($this->root);
}
function readSettings() {
if (file_exists($this->settingsFile)) {
$this->setSettings(file_get_contents($this->settingsFile));
} else {
error_log("could not find file ". $this->settingsFile);
}
}
function writeSettings() {
file_put_contents($this->settingsFile, $this->getSettings());
}
function getSettings() {
$data = array(
'log' => realpath($this->log),
'includes' => $this->includes,
'excludes' => $this->excludes);
return serialize($data);
}
function setSettings($settings) {
$data = unserialize($settings);
$this->log = $data['log'];
$this->includes = $data['includes'];
$this->excludes = $data['excludes'];
}
function filter(&$coverage) {
foreach ($coverage as $file => $line) {
if (!$this->isFileIncluded($file)) {
unset($coverage[$file]);
}
}
}
function isFileIncluded($file) {
if (!empty($this->excludes)) {
foreach ($this->excludes as $path) {
if (preg_match('|' . $path . '|', $file)) {
return False;
}
}
}
if (!empty($this->includes)) {
foreach ($this->includes as $path) {
if (preg_match('|' . $path . '|', $file)) {
return True;
}
}
return False;
}
return True;
}
function isDirectoryIncluded($dir, $directoryDepth) {
if ($directoryDepth >= $this->maxDirectoryDepth) {
return false;
}
if (isset($this->excludes)) {
foreach ($this->excludes as $path) {
if (preg_match('|' . $path . '|', $dir)) {
return False;
}
}
}
return True;
}
static function isCoverageOn() {
$coverage = self::getInstance();
$coverage->readSettings();
if (empty($coverage->log) || !file_exists($coverage->log)) {
trigger_error('No coverage log');
return False;
}
return True;
}
static function getInstance() {
if (self::$instance == NULL) {
self::$instance = new CodeCoverage();
self::$instance->readSettings();
}
return self::$instance;
}
}
?>

View File

@ -0,0 +1,98 @@
<?php
/**
* @package SimpleTest
* @subpackage Extensions
*/
/**
* @package SimpleTest
* @subpackage Extensions
*/
class CoverageCalculator {
function coverageByFileVariables($file, $coverage) {
$hnd = fopen($file, 'r');
if ($hnd == null) {
throw new Exception("File $file is missing");
}
$lines = array();
for ($i = 1; !feof($hnd); $i++) {
$line = fgets($hnd);
$lineCoverage = $this->lineCoverageCodeToStyleClass($coverage, $i);
$lines[$i] = array('lineCoverage' => $lineCoverage, 'code' => $line);
}
fclose($hnd);
$var = compact('file', 'lines', 'coverage');
return $var;
}
function lineCoverageCodeToStyleClass($coverage, $line) {
if (!array_key_exists($line, $coverage)) {
return "comment";
}
$code = $coverage[$line];
if (empty($code)) {
return "comment";
}
switch ($code) {
case -1:
return "missed";
case -2:
return "dead";
}
return "covered";
}
function totalLoc($total, $coverage) {
return $total + sizeof($coverage);
}
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);
}
function totalCoverage($total, $coverage) {
return $total + array_reduce($coverage, array(&$this, "lineCoverage"));
}
static function reportFilename($filename) {
return preg_replace('|[/\\\\]|', '_', $filename) . '.html';
}
function percentCoverageByFile($coverage, $file, &$results) {
$byFileReport = self::reportFilename($file);
$loc = sizeof($coverage);
if ($loc == 0)
return 0;
$lineCoverage = array_reduce($coverage, array(&$this, "lineCoverage"));
$percentage = 100 * ($lineCoverage / $loc);
$results[0][$file] = array('byFileReport' => $byFileReport, 'percentage' => $percentage);
}
function variables($coverage, $untouched) {
$coverageByFile = array();
array_walk($coverage, array(&$this, "percentCoverageByFile"), array(&$coverageByFile));
$totalLoc = array_reduce($coverage, array(&$this, "totalLoc"));
if ($totalLoc > 0) {
$totalLinesOfCoverage = array_reduce($coverage, array(&$this, "totalCoverage"));
$totalPercentCoverage = 100 * ($totalLinesOfCoverage / $totalLoc);
}
$untouchedPercentageDenominator = sizeof($coverage) + sizeof($untouched);
if ($untouchedPercentageDenominator > 0) {
$filesTouchedPercentage = 100 * sizeof($coverage) / $untouchedPercentageDenominator;
}
$var = compact('coverageByFile', 'totalPercentCoverage', 'totalLoc', 'totalLinesOfCoverage', 'filesTouchedPercentage');
$var['untouched'] = $untouched;
return $var;
}
}
?>

View File

@ -0,0 +1,125 @@
<?php
/**
* @package SimpleTest
* @subpackage Extensions
*/
/**
* @todo which db abstraction layer is this?
*/
require_once 'DB/sqlite.php';
/**
* Persists code coverage data into SQLite database and aggregate data for convienent
* interpretation in report generator. Be sure to not to keep an instance longer
* than you have, otherwise you risk overwriting database edits from another process
* also trying to make updates.
* @package SimpleTest
* @subpackage Extensions
*/
class CoverageDataHandler {
var $db;
function __construct($filename) {
$this->filename = $filename;
$this->db = new SQLiteDatabase($filename);
if (empty($this->db)) {
throw new Exception("Could not create sqlite db ". $filename);
}
}
function createSchema() {
$this->db->queryExec("create table untouched (filename text)");
$this->db->queryExec("create table coverage (name text, coverage text)");
}
function &getFilenames() {
$filenames = array();
$cursor = $this->db->unbufferedQuery("select distinct name from coverage");
while ($row = $cursor->fetch()) {
$filenames[] = $row[0];
}
return $filenames;
}
function write($coverage) {
foreach ($coverage as $file => $lines) {
$coverageStr = serialize($lines);
$relativeFilename = self::ltrim(getcwd() . '/', $file);
$sql = "insert into coverage (name, coverage) values ('$relativeFilename', '$coverageStr')";
# if this fails, check you have write permission
$this->db->queryExec($sql);
}
}
function read() {
$coverage = array_flip($this->getFilenames());
foreach($coverage as $file => $garbage) {
$coverage[$file] = $this->readFile($file);
}
return $coverage;
}
function &readFile($file) {
$sql = "select coverage from coverage where name = '$file'";
$aggregate = array();
$result = $this->db->query($sql);
while ($result->valid()) {
$row = $result->current();
$this->aggregateCoverage($aggregate, unserialize($row[0]));
$result->next();
}
return $aggregate;
}
function aggregateCoverage(&$total, $next) {
foreach ($next as $lineno => $code) {
if (!isset($total[$lineno])) {
$total[$lineno] = $code;
} else {
$total[$lineno] = $this->aggregateCoverageCode($total[$lineno], $code);
}
}
}
function aggregateCoverageCode($code1, $code2) {
switch($code1) {
case -2: return -2;
case -1: return $code2;
default:
switch ($code2) {
case -2: return -2;
case -1: return $code1;
}
}
return $code1 + $code2;
}
static function ltrim($cruft, $pristine) {
if(stripos($pristine, $cruft) === 0) {
return substr($pristine, strlen($cruft));
}
return $pristine;
}
function writeUntouchedFile($file) {
$relativeFile = CoverageDataHandler::ltrim('./', $file);
$sql = "insert into untouched values ('$relativeFile')";
$this->db->queryExec($sql);
}
function &readUntouchedFiles() {
$untouched = array();
$result = $this->db->query("select filename from untouched order by filename");
while ($result->valid()) {
$row = $result->current();
$untouched[] = $row[0];
$result->next();
}
return $untouched;
}
}
?>

View File

@ -0,0 +1,68 @@
<?php
/**
* @package SimpleTest
* @subpackage Extensions
*/
/**#@+
* include additional coverage files
*/
require_once dirname(__FILE__) .'/coverage_calculator.php';
require_once dirname(__FILE__) .'/coverage_utils.php';
require_once dirname(__FILE__) .'/simple_coverage_writer.php';
/**#@-*/
/**
* Take aggregated coverage data and generate reports from it using smarty
* templates
* @package SimpleTest
* @subpackage Extensions
*/
class CoverageReporter {
var $coverage;
var $untouched;
var $reportDir;
var $title = 'Coverage';
var $writer;
var $calculator;
function __construct() {
$this->writer = new SimpleCoverageWriter();
$this->calculator = new CoverageCalculator();
}
function generateSummaryReport($out) {
$variables = $this->calculator->variables($this->coverage, $this->untouched);
$variables['title'] = $this->title;
$report = $this->writer->writeSummary($out, $variables);
fwrite($out, $report);
}
function generate() {
CoverageUtils::mkdir($this->reportDir);
$index = $this->reportDir .'/index.html';
$hnd = fopen($index, 'w');
$this->generateSummaryReport($hnd);
fclose($hnd);
foreach ($this->coverage as $file => $cov) {
$byFile = $this->reportDir .'/'. self::reportFilename($file);
$byFileHnd = fopen($byFile, 'w');
$this->generateCoverageByFile($byFileHnd, $file, $cov);
fclose($byFileHnd);
}
echo "generated report $index\n";
}
function generateCoverageByFile($out, $file, $cov) {
$variables = $this->calculator->coverageByFileVariables($file, $cov);
$variables['title'] = $this->title .' - '. $file;
$this->writer->writeByFile($out, $variables);
}
static function reportFilename($filename) {
return preg_replace('|[/\\\\]|', '_', $filename) . '.html';
}
}
?>

View File

@ -0,0 +1,114 @@
<?php
/**
* @package SimpleTest
* @subpackage Extensions
*/
/**
* @package SimpleTest
* @subpackage Extensions
*/
class CoverageUtils {
static function mkdir($dir) {
if (!file_exists($dir)) {
mkdir($dir, 0777, True);
} else {
if (!is_dir($dir)) {
throw new Exception($dir .' exists as a file, not a directory');
}
}
}
static function requireSqlite() {
if (!self::isPackageClassAvailable('DB/sqlite.php', 'SQLiteDatabase')) {
echo "sqlite library is required to be installed and available in include_path";
exit(1);
}
}
static function isPackageClassAvailable($includeFile, $class) {
@include_once($includeFile);
return class_exists($class);
}
/**
* Parses simple parameters from CLI.
*
* Puts trailing parameters into string array in 'extraArguments'
*
* Example:
* $args = CoverageUtil::parseArguments($_SERVER['argv']);
* if ($args['verbose']) echo "Verbose Mode On\n";
* $files = $args['extraArguments'];
*
* Example CLI:
* --foo=blah -x -h some trailing arguments
*
* if multiValueMode is true
* Example CLI:
* --include=a --include=b --exclude=c
* Then
* $args = CoverageUtil::parseArguments($_SERVER['argv']);
* $args['include[]'] will equal array('a', 'b')
* $args['exclude[]'] will equal array('c')
* $args['exclude'] will equal c
* $args['include'] will equal b NOTE: only keeps last value
*
* @param unknown_type $argv
* @param supportMutliValue - will store 2nd copy of value in an array with key "foo[]"
* @return unknown
*/
static public function parseArguments($argv, $mutliValueMode = False) {
$args = array();
$args['extraArguments'] = array();
array_shift($argv); // scriptname
foreach ($argv as $arg) {
if (ereg('^--([^=]+)=(.*)', $arg, $reg)) {
$args[$reg[1]] = $reg[2];
if ($mutliValueMode) {
self::addItemAsArray($args, $reg[1], $reg[2]);
}
} elseif (ereg('^[-]{1,2}([^[:blank:]]+)', $arg, $reg)) {
$nonnull = '';
$args[$reg[1]] = $nonnull;
if ($mutliValueMode) {
self::addItemAsArray($args, $reg[1], $nonnull);
}
} else {
$args['extraArguments'][] = $arg;
}
}
return $args;
}
/**
* Adds a value as an array of one, or appends to an existing array elements
*
* @param unknown_type $array
* @param unknown_type $item
*/
static function addItemAsArray(&$array, $key, $item) {
$array_key = $key .'[]';
if (array_key_exists($array_key, $array)) {
$array[$array_key][] = $item;
} else {
$array[$array_key] = array($item);
}
}
/**
* isset function with default value
*
* Example: $z = CoverageUtils::issetOr($array[$key], 'no value given')
*
* @param unknown_type $val
* @param unknown_type $default
* @return first value unless value is not set then returns 2nd arg or null if no 2nd arg
*/
static public function issetOr(&$val, $default = null)
{
return isset($val) ? $val : $default;
}
}
?>

View File

@ -0,0 +1,16 @@
<?php
/**
* @package SimpleTest
* @subpackage Extensions
*/
/**
* @package SimpleTest
* @subpackage Extensions
*/
interface CoverageWriter {
function writeSummary($out, $variables);
function writeByFile($out, $variables);
}
?>

View File

@ -0,0 +1,39 @@
<?php
/**
* SimpleCoverageWriter class file
* @package SimpleTest
* @subpackage UnitTester
* @version $Id: unit_tester.php 1882 2009-07-01 14:30:05Z lastcraft $
*/
/**
* base coverage writer class
*/
require_once dirname(__FILE__) .'/coverage_writer.php';
/**
* SimpleCoverageWriter class
* @package SimpleTest
* @subpackage UnitTester
*/
class SimpleCoverageWriter implements CoverageWriter {
function writeSummary($out, $variables) {
extract($variables);
$now = date("F j, Y, g:i a");
ob_start();
include dirname(__FILE__) . '/templates/index.php';
$contents = ob_get_contents();
fwrite ($out, $contents);
ob_end_clean();
}
function writeByFile($out, $variables) {
extract($variables);
ob_start();
include dirname(__FILE__) . '/templates/file.php';
$contents = ob_get_contents();
fwrite ($out, $contents);
ob_end_clean();
}
}
?>

View File

@ -0,0 +1,60 @@
<html>
<head>
<title><?php echo $title ?></title>
</head>
<style type="text/css">
body {
font-family: "Gill Sans MT", "Gill Sans", GillSans, Arial, Helvetica, sans-serif;
}
h1 {
font-size: medium;
}
#code {
border-spacing: 0;
}
.lineNo {
color: #ccc;
}
.code, .lineNo {
white-space: pre;
font-family: monospace;
}
.covered {
color: #090;
}
.missed {
color: #f00;
}
.dead {
color: #00f;
}
.comment {
color: #333;
}
</style>
<body>
<h1 id="title"><?php echo $title ?></h1>
<table id="code">
<tbody>
<?php foreach ($lines as $lineNo => $line) { ?>
<tr>
<td><span class="lineNo"><?php echo $lineNo ?></span></td>
<td><span class="<?php echo $line['lineCoverage'] ?> code"><?php echo htmlentities($line['code']) ?></span></td>
</tr>
<?php } ?>
</tbody>
</table>
<h2>Legend</h2>
<dl>
<dt><span class="missed">Missed</span></dt>
<dd>lines code that <strong>were not</strong> excersized during program execution.</dd>
<dt><span class="covered">Covered</span></dt>
<dd>lines code <strong>were</strong> excersized during program execution.</dd>
<dt><span class="comment">Comment/non executable</span></dt>
<dd>Comment or non-executable line of code.</dd>
<dt><span class="dead">Dead</span></dt>
<dd>lines of code that according to xdebug could not be executed. This is counted as coverage code because
in almost all cases it is code that runnable.</dd>
</dl>
</body>
</html>

View File

@ -0,0 +1,106 @@
<html>
<head>
<title><?php echo $title ?></title>
</head>
<style type="text/css">
h1 {
font-size: medium;
}
body {
font-family: "Gill Sans MT", "Gill Sans", GillSans, Arial, Helvetica,
sans-serif;
}
td.percentage {
text-align: right;
}
caption {
border-bottom: thin solid;
font-weight: bolder;
}
dt {
font-weight: bolder;
}
table {
margin: 1em;
}
</style>
<body>
<h1 id="title"><?php echo $title ?></h1>
<table>
<caption>Summary</caption>
<tbody>
<tr>
<td>Total Coverage (<a href="#total-coverage">?</a>) :</td>
<td class="percentage"><span class="totalPercentCoverage"><?php echo number_format($totalPercentCoverage, 0) ?>%</span></td>
</tr>
<tr>
<td>Total Files Covered (<a href="#total-files-covered">?</a>) :</td>
<td class="percentage"><span class="filesTouchedPercentage"><?php echo number_format($filesTouchedPercentage, 0) ?>%</span></td>
</tr>
<tr>
<td>Report Generation Date :</td>
<td><?php echo $now ?></td>
</tr>
</tbody>
</table>
<table id="covered-files">
<caption>Coverage (<a href="#coverage">?</a>)</caption>
<thead>
<tr>
<th>File</th>
<th>Coverage</th>
</tr>
</thead>
<tbody>
<?php foreach ($coverageByFile as $file => $coverage) { ?>
<tr>
<td><a class="byFileReportLink" href="<?php echo $coverage['byFileReport'] ?>"><?php echo $file ?></a></td>
<td class="percentage"><span class="percentCoverage"><?php echo number_format($coverage['percentage'], 0) ?>%</span></td>
</tr>
<?php } ?>
</tbody>
</table>
<table>
<caption>Files Not Covered (<a href="#untouched">?</a>)</caption>
<tbody>
<?php foreach ($untouched as $key => $file) { ?>
<tr>
<td><span class="untouchedFile"><?php echo $file ?></span></td>
</tr>
<?php } ?>
</tbody>
</table>
<h2>Glossary</h2>
<dl>
<dt><a name="total-coverage">Total Coverage</a></dt>
<dd>Ratio of all the lines of executable code that were executed to the
lines of code that were not executed. This does not include the files
that were not covered at all.</dd>
<dt><a name="total-files-covered">Total Files Covered</a></dt>
<dd>This is the ratio of the number of files tested, to the number of
files not tested at all.</dd>
<dt><a name="coverage">Coverage</a></dt>
<dd>These files were parsed and loaded by the php interpreter while
running the tests. Percentage is determined by the ratio of number of
lines of code executed to the number of possible executable lines of
code. "dead" lines of code, or code that could not be executed
according to xdebug, are counted as covered because in almost all cases
it is the end of a logical loop.</dd>
<dt><a name="untouched">Files Not Covered</a></dt>
<dd>These files were not loaded by the php interpreter at anytime
during a unit test. You could consider these files having 0% coverage,
but because it is difficult to determine the total coverage unless you
could count the lines for executable code, this is not reflected in the
Total Coverage calculation.</dd>
</dl>
<p>Code coverage generated by <a href="http://www.simpletest.org">SimpleTest</a></p>
</body>
</html>

View File

@ -0,0 +1,65 @@
<?php
require_once(dirname(__FILE__) . '/../../../autorun.php');
class CoverageCalculatorTest extends UnitTestCase {
function skip() {
$this->skipIf(
!file_exists('DB/sqlite.php'),
'The Coverage extension needs to have PEAR installed');
}
function setUp() {
require_once dirname(__FILE__) .'/../coverage_calculator.php';
$this->calc = new CoverageCalculator();
}
function testVariables() {
$coverage = array('file' => array(1,1,1,1));
$untouched = array('missed-file');
$variables = $this->calc->variables($coverage, $untouched);
$this->assertEqual(4, $variables['totalLoc']);
$this->assertEqual(100, $variables['totalPercentCoverage']);
$this->assertEqual(4, $variables['totalLinesOfCoverage']);
$expected = array('file' => array('byFileReport' => 'file.html', 'percentage' => 100));
$this->assertEqual($expected, $variables['coverageByFile']);
$this->assertEqual(50, $variables['filesTouchedPercentage']);
$this->assertEqual($untouched, $variables['untouched']);
}
function testPercentageCoverageByFile() {
$coverage = array(0,0,0,1,1,1);
$results = array();
$this->calc->percentCoverageByFile($coverage, 'file', $results);
$pct = $results[0];
$this->assertEqual(50, $pct['file']['percentage']);
$this->assertEqual('file.html', $pct['file']['byFileReport']);
}
function testTotalLoc() {
$this->assertEqual(13, $this->calc->totalLoc(10, array(1,2,3)));
}
function testLineCoverage() {
$this->assertEqual(10, $this->calc->lineCoverage(10, -1));
$this->assertEqual(10, $this->calc->lineCoverage(10, 0));
$this->assertEqual(11, $this->calc->lineCoverage(10, 1));
}
function testTotalCoverage() {
$this->assertEqual(11, $this->calc->totalCoverage(10, array(-1,1)));
}
static function getAttribute($element, $attribute) {
$a = $element->attributes();
return $a[$attribute];
}
static function dom($stream) {
rewind($stream);
$actual = stream_get_contents($stream);
$html = DOMDocument::loadHTML($actual);
return simplexml_import_dom($html);
}
}
?>

View File

@ -0,0 +1,83 @@
<?php
require_once(dirname(__FILE__) . '/../../../autorun.php');
class CoverageDataHandlerTest extends UnitTestCase {
function skip() {
$this->skipIf(
!file_exists('DB/sqlite.php'),
'The Coverage extension needs to have PEAR installed');
}
function setUp() {
require_once dirname(__FILE__) .'/../coverage_data_handler.php';
}
function testAggregateCoverageCode() {
$handler = new CoverageDataHandler($this->tempdb());
$this->assertEqual(-2, $handler->aggregateCoverageCode(-2, -2));
$this->assertEqual(-2, $handler->aggregateCoverageCode(-2, 10));
$this->assertEqual(-2, $handler->aggregateCoverageCode(10, -2));
$this->assertEqual(-1, $handler->aggregateCoverageCode(-1, -1));
$this->assertEqual(10, $handler->aggregateCoverageCode(-1, 10));
$this->assertEqual(10, $handler->aggregateCoverageCode(10, -1));
$this->assertEqual(20, $handler->aggregateCoverageCode(10, 10));
}
function testSimpleWriteRead() {
$handler = new CoverageDataHandler($this->tempdb());
$handler->createSchema();
$coverage = array(10 => -2, 20 => -1, 30 => 0, 40 => 1);
$handler->write(array('file' => $coverage));
$actual = $handler->readFile('file');
$expected = array(10 => -2, 20 => -1, 30 => 0, 40 => 1);
$this->assertEqual($expected, $actual);
}
function testMultiFileWriteRead() {
$handler = new CoverageDataHandler($this->tempdb());
$handler->createSchema();
$handler->write(array(
'file1' => array(-2, -1, 1),
'file2' => array(-2, -1, 1)
));
$handler->write(array(
'file1' => array(-2, -1, 1)
));
$expected = array(
'file1' => array(-2, -1, 2),
'file2' => array(-2, -1, 1)
);
$actual = $handler->read();
$this->assertEqual($expected, $actual);
}
function testGetfilenames() {
$handler = new CoverageDataHandler($this->tempdb());
$handler->createSchema();
$rawCoverage = array('file0' => array(), 'file1' => array());
$handler->write($rawCoverage);
$actual = $handler->getFilenames();
$this->assertEqual(array('file0', 'file1'), $actual);
}
function testWriteUntouchedFiles() {
$handler = new CoverageDataHandler($this->tempdb());
$handler->createSchema();
$handler->writeUntouchedFile('bluejay');
$handler->writeUntouchedFile('robin');
$this->assertEqual(array('bluejay', 'robin'), $handler->readUntouchedFiles());
}
function testLtrim() {
$this->assertEqual('ber', CoverageDataHandler::ltrim('goo', 'goober'));
$this->assertEqual('some/file', CoverageDataHandler::ltrim('./', './some/file'));
$this->assertEqual('/x/y/z/a/b/c', CoverageDataHandler::ltrim('/a/b/', '/x/y/z/a/b/c'));
}
function tempdb() {
return tempnam(NULL, 'coverage.test.db');
}
}
?>

View File

@ -0,0 +1,22 @@
<?php
require_once(dirname(__FILE__) . '/../../../autorun.php');
class CoverageReporterTest extends UnitTestCase {
function skip() {
$this->skipIf(
!file_exists('DB/sqlite.php'),
'The Coverage extension needs to have PEAR installed');
}
function setUp() {
require_once dirname(__FILE__) .'/../coverage_reporter.php';
new CoverageReporter();
}
function testreportFilename() {
$this->assertEqual("parula.php.html", CoverageReporter::reportFilename("parula.php"));
$this->assertEqual("warbler_parula.php.html", CoverageReporter::reportFilename("warbler/parula.php"));
$this->assertEqual("warbler_parula.php.html", CoverageReporter::reportFilename("warbler\\parula.php"));
}
}
?>

View File

@ -0,0 +1,109 @@
<?php
require_once(dirname(__FILE__) . '/../../../autorun.php');
require_once(dirname(__FILE__) . '/../../../mock_objects.php');
class CodeCoverageTest extends UnitTestCase {
function skip() {
$this->skipIf(
!file_exists('DB/sqlite.php'),
'The Coverage extension needs to have PEAR installed');
}
function setUp() {
require_once dirname(__FILE__) .'/../coverage.php';
}
function testIsFileIncluded() {
$coverage = new CodeCoverage();
$this->assertTrue($coverage->isFileIncluded('aaa'));
$coverage->includes = array('a');
$this->assertTrue($coverage->isFileIncluded('aaa'));
$coverage->includes = array('x');
$this->assertFalse($coverage->isFileIncluded('aaa'));
$coverage->excludes = array('aa');
$this->assertFalse($coverage->isFileIncluded('aaa'));
}
function testIsFileIncludedRegexp() {
$coverage = new CodeCoverage();
$coverage->includes = array('modules/.*\.php$');
$coverage->excludes = array('bad-bunny.php');
$this->assertFalse($coverage->isFileIncluded('modules/a.test'));
$this->assertFalse($coverage->isFileIncluded('modules/bad-bunny.test'));
$this->assertTrue($coverage->isFileIncluded('modules/test.php'));
$this->assertFalse($coverage->isFileIncluded('module-bad/good-bunny.php'));
$this->assertTrue($coverage->isFileIncluded('modules/good-bunny.php'));
}
function testIsDirectoryIncludedPastMaxDepth() {
$coverage = new CodeCoverage();
$coverage->maxDirectoryDepth = 5;
$this->assertTrue($coverage->isDirectoryIncluded('aaa', 1));
$this->assertFalse($coverage->isDirectoryIncluded('aaa', 5));
}
function testIsDirectoryIncluded() {
$coverage = new CodeCoverage();
$this->assertTrue($coverage->isDirectoryIncluded('aaa', 0));
$coverage->excludes = array('b$');
$this->assertTrue($coverage->isDirectoryIncluded('aaa', 0));
$coverage->includes = array('a$'); // includes are ignore, all dirs are included unless excluded
$this->assertTrue($coverage->isDirectoryIncluded('aaa', 0));
$coverage->excludes = array('.*a$');
$this->assertFalse($coverage->isDirectoryIncluded('aaa', 0));
}
function testFilter() {
$coverage = new CodeCoverage();
$data = array('a' => 0, 'b' => 0, 'c' => 0);
$coverage->includes = array('b');
$coverage->filter($data);
$this->assertEqual(array('b' => 0), $data);
}
function testUntouchedFiles() {
$coverage = new CodeCoverage();
$touched = array_flip(array("test/coverage_test.php"));
$actual = array();
$coverage->includes = array('coverage_test\.php$');
$parentDir = realpath(dirname(__FILE__));
$coverage->getUntouchedFiles($actual, $touched, $parentDir, $parentDir);
$this->assertEqual(array("coverage_test.php"), $actual);
}
function testResetLog() {
$coverage = new CodeCoverage();
$coverage->log = tempnam(NULL, 'php.xdebug.coverage.test.');
$coverage->resetLog();
$this->assertTrue(file_exists($coverage->log));
}
function testSettingsSerialization() {
$coverage = new CodeCoverage();
$coverage->log = '/banana/boat';
$coverage->includes = array('apple', 'orange');
$coverage->excludes = array('tomato', 'pea');
$data = $coverage->getSettings();
$this->assertNotNull($data);
$actual = new CodeCoverage();
$actual->setSettings($data);
$this->assertEqual('/banana/boat', $actual->log);
$this->assertEqual(array('apple', 'orange'), $actual->includes);
$this->assertEqual(array('tomato', 'pea'), $actual->excludes);
}
function testSettingsCanBeReadWrittenToDisk() {
$settings_file = 'banana-boat-coverage-settings-test.dat';
$coverage = new CodeCoverage();
$coverage->log = '/banana/boat';
$coverage->settingsFile = $settings_file;
$coverage->writeSettings();
$actual = new CodeCoverage();
$actual->settingsFile = $settings_file;
$actual->readSettings();
$this->assertEqual('/banana/boat', $actual->log);
}
}
?>

View File

@ -0,0 +1,70 @@
<?php
require_once dirname(__FILE__) . '/../../../autorun.php';
class CoverageUtilsTest extends UnitTestCase {
function skip() {
$this->skipIf(
!file_exists('DB/sqlite.php'),
'The Coverage extension needs to have PEAR installed');
}
function setUp() {
require_once dirname(__FILE__) .'/../coverage_utils.php';
}
function testMkdir() {
CoverageUtils::mkdir(dirname(__FILE__));
try {
CoverageUtils::mkdir(__FILE__);
$this->fail("Should give error about cannot create dir of a file");
} catch (Exception $expected) {
}
}
function testIsPackageClassAvailable() {
$coverageSource = dirname(__FILE__) .'/../coverage_calculator.php';
$this->assertTrue(CoverageUtils::isPackageClassAvailable($coverageSource, 'CoverageCalculator'));
$this->assertFalse(CoverageUtils::isPackageClassAvailable($coverageSource, 'BogusCoverage'));
$this->assertFalse(CoverageUtils::isPackageClassAvailable('bogus-file', 'BogusCoverage'));
$this->assertTrue(CoverageUtils::isPackageClassAvailable('bogus-file', 'CoverageUtils'));
}
function testParseArgumentsMultiValue() {
$actual = CoverageUtils::parseArguments(array('scriptname', '--a=b', '--a=c'), True);
$expected = array('extraArguments' => array(), 'a' => 'c', 'a[]' => array('b', 'c'));
$this->assertEqual($expected, $actual);
}
function testParseArguments() {
$actual = CoverageUtils::parseArguments(array('scriptname', '--a=b', '-c', 'xxx'));
$expected = array('a' => 'b', 'c' => '', 'extraArguments' => array('xxx'));
$this->assertEqual($expected, $actual);
}
function testParseDoubleDashNoArguments() {
$actual = CoverageUtils::parseArguments(array('scriptname', '--aa'));
$this->assertTrue(isset($actual['aa']));
}
function testParseHyphenedExtraArguments() {
$actual = CoverageUtils::parseArguments(array('scriptname', '--alpha-beta=b', 'gamma-lambda'));
$expected = array('alpha-beta' => 'b', 'extraArguments' => array('gamma-lambda'));
$this->assertEqual($expected, $actual);
}
function testAddItemAsArray() {
$actual = array();
CoverageUtils::addItemAsArray($actual, 'bird', 'duck');
$this->assertEqual(array('bird[]' => array('duck')), $actual);
CoverageUtils::addItemAsArray(&$actual, 'bird', 'pigeon');
$this->assertEqual(array('bird[]' => array('duck', 'pigeon')), $actual);
}
function testIssetOr() {
$data = array('bird' => 'gull');
$this->assertEqual('lab', CoverageUtils::issetOr($data['dog'], 'lab'));
$this->assertEqual('gull', CoverageUtils::issetOr($data['bird'], 'sparrow'));
}
}
?>

View File

@ -0,0 +1,4 @@
<?php
// sample code
$x = 1 + 2;
if (false) echo "dead";

View File

@ -0,0 +1,69 @@
<?php
require_once(dirname(__FILE__) . '/../../../autorun.php');
class SimpleCoverageWriterTest extends UnitTestCase {
function skip() {
$this->skipIf(
!file_exists('DB/sqlite.php'),
'The Coverage extension needs to have PEAR installed');
}
function setUp() {
require_once dirname(__FILE__) .'/../simple_coverage_writer.php';
require_once dirname(__FILE__) .'/../coverage_calculator.php';
}
function testGenerateSummaryReport() {
$writer = new SimpleCoverageWriter();
$coverage = array('file' => array(0, 1));
$untouched = array('missed-file');
$calc = new CoverageCalculator();
$variables = $calc->variables($coverage, $untouched);
$variables['title'] = 'coverage';
$out = fopen("php://memory", 'w');
$writer->writeSummary($out, $variables);
$dom = self::dom($out);
$totalPercentCoverage = $dom->elements->xpath("//span[@class='totalPercentCoverage']");
$this->assertEqual('50%', (string)$totalPercentCoverage[0]);
$fileLinks = $dom->elements->xpath("//a[@class='byFileReportLink']");
$fileLinkAttr = $fileLinks[0]->attributes();
$this->assertEqual('file.html', $fileLinkAttr['href']);
$this->assertEqual('file', (string)($fileLinks[0]));
$untouchedFile = $dom->elements->xpath("//span[@class='untouchedFile']");
$this->assertEqual('missed-file', (string)$untouchedFile[0]);
}
function testGenerateCoverageByFile() {
$writer = new SimpleCoverageWriter();
$cov = array(3 => 1, 4 => -2); // 2 comments, 1 code, 1 dead (1-based indexes)
$out = fopen("php://memory", 'w');
$file = dirname(__FILE__) .'/sample/code.php';
$calc = new CoverageCalculator();
$variables = $calc->coverageByFileVariables($file, $cov);
$variables['title'] = 'coverage';
$writer->writeByFile($out, $variables);
$dom = self::dom($out);
$cells = $dom->elements->xpath("//table[@id='code']/tbody/tr/td/span");
$this->assertEqual("comment code", self::getAttribute($cells[1], 'class'));
$this->assertEqual("comment code", self::getAttribute($cells[3], 'class'));
$this->assertEqual("covered code", self::getAttribute($cells[5], 'class'));
$this->assertEqual("dead code", self::getAttribute($cells[7], 'class'));
}
static function getAttribute($element, $attribute) {
$a = $element->attributes();
return $a[$attribute];
}
static function dom($stream) {
rewind($stream);
$actual = stream_get_contents($stream);
$html = DOMDocument::loadHTML($actual);
return simplexml_import_dom($html);
}
}
?>

View File

@ -0,0 +1,14 @@
<?php
// $Id: $
require_once(dirname(__FILE__) . '/../../../autorun.php');
class CoverageUnitTests extends TestSuite {
function CoverageUnitTests() {
$this->TestSuite('Coverage Unit tests');
$path = dirname(__FILE__) . '/*_test.php';
foreach(glob($path) as $test) {
$this->addFile($test);
}
}
}
?>