Text.php 8.6 KB

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