diff --git a/src/TUnit/framework/ConsoleTestRunner.php b/src/TUnit/framework/ConsoleTestRunner.php index a8b7f4a..389b3f1 100644 --- a/src/TUnit/framework/ConsoleTestRunner.php +++ b/src/TUnit/framework/ConsoleTestRunner.php @@ -40,6 +40,16 @@ } } + /** + * {@inheritdoc} + * + * @author Tommy Montgomery + * @since 1.0 + * @version 1.0 + * @uses getOption() + * @uses CoverageReporter::createConsoleReport() + * @uses CoverageReporter::createHtmlReport() + */ protected function postRun() { $html = $this->getOption('coverage-html'); $console = $this->getOption('coverage-console'); diff --git a/src/TUnit/framework/mock/MockObjectCreator.php b/src/TUnit/framework/mock/MockObjectCreator.php index c3a8aa0..1504d8b 100644 --- a/src/TUnit/framework/mock/MockObjectCreator.php +++ b/src/TUnit/framework/mock/MockObjectCreator.php @@ -66,10 +66,19 @@ throw new LogicException('The class "' . $class . '" is final and cannot be mocked'); } + $constructor = $refClass->getConstructor(); + if ($constructor === null) { + $constructor = '__construct'; + $callParent = false; + } else { + $constructor = $constructor->getName(); + } + $this->referenceObject = $refClass; + $this->methods = array( 'default' => array( - $this->referenceObject->getConstructor()->getName() => array( + $constructor => array( 'body' => '', 'call_parent' => (bool)$callParent ) diff --git a/src/TUnit/framework/reporting/CoverageReporter.php b/src/TUnit/framework/reporting/CoverageReporter.php index b3bd6db..6e81885 100644 --- a/src/TUnit/framework/reporting/CoverageReporter.php +++ b/src/TUnit/framework/reporting/CoverageReporter.php @@ -2,8 +2,10 @@ class CoverageReporter { - const UNUSED = -1; - const DEAD = -2; + const UNUSED = -1; + const DEAD = -2; + + const TEMPLATE_DIR = 'template'; private function __construct() {} @@ -45,24 +47,130 @@ fwrite(STDOUT, " Executable: $totloc (" . round($totcloc / $totloc * 100, 2) . "%)\n"); } - public static function createXmlReport($file, array $coverageData) { - - } - public static function createHtmlReport($dir, array $coverageData) { if (!is_dir($dir)) { throw new TUnitException($dir . ' is not a directory'); } - - $coverageData = CoverageFilter::filter($coverageData); + + $baseDir = array(); foreach ($coverageData as $file => $data) { - foreach ($data as $unitsCovered) { - + $dirs = explode(DIRECTORY_SEPARATOR, dirname($file)); + if (empty($baseDir)) { + $baseDir = $dirs; + } else { + for ($i = 0, $len = count($dirs); $i < $len; $i++) { + if (!isset($baseDir[$i]) || $baseDir[$i] !== $dirs[$i]) { + break; + } + } + + $baseDir = array_slice($dirs, 0, $i); } } + $baseDir = implode(DIRECTORY_SEPARATOR, $baseDir) . DIRECTORY_SEPARATOR; + + $totalData = 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); + } + + //copy css over + $template = dirname(__FILE__) . DIRECTORY_SEPARATOR . self::TEMPLATE_DIR . DIRECTORY_SEPARATOR; + copy($template . 'style.css', $dir . DIRECTORY_SEPARATOR . 'style.css'); + } + + private static function writeHtmlFile($sourceFile, $baseDir, $coverageDir, array $data) { + $lines = file($sourceFile, FILE_IGNORE_NEW_LINES); + $code = ''; + $lineNumbers = ''; + for ($i = 1, $len = count($lines); $i <= $len; $i++) { + $lineNumbers .= '
' . $i . '
'; + $code .= ' 0) { + $code .= 'covered'; + } else if ($data[$i] === self::DEAD) { + $code .= 'dead'; + } else if ($data[$i] === self::UNUSED) { + $code .= 'uncovered'; + } + + $code .= '">'; + } else { + $code .= '>'; + } + + if (empty($lines[$i])) { + $lines[$i] = ' '; + } + + $code .= htmlentities(str_replace("\t", ' ', $lines[$i - 1]), ENT_QUOTES) ."\n"; + } + + unset($lines); + + $fileName = str_replace(array($baseDir, DIRECTORY_SEPARATOR), array('', '_'), $sourceFile); + $newFile = $coverageDir . DIRECTORY_SEPARATOR . $fileName . '.html'; + + $link = '' . $baseDir . ''; + $dirs = preg_split('@\\' . DIRECTORY_SEPARATOR . '@', str_replace($baseDir, '', dirname($sourceFile) . DIRECTORY_SEPARATOR), -1, PREG_SPLIT_NO_EMPTY); + $path = ''; + foreach ($dirs as $dir) { + $path = ltrim($path . '_' . $dir, '_'); + $link .= '' . $dir . '' . DIRECTORY_SEPARATOR; + } + + $template = file_get_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . self::TEMPLATE_DIR . DIRECTORY_SEPARATOR . 'file.html'); + $template = preg_replace( + array( + '/\$\{title\}/', + '/\$\{file\.name\}/', + '/\$\{line.numbers\}/', + '/\$\{code\}/', + '/\$\{timestamp\}/', + '/\$\{product\.name\}/', + '/\$\{product\.version\}/', + '/\$\{product\.website\}/', + '/\$\{product\.author\}/' + ), + array( + Product::NAME . ' - Coverage Report', + $link . basename($sourceFile), + $lineNumbers, + $code, + date('Y-m-d H:i:s'), + Product::NAME, + Product::VERSION, + Product::WEBSITE, + Product::AUTHOR + ), + $template + ); + + return file_put_contents($newFile, $template); + } + + private static function writeHtmlDir(array $data) { + } } diff --git a/src/TUnit/framework/reporting/template/file.html b/src/TUnit/framework/reporting/template/file.html new file mode 100644 index 0000000..ba91835 --- /dev/null +++ b/src/TUnit/framework/reporting/template/file.html @@ -0,0 +1,39 @@ + + + + + ${title} + + + + + + +
+ + + +
+
+${line.numbers} +
+
+${code} +
+
+ + + +
+ + + \ No newline at end of file diff --git a/src/TUnit/framework/reporting/template/style.css b/src/TUnit/framework/reporting/template/style.css new file mode 100644 index 0000000..e86d469 --- /dev/null +++ b/src/TUnit/framework/reporting/template/style.css @@ -0,0 +1,46 @@ +html { + overflow: auto; +} +body { + background-color: #FFFFCC; + color: #000000; +} + +#code-wrapper { + background-color: #FFFFFF; + font-family: Consolas, "Courier New", monospace; + font-size: 16px; + border: 2px solid #000000; +} +#line-numbers { + background-color: #CCCCCC; + float: left; + text-align: right; + padding: 0 5px; + border-right: 2px solid #000000; +} +#line-numbers a { + color: #000000; + text-decoration: none; + display: block; +} +#line-numbers div { + white-space: pre; +} +#code { + padding-left: 10px; + overflow: auto; +} +#code div { + white-space: pre; +} + +.covered { + background-color: #66FF66; +} +.uncovered { + background-color: #FF6666; +} +.dead { + background-color: #999999; +} \ No newline at end of file diff --git a/src/TUnit/util/entry_console.php b/src/TUnit/util/entry_console.php index 38a683f..7be46f4 100644 --- a/src/TUnit/util/entry_console.php +++ b/src/TUnit/util/entry_console.php @@ -48,7 +48,7 @@ ->addSwitch(new CliSwitch('usage', null, false, null, 'Display this help message')) ->addSwitch(new CliSwitch('recursive', null, false, null, 'Recurse into subdirectories')) ->addSwitch(new CliSwitch('bootstrap', 'b', false, 'file', 'File to include before tests are run')) - ->addSwitch(new CliSwitch('coverage-xml', null, false, 'file', 'Generate code coverage report in XML clover format (requires xdebug)')) + //->addSwitch(new CliSwitch('coverage-xml', null, false, 'file', 'Generate code coverage report in XML clover format (requires xdebug)')) ->addSwitch(new CliSwitch('coverage-html', null, false, 'dir', 'Generate code coverage report in HTML (requires xdebug, optionally uses ezComponents)')) ->addSwitch(new CliSwitch('coverage-console', null, false, null, 'Generate code coverage report suitable for console viewing'));