123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- <?php declare(strict_types=1);
- /*
- * This file is part of phpunit/php-code-coverage.
- *
- * (c) Sebastian Bergmann <sebastian@phpunit.de>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace SebastianBergmann\CodeCoverage;
- use function array_key_exists;
- use function array_keys;
- use function array_merge;
- use function array_unique;
- use function count;
- use function is_array;
- use function ksort;
- use SebastianBergmann\CodeCoverage\Driver\Driver;
- /**
- * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
- */
- final class ProcessedCodeCoverageData
- {
- /**
- * Line coverage data.
- * An array of filenames, each having an array of linenumbers, each executable line having an array of testcase ids.
- *
- * @var array
- */
- private $lineCoverage = [];
- /**
- * Function coverage data.
- * Maintains base format of raw data (@see https://xdebug.org/docs/code_coverage), but each 'hit' entry is an array
- * of testcase ids.
- *
- * @var array
- */
- private $functionCoverage = [];
- public function initializeUnseenData(RawCodeCoverageData $rawData): void
- {
- foreach ($rawData->lineCoverage() as $file => $lines) {
- if (!isset($this->lineCoverage[$file])) {
- $this->lineCoverage[$file] = [];
- foreach ($lines as $k => $v) {
- $this->lineCoverage[$file][$k] = $v === Driver::LINE_NOT_EXECUTABLE ? null : [];
- }
- }
- }
- foreach ($rawData->functionCoverage() as $file => $functions) {
- foreach ($functions as $functionName => $functionData) {
- if (isset($this->functionCoverage[$file][$functionName])) {
- $this->initPreviouslySeenFunction($file, $functionName, $functionData);
- } else {
- $this->initPreviouslyUnseenFunction($file, $functionName, $functionData);
- }
- }
- }
- }
- public function markCodeAsExecutedByTestCase(string $testCaseId, RawCodeCoverageData $executedCode): void
- {
- foreach ($executedCode->lineCoverage() as $file => $lines) {
- foreach ($lines as $k => $v) {
- if ($v === Driver::LINE_EXECUTED) {
- $this->lineCoverage[$file][$k][] = $testCaseId;
- }
- }
- }
- foreach ($executedCode->functionCoverage() as $file => $functions) {
- foreach ($functions as $functionName => $functionData) {
- foreach ($functionData['branches'] as $branchId => $branchData) {
- if ($branchData['hit'] === Driver::BRANCH_HIT) {
- $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'][] = $testCaseId;
- }
- }
- foreach ($functionData['paths'] as $pathId => $pathData) {
- if ($pathData['hit'] === Driver::BRANCH_HIT) {
- $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'][] = $testCaseId;
- }
- }
- }
- }
- }
- public function setLineCoverage(array $lineCoverage): void
- {
- $this->lineCoverage = $lineCoverage;
- }
- public function lineCoverage(): array
- {
- ksort($this->lineCoverage);
- return $this->lineCoverage;
- }
- public function setFunctionCoverage(array $functionCoverage): void
- {
- $this->functionCoverage = $functionCoverage;
- }
- public function functionCoverage(): array
- {
- ksort($this->functionCoverage);
- return $this->functionCoverage;
- }
- public function coveredFiles(): array
- {
- ksort($this->lineCoverage);
- return array_keys($this->lineCoverage);
- }
- public function renameFile(string $oldFile, string $newFile): void
- {
- $this->lineCoverage[$newFile] = $this->lineCoverage[$oldFile];
- if (isset($this->functionCoverage[$oldFile])) {
- $this->functionCoverage[$newFile] = $this->functionCoverage[$oldFile];
- }
- unset($this->lineCoverage[$oldFile], $this->functionCoverage[$oldFile]);
- }
- public function removeFilesWithNoCoverage(): void
- {
- foreach ($this->lineCoverage as $file => $lines) {
- foreach ($lines as $line) {
- if (is_array($line) && !empty($line)) {
- continue 2;
- }
- }
- unset($file);
- }
- }
- public function merge(self $newData): void
- {
- foreach ($newData->lineCoverage as $file => $lines) {
- if (!isset($this->lineCoverage[$file])) {
- $this->lineCoverage[$file] = $lines;
- continue;
- }
- // we should compare the lines if any of two contains data
- $compareLineNumbers = array_unique(
- array_merge(
- array_keys($this->lineCoverage[$file]),
- array_keys($newData->lineCoverage[$file])
- )
- );
- foreach ($compareLineNumbers as $line) {
- $thatPriority = $this->priorityForLine($newData->lineCoverage[$file], $line);
- $thisPriority = $this->priorityForLine($this->lineCoverage[$file], $line);
- if ($thatPriority > $thisPriority) {
- $this->lineCoverage[$file][$line] = $newData->lineCoverage[$file][$line];
- } elseif ($thatPriority === $thisPriority && is_array($this->lineCoverage[$file][$line])) {
- $this->lineCoverage[$file][$line] = array_unique(
- array_merge($this->lineCoverage[$file][$line], $newData->lineCoverage[$file][$line])
- );
- }
- }
- }
- foreach ($newData->functionCoverage as $file => $functions) {
- if (!isset($this->functionCoverage[$file])) {
- $this->functionCoverage[$file] = $functions;
- continue;
- }
- foreach ($functions as $functionName => $functionData) {
- if (isset($this->functionCoverage[$file][$functionName])) {
- $this->initPreviouslySeenFunction($file, $functionName, $functionData);
- } else {
- $this->initPreviouslyUnseenFunction($file, $functionName, $functionData);
- }
- foreach ($functionData['branches'] as $branchId => $branchData) {
- $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'], $branchData['hit']));
- }
- foreach ($functionData['paths'] as $pathId => $pathData) {
- $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = array_unique(array_merge($this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'], $pathData['hit']));
- }
- }
- }
- }
- /**
- * Determine the priority for a line.
- *
- * 1 = the line is not set
- * 2 = the line has not been tested
- * 3 = the line is dead code
- * 4 = the line has been tested
- *
- * During a merge, a higher number is better.
- */
- private function priorityForLine(array $data, int $line): int
- {
- if (!array_key_exists($line, $data)) {
- return 1;
- }
- if (is_array($data[$line]) && count($data[$line]) === 0) {
- return 2;
- }
- if ($data[$line] === null) {
- return 3;
- }
- return 4;
- }
- /**
- * For a function we have never seen before, copy all data over and simply init the 'hit' array.
- */
- private function initPreviouslyUnseenFunction(string $file, string $functionName, array $functionData): void
- {
- $this->functionCoverage[$file][$functionName] = $functionData;
- foreach (array_keys($functionData['branches']) as $branchId) {
- $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = [];
- }
- foreach (array_keys($functionData['paths']) as $pathId) {
- $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = [];
- }
- }
- /**
- * For a function we have seen before, only copy over and init the 'hit' array for any unseen branches and paths.
- * Techniques such as mocking and where the contents of a file are different vary during tests (e.g. compiling
- * containers) mean that the functions inside a file cannot be relied upon to be static.
- */
- private function initPreviouslySeenFunction(string $file, string $functionName, array $functionData): void
- {
- foreach ($functionData['branches'] as $branchId => $branchData) {
- if (!isset($this->functionCoverage[$file][$functionName]['branches'][$branchId])) {
- $this->functionCoverage[$file][$functionName]['branches'][$branchId] = $branchData;
- $this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = [];
- }
- }
- foreach ($functionData['paths'] as $pathId => $pathData) {
- if (!isset($this->functionCoverage[$file][$functionName]['paths'][$pathId])) {
- $this->functionCoverage[$file][$functionName]['paths'][$pathId] = $pathData;
- $this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = [];
- }
- }
- }
- }
|