Text.php 8.5 KB

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