Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. ProcessedCodeCoverageData.php

class ProcessedCodeCoverageData

@internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage

@psalm-import-type XdebugFunctionCoverageType from \SebastianBergmann\CodeCoverage\Driver\XdebugDriver

@psalm-type TestIdType = string

Hierarchy

  • class \SebastianBergmann\CodeCoverage\Data\ProcessedCodeCoverageData

Expanded class hierarchy of ProcessedCodeCoverageData

2 files declare their use of ProcessedCodeCoverageData
Builder.php in vendor/phpunit/php-code-coverage/src/Node/Builder.php
CodeCoverage.php in vendor/phpunit/php-code-coverage/src/CodeCoverage.php

File

vendor/phpunit/php-code-coverage/src/Data/ProcessedCodeCoverageData.php, line 28

Namespace

SebastianBergmann\CodeCoverage\Data
View source
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.
     *
     * @psalm-var array<string, array<int, null|list<TestIdType>>>
     */
    private array $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.
     *
     * @psalm-var array<string, array<string, array{
     *     branches: array<int, array{
     *         op_start: int,
     *         op_end: int,
     *         line_start: int,
     *         line_end: int,
     *         hit: list<TestIdType>,
     *         out: array<int, int>,
     *         out_hit: array<int, int>,
     *     }>,
     *     paths: array<int, array{
     *         path: array<int, int>,
     *         hit: list<TestIdType>,
     *     }>,
     *     hit: list<TestIdType>
     * }>>
     */
    private array $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 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.
     *
     * @psalm-param XdebugFunctionCoverageType $functionData
     */
    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.
     *
     * @psalm-param XdebugFunctionCoverageType $functionData
     */
    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'] = [];
            }
        }
    }

}

Members

Title Sort descending Modifiers Object type Summary
ProcessedCodeCoverageData::$functionCoverage private property Function coverage data.
Maintains base format of raw data (of testcase ids.
ProcessedCodeCoverageData::$lineCoverage private property Line coverage data.
An array of filenames, each having an array of linenumbers, each executable line having an array of testcase ids.
ProcessedCodeCoverageData::coveredFiles public function
ProcessedCodeCoverageData::functionCoverage public function
ProcessedCodeCoverageData::initializeUnseenData public function
ProcessedCodeCoverageData::initPreviouslySeenFunction private function For a function we have seen before, only copy over and init the &#039;hit&#039; 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…
ProcessedCodeCoverageData::initPreviouslyUnseenFunction private function For a function we have never seen before, copy all data over and simply init the &#039;hit&#039; array.
ProcessedCodeCoverageData::lineCoverage public function
ProcessedCodeCoverageData::markCodeAsExecutedByTestCase public function
ProcessedCodeCoverageData::merge public function
ProcessedCodeCoverageData::priorityForLine private function Determine the priority for a line.
ProcessedCodeCoverageData::renameFile public function
ProcessedCodeCoverageData::setFunctionCoverage public function
ProcessedCodeCoverageData::setLineCoverage public function

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal