. */ require_once 'phing/TaskPhing.php'; require_once 'phing/system/io/PhingFile.php'; require_once 'phing/system/io/Writer.php'; require_once 'phing/system/util/Properties.php'; require_once 'phing/tasks/ext/phpunit2/PHPUnit2Util.php'; require_once 'phing/tasks/ext/coverage/CoverageReportTransformer.php'; /** * Transforms information in a code coverage database to XML * * @author Michiel Rook * @version $Id: CoverageReportTask.php 3076 2006-12-18 08:52:12Z fabien $ * @package phing.tasks.ext.coverage * @since 2.1.0 */ class CoverageReportTask extends TaskPhing { private $outfile = "coverage.xml"; private $transformers = array(); /** the classpath to use (optional) */ private $classpath = NULL; /** the path to the GeSHi library (optional) */ private $geshipath = ""; /** the path to the GeSHi language files (optional) */ private $geshilanguagespath = ""; function setClasspath(Path $classpath) { if ($this->classpath === null) { $this->classpath = $classpath; } else { $this->classpath->append($classpath); } } function createClasspath() { $this->classpath = new Path(); return $this->classpath; } function setGeshiPath($path) { $this->geshipath = $path; } function setGeshiLanguagesPath($path) { $this->geshilanguagespath = $path; } function __construct() { $this->doc = new DOMDocument(); $this->doc->encoding = 'UTF-8'; $this->doc->formatOutput = true; $this->doc->appendChild($this->doc->createElement('snapshot')); } function setOutfile($outfile) { $this->outfile = $outfile; } /** * Generate a report based on the XML created by this task */ function createReport() { $transformer = new CoverageReportTransformer($this); $this->transformers[] = $transformer; return $transformer; } protected function getPackageElement($packageName) { $packages = $this->doc->documentElement->getElementsByTagName('package'); foreach ($packages as $package) { if ($package->getAttribute('name') == $packageName) { return $package; } } return NULL; } protected function addClassToPackage($classname, $element) { $packageName = PHPUnit2Util::getPackageName($classname); $package = $this->getPackageElement($packageName); if ($package === NULL) { $package = $this->doc->createElement('package'); $package->setAttribute('name', $packageName); $this->doc->documentElement->appendChild($package); } $package->appendChild($element); } protected function stripDiv($source) { $openpos = strpos($source, "", $openpos); $line = substr($source, $closepos + 1); $tagclosepos = strpos($line, ""); $line = substr($line, 0, $tagclosepos); return $line; } protected function highlightSourceFile($filename) { if ($this->geshipath) { require_once $this->geshipath . '/geshi.php'; $source = file_get_contents($filename); $geshi = new GeSHi($source, 'php', $this->geshilanguagespath); $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS); $geshi->enable_strict_mode(true); $geshi->enable_classes(true); $geshi->set_url_for_keyword_group(3, ''); $html = $geshi->parse_code(); $lines = explode("
  • |
  • ", $html); // skip first and last line array_pop($lines); array_shift($lines); $lines = array_filter($lines); $lines = array_map(array($this, 'stripDiv'), $lines); return $lines; } else { $lines = file($filename); for ($i = 0; $i < count($lines); $i++) { $line = $lines[$i]; $line = rtrim($line); $lines[$i] = utf8_encode($line); } return $lines; } } protected function transformSourceFile($filename, $coverageInformation, $classStartLine = 1) { $sourceElement = $this->doc->createElement('sourcefile'); $sourceElement->setAttribute('name', basename($filename)); $filelines = $this->highlightSourceFile($filename); $linenr = 1; foreach ($filelines as $line) { $lineElement = $this->doc->createElement('sourceline'); $lineElement->setAttribute('coveredcount', (isset($coverageInformation[$linenr]) ? $coverageInformation[$linenr] : '0')); if ($linenr == $classStartLine) { $lineElement->setAttribute('startclass', 1); } $textnode = $this->doc->createTextNode($line); $lineElement->appendChild($textnode); $sourceElement->appendChild($lineElement); $linenr++; } return $sourceElement; } protected function filterCovered($var) { return ($var >= 0); } protected function transformCoverageInformation($filename, $coverageInformation) { // Strip last line of coverage information end($coverageInformation); unset($coverageInformation[key($coverageInformation)]); $classes = PHPUnit2Util::getDefinedClasses($filename, $this->classpath); if (is_array($classes)) { foreach ($classes as $classname) { $reflection = new ReflectionClass($classname); $methods = $reflection->getMethods(); $classElement = $this->doc->createElement('class'); $classElement->setAttribute('name', $reflection->getName()); $this->addClassToPackage($reflection->getName(), $classElement); $classStartLine = $reflection->getStartLine(); $methodscovered = 0; $methodcount = 0; // Strange PHP5 reflection bug, classes without parent class or implemented interfaces seem to start one line off if ($reflection->getParentClass() == NULL && count($reflection->getInterfaces()) == 0) { unset($coverageInformation[$classStartLine + 1]); } else { unset($coverageInformation[$classStartLine]); } reset($coverageInformation); foreach ($methods as $method) { // PHP5 reflection considers methods of a parent class to be part of a subclass, we don't if ($method->getDeclaringClass()->getName() != $reflection->getName()) { continue; } // small fix for XDEBUG_CC_UNUSED if (isset($coverageInformation[$method->getStartLine()])) { unset($coverageInformation[$method->getStartLine()]); } if (isset($coverageInformation[$method->getEndLine()])) { unset($coverageInformation[$method->getEndLine()]); } if ($method->isAbstract()) { continue; } $linenr = key($coverageInformation); while ($linenr !== null && $linenr < $method->getStartLine()) { next($coverageInformation); $linenr = key($coverageInformation); } if (current($coverageInformation) > 0 && $method->getStartLine() <= $linenr && $linenr <= $method->getEndLine()) { $methodscovered++; } $methodcount++; } $statementcount = count($coverageInformation); $statementscovered = count(array_filter($coverageInformation, array($this, 'filterCovered'))); $classElement->appendChild($this->transformSourceFile($filename, $coverageInformation, $classStartLine)); $classElement->setAttribute('methodcount', $methodcount); $classElement->setAttribute('methodscovered', $methodscovered); $classElement->setAttribute('statementcount', $statementcount); $classElement->setAttribute('statementscovered', $statementscovered); $classElement->setAttribute('totalcount', $methodcount + $statementcount); $classElement->setAttribute('totalcovered', $methodscovered + $statementscovered); } } } protected function calculateStatistics() { $packages = $this->doc->documentElement->getElementsByTagName('package'); $totalmethodcount = 0; $totalmethodscovered = 0; $totalstatementcount = 0; $totalstatementscovered = 0; foreach ($packages as $package) { $methodcount = 0; $methodscovered = 0; $statementcount = 0; $statementscovered = 0; $classes = $package->getElementsByTagName('class'); foreach ($classes as $class) { $methodcount += $class->getAttribute('methodcount'); $methodscovered += $class->getAttribute('methodscovered'); $statementcount += $class->getAttribute('statementcount'); $statementscovered += $class->getAttribute('statementscovered'); } $package->setAttribute('methodcount', $methodcount); $package->setAttribute('methodscovered', $methodscovered); $package->setAttribute('statementcount', $statementcount); $package->setAttribute('statementscovered', $statementscovered); $package->setAttribute('totalcount', $methodcount + $statementcount); $package->setAttribute('totalcovered', $methodscovered + $statementscovered); $totalmethodcount += $methodcount; $totalmethodscovered += $methodscovered; $totalstatementcount += $statementcount; $totalstatementscovered += $statementscovered; } $this->doc->documentElement->setAttribute('methodcount', $totalmethodcount); $this->doc->documentElement->setAttribute('methodscovered', $totalmethodscovered); $this->doc->documentElement->setAttribute('statementcount', $totalstatementcount); $this->doc->documentElement->setAttribute('statementscovered', $totalstatementscovered); $this->doc->documentElement->setAttribute('totalcount', $totalmethodcount + $totalstatementcount); $this->doc->documentElement->setAttribute('totalcovered', $totalmethodscovered + $totalstatementscovered); } function main() { $this->log("Transforming coverage report"); $database = new PhingFile($this->project->getProperty('coverage.database')); $props = new Properties(); $props->load($database); foreach ($props->keys() as $filename) { $file = unserialize($props->getProperty($filename)); $this->transformCoverageInformation($file['fullname'], $file['coverage']); } $this->calculateStatistics(); $this->doc->save($this->outfile); foreach ($this->transformers as $transformer) { $transformer->setXmlDocument($this->doc); $transformer->transform(); } } } ?>