From d2d5e4682d97f7ba01c08d93d6f730735ead7f26 Mon Sep 17 00:00:00 2001 From: Doug Wright Date: Sat, 5 Sep 2020 19:08:21 +0100 Subject: [PATCH 1/2] Remove preloading (again) --- src/CodeCoverage.php | 79 +++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 6eccd3b93..e14283bea 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -108,13 +108,6 @@ final class CodeCoverage */ private $parentClassesExcludedFromUnintentionallyCoveredCodeCheck = []; - /** - * Determine if the data has been initialized or not. - * - * @var bool - */ - private $isInitialized = false; - /** * @var ?CoveredFileAnalyser */ @@ -151,10 +144,9 @@ public function getReport(): Directory */ public function clear(): void { - $this->isInitialized = false; - $this->currentId = null; - $this->data = new ProcessedCodeCoverageData; - $this->tests = []; + $this->currentId = null; + $this->data = new ProcessedCodeCoverageData; + $this->tests = []; } /** @@ -170,8 +162,12 @@ public function filter(): Filter */ public function getData(bool $raw = false): ProcessedCodeCoverageData { - if (!$raw && $this->includeUncoveredFiles) { - $this->addUncoveredFilesFromFilter(); + if (!$raw) { + if ($this->processUncoveredFiles) { + $this->processUncoveredFilesFromFilter(); + } elseif ($this->includeUncoveredFiles) { + $this->addUncoveredFilesFromFilter(); + } } return $this->data; @@ -212,10 +208,6 @@ public function start($id, bool $clear = false): void $this->clear(); } - if ($this->isInitialized === false) { - $this->initializeData(); - } - $this->currentId = $id; $this->driver->start(); @@ -510,7 +502,7 @@ private function addUncoveredFilesFromFilter(): void { $uncoveredFiles = array_diff( $this->filter->files(), - array_keys($this->data->lineCoverage()) + $this->data->coveredFiles() ); foreach ($uncoveredFiles as $uncoveredFile) { @@ -526,6 +518,27 @@ private function addUncoveredFilesFromFilter(): void } } + /** + * @throws UnintentionallyCoveredCodeException + */ + private function processUncoveredFilesFromFilter(): void + { + $uncoveredFiles = array_diff( + $this->filter->files(), + $this->data->coveredFiles() + ); + + $this->driver->start(); + + foreach ($uncoveredFiles as $uncoveredFile) { + if (file_exists($uncoveredFile)) { + include_once $uncoveredFile; + } + } + + $this->append($this->driver->stop(), self::UNCOVERED_FILES); + } + /** * @throws UnintentionallyCoveredCodeException * @throws ReflectionException @@ -628,36 +641,6 @@ private function processUnintentionallyCoveredUnits(array $unintentionallyCovere return array_values($unintentionallyCoveredUnits); } - /** - * @throws UnintentionallyCoveredCodeException - */ - private function initializeData(): void - { - $this->isInitialized = true; - - if ($this->processUncoveredFiles) { - // by collecting dead code data here on an initial pass, future runs with test data do not need to - if ($this->driver->canDetectDeadCode()) { - $this->driver->enableDeadCodeDetection(); - } - - $this->driver->start(); - - foreach ($this->filter->files() as $file) { - if ($this->filter->isFile($file)) { - include_once $file; - } - } - - // having now collected dead code for the entire list of files, we can safely skip this data on subsequent runs - if ($this->driver->canDetectDeadCode()) { - $this->driver->disableDeadCodeDetection(); - } - - $this->append($this->driver->stop(), self::UNCOVERED_FILES); - } - } - private function coveredFileAnalyser(): CoveredFileAnalyser { if ($this->coveredFileAnalyser !== null) { From 745be79cf9ff2002911649f997e760ffe420f115 Mon Sep 17 00:00:00 2001 From: Doug Wright Date: Sat, 5 Sep 2020 19:55:10 +0100 Subject: [PATCH 2/2] Skip empty lines when storing coverage --- src/RawCodeCoverageData.php | 43 +++++++++++++++++++++++++ tests/tests/RawCodeCoverageDataTest.php | 1 - 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/RawCodeCoverageData.php b/src/RawCodeCoverageData.php index cb306e01e..ae5044ffe 100644 --- a/src/RawCodeCoverageData.php +++ b/src/RawCodeCoverageData.php @@ -26,6 +26,11 @@ */ final class RawCodeCoverageData { + /** + * @var array> + */ + private static $emptyLineCache = []; + /** * @var array * @@ -94,6 +99,8 @@ private function __construct(array $lineCoverage, array $functionCoverage) { $this->lineCoverage = $lineCoverage; $this->functionCoverage = $functionCoverage; + + $this->skipEmptyLines(); } public function clear(): void @@ -181,4 +188,40 @@ public function removeCoverageDataForLines(string $filename, array $lines): void } } } + + /** + * At the end of a file, the PHP interpreter always sees an implicit return. Where this occurs in a file that has + * e.g. a class definition, that line cannot be invoked from a test and results in confusing coverage. This engine + * implementation detail therefore needs to be masked which is done here by simply ensuring that all empty lines + * are skipped over for coverage purposes. + * + * @see https://github.com/sebastianbergmann/php-code-coverage/issues/799 + */ + private function skipEmptyLines(): void + { + foreach ($this->lineCoverage as $filename => $coverage) { + foreach ($this->getEmptyLinesForFile($filename) as $emptyLine) { + unset($this->lineCoverage[$filename][$emptyLine]); + } + } + } + + private function getEmptyLinesForFile(string $filename): array + { + if (!isset(self::$emptyLineCache[$filename])) { + self::$emptyLineCache[$filename] = []; + + if (is_file($filename)) { + $sourceLines = explode("\n", file_get_contents($filename)); + + foreach ($sourceLines as $line => $source) { + if (trim($source) === '') { + self::$emptyLineCache[$filename][] = ($line + 1); + } + } + } + } + + return self::$emptyLineCache[$filename]; + } } diff --git a/tests/tests/RawCodeCoverageDataTest.php b/tests/tests/RawCodeCoverageDataTest.php index 542af2a0e..9c0b554e6 100644 --- a/tests/tests/RawCodeCoverageDataTest.php +++ b/tests/tests/RawCodeCoverageDataTest.php @@ -439,7 +439,6 @@ public function testCoverageForFileWithInlineAnnotations(): void 26 => -1, 35 => -1, 36 => -1, - 37 => -1, ], $coverage->lineCoverage()[$filename] );