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

https://gitlab.com/madwanz64/laravel · PHP · 255 lines · 173 code · 39 blank · 43 comment · 19 complexity · 5b271089764452e7de043c12fff2ade6 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_key_exists;
  12. use function array_keys;
  13. use function array_merge;
  14. use function array_unique;
  15. use function count;
  16. use function is_array;
  17. use function ksort;
  18. use SebastianBergmann\CodeCoverage\Driver\Driver;
  19. /**
  20. * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
  21. */
  22. final class ProcessedCodeCoverageData
  23. {
  24. /**
  25. * Line coverage data.
  26. * An array of filenames, each having an array of linenumbers, each executable line having an array of testcase ids.
  27. *
  28. * @var array
  29. */
  30. private $lineCoverage = [];
  31. /**
  32. * Function coverage data.
  33. * Maintains base format of raw data (@see https://xdebug.org/docs/code_coverage), but each 'hit' entry is an array
  34. * of testcase ids.
  35. *
  36. * @var array
  37. */
  38. private $functionCoverage = [];
  39. public function initializeUnseenData(RawCodeCoverageData $rawData): void
  40. {
  41. foreach ($rawData->lineCoverage() as $file => $lines) {
  42. if (!isset($this->lineCoverage[$file])) {
  43. $this->lineCoverage[$file] = [];
  44. foreach ($lines as $k => $v) {
  45. $this->lineCoverage[$file][$k] = $v === Driver::LINE_NOT_EXECUTABLE ? null : [];
  46. }
  47. }
  48. }
  49. foreach ($rawData->functionCoverage() as $file => $functions) {
  50. foreach ($functions as $functionName => $functionData) {
  51. if (isset($this->functionCoverage[$file][$functionName])) {
  52. $this->initPreviouslySeenFunction($file, $functionName, $functionData);
  53. } else {
  54. $this->initPreviouslyUnseenFunction($file, $functionName, $functionData);
  55. }
  56. }
  57. }
  58. }
  59. public function markCodeAsExecutedByTestCase(string $testCaseId, RawCodeCoverageData $executedCode): void
  60. {
  61. foreach ($executedCode->lineCoverage() as $file => $lines) {
  62. foreach ($lines as $k => $v) {
  63. if ($v === Driver::LINE_EXECUTED) {
  64. $this->lineCoverage[$file][$k][] = $testCaseId;
  65. }
  66. }
  67. }
  68. foreach ($executedCode->functionCoverage() as $file => $functions) {
  69. foreach ($functions as $functionName => $functionData) {
  70. foreach ($functionData['branches'] as $branchId => $branchData) {
  71. if ($branchData['hit'] === Driver::BRANCH_HIT) {
  72. $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'][] = $testCaseId;
  73. }
  74. }
  75. foreach ($functionData['paths'] as $pathId => $pathData) {
  76. if ($pathData['hit'] === Driver::BRANCH_HIT) {
  77. $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'][] = $testCaseId;
  78. }
  79. }
  80. }
  81. }
  82. }
  83. public function setLineCoverage(array $lineCoverage): void
  84. {
  85. $this->lineCoverage = $lineCoverage;
  86. }
  87. public function lineCoverage(): array
  88. {
  89. ksort($this->lineCoverage);
  90. return $this->lineCoverage;
  91. }
  92. public function setFunctionCoverage(array $functionCoverage): void
  93. {
  94. $this->functionCoverage = $functionCoverage;
  95. }
  96. public function functionCoverage(): array
  97. {
  98. ksort($this->functionCoverage);
  99. return $this->functionCoverage;
  100. }
  101. public function coveredFiles(): array
  102. {
  103. ksort($this->lineCoverage);
  104. return array_keys($this->lineCoverage);
  105. }
  106. public function renameFile(string $oldFile, string $newFile): void
  107. {
  108. $this->lineCoverage[$newFile] = $this->lineCoverage[$oldFile];
  109. if (isset($this->functionCoverage[$oldFile])) {
  110. $this->functionCoverage[$newFile] = $this->functionCoverage[$oldFile];
  111. }
  112. unset($this->lineCoverage[$oldFile], $this->functionCoverage[$oldFile]);
  113. }
  114. public function merge(self $newData): void
  115. {
  116. foreach ($newData->lineCoverage as $file => $lines) {
  117. if (!isset($this->lineCoverage[$file])) {
  118. $this->lineCoverage[$file] = $lines;
  119. continue;
  120. }
  121. // we should compare the lines if any of two contains data
  122. $compareLineNumbers = array_unique(
  123. array_merge(
  124. array_keys($this->lineCoverage[$file]),
  125. array_keys($newData->lineCoverage[$file])
  126. )
  127. );
  128. foreach ($compareLineNumbers as $line) {
  129. $thatPriority = $this->priorityForLine($newData->lineCoverage[$file], $line);
  130. $thisPriority = $this->priorityForLine($this->lineCoverage[$file], $line);
  131. if ($thatPriority > $thisPriority) {
  132. $this->lineCoverage[$file][$line] = $newData->lineCoverage[$file][$line];
  133. } elseif ($thatPriority === $thisPriority && is_array($this->lineCoverage[$file][$line])) {
  134. $this->lineCoverage[$file][$line] = array_unique(
  135. array_merge($this->lineCoverage[$file][$line], $newData->lineCoverage[$file][$line])
  136. );
  137. }
  138. }
  139. }
  140. foreach ($newData->functionCoverage as $file => $functions) {
  141. if (!isset($this->functionCoverage[$file])) {
  142. $this->functionCoverage[$file] = $functions;
  143. continue;
  144. }
  145. foreach ($functions as $functionName => $functionData) {
  146. if (isset($this->functionCoverage[$file][$functionName])) {
  147. $this->initPreviouslySeenFunction($file, $functionName, $functionData);
  148. } else {
  149. $this->initPreviouslyUnseenFunction($file, $functionName, $functionData);
  150. }
  151. foreach ($functionData['branches'] as $branchId => $branchData) {
  152. $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'], $branchData['hit']));
  153. }
  154. foreach ($functionData['paths'] as $pathId => $pathData) {
  155. $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'], $pathData['hit']));
  156. }
  157. }
  158. }
  159. }
  160. /**
  161. * Determine the priority for a line.
  162. *
  163. * 1 = the line is not set
  164. * 2 = the line has not been tested
  165. * 3 = the line is dead code
  166. * 4 = the line has been tested
  167. *
  168. * During a merge, a higher number is better.
  169. */
  170. private function priorityForLine(array $data, int $line): int
  171. {
  172. if (!array_key_exists($line, $data)) {
  173. return 1;
  174. }
  175. if (is_array($data[$line]) && count($data[$line]) === 0) {
  176. return 2;
  177. }
  178. if ($data[$line] === null) {
  179. return 3;
  180. }
  181. return 4;
  182. }
  183. /**
  184. * For a function we have never seen before, copy all data over and simply init the 'hit' array.
  185. */
  186. private function initPreviouslyUnseenFunction(string $file, string $functionName, array $functionData): void
  187. {
  188. $this->functionCoverage[$file][$functionName] = $functionData;
  189. foreach (array_keys($functionData['branches']) as $branchId) {
  190. $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = [];
  191. }
  192. foreach (array_keys($functionData['paths']) as $pathId) {
  193. $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = [];
  194. }
  195. }
  196. /**
  197. * For a function we have seen before, only copy over and init the 'hit' array for any unseen branches and paths.
  198. * Techniques such as mocking and where the contents of a file are different vary during tests (e.g. compiling
  199. * containers) mean that the functions inside a file cannot be relied upon to be static.
  200. */
  201. private function initPreviouslySeenFunction(string $file, string $functionName, array $functionData): void
  202. {
  203. foreach ($functionData['branches'] as $branchId => $branchData) {
  204. if (!isset($this->functionCoverage[$file][$functionName]['branches'][$branchId])) {
  205. $this->functionCoverage[$file][$functionName]['branches'][$branchId] = $branchData;
  206. $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = [];
  207. }
  208. }
  209. foreach ($functionData['paths'] as $pathId => $pathData) {
  210. if (!isset($this->functionCoverage[$file][$functionName]['paths'][$pathId])) {
  211. $this->functionCoverage[$file][$functionName]['paths'][$pathId] = $pathData;
  212. $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = [];
  213. }
  214. }
  215. }
  216. }