Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** $Id: CoverageReportTask.php 426 2008-10-28 19:29:49Z mrook $** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.** This software consists of voluntary contributions made by many individuals* and is licensed under the LGPL. For more information please see* <http://phing.info>.*/require_once 'phing/Task.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/phpunit/PHPUnitUtil.php';require_once 'phing/tasks/ext/coverage/CoverageReportTransformer.php';/*** Transforms information in a code coverage database to XML** @author Michiel Rook <michiel.rook@gmail.com>* @version $Id: CoverageReportTask.php 426 2008-10-28 19:29:49Z mrook $* @package phing.tasks.ext.coverage* @since 2.1.0*/class CoverageReportTask extends Task{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 = PHPUnitUtil::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, "<div");$closepos = strpos($source, ">", $openpos);$line = substr($source, $closepos + 1);$tagclosepos = strpos($line, "</div>");$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 = split("<li>|</li>", $html);// skip first and last linearray_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);if (function_exists('mb_convert_encoding')){$lines[$i] = mb_convert_encoding($line, 'UTF-8');}else{$lines[$i] = utf8_encode($line);}}return $lines;}}protected function transformSourceFile($filename, $coverageInformation, $classStartLine = 1){$sourceElement = $this->doc->createElement('sourcefile');$sourceElement->setAttribute('name', basename($filename));/*** Add original/full filename to document*/$sourceElement->setAttribute('sourcefile', $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){$classes = PHPUnitUtil::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 offif ($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'tif ($method->getDeclaringClass()->getName() != $reflection->getName()){continue;}// small fix for XDEBUG_CC_UNUSEDif (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();}}}