Subversion-Projekte lars-tiefland.php_share

Revision

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 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);
                                
                                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 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();
                }
        }
}