more code coverage reporting
This commit is contained in:
parent
d6faf4533e
commit
6b0d911cd2
@ -19,6 +19,43 @@
|
||||
*/
|
||||
class ConsoleTestRunner extends TestRunner {
|
||||
|
||||
/**
|
||||
* @see setFiles()
|
||||
* @var array
|
||||
*/
|
||||
protected $files;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
*
|
||||
* @param array $tests
|
||||
* @param array $listeners
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(array $tests = array(), array $listeners = array(), array $options = array()) {
|
||||
parent::__construct($tests, $listeners, $options);
|
||||
$this->files = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the files to parse for tests
|
||||
*
|
||||
* @author Tommy Montgomery
|
||||
* @version 1.0
|
||||
* @since 1.0
|
||||
*
|
||||
* @param array $files Array of files and/or directories to parse for tests
|
||||
* @return ConsoleTestRunner
|
||||
*/
|
||||
public final function setFiles(array $files) {
|
||||
$this->files = $files;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
@ -36,6 +73,14 @@
|
||||
if ($bootstrap !== null) {
|
||||
require_once $bootstrap;
|
||||
}
|
||||
|
||||
|
||||
//accumulate tests from files
|
||||
if (!empty($this->files)) {
|
||||
foreach (TestAccumulator::getTests($this->files, $this->getOption('recursive')) as $test) {
|
||||
$this->addTest($test);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9,6 +9,70 @@
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
private static function parseCoverageData(array $coverageData) {
|
||||
$newData = array();
|
||||
foreach ($coverageData as $file => $data) {
|
||||
$classes = Util::getClassNamesFromFile($file);
|
||||
//print_r($classes);
|
||||
$refClasses = array();
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$refClasses[] = new ReflectionClass($class);
|
||||
}
|
||||
|
||||
$newData[$file] = array();
|
||||
foreach ($data as $line => $unitsCovered) {
|
||||
$loc = 1;
|
||||
$dloc = ($unitsCovered === self::DEAD) ? 1 : 0;
|
||||
$cloc = ($unitsCovered > 0) ? 1 : 0;
|
||||
|
||||
//find the class this line resides in, if any
|
||||
$class = null;
|
||||
foreach ($refClasses as $refClass) {
|
||||
if ($line >= $refClass->getStartLine() && $line <= $refClass->getEndLine()) {
|
||||
$class = $refClass;
|
||||
//find the method this line resides in
|
||||
foreach ($class->getMethods() as $refMethod) {
|
||||
if ($line >= $refMethod->getStartLine() && $line <= $refMethod->getEndLine()) {
|
||||
|
||||
if (isset($newData[$file]['classes'][$class->getName()][$refMethod->getName()])) {
|
||||
$newData[$file]['classes'][$class->getName()][$refMethod->getName()]['loc'] += $loc;
|
||||
$newData[$file]['classes'][$class->getName()][$refMethod->getName()]['dloc'] += $dloc;
|
||||
$newData[$file]['classes'][$class->getName()][$refMethod->getName()]['cloc'] += $cloc;
|
||||
} else {
|
||||
$newData[$file]['classes'][$class->getName()][$refMethod->getName()] = array(
|
||||
'loc' => $loc,
|
||||
'dloc' => $dloc,
|
||||
'cloc' => $cloc
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//procedural code
|
||||
if ($class === null) {
|
||||
if (isset($newData[$file]['procedural'])) {
|
||||
$newData[$file]['procedural']['loc'] += $loc;
|
||||
$newData[$file]['procedural']['dloc'] += $dloc;
|
||||
$newData[$file]['procedural']['cloc'] += $cloc;
|
||||
} else {
|
||||
$newData[$file]['procedural'] = array(
|
||||
'loc' => $loc,
|
||||
'dloc' => $dloc,
|
||||
'cloc' => $cloc
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $newData;
|
||||
}
|
||||
|
||||
public static function createConsoleReport(array $coverageData) {
|
||||
$coverageData = CoverageFilter::filter($coverageData);
|
||||
fwrite(STDOUT, "\nCode coverage information:\n\n");
|
||||
@ -47,11 +111,7 @@
|
||||
fwrite(STDOUT, " Executable: $totloc (" . round($totcloc / $totloc * 100, 2) . "%)\n");
|
||||
}
|
||||
|
||||
public static function createHtmlReport($dir, array $coverageData) {
|
||||
if (!is_dir($dir)) {
|
||||
throw new TUnitException($dir . ' is not a directory');
|
||||
}
|
||||
|
||||
public static function createHtmlReport($coverageDir, array $coverageData) {
|
||||
$coverageData = CoverageFilter::filter($coverageData);
|
||||
|
||||
$baseDir = array();
|
||||
@ -72,45 +132,72 @@
|
||||
|
||||
$baseDir = implode(DIRECTORY_SEPARATOR, $baseDir) . DIRECTORY_SEPARATOR;
|
||||
|
||||
$totalData = array();
|
||||
$dirData = array();
|
||||
foreach ($coverageData as $file => $data) {
|
||||
$totalData[$file] = array(
|
||||
'loc' => 0,
|
||||
'dloc' => 0,
|
||||
'cloc' => 0
|
||||
);
|
||||
|
||||
foreach ($data as $line => $unitsCovered) {
|
||||
$totalData[$file]['loc']++;
|
||||
if ($unitsCovered > 0) {
|
||||
$totalData[$file]['cloc']++;
|
||||
} else if ($unitsCovered === self::DEAD) {
|
||||
$totalData[$file]['dloc']++;
|
||||
}
|
||||
}
|
||||
|
||||
self::writeHtmlFile($file, $baseDir, $dir, $data);
|
||||
self::writeHtmlFile($file, $baseDir, $coverageDir, self::parseCoverageData(array($file => $data)), $data);
|
||||
}
|
||||
|
||||
//copy css over
|
||||
$template = dirname(__FILE__) . DIRECTORY_SEPARATOR . self::TEMPLATE_DIR . DIRECTORY_SEPARATOR;
|
||||
copy($template . 'style.css', $dir . DIRECTORY_SEPARATOR . 'style.css');
|
||||
copy($template . 'style.css', $coverageDir . DIRECTORY_SEPARATOR . 'style.css');
|
||||
}
|
||||
|
||||
private static function writeHtmlFile($sourceFile, $baseDir, $coverageDir, array $data) {
|
||||
private static function writeHtmlFile($sourceFile, $baseDir, $coverageDir, array $classData, array $coverageData) {
|
||||
//summary view
|
||||
$fileCoverage = '';
|
||||
$classCoverage = '';
|
||||
|
||||
$tloc = 0;
|
||||
$tdloc = 0;
|
||||
$tcloc = 0;
|
||||
foreach ($classData[$sourceFile]['classes'] as $class => $methods) {
|
||||
$classCoverage = '';
|
||||
$classLoc = 0;
|
||||
$classDloc = 0;
|
||||
$classCloc = 0;
|
||||
$methodCoverage = '';
|
||||
$refClass = new ReflectionClass($class);
|
||||
foreach ($methods as $method => $methodData) {
|
||||
$tloc += $methodData['loc'];
|
||||
$tdloc += $methodData['dloc'];
|
||||
$tcloc += $methodData['cloc'];
|
||||
|
||||
$methodStartLine = $refClass->getMethod($method)->getStartLine();
|
||||
$methodCoverage .= "<tr class=\"method-coverage\"><th> <a href=\"#line-$methodStartLine\">$method</a></th>";
|
||||
$methodCoverage .= "<td>$methodData[cloc] / " . ($methodData['loc'] - $methodData['dloc']) . "</td>";
|
||||
$methodCoverage .= "<td>" . round($methodData['cloc'] / ($methodData['loc'] - $methodData['dloc']) * 100, 2) . "%</td></tr>\n";
|
||||
|
||||
$classLoc += $methodData['loc'];
|
||||
$classDloc += $methodData['dloc'];
|
||||
$classCloc += $methodData['cloc'];
|
||||
}
|
||||
|
||||
$classStartLine = $refClass->getStartLine();
|
||||
$classCoverage .= "<tr class=\"class-coverage\"><th> <a href=\"#line-$classStartLine\">$class</a></th><td>$classCloc / " . ($classLoc - $classDloc) . "</td>";
|
||||
$classCoverage .= "<td>" . round($classCloc / ($classLoc - $classDloc) * 100, 2) . "%</td></tr>\n";
|
||||
$classCoverage .= $methodCoverage;
|
||||
}
|
||||
|
||||
$fileCoveragePercent = round($tcloc / max($tloc - $tdloc, 1) * 100, 2);
|
||||
$fileCoverage = "<tr class=\"file-coverage\"><th>$sourceFile</th><td>$tcloc / " . ($tloc - $tdloc) . "</td><td>$fileCoveragePercent%</td></tr>\n";
|
||||
$fileCoverage .= $classCoverage;
|
||||
unset($classCoverage, $methodCoverage, $classData, $refClass);
|
||||
|
||||
//code view
|
||||
$lines = file($sourceFile, FILE_IGNORE_NEW_LINES);
|
||||
$code = '';
|
||||
$lineNumbers = '';
|
||||
|
||||
for ($i = 1, $len = count($lines); $i <= $len; $i++) {
|
||||
$lineNumbers .= '<div><a href="#line-' . $i . '">' . $i . '</a></div>';
|
||||
$lineNumbers .= '<div><a name="#line-' . $i . '" href="#line-' . $i . '">' . $i . '</a></div>';
|
||||
$code .= '<div';
|
||||
if (isset($data[$i])) {
|
||||
if (isset($coverageData[$i])) {
|
||||
$code .= ' class="';
|
||||
if ($data[$i] > 0) {
|
||||
if ($coverageData[$i] > 0) {
|
||||
$code .= 'covered';
|
||||
} else if ($data[$i] === self::DEAD) {
|
||||
} else if ($coverageData[$i] === self::DEAD) {
|
||||
$code .= 'dead';
|
||||
} else if ($data[$i] === self::UNUSED) {
|
||||
} else if ($coverageData[$i] === self::UNUSED) {
|
||||
$code .= 'uncovered';
|
||||
}
|
||||
|
||||
@ -125,7 +212,6 @@
|
||||
|
||||
$code .= htmlentities(str_replace("\t", ' ', $lines[$i - 1]), ENT_QUOTES) ."</div>\n";
|
||||
}
|
||||
|
||||
unset($lines);
|
||||
|
||||
$fileName = str_replace(array($baseDir, DIRECTORY_SEPARATOR), array('', '_'), $sourceFile);
|
||||
@ -140,21 +226,23 @@
|
||||
}
|
||||
|
||||
$template = file_get_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . self::TEMPLATE_DIR . DIRECTORY_SEPARATOR . 'file.html');
|
||||
$template = preg_replace(
|
||||
$template = str_replace(
|
||||
array(
|
||||
'/\$\{title\}/',
|
||||
'/\$\{file\.name\}/',
|
||||
'/\$\{line.numbers\}/',
|
||||
'/\$\{code\}/',
|
||||
'/\$\{timestamp\}/',
|
||||
'/\$\{product\.name\}/',
|
||||
'/\$\{product\.version\}/',
|
||||
'/\$\{product\.website\}/',
|
||||
'/\$\{product\.author\}/'
|
||||
'${title}',
|
||||
'${file.link}',
|
||||
'${file.coverage}',
|
||||
'${line.numbers}',
|
||||
'${code}',
|
||||
'${timestamp}',
|
||||
'${product.name}',
|
||||
'${product.version}',
|
||||
'${product.website}',
|
||||
'${product.author}'
|
||||
),
|
||||
array(
|
||||
Product::NAME . ' - Coverage Report',
|
||||
$link . basename($sourceFile),
|
||||
$fileCoverage,
|
||||
$lineNumbers,
|
||||
$code,
|
||||
date('Y-m-d H:i:s'),
|
||||
@ -169,8 +257,14 @@
|
||||
return file_put_contents($newFile, $template);
|
||||
}
|
||||
|
||||
private static function writeHtmlDir(array $data) {
|
||||
private static function writeHtmlDirectories($coverageDir, $baseDir, array $dirData) {
|
||||
ksort($dirData);
|
||||
|
||||
$newDirData = array();
|
||||
foreach ($dirData as $dir => $data) {
|
||||
$dir = str_replace($baseDir, '', $dir);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,16 @@
|
||||
<div id="wrapper">
|
||||
|
||||
<div id="header">
|
||||
<h1>${file.name}</h1>
|
||||
<h1>${file.link}</h1>
|
||||
</div>
|
||||
|
||||
<div id="summary">
|
||||
<table>
|
||||
<tr>
|
||||
<td></td><th colspan="3">Coverage</th>
|
||||
</tr>
|
||||
${file.coverage}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="code-wrapper">
|
||||
|
@ -4,6 +4,8 @@ html {
|
||||
body {
|
||||
background-color: #FFFFCC;
|
||||
color: #000000;
|
||||
font-family: Verdana, sans-serif;
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
#code-wrapper {
|
||||
@ -34,6 +36,13 @@ body {
|
||||
#code div {
|
||||
white-space: pre;
|
||||
}
|
||||
#summary th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.covered {
|
||||
background-color: #66FF66;
|
||||
|
@ -248,6 +248,23 @@
|
||||
return $closure;
|
||||
}
|
||||
|
||||
public static function getClassNamesFromFile($file) {
|
||||
$tokens = token_get_all(file_get_contents($file));
|
||||
$classes = array();
|
||||
for ($i = 0, $len = count($tokens); $i < $len; $i++) {
|
||||
if (
|
||||
is_array($tokens[$i]) && $tokens[$i][0] === T_CLASS &&
|
||||
isset($tokens[$i + 1]) && is_array($tokens[$i + 1]) && $tokens[$i + 1][0] === T_WHITESPACE &&
|
||||
isset($tokens[$i + 2]) && is_array($tokens[$i + 2]) && $tokens[$i + 2][0] === T_STRING
|
||||
) {
|
||||
$classes[] = $tokens[$i + 2][1];
|
||||
$i += 2;
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
@ -71,15 +71,7 @@
|
||||
$runner = new ConsoleTestRunner();
|
||||
$runner->setOptions($options);
|
||||
|
||||
//accumulate tests
|
||||
$tests = TestAccumulator::getTests($args, $runner->getOption('recursive'));
|
||||
|
||||
if (empty($tests)) {
|
||||
fwrite(STDERR, 'Found no TestCase subclasses in the given files');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$runner->addTests($tests)
|
||||
$runner->setFiles($args)
|
||||
->addListener(new ConsoleListener())
|
||||
->run();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user