From 064529c480204fae9338a2ff1d10a52eba5be173 Mon Sep 17 00:00:00 2001 From: Doug Wright Date: Fri, 15 May 2020 19:33:24 +0100 Subject: [PATCH 1/3] Change PHP report to use serialize, rather than var_export for better compatibility with data structure changes --- src/Report/PHP.php | 15 ++------------- tests/tests/PHPTest.php | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 tests/tests/PHPTest.php diff --git a/src/Report/PHP.php b/src/Report/PHP.php index 92926e3cf..61f00a004 100644 --- a/src/Report/PHP.php +++ b/src/Report/PHP.php @@ -22,21 +22,10 @@ final class PHP */ public function process(CodeCoverage $coverage, ?string $target = null): string { - $filter = $coverage->filter(); - $buffer = \sprintf( 'setData(%s); -$coverage->setTests(%s); - -$filter = $coverage->filter(); -$filter->setWhitelistedFiles(%s); - -return $coverage;', - \var_export($coverage->getData(true), true), - \var_export($coverage->getTests(), true), - \var_export($filter->getWhitelistedFiles(), true) +return \unserialize(\'%s\');', + \serialize($coverage) ); if ($target !== null) { diff --git a/tests/tests/PHPTest.php b/tests/tests/PHPTest.php new file mode 100644 index 000000000..204cd1f27 --- /dev/null +++ b/tests/tests/PHPTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage\Report; + +use SebastianBergmann\CodeCoverage\TestCase; + +class PHPTest extends TestCase +{ + protected function tearDown(): void + { + parent::tearDown(); + + $this->removeTemporaryFiles(); + } + + public function testPHPSerialisationProducesValidCode(): void + { + $coverage = $this->getCoverageForBankAccount(); + + (new PHP())->process($coverage, self::$TEST_TMP_PATH . '/serialised.php'); + + $unserialised = require self::$TEST_TMP_PATH . '/serialised.php'; + + $this->assertEquals($coverage, $unserialised); + } +} From b56ffe98409e4a55ad81765ea4503cd8577635a2 Mon Sep 17 00:00:00 2001 From: Doug Wright Date: Thu, 14 May 2020 13:31:13 +0100 Subject: [PATCH 2/3] Encapsulate processed coverage data --- src/CodeCoverage.php | 105 ++------------ src/Node/Builder.php | 29 ++-- src/ProcessedCodeCoverageData.php | 134 ++++++++++++++++++ tests/TestCase.php | 16 ++- tests/tests/BuilderTest.php | 35 +++-- tests/tests/CodeCoverageTest.php | 28 +++- tests/tests/HTMLTest.php | 11 +- tests/tests/ProcessedCodeCoverageDataTest.php | 41 ++++++ 8 files changed, 260 insertions(+), 139 deletions(-) create mode 100644 src/ProcessedCodeCoverageData.php create mode 100644 tests/tests/ProcessedCodeCoverageDataTest.php diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 8736f51e1..e7dcaccef 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -89,9 +89,9 @@ final class CodeCoverage /** * Code coverage data. * - * @var array + * @var ProcessedCodeCoverageData */ - private $data = []; + private $data; /** * @var array @@ -151,6 +151,7 @@ public function __construct(Driver $driver = null, Filter $filter = null) $this->filter = $filter; $this->wizard = new Wizard; + $this->data = new ProcessedCodeCoverageData(); } /** @@ -172,7 +173,7 @@ public function clear(): void { $this->isInitialized = false; $this->currentId = null; - $this->data = []; + $this->data = new ProcessedCodeCoverageData(); $this->tests = []; $this->report = null; } @@ -188,7 +189,7 @@ public function filter(): Filter /** * Returns the collected code coverage data. */ - public function getData(bool $raw = false): array + public function getData(bool $raw = false): ProcessedCodeCoverageData { if (!$raw && $this->addUncoveredFilesFromWhitelist) { $this->addUncoveredFilesFromWhitelist(); @@ -200,7 +201,7 @@ public function getData(bool $raw = false): array /** * Sets the coverage data. */ - public function setData(array $data): void + public function setData(ProcessedCodeCoverageData $data): void { $this->data = $data; $this->report = null; @@ -297,7 +298,7 @@ public function append(RawCodeCoverageData $rawData, $id = null, bool $append = $this->applyWhitelistFilter($rawData); $this->applyIgnoredLinesFilter($rawData); - $this->initializeFilesThatAreSeenTheFirstTime($rawData); + $this->data->initializeFilesThatAreSeenTheFirstTime($rawData); if (!$append) { return; @@ -339,19 +340,7 @@ public function append(RawCodeCoverageData $rawData, $id = null, bool $append = $this->tests[$id] = ['size' => $size, 'status' => $status]; - foreach ($rawData->getLineCoverage() as $file => $lines) { - if (!$this->filter->isFile($file)) { - continue; - } - - foreach ($lines as $k => $v) { - if ($v === Driver::LINE_EXECUTED) { - if (empty($this->data[$file][$k]) || !\in_array($id, $this->data[$file][$k])) { - $this->data[$file][$k][] = $id; - } - } - } - } + $this->data->markCodeAsExecutedByTestCase($id, $rawData); $this->report = null; } @@ -367,36 +356,7 @@ public function merge(self $that): void \array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles()) ); - foreach ($that->data as $file => $lines) { - if (!isset($this->data[$file])) { - if (!$this->filter->isFiltered($file)) { - $this->data[$file] = $lines; - } - - continue; - } - - // we should compare the lines if any of two contains data - $compareLineNumbers = \array_unique( - \array_merge( - \array_keys($this->data[$file]), - \array_keys($that->data[$file]) - ) - ); - - foreach ($compareLineNumbers as $line) { - $thatPriority = $this->getLinePriority($that->data[$file], $line); - $thisPriority = $this->getLinePriority($this->data[$file], $line); - - if ($thatPriority > $thisPriority) { - $this->data[$file][$line] = $that->data[$file][$line]; - } elseif ($thatPriority === $thisPriority && \is_array($this->data[$file][$line])) { - $this->data[$file][$line] = \array_unique( - \array_merge($this->data[$file][$line], $that->data[$file][$line]) - ); - } - } - } + $this->data->merge($that->data); $this->tests = \array_merge($this->tests, $that->getTests()); $this->report = null; @@ -457,38 +417,6 @@ public function setUnintentionallyCoveredSubclassesWhitelist(array $whitelist): $this->unintentionallyCoveredSubclassesWhitelist = $whitelist; } - /** - * 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. - * - * @param array $data - * @param int $line - * - * @return int - */ - private function getLinePriority($data, $line) - { - 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; - } - /** * Applies the @covers annotation filtering. * @@ -559,19 +487,6 @@ private function applyIgnoredLinesFilter(RawCodeCoverageData $data): void } } - private function initializeFilesThatAreSeenTheFirstTime(RawCodeCoverageData $data): void - { - foreach ($data->getLineCoverage() as $file => $lines) { - if (!isset($this->data[$file]) && $this->filter->isFile($file)) { - $this->data[$file] = []; - - foreach ($lines as $k => $v) { - $this->data[$file][$k] = $v === -2 ? null : []; - } - } - } - } - /** * @throws CoveredCodeNotExecutedException * @throws InvalidArgumentException @@ -585,7 +500,7 @@ private function addUncoveredFilesFromWhitelist(): void $data = []; $uncoveredFiles = \array_diff( $this->filter->getWhitelist(), - \array_keys($this->data) + \array_keys($this->data->getLineCoverage()) ); foreach ($uncoveredFiles as $uncoveredFile) { diff --git a/src/Node/Builder.php b/src/Node/Builder.php index 7c20ba752..bd75b4f6b 100644 --- a/src/Node/Builder.php +++ b/src/Node/Builder.php @@ -10,13 +10,14 @@ namespace SebastianBergmann\CodeCoverage\Node; use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData; final class Builder { public function build(CodeCoverage $coverage): Directory { - $files = $coverage->getData(); - $commonPath = $this->reducePaths($files); + $data = clone $coverage->getData(); // clone because path munging is destructive to the original data + $commonPath = $this->reducePaths($data); $root = new Directory( $commonPath, null @@ -24,7 +25,7 @@ public function build(CodeCoverage $coverage): Directory $this->addItems( $root, - $this->buildDirectoryStructure($files), + $this->buildDirectoryStructure($data), $coverage->getTests(), $coverage->getCacheTokens() ); @@ -90,8 +91,9 @@ private function addItems(Directory $root, array $items, array $tests, bool $cac * ) * */ - private function buildDirectoryStructure(array $files): array + private function buildDirectoryStructure(ProcessedCodeCoverageData $data): array { + $files = $data->getLineCoverage(); $result = []; foreach ($files as $path => $file) { @@ -152,20 +154,18 @@ private function buildDirectoryStructure(array $files): array * ) * */ - private function reducePaths(array &$files): string + private function reducePaths(ProcessedCodeCoverageData $coverage): string { - if (empty($files)) { + if (empty($coverage->getCoveredFiles())) { return '.'; } $commonPath = ''; - $paths = \array_keys($files); + $paths = $coverage->getCoveredFiles(); - if (\count($files) === 1) { + if (\count($paths) === 1) { $commonPath = \dirname($paths[0]) . \DIRECTORY_SEPARATOR; - $files[\basename($paths[0])] = $files[$paths[0]]; - - unset($files[$paths[0]]); + $coverage->renameFile($paths[0], \basename($paths[0])); return $commonPath; } @@ -212,16 +212,13 @@ private function reducePaths(array &$files): string } } - $original = \array_keys($files); + $original = $coverage->getCoveredFiles(); $max = \count($original); for ($i = 0; $i < $max; $i++) { - $files[\implode(\DIRECTORY_SEPARATOR, $paths[$i])] = $files[$original[$i]]; - unset($files[$original[$i]]); + $coverage->renameFile($original[$i], \implode(\DIRECTORY_SEPARATOR, $paths[$i])); } - \ksort($files); - return \substr($commonPath, 0, -1); } } diff --git a/src/ProcessedCodeCoverageData.php b/src/ProcessedCodeCoverageData.php new file mode 100644 index 000000000..5564ef84b --- /dev/null +++ b/src/ProcessedCodeCoverageData.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +use SebastianBergmann\CodeCoverage\Driver\Driver; + +/** + * Processed/context-added code coverage data for SUT. + */ +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 = []; + + public function initializeFilesThatAreSeenTheFirstTime(RawCodeCoverageData $rawData): void + { + foreach ($rawData->getLineCoverage() 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 : []; + } + } + } + } + + public function markCodeAsExecutedByTestCase(string $testCaseId, RawCodeCoverageData $executedCode): void + { + foreach ($executedCode->getLineCoverage() as $file => $lines) { + foreach ($lines as $k => $v) { + if ($v === Driver::LINE_EXECUTED) { + if (empty($this->lineCoverage[$file][$k]) || !\in_array($testCaseId, $this->lineCoverage[$file][$k], true)) { + $this->lineCoverage[$file][$k][] = $testCaseId; + } + } + } + } + } + + public function setLineCoverage(array $lineCoverage): void + { + $this->lineCoverage = $lineCoverage; + } + + public function getLineCoverage(): array + { + \ksort($this->lineCoverage); + + return $this->lineCoverage; + } + + public function getCoveredFiles(): array + { + return \array_keys($this->lineCoverage); + } + + public function renameFile(string $oldFile, string $newFile): void + { + $this->lineCoverage[$newFile] = $this->lineCoverage[$oldFile]; + unset($this->lineCoverage[$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->getLinePriority($newData->lineCoverage[$file], $line); + $thisPriority = $this->getLinePriority($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]) + ); + } + } + } + } + + /** + * 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 getLinePriority(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; + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 45e12dd2a..0120f2e71 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -178,7 +178,7 @@ protected function getCoverageForBankAccountForFirstTwoTests(): CodeCoverage return $coverage; } - protected function getCoverageForBankAccountForLastTwoTests() + protected function getCoverageForBankAccountForLastTwoTests(): CodeCoverage { $data = $this->getXdebugDataForBankAccount(); @@ -394,4 +394,18 @@ protected function setUpXdebugStubForCrashParsing(): Driver return $stub; } + + protected function removeTemporaryFiles(): void + { + $tmpFilesIterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator(self::$TEST_TMP_PATH, \RecursiveDirectoryIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($tmpFilesIterator as $path => $fileInfo) { + /* @var \SplFileInfo $fileInfo */ + $pathname = $fileInfo->getPathname(); + $fileInfo->isDir() ? \rmdir($pathname) : \unlink($pathname); + } + } } diff --git a/tests/tests/BuilderTest.php b/tests/tests/BuilderTest.php index 11d6210af..4c3bb4e02 100644 --- a/tests/tests/BuilderTest.php +++ b/tests/tests/BuilderTest.php @@ -10,6 +10,7 @@ namespace SebastianBergmann\CodeCoverage\Report; use SebastianBergmann\CodeCoverage\Node\Builder; +use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData; use SebastianBergmann\CodeCoverage\TestCase; class BuilderTest extends TestCase @@ -134,7 +135,7 @@ public function testNotCrashParsing(): void $this->assertEquals($expectedPath, $root->getPath()); $this->assertEquals(2, $root->getNumExecutableLines()); $this->assertEquals(0, $root->getNumExecutedLines()); - $data = $coverage->getData(); + $data = $coverage->getData()->getLineCoverage(); $expectedFile = $expectedPath . \DIRECTORY_SEPARATOR . 'Crash.php'; $this->assertSame([$expectedFile => [1 => [], 2 => []]], $data); } @@ -166,11 +167,11 @@ public function testBuildDirectoryStructure(): void ], $method->invoke( $this->factory, - [ + $this->pathsToProcessedDataObjectHelper([ "src{$s}Money.php" => [], "src{$s}MoneyBag.php" => [], "src{$s}Foo{$s}Bar{$s}Baz{$s}Foo.php" => [], - ] + ]) ) ); } @@ -178,7 +179,7 @@ public function testBuildDirectoryStructure(): void /** * @dataProvider reducePathsProvider */ - public function testReducePaths($reducedPaths, $commonPath, $paths): void + public function testReducePaths(array $reducedPaths, string $commonPath, ProcessedCodeCoverageData $paths): void { $method = new \ReflectionMethod( Builder::class, @@ -187,9 +188,9 @@ public function testReducePaths($reducedPaths, $commonPath, $paths): void $method->setAccessible(true); - $_commonPath = $method->invokeArgs($this->factory, [&$paths]); + $_commonPath = $method->invokeArgs($this->factory, [$paths]); - $this->assertEquals($reducedPaths, $paths); + $this->assertEquals($reducedPaths, $paths->getLineCoverage()); $this->assertEquals($commonPath, $_commonPath); } @@ -200,7 +201,7 @@ public function reducePathsProvider() yield [ [], '.', - [], + $this->pathsToProcessedDataObjectHelper([]), ]; $prefixes = ["C:$s", "$s"]; @@ -211,9 +212,9 @@ public function reducePathsProvider() 'Money.php' => [], ], "{$p}home{$s}sb{$s}Money{$s}", - [ + $this->pathsToProcessedDataObjectHelper([ "{$p}home{$s}sb{$s}Money{$s}Money.php" => [], - ], + ]), ]; yield [ @@ -222,10 +223,10 @@ public function reducePathsProvider() 'MoneyBag.php' => [], ], "{$p}home{$s}sb{$s}Money", - [ + $this->pathsToProcessedDataObjectHelper([ "{$p}home{$s}sb{$s}Money{$s}Money.php" => [], "{$p}home{$s}sb{$s}Money{$s}MoneyBag.php" => [], - ], + ]), ]; yield [ @@ -235,12 +236,20 @@ public function reducePathsProvider() "Cash.phar{$s}Cash.php" => [], ], "{$p}home{$s}sb{$s}Money", - [ + $this->pathsToProcessedDataObjectHelper([ "{$p}home{$s}sb{$s}Money{$s}Money.php" => [], "{$p}home{$s}sb{$s}Money{$s}MoneyBag.php" => [], "phar://{$p}home{$s}sb{$s}Money{$s}Cash.phar{$s}Cash.php" => [], - ], + ]), ]; } } + + private function pathsToProcessedDataObjectHelper(array $paths): ProcessedCodeCoverageData + { + $coverage = new ProcessedCodeCoverageData(); + $coverage->setLineCoverage($paths); + + return $coverage; + } } diff --git a/tests/tests/CodeCoverageTest.php b/tests/tests/CodeCoverageTest.php index 3b7c1ae0d..da5944cb3 100644 --- a/tests/tests/CodeCoverageTest.php +++ b/tests/tests/CodeCoverageTest.php @@ -53,7 +53,7 @@ public function testCollect(): void $this->assertEquals( $this->getExpectedDataArrayForBankAccount(), - $coverage->getData() + $coverage->getData()->getLineCoverage() ); $this->assertEquals( @@ -67,6 +67,26 @@ public function testCollect(): void ); } + public function testWhitelistFiltering(): void + { + $this->coverage->filter()->addFileToWhitelist(TEST_FILES_PATH . 'BankAccount.php'); + + $data = new RawCodeCoverageData([ + TEST_FILES_PATH . 'BankAccount.php' => [ + 29 => -1, + 31 => -1, + ], + TEST_FILES_PATH . 'CoverageClassTest.php' => [ + 29 => -1, + 31 => -1, + ], + ]); + + $this->coverage->append($data, 'A test', true); + $this->assertContains(TEST_FILES_PATH . 'BankAccount.php', $this->coverage->getData()->getCoveredFiles()); + $this->assertNotContains(TEST_FILES_PATH . 'CoverageClassTest.php', $this->coverage->getData()->getCoveredFiles()); + } + public function testMerge(): void { $coverage = $this->getCoverageForBankAccountForFirstTwoTests(); @@ -74,7 +94,7 @@ public function testMerge(): void $this->assertEquals( $this->getExpectedDataArrayForBankAccount(), - $coverage->getData() + $coverage->getData()->getLineCoverage() ); } @@ -85,7 +105,7 @@ public function testMergeReverseOrder(): void $this->assertEquals( $this->getExpectedDataArrayForBankAccountInReverseOrder(), - $coverage->getData() + $coverage->getData()->getLineCoverage() ); } @@ -100,7 +120,7 @@ public function testMerge2(): void $this->assertEquals( $this->getExpectedDataArrayForBankAccount(), - $coverage->getData() + $coverage->getData()->getLineCoverage() ); } diff --git a/tests/tests/HTMLTest.php b/tests/tests/HTMLTest.php index f93cf8867..0c7e91ce6 100644 --- a/tests/tests/HTMLTest.php +++ b/tests/tests/HTMLTest.php @@ -26,16 +26,7 @@ protected function tearDown(): void { parent::tearDown(); - $tmpFilesIterator = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator(self::$TEST_TMP_PATH, \RecursiveDirectoryIterator::SKIP_DOTS), - \RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach ($tmpFilesIterator as $path => $fileInfo) { - /* @var \SplFileInfo $fileInfo */ - $pathname = $fileInfo->getPathname(); - $fileInfo->isDir() ? \rmdir($pathname) : \unlink($pathname); - } + $this->removeTemporaryFiles(); } public function testForBankAccountTest(): void diff --git a/tests/tests/ProcessedCodeCoverageDataTest.php b/tests/tests/ProcessedCodeCoverageDataTest.php new file mode 100644 index 000000000..f383fb922 --- /dev/null +++ b/tests/tests/ProcessedCodeCoverageDataTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace SebastianBergmann\CodeCoverage; + +class ProcessedCodeCoverageDataTest extends TestCase +{ + public function testMerge(): void + { + $coverage = $this->getCoverageForBankAccountForFirstTwoTests()->getData(); + $coverage->merge($this->getCoverageForBankAccountForLastTwoTests()->getData()); + + $this->assertEquals( + $this->getExpectedDataArrayForBankAccount(), + $coverage->getLineCoverage() + ); + } + + public function testMergeOfAPreviouslyUnseenLine(): void + { + $newCoverage = new ProcessedCodeCoverageData(); + $newCoverage->setLineCoverage( + [ + '/some/path/SomeClass.php' => [ + 12 => [], + 34 => null, + ], + ] + ); + + $existingCoverage = new ProcessedCodeCoverageData(); + $existingCoverage->merge($newCoverage); + $this->assertArrayHasKey(12, $existingCoverage->getLineCoverage()['/some/path/SomeClass.php']); + } +} From 01033e44eac3f1f0024243b7cd006ea7f7f4b7db Mon Sep 17 00:00:00 2001 From: Doug Wright Date: Thu, 14 May 2020 20:56:19 +0100 Subject: [PATCH 3/3] Use named constructors as requested --- src/CodeCoverage.php | 4 +- src/Driver/PCOV.php | 2 +- src/Driver/PHPDBG.php | 2 +- src/Driver/Xdebug.php | 2 +- .../UnknownCoverageDataFormatException.php | 26 ------------ src/RawCodeCoverageData.php | 26 +++++++----- tests/TestCase.php | 14 +++---- tests/tests/CodeCoverageTest.php | 8 ++-- tests/tests/RawCodeCoverageDataTest.php | 40 ++++--------------- 9 files changed, 40 insertions(+), 84 deletions(-) delete mode 100644 src/Exception/UnknownCoverageDataFormatException.php diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index e7dcaccef..9c26e729b 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -517,7 +517,7 @@ private function addUncoveredFilesFromWhitelist(): void } } - $this->append(new RawCodeCoverageData($data), 'UNCOVERED_FILES_FROM_WHITELIST'); + $this->append(RawCodeCoverageData::fromXdebugWithoutPathCoverage($data), 'UNCOVERED_FILES_FROM_WHITELIST'); } private function getLinesToBeIgnored(string $fileName): array @@ -887,7 +887,7 @@ private function initializeData(): void $data[$file] = $fileCoverage; } - $this->append(new RawCodeCoverageData($data), 'UNCOVERED_FILES_FROM_WHITELIST'); + $this->append(RawCodeCoverageData::fromXdebugWithoutPathCoverage($data), 'UNCOVERED_FILES_FROM_WHITELIST'); } } diff --git a/src/Driver/PCOV.php b/src/Driver/PCOV.php index af830c34c..379625494 100644 --- a/src/Driver/PCOV.php +++ b/src/Driver/PCOV.php @@ -46,6 +46,6 @@ public function stop(): RawCodeCoverageData \pcov\clear(); - return new RawCodeCoverageData($collect); + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($collect); } } diff --git a/src/Driver/PHPDBG.php b/src/Driver/PHPDBG.php index 57871d6cb..126e846e7 100644 --- a/src/Driver/PHPDBG.php +++ b/src/Driver/PHPDBG.php @@ -72,7 +72,7 @@ public function stop(): RawCodeCoverageData $fetchedLines = \array_merge($fetchedLines, $sourceLines); - return new RawCodeCoverageData($this->detectExecutedLines($fetchedLines, $dbgData)); + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($this->detectExecutedLines($fetchedLines, $dbgData)); } /** diff --git a/src/Driver/Xdebug.php b/src/Driver/Xdebug.php index 1264b21eb..672ce0e70 100644 --- a/src/Driver/Xdebug.php +++ b/src/Driver/Xdebug.php @@ -55,6 +55,6 @@ public function stop(): RawCodeCoverageData \xdebug_stop_code_coverage(); - return new RawCodeCoverageData($data); + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data); } } diff --git a/src/Exception/UnknownCoverageDataFormatException.php b/src/Exception/UnknownCoverageDataFormatException.php deleted file mode 100644 index f8cd7dc5a..000000000 --- a/src/Exception/UnknownCoverageDataFormatException.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace SebastianBergmann\CodeCoverage; - -/** - * Exception that is raised when a driver supplies coverage data in a format that cannot be handled. - */ -final class UnknownCoverageDataFormatException extends RuntimeException -{ - public static function create(string $filename): self - { - return new self( - \sprintf( - 'Coverage data for file "%s" must be in Xdebug-compatible format, see https://xdebug.org/docs/code_coverage', - $filename - ) - ); - } -} diff --git a/src/RawCodeCoverageData.php b/src/RawCodeCoverageData.php index 6f3af62a8..bd2ab653d 100644 --- a/src/RawCodeCoverageData.php +++ b/src/RawCodeCoverageData.php @@ -21,19 +21,25 @@ final class RawCodeCoverageData */ private $lineCoverage = []; - public function __construct(array $rawCoverage = []) + public static function fromXdebugWithoutPathCoverage(array $rawCoverage): self { - foreach ($rawCoverage as $file => $fileCoverageData) { - $hasOnlyIntegerKeys = \count(\array_filter(\array_keys($fileCoverageData), 'is_int')) === \count($fileCoverageData); + return new self($rawCoverage); + } - if ($hasOnlyIntegerKeys) { - $this->lineCoverage[$file] = $fileCoverageData; - } elseif (\count($fileCoverageData) === 2 && isset($fileCoverageData['lines'], $fileCoverageData['functions'])) { - $this->lineCoverage[$file] = $fileCoverageData['lines']; - } else { - throw UnknownCoverageDataFormatException::create($file); - } + public static function fromXdebugWithPathCoverage(array $rawCoverage): self + { + $lineCoverage = []; + + foreach ($rawCoverage as $file => $fileCoverageData) { + $lineCoverage[$file] = $fileCoverageData['lines']; } + + return new self($lineCoverage); + } + + private function __construct(array $lineCoverage) + { + $this->lineCoverage = $lineCoverage; } public function clear(): void diff --git a/tests/TestCase.php b/tests/TestCase.php index 0120f2e71..c6c68df59 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,7 +23,7 @@ public static function setUpBeforeClass(): void protected function getXdebugDataForBankAccount() { return [ - new RawCodeCoverageData([ + RawCodeCoverageData::fromXdebugWithoutPathCoverage([ TEST_FILES_PATH . 'BankAccount.php' => [ 8 => 1, 9 => -2, @@ -40,7 +40,7 @@ protected function getXdebugDataForBankAccount() 32 => -2, ], ]), - new RawCodeCoverageData([ + RawCodeCoverageData::fromXdebugWithoutPathCoverage([ TEST_FILES_PATH . 'BankAccount.php' => [ 8 => 1, 13 => 1, @@ -48,7 +48,7 @@ protected function getXdebugDataForBankAccount() 29 => 1, ], ]), - new RawCodeCoverageData([ + RawCodeCoverageData::fromXdebugWithoutPathCoverage([ TEST_FILES_PATH . 'BankAccount.php' => [ 8 => 1, 13 => 1, @@ -56,7 +56,7 @@ protected function getXdebugDataForBankAccount() 22 => 1, ], ]), - new RawCodeCoverageData([ + RawCodeCoverageData::fromXdebugWithoutPathCoverage([ TEST_FILES_PATH . 'BankAccount.php' => [ 8 => 1, 13 => 1, @@ -314,7 +314,7 @@ protected function setUpXdebugStubForFileWithIgnoredLines(): Driver $stub->expects($this->any()) ->method('stop') ->will($this->returnValue( - new RawCodeCoverageData( + RawCodeCoverageData::fromXdebugWithoutPathCoverage( [ TEST_FILES_PATH . 'source_with_ignore.php' => [ 2 => 1, @@ -352,7 +352,7 @@ protected function setUpXdebugStubForClassWithAnonymousFunction(): Driver $stub->expects($this->any()) ->method('stop') ->will($this->returnValue( - new RawCodeCoverageData( + RawCodeCoverageData::fromXdebugWithoutPathCoverage( [ TEST_FILES_PATH . 'source_with_class_and_anonymous_function.php' => [ 7 => 1, @@ -390,7 +390,7 @@ protected function setUpXdebugStubForCrashParsing(): Driver $stub->expects($this->any()) ->method('stop') - ->will($this->returnValue(new RawCodeCoverageData([]))); + ->will($this->returnValue(RawCodeCoverageData::fromXdebugWithoutPathCoverage([]))); return $stub; } diff --git a/tests/tests/CodeCoverageTest.php b/tests/tests/CodeCoverageTest.php index da5944cb3..5574d360d 100644 --- a/tests/tests/CodeCoverageTest.php +++ b/tests/tests/CodeCoverageTest.php @@ -44,7 +44,7 @@ public function testCannotAppendWithInvalidArgument(): void { $this->expectException(Exception::class); - $this->coverage->append(new RawCodeCoverageData([]), null); + $this->coverage->append(RawCodeCoverageData::fromXdebugWithoutPathCoverage([]), null); } public function testCollect(): void @@ -71,7 +71,7 @@ public function testWhitelistFiltering(): void { $this->coverage->filter()->addFileToWhitelist(TEST_FILES_PATH . 'BankAccount.php'); - $data = new RawCodeCoverageData([ + $data = RawCodeCoverageData::fromXdebugWithoutPathCoverage([ TEST_FILES_PATH . 'BankAccount.php' => [ 29 => -1, 31 => -1, @@ -310,7 +310,7 @@ public function testAppendThrowsExceptionIfCoveredCodeWasNotExecuted(): void $this->coverage->filter()->addDirectoryToWhitelist(TEST_FILES_PATH); $this->coverage->setCheckForUnexecutedCoveredCode(true); - $data = new RawCodeCoverageData([ + $data = RawCodeCoverageData::fromXdebugWithoutPathCoverage([ TEST_FILES_PATH . 'BankAccount.php' => [ 29 => -1, 31 => -1, @@ -336,7 +336,7 @@ public function testAppendThrowsExceptionIfUsedCodeWasNotExecuted(): void $this->coverage->filter()->addDirectoryToWhitelist(TEST_FILES_PATH); $this->coverage->setCheckForUnexecutedCoveredCode(true); - $data = new RawCodeCoverageData([ + $data = RawCodeCoverageData::fromXdebugWithoutPathCoverage([ TEST_FILES_PATH . 'BankAccount.php' => [ 29 => -1, 31 => -1, diff --git a/tests/tests/RawCodeCoverageDataTest.php b/tests/tests/RawCodeCoverageDataTest.php index d21d52e50..8f7dfef5e 100644 --- a/tests/tests/RawCodeCoverageDataTest.php +++ b/tests/tests/RawCodeCoverageDataTest.php @@ -24,14 +24,14 @@ public function testLineDataFromStandardXDebugFormat(): void ], ]; - $dataObject = new RawCodeCoverageData($lineDataFromDriver); + $dataObject = RawCodeCoverageData::fromXdebugWithoutPathCoverage($lineDataFromDriver); $this->assertEquals($lineDataFromDriver, $dataObject->getLineCoverage()); } /** - * In the branch-check XDebug format, the line data exists inside a "lines" array key. + * In the path-coverage XDebug format, the line data exists inside a "lines" array key. */ - public function testLineDataFromBranchCheckXDebugFormat(): void + public function testLineDataFromPathCoverageXDebugFormat(): void { $rawDataFromDriver = [ '/some/path/SomeClass.php' => [ @@ -54,34 +54,10 @@ public function testLineDataFromBranchCheckXDebugFormat(): void ], ]; - $dataObject = new RawCodeCoverageData($rawDataFromDriver); + $dataObject = RawCodeCoverageData::fromXdebugWithPathCoverage($rawDataFromDriver); $this->assertEquals($lineData, $dataObject->getLineCoverage()); } - /** - * Coverage data that does not match a known format should throw an exception. - */ - public function testDataFromUnknownFormat(): void - { - $this->expectException(UnknownCoverageDataFormatException::class); - - $lineDataFromDriver = [ - '/some/path/SomeClass.php' => [ - 'executedLines' => [ - 8, - ], - 'unExecutedLines' => [ - 13, - ], - 'nonExecutableLines' => [ - 9, - ], - ], - ]; - - $dataObject = new RawCodeCoverageData($lineDataFromDriver); - } - public function testClear(): void { $lineDataFromDriver = [ @@ -92,7 +68,7 @@ public function testClear(): void ], ]; - $dataObject = new RawCodeCoverageData($lineDataFromDriver); + $dataObject = RawCodeCoverageData::fromXdebugWithoutPathCoverage($lineDataFromDriver); $dataObject->clear(); $this->assertEmpty($dataObject->getLineCoverage()); } @@ -130,7 +106,7 @@ public function testRemoveCoverageDataForFile(): void ], ]; - $dataObject = new RawCodeCoverageData($lineDataFromDriver); + $dataObject = RawCodeCoverageData::fromXdebugWithoutPathCoverage($lineDataFromDriver); $dataObject->removeCoverageDataForFile('/some/path/SomeOtherClass.php'); $this->assertEquals($expectedFilterResult, $dataObject->getLineCoverage()); } @@ -167,7 +143,7 @@ public function testKeepCoverageDataOnlyForLines(): void ], ]; - $dataObject = new RawCodeCoverageData($lineDataFromDriver); + $dataObject = RawCodeCoverageData::fromXdebugWithoutPathCoverage($lineDataFromDriver); $dataObject->keepCoverageDataOnlyForLines('/some/path/SomeClass.php', [9, 13]); $dataObject->keepCoverageDataOnlyForLines('/some/path/SomeOtherClass.php', [999]); $dataObject->keepCoverageDataOnlyForLines('/some/path/AnotherClass.php', [28]); @@ -209,7 +185,7 @@ public function testRemoveCoverageDataForLines(): void ], ]; - $dataObject = new RawCodeCoverageData($lineDataFromDriver); + $dataObject = RawCodeCoverageData::fromXdebugWithoutPathCoverage($lineDataFromDriver); $dataObject->removeCoverageDataForLines('/some/path/SomeClass.php', [9, 13]); $dataObject->removeCoverageDataForLines('/some/path/SomeOtherClass.php', [999]); $dataObject->removeCoverageDataForLines('/some/path/AnotherClass.php', [28]);