PageRenderTime 38ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/phpunit/php-code-coverage/src/RawCodeCoverageData.php

https://gitlab.com/jjpa2018/dashboard
PHP | 227 lines | 151 code | 36 blank | 40 comment | 13 complexity | 006356e880fe76f64ab63af3a3e22a36 MD5 | raw file
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of phpunit/php-code-coverage.
  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;
  11. use function array_diff;
  12. use function array_diff_key;
  13. use function array_flip;
  14. use function array_intersect;
  15. use function array_intersect_key;
  16. use function count;
  17. use function file;
  18. use function in_array;
  19. use function range;
  20. use SebastianBergmann\CodeCoverage\Driver\Driver;
  21. use SebastianBergmann\CodeCoverage\StaticAnalysis\UncoveredFileAnalyser;
  22. /**
  23. * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
  24. */
  25. final class RawCodeCoverageData
  26. {
  27. /**
  28. * @var array<string, array<int>>
  29. */
  30. private static $emptyLineCache = [];
  31. /**
  32. * @var array
  33. *
  34. * @see https://xdebug.org/docs/code_coverage for format
  35. */
  36. private $lineCoverage;
  37. /**
  38. * @var array
  39. *
  40. * @see https://xdebug.org/docs/code_coverage for format
  41. */
  42. private $functionCoverage;
  43. public static function fromXdebugWithoutPathCoverage(array $rawCoverage): self
  44. {
  45. return new self($rawCoverage, []);
  46. }
  47. public static function fromXdebugWithPathCoverage(array $rawCoverage): self
  48. {
  49. $lineCoverage = [];
  50. $functionCoverage = [];
  51. foreach ($rawCoverage as $file => $fileCoverageData) {
  52. $lineCoverage[$file] = $fileCoverageData['lines'];
  53. $functionCoverage[$file] = $fileCoverageData['functions'];
  54. }
  55. return new self($lineCoverage, $functionCoverage);
  56. }
  57. public static function fromXdebugWithMixedCoverage(array $rawCoverage): self
  58. {
  59. $lineCoverage = [];
  60. $functionCoverage = [];
  61. foreach ($rawCoverage as $file => $fileCoverageData) {
  62. if (!isset($fileCoverageData['functions'])) {
  63. // Current file does not have functions, so line coverage
  64. // is stored in $fileCoverageData, not in $fileCoverageData['lines']
  65. $lineCoverage[$file] = $fileCoverageData;
  66. continue;
  67. }
  68. $lineCoverage[$file] = $fileCoverageData['lines'];
  69. $functionCoverage[$file] = $fileCoverageData['functions'];
  70. }
  71. return new self($lineCoverage, $functionCoverage);
  72. }
  73. public static function fromUncoveredFile(string $filename, UncoveredFileAnalyser $uncoveredFileAnalyser): self
  74. {
  75. $lineCoverage = [];
  76. foreach ($uncoveredFileAnalyser->executableLinesIn($filename) as $line) {
  77. $lineCoverage[$line] = Driver::LINE_NOT_EXECUTED;
  78. }
  79. return new self([$filename => $lineCoverage], []);
  80. }
  81. private function __construct(array $lineCoverage, array $functionCoverage)
  82. {
  83. $this->lineCoverage = $lineCoverage;
  84. $this->functionCoverage = $functionCoverage;
  85. $this->skipEmptyLines();
  86. }
  87. public function clear(): void
  88. {
  89. $this->lineCoverage = $this->functionCoverage = [];
  90. }
  91. public function lineCoverage(): array
  92. {
  93. return $this->lineCoverage;
  94. }
  95. public function functionCoverage(): array
  96. {
  97. return $this->functionCoverage;
  98. }
  99. public function removeCoverageDataForFile(string $filename): void
  100. {
  101. unset($this->lineCoverage[$filename], $this->functionCoverage[$filename]);
  102. }
  103. /**
  104. * @param int[] $lines
  105. */
  106. public function keepCoverageDataOnlyForLines(string $filename, array $lines): void
  107. {
  108. if (!isset($this->lineCoverage[$filename])) {
  109. return;
  110. }
  111. $this->lineCoverage[$filename] = array_intersect_key(
  112. $this->lineCoverage[$filename],
  113. array_flip($lines)
  114. );
  115. if (isset($this->functionCoverage[$filename])) {
  116. foreach ($this->functionCoverage[$filename] as $functionName => $functionData) {
  117. foreach ($functionData['branches'] as $branchId => $branch) {
  118. if (count(array_diff(range($branch['line_start'], $branch['line_end']), $lines)) > 0) {
  119. unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]);
  120. foreach ($functionData['paths'] as $pathId => $path) {
  121. if (in_array($branchId, $path['path'], true)) {
  122. unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]);
  123. }
  124. }
  125. }
  126. }
  127. }
  128. }
  129. }
  130. /**
  131. * @param int[] $lines
  132. */
  133. public function removeCoverageDataForLines(string $filename, array $lines): void
  134. {
  135. if (empty($lines)) {
  136. return;
  137. }
  138. if (!isset($this->lineCoverage[$filename])) {
  139. return;
  140. }
  141. $this->lineCoverage[$filename] = array_diff_key(
  142. $this->lineCoverage[$filename],
  143. array_flip($lines)
  144. );
  145. if (isset($this->functionCoverage[$filename])) {
  146. foreach ($this->functionCoverage[$filename] as $functionName => $functionData) {
  147. foreach ($functionData['branches'] as $branchId => $branch) {
  148. if (count(array_intersect($lines, range($branch['line_start'], $branch['line_end']))) > 0) {
  149. unset($this->functionCoverage[$filename][$functionName]['branches'][$branchId]);
  150. foreach ($functionData['paths'] as $pathId => $path) {
  151. if (in_array($branchId, $path['path'], true)) {
  152. unset($this->functionCoverage[$filename][$functionName]['paths'][$pathId]);
  153. }
  154. }
  155. }
  156. }
  157. }
  158. }
  159. }
  160. /**
  161. * At the end of a file, the PHP interpreter always sees an implicit return. Where this occurs in a file that has
  162. * e.g. a class definition, that line cannot be invoked from a test and results in confusing coverage. This engine
  163. * implementation detail therefore needs to be masked which is done here by simply ensuring that all empty lines
  164. * are skipped over for coverage purposes.
  165. *
  166. * @see https://github.com/sebastianbergmann/php-code-coverage/issues/799
  167. */
  168. private function skipEmptyLines(): void
  169. {
  170. foreach ($this->lineCoverage as $filename => $coverage) {
  171. foreach ($this->getEmptyLinesForFile($filename) as $emptyLine) {
  172. unset($this->lineCoverage[$filename][$emptyLine]);
  173. }
  174. }
  175. }
  176. private function getEmptyLinesForFile(string $filename): array
  177. {
  178. if (!isset(self::$emptyLineCache[$filename])) {
  179. self::$emptyLineCache[$filename] = [];
  180. if (is_file($filename)) {
  181. $sourceLines = explode("\n", file_get_contents($filename));
  182. foreach ($sourceLines as $line => $source) {
  183. if (trim($source) === '') {
  184. self::$emptyLineCache[$filename][] = ($line + 1);
  185. }
  186. }
  187. }
  188. }
  189. return self::$emptyLineCache[$filename];
  190. }
  191. }