Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/Data/ProcessedBranchCoverageData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?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\Data;

use function array_merge;
use function array_unique;
use NoDiscard;
use SebastianBergmann\CodeCoverage\Driver\XdebugDriver;

/**
* @phpstan-import-type TestIdType from ProcessedCodeCoverageData
* @phpstan-import-type XdebugBranchCoverageType from XdebugDriver
*/
final class ProcessedBranchCoverageData
{
/**
* @param XdebugBranchCoverageType $xdebugCoverageData
*/
public static function fromXdebugCoverage(array $xdebugCoverageData): self
{
return new self(
$xdebugCoverageData['op_start'],
$xdebugCoverageData['op_end'],
$xdebugCoverageData['line_start'],
$xdebugCoverageData['line_end'],
[],
$xdebugCoverageData['out'],
$xdebugCoverageData['out_hit'],
);
}

public function __construct(
public readonly int $op_start,
public readonly int $op_end,
public readonly int $line_start,
public readonly int $line_end,
/** @var list<TestIdType> */
public array $hit,
/** @var array<int, int> */
public readonly array $out,
/** @var array<int, int> */
public readonly array $out_hit,
) {
}

#[NoDiscard]
public function merge(self $data): self
{
if ($data->hit === []) {
return $this;
}

return new self(
$this->op_start,
$this->op_end,
$this->line_start,
$this->line_end,
array_unique(array_merge($this->hit, $data->hit)),
$this->out,
$this->out_hit,
);
}

/**
* @param TestIdType $testCaseId
*/
public function recordHit(string $testCaseId): void
{
$this->hit[] = $testCaseId;
}
}
63 changes: 15 additions & 48 deletions src/Data/ProcessedCodeCoverageData.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,7 @@
* @phpstan-import-type XdebugFunctionCoverageType from XdebugDriver
*
* @phpstan-type TestIdType string
* @phpstan-type FunctionCoverageDataType 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>,
* }>
* }
* @phpstan-type FunctionCoverageType array<string, array<string, FunctionCoverageDataType>>
* @phpstan-type FunctionCoverageType array<string, array<string, ProcessedFunctionCoverageData>>
* @phpstan-type LineCoverageType array<string, array<int, null|list<TestIdType>>>
*/
final class ProcessedCodeCoverageData
Expand Down Expand Up @@ -99,13 +84,13 @@ public function markCodeAsExecutedByTestCase(string $testCaseId, RawCodeCoverage
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;
$this->functionCoverage[$file][$functionName]->recordBranchHit($branchId, $testCaseId);
}
}

foreach ($functionData['paths'] as $pathId => $pathData) {
if ($pathData['hit'] === Driver::BRANCH_HIT) {
$this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'][] = $testCaseId;
$this->functionCoverage[$file][$functionName]->recordPathHit($pathId, $testCaseId);
}
}
}
Expand Down Expand Up @@ -213,14 +198,6 @@ public function merge(self $newData): void
} 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']));
}
}
}
}
Expand Down Expand Up @@ -257,42 +234,32 @@ private function priorityForLine(array $data, int $line): int
/**
* For a function we have never seen before, copy all data over and simply init the 'hit' array.
*
* @param FunctionCoverageDataType|XdebugFunctionCoverageType $functionData
* @param ProcessedFunctionCoverageData|XdebugFunctionCoverageType $functionData
*/
private function initPreviouslyUnseenFunction(string $file, string $functionName, array $functionData): void
private function initPreviouslyUnseenFunction(string $file, string $functionName, array|ProcessedFunctionCoverageData $functionData): void
{
$this->functionCoverage[$file][$functionName] = $functionData;

foreach (array_keys($functionData['branches']) as $branchId) {
$this->functionCoverage[$file][$functionName]['branches'][$branchId]['hit'] = [];
if (is_array($functionData)) {
$functionData = ProcessedFunctionCoverageData::fromXdebugCoverage($functionData);
}

foreach (array_keys($functionData['paths']) as $pathId) {
$this->functionCoverage[$file][$functionName]['paths'][$pathId]['hit'] = [];
}
$this->functionCoverage[$file][$functionName] = $functionData;
}

/**
* 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.
*
* @param FunctionCoverageDataType|XdebugFunctionCoverageType $functionData
* @param ProcessedFunctionCoverageData|XdebugFunctionCoverageType $functionData
*/
private function initPreviouslySeenFunction(string $file, string $functionName, array $functionData): void
private function initPreviouslySeenFunction(string $file, string $functionName, array|ProcessedFunctionCoverageData $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'] = [];
}
if (is_array($functionData)) {
$functionData = ProcessedFunctionCoverageData::fromXdebugCoverage($functionData);
}

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'] = [];
}
}
$this->functionCoverage[$file][$functionName] = $this->functionCoverage[$file][$functionName]->merge(
$functionData,
);
}
}
105 changes: 105 additions & 0 deletions src/Data/ProcessedFunctionCoverageData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?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\Data;

use SebastianBergmann\CodeCoverage\Driver\XdebugDriver;

/**
* @phpstan-import-type TestIdType from ProcessedCodeCoverageData
* @phpstan-import-type XdebugFunctionCoverageType from XdebugDriver
*/
final readonly class ProcessedFunctionCoverageData
{
/**
* @param XdebugFunctionCoverageType $xdebugCoverageData
*/
public static function fromXdebugCoverage(array $xdebugCoverageData): self
{
$branches = [];

foreach ($xdebugCoverageData['branches'] as $branchId => $branch) {
$branches[$branchId] = ProcessedBranchCoverageData::fromXdebugCoverage($branch);
}
$paths = [];

foreach ($xdebugCoverageData['paths'] as $pathId => $path) {
$paths[$pathId] = ProcessedPathCoverageData::fromXdebugCoverage($path);
}

return new self(
$branches,
$paths,
);
}

public function __construct(
/** @var array<int, ProcessedBranchCoverageData> */
public array $branches,
/** @var array<int, ProcessedPathCoverageData> */
public array $paths,
) {
}

public function merge(self $data): self
{
$branches = null;

if ($data->branches !== $this->branches) {
$branches = $this->branches;

foreach ($data->branches as $branchId => $branch) {
if (!isset($branches[$branchId])) {
$branches[$branchId] = $branch;
} else {
$branches[$branchId] = $branches[$branchId]->merge($branch);
}
}
}

$paths = null;

if ($data->paths !== $this->paths) {
$paths = $this->paths;

foreach ($data->paths as $pathId => $path) {
if (!isset($paths[$pathId])) {
$paths[$pathId] = $path;
} else {
$paths[$pathId] = $paths[$pathId]->merge($path);
}
}
}

if ($branches === null && $paths === null) {
return $this;
}

return new self(
$branches ?? $this->branches,
$paths ?? $this->paths,
);
}

/**
* @param TestIdType $testCaseId
*/
public function recordBranchHit(int $branchId, string $testCaseId): void
{
$this->branches[$branchId]->recordHit($testCaseId);
}

/**
* @param TestIdType $testCaseId
*/
public function recordPathHit(int $pathId, string $testCaseId): void
{
$this->paths[$pathId]->recordHit($testCaseId);
}
}
62 changes: 62 additions & 0 deletions src/Data/ProcessedPathCoverageData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?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\Data;

use function array_merge;
use function array_unique;
use NoDiscard;
use SebastianBergmann\CodeCoverage\Driver\XdebugDriver;

/**
* @phpstan-import-type TestIdType from ProcessedCodeCoverageData
* @phpstan-import-type XdebugPathCoverageType from XdebugDriver
*/
final class ProcessedPathCoverageData
{
/**
* @param XdebugPathCoverageType $xdebugCoverageData
*/
public static function fromXdebugCoverage(array $xdebugCoverageData): self
{
return new self(
$xdebugCoverageData['path'],
[],
);
}

public function __construct(
/** @var array<int, int> */
public readonly array $path,
/** @var list<TestIdType> */
public array $hit,
) {
}

#[NoDiscard]
public function merge(self $data): self
{
if ($data->hit === []) {
return $this;
}

return new self(
$this->path,
array_unique(array_merge($this->hit, $data->hit)),
);
}

/**
* @param TestIdType $testCaseId
*/
public function recordHit(string $testCaseId): void
{
$this->hit[] = $testCaseId;
}
}
Loading