1<?php 2/* 3 * This file is part of the php-code-coverage package. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11namespace SebastianBergmann\CodeCoverage\Report; 12 13use SebastianBergmann\CodeCoverage\CodeCoverage; 14use SebastianBergmann\CodeCoverage\Node\File; 15use SebastianBergmann\CodeCoverage\Util; 16 17/** 18 * Generates human readable output from a code coverage object. 19 * 20 * The output gets put into a text file our written to the CLI. 21 */ 22class Text 23{ 24 private $lowUpperBound; 25 private $highLowerBound; 26 private $showUncoveredFiles; 27 private $showOnlySummary; 28 29 private $colors = [ 30 'green' => "\x1b[30;42m", 31 'yellow' => "\x1b[30;43m", 32 'red' => "\x1b[37;41m", 33 'header' => "\x1b[1;37;40m", 34 'reset' => "\x1b[0m", 35 'eol' => "\x1b[2K", 36 ]; 37 38 /** 39 * @param int $lowUpperBound 40 * @param int $highLowerBound 41 * @param bool $showUncoveredFiles 42 * @param bool $showOnlySummary 43 */ 44 public function __construct($lowUpperBound = 50, $highLowerBound = 90, $showUncoveredFiles = false, $showOnlySummary = false) 45 { 46 $this->lowUpperBound = $lowUpperBound; 47 $this->highLowerBound = $highLowerBound; 48 $this->showUncoveredFiles = $showUncoveredFiles; 49 $this->showOnlySummary = $showOnlySummary; 50 } 51 52 /** 53 * @param CodeCoverage $coverage 54 * @param bool $showColors 55 * 56 * @return string 57 */ 58 public function process(CodeCoverage $coverage, $showColors = false) 59 { 60 $output = PHP_EOL . PHP_EOL; 61 $report = $coverage->getReport(); 62 unset($coverage); 63 64 $colors = [ 65 'header' => '', 66 'classes' => '', 67 'methods' => '', 68 'lines' => '', 69 'reset' => '', 70 'eol' => '' 71 ]; 72 73 if ($showColors) { 74 $colors['classes'] = $this->getCoverageColor( 75 $report->getNumTestedClassesAndTraits(), 76 $report->getNumClassesAndTraits() 77 ); 78 $colors['methods'] = $this->getCoverageColor( 79 $report->getNumTestedMethods(), 80 $report->getNumMethods() 81 ); 82 $colors['lines'] = $this->getCoverageColor( 83 $report->getNumExecutedLines(), 84 $report->getNumExecutableLines() 85 ); 86 $colors['reset'] = $this->colors['reset']; 87 $colors['header'] = $this->colors['header']; 88 $colors['eol'] = $this->colors['eol']; 89 } 90 91 $classes = sprintf( 92 ' Classes: %6s (%d/%d)', 93 Util::percent( 94 $report->getNumTestedClassesAndTraits(), 95 $report->getNumClassesAndTraits(), 96 true 97 ), 98 $report->getNumTestedClassesAndTraits(), 99 $report->getNumClassesAndTraits() 100 ); 101 102 $methods = sprintf( 103 ' Methods: %6s (%d/%d)', 104 Util::percent( 105 $report->getNumTestedMethods(), 106 $report->getNumMethods(), 107 true 108 ), 109 $report->getNumTestedMethods(), 110 $report->getNumMethods() 111 ); 112 113 $lines = sprintf( 114 ' Lines: %6s (%d/%d)', 115 Util::percent( 116 $report->getNumExecutedLines(), 117 $report->getNumExecutableLines(), 118 true 119 ), 120 $report->getNumExecutedLines(), 121 $report->getNumExecutableLines() 122 ); 123 124 $padding = max(array_map('strlen', [$classes, $methods, $lines])); 125 126 if ($this->showOnlySummary) { 127 $title = 'Code Coverage Report Summary:'; 128 $padding = max($padding, strlen($title)); 129 130 $output .= $this->format($colors['header'], $padding, $title); 131 } else { 132 $date = date(' Y-m-d H:i:s', $_SERVER['REQUEST_TIME']); 133 $title = 'Code Coverage Report:'; 134 135 $output .= $this->format($colors['header'], $padding, $title); 136 $output .= $this->format($colors['header'], $padding, $date); 137 $output .= $this->format($colors['header'], $padding, ''); 138 $output .= $this->format($colors['header'], $padding, ' Summary:'); 139 } 140 141 $output .= $this->format($colors['classes'], $padding, $classes); 142 $output .= $this->format($colors['methods'], $padding, $methods); 143 $output .= $this->format($colors['lines'], $padding, $lines); 144 145 if ($this->showOnlySummary) { 146 return $output . PHP_EOL; 147 } 148 149 $classCoverage = []; 150 151 foreach ($report as $item) { 152 if (!$item instanceof File) { 153 continue; 154 } 155 156 $classes = $item->getClassesAndTraits(); 157 158 foreach ($classes as $className => $class) { 159 $classStatements = 0; 160 $coveredClassStatements = 0; 161 $coveredMethods = 0; 162 $classMethods = 0; 163 164 foreach ($class['methods'] as $method) { 165 if ($method['executableLines'] == 0) { 166 continue; 167 } 168 169 $classMethods++; 170 $classStatements += $method['executableLines']; 171 $coveredClassStatements += $method['executedLines']; 172 if ($method['coverage'] == 100) { 173 $coveredMethods++; 174 } 175 } 176 177 if (!empty($class['package']['namespace'])) { 178 $namespace = '\\' . $class['package']['namespace'] . '::'; 179 } elseif (!empty($class['package']['fullPackage'])) { 180 $namespace = '@' . $class['package']['fullPackage'] . '::'; 181 } else { 182 $namespace = ''; 183 } 184 185 $classCoverage[$namespace . $className] = [ 186 'namespace' => $namespace, 187 'className ' => $className, 188 'methodsCovered' => $coveredMethods, 189 'methodCount' => $classMethods, 190 'statementsCovered' => $coveredClassStatements, 191 'statementCount' => $classStatements, 192 ]; 193 } 194 } 195 196 ksort($classCoverage); 197 198 $methodColor = ''; 199 $linesColor = ''; 200 $resetColor = ''; 201 202 foreach ($classCoverage as $fullQualifiedPath => $classInfo) { 203 if ($classInfo['statementsCovered'] != 0 || 204 $this->showUncoveredFiles) { 205 if ($showColors) { 206 $methodColor = $this->getCoverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); 207 $linesColor = $this->getCoverageColor($classInfo['statementsCovered'], $classInfo['statementCount']); 208 $resetColor = $colors['reset']; 209 } 210 211 $output .= PHP_EOL . $fullQualifiedPath . PHP_EOL 212 . ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], 2) . $resetColor . ' ' 213 . ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], 3) . $resetColor 214 ; 215 } 216 } 217 218 return $output . PHP_EOL; 219 } 220 221 protected function getCoverageColor($numberOfCoveredElements, $totalNumberOfElements) 222 { 223 $coverage = Util::percent( 224 $numberOfCoveredElements, 225 $totalNumberOfElements 226 ); 227 228 if ($coverage >= $this->highLowerBound) { 229 return $this->colors['green']; 230 } elseif ($coverage > $this->lowUpperBound) { 231 return $this->colors['yellow']; 232 } 233 234 return $this->colors['red']; 235 } 236 237 protected function printCoverageCounts($numberOfCoveredElements, $totalNumberOfElements, $precision) 238 { 239 $format = '%' . $precision . 's'; 240 241 return Util::percent( 242 $numberOfCoveredElements, 243 $totalNumberOfElements, 244 true, 245 true 246 ) . 247 ' (' . sprintf($format, $numberOfCoveredElements) . '/' . 248 sprintf($format, $totalNumberOfElements) . ')'; 249 } 250 251 private function format($color, $padding, $string) 252 { 253 $reset = $color ? $this->colors['reset'] : ''; 254 255 return $color . str_pad($string, $padding) . $reset . PHP_EOL; 256 } 257} 258