| <?php |
| /** |
| * PHPUnit |
| * |
| * Copyright (c) 2002-2008, Sebastian Bergmann <sb@sebastian-bergmann.de>. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * * Neither the name of Sebastian Bergmann nor the names of his |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * 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. |
| * |
| * @category Testing |
| * @package PHPUnit |
| * @author Sebastian Bergmann <sb@sebastian-bergmann.de> |
| * @copyright 2002-2008 Sebastian Bergmann <sb@sebastian-bergmann.de> |
| * @license http://www.opensource.org/licenses/bsd-license.php BSD License |
| * @version SVN: $Id: File.php 1985 2007-12-26 18:11:55Z sb $ |
| * @link http://www.phpunit.de/ |
| * @since File available since Release 3.2.0 |
| */ |
| |
| require_once 'PHPUnit/Util/Filter.php'; |
| require_once 'PHPUnit/Util/Filesystem.php'; |
| require_once 'PHPUnit/Util/Template.php'; |
| require_once 'PHPUnit/Util/Report/Node.php'; |
| |
| PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT'); |
| |
| /** |
| * |
| * |
| * @category Testing |
| * @package PHPUnit |
| * @author Sebastian Bergmann <sb@sebastian-bergmann.de> |
| * @copyright 2002-2008 Sebastian Bergmann <sb@sebastian-bergmann.de> |
| * @license http://www.opensource.org/licenses/bsd-license.php BSD License |
| * @version Release: 3.2.9 |
| * @link http://www.phpunit.de/ |
| * @since Class available since Release 3.2.0 |
| */ |
| class PHPUnit_Util_Report_Node_File extends PHPUnit_Util_Report_Node { |
| /** |
| * @var array |
| * @access protected |
| */ |
| protected $codeLines; |
| |
| /** |
| * @var array |
| * @access protected |
| */ |
| protected $codeLinesFillup = array(); |
| |
| /** |
| * @var array |
| * @access protected |
| */ |
| protected $executedLines; |
| |
| /** |
| * @var boolean |
| * @access protected |
| */ |
| protected $yui = TRUE; |
| |
| /** |
| * @var boolean |
| * @access protected |
| */ |
| protected $highlight = FALSE; |
| |
| /** |
| * @var integer |
| * @access protected |
| */ |
| protected $numExecutableLines = 0; |
| |
| /** |
| * @var integer |
| * @access protected |
| */ |
| protected $numExecutedLines = 0; |
| |
| /** |
| * @var array |
| * @access protected |
| */ |
| protected $classes = array(); |
| |
| /** |
| * @var integer |
| * @access protected |
| */ |
| protected $numClasses = 0; |
| |
| /** |
| * @var integer |
| * @access protected |
| */ |
| protected $numCalledClasses = 0; |
| |
| /** |
| * @var integer |
| * @access protected |
| */ |
| protected $numMethods = 0; |
| |
| /** |
| * @var integer |
| * @access protected |
| */ |
| protected $numCalledMethods = 0; |
| |
| /** |
| * @var string |
| * @access protected |
| */ |
| protected $yuiPanelJS = ''; |
| |
| /** |
| * Constructor. |
| * |
| * @param string $name |
| * @param PHPUnit_Util_Report_Node $parent |
| * @param array $executedLines |
| * @param boolean $yui |
| * @param boolean $highlight |
| * @throws RuntimeException |
| * @access public |
| */ |
| public function __construct($name, PHPUnit_Util_Report_Node $parent = NULL, array $executedLines, $yui = TRUE, $highlight = FALSE) { |
| parent::__construct($name, $parent); |
| |
| $path = $this->getPath(); |
| |
| if (! file_exists($path)) { |
| throw new RuntimeException(); |
| } |
| |
| $this->executedLines = $executedLines; |
| $this->highlight = $highlight; |
| $this->yui = $yui; |
| $this->codeLines = $this->loadFile($path); |
| |
| $this->calculateStatistics(); |
| } |
| |
| /** |
| * Returns the classes of this node. |
| * |
| * @return array |
| * @access public |
| */ |
| public function getClasses() { |
| return $this->classes; |
| } |
| |
| /** |
| * Returns the number of executable lines. |
| * |
| * @return integer |
| * @access public |
| */ |
| public function getNumExecutableLines() { |
| return $this->numExecutableLines; |
| } |
| |
| /** |
| * Returns the number of executed lines. |
| * |
| * @return integer |
| * @access public |
| */ |
| public function getNumExecutedLines() { |
| return $this->numExecutedLines; |
| } |
| |
| /** |
| * Returns the number of classes. |
| * |
| * @return integer |
| * @access public |
| */ |
| public function getNumClasses() { |
| return $this->numClasses; |
| } |
| |
| /** |
| * Returns the number of classes of which at least one method |
| * has been called at least once. |
| * |
| * @return integer |
| * @access public |
| */ |
| public function getNumCalledClasses() { |
| return $this->numCalledClasses; |
| } |
| |
| /** |
| * Returns the number of methods. |
| * |
| * @return integer |
| * @access public |
| */ |
| public function getNumMethods() { |
| return $this->numMethods; |
| } |
| |
| /** |
| * Returns the number of methods that has been called at least once. |
| * |
| * @return integer |
| * @access public |
| */ |
| public function getNumCalledMethods() { |
| return $this->numCalledMethods; |
| } |
| |
| /** |
| * Renders this node. |
| * |
| * @param string $target |
| * @param string $title |
| * @param string $charset |
| * @param boolean $highlight |
| * @param integer $lowUpperBound |
| * @param integer $highLowerBound |
| * @access public |
| */ |
| public function render($target, $title, $charset = 'ISO-8859-1', $highlight = FALSE, $lowUpperBound = 35, $highLowerBound = 70) { |
| if ($this->yui) { |
| $template = new PHPUnit_Util_Template(PHPUnit_Util_Report::$templatePath . 'file.html'); |
| |
| $yuiTemplate = new PHPUnit_Util_Template(PHPUnit_Util_Report::$templatePath . 'yui_item.js'); |
| } else { |
| $template = new PHPUnit_Util_Template(PHPUnit_Util_Report::$templatePath . 'file_no_yui.html'); |
| } |
| |
| $i = 1; |
| $lines = ''; |
| $ignore = FALSE; |
| |
| foreach ($this->codeLines as $line) { |
| if (strpos($line, '@codeCoverageIgnore') !== FALSE) { |
| if (strpos($line, '@codeCoverageIgnoreStart') !== FALSE) { |
| $ignore = TRUE; |
| } |
| |
| else if (strpos($line, '@codeCoverageIgnoreEnd') !== FALSE) { |
| $ignore = FALSE; |
| } |
| } |
| |
| $css = ''; |
| |
| if (! $ignore && isset($this->executedLines[$i])) { |
| $count = ''; |
| |
| // Array: Line is executable and was executed. |
| // count(Array) = Number of tests that hit this line. |
| if (is_array($this->executedLines[$i])) { |
| $color = 'lineCov'; |
| $numTests = count($this->executedLines[$i]); |
| $count = sprintf('%8d', $numTests); |
| |
| if ($this->yui) { |
| $buffer = ''; |
| |
| foreach ($this->executedLines[$i] as $test) { |
| if (! isset($test->__liHtml)) { |
| $test->__liHtml = ''; |
| |
| if ($test instanceof PHPUnit_Framework_SelfDescribing) { |
| $testName = $test->toString(); |
| |
| if ($test instanceof PHPUnit_Framework_TestCase) { |
| switch ($test->getStatus()) { |
| case PHPUnit_Runner_BaseTestRunner::STATUS_PASSED: |
| { |
| $testCSS = ' class=\"testPassed\"'; |
| } |
| break; |
| |
| case PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE: |
| { |
| $testCSS = ' class=\"testFailure\"'; |
| } |
| break; |
| |
| case PHPUnit_Runner_BaseTestRunner::STATUS_ERROR: |
| { |
| $testCSS = ' class=\"testError\"'; |
| } |
| break; |
| |
| case PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE: |
| case PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED: |
| { |
| $testCSS = ' class=\"testIncomplete\"'; |
| } |
| break; |
| |
| default: |
| { |
| $testCSS = ''; |
| } |
| } |
| } |
| } |
| |
| $test->__liHtml .= sprintf('<li%s>%s</li>', |
| |
| $testCSS, $testName); |
| } |
| |
| $buffer .= $test->__liHtml; |
| } |
| |
| if ($numTests > 1) { |
| $header = $numTests . ' tests cover'; |
| } else { |
| $header = '1 test covers'; |
| } |
| |
| $header .= ' line ' . $i; |
| |
| $yuiTemplate->setVar(array('line' => $i, 'header' => $header, |
| 'tests' => $buffer), FALSE); |
| |
| $this->yuiPanelJS .= $yuiTemplate->render(); |
| } |
| } |
| |
| // -1: Line is executable and was not executed. |
| else if ($this->executedLines[$i] == - 1) { |
| $color = 'lineNoCov'; |
| $count = sprintf('%8d', 0); |
| } |
| |
| // -2: Line is dead code. |
| else { |
| $color = 'lineDeadCode'; |
| $count = ' '; |
| } |
| |
| $css = sprintf('<span class="%s"> %s : ', |
| |
| $color, $count); |
| } |
| |
| $fillup = array_shift($this->codeLinesFillup); |
| |
| if ($fillup > 0) { |
| $line .= str_repeat(' ', $fillup); |
| } |
| |
| $lines .= sprintf('<span class="lineNum" id="container%d"><a name="%d"></a><a href="#%d" id="line%d">%8d</a> </span>%s%s%s' . "\n", |
| |
| $i, $i, $i, $i, $i, ! empty($css) ? $css : ' : ', ! $this->highlight ? htmlspecialchars($line) : $line, ! empty($css) ? '</span>' : ''); |
| |
| $i ++; |
| } |
| |
| $items = ''; |
| |
| foreach ($this->classes as $className => $classData) { |
| $numCalledClasses = $classData['executedLines'] > 0 ? 1 : 0; |
| $calledClassesPercent = $numCalledClasses == 1 ? 100 : 0; |
| |
| $numCalledMethods = 0; |
| $numMethods = count($classData['methods']); |
| |
| foreach ($classData['methods'] as $method) { |
| if ($method['executedLines'] > 0) { |
| $numCalledMethods ++; |
| } |
| } |
| |
| $items .= $this->doRenderItem(array( |
| 'name' => sprintf('<b><a href="#%d">%s</a></b>', |
| |
| $classData['startLine'], $className), 'numClasses' => 1, |
| 'numCalledClasses' => $numCalledClasses, |
| 'calledClassesPercent' => sprintf('%01.2f', $calledClassesPercent), |
| 'numMethods' => $numMethods, |
| 'numCalledMethods' => $numCalledMethods, |
| 'calledMethodsPercent' => $this->calculatePercent($numCalledMethods, $numMethods), |
| 'numExecutableLines' => $classData['executableLines'], |
| 'numExecutedLines' => $classData['executedLines'], |
| 'executedLinesPercent' => $this->calculatePercent($classData['executedLines'], $classData['executableLines'])), $lowUpperBound, $highLowerBound); |
| |
| foreach ($classData['methods'] as $methodName => $methodData) { |
| $numCalledMethods = $methodData['executedLines'] > 0 ? 1 : 0; |
| $calledMethodsPercent = $numCalledMethods == 1 ? 100 : 0; |
| |
| $items .= $this->doRenderItem(array( |
| 'name' => sprintf(' <a href="#%d">%s</a>', |
| |
| $methodData['startLine'], PHPUnit_Util_Class::getMethodSignature(new ReflectionMethod($className, $methodName))), |
| 'numClasses' => '', 'numCalledClasses' => '', |
| 'calledClassesPercent' => '', 'numMethods' => 1, |
| 'numCalledMethods' => $numCalledMethods, |
| 'calledMethodsPercent' => sprintf('%01.2f', $calledMethodsPercent), |
| 'numExecutableLines' => $methodData['executableLines'], |
| 'numExecutedLines' => $methodData['executedLines'], |
| 'executedLinesPercent' => $this->calculatePercent($methodData['executedLines'], $methodData['executableLines'])), $lowUpperBound, $highLowerBound, 'method_item.html'); |
| } |
| } |
| |
| $this->setTemplateVars($template, $title, $charset); |
| |
| $template->setVar(array('lines' => $lines, |
| 'total_item' => $this->renderTotalItem($lowUpperBound, $highLowerBound, FALSE), |
| 'items' => $items, 'yuiPanelJS' => $this->yuiPanelJS)); |
| |
| $cleanId = PHPUnit_Util_Filesystem::getSafeFilename($this->getId()); |
| $template->renderTo($target . $cleanId . '.html'); |
| } |
| |
| /** |
| * Calculates coverage statistics for the file. |
| * |
| * @access protected |
| */ |
| protected function calculateStatistics() { |
| $classes = PHPUnit_Util_Class::getClassesInFile($this->getPath()); |
| |
| $startLines = array(); |
| $endLines = array(); |
| |
| foreach ($classes as $class) { |
| if (! $class->isInterface()) { |
| $className = $class->getName(); |
| $classStartLine = $class->getStartLine(); |
| $classEndLine = $class->getEndLine(); |
| |
| $this->classes[$className] = array('methods' => array(), |
| 'startLine' => $classStartLine, 'executableLines' => 0, |
| 'executedLines' => 0); |
| |
| $startLines[$classStartLine] = &$this->classes[$className]; |
| $endLines[$classEndLine] = &$this->classes[$className]; |
| |
| foreach ($class->getMethods() as $method) { |
| if (! $method->isAbstract() && $method->getDeclaringClass()->getName() == $className) { |
| $methodName = $method->getName(); |
| $methodStartLine = $method->getStartLine(); |
| $methodEndLine = $method->getEndLine(); |
| |
| $this->classes[$className]['methods'][$methodName] = array( |
| 'startLine' => $methodStartLine, |
| 'executableLines' => 0, |
| 'executedLines' => 0); |
| |
| $startLines[$methodStartLine] = &$this->classes[$className]['methods'][$methodName]; |
| $endLines[$methodEndLine] = &$this->classes[$className]['methods'][$methodName]; |
| |
| $this->numMethods ++; |
| } |
| } |
| |
| $this->numClasses ++; |
| } |
| } |
| |
| $ignoreStart = - 1; |
| $lineNumber = 1; |
| |
| foreach ($this->codeLines as $line) { |
| if (isset($startLines[$lineNumber])) { |
| // Start line of a class. |
| if (isset($startLines[$lineNumber]['methods'])) { |
| $currentClass = &$startLines[$lineNumber]; |
| } |
| |
| // Start line of a method. |
| else { |
| $currentMethod = &$startLines[$lineNumber]; |
| } |
| } |
| |
| if (strpos($line, '@codeCoverageIgnore') !== FALSE) { |
| if (strpos($line, '@codeCoverageIgnoreStart') !== FALSE) { |
| $ignoreStart = $line; |
| } |
| |
| else if (strpos($line, '@codeCoverageIgnoreEnd') !== FALSE) { |
| $ignoreStart = - 1; |
| } |
| } |
| |
| if (isset($this->executedLines[$lineNumber])) { |
| // Array: Line is executable and was executed. |
| if (is_array($this->executedLines[$lineNumber])) { |
| if (isset($currentClass)) { |
| $currentClass['executableLines'] ++; |
| $currentClass['executedLines'] ++; |
| } |
| |
| if (isset($currentMethod)) { |
| $currentMethod['executableLines'] ++; |
| $currentMethod['executedLines'] ++; |
| } |
| |
| $this->numExecutableLines ++; |
| $this->numExecutedLines ++; |
| } |
| |
| // -1: Line is executable and was not executed. |
| else if ($this->executedLines[$lineNumber] == - 1) { |
| if (isset($currentClass)) { |
| $currentClass['executableLines'] ++; |
| } |
| |
| if (isset($currentMethod)) { |
| $currentMethod['executableLines'] ++; |
| } |
| |
| $this->numExecutableLines ++; |
| |
| if ($ignoreStart != - 1 && $line > $ignoreStart) { |
| if (isset($currentClass)) { |
| $currentClass['executedLines'] ++; |
| } |
| |
| if (isset($currentMethod)) { |
| $currentMethod['executedLines'] ++; |
| } |
| |
| $this->numExecutedLines ++; |
| } |
| } |
| } |
| |
| if (isset($endLines[$lineNumber])) { |
| // End line of a class. |
| if (isset($endLines[$lineNumber]['methods'])) { |
| unset($currentClass); |
| } |
| |
| // End line of a method. |
| else { |
| unset($currentMethod); |
| } |
| } |
| |
| $lineNumber ++; |
| } |
| |
| foreach ($this->classes as $class) { |
| foreach ($class['methods'] as $method) { |
| if ($method['executedLines'] > 0) { |
| $this->numCalledMethods ++; |
| } |
| } |
| |
| if ($class['executedLines'] > 0) { |
| $this->numCalledClasses ++; |
| } |
| } |
| } |
| |
| /** |
| * @author Aidan Lister <aidan@php.net> |
| * @author Sebastian Bergmann <sb@sebastian-bergmann.de> |
| * @param string $file |
| * @return array |
| * @access protected |
| */ |
| protected function loadFile($file) { |
| $lines = explode("\n", str_replace("\t", ' ', file_get_contents($file))); |
| $result = array(); |
| |
| if (count($lines) == 0) { |
| return $result; |
| } |
| |
| $lines = array_map('rtrim', $lines); |
| $linesLength = array_map('strlen', $lines); |
| $width = max($linesLength); |
| |
| foreach ($linesLength as $line => $length) { |
| $this->codeLinesFillup[$line] = $width - $length; |
| } |
| |
| if (! $this->highlight) { |
| return $lines; |
| } |
| |
| $tokens = token_get_all(file_get_contents($file)); |
| $stringFlag = FALSE; |
| $i = 0; |
| $result[$i] = ''; |
| |
| foreach ($tokens as $j => $token) { |
| if (is_string($token)) { |
| if ($token === '"' && $tokens[$j - 1] !== '\\') { |
| $result[$i] .= sprintf('<span class="string">%s</span>', |
| |
| htmlspecialchars($token)); |
| |
| $stringFlag = ! $stringFlag; |
| } else { |
| $result[$i] .= sprintf('<span class="keyword">%s</span>', |
| |
| htmlspecialchars($token)); |
| } |
| |
| continue; |
| } |
| |
| list($token, $value) = $token; |
| |
| $value = str_replace(array("\t", ' '), array(' ', ' '), htmlspecialchars($value)); |
| |
| if ($value === "\n") { |
| $result[++ $i] = ''; |
| } else { |
| $lines = explode("\n", $value); |
| |
| foreach ($lines as $jj => $line) { |
| $line = trim($line); |
| |
| if ($line !== '') { |
| if ($stringFlag) { |
| $colour = 'string'; |
| } else { |
| switch ($token) { |
| case T_INLINE_HTML: |
| { |
| $colour = 'html'; |
| } |
| break; |
| |
| case T_COMMENT: |
| case T_DOC_COMMENT: |
| { |
| $colour = 'comment'; |
| } |
| break; |
| |
| case T_ABSTRACT: |
| case T_ARRAY: |
| case T_ARRAY_CAST: |
| case T_AS: |
| case T_BOOLEAN_AND: |
| case T_BOOLEAN_OR: |
| case T_BOOL_CAST: |
| case T_BREAK: |
| case T_CASE: |
| case T_CATCH: |
| case T_CLASS: |
| case T_CLONE: |
| case T_CONCAT_EQUAL: |
| case T_CONTINUE: |
| case T_DEFAULT: |
| case T_DOUBLE_ARROW: |
| case T_DOUBLE_CAST: |
| case T_ECHO: |
| case T_ELSE: |
| case T_ELSEIF: |
| case T_EMPTY: |
| case T_ENDDECLARE: |
| case T_ENDFOR: |
| case T_ENDFOREACH: |
| case T_ENDIF: |
| case T_ENDSWITCH: |
| case T_ENDWHILE: |
| case T_END_HEREDOC: |
| case T_EXIT: |
| case T_EXTENDS: |
| case T_FINAL: |
| case T_FOREACH: |
| case T_FUNCTION: |
| case T_GLOBAL: |
| case T_IF: |
| case T_INC: |
| case T_INCLUDE: |
| case T_INCLUDE_ONCE: |
| case T_INSTANCEOF: |
| case T_INT_CAST: |
| case T_ISSET: |
| case T_IS_EQUAL: |
| case T_IS_IDENTICAL: |
| case T_IS_NOT_IDENTICAL: |
| case T_IS_SMALLER_OR_EQUAL: |
| case T_NEW: |
| case T_OBJECT_CAST: |
| case T_OBJECT_OPERATOR: |
| case T_PAAMAYIM_NEKUDOTAYIM: |
| case T_PRIVATE: |
| case T_PROTECTED: |
| case T_PUBLIC: |
| case T_REQUIRE: |
| case T_REQUIRE_ONCE: |
| case T_RETURN: |
| case T_SL: |
| case T_SL_EQUAL: |
| case T_SR: |
| case T_SR_EQUAL: |
| case T_START_HEREDOC: |
| case T_STATIC: |
| case T_STRING_CAST: |
| case T_THROW: |
| case T_TRY: |
| case T_UNSET_CAST: |
| case T_VAR: |
| case T_WHILE: |
| { |
| $colour = 'keyword'; |
| } |
| break; |
| |
| default: |
| { |
| $colour = 'default'; |
| } |
| } |
| } |
| |
| $result[$i] .= sprintf('<span class="%s">%s</span>', |
| |
| $colour, $line); |
| } |
| |
| if (isset($lines[$jj + 1])) { |
| $result[++ $i] = ''; |
| } |
| } |
| } |
| } |
| |
| unset($result[count($result) - 1]); |
| |
| return $result; |
| } |
| } |
| ?> |