5
* Copyright (c) 2002-2010, Sebastian Bergmann <sb@sebastian-bergmann.de>.
8
* Redistribution and use in source and binary forms, with or without
9
* modification, are permitted provided that the following conditions
12
* * Redistributions of source code must retain the above copyright
13
* notice, this list of conditions and the following disclaimer.
15
* * Redistributions in binary form must reproduce the above copyright
16
* notice, this list of conditions and the following disclaimer in
17
* the documentation and/or other materials provided with the
20
* * Neither the name of Sebastian Bergmann nor the names of his
21
* contributors may be used to endorse or promote products derived
22
* from this software without specific prior written permission.
24
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35
* POSSIBILITY OF SUCH DAMAGE.
39
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
40
* @copyright 2002-2010 Sebastian Bergmann <sb@sebastian-bergmann.de>
41
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
42
* @link http://www.phpunit.de/
43
* @since File available since Release 3.2.0
46
require_once 'PHPUnit/Util/Filter.php';
47
require_once 'PHPUnit/Util/File.php';
48
require_once 'PHPUnit/Util/Filesystem.php';
49
require_once 'PHPUnit/Util/Template.php';
50
require_once 'PHPUnit/Util/Report/Node.php';
52
PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT');
59
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
60
* @copyright 2002-2010 Sebastian Bergmann <sb@sebastian-bergmann.de>
61
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
62
* @version Release: 3.4.14
63
* @link http://www.phpunit.de/
64
* @since Class available since Release 3.2.0
66
class PHPUnit_Util_Report_Node_File extends PHPUnit_Util_Report_Node
76
protected $codeLinesFillup = array();
81
protected $executedLines;
86
protected $yui = TRUE;
91
protected $highlight = FALSE;
96
protected $numExecutableLines = 0;
101
protected $numExecutedLines = 0;
106
protected $classes = array();
111
protected $numClasses = 0;
116
protected $numTestedClasses = 0;
121
protected $numMethods = 0;
126
protected $numTestedMethods = 0;
131
protected $yuiPanelJS = '';
136
protected $startLines = array();
141
protected $endLines = array();
146
* @param string $name
147
* @param PHPUnit_Util_Report_Node $parent
148
* @param array $executedLines
149
* @param boolean $yui
150
* @param boolean $highlight
151
* @throws RuntimeException
153
public function __construct($name, PHPUnit_Util_Report_Node $parent = NULL, array $executedLines, $yui = TRUE, $highlight = FALSE)
155
parent::__construct($name, $parent);
157
$path = $this->getPath();
159
if (!file_exists($path)) {
160
throw new PHPUnit_Framework_Exception(
161
sprintf('Path "%s" does not exist.', $path)
165
$this->executedLines = $executedLines;
166
$this->highlight = $highlight;
168
$this->codeLines = $this->loadFile($path);
170
$this->calculateStatistics();
174
* Returns the classes of this node.
178
public function getClasses()
180
return $this->classes;
184
* Returns the number of executable lines.
188
public function getNumExecutableLines()
190
return $this->numExecutableLines;
194
* Returns the number of executed lines.
198
public function getNumExecutedLines()
200
return $this->numExecutedLines;
204
* Returns the number of classes.
208
public function getNumClasses()
210
return $this->numClasses;
214
* Returns the number of tested classes.
218
public function getNumTestedClasses()
220
return $this->numTestedClasses;
224
* Returns the number of methods.
228
public function getNumMethods()
230
return $this->numMethods;
234
* Returns the number of tested methods.
238
public function getNumTestedMethods()
240
return $this->numTestedMethods;
246
* @param string $target
247
* @param string $title
248
* @param string $charset
249
* @param integer $lowUpperBound
250
* @param integer $highLowerBound
252
public function render($target, $title, $charset = 'ISO-8859-1', $lowUpperBound = 35, $highLowerBound = 70)
255
$template = new PHPUnit_Util_Template(
256
PHPUnit_Util_Report::$templatePath . 'file.html'
259
$yuiTemplate = new PHPUnit_Util_Template(
260
PHPUnit_Util_Report::$templatePath . 'yui_item.js'
263
$template = new PHPUnit_Util_Template(
264
PHPUnit_Util_Report::$templatePath . 'file_no_yui.html'
272
foreach ($this->codeLines as $line) {
273
if (strpos($line, '@codeCoverageIgnoreStart') !== FALSE) {
277
else if (strpos($line, '@codeCoverageIgnoreEnd') !== FALSE) {
283
if (!$ignore && isset($this->executedLines[$i])) {
286
// Array: Line is executable and was executed.
287
// count(Array) = Number of tests that hit this line.
288
if (is_array($this->executedLines[$i])) {
290
$numTests = count($this->executedLines[$i]);
291
$count = sprintf('%8d', $numTests);
297
foreach ($this->executedLines[$i] as $test) {
298
if (!isset($test->__liHtml)) {
299
$test->__liHtml = '';
301
if ($test instanceof PHPUnit_Framework_SelfDescribing) {
302
$testName = $test->toString();
304
if ($test instanceof PHPUnit_Framework_TestCase) {
305
switch ($test->getStatus()) {
306
case PHPUnit_Runner_BaseTestRunner::STATUS_PASSED: {
307
$testCSS = ' class=\"testPassed\"';
311
case PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE: {
312
$testCSS = ' class=\"testFailure\"';
316
case PHPUnit_Runner_BaseTestRunner::STATUS_ERROR: {
317
$testCSS = ' class=\"testError\"';
321
case PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE:
322
case PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED: {
323
$testCSS = ' class=\"testIncomplete\"';
334
$test->__liHtml .= sprintf(
338
addslashes(htmlspecialchars($testName))
342
$buffer .= $test->__liHtml;
346
$header = $numTests . ' tests cover';
348
$header = '1 test covers';
351
$header .= ' line ' . $i;
353
$yuiTemplate->setVar(
362
$this->yuiPanelJS .= $yuiTemplate->render();
366
// -1: Line is executable and was not executed.
367
else if ($this->executedLines[$i] == -1) {
368
$color = 'lineNoCov';
369
$count = sprintf('%8d', 0);
372
// -2: Line is dead code.
374
$color = 'lineDeadCode';
379
'<span class="%s"> %s : ',
386
$fillup = array_shift($this->codeLinesFillup);
389
$line .= str_repeat(' ', $fillup);
393
'<span class="lineNum" id="container%d"><a name="%d"></a><a href="#%d" id="line%d">%8d</a> </span>%s%s%s' . "\n",
400
!empty($css) ? $css : ' : ',
401
!$this->highlight ? htmlspecialchars($line) : $line,
402
!empty($css) ? '</span>' : ''
410
foreach ($this->classes as $className => $classData) {
411
if ($classData['executedLines'] == $classData['executableLines']) {
412
$numTestedClasses = 1;
413
$testedClassesPercent = 100;
415
$numTestedClasses = 0;
416
$testedClassesPercent = 0;
419
$numTestedMethods = 0;
420
$numMethods = count($classData['methods']);
422
foreach ($classData['methods'] as $method) {
423
if ($method['executedLines'] == $method['executableLines']) {
428
$items .= $this->doRenderItem(
431
'<b><a href="#%d">%s</a></b>',
433
$classData['startLine'],
437
'numTestedClasses' => $numTestedClasses,
438
'testedClassesPercent' => sprintf('%01.2f', $testedClassesPercent),
439
'numMethods' => $numMethods,
440
'numTestedMethods' => $numTestedMethods,
441
'testedMethodsPercent' => $this->calculatePercent(
442
$numTestedMethods, $numMethods
444
'numExecutableLines' => $classData['executableLines'],
445
'numExecutedLines' => $classData['executedLines'],
446
'executedLinesPercent' => $this->calculatePercent(
447
$classData['executedLines'], $classData['executableLines']
454
foreach ($classData['methods'] as $methodName => $methodData) {
455
if ($methodData['executedLines'] == $methodData['executableLines']) {
456
$numTestedMethods = 1;
457
$testedMethodsPercent = 100;
459
$numTestedMethods = 0;
460
$testedMethodsPercent = 0;
463
$items .= $this->doRenderItem(
466
' <a href="#%d">%s</a>',
468
$methodData['startLine'],
469
htmlspecialchars($methodData['signature'])
472
'numTestedClasses' => '',
473
'testedClassesPercent' => '',
475
'numTestedMethods' => $numTestedMethods,
476
'testedMethodsPercent' => sprintf('%01.2f', $testedMethodsPercent),
477
'numExecutableLines' => $methodData['executableLines'],
478
'numExecutedLines' => $methodData['executedLines'],
479
'executedLinesPercent' => $this->calculatePercent(
480
$methodData['executedLines'], $methodData['executableLines']
490
$this->setTemplateVars($template, $title, $charset);
495
'total_item' => $this->renderTotalItem($lowUpperBound, $highLowerBound, FALSE),
497
'yuiPanelJS' => $this->yuiPanelJS
501
$cleanId = PHPUnit_Util_Filesystem::getSafeFilename($this->getId());
502
$template->renderTo($target . $cleanId . '.html');
504
$this->yuiPanelJS = '';
505
$this->executedLines = array();
509
* Calculates coverage statistics for the file.
512
protected function calculateStatistics()
514
$this->processClasses();
515
$this->processFunctions();
520
foreach ($this->codeLines as $line) {
521
if (isset($this->startLines[$lineNumber])) {
522
// Start line of a class.
523
if (isset($this->startLines[$lineNumber]['methods'])) {
524
$currentClass = &$this->startLines[$lineNumber];
527
// Start line of a method.
529
$currentMethod = &$this->startLines[$lineNumber];
533
if (strpos($line, '@codeCoverageIgnore') !== FALSE) {
534
if (strpos($line, '@codeCoverageIgnoreStart') !== FALSE) {
535
$ignoreStart = $lineNumber;
538
else if (strpos($line, '@codeCoverageIgnoreEnd') !== FALSE) {
543
if (isset($this->executedLines[$lineNumber])) {
544
// Array: Line is executable and was executed.
545
if (is_array($this->executedLines[$lineNumber])) {
546
if (isset($currentClass)) {
547
$currentClass['executableLines']++;
548
$currentClass['executedLines']++;
551
if (isset($currentMethod)) {
552
$currentMethod['executableLines']++;
553
$currentMethod['executedLines']++;
556
$this->numExecutableLines++;
557
$this->numExecutedLines++;
560
// -1: Line is executable and was not executed.
561
else if ($this->executedLines[$lineNumber] == -1) {
562
if (isset($currentClass)) {
563
$currentClass['executableLines']++;
566
if (isset($currentMethod)) {
567
$currentMethod['executableLines']++;
570
$this->numExecutableLines++;
572
if ($ignoreStart != -1 && $lineNumber > $ignoreStart) {
573
if (isset($currentClass)) {
574
$currentClass['executedLines']++;
577
if (isset($currentMethod)) {
578
$currentMethod['executedLines']++;
581
$this->numExecutedLines++;
586
if (isset($this->endLines[$lineNumber])) {
587
// End line of a class.
588
if (isset($this->endLines[$lineNumber]['methods'])) {
589
unset($currentClass);
592
// End line of a method.
594
unset($currentMethod);
601
foreach ($this->classes as $className => $class) {
602
foreach ($class['methods'] as $method) {
603
if ($method['executedLines'] == $method['executableLines']) {
604
$this->numTestedMethods++;
608
if ($className != '*') {
609
if ($class['executedLines'] == $class['executableLines']) {
610
$this->numTestedClasses++;
617
* @author Aidan Lister <aidan@php.net>
618
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
619
* @param string $file
622
protected function loadFile($file)
624
$lines = explode("\n", str_replace("\t", ' ', file_get_contents($file)));
627
if (count($lines) == 0) {
631
$lines = array_map('rtrim', $lines);
632
$linesLength = array_map('strlen', $lines);
633
$width = max($linesLength);
635
foreach ($linesLength as $line => $length) {
636
$this->codeLinesFillup[$line] = $width - $length;
639
if (!$this->highlight) {
640
unset($lines[count($lines)-1]);
644
$tokens = token_get_all(file_get_contents($file));
649
foreach ($tokens as $j => $token) {
650
if (is_string($token)) {
651
if ($token === '"' && $tokens[$j - 1] !== '\\') {
652
$result[$i] .= sprintf(
653
'<span class="string">%s</span>',
655
htmlspecialchars($token)
658
$stringFlag = !$stringFlag;
660
$result[$i] .= sprintf(
661
'<span class="keyword">%s</span>',
663
htmlspecialchars($token)
670
list ($token, $value) = $token;
672
$value = str_replace(
674
array(' ', ' '),
675
htmlspecialchars($value)
678
if ($value === "\n") {
681
$lines = explode("\n", $value);
683
foreach ($lines as $jj => $line) {
691
case T_INLINE_HTML: {
697
case T_DOC_COMMENT: {
745
case T_IS_NOT_IDENTICAL:
746
case T_IS_SMALLER_OR_EQUAL:
749
case T_OBJECT_OPERATOR:
750
case T_PAAMAYIM_NEKUDOTAYIM:
761
case T_START_HEREDOC:
779
$result[$i] .= sprintf(
780
'<span class="%s">%s</span>',
787
if (isset($lines[$jj + 1])) {
794
unset($result[count($result)-1]);
799
protected function processClasses()
801
$classes = PHPUnit_Util_File::getClassesInFile($this->getPath());
803
foreach ($classes as $className => $class) {
804
$this->classes[$className] = array(
805
'methods' => array(),
806
'startLine' => $class['startLine'],
807
'executableLines' => 0,
811
$this->startLines[$class['startLine']] = &$this->classes[$className];
812
$this->endLines[$class['endLine']] = &$this->classes[$className];
814
foreach ($class['methods'] as $methodName => $method) {
815
$this->classes[$className]['methods'][$methodName] = array(
816
'signature' => $method['signature'],
817
'startLine' => $method['startLine'],
818
'executableLines' => 0,
822
$this->startLines[$method['startLine']] = &$this->classes[$className]['methods'][$methodName];
823
$this->endLines[$method['endLine']] = &$this->classes[$className]['methods'][$methodName];
832
protected function processFunctions()
834
$functions = PHPUnit_Util_File::getFunctionsInFile($this->getPath());
836
if (count($functions) > 0 && !isset($this->classes['*'])) {
837
$this->classes['*'] = array(
838
'methods' => array(),
840
'executableLines' => 0,
845
foreach ($functions as $functionName => $function) {
846
$this->classes['*']['methods'][$functionName] = array(
847
'signature' => $function['signature'],
848
'startLine' => $function['startLine'],
849
'executableLines' => 0,
853
$this->startLines[$function['startLine']] = &$this->classes['*']['methods'][$functionName];
854
$this->endLines[$function['endLine']] = &$this->classes['*']['methods'][$functionName];
b'\\ No newline at end of file'