diff --git a/src/TUnit/framework/ConsoleTestRunner.php b/src/TUnit/framework/ConsoleTestRunner.php index 2df232e..b7c7822 100644 --- a/src/TUnit/framework/ConsoleTestRunner.php +++ b/src/TUnit/framework/ConsoleTestRunner.php @@ -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); + } + } } /** diff --git a/src/TUnit/framework/reporting/CoverageReporter.php b/src/TUnit/framework/reporting/CoverageReporter.php index 6e81885..b230cda 100644 --- a/src/TUnit/framework/reporting/CoverageReporter.php +++ b/src/TUnit/framework/reporting/CoverageReporter.php @@ -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) { - $lines = file($sourceFile, FILE_IGNORE_NEW_LINES); - $code = ''; + 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 .= "    $method"; + $methodCoverage .= "$methodData[cloc] / " . ($methodData['loc'] - $methodData['dloc']) . ""; + $methodCoverage .= "" . round($methodData['cloc'] / ($methodData['loc'] - $methodData['dloc']) * 100, 2) . "%\n"; + + $classLoc += $methodData['loc']; + $classDloc += $methodData['dloc']; + $classCloc += $methodData['cloc']; + } + + $classStartLine = $refClass->getStartLine(); + $classCoverage .= "  $class$classCloc / " . ($classLoc - $classDloc) . ""; + $classCoverage .= "" . round($classCloc / ($classLoc - $classDloc) * 100, 2) . "%\n"; + $classCoverage .= $methodCoverage; + } + + $fileCoveragePercent = round($tcloc / max($tloc - $tdloc, 1) * 100, 2); + $fileCoverage = "$sourceFile$tcloc / " . ($tloc - $tdloc) . "$fileCoveragePercent%\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 .= '
' . $i . '
'; + $lineNumbers .= '
' . $i . '
'; $code .= ' 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) ."\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); + + } } } diff --git a/src/TUnit/framework/reporting/template/file.html b/src/TUnit/framework/reporting/template/file.html index ba91835..94402ff 100644 --- a/src/TUnit/framework/reporting/template/file.html +++ b/src/TUnit/framework/reporting/template/file.html @@ -13,7 +13,16 @@
+ +
+ + + + +${file.coverage} +
Coverage
diff --git a/src/TUnit/framework/reporting/template/style.css b/src/TUnit/framework/reporting/template/style.css index e86d469..ae1a43d 100644 --- a/src/TUnit/framework/reporting/template/style.css +++ b/src/TUnit/framework/reporting/template/style.css @@ -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; diff --git a/src/TUnit/util/Util.php b/src/TUnit/util/Util.php index 3760012..cb2e508 100644 --- a/src/TUnit/util/Util.php +++ b/src/TUnit/util/Util.php @@ -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; + } + } ?> \ No newline at end of file diff --git a/src/TUnit/util/entry_console.php b/src/TUnit/util/entry_console.php index 7be46f4..75c5299 100644 --- a/src/TUnit/util/entry_console.php +++ b/src/TUnit/util/entry_console.php @@ -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();