From 9d55bf08567a8ac455b0bf6aa5c65806a1f96707 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 8 Nov 2025 13:16:09 +0100 Subject: [PATCH 01/20] Represent FunctionCover in objects --- src/Data/ProcessedBranchCoverageData.php | 83 ++++++++++++++++++++++ src/Data/ProcessedCodeCoverageData.php | 58 ++++----------- src/Data/ProcessedFunctionCoverageData.php | 81 +++++++++++++++++++++ src/Data/ProcessedPathCoverageData.php | 59 +++++++++++++++ 4 files changed, 236 insertions(+), 45 deletions(-) create mode 100644 src/Data/ProcessedBranchCoverageData.php create mode 100644 src/Data/ProcessedFunctionCoverageData.php create mode 100644 src/Data/ProcessedPathCoverageData.php diff --git a/src/Data/ProcessedBranchCoverageData.php b/src/Data/ProcessedBranchCoverageData.php new file mode 100644 index 000000000..91a206b81 --- /dev/null +++ b/src/Data/ProcessedBranchCoverageData.php @@ -0,0 +1,83 @@ + + * + * 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 XdebugBranchCoverageType from XdebugDriver + */ +final readonly class ProcessedBranchCoverageData +{ + public function __construct( + public int $op_start, + public int $op_end, + public int $line_start, + public int $line_end, + /** @var list */ + public array $hit, + /** @var array */ + public array $out, + /** @var array */ + public array $out_hit, + + ) + { + } + + /** + * @param XdebugBranchCoverageType $xdebugCoverageData + */ + static public 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 merge(self $data): self + { + 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): self { + $hit = $this->hit; + $hit[] = $testCaseId; + + return new self( + $this->op_start, + $this->op_end, + $this->line_start, + $this->line_end, + $hit, + $this->out, + $this->out_hit, + ); + + } +} \ No newline at end of file diff --git a/src/Data/ProcessedCodeCoverageData.php b/src/Data/ProcessedCodeCoverageData.php index 49a103236..f896ed5ae 100644 --- a/src/Data/ProcessedCodeCoverageData.php +++ b/src/Data/ProcessedCodeCoverageData.php @@ -25,21 +25,7 @@ * @phpstan-import-type XdebugFunctionCoverageType from XdebugDriver * * @phpstan-type TestIdType string - * @phpstan-type FunctionCoverageDataType array{ - * branches: array, - * out: array, - * out_hit: array, - * }>, - * paths: array, - * hit: list, - * }> - * } + * @phpstan-type FunctionCoverageDataType ProcessedFunctionCoverageData * @phpstan-type FunctionCoverageType array> * @phpstan-type LineCoverageType array>> */ @@ -99,13 +85,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); } } } @@ -213,14 +199,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'])); - } } } } @@ -259,17 +237,13 @@ private function priorityForLine(array $data, int $line): int * * @param FunctionCoverageDataType|XdebugFunctionCoverageType $functionData */ - private function initPreviouslyUnseenFunction(string $file, string $functionName, array $functionData): void + private function initPreviouslyUnseenFunction(string $file, string $functionName, ProcessedFunctionCoverageData|array $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; } /** @@ -279,20 +253,14 @@ private function initPreviouslyUnseenFunction(string $file, string $functionName * * @param FunctionCoverageDataType|XdebugFunctionCoverageType $functionData */ - private function initPreviouslySeenFunction(string $file, string $functionName, array $functionData): void + private function initPreviouslySeenFunction(string $file, string $functionName, ProcessedFunctionCoverageData|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'] = []; - } + 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 + ); } } diff --git a/src/Data/ProcessedFunctionCoverageData.php b/src/Data/ProcessedFunctionCoverageData.php new file mode 100644 index 000000000..7af1f1438 --- /dev/null +++ b/src/Data/ProcessedFunctionCoverageData.php @@ -0,0 +1,81 @@ + + * + * 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 class ProcessedFunctionCoverageData { + public function __construct( + /** @var array */ + public array $branches, + /** @var array */ + public array $paths, + + ) {} + + /** + * @param XdebugFunctionCoverageType $xdebugCoverageData + */ + static public 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 merge(self $data): self + { + $branches = $this->branches; + foreach($data->branches as $branchId => $branch) { + if (isset($branches[$branchId])) { + continue; + } + $branches[$branchId] = $branches[$branchId]->merge($branch); + } + + $paths = $this->paths; + foreach($data->paths as $pathId => $path) { + if (isset($paths[$pathId])) { + continue; + } + $paths[$pathId] = $paths[$pathId]->merge($path); + } + + return new self( + $branches, + $paths + ); + } + + /** + * @param TestIdType $testCaseId + */ + public function recordBranchHit(int $branchId, string $testCaseId): void { + $this->branches[$branchId]->recordHit($testCaseId); + } + + public function recordPathHit(int $pathId, string $testCaseId): void { + $this->paths[$pathId]->recordHit($testCaseId); + } +} diff --git a/src/Data/ProcessedPathCoverageData.php b/src/Data/ProcessedPathCoverageData.php new file mode 100644 index 000000000..1fae81bbf --- /dev/null +++ b/src/Data/ProcessedPathCoverageData.php @@ -0,0 +1,59 @@ + + * + * 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 XdebugPathCoverageType from XdebugDriver + */ +final readonly class ProcessedPathCoverageData { + public function __construct( + /** @var array */ + public array $path, + /** @var list */ + public array $hit, + ) {} + + /** + * @param XdebugPathCoverageType $xdebugCoverageData + */ + static public function fromXdebugCoverage(array $xdebugCoverageData): self + { + return new self( + $xdebugCoverageData['path'], + [], + ); + } + + public function merge(self $data): self + { + return new self( + $this->path, + array_unique(array_merge($this->hit, $data->hit)), + ); + } + + /** + * @param TestIdType $testCaseId + */ + public function recordHit(string $testCaseId): self + { + $hit = $this->hit; + $hit[] = $testCaseId; + + return new self( + $this->path, + $hit + ); + } + +} From b5ed1f54fe2cf916c3ab021ac83de118086e35e7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 8 Nov 2025 16:23:54 +0100 Subject: [PATCH 02/20] Update ProcessedFunctionCoverageData.php --- src/Data/ProcessedFunctionCoverageData.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Data/ProcessedFunctionCoverageData.php b/src/Data/ProcessedFunctionCoverageData.php index 7af1f1438..a8a9be720 100644 --- a/src/Data/ProcessedFunctionCoverageData.php +++ b/src/Data/ProcessedFunctionCoverageData.php @@ -48,18 +48,20 @@ public function merge(self $data): self { $branches = $this->branches; foreach($data->branches as $branchId => $branch) { - if (isset($branches[$branchId])) { - continue; + if (!isset($branches[$branchId])) { + $branches[$branchId] = $branch; + } else { + $branches[$branchId] = $branches[$branchId]->merge($branch); } - $branches[$branchId] = $branches[$branchId]->merge($branch); } $paths = $this->paths; foreach($data->paths as $pathId => $path) { - if (isset($paths[$pathId])) { - continue; + if (!isset($paths[$pathId])) { + $paths[$pathId] = $path; + } else { + $paths[$pathId] = $paths[$pathId]->merge($path); } - $paths[$pathId] = $paths[$pathId]->merge($path); } return new self( From 796232d5f9fdca2c253ac02a876867e95163a371 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 8 Nov 2025 17:30:48 +0100 Subject: [PATCH 03/20] simplify doctypes --- src/Data/ProcessedCodeCoverageData.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Data/ProcessedCodeCoverageData.php b/src/Data/ProcessedCodeCoverageData.php index f896ed5ae..929e9c0a6 100644 --- a/src/Data/ProcessedCodeCoverageData.php +++ b/src/Data/ProcessedCodeCoverageData.php @@ -25,8 +25,7 @@ * @phpstan-import-type XdebugFunctionCoverageType from XdebugDriver * * @phpstan-type TestIdType string - * @phpstan-type FunctionCoverageDataType ProcessedFunctionCoverageData - * @phpstan-type FunctionCoverageType array> + * @phpstan-type FunctionCoverageType array> * @phpstan-type LineCoverageType array>> */ final class ProcessedCodeCoverageData @@ -235,7 +234,7 @@ 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, ProcessedFunctionCoverageData|array $functionData): void { @@ -251,7 +250,7 @@ private function initPreviouslyUnseenFunction(string $file, string $functionName * 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, ProcessedFunctionCoverageData|array $functionData): void { From 466072b5feee3dec8bb64b6958da91195f5befd0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 8 Nov 2025 17:39:11 +0100 Subject: [PATCH 04/20] Update ProcessedCodeCoverageDataTest.php --- .../Data/ProcessedCodeCoverageDataTest.php | 143 +++++++++--------- 1 file changed, 68 insertions(+), 75 deletions(-) diff --git a/tests/tests/Data/ProcessedCodeCoverageDataTest.php b/tests/tests/Data/ProcessedCodeCoverageDataTest.php index 46e144eec..7e7fc7e20 100644 --- a/tests/tests/Data/ProcessedCodeCoverageDataTest.php +++ b/tests/tests/Data/ProcessedCodeCoverageDataTest.php @@ -75,29 +75,28 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void $coverage->setFunctionCoverage( [ '/some/path/SomeClass.php' => [ - 'SomeClass->firstFunction' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 14, - 'line_start' => 20, - 'line_end' => 25, - 'hit' => [], - 'out' => [ - ], - 'out_hit' => [ - ], - ], + 'SomeClass->firstFunction' => + new ProcessedFunctionCoverageData( + [ + new ProcessedBranchCoverageData( + 0, + 14, + 20, + 25, + [], + [], + [] + ) ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [], - ], - ], - ], + [], + ), + ] + ), ], ], ); @@ -106,69 +105,63 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void $newCoverage->setFunctionCoverage( [ '/some/path/SomeClass.php' => [ - 'SomeClass->firstFunction' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 14, - 'line_start' => 20, - 'line_end' => 25, - 'hit' => [], - 'out' => [ - ], - 'out_hit' => [ - ], - ], - 1 => [ - 'op_start' => 15, - 'op_end' => 16, - 'line_start' => 26, - 'line_end' => 27, - 'hit' => [], - 'out' => [ - ], - 'out_hit' => [ - ], - ], + 'SomeClass->firstFunction' => new ProcessedFunctionCoverageData( + [ + new ProcessedBranchCoverageData( + 0, + 14, + 20, + 25, + [], + [], + [] + ), + new ProcessedBranchCoverageData( + 15, + 16, + 26, + 27, + [], + [], + [] + ) ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [], - ], - 1 => [ - 'path' => [ + [] + ), + new ProcessedPathCoverageData( + [ 0 => 1, ], - 'hit' => [], - ], + [] + ), + ] + ), + 'SomeClass->secondFunction' => new ProcessedFunctionCoverageData( + [ + new ProcessedBranchCoverageData( + 0, + 24, + 30, + 35, + [], + [], + [] + ), ], - ], - 'SomeClass->secondFunction' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 24, - 'line_start' => 30, - 'line_end' => 35, - 'hit' => [], - 'out' => [ - ], - 'out_hit' => [ - ], - ], - ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [], - ], - ], - ], + [] + ), + ] + ), ], ], ); From c90d6aeb56f00abaa46cf5f9ee2ea5ff66efc481 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 08:21:33 +0100 Subject: [PATCH 05/20] cs --- src/Data/ProcessedBranchCoverageData.php | 42 ++++---- src/Data/ProcessedCodeCoverageData.php | 6 +- src/Data/ProcessedFunctionCoverageData.php | 43 ++++---- src/Data/ProcessedPathCoverageData.php | 27 +++--- .../Data/ProcessedCodeCoverageDataTest.php | 97 ++++++++++--------- 5 files changed, 113 insertions(+), 102 deletions(-) diff --git a/src/Data/ProcessedBranchCoverageData.php b/src/Data/ProcessedBranchCoverageData.php index 91a206b81..1d32d604a 100644 --- a/src/Data/ProcessedBranchCoverageData.php +++ b/src/Data/ProcessedBranchCoverageData.php @@ -9,6 +9,8 @@ */ namespace SebastianBergmann\CodeCoverage\Data; +use function array_merge; +use function array_unique; use SebastianBergmann\CodeCoverage\Driver\XdebugDriver; /** @@ -17,26 +19,10 @@ */ final readonly class ProcessedBranchCoverageData { - public function __construct( - public int $op_start, - public int $op_end, - public int $line_start, - public int $line_end, - /** @var list */ - public array $hit, - /** @var array */ - public array $out, - /** @var array */ - public array $out_hit, - - ) - { - } - /** * @param XdebugBranchCoverageType $xdebugCoverageData */ - static public function fromXdebugCoverage(array $xdebugCoverageData): self + public static function fromXdebugCoverage(array $xdebugCoverageData): self { return new self( $xdebugCoverageData['op_start'], @@ -49,6 +35,20 @@ static public function fromXdebugCoverage(array $xdebugCoverageData): self ); } + public function __construct( + public int $op_start, + public int $op_end, + public int $line_start, + public int $line_end, + /** @var list */ + public array $hit, + /** @var array */ + public array $out, + /** @var array */ + public array $out_hit, + ) { + } + public function merge(self $data): self { return new self( @@ -65,8 +65,9 @@ public function merge(self $data): self /** * @param TestIdType $testCaseId */ - public function recordHit(string $testCaseId): self { - $hit = $this->hit; + public function recordHit(string $testCaseId): self + { + $hit = $this->hit; $hit[] = $testCaseId; return new self( @@ -78,6 +79,5 @@ public function recordHit(string $testCaseId): self { $this->out, $this->out_hit, ); - } -} \ No newline at end of file +} diff --git a/src/Data/ProcessedCodeCoverageData.php b/src/Data/ProcessedCodeCoverageData.php index 929e9c0a6..5745ccb08 100644 --- a/src/Data/ProcessedCodeCoverageData.php +++ b/src/Data/ProcessedCodeCoverageData.php @@ -236,7 +236,7 @@ private function priorityForLine(array $data, int $line): int * * @param ProcessedFunctionCoverageData|XdebugFunctionCoverageType $functionData */ - private function initPreviouslyUnseenFunction(string $file, string $functionName, ProcessedFunctionCoverageData|array $functionData): void + private function initPreviouslyUnseenFunction(string $file, string $functionName, array|ProcessedFunctionCoverageData $functionData): void { if (is_array($functionData)) { $functionData = ProcessedFunctionCoverageData::fromXdebugCoverage($functionData); @@ -252,14 +252,14 @@ private function initPreviouslyUnseenFunction(string $file, string $functionName * * @param ProcessedFunctionCoverageData|XdebugFunctionCoverageType $functionData */ - private function initPreviouslySeenFunction(string $file, string $functionName, ProcessedFunctionCoverageData|array $functionData): void + private function initPreviouslySeenFunction(string $file, string $functionName, array|ProcessedFunctionCoverageData $functionData): void { if (is_array($functionData)) { $functionData = ProcessedFunctionCoverageData::fromXdebugCoverage($functionData); } $this->functionCoverage[$file][$functionName] = $this->functionCoverage[$file][$functionName]->merge( - $functionData + $functionData, ); } } diff --git a/src/Data/ProcessedFunctionCoverageData.php b/src/Data/ProcessedFunctionCoverageData.php index a8a9be720..9956c066e 100644 --- a/src/Data/ProcessedFunctionCoverageData.php +++ b/src/Data/ProcessedFunctionCoverageData.php @@ -15,39 +15,43 @@ * @phpstan-import-type TestIdType from ProcessedCodeCoverageData * @phpstan-import-type XdebugFunctionCoverageType from XdebugDriver */ -final class ProcessedFunctionCoverageData { - public function __construct( - /** @var array */ - public array $branches, - /** @var array */ - public array $paths, - - ) {} - +final class ProcessedFunctionCoverageData +{ /** * @param XdebugFunctionCoverageType $xdebugCoverageData */ - static public function fromXdebugCoverage(array $xdebugCoverageData): self + public static function fromXdebugCoverage(array $xdebugCoverageData): self { $branches = []; - foreach($xdebugCoverageData['branches'] as $branchId => $branch) { + + foreach ($xdebugCoverageData['branches'] as $branchId => $branch) { $branches[$branchId] = ProcessedBranchCoverageData::fromXdebugCoverage($branch); } $paths = []; - foreach($xdebugCoverageData['paths'] as $pathId => $path) { + + foreach ($xdebugCoverageData['paths'] as $pathId => $path) { $paths[$pathId] = ProcessedPathCoverageData::fromXdebugCoverage($path); } return new self( $branches, - $paths + $paths, ); } + public function __construct( + /** @var array */ + public array $branches, + /** @var array */ + public array $paths, + ) { + } + public function merge(self $data): self { $branches = $this->branches; - foreach($data->branches as $branchId => $branch) { + + foreach ($data->branches as $branchId => $branch) { if (!isset($branches[$branchId])) { $branches[$branchId] = $branch; } else { @@ -56,7 +60,8 @@ public function merge(self $data): self } $paths = $this->paths; - foreach($data->paths as $pathId => $path) { + + foreach ($data->paths as $pathId => $path) { if (!isset($paths[$pathId])) { $paths[$pathId] = $path; } else { @@ -66,18 +71,20 @@ public function merge(self $data): self return new self( $branches, - $paths + $paths, ); } /** * @param TestIdType $testCaseId */ - public function recordBranchHit(int $branchId, string $testCaseId): void { + public function recordBranchHit(int $branchId, string $testCaseId): void + { $this->branches[$branchId]->recordHit($testCaseId); } - public function recordPathHit(int $pathId, string $testCaseId): void { + public function recordPathHit(int $pathId, string $testCaseId): void + { $this->paths[$pathId]->recordHit($testCaseId); } } diff --git a/src/Data/ProcessedPathCoverageData.php b/src/Data/ProcessedPathCoverageData.php index 1fae81bbf..6c18fd403 100644 --- a/src/Data/ProcessedPathCoverageData.php +++ b/src/Data/ProcessedPathCoverageData.php @@ -9,24 +9,20 @@ */ namespace SebastianBergmann\CodeCoverage\Data; +use function array_merge; +use function array_unique; use SebastianBergmann\CodeCoverage\Driver\XdebugDriver; /** * @phpstan-import-type TestIdType from ProcessedCodeCoverageData * @phpstan-import-type XdebugPathCoverageType from XdebugDriver */ -final readonly class ProcessedPathCoverageData { - public function __construct( - /** @var array */ - public array $path, - /** @var list */ - public array $hit, - ) {} - +final readonly class ProcessedPathCoverageData +{ /** * @param XdebugPathCoverageType $xdebugCoverageData */ - static public function fromXdebugCoverage(array $xdebugCoverageData): self + public static function fromXdebugCoverage(array $xdebugCoverageData): self { return new self( $xdebugCoverageData['path'], @@ -34,6 +30,14 @@ static public function fromXdebugCoverage(array $xdebugCoverageData): self ); } + public function __construct( + /** @var array */ + public array $path, + /** @var list */ + public array $hit, + ) { + } + public function merge(self $data): self { return new self( @@ -47,13 +51,12 @@ public function merge(self $data): self */ public function recordHit(string $testCaseId): self { - $hit = $this->hit; + $hit = $this->hit; $hit[] = $testCaseId; return new self( $this->path, - $hit + $hit, ); } - } diff --git a/tests/tests/Data/ProcessedCodeCoverageDataTest.php b/tests/tests/Data/ProcessedCodeCoverageDataTest.php index 7e7fc7e20..9d19eacb1 100644 --- a/tests/tests/Data/ProcessedCodeCoverageDataTest.php +++ b/tests/tests/Data/ProcessedCodeCoverageDataTest.php @@ -75,28 +75,29 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void $coverage->setFunctionCoverage( [ '/some/path/SomeClass.php' => [ - 'SomeClass->firstFunction' => - new ProcessedFunctionCoverageData( - [ - new ProcessedBranchCoverageData( - 0, - 14, - 20, - 25, - [], - [], - [] - ) - ], - [ - new ProcessedPathCoverageData( - [ - 0 => 0, - ], - [], - ), - ] - ), + 'SomeClass->firstFunction' => [ + new ProcessedFunctionCoverageData( + [ + new ProcessedBranchCoverageData( + 0, + 14, + 20, + 25, + [], + [], + [], + ), + ], + [ + new ProcessedPathCoverageData( + [ + 0 => 0, + ], + [], + ), + ], + ), + ], ], ], ); @@ -105,7 +106,7 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void $newCoverage->setFunctionCoverage( [ '/some/path/SomeClass.php' => [ - 'SomeClass->firstFunction' => new ProcessedFunctionCoverageData( + 'SomeClass->firstFunction' => [new ProcessedFunctionCoverageData( [ new ProcessedBranchCoverageData( 0, @@ -114,7 +115,7 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void 25, [], [], - [] + [], ), new ProcessedBranchCoverageData( 15, @@ -123,45 +124,45 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void 27, [], [], - [] - ) + [], + ), ], [ new ProcessedPathCoverageData( [ 0 => 0, ], - [] + [], ), new ProcessedPathCoverageData( [ 0 => 1, ], - [] - ), - ] - ), - 'SomeClass->secondFunction' => new ProcessedFunctionCoverageData( - [ - new ProcessedBranchCoverageData( - 0, - 24, - 30, - 35, - [], [], - [] ), ], - [ - new ProcessedPathCoverageData( - [ - 0 => 0, - ], - [] - ), - ] ), + 'SomeClass->secondFunction' => new ProcessedFunctionCoverageData( + [ + new ProcessedBranchCoverageData( + 0, + 24, + 30, + 35, + [], + [], + [], + ), + ], + [ + new ProcessedPathCoverageData( + [ + 0 => 0, + ], + [], + ), + ], + )], ], ], ); From 062c1d408d1afeef89e72dda1b4e8d1c6d931144 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 08:27:47 +0100 Subject: [PATCH 06/20] cs --- .../Data/ProcessedCodeCoverageDataTest.php | 88 +++++++++---------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/tests/tests/Data/ProcessedCodeCoverageDataTest.php b/tests/tests/Data/ProcessedCodeCoverageDataTest.php index 9d19eacb1..ef8c75e86 100644 --- a/tests/tests/Data/ProcessedCodeCoverageDataTest.php +++ b/tests/tests/Data/ProcessedCodeCoverageDataTest.php @@ -75,29 +75,27 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void $coverage->setFunctionCoverage( [ '/some/path/SomeClass.php' => [ - 'SomeClass->firstFunction' => [ - new ProcessedFunctionCoverageData( - [ - new ProcessedBranchCoverageData( - 0, - 14, - 20, - 25, - [], - [], - [], - ), - ], - [ - new ProcessedPathCoverageData( - [ - 0 => 0, - ], - [], - ), - ], - ), - ], + 'SomeClass->firstFunction' => new ProcessedFunctionCoverageData( + [ + new ProcessedBranchCoverageData( + 0, + 14, + 20, + 25, + [], + [], + [], + ), + ], + [ + new ProcessedPathCoverageData( + [ + 0 => 0, + ], + [], + ), + ], + ), ], ], ); @@ -106,7 +104,7 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void $newCoverage->setFunctionCoverage( [ '/some/path/SomeClass.php' => [ - 'SomeClass->firstFunction' => [new ProcessedFunctionCoverageData( + 'SomeClass->firstFunction' => new ProcessedFunctionCoverageData( [ new ProcessedBranchCoverageData( 0, @@ -142,27 +140,27 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void ), ], ), - 'SomeClass->secondFunction' => new ProcessedFunctionCoverageData( - [ - new ProcessedBranchCoverageData( - 0, - 24, - 30, - 35, - [], - [], - [], - ), - ], - [ - new ProcessedPathCoverageData( - [ - 0 => 0, - ], - [], - ), - ], - )], + 'SomeClass->secondFunction' => new ProcessedFunctionCoverageData( + [ + new ProcessedBranchCoverageData( + 0, + 24, + 30, + 35, + [], + [], + [], + ), + ], + [ + new ProcessedPathCoverageData( + [ + 0 => 0, + ], + [], + ), + ], + ), ], ], ); From 0e0fe598f8234f7563c1a93512ed38b2965c0e0d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 08:39:32 +0100 Subject: [PATCH 07/20] fix --- src/Node/File.php | 22 ++++++++++--------- .../Data/ProcessedCodeCoverageDataTest.php | 1 + 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Node/File.php b/src/Node/File.php index 54ee70b4a..5ba131357 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -9,6 +9,8 @@ */ namespace SebastianBergmann\CodeCoverage\Node; +use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; use function array_filter; use function count; use function range; @@ -664,33 +666,33 @@ private function newMethod(string $className, Method $method, string $link): arr $key = $className . '->' . $method->name(); - if (isset($this->functionCoverageData[$key]['branches'])) { + if (isset($this->functionCoverageData[$key])) { $methodData['executableBranches'] = count( - $this->functionCoverageData[$key]['branches'], + $this->functionCoverageData[$key]->branches, ); $methodData['executedBranches'] = count( array_filter( - $this->functionCoverageData[$key]['branches'], - static function (array $branch) + $this->functionCoverageData[$key]->branches, + static function (ProcessedBranchCoverageData $branch) { - return (bool) $branch['hit']; + return (bool) $branch->hit; }, ), ); } - if (isset($this->functionCoverageData[$key]['paths'])) { + if (isset($this->functionCoverageData[$key])) { $methodData['executablePaths'] = count( - $this->functionCoverageData[$key]['paths'], + $this->functionCoverageData[$key]->paths, ); $methodData['executedPaths'] = count( array_filter( - $this->functionCoverageData[$key]['paths'], - static function (array $path) + $this->functionCoverageData[$key]->paths, + static function (ProcessedPathCoverageData $path) { - return (bool) $path['hit']; + return (bool) $path->hit; }, ), ); diff --git a/tests/tests/Data/ProcessedCodeCoverageDataTest.php b/tests/tests/Data/ProcessedCodeCoverageDataTest.php index ef8c75e86..1e380e615 100644 --- a/tests/tests/Data/ProcessedCodeCoverageDataTest.php +++ b/tests/tests/Data/ProcessedCodeCoverageDataTest.php @@ -167,6 +167,7 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void $coverage->merge($newCoverage); + $this->assertIsArray($newCoverage->functionCoverage()['/some/path/SomeClass.php']); $this->assertArrayHasKey('SomeClass->secondFunction', $newCoverage->functionCoverage()['/some/path/SomeClass.php']); } } From 416967d0f8ce33a2fb16449070b5290401c3a47d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 08:41:41 +0100 Subject: [PATCH 08/20] Update ProcessedCodeCoverageDataTest.php --- .../tests/Data/ProcessedCodeCoverageDataTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/tests/Data/ProcessedCodeCoverageDataTest.php b/tests/tests/Data/ProcessedCodeCoverageDataTest.php index 1e380e615..e903636b8 100644 --- a/tests/tests/Data/ProcessedCodeCoverageDataTest.php +++ b/tests/tests/Data/ProcessedCodeCoverageDataTest.php @@ -77,7 +77,7 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void '/some/path/SomeClass.php' => [ 'SomeClass->firstFunction' => new ProcessedFunctionCoverageData( [ - new ProcessedBranchCoverageData( + 0 => new ProcessedBranchCoverageData( 0, 14, 20, @@ -88,7 +88,7 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void ), ], [ - new ProcessedPathCoverageData( + 0 => new ProcessedPathCoverageData( [ 0 => 0, ], @@ -106,7 +106,7 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void '/some/path/SomeClass.php' => [ 'SomeClass->firstFunction' => new ProcessedFunctionCoverageData( [ - new ProcessedBranchCoverageData( + 0 => new ProcessedBranchCoverageData( 0, 14, 20, @@ -115,7 +115,7 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void [], [], ), - new ProcessedBranchCoverageData( + 1 => new ProcessedBranchCoverageData( 15, 16, 26, @@ -126,13 +126,13 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void ), ], [ - new ProcessedPathCoverageData( + 0 => new ProcessedPathCoverageData( [ 0 => 0, ], [], ), - new ProcessedPathCoverageData( + 1 => new ProcessedPathCoverageData( [ 0 => 1, ], @@ -142,7 +142,7 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void ), 'SomeClass->secondFunction' => new ProcessedFunctionCoverageData( [ - new ProcessedBranchCoverageData( + 0 => new ProcessedBranchCoverageData( 0, 24, 30, @@ -153,7 +153,7 @@ public function testMergeDoesNotCrashWhenFileContentsHaveChanged(): void ), ], [ - new ProcessedPathCoverageData( + 0 => new ProcessedPathCoverageData( [ 0 => 0, ], From a3216ce354b4a9ba376501c116d21c3390ba91c0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 16:26:18 +0100 Subject: [PATCH 09/20] fix --- src/Data/ProcessedBranchCoverageData.php | 30 ++++++++-------------- src/Data/ProcessedFunctionCoverageData.php | 2 +- src/Data/ProcessedPathCoverageData.php | 16 +++++------- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/src/Data/ProcessedBranchCoverageData.php b/src/Data/ProcessedBranchCoverageData.php index 1d32d604a..dfaa8cea4 100644 --- a/src/Data/ProcessedBranchCoverageData.php +++ b/src/Data/ProcessedBranchCoverageData.php @@ -17,7 +17,7 @@ * @phpstan-import-type TestIdType from ProcessedCodeCoverageData * @phpstan-import-type XdebugBranchCoverageType from XdebugDriver */ -final readonly class ProcessedBranchCoverageData +final class ProcessedBranchCoverageData { /** * @param XdebugBranchCoverageType $xdebugCoverageData @@ -36,19 +36,20 @@ public static function fromXdebugCoverage(array $xdebugCoverageData): self } public function __construct( - public int $op_start, - public int $op_end, - public int $line_start, - public int $line_end, + readonly public int $op_start, + readonly public int $op_end, + readonly public int $line_start, + readonly public int $line_end, /** @var list */ public array $hit, /** @var array */ - public array $out, + readonly public array $out, /** @var array */ - public array $out_hit, + readonly public array $out_hit, ) { } + #[\NoDiscard] public function merge(self $data): self { return new self( @@ -65,19 +66,8 @@ public function merge(self $data): self /** * @param TestIdType $testCaseId */ - public function recordHit(string $testCaseId): self + public function recordHit(string $testCaseId): void { - $hit = $this->hit; - $hit[] = $testCaseId; - - return new self( - $this->op_start, - $this->op_end, - $this->line_start, - $this->line_end, - $hit, - $this->out, - $this->out_hit, - ); + $this->hit[] = $testCaseId; } } diff --git a/src/Data/ProcessedFunctionCoverageData.php b/src/Data/ProcessedFunctionCoverageData.php index 9956c066e..52b63427b 100644 --- a/src/Data/ProcessedFunctionCoverageData.php +++ b/src/Data/ProcessedFunctionCoverageData.php @@ -15,7 +15,7 @@ * @phpstan-import-type TestIdType from ProcessedCodeCoverageData * @phpstan-import-type XdebugFunctionCoverageType from XdebugDriver */ -final class ProcessedFunctionCoverageData +final readonly class ProcessedFunctionCoverageData { /** * @param XdebugFunctionCoverageType $xdebugCoverageData diff --git a/src/Data/ProcessedPathCoverageData.php b/src/Data/ProcessedPathCoverageData.php index 6c18fd403..aabc71e82 100644 --- a/src/Data/ProcessedPathCoverageData.php +++ b/src/Data/ProcessedPathCoverageData.php @@ -9,6 +9,7 @@ */ namespace SebastianBergmann\CodeCoverage\Data; +use NoDiscard; use function array_merge; use function array_unique; use SebastianBergmann\CodeCoverage\Driver\XdebugDriver; @@ -17,7 +18,7 @@ * @phpstan-import-type TestIdType from ProcessedCodeCoverageData * @phpstan-import-type XdebugPathCoverageType from XdebugDriver */ -final readonly class ProcessedPathCoverageData +final class ProcessedPathCoverageData { /** * @param XdebugPathCoverageType $xdebugCoverageData @@ -32,12 +33,13 @@ public static function fromXdebugCoverage(array $xdebugCoverageData): self public function __construct( /** @var array */ - public array $path, + public readonly array $path, /** @var list */ public array $hit, ) { } + #[\NoDiscard] public function merge(self $data): self { return new self( @@ -49,14 +51,8 @@ public function merge(self $data): self /** * @param TestIdType $testCaseId */ - public function recordHit(string $testCaseId): self + public function recordHit(string $testCaseId): void { - $hit = $this->hit; - $hit[] = $testCaseId; - - return new self( - $this->path, - $hit, - ); + $this->hit[] = $testCaseId; } } From 1720a5d57a458aa1daad5b6b8a6d9b96e33d2b4e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 16:30:04 +0100 Subject: [PATCH 10/20] Update File.php --- src/Node/File.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Node/File.php b/src/Node/File.php index 5ba131357..bf5cc12f3 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -602,33 +602,33 @@ private function processFunctions(array $functions): void $this->codeUnitsByLine[$lineNumber] = [&$this->functions[$functionName]]; } - if (isset($this->functionCoverageData[$functionName]['branches'])) { + if (isset($this->functionCoverageData[$functionName])) { $this->functions[$functionName]['executableBranches'] = count( - $this->functionCoverageData[$functionName]['branches'], + $this->functionCoverageData[$functionName]->branches, ); $this->functions[$functionName]['executedBranches'] = count( array_filter( - $this->functionCoverageData[$functionName]['branches'], - static function (array $branch) + $this->functionCoverageData[$functionName]->branches, + static function (ProcessedBranchCoverageData $branch) { - return (bool) $branch['hit']; + return (bool) $branch->hit; }, ), ); } - if (isset($this->functionCoverageData[$functionName]['paths'])) { + if (isset($this->functionCoverageData[$functionName])) { $this->functions[$functionName]['executablePaths'] = count( - $this->functionCoverageData[$functionName]['paths'], + $this->functionCoverageData[$functionName]->paths, ); $this->functions[$functionName]['executedPaths'] = count( array_filter( - $this->functionCoverageData[$functionName]['paths'], - static function (array $path) + $this->functionCoverageData[$functionName]->paths, + static function (ProcessedPathCoverageData $path) { - return (bool) $path['hit']; + return (bool) $path->hit; }, ), ); From 1f8d78f8e7458722d5d011f95764b90992525ef5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 17:05:39 +0100 Subject: [PATCH 11/20] optimize --- src/Data/ProcessedBranchCoverageData.php | 4 ++++ src/Data/ProcessedPathCoverageData.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Data/ProcessedBranchCoverageData.php b/src/Data/ProcessedBranchCoverageData.php index dfaa8cea4..043460c89 100644 --- a/src/Data/ProcessedBranchCoverageData.php +++ b/src/Data/ProcessedBranchCoverageData.php @@ -52,6 +52,10 @@ public function __construct( #[\NoDiscard] public function merge(self $data): self { + if ($data->hit === []) { + return $this; + } + return new self( $this->op_start, $this->op_end, diff --git a/src/Data/ProcessedPathCoverageData.php b/src/Data/ProcessedPathCoverageData.php index aabc71e82..d40bc4723 100644 --- a/src/Data/ProcessedPathCoverageData.php +++ b/src/Data/ProcessedPathCoverageData.php @@ -42,6 +42,10 @@ public function __construct( #[\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)), From 21857262143ac3dd4ec647b872da0ab8ef40d97e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 17:10:17 +0100 Subject: [PATCH 12/20] Update ProcessedPathCoverageData.php --- src/Data/ProcessedPathCoverageData.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/ProcessedPathCoverageData.php b/src/Data/ProcessedPathCoverageData.php index d40bc4723..4314e6a85 100644 --- a/src/Data/ProcessedPathCoverageData.php +++ b/src/Data/ProcessedPathCoverageData.php @@ -45,7 +45,7 @@ public function merge(self $data): self if ($data->hit === []) { return $this; } - + return new self( $this->path, array_unique(array_merge($this->hit, $data->hit)), From 8122464107e8c3dfff427db7a3d01211e16f1fc1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 17:22:03 +0100 Subject: [PATCH 13/20] Update ProcessedFunctionCoverageData.php --- src/Data/ProcessedFunctionCoverageData.php | 39 ++++++++++++++-------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/Data/ProcessedFunctionCoverageData.php b/src/Data/ProcessedFunctionCoverageData.php index 52b63427b..03414d9e6 100644 --- a/src/Data/ProcessedFunctionCoverageData.php +++ b/src/Data/ProcessedFunctionCoverageData.php @@ -49,26 +49,34 @@ public function __construct( public function merge(self $data): self { - $branches = $this->branches; - - foreach ($data->branches as $branchId => $branch) { - if (!isset($branches[$branchId])) { - $branches[$branchId] = $branch; - } else { - $branches[$branchId] = $branches[$branchId]->merge($branch); + $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 = $this->paths; - - foreach ($data->paths as $pathId => $path) { - if (!isset($paths[$pathId])) { - $paths[$pathId] = $path; - } else { - $paths[$pathId] = $paths[$pathId]->merge($path); + $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, $paths, @@ -83,6 +91,9 @@ 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); From 1b73b3ca0d35a186ae127b374381fbd3089690f3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 17:22:22 +0100 Subject: [PATCH 14/20] cs --- src/Data/ProcessedBranchCoverageData.php | 15 ++++++++------- src/Data/ProcessedFunctionCoverageData.php | 4 ++++ src/Data/ProcessedPathCoverageData.php | 4 ++-- src/Node/File.php | 4 ++-- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Data/ProcessedBranchCoverageData.php b/src/Data/ProcessedBranchCoverageData.php index 043460c89..2b6076160 100644 --- a/src/Data/ProcessedBranchCoverageData.php +++ b/src/Data/ProcessedBranchCoverageData.php @@ -11,6 +11,7 @@ use function array_merge; use function array_unique; +use NoDiscard; use SebastianBergmann\CodeCoverage\Driver\XdebugDriver; /** @@ -36,20 +37,20 @@ public static function fromXdebugCoverage(array $xdebugCoverageData): self } public function __construct( - readonly public int $op_start, - readonly public int $op_end, - readonly public int $line_start, - readonly public int $line_end, + public readonly int $op_start, + public readonly int $op_end, + public readonly int $line_start, + public readonly int $line_end, /** @var list */ public array $hit, /** @var array */ - readonly public array $out, + public readonly array $out, /** @var array */ - readonly public array $out_hit, + public readonly array $out_hit, ) { } - #[\NoDiscard] + #[NoDiscard] public function merge(self $data): self { if ($data->hit === []) { diff --git a/src/Data/ProcessedFunctionCoverageData.php b/src/Data/ProcessedFunctionCoverageData.php index 03414d9e6..39dcd9205 100644 --- a/src/Data/ProcessedFunctionCoverageData.php +++ b/src/Data/ProcessedFunctionCoverageData.php @@ -50,8 +50,10 @@ public function __construct( 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; @@ -62,8 +64,10 @@ public function merge(self $data): self } $paths = null; + if ($data->paths !== $this->paths) { $paths = $this->paths; + foreach ($data->paths as $pathId => $path) { if (!isset($paths[$pathId])) { $paths[$pathId] = $path; diff --git a/src/Data/ProcessedPathCoverageData.php b/src/Data/ProcessedPathCoverageData.php index 4314e6a85..ccb651931 100644 --- a/src/Data/ProcessedPathCoverageData.php +++ b/src/Data/ProcessedPathCoverageData.php @@ -9,9 +9,9 @@ */ namespace SebastianBergmann\CodeCoverage\Data; -use NoDiscard; use function array_merge; use function array_unique; +use NoDiscard; use SebastianBergmann\CodeCoverage\Driver\XdebugDriver; /** @@ -39,7 +39,7 @@ public function __construct( ) { } - #[\NoDiscard] + #[NoDiscard] public function merge(self $data): self { if ($data->hit === []) { diff --git a/src/Node/File.php b/src/Node/File.php index bf5cc12f3..18f97f808 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -9,12 +9,12 @@ */ namespace SebastianBergmann\CodeCoverage\Node; -use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; -use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; use function array_filter; use function count; use function range; use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; use SebastianBergmann\CodeCoverage\StaticAnalysis\AnalysisResult; use SebastianBergmann\CodeCoverage\StaticAnalysis\Class_; use SebastianBergmann\CodeCoverage\StaticAnalysis\Function_; From f29af673347500b73c7314219b898864f626b73f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 17:56:50 +0100 Subject: [PATCH 15/20] fix --- src/Report/Html/Renderer/File.php | 49 +++++++++++++++++-------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/Report/Html/Renderer/File.php b/src/Report/Html/Renderer/File.php index 09dbe31fe..b154f7d70 100644 --- a/src/Report/Html/Renderer/File.php +++ b/src/Report/Html/Renderer/File.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Html; +use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; use const ENT_COMPAT; use const ENT_HTML401; use const ENT_SUBSTITUTE; @@ -607,18 +610,20 @@ private function renderSourceWithBranchCoverage(FileNode $node): string ]; } + /** @var ProcessedFunctionCoverageData $method */ foreach ($functionCoverageData as $method) { - foreach ($method['branches'] as $branch) { - foreach (range($branch['line_start'], $branch['line_end']) as $line) { + /** @var ProcessedBranchCoverageData $branch */ + foreach ($method->branches as $branch) { + foreach (range($branch->line_start, $branch->line_end) as $line) { if (!isset($lineData[$line])) { // blank line at end of file is sometimes included here continue; } $lineData[$line]['includedInBranches']++; - if ($branch['hit']) { + if ($branch->hit !== []) { $lineData[$line]['includedInHitBranches']++; - $lineData[$line]['tests'] = array_unique(array_merge($lineData[$line]['tests'], $branch['hit'])); + $lineData[$line]['tests'] = array_unique(array_merge($lineData[$line]['tests'], $branch->hit)); } } } @@ -693,18 +698,20 @@ private function renderSourceWithPathCoverage(FileNode $node): string ]; } + /** @var ProcessedFunctionCoverageData $method */ foreach ($functionCoverageData as $method) { - foreach ($method['paths'] as $pathId => $path) { - foreach ($path['path'] as $branchTaken) { - foreach (range($method['branches'][$branchTaken]['line_start'], $method['branches'][$branchTaken]['line_end']) as $line) { + /** @var ProcessedPathCoverageData $path */ + foreach ($method->paths as $pathId => $path) { + foreach ($path->path as $branchTaken) { + foreach (range($method->branches[$branchTaken]->line_start, $method->branches[$branchTaken]->line_end) as $line) { if (!isset($lineData[$line])) { continue; } $lineData[$line]['includedInPaths'][] = $pathId; - if ($path['hit']) { + if ($path->hit !== []) { $lineData[$line]['includedInHitPaths'][] = $pathId; - $lineData[$line]['tests'] = array_unique(array_merge($lineData[$line]['tests'], $path['hit'])); + $lineData[$line]['tests'] = array_unique(array_merge($lineData[$line]['tests'], $path->hit)); } } } @@ -877,21 +884,18 @@ private function renderPathStructure(FileNode $node): string ksort($coverageData); + /** @var ProcessedFunctionCoverageData $methodData */ foreach ($coverageData as $methodName => $methodData) { - if (!$methodData['paths']) { - continue; - } - $pathStructure = ''; - if (count($methodData['paths']) > 100) { - $pathStructure .= '

' . count($methodData['paths']) . ' is too many paths to sensibly render, consider refactoring your code to bring this number down.

'; + if (count($methodData->paths) > 100) { + $pathStructure .= '

' . count($methodData->paths) . ' is too many paths to sensibly render, consider refactoring your code to bring this number down.

'; continue; } - foreach ($methodData['paths'] as $path) { - $pathStructure .= $this->renderPathLines($path, $methodData['branches'], $codeLines, $testData); + foreach ($methodData->paths as $path) { + $pathStructure .= $this->renderPathLines($path, $methodData->branches, $codeLines, $testData); } if ($pathStructure !== '') { @@ -906,9 +910,10 @@ private function renderPathStructure(FileNode $node): string } /** + * @param array $branches * @param list $codeLines */ - private function renderPathLines(array $path, array $branches, array $codeLines, array $testData): string + private function renderPathLines(ProcessedPathCoverageData $path, array $branches, array $codeLines, array $testData): string { $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); @@ -916,14 +921,14 @@ private function renderPathLines(array $path, array $branches, array $codeLines, $lines = ''; $first = true; - foreach ($path['path'] as $branchId) { + foreach ($path->path as $branchId) { if ($first) { $first = false; } else { $lines .= '  ' . "\n"; } - $branchLines = range($branches[$branchId]['line_start'], $branches[$branchId]['line_end']); + $branchLines = range($branches[$branchId]->line_start, $branches[$branchId]->line_end); sort($branchLines); // sometimes end_line < start_line /** @var int $line */ @@ -935,7 +940,7 @@ private function renderPathLines(array $path, array $branches, array $codeLines, $popoverContent = ''; $popoverTitle = ''; - $numTests = count($path['hit']); + $numTests = count($path->hit); if ($numTests === 0) { $trClass = 'danger'; @@ -949,7 +954,7 @@ private function renderPathLines(array $path, array $branches, array $codeLines, $popoverTitle = '1 test covers this path'; } - foreach ($path['hit'] as $test) { + foreach ($path->hit as $test) { if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { $lineCss = 'covered-by-medium-tests'; } elseif ($testData[$test]['size'] === 'small') { From 3b17cf583e35bb14403ab2be85a70524dde2cca7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 18:10:49 +0100 Subject: [PATCH 16/20] Update TestCase.php --- tests/src/TestCase.php | 247 +++++++++++++++++++++-------------------- 1 file changed, 124 insertions(+), 123 deletions(-) diff --git a/tests/src/TestCase.php b/tests/src/TestCase.php index ba87455db..af2ed2766 100644 --- a/tests/src/TestCase.php +++ b/tests/src/TestCase.php @@ -9,6 +9,9 @@ */ namespace SebastianBergmann\CodeCoverage; +use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; use function rmdir; use function unlink; use BankAccount; @@ -1560,201 +1563,199 @@ protected function getExpectedPathCoverageDataArrayForBankAccount(): array { return [ TEST_FILES_PATH . 'BankAccount.php' => [ - 'BankAccount->depositMoney' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 14, - 'line_start' => 20, - 'line_end' => 25, - 'hit' => [ + 'BankAccount->depositMoney' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 14, + 20, + 25, + [ 0 => 'BankAccountTest::testBalanceCannotBecomeNegative2', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - 'out' => [ - ], - 'out_hit' => [ - ], - ], + [], + [], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [ + [ 0 => 'BankAccountTest::testBalanceCannotBecomeNegative2', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - ], + ), ], - ], - 'BankAccount->getBalance' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 5, - 'line_start' => 6, - 'line_end' => 9, - 'hit' => [ + ), + 'BankAccount->getBalance' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 5, + 6, + 9, + [ 0 => 'BankAccountTest::testBalanceIsInitiallyZero', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - 'out' => [ + [ ], - 'out_hit' => [ + [ ], - ], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [ + [ 0 => 'BankAccountTest::testBalanceIsInitiallyZero', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - ], + ), ], - ], - 'BankAccount->withdrawMoney' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 14, - 'line_start' => 27, - 'line_end' => 32, - 'hit' => [ + ), + 'BankAccount->withdrawMoney' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 14, + 27, + 32, + [ 0 => 'BankAccountTest::testBalanceCannotBecomeNegative', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - 'out' => [ + [ ], - 'out_hit' => [ + [ ], - ], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [ + [ 0 => 'BankAccountTest::testBalanceCannotBecomeNegative', 1 => 'BankAccountTest::testDepositWithdrawMoney', ], - ], + ), ], - ], - '{main}' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 1, - 'line_start' => 34, - 'line_end' => 34, - 'hit' => [ - ], - 'out' => [ + ), + '{main}' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 1, + 34, + 34, + [ + ], + [ 0 => 2147483645, ], - 'out_hit' => [ + [ 0 => 0, ], - ], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, ], - 'hit' => [ + [ ], - ], + ), ], - ], - 'BankAccount->setBalance' => [ - 'branches' => [ - 0 => [ - 'op_start' => 0, - 'op_end' => 4, - 'line_start' => 11, - 'line_end' => 13, - 'hit' => [ - ], - 'out' => [ + ), + 'BankAccount->setBalance' => new ProcessedFunctionCoverageData( + [ + 0 => new ProcessedBranchCoverageData( + 0, + 4, + 11, + 13, + [ + ], + [ 0 => 5, 1 => 9, ], - 'out_hit' => [ + [ 0 => 0, 1 => 0, ], - ], - 5 => [ - 'op_start' => 5, - 'op_end' => 8, - 'line_start' => 14, - 'line_end' => 14, - 'hit' => [ - ], - 'out' => [ + ), + 5 => new ProcessedBranchCoverageData( + 5, + 8, + 14, + 14, + [ + ], + [ 0 => 13, ], - 'out_hit' => [ + [ 0 => 0, ], - ], - 9 => [ - 'op_start' => 9, - 'op_end' => 12, - 'line_start' => 16, - 'line_end' => 16, - 'hit' => [ - ], - 'out' => [ + ), + 9 => new ProcessedBranchCoverageData( + 9, + 12, + 16, + 16, + [ + ], + [ 0 => 2147483645, ], - 'out_hit' => [ + [ 0 => 0, ], - ], - 13 => [ - 'op_start' => 13, - 'op_end' => 14, - 'line_start' => 18, - 'line_end' => 18, - 'hit' => [ - ], - 'out' => [ + ), + 13 => new ProcessedBranchCoverageData( + 13, + 14, + 18, + 18, + [ + ], + [ 0 => 2147483645, ], - 'out_hit' => [ + [ 0 => 0, ], - ], + ), ], - 'paths' => [ - 0 => [ - 'path' => [ + [ + 0 => new ProcessedPathCoverageData( + [ 0 => 0, 1 => 5, 2 => 13, ], - 'hit' => [ + [ ], - ], - 1 => [ - 'path' => [ + ), + 1 => new ProcessedPathCoverageData( + [ 0 => 0, 1 => 9, ], - 'hit' => [ + [ ], - ], + ), ], - ], + ), ], ]; } From 73a4a6ca430ef8f4047fe895e190808afeebc170 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 18:10:58 +0100 Subject: [PATCH 17/20] cs --- src/Report/Html/Renderer/File.php | 8 ++++---- tests/src/TestCase.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Report/Html/Renderer/File.php b/src/Report/Html/Renderer/File.php index b154f7d70..ad8992662 100644 --- a/src/Report/Html/Renderer/File.php +++ b/src/Report/Html/Renderer/File.php @@ -9,9 +9,6 @@ */ namespace SebastianBergmann\CodeCoverage\Report\Html; -use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; -use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionCoverageData; -use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; use const ENT_COMPAT; use const ENT_HTML401; use const ENT_SUBSTITUTE; @@ -105,6 +102,9 @@ use function str_replace; use function token_get_all; use function trim; +use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException; use SebastianBergmann\CodeCoverage\Node\File as FileNode; use SebastianBergmann\CodeCoverage\Util\Percentage; @@ -911,7 +911,7 @@ private function renderPathStructure(FileNode $node): string /** * @param array $branches - * @param list $codeLines + * @param list $codeLines */ private function renderPathLines(ProcessedPathCoverageData $path, array $branches, array $codeLines, array $testData): string { diff --git a/tests/src/TestCase.php b/tests/src/TestCase.php index af2ed2766..f8273d17c 100644 --- a/tests/src/TestCase.php +++ b/tests/src/TestCase.php @@ -9,14 +9,14 @@ */ namespace SebastianBergmann\CodeCoverage; -use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; -use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionCoverageData; -use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; use function rmdir; use function unlink; use BankAccount; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; +use SebastianBergmann\CodeCoverage\Data\ProcessedBranchCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionCoverageData; +use SebastianBergmann\CodeCoverage\Data\ProcessedPathCoverageData; use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData; use SebastianBergmann\CodeCoverage\Driver\Driver; use SebastianBergmann\CodeCoverage\Test\Target\Target; From adb03c042560fdf9269896f6900d0fc10b779a1e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 18:15:32 +0100 Subject: [PATCH 18/20] Update File.php --- src/Report/Html/Renderer/File.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Report/Html/Renderer/File.php b/src/Report/Html/Renderer/File.php index ad8992662..7b77bc38c 100644 --- a/src/Report/Html/Renderer/File.php +++ b/src/Report/Html/Renderer/File.php @@ -781,14 +781,12 @@ private function renderBranchStructure(FileNode $node): string ksort($coverageData); + /** @var ProcessedFunctionCoverageData $methodData */ foreach ($coverageData as $methodName => $methodData) { - if (!$methodData['branches']) { - continue; - } - $branchStructure = ''; - foreach ($methodData['branches'] as $branch) { + /** @var ProcessedBranchCoverageData $branch */ + foreach ($methodData->branches as $branch) { $branchStructure .= $this->renderBranchLines($branch, $codeLines, $testData); } @@ -806,14 +804,14 @@ private function renderBranchStructure(FileNode $node): string /** * @param list $codeLines */ - private function renderBranchLines(array $branch, array $codeLines, array $testData): string + private function renderBranchLines(ProcessedBranchCoverageData $branch, array $codeLines, array $testData): string { $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); $lines = ''; - $branchLines = range($branch['line_start'], $branch['line_end']); + $branchLines = range($branch->line_start, $branch->line_end); sort($branchLines); // sometimes end_line < start_line /** @var int $line */ @@ -825,7 +823,7 @@ private function renderBranchLines(array $branch, array $codeLines, array $testD $popoverContent = ''; $popoverTitle = ''; - $numTests = count($branch['hit']); + $numTests = count($branch->hit); if ($numTests === 0) { $trClass = 'danger'; @@ -839,7 +837,7 @@ private function renderBranchLines(array $branch, array $codeLines, array $testD $popoverTitle = '1 test covers this branch'; } - foreach ($branch['hit'] as $test) { + foreach ($branch->hit as $test) { if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { $lineCss = 'covered-by-medium-tests'; } elseif ($testData[$test]['size'] === 'small') { From 3e53a48b4f6246e77ba7d0fd7810dd6e5980c9a4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 9 Nov 2025 18:29:57 +0100 Subject: [PATCH 19/20] Update ProcessedFunctionCoverageData.php --- src/Data/ProcessedFunctionCoverageData.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Data/ProcessedFunctionCoverageData.php b/src/Data/ProcessedFunctionCoverageData.php index 39dcd9205..7a12f32d7 100644 --- a/src/Data/ProcessedFunctionCoverageData.php +++ b/src/Data/ProcessedFunctionCoverageData.php @@ -82,8 +82,8 @@ public function merge(self $data): self } return new self( - $branches, - $paths, + $branches ?? $this->branches, + $paths ?? $this->paths, ); } From 43206aa107d02fb9c961fc4194f0896aa67a8355 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 10 Nov 2025 08:37:37 +0100 Subject: [PATCH 20/20] new classes are internal --- src/Data/ProcessedBranchCoverageData.php | 2 ++ src/Data/ProcessedFunctionCoverageData.php | 2 ++ src/Data/ProcessedPathCoverageData.php | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Data/ProcessedBranchCoverageData.php b/src/Data/ProcessedBranchCoverageData.php index 2b6076160..23687ef81 100644 --- a/src/Data/ProcessedBranchCoverageData.php +++ b/src/Data/ProcessedBranchCoverageData.php @@ -15,6 +15,8 @@ use SebastianBergmann\CodeCoverage\Driver\XdebugDriver; /** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * * @phpstan-import-type TestIdType from ProcessedCodeCoverageData * @phpstan-import-type XdebugBranchCoverageType from XdebugDriver */ diff --git a/src/Data/ProcessedFunctionCoverageData.php b/src/Data/ProcessedFunctionCoverageData.php index 7a12f32d7..0fa54a0b6 100644 --- a/src/Data/ProcessedFunctionCoverageData.php +++ b/src/Data/ProcessedFunctionCoverageData.php @@ -12,6 +12,8 @@ use SebastianBergmann\CodeCoverage\Driver\XdebugDriver; /** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * * @phpstan-import-type TestIdType from ProcessedCodeCoverageData * @phpstan-import-type XdebugFunctionCoverageType from XdebugDriver */ diff --git a/src/Data/ProcessedPathCoverageData.php b/src/Data/ProcessedPathCoverageData.php index ccb651931..81f73da77 100644 --- a/src/Data/ProcessedPathCoverageData.php +++ b/src/Data/ProcessedPathCoverageData.php @@ -15,6 +15,8 @@ use SebastianBergmann\CodeCoverage\Driver\XdebugDriver; /** + * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage + * * @phpstan-import-type TestIdType from ProcessedCodeCoverageData * @phpstan-import-type XdebugPathCoverageType from XdebugDriver */