From 0a9ff6efa72290fe809f122638c58fa6d88fbfea Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Tue, 19 Mar 2019 17:16:48 -0400 Subject: [PATCH 01/31] Start adding basic branch coverage requirements --- src/CodeCoverage.php | 22 ++++++++++++++++++++++ src/Driver/Driver.php | 5 +++++ src/Driver/PHPDBG.php | 8 ++++++++ src/Driver/Xdebug.php | 27 ++++++++++++++++++++++++--- 4 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 2c92ae2dc..ab249c931 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -134,6 +134,11 @@ final class CodeCoverage */ private $report; + /** + * @var bool + */ + private $determineBranchCoverage; + /** * @throws RuntimeException */ @@ -457,6 +462,23 @@ public function setUnintentionallyCoveredSubclassesWhitelist(array $whitelist): $this->unintentionallyCoveredSubclassesWhitelist = $whitelist; } + /** + * Specify whether branch coverage should be processed, if the chosen driver supports branch coverage + * Branch coverage is only supported for the Xdebug driver, with an xdebug version of >= 2.3.2 + */ + public function setDetermineBranchCoverage(bool $flag): void + { + if ($flag) { + if ($this->driver instanceof Xdebug && \version_compare(\phpversion('xdebug'), '2.3.2', '>=')) { + $this->determineBranchCoverage = $flag; + } else { + throw new RuntimeException('Branch coverage requires Xdebug version 2.3.2 or newer'); + } + } else { + $this->determineBranchCoverage = false; + } + } + /** * Determine the priority for a line * diff --git a/src/Driver/Driver.php b/src/Driver/Driver.php index 17acbf62d..6154a08c0 100644 --- a/src/Driver/Driver.php +++ b/src/Driver/Driver.php @@ -44,4 +44,9 @@ public function start(bool $determineUnusedAndDead = true): void; * Stop collection of code coverage information. */ public function stop(): array; + + /** + * Specify that branch coverage should be included with collected code coverage information. + */ + public function setDetermineBranchCoverage(bool $flag): void; } diff --git a/src/Driver/PHPDBG.php b/src/Driver/PHPDBG.php index e9f999a05..499c6d952 100644 --- a/src/Driver/PHPDBG.php +++ b/src/Driver/PHPDBG.php @@ -76,6 +76,14 @@ public function stop(): array return $this->detectExecutedLines($fetchedLines, $dbgData); } + /** + * Specify that branch coverage should be included with collected code coverage information. + */ + public function setDetermineBranchCoverage(bool $flag): void + { + throw new RuntimeException('Branch coverage is not supported in PHPDBG'); + } + /** * Convert phpdbg based data into the format CodeCoverage expects */ diff --git a/src/Driver/Xdebug.php b/src/Driver/Xdebug.php index 737949636..ea4999ff6 100644 --- a/src/Driver/Xdebug.php +++ b/src/Driver/Xdebug.php @@ -29,6 +29,11 @@ final class Xdebug implements Driver */ private $filter; + /** + * @var bool + */ + private $determineBranchCoverage = false; + /** * @throws RuntimeException */ @@ -54,11 +59,16 @@ public function __construct(Filter $filter = null) */ public function start(bool $determineUnusedAndDead = true): void { + $flag = 0; + if ($determineUnusedAndDead) { - \xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); - } else { - \xdebug_start_code_coverage(); + $flag = XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE; + } + + if ($this->determineBranchCoverage) { + $flag |= XDEBUG_CC_BRANCH_CHECK; } + \xdebug_start_code_coverage($flag); } /** @@ -73,6 +83,17 @@ public function stop(): array return $this->cleanup($data); } + /** + * Specify that branch coverage should be included with collected code coverage information. + */ + public function setDetermineBranchCoverage(bool $flag): void + { + if ($flag && \version_compare(\phpversion('xdebug'), '2.3.2', '<')) { + throw new RuntimeException('Branch coverage requires Xdebug 2.3.2 or newer'); + } + $this->determineBranchCoverage = $flag; + } + private function cleanup(array $data): array { foreach (\array_keys($data) as $file) { From cb9fbf7ed9281dd41667c485bd84c27b6dcc97ca Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Wed, 20 Mar 2019 08:25:23 -0400 Subject: [PATCH 02/31] Add in missing call to setDetermineBranchCoverage --- src/CodeCoverage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index ab249c931..725d6992a 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -246,6 +246,7 @@ public function start($id, bool $clear = false): void $this->currentId = $id; + $this->driver->setDetermineBranchCoverage($this->determineBranchCoverage); $this->driver->start($this->shouldCheckForDeadAndUnused); } @@ -973,7 +974,6 @@ private function initializeData(): void if ($this->processUncoveredFilesFromWhitelist) { $this->shouldCheckForDeadAndUnused = false; - $this->driver->start(); foreach ($this->filter->getWhitelist() as $file) { From c994f5e139b4777db6ce748faa16c9f3720e84cf Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Wed, 20 Mar 2019 12:50:31 -0400 Subject: [PATCH 03/31] Fix XDebug flag bitwise logic --- src/Driver/Xdebug.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Driver/Xdebug.php b/src/Driver/Xdebug.php index ea4999ff6..d5687c5bd 100644 --- a/src/Driver/Xdebug.php +++ b/src/Driver/Xdebug.php @@ -66,8 +66,9 @@ public function start(bool $determineUnusedAndDead = true): void } if ($this->determineBranchCoverage) { - $flag |= XDEBUG_CC_BRANCH_CHECK; + $flag = XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE | XDEBUG_CC_BRANCH_CHECK; } + \xdebug_start_code_coverage($flag); } From 3be6b40f00a0395f9de55fb65154836616e498b1 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Wed, 20 Mar 2019 13:43:35 -0400 Subject: [PATCH 04/31] Transform Xdebug driver response to consistent structure Add branch coverage flags to Command and Runner --- src/Driver/Xdebug.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Driver/Xdebug.php b/src/Driver/Xdebug.php index d5687c5bd..4f9cd0b08 100644 --- a/src/Driver/Xdebug.php +++ b/src/Driver/Xdebug.php @@ -98,7 +98,14 @@ public function setDetermineBranchCoverage(bool $flag): void private function cleanup(array $data): array { foreach (\array_keys($data) as $file) { - unset($data[$file][0]); + if (!isset($data[$file]['lines'])) { + $data[$file] = ['lines' => $data[$file]]; + } + if (!isset($data[$file]['functions'])) { + $data[$file]['functions'] = []; + } + + unset($data[$file]['lines'][0]); if (!$this->filter->isFile($file)) { continue; @@ -106,9 +113,9 @@ private function cleanup(array $data): array $numLines = $this->getNumberOfLinesInFile($file); - foreach (\array_keys($data[$file]) as $line) { + foreach (\array_keys($data[$file]['lines']) as $line) { if ($line > $numLines) { - unset($data[$file][$line]); + unset($data[$file]['lines'][$line]); } } } From 45c6711a15525d23b849befb8ebfa935741b793c Mon Sep 17 00:00:00 2001 From: "Thomason, James" Date: Thu, 21 Mar 2019 07:19:00 -0400 Subject: [PATCH 05/31] Implement revised contract in PHPDBG Driver --- src/Driver/PHPDBG.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Driver/PHPDBG.php b/src/Driver/PHPDBG.php index 499c6d952..c3d820d9f 100644 --- a/src/Driver/PHPDBG.php +++ b/src/Driver/PHPDBG.php @@ -99,6 +99,13 @@ private function detectExecutedLines(array $sourceLines, array $dbgData): array } } + foreach ($sourceLines as $file => $lines) { + $sourceLines[$file] = [ + 'lines' => $lines, + 'functions' => [], + ]; + } + return $sourceLines; } } From dd50e49a732f0b5c74d88a1d4ed150ab51c70ad2 Mon Sep 17 00:00:00 2001 From: "Thomason, James" Date: Thu, 21 Mar 2019 11:39:02 -0400 Subject: [PATCH 06/31] Default determineBranchCoverage to false --- src/CodeCoverage.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 725d6992a..989e43a78 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -135,9 +135,11 @@ final class CodeCoverage private $report; /** + * Determine whether to display branch coverage + * * @var bool */ - private $determineBranchCoverage; + private $determineBranchCoverage = false; /** * @throws RuntimeException From 60c4aef419edee50768a57327ad023b00aaea3b4 Mon Sep 17 00:00:00 2001 From: "Thomason, James" Date: Thu, 21 Mar 2019 12:25:37 -0400 Subject: [PATCH 07/31] Add branch path support to abstract node --- src/Node/AbstractNode.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/Node/AbstractNode.php b/src/Node/AbstractNode.php index 116a09f17..af07326c3 100644 --- a/src/Node/AbstractNode.php +++ b/src/Node/AbstractNode.php @@ -180,6 +180,20 @@ public function getTestedMethodsPercent(bool $asString = true) ); } + /** + * Returns the percentage of paths that have been tested. + * + * @return int|string + */ + public function getPathsTestedPercent(bool $asString = true) + { + return Util::percent( + $this->getNumTestedPaths(), + $this->getNumPaths(), + $asString + ); + } + /** * Returns the percentage of functions and methods that has been tested. * @@ -276,6 +290,11 @@ abstract public function getFunctions(): array; */ abstract public function getLinesOfCode(): array; + /** + * Returns the paths of this node. + */ + abstract public function getPaths(): array; + /** * Returns the number of executable lines. */ @@ -325,4 +344,14 @@ abstract public function getNumFunctions(): int; * Returns the number of tested functions. */ abstract public function getNumTestedFunctions(): int; + + /** + * Returns the number of paths. + */ + abstract public function getNumPaths(): int; + + /** + * Returns the number of tested paths. + */ + abstract public function getNumTestedPaths(): int; } From b503e8299db93330251ab9b8ea62a3cae8bd8e6a Mon Sep 17 00:00:00 2001 From: "Thomason, James" Date: Thu, 21 Mar 2019 14:44:22 -0400 Subject: [PATCH 08/31] Reorder method name, add branches support --- src/Node/AbstractNode.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Node/AbstractNode.php b/src/Node/AbstractNode.php index af07326c3..c856b4454 100644 --- a/src/Node/AbstractNode.php +++ b/src/Node/AbstractNode.php @@ -185,7 +185,7 @@ public function getTestedMethodsPercent(bool $asString = true) * * @return int|string */ - public function getPathsTestedPercent(bool $asString = true) + public function getTestedPathsPercent(bool $asString = true) { return Util::percent( $this->getNumTestedPaths(), @@ -194,6 +194,20 @@ public function getPathsTestedPercent(bool $asString = true) ); } + /** + * Returns the percentage of branches that have been tested. + * + * @return int|string + */ + public function getTestedBranchesPercent(bool $asString = true) + { + return Util::percent( + $this->getNumTestedBranches(), + $this->getNumBranches(), + $asString + ); + } + /** * Returns the percentage of functions and methods that has been tested. * From b49e9297608f20ba277fa926f51a4d0b5e5a11c7 Mon Sep 17 00:00:00 2001 From: "Thomason, James" Date: Thu, 21 Mar 2019 14:45:50 -0400 Subject: [PATCH 09/31] Oh. Those methods, too --- src/Node/AbstractNode.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Node/AbstractNode.php b/src/Node/AbstractNode.php index c856b4454..4f4f07881 100644 --- a/src/Node/AbstractNode.php +++ b/src/Node/AbstractNode.php @@ -309,6 +309,11 @@ abstract public function getLinesOfCode(): array; */ abstract public function getPaths(): array; + /** + * Returns the branches of this node. + */ + abstract public function getBranches(): array; + /** * Returns the number of executable lines. */ @@ -368,4 +373,14 @@ abstract public function getNumPaths(): int; * Returns the number of tested paths. */ abstract public function getNumTestedPaths(): int; + + /** + * Returns the number of branches. + */ + abstract public function getNumBranches(): int; + + /** + * Returns the number of tested branches. + */ + abstract public function getNumTestedBranches(): int; } From bcbe25b9442e9518a345206ab5239ac42bacaaae Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Thu, 21 Mar 2019 09:59:29 -0400 Subject: [PATCH 10/31] Start working on branch support in CodeCoverage --- src/CodeCoverage.php | 55 ++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 989e43a78..3e0141633 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -347,15 +347,36 @@ public function append(array $data, $id = null, bool $append = true, $linesToBeC $this->tests[$id] = ['size' => $size, 'status' => $status]; - foreach ($data as $file => $lines) { + foreach ($data as $file => $fileData) { 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; + foreach ($fileData['lines'] as $line => $lineCoverage) { + if ($lineCoverage === Driver::LINE_EXECUTED) { + if ($this->data[$file]['lines'][$line] === null) { + $this->data[$file]['lines'][$line] = [ + 'pathCovered' => false, + 'tests' => [$id], + ]; + } elseif (!\in_array($id, $this->data[$file]['lines'][$line]['tests'], true)) { + $this->data[$file]['lines'][$line]['tests'][] = [$id]; + } + } + } + + foreach ($fileData['functions'] as $function => $functionCoverage) { + foreach ($functionCoverage['branches'] as $branch => $branchCoverage) { + if ($branchCoverage['hit'] === 1) { + $this->data[$file]['branches'][$function][$branch]['hit'] = 1; + if (!\in_array($id, $this->data[$file]['branches'][$function][$branch]['tests'], true)) { + $this->data[$file]['branches'][$function][$branch]['tests'][] = $id; + } + } + } + foreach ($functionCoverage['paths'] as $path => $pathCoverage) { + if ($pathCoverage['hit'] === 1 && $this->data[$file]['paths'][$function][$path]['hit'] === 0) { + $this->data[$file]['paths'][$function][$path]['hit'] = 1; } } } @@ -375,10 +396,11 @@ public function merge(self $that): void \array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles()) ); - foreach ($that->data as $file => $lines) { + // I don't know how / why this works, but it should be refactored to ->getData() + foreach ($that->getData() as $file => $fileData) { if (!isset($this->data[$file])) { if (!$this->filter->isFiltered($file)) { - $this->data[$file] = $lines; + $this->data[$file] = $fileData; } continue; @@ -387,20 +409,23 @@ public function merge(self $that): void // 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]) + \array_keys($this->data[$file]['lines']), + \array_keys($that->data[$file]['lines']) // can this be $fileData? ) ); foreach ($compareLineNumbers as $line) { - $thatPriority = $this->getLinePriority($that->data[$file], $line); - $thisPriority = $this->getLinePriority($this->data[$file], $line); + $thatPriority = $this->getLinePriority($that->data[$file]['lines'], $line); + $thisPriority = $this->getLinePriority($this->data[$file]['lines'], $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[$file]['lines'][$line] = $that->data[$file]['lines'][$line]; + } elseif ($thatPriority === $thisPriority && \is_array($this->data[$file]['lines'][$line])) { + if ($line['pathCovered'] === true) { + $this->data[$file]['lines']['line']['pathCovered'] = $line['pathCovered']; + } + $this->data[$file]['lines'][$line] = \array_unique( + \array_merge($this->data[$file]['lines'][$line], $that->data[$file]['lines'][$line]) ); } } From 12baaf6b41c85404d3afcc8cd153e0dc46016976 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Thu, 21 Mar 2019 17:06:09 -0400 Subject: [PATCH 11/31] Lint a few items in PHPDBG and Xdebug. More work in CodeCoverage to add path and branch coverage --- src/CodeCoverage.php | 288 ++++++++++++++++++++++++++++++++++-------- src/Driver/Xdebug.php | 1 + 2 files changed, 238 insertions(+), 51 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 3e0141633..b23c65560 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -354,30 +354,21 @@ public function append(array $data, $id = null, bool $append = true, $linesToBeC foreach ($fileData['lines'] as $line => $lineCoverage) { if ($lineCoverage === Driver::LINE_EXECUTED) { - if ($this->data[$file]['lines'][$line] === null) { - $this->data[$file]['lines'][$line] = [ - 'pathCovered' => false, - 'tests' => [$id], - ]; - } elseif (!\in_array($id, $this->data[$file]['lines'][$line]['tests'], true)) { - $this->data[$file]['lines'][$line]['tests'][] = [$id]; - } + $this->addCoverageLinePathCovered($file, $line, false); + $this->addCoverageLineTest($file, $line, $id); } } foreach ($fileData['functions'] as $function => $functionCoverage) { foreach ($functionCoverage['branches'] as $branch => $branchCoverage) { - if ($branchCoverage['hit'] === 1) { - $this->data[$file]['branches'][$function][$branch]['hit'] = 1; - if (!\in_array($id, $this->data[$file]['branches'][$function][$branch]['tests'], true)) { - $this->data[$file]['branches'][$function][$branch]['tests'][] = $id; - } + if (($branchCoverage['hit'] ?? 0) === 1) { + $this->addCoverageBranchHit($file, $function, $branch, $branchCoverage['hit'] ?? 0); + $this->addCoverageBranchTest($file, $function, $branch, $id); } } + foreach ($functionCoverage['paths'] as $path => $pathCoverage) { - if ($pathCoverage['hit'] === 1 && $this->data[$file]['paths'][$function][$path]['hit'] === 0) { - $this->data[$file]['paths'][$function][$path]['hit'] = 1; - } + $this->addCoveragePathHit($file, $function, $path, $pathCoverage['hit'] ?? 0); } } } @@ -396,11 +387,13 @@ public function merge(self $that): void \array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles()) ); - // I don't know how / why this works, but it should be refactored to ->getData() - foreach ($that->getData() as $file => $fileData) { - if (!isset($this->data[$file])) { + $thisData = $this->getData(); + $thatData = $that->getData(); + + foreach ($thatData as $file => $fileData) { + if (!isset($thisData[$file])) { if (!$this->filter->isFiltered($file)) { - $this->data[$file] = $fileData; + $thisData[$file] = $fileData; } continue; @@ -409,30 +402,30 @@ public function merge(self $that): void // we should compare the lines if any of two contains data $compareLineNumbers = \array_unique( \array_merge( - \array_keys($this->data[$file]['lines']), - \array_keys($that->data[$file]['lines']) // can this be $fileData? + \array_keys($thisData[$file]['lines']), + \array_keys($thatData[$file]['lines']) // can this be $fileData? ) ); foreach ($compareLineNumbers as $line) { - $thatPriority = $this->getLinePriority($that->data[$file]['lines'], $line); - $thisPriority = $this->getLinePriority($this->data[$file]['lines'], $line); + $thatPriority = $this->getLinePriority($thatData[$file]['lines'], $line); + $thisPriority = $this->getLinePriority($thisData[$file]['lines'], $line); if ($thatPriority > $thisPriority) { - $this->data[$file]['lines'][$line] = $that->data[$file]['lines'][$line]; - } elseif ($thatPriority === $thisPriority && \is_array($this->data[$file]['lines'][$line])) { + $thisData[$file]['lines'][$line] = $thatData[$file]['lines'][$line]; + } elseif ($thatPriority === $thisPriority && \is_array($thisData[$file]['lines'][$line])) { if ($line['pathCovered'] === true) { - $this->data[$file]['lines']['line']['pathCovered'] = $line['pathCovered']; + $thisData[$file]['lines'][$line]['pathCovered'] = $line['pathCovered']; } - $this->data[$file]['lines'][$line] = \array_unique( - \array_merge($this->data[$file]['lines'][$line], $that->data[$file]['lines'][$line]) + $thisData[$file]['lines'][$line] = \array_unique( + \array_merge($thisData[$file]['lines'][$line], $thatData[$file]['lines'][$line]) ); } } } - $this->tests = \array_merge($this->tests, $that->getTests()); - $this->report = null; + $this->tests = \array_merge($this->tests, $that->getTests()); + $this->setData($thisData); } public function setCacheTokens(bool $flag): void @@ -517,12 +510,9 @@ public function setDetermineBranchCoverage(bool $flag): void * * During a merge, a higher number is better. * - * @param array $data - * @param int $line - * * @return int */ - private function getLinePriority($data, $line) + private function getLinePriority(array $data, int $line) { if (!\array_key_exists($line, $data)) { return 1; @@ -557,7 +547,10 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar throw new MissingCoversAnnotationException; } - $data = []; + $data = [ + 'lines' => [], + 'functions' => [], + ]; return; } @@ -568,7 +561,7 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar if ($this->checkForUnintentionallyCoveredCode && (!$this->currentId instanceof TestCase || - (!$this->currentId->isMedium() && !$this->currentId->isLarge()))) { + (!$this->currentId->isMedium() && !$this->currentId->isLarge()))) { $this->performUnintentionallyCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed); } @@ -580,7 +573,11 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar foreach (\array_keys($data) as $filename) { $_linesToBeCovered = \array_flip($linesToBeCovered[$filename]); - $data[$filename] = \array_intersect_key($data[$filename], $_linesToBeCovered); + + $data[$filename]['lines'] = \array_intersect_key( + $data[$filename], + $_linesToBeCovered + ); } } @@ -604,24 +601,210 @@ private function applyIgnoredLinesFilter(array &$data): void } foreach ($this->getLinesToBeIgnored($filename) as $line) { - unset($data[$filename][$line]); + unset($data[$filename]['lines'][$line]); } } } private function initializeFilesThatAreSeenTheFirstTime(array $data): void { - foreach ($data as $file => $lines) { - if (!isset($this->data[$file]) && $this->filter->isFile($file)) { - $this->data[$file] = []; + foreach ($data as $file => $fileData) { + if (isset($this->data[$file]) || !$this->filter->isFile($file)) { + continue; + } + $this->initializeFileCoverageData($file); - foreach ($lines as $k => $v) { - $this->data[$file][$k] = $v === -2 ? null : []; + // If this particular line is identified as not covered, mark it as null + foreach ($fileData['lines'] as $lineNumber => $flag) { + if ($flag === Driver::LINE_NOT_EXECUTABLE) { + $this->data[$file]['lines'][$lineNumber] = null; + } + } + + foreach ($fileData['functions'] as $functionName => $functionData) { + // @todo - should this have a helper to merge covered paths? + $this->data[$file]['paths'][$functionName] = $functionData['paths']; + + foreach ($functionData['branches'] as $branchIndex => $branchData) { + $this->addCoverageBranchHit($file, $functionName, $branchIndex, $branchData['hit']); + $this->addCoverageBranchLineStart($file, $functionName, $branchIndex, $branchData['line_start']); + $this->addCoverageBranchLineEnd($file, $functionName, $branchIndex, $branchData['line_end']); + + for ($curLine = $branchData['line_start']; $curLine < $branchData['line_end']; $curLine++) { + if (isset($this->data[$file]['lines'][$curLine])) { + $this->addCoverageLinePathCovered($file, $curLine, (bool) $branchData['hit']); + } + } } } } } + private function initializeFileCoverageData(string $file): void + { + if (!isset($this->data[$file]) && $this->filter->isFile($file)) { + $this->data[$file] = [ + 'lines' => [], + 'branches' => [], + 'paths' => [], + ]; + } + } + + private function addCoverageLinePathCovered(string $file, int $lineNumber, bool $isCovered): void + { + $this->initializeFileCoverageData($file); + + // Initialize the data coverage array for this line + if (!isset($this->data[$file]['lines'][$lineNumber])) { + $this->data[$file]['lines'][$lineNumber] = [ + 'pathCovered' => false, + 'tests' => [], + ]; + } + + $this->data[$file]['lines'][$lineNumber]['pathCovered'] = $isCovered; + } + + private function addCoverageLineTest(string $file, int $lineNumber, string $testId): void + { + $this->initializeFileCoverageData($file); + + // Initialize the data coverage array for this line + if (!isset($this->data[$file]['lines'][$lineNumber])) { + $this->data[$file]['lines'][$lineNumber] = [ + 'pathCovered' => false, + 'tests' => [], + ]; + } + + if (!\in_array($testId, $this->data[$file]['lines'][$lineNumber]['tests'], true)) { + $this->data[$file]['lines'][$lineNumber]['tests'][] = $testId; + } + } + + private function addCoverageBranchHit(string $file, string $functionName, int $branchIndex, int $hit): void + { + $this->initializeFileCoverageData($file); + + if (!\array_key_exists($functionName, $this->data[$file]['branches'])) { + $this->data[$file]['branches'][$functionName] = []; + } + + if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) { + $this->data[$file]['branches'][$functionName][$branchIndex] = [ + 'hit' => 0, + 'line_start' => 0, + 'line_end' => 0, + 'tests' => [], + ]; + } + + $this->data[$file]['branches'][$functionName][$branchIndex]['hit'] = \max( + $this->data[$file]['branches'][$functionName][$branchIndex]['hit'], + $hit + ); + } + + private function addCoverageBranchLineStart( + string $file, + string $functionName, + int $branchIndex, + int $lineStart + ): void { + $this->initializeFileCoverageData($file); + + if (!\array_key_exists($functionName, $this->data[$file]['branches'])) { + $this->data[$file]['branches'][$functionName] = []; + } + + if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) { + $this->data[$file]['branches'][$functionName][$branchIndex] = [ + 'hit' => 0, + 'line_start' => 0, + 'line_end' => 0, + 'tests' => [], + ]; + } + + $this->data[$file]['branches'][$functionName][$branchIndex]['line_start'] = $lineStart; + } + + private function addCoverageBranchLineEnd( + string $file, + string $functionName, + int $branchIndex, + int $lineEnd + ): void { + $this->initializeFileCoverageData($file); + + if (!\array_key_exists($functionName, $this->data[$file]['branches'])) { + $this->data[$file]['branches'][$functionName] = []; + } + + if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) { + $this->data[$file]['branches'][$functionName][$branchIndex] = [ + 'hit' => 0, + 'line_start' => 0, + 'line_end' => 0, + 'tests' => [], + ]; + } + + $this->data[$file]['branches'][$functionName][$branchIndex]['line_end'] = $lineEnd; + } + + private function addCoverageBranchTest( + string $file, + string $functionName, + int $branchIndex, + string $testId + ): void { + $this->initializeFileCoverageData($file); + + if (!\array_key_exists($functionName, $this->data[$file]['branches'])) { + $this->data[$file]['branches'][$functionName] = []; + } + + if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) { + $this->data[$file]['branches'][$functionName][$branchIndex] = [ + 'hit' => 0, + 'line_start' => 0, + 'line_end' => 0, + 'tests' => [], + ]; + } + + if (!\in_array($testId, $this->data[$file]['branches'][$functionName][$branchIndex]['tests'], true)) { + $this->data[$file]['branches'][$functionName][$branchIndex]['tests'][] = $testId; + } + } + + private function addCoveragePathHit( + string $file, + string $functionName, + int $pathId, + int $hit + ): void { + $this->initializeFileCoverageData($file); + + if (!\array_key_exists($functionName, $this->data[$file]['paths'])) { + $this->data[$file]['paths'][$functionName] = []; + } + + if (!\array_key_exists($pathId, $this->data[$file]['paths'][$functionName])) { + $this->data[$file]['paths'][$functionName][$pathId] = [ + 'hit' => 0, + 'path' => [], + ]; + } + + $this->data[$file]['paths'][$functionName][$pathId]['hit'] = \max( + $this->data[$file]['paths'][$functionName][$pathId]['hit'], + $hit + ); + } + /** * @throws CoveredCodeNotExecutedException * @throws InvalidArgumentException @@ -643,12 +826,15 @@ private function addUncoveredFilesFromWhitelist(): void continue; } - $data[$uncoveredFile] = []; + $data[$uncoveredFile] = [ + 'lines' => [], + 'functions' => [], + ]; $lines = \count(\file($uncoveredFile)); - for ($i = 1; $i <= $lines; $i++) { - $data[$uncoveredFile][$i] = Driver::LINE_NOT_EXECUTED; + for ($line = 1; $line <= $lines; $line++) { + $data[$uncoveredFile]['lines'][$line] = Driver::LINE_NOT_EXECUTED; } } @@ -852,10 +1038,10 @@ private function performUnintentionallyCoveredCodeCheck(array &$data, array $lin $unintentionallyCoveredUnits = []; - foreach ($data as $file => $_data) { - foreach ($_data as $line => $flag) { - if ($flag === 1 && !isset($allowedLines[$file][$line])) { - $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line); + foreach ($data as $file => $fileData) { + foreach ($fileData['lines'] as $lineNumber => $flag) { + if ($flag === 1 && !isset($allowedLines[$file][$lineNumber])) { + $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $lineNumber); } } } diff --git a/src/Driver/Xdebug.php b/src/Driver/Xdebug.php index 4f9cd0b08..f89d25315 100644 --- a/src/Driver/Xdebug.php +++ b/src/Driver/Xdebug.php @@ -101,6 +101,7 @@ private function cleanup(array $data): array if (!isset($data[$file]['lines'])) { $data[$file] = ['lines' => $data[$file]]; } + if (!isset($data[$file]['functions'])) { $data[$file]['functions'] = []; } From 67c1d8c0bfaecf7d6c298971850dc42e8e117129 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Mon, 10 Jun 2019 10:25:55 -0400 Subject: [PATCH 12/31] Tidy up CodeCoverage changes --- src/CodeCoverage.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index b23c65560..e4cc9a9f1 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -354,7 +354,7 @@ public function append(array $data, $id = null, bool $append = true, $linesToBeC foreach ($fileData['lines'] as $line => $lineCoverage) { if ($lineCoverage === Driver::LINE_EXECUTED) { - $this->addCoverageLinePathCovered($file, $line, false); + $this->addCoverageLinePathCovered($file, $line, true); $this->addCoverageLineTest($file, $line, $id); } } @@ -836,6 +836,7 @@ private function addUncoveredFilesFromWhitelist(): void for ($line = 1; $line <= $lines; $line++) { $data[$uncoveredFile]['lines'][$line] = Driver::LINE_NOT_EXECUTED; } + // @todo - do the same here with functions and paths } $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST'); From f50cdae2d5a4e505c7127e7eec30b25d5a4ac5bf Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Mon, 10 Jun 2019 11:52:49 -0400 Subject: [PATCH 13/31] Add method stubs for Node objects --- src/Node/Directory.php | 134 +++++++++++++++++++++++++++++++++++++++++ src/Node/File.php | 68 +++++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/src/Node/Directory.php b/src/Node/Directory.php index 7f1b5b228..61e324f71 100644 --- a/src/Node/Directory.php +++ b/src/Node/Directory.php @@ -51,6 +51,16 @@ final class Directory extends AbstractNode implements \IteratorAggregate */ private $linesOfCode; + /** + * @var array + */ + private $paths; + + /** + * @var array + */ + private $branches; + /** * @var int */ @@ -106,6 +116,26 @@ final class Directory extends AbstractNode implements \IteratorAggregate */ private $numTestedFunctions = -1; + /** + * @var int + */ + private $numPaths = -1; + + /** + * @var int + */ + private $numTestedPaths = -1; + + /** + * @var int + */ + private $numBranches = -1; + + /** + * @var int + */ + private $numTestedBranches = -1; + /** * Returns the number of files in/under this node. */ @@ -160,6 +190,8 @@ public function addFile(string $name, array $coverageData, array $testData, bool $this->numExecutableLines = -1; $this->numExecutedLines = -1; + $this->numPaths = -1; + $this->numTestedPaths = -1; return $file; } @@ -424,4 +456,106 @@ public function getNumTestedFunctions(): int return $this->numTestedFunctions; } + + /** + * Returns the paths of this node. + */ + public function getPaths(): array + { + if ($this->paths === null) { + $this->paths = []; + + foreach ($this->children as $child) { + $this->paths = \array_merge( + $this->paths, + $child->getPaths() + ); + } + } + + return $this->paths; + } + + /** + * Returns the branches of this node. + */ + public function getBranches(): array + { + if ($this->branches === null) { + $this->branches = []; + + foreach ($this->children as $child) { + $this->branches = \array_merge( + $this->branches, + $child->getBranches() + ); + } + } + + return $this->branches; + } + + /** + * Returns the number of paths. + */ + public function getNumPaths(): int + { + if ($this->numPaths === -1) { + $this->numPaths = 0; + + foreach ($this->children as $child) { + $this->numMethods += $child->getNumPaths(); + } + } + + return $this->numPaths; + } + + /** + * Returns the number of tested paths. + */ + public function getNumTestedPaths(): int + { + if ($this->numTestedPaths === -1) { + $this->numTestedPaths = 0; + + foreach ($this->children as $child) { + $this->numTestedPaths += $child->getNumTestedPaths(); + } + } + + return $this->numTestedPaths; + } + + /** + * Returns the number of branches. + */ + public function getNumBranches(): int + { + if ($this->numBranches === -1) { + $this->numBranches = 0; + + foreach ($this->children as $child) { + $this->numBranches += $child->getNumBranches(); + } + } + + return $this->numBranches; + } + + /** + * Returns the number of tested branches. + */ + public function getNumTestedBranches(): int + { + if ($this->numTestedBranches === -1) { + $this->numTestedBranches = 0; + + foreach ($this->children as $child) { + $this->numTestedBranches += $child->getNumTestedBranches(); + } + } + + return $this->numTestedBranches; + } } diff --git a/src/Node/File.php b/src/Node/File.php index d20c4dda8..af63aa227 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -89,6 +89,26 @@ final class File extends AbstractNode */ private $numTestedFunctions; + /** + * @var int + */ + private $numPaths = 0; + + /** + * @var int + */ + private $numTestedPaths = 0; + + /** + * @var int + */ + private $numBranches = 0; + + /** + * @var int + */ + private $numTestedBranches = 0; + /** * @var bool */ @@ -608,4 +628,52 @@ private function newMethod(string $methodName, array $method, string $link): arr 'link' => $link . $method['startLine'], ]; } + + /** + * Returns the paths of this node. + */ + public function getPaths(): array + { + // TODO: Implement getPaths() method. + } + + /** + * Returns the branches of this node. + */ + public function getBranches(): array + { + // TODO: Implement getBranches() method. + } + + /** + * Returns the number of paths. + */ + public function getNumPaths(): int + { + // TODO: Implement getNumPaths() method. + } + + /** + * Returns the number of tested paths. + */ + public function getNumTestedPaths(): int + { + // TODO: Implement getNumTestedPaths() method. + } + + /** + * Returns the number of branches. + */ + public function getNumBranches(): int + { + // TODO: Implement getNumBranches() method. + } + + /** + * Returns the number of tested branches. + */ + public function getNumTestedBranches(): int + { + // TODO: Implement getNumTestedBranches() method. + } } From a2e60c4478cb85c94835d150e74d2db39d64965b Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Mon, 10 Jun 2019 14:04:38 -0400 Subject: [PATCH 14/31] Add path and branch support into Node/File.php --- src/Node/File.php | 321 +++++++++++++++++++++++++++++++--------------- 1 file changed, 221 insertions(+), 100 deletions(-) diff --git a/src/Node/File.php b/src/Node/File.php index af63aa227..a023c74c2 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -49,6 +49,16 @@ final class File extends AbstractNode */ private $functions = []; + /** + * @var array + */ + private $branches = []; + + /** + * @var array + */ + private $paths = []; + /** * @var array */ @@ -372,7 +382,7 @@ private function calculateStatistics(): void unset($tokens); foreach (\range(1, $this->linesOfCode['loc']) as $lineNumber) { - if (isset($this->coverageData[$lineNumber])) { + if (isset($this->coverageData['lines'][$lineNumber])) { foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { $codeUnit['executableLines']++; } @@ -381,7 +391,7 @@ private function calculateStatistics(): void $this->numExecutableLines++; - if (\count($this->coverageData[$lineNumber]) > 0) { + if (\count($this->coverageData['lines'][$lineNumber]) > 0) { foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { $codeUnit['executedLines']++; } @@ -394,7 +404,106 @@ private function calculateStatistics(): void } foreach ($this->traits as &$trait) { - foreach ($trait['methods'] as &$method) { + $this->calcAndApplyClassAggregate($trait, $trait['traitName'], $this->numTestedTraits); + } + + unset($trait); + + foreach ($this->classes as &$class) { + $this->calcAndApplyClassAggregate($class, $class['className'], $this->numTestedClasses); + } + + unset($class); + + foreach ($this->functions as &$function) { + if ($function['executableLines'] > 0) { + $function['coverage'] = ($function['executedLines'] / + $function['executableLines']) * 100; + } else { + $function['coverage'] = 100; + } + + if ($function['coverage'] === 100) { + $this->numTestedFunctions++; + } + + $function['crap'] = $this->crap( + $function['ccn'], + $function['coverage'] + ); + } + + unset($function); + + // Process Path Coverage for non-class functions + foreach ($this->functions as &$function) { + if (isset($this->coverageData['paths'][$function['functionName']])) { + $functionPaths = $this->coverageData['paths'][$function['functionName']]; + + $this->calculatePathsAggregate($functionPaths, $numExecutablePaths, $numExecutedPaths); + + $function['executablePaths'] = $numExecutablePaths; + $this->numPaths += $numExecutablePaths; + + $function['executedPaths'] = $numExecutedPaths; + $this->numTestedPaths += $numExecutablePaths; + } + + if (isset($this->coverageData['branches'][$function['functionName']])) { + $functionBranches = $this->coverageData['branches'][$function['functionName']]; + + $this->calculatePathsAggregate($functionBranches, $numExecutableBranches, $numExecutedBranches); + + $function['executableBranches'] = $numExecutableBranches; + $this->numPaths += $numExecutableBranches; + + $function['executedBranches'] = $numExecutedBranches; + $this->numTestedPaths += $numExecutedBranches; + } + } + } + + + /** + * @param array $classOrTrait + * @param string $classOrTraitName + * @param int $numTestedClassOrTrait + */ + private function calcAndApplyClassAggregate( + array &$classOrTrait, + string $classOrTraitName, + int &$numTestedClassOrTrait + ): void { + foreach ($classOrTrait['methods'] as &$method) { + $methodName = $method['methodName']; + + if (isset($this->coverageData['paths'])) { + $methodCoveragePath = $methodName; + + // @todo - Might not need this anonymous function handling... + if ($methodName === 'anonymous function') { + foreach ($this->coverageData['paths'] as $index => $path) { + if ($method['startLine'] === $path[0]['line_start']) { + $methodCoveragePath = $index; + } + } + } + + $methodCoveragePath = $classOrTraitName . '->' . $methodCoveragePath; + + if (isset($this->coverageData['paths'][$methodCoveragePath])) { + $methodPaths = $this->coverageData['paths'][$methodCoveragePath]; + $this->calculatePathsAggregate($methodPaths, $numExecutablePaths, $numExexutedPaths); + + $method['executablePaths'] = $numExecutablePaths; + $classOrTrait['executablePaths'] += $numExecutablePaths; + $this->numPaths += $numExecutablePaths; + + $method['executedPaths'] = $numExexutedPaths; + $classOrTrait['executedPaths'] += $numExexutedPaths; + $this->numTestedPaths += $numExexutedPaths; + } + if ($method['executableLines'] > 0) { $method['coverage'] = ($method['executedLines'] / $method['executableLines']) * 100; @@ -407,32 +516,37 @@ private function calculateStatistics(): void $method['coverage'] ); - $trait['ccn'] += $method['ccn']; + $classOrTrait['ccn'] += $method['ccn']; } - unset($method); - - if ($trait['executableLines'] > 0) { - $trait['coverage'] = ($trait['executedLines'] / - $trait['executableLines']) * 100; + if (isset($this->coverageData['branches'])) { + $methodCoverageBranch = $methodName; - if ($trait['coverage'] === 100) { - $this->numTestedClasses++; + // @todo - Might not need this anonymous function handling... + if ($methodName === 'anonymous function') { + foreach ($this->coverageData['branches'] as $index => $branch) { + if ($method['startLine'] === $branch[0]['line_start']) { + $methodCoverageBranch = $index; + } + } } - } else { - $trait['coverage'] = 100; - } - $trait['crap'] = $this->crap( - $trait['ccn'], - $trait['coverage'] - ); - } + $methodCoverageBranch = $classOrTraitName . '->' . $methodCoverageBranch; - unset($trait); + if (isset($this->coverageData['branches'][$methodCoverageBranch])) { + $methodPaths = $this->coverageData['branches'][$methodCoverageBranch]; + $this->calculatePathsAggregate($methodPaths, $numExecutableBranches, $numExexutedBranches); - foreach ($this->classes as &$class) { - foreach ($class['methods'] as &$method) { + $method['executableBranches'] = $numExecutableBranches; + $classOrTrait['executableBranches'] += $numExecutableBranches; + $this->numPaths += $numExecutableBranches; + + $method['executedBranches'] = $numExexutedBranches; + $classOrTrait['executedBranches'] += $numExexutedBranches; + $this->numTestedBranches += $numExexutedBranches; + } + + // @todo - Are these needed for both path and branches, or just paths? if ($method['executableLines'] > 0) { $method['coverage'] = ($method['executedLines'] / $method['executableLines']) * 100; @@ -445,47 +559,38 @@ private function calculateStatistics(): void $method['coverage'] ); - $class['ccn'] += $method['ccn']; + $classOrTrait['ccn'] += $method['ccn']; } + } + unset($method); - unset($method); - - if ($class['executableLines'] > 0) { - $class['coverage'] = ($class['executedLines'] / - $class['executableLines']) * 100; + if ($classOrTrait['executableLines'] > 0) { + $classOrTrait['coverage'] = ($classOrTrait['executedLines'] / + $classOrTrait['executableLines']) * 100; - if ($class['coverage'] === 100) { - $this->numTestedClasses++; - } - } else { - $class['coverage'] = 100; + if ($classOrTrait['coverage'] === 100) { + $numTestedClassOrTrait++; } - - $class['crap'] = $this->crap( - $class['ccn'], - $class['coverage'] - ); + } else { + $classOrTrait['coverage'] = 100; } - unset($class); + $classOrTrait['crap'] = $this->crap( + $classOrTrait['ccn'], + $classOrTrait['coverage'] + ); + } - foreach ($this->functions as &$function) { - if ($function['executableLines'] > 0) { - $function['coverage'] = ($function['executedLines'] / - $function['executableLines']) * 100; - } else { - $function['coverage'] = 100; - } + private function calculatePathsAggregate(array $paths, &$functionExecutablePaths, &$functionExecutedPaths): void + { + $functionExecutablePaths = \count($paths); - if ($function['coverage'] === 100) { - $this->numTestedFunctions++; + $functionExecutedPaths = \array_reduce( + $paths, + static function ($carry, $value) { + return ($value['hit'] > 0) ? $carry + 1 : $carry; } - - $function['crap'] = $this->crap( - $function['ccn'], - $function['coverage'] - ); - } + ); } private function processClasses(\PHP_Token_Stream $tokens): void @@ -503,16 +608,20 @@ private function processClasses(\PHP_Token_Stream $tokens): void } $this->classes[$className] = [ - 'className' => $className, - 'methods' => [], - 'startLine' => $class['startLine'], - 'executableLines' => 0, - 'executedLines' => 0, - 'ccn' => 0, - 'coverage' => 0, - 'crap' => 0, - 'package' => $class['package'], - 'link' => $link . $class['startLine'], + 'className' => $className, + 'methods' => [], + 'startLine' => $class['startLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, + 'executableBranches' => 0, + 'executedBranches' => 0, + 'ccn' => 0, + 'coverage' => 0, + 'crap' => 0, + 'package' => $class['package'], + 'link' => $link . $class['startLine'], ]; foreach ($class['methods'] as $methodName => $method) { @@ -539,16 +648,20 @@ private function processTraits(\PHP_Token_Stream $tokens): void foreach ($traits as $traitName => $trait) { $this->traits[$traitName] = [ - 'traitName' => $traitName, - 'methods' => [], - 'startLine' => $trait['startLine'], - 'executableLines' => 0, - 'executedLines' => 0, - 'ccn' => 0, - 'coverage' => 0, - 'crap' => 0, - 'package' => $trait['package'], - 'link' => $link . $trait['startLine'], + 'traitName' => $traitName, + 'methods' => [], + 'startLine' => $trait['startLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, + 'executableBranches' => 0, + 'executedBranches' => 0, + 'ccn' => 0, + 'coverage' => 0, + 'crap' => 0, + 'package' => $trait['package'], + 'link' => $link . $trait['startLine'], ]; foreach ($trait['methods'] as $methodName => $method) { @@ -579,15 +692,19 @@ private function processFunctions(\PHP_Token_Stream $tokens): void } $this->functions[$functionName] = [ - 'functionName' => $functionName, - 'signature' => $function['signature'], - 'startLine' => $function['startLine'], - 'executableLines' => 0, - 'executedLines' => 0, - 'ccn' => $function['ccn'], - 'coverage' => 0, - 'crap' => 0, - 'link' => $link . $function['startLine'], + 'functionName' => $functionName, + 'signature' => $function['signature'], + 'startLine' => $function['startLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, + 'executableBranches' => 0, + 'executedBranches' => 0, + 'ccn' => $function['ccn'], + 'coverage' => 0, + 'crap' => 0, + 'link' => $link . $function['startLine'], ]; foreach (\range($function['startLine'], $function['endLine']) as $lineNumber) { @@ -615,17 +732,21 @@ private function crap(int $ccn, float $coverage): string private function newMethod(string $methodName, array $method, string $link): array { return [ - 'methodName' => $methodName, - 'visibility' => $method['visibility'], - 'signature' => $method['signature'], - 'startLine' => $method['startLine'], - 'endLine' => $method['endLine'], - 'executableLines' => 0, - 'executedLines' => 0, - 'ccn' => $method['ccn'], - 'coverage' => 0, - 'crap' => 0, - 'link' => $link . $method['startLine'], + 'methodName' => $methodName, + 'visibility' => $method['visibility'], + 'signature' => $method['signature'], + 'startLine' => $method['startLine'], + 'endLine' => $method['endLine'], + 'executableLines' => 0, + 'executedLines' => 0, + 'executablePaths' => 0, + 'executedPaths' => 0, + 'executableBranches' => 0, + 'executedBranches' => 0, + 'ccn' => $method['ccn'], + 'coverage' => 0, + 'crap' => 0, + 'link' => $link . $method['startLine'], ]; } @@ -634,7 +755,7 @@ private function newMethod(string $methodName, array $method, string $link): arr */ public function getPaths(): array { - // TODO: Implement getPaths() method. + return $this->paths; } /** @@ -642,7 +763,7 @@ public function getPaths(): array */ public function getBranches(): array { - // TODO: Implement getBranches() method. + return $this->branches; } /** @@ -650,7 +771,7 @@ public function getBranches(): array */ public function getNumPaths(): int { - // TODO: Implement getNumPaths() method. + return $this->numPaths; } /** @@ -658,7 +779,7 @@ public function getNumPaths(): int */ public function getNumTestedPaths(): int { - // TODO: Implement getNumTestedPaths() method. + return $this->numTestedPaths; } /** @@ -666,7 +787,7 @@ public function getNumTestedPaths(): int */ public function getNumBranches(): int { - // TODO: Implement getNumBranches() method. + return $this->numBranches; } /** @@ -674,6 +795,6 @@ public function getNumBranches(): int */ public function getNumTestedBranches(): int { - // TODO: Implement getNumTestedBranches() method. + return $this->numTestedBranches; } } From f623d3394076ab77bbead9a88c33acd8277fa008 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Mon, 10 Jun 2019 14:13:56 -0400 Subject: [PATCH 15/31] Cleanup branch calculation code --- src/Node/File.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/Node/File.php b/src/Node/File.php index a023c74c2..506bf613d 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -545,21 +545,6 @@ private function calcAndApplyClassAggregate( $classOrTrait['executedBranches'] += $numExexutedBranches; $this->numTestedBranches += $numExexutedBranches; } - - // @todo - Are these needed for both path and branches, or just paths? - if ($method['executableLines'] > 0) { - $method['coverage'] = ($method['executedLines'] / - $method['executableLines']) * 100; - } else { - $method['coverage'] = 100; - } - - $method['crap'] = $this->crap( - $method['ccn'], - $method['coverage'] - ); - - $classOrTrait['ccn'] += $method['ccn']; } } unset($method); From 5ab4472bfd613142f127ed88c90a262b4871edd4 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Mon, 10 Jun 2019 14:45:23 -0400 Subject: [PATCH 16/31] Fix errant assignment to wrong properties --- src/Node/File.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Node/File.php b/src/Node/File.php index 506bf613d..96fff9b64 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -455,10 +455,10 @@ private function calculateStatistics(): void $this->calculatePathsAggregate($functionBranches, $numExecutableBranches, $numExecutedBranches); $function['executableBranches'] = $numExecutableBranches; - $this->numPaths += $numExecutableBranches; + $this->numBranches += $numExecutableBranches; $function['executedBranches'] = $numExecutedBranches; - $this->numTestedPaths += $numExecutedBranches; + $this->numTestedBranches += $numExecutedBranches; } } } @@ -539,7 +539,7 @@ private function calcAndApplyClassAggregate( $method['executableBranches'] = $numExecutableBranches; $classOrTrait['executableBranches'] += $numExecutableBranches; - $this->numPaths += $numExecutableBranches; + $this->numBranches += $numExecutableBranches; $method['executedBranches'] = $numExexutedBranches; $classOrTrait['executedBranches'] += $numExexutedBranches; From 35466533c18b70483cb46b20bb65564dee0498c0 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Mon, 10 Jun 2019 14:56:21 -0400 Subject: [PATCH 17/31] Fix one more erroneous property assignment --- src/Node/Directory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Node/Directory.php b/src/Node/Directory.php index 61e324f71..bc9908ff9 100644 --- a/src/Node/Directory.php +++ b/src/Node/Directory.php @@ -504,7 +504,7 @@ public function getNumPaths(): int $this->numPaths = 0; foreach ($this->children as $child) { - $this->numMethods += $child->getNumPaths(); + $this->numPaths += $child->getNumPaths(); } } From e8a14578c511ff6abf0951609db3f492ed5a32c9 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Mon, 10 Jun 2019 14:43:06 -0400 Subject: [PATCH 18/31] Add overall report --- src/Report/Text.php | 54 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/src/Report/Text.php b/src/Report/Text.php index 9593a2285..480309d30 100644 --- a/src/Report/Text.php +++ b/src/Report/Text.php @@ -84,12 +84,14 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $report = $coverage->getReport(); $colors = [ - 'header' => '', - 'classes' => '', - 'methods' => '', - 'lines' => '', - 'reset' => '', - 'eol' => '', + 'header' => '', + 'classes' => '', + 'methods' => '', + 'lines' => '', + 'branches' => '', + 'paths' => '', + 'reset' => '', + 'eol' => '', ]; if ($showColors) { @@ -108,13 +110,23 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $report->getNumExecutableLines() ); + $colors['branches'] = $this->getCoverageColor( + $report->getNumTestedBranches(), + $report->getNumBranches() + ); + + $colors['paths'] = $this->getCoverageColor( + $report->getNumTestedPaths(), + $report->getNumPaths() + ); + $colors['reset'] = self::COLOR_RESET; $colors['header'] = self::COLOR_HEADER; $colors['eol'] = self::COLOR_EOL; } $classes = \sprintf( - ' Classes: %6s (%d/%d)', + ' Classes: %6s (%d/%d)', Util::percent( $report->getNumTestedClassesAndTraits(), $report->getNumClassesAndTraits(), @@ -125,7 +137,7 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin ); $methods = \sprintf( - ' Methods: %6s (%d/%d)', + ' Methods: %6s (%d/%d)', Util::percent( $report->getNumTestedMethods(), $report->getNumMethods(), @@ -136,7 +148,7 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin ); $lines = \sprintf( - ' Lines: %6s (%d/%d)', + ' Lines: %6s (%d/%d)', Util::percent( $report->getNumExecutedLines(), $report->getNumExecutableLines(), @@ -146,6 +158,28 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $report->getNumExecutableLines() ); + $branches = \sprintf( + ' Branches: %6s (%d/%d)', + Util::percent( + $report->getNumTestedBranches(), + $report->getNumBranches(), + true + ), + $report->getNumTestedBranches(), + $report->getNumBranches() + ); + + $paths = \sprintf( + ' Paths: %6s (%d/%d)', + Util::percent( + $report->getNumTestedPaths(), + $report->getNumPaths(), + true + ), + $report->getNumTestedPaths(), + $report->getNumPaths() + ); + $padding = \max(\array_map('strlen', [$classes, $methods, $lines])); if ($this->showOnlySummary) { @@ -166,6 +200,8 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $output .= $this->format($colors['classes'], $padding, $classes); $output .= $this->format($colors['methods'], $padding, $methods); $output .= $this->format($colors['lines'], $padding, $lines); + $output .= $this->format($colors['branches'], $padding, $branches); + $output .= $this->format($colors['paths'], $padding, $paths); if ($this->showOnlySummary) { return $output . \PHP_EOL; From 815cc9e7873f8a54871ff3ec5b866abd7a8574bc Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Mon, 10 Jun 2019 15:17:30 -0400 Subject: [PATCH 19/31] Finish per-class report --- src/Report/Text.php | 66 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/src/Report/Text.php b/src/Report/Text.php index 480309d30..75fbca709 100644 --- a/src/Report/Text.php +++ b/src/Report/Text.php @@ -209,6 +209,11 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $classCoverage = []; + $maxMethods = 0; + $maxLines = 0; + $maxBranches = 0; + $maxPaths = 0; + foreach ($report as $item) { if (!$item instanceof File) { continue; @@ -221,21 +226,50 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $coveredClassStatements = 0; $coveredMethods = 0; $classMethods = 0; + $classPaths = 0; + $coveredClassPaths = 0; + $classBranches = 0; + $coveredClassBranches = 0; foreach ($class['methods'] as $method) { - if ($method['executableLines'] == 0) { + if ($method['executableLines'] === 0) { continue; } $classMethods++; $classStatements += $method['executableLines']; $coveredClassStatements += $method['executedLines']; + $classPaths += $method['executablePaths']; + $coveredClassPaths += $method['executedPaths']; + $classBranches += $method['executableBranches']; + $coveredClassBranches += $method['executedBranches']; - if ($method['coverage'] == 100) { + if ($method['coverage'] === 100) { $coveredMethods++; } } + $maxMethods = \max( + $maxMethods, + \strlen((string) $classMethods), + \strlen((string) $coveredMethods) + ); + $maxLines = \max( + $maxLines, + \strlen((string) $classStatements), + \strlen((string) $coveredClassStatements) + ); + $maxBranches = \max( + $maxBranches, + \strlen((string) $classBranches), + \strlen((string) $coveredClassBranches) + ); + $maxPaths = \max( + $maxPaths, + \strlen((string) $classPaths), + \strlen((string) $coveredClassPaths) + ); + $namespace = ''; if (!empty($class['package']['namespace'])) { @@ -251,27 +285,37 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin 'methodCount' => $classMethods, 'statementsCovered' => $coveredClassStatements, 'statementCount' => $classStatements, + 'pathsCovered' => $coveredClassPaths, + 'pathCount' => $classPaths, + 'branchesCovered' => $coveredClassBranches, + 'branchCount' => $classBranches, ]; } } \ksort($classCoverage); - $methodColor = ''; - $linesColor = ''; - $resetColor = ''; + $methodColor = ''; + $linesColor = ''; + $resetColor = ''; + $pathsColor = ''; + $branchesColor = ''; foreach ($classCoverage as $fullQualifiedPath => $classInfo) { - if ($this->showUncoveredFiles || $classInfo['statementsCovered'] != 0) { + if ($this->showUncoveredFiles || $classInfo['statementsCovered'] !== 0) { if ($showColors) { - $methodColor = $this->getCoverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); - $linesColor = $this->getCoverageColor($classInfo['statementsCovered'], $classInfo['statementCount']); - $resetColor = $colors['reset']; + $methodColor = $this->getCoverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); + $linesColor = $this->getCoverageColor($classInfo['statementsCovered'], $classInfo['statementCount']); + $branchesColor = $this->getCoverageColor($classInfo['branchesCovered'], $classInfo['branchCount']); + $pathsColor = $this->getCoverageColor($classInfo['pathsCovered'], $classInfo['pathCount']); + $resetColor = $colors['reset']; } $output .= \PHP_EOL . $fullQualifiedPath . \PHP_EOL - . ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], 2) . $resetColor . ' ' - . ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], 3) . $resetColor; + . ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], $maxMethods) . $resetColor . ' ' + . ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], $maxLines) . $resetColor . ' ' + . ' ' . $branchesColor . 'Branches: ' . $this->printCoverageCounts($classInfo['branchesCovered'], $classInfo['branchCount'], $maxBranches) . $resetColor . ' ' + . ' ' . $pathsColor . 'Paths: ' . $this->printCoverageCounts($classInfo['pathsCovered'], $classInfo['pathCount'], $maxPaths) . $resetColor; } } From 91039108d61bceb8ca26e5d24f231442cb8d0aa6 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Mon, 10 Jun 2019 15:18:11 -0400 Subject: [PATCH 20/31] Make the percent Util class return the same fixed-width empty string if the percent will be 0 --- src/Util.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util.php b/src/Util.php index ee8894c14..831b0f544 100644 --- a/src/Util.php +++ b/src/Util.php @@ -20,7 +20,7 @@ final class Util public static function percent(float $a, float $b, bool $asString = false, bool $fixedWidth = false) { if ($asString && $b == 0) { - return ''; + return $fixedWidth ? ' ' : ''; } $percent = 100; From 2b1d909543ec3b31e9a443fb130f0eead5faf5b2 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Tue, 11 Jun 2019 08:49:45 -0400 Subject: [PATCH 21/31] Add branch & path coverage flag to Text reporter --- src/Report/Text.php | 133 +++++++++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 52 deletions(-) diff --git a/src/Report/Text.php b/src/Report/Text.php index 75fbca709..fc6913aeb 100644 --- a/src/Report/Text.php +++ b/src/Report/Text.php @@ -70,12 +70,18 @@ final class Text */ private $showOnlySummary; - public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, bool $showUncoveredFiles = false, bool $showOnlySummary = false) + /** + * @var bool + */ + private $determineBranchCoverage; + + public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, bool $showUncoveredFiles = false, bool $showOnlySummary = false, bool $determineBranchCoverage = false) { - $this->lowUpperBound = $lowUpperBound; - $this->highLowerBound = $highLowerBound; - $this->showUncoveredFiles = $showUncoveredFiles; - $this->showOnlySummary = $showOnlySummary; + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + $this->showUncoveredFiles = $showUncoveredFiles; + $this->showOnlySummary = $showOnlySummary; + $this->determineBranchCoverage = $determineBranchCoverage; } public function process(CodeCoverage $coverage, bool $showColors = false): string @@ -110,15 +116,17 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $report->getNumExecutableLines() ); - $colors['branches'] = $this->getCoverageColor( - $report->getNumTestedBranches(), - $report->getNumBranches() - ); + if ($this->determineBranchCoverage) { + $colors['branches'] = $this->getCoverageColor( + $report->getNumTestedBranches(), + $report->getNumBranches() + ); - $colors['paths'] = $this->getCoverageColor( - $report->getNumTestedPaths(), - $report->getNumPaths() - ); + $colors['paths'] = $this->getCoverageColor( + $report->getNumTestedPaths(), + $report->getNumPaths() + ); + } $colors['reset'] = self::COLOR_RESET; $colors['header'] = self::COLOR_HEADER; @@ -158,27 +166,32 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $report->getNumExecutableLines() ); - $branches = \sprintf( - ' Branches: %6s (%d/%d)', - Util::percent( + $paths = ''; + $branches = ''; + + if ($this->determineBranchCoverage) { + $branches = \sprintf( + ' Branches: %6s (%d/%d)', + Util::percent( + $report->getNumTestedBranches(), + $report->getNumBranches(), + true + ), $report->getNumTestedBranches(), - $report->getNumBranches(), - true - ), - $report->getNumTestedBranches(), - $report->getNumBranches() - ); + $report->getNumBranches() + ); - $paths = \sprintf( - ' Paths: %6s (%d/%d)', - Util::percent( + $paths = \sprintf( + ' Paths: %6s (%d/%d)', + Util::percent( + $report->getNumTestedPaths(), + $report->getNumPaths(), + true + ), $report->getNumTestedPaths(), - $report->getNumPaths(), - true - ), - $report->getNumTestedPaths(), - $report->getNumPaths() - ); + $report->getNumPaths() + ); + } $padding = \max(\array_map('strlen', [$classes, $methods, $lines])); @@ -200,8 +213,11 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $output .= $this->format($colors['classes'], $padding, $classes); $output .= $this->format($colors['methods'], $padding, $methods); $output .= $this->format($colors['lines'], $padding, $lines); - $output .= $this->format($colors['branches'], $padding, $branches); - $output .= $this->format($colors['paths'], $padding, $paths); + + if ($this->determineBranchCoverage) { + $output .= $this->format($colors['branches'], $padding, $branches); + $output .= $this->format($colors['paths'], $padding, $paths); + } if ($this->showOnlySummary) { return $output . \PHP_EOL; @@ -239,10 +255,13 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin $classMethods++; $classStatements += $method['executableLines']; $coveredClassStatements += $method['executedLines']; - $classPaths += $method['executablePaths']; - $coveredClassPaths += $method['executedPaths']; - $classBranches += $method['executableBranches']; - $coveredClassBranches += $method['executedBranches']; + + if ($this->determineBranchCoverage) { + $classPaths += $method['executablePaths']; + $coveredClassPaths += $method['executedPaths']; + $classBranches += $method['executableBranches']; + $coveredClassBranches += $method['executedBranches']; + } if ($method['coverage'] === 100) { $coveredMethods++; @@ -259,16 +278,19 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin \strlen((string) $classStatements), \strlen((string) $coveredClassStatements) ); - $maxBranches = \max( - $maxBranches, - \strlen((string) $classBranches), - \strlen((string) $coveredClassBranches) - ); - $maxPaths = \max( - $maxPaths, - \strlen((string) $classPaths), - \strlen((string) $coveredClassPaths) - ); + + if ($this->determineBranchCoverage) { + $maxBranches = \max( + $maxBranches, + \strlen((string) $classBranches), + \strlen((string) $coveredClassBranches) + ); + $maxPaths = \max( + $maxPaths, + \strlen((string) $classPaths), + \strlen((string) $coveredClassPaths) + ); + } $namespace = ''; @@ -306,16 +328,23 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin if ($showColors) { $methodColor = $this->getCoverageColor($classInfo['methodsCovered'], $classInfo['methodCount']); $linesColor = $this->getCoverageColor($classInfo['statementsCovered'], $classInfo['statementCount']); - $branchesColor = $this->getCoverageColor($classInfo['branchesCovered'], $classInfo['branchCount']); - $pathsColor = $this->getCoverageColor($classInfo['pathsCovered'], $classInfo['pathCount']); + + if ($this->determineBranchCoverage) { + $branchesColor = $this->getCoverageColor($classInfo['branchesCovered'], $classInfo['branchCount']); + $pathsColor = $this->getCoverageColor($classInfo['pathsCovered'], $classInfo['pathCount']); + } $resetColor = $colors['reset']; } $output .= \PHP_EOL . $fullQualifiedPath . \PHP_EOL . ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], $maxMethods) . $resetColor . ' ' - . ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], $maxLines) . $resetColor . ' ' - . ' ' . $branchesColor . 'Branches: ' . $this->printCoverageCounts($classInfo['branchesCovered'], $classInfo['branchCount'], $maxBranches) . $resetColor . ' ' - . ' ' . $pathsColor . 'Paths: ' . $this->printCoverageCounts($classInfo['pathsCovered'], $classInfo['pathCount'], $maxPaths) . $resetColor; + . ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], $maxLines) . $resetColor; + + if ($this->determineBranchCoverage) { + $output .= '' + . ' ' . $branchesColor . 'Branches: ' . $this->printCoverageCounts($classInfo['branchesCovered'], $classInfo['branchCount'], $maxBranches) . $resetColor . ' ' + . ' ' . $pathsColor . 'Paths: ' . $this->printCoverageCounts($classInfo['pathsCovered'], $classInfo['pathCount'], $maxPaths) . $resetColor; + } } } From 079736ac9afb8b57f219c4815250330059de00fd Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Tue, 11 Jun 2019 08:59:27 -0400 Subject: [PATCH 22/31] Add flag to TextReport constructor --- phpunit.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpunit.xml b/phpunit.xml index 37e22199f..fe570a8a2 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,6 +8,10 @@ tests/tests + + + + src From 02a1cd2e59039a4668bded85a6123e8a29a8c44f Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Tue, 11 Jun 2019 15:29:17 -0400 Subject: [PATCH 23/31] Fix some of the coverage issues --- src/CodeCoverage.php | 9 ++++++--- src/Driver/Xdebug.php | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index e4cc9a9f1..944b1e936 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -618,7 +618,10 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data): void foreach ($fileData['lines'] as $lineNumber => $flag) { if ($flag === Driver::LINE_NOT_EXECUTABLE) { $this->data[$file]['lines'][$lineNumber] = null; + } else { + $this->addCoverageLinePathCovered($file, $lineNumber, false); } + } foreach ($fileData['functions'] as $functionName => $functionData) { @@ -1203,9 +1206,9 @@ private function initializeData(): void continue; } - foreach (\array_keys($fileCoverage) as $key) { - if ($fileCoverage[$key] === Driver::LINE_EXECUTED) { - $fileCoverage[$key] = Driver::LINE_NOT_EXECUTED; + foreach (\array_keys($fileCoverage['lines']) as $key) { + if ($fileCoverage['lines'][$key] === Driver::LINE_EXECUTED) { + $fileCoverage['lines'][$key] = Driver::LINE_NOT_EXECUTED; } } diff --git a/src/Driver/Xdebug.php b/src/Driver/Xdebug.php index f89d25315..34c51b48c 100644 --- a/src/Driver/Xdebug.php +++ b/src/Driver/Xdebug.php @@ -66,7 +66,7 @@ public function start(bool $determineUnusedAndDead = true): void } if ($this->determineBranchCoverage) { - $flag = XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE | XDEBUG_CC_BRANCH_CHECK; + $flag |= XDEBUG_CC_BRANCH_CHECK; } \xdebug_start_code_coverage($flag); From 85548b83482f7c449d62847784e169331c67578f Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Wed, 12 Jun 2019 08:29:00 -0400 Subject: [PATCH 24/31] Add flag to TextReport constructor Fix additional covered lines functionality --- phpunit.xml | 4 ---- src/CodeCoverage.php | 2 -- src/Node/File.php | 45 ++++++++++++++++++++++++-------------------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index fe570a8a2..37e22199f 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,10 +8,6 @@ tests/tests - - - - src diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 944b1e936..8c60ea9f2 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -625,7 +625,6 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data): void } foreach ($fileData['functions'] as $functionName => $functionData) { - // @todo - should this have a helper to merge covered paths? $this->data[$file]['paths'][$functionName] = $functionData['paths']; foreach ($functionData['branches'] as $branchIndex => $branchData) { @@ -839,7 +838,6 @@ private function addUncoveredFilesFromWhitelist(): void for ($line = 1; $line <= $lines; $line++) { $data[$uncoveredFile]['lines'][$line] = Driver::LINE_NOT_EXECUTED; } - // @todo - do the same here with functions and paths } $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST'); diff --git a/src/Node/File.php b/src/Node/File.php index 96fff9b64..496261bf2 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -382,16 +382,21 @@ private function calculateStatistics(): void unset($tokens); foreach (\range(1, $this->linesOfCode['loc']) as $lineNumber) { - if (isset($this->coverageData['lines'][$lineNumber])) { - foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { - $codeUnit['executableLines']++; - } + // Check to see if we've identified this line as executed, not executed, or not executable + if (\array_key_exists($lineNumber, $this->coverageData['lines'])) { + // If the element is null, that indicates this line is not executable + if ($this->coverageData['lines'][$lineNumber] !== null) { + foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { + $codeUnit['executableLines']++; + } + + unset($codeUnit); - unset($codeUnit); + $this->numExecutableLines++; + } - $this->numExecutableLines++; - if (\count($this->coverageData['lines'][$lineNumber]) > 0) { + if ($this->coverageData['lines'][$lineNumber]['pathCovered'] === true) { foreach ($this->codeUnitsByLine[$lineNumber] as &$codeUnit) { $codeUnit['executedLines']++; } @@ -477,6 +482,19 @@ private function calcAndApplyClassAggregate( foreach ($classOrTrait['methods'] as &$method) { $methodName = $method['methodName']; + if ($method['executableLines'] > 0) { + $method['coverage'] = ($method['executedLines'] / $method['executableLines']) * 100; + } else { + $method['coverage'] = 100; + } + + $method['crap'] = $this->crap( + $method['ccn'], + $method['coverage'] + ); + + $classOrTrait['ccn'] += $method['ccn']; + if (isset($this->coverageData['paths'])) { $methodCoveragePath = $methodName; @@ -504,19 +522,6 @@ private function calcAndApplyClassAggregate( $this->numTestedPaths += $numExexutedPaths; } - if ($method['executableLines'] > 0) { - $method['coverage'] = ($method['executedLines'] / - $method['executableLines']) * 100; - } else { - $method['coverage'] = 100; - } - - $method['crap'] = $this->crap( - $method['ccn'], - $method['coverage'] - ); - - $classOrTrait['ccn'] += $method['ccn']; } if (isset($this->coverageData['branches'])) { From 4166db9b8848a300f63c7c874a9e5313b3e8df0e Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Wed, 12 Jun 2019 08:52:41 -0400 Subject: [PATCH 25/31] Fix XDebug filters --- src/Driver/Xdebug.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Driver/Xdebug.php b/src/Driver/Xdebug.php index 34c51b48c..f89d25315 100644 --- a/src/Driver/Xdebug.php +++ b/src/Driver/Xdebug.php @@ -66,7 +66,7 @@ public function start(bool $determineUnusedAndDead = true): void } if ($this->determineBranchCoverage) { - $flag |= XDEBUG_CC_BRANCH_CHECK; + $flag = XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE | XDEBUG_CC_BRANCH_CHECK; } \xdebug_start_code_coverage($flag); From 8d4aa8e9d45402dee0f618ca15cf54c8199c9b47 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Tue, 11 Jun 2019 08:36:13 -0400 Subject: [PATCH 26/31] First pass at adding in feature flags for path and branch analysis --- src/CodeCoverage.php | 74 ++++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 8c60ea9f2..e04946383 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -359,16 +359,18 @@ public function append(array $data, $id = null, bool $append = true, $linesToBeC } } - foreach ($fileData['functions'] as $function => $functionCoverage) { - foreach ($functionCoverage['branches'] as $branch => $branchCoverage) { - if (($branchCoverage['hit'] ?? 0) === 1) { - $this->addCoverageBranchHit($file, $function, $branch, $branchCoverage['hit'] ?? 0); - $this->addCoverageBranchTest($file, $function, $branch, $id); + if ($this->determineBranchCoverage) { + foreach ($fileData['functions'] as $function => $functionCoverage) { + foreach ($functionCoverage['branches'] as $branch => $branchCoverage) { + if (($branchCoverage['hit'] ?? 0) === 1) { + $this->addCoverageBranchHit($file, $function, $branch, $branchCoverage['hit'] ?? 0); + $this->addCoverageBranchTest($file, $function, $branch, $id); + } } - } - foreach ($functionCoverage['paths'] as $path => $pathCoverage) { - $this->addCoveragePathHit($file, $function, $path, $pathCoverage['hit'] ?? 0); + foreach ($functionCoverage['paths'] as $path => $pathCoverage) { + $this->addCoveragePathHit($file, $function, $path, $pathCoverage['hit'] ?? 0); + } } } } @@ -621,20 +623,21 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data): void } else { $this->addCoverageLinePathCovered($file, $lineNumber, false); } - } - foreach ($fileData['functions'] as $functionName => $functionData) { - $this->data[$file]['paths'][$functionName] = $functionData['paths']; + if ($this->determineBranchCoverage) { + foreach ($fileData['functions'] as $functionName => $functionData) { + $this->data[$file]['paths'][$functionName] = $functionData['paths']; - foreach ($functionData['branches'] as $branchIndex => $branchData) { - $this->addCoverageBranchHit($file, $functionName, $branchIndex, $branchData['hit']); - $this->addCoverageBranchLineStart($file, $functionName, $branchIndex, $branchData['line_start']); - $this->addCoverageBranchLineEnd($file, $functionName, $branchIndex, $branchData['line_end']); + foreach ($functionData['branches'] as $branchIndex => $branchData) { + $this->addCoverageBranchHit($file, $functionName, $branchIndex, $branchData['hit']); + $this->addCoverageBranchLineStart($file, $functionName, $branchIndex, $branchData['line_start']); + $this->addCoverageBranchLineEnd($file, $functionName, $branchIndex, $branchData['line_end']); - for ($curLine = $branchData['line_start']; $curLine < $branchData['line_end']; $curLine++) { - if (isset($this->data[$file]['lines'][$curLine])) { - $this->addCoverageLinePathCovered($file, $curLine, (bool) $branchData['hit']); + for ($curLine = $branchData['line_start']; $curLine < $branchData['line_end']; $curLine++) { + if (isset($this->data[$file]['lines'][$curLine])) { + $this->addCoverageLinePathCovered($file, $curLine, (bool) $branchData['hit']); + } } } } @@ -645,11 +648,17 @@ private function initializeFilesThatAreSeenTheFirstTime(array $data): void private function initializeFileCoverageData(string $file): void { if (!isset($this->data[$file]) && $this->filter->isFile($file)) { - $this->data[$file] = [ - 'lines' => [], - 'branches' => [], - 'paths' => [], - ]; + $default = ['lines' => []]; + + if ($this->determineBranchCoverage) { + $default = [ + 'lines' => [], + 'branches' => [], + 'paths' => [], + ]; + } + + $this->data[$file] = $default; } } @@ -688,6 +697,9 @@ private function addCoverageLineTest(string $file, int $lineNumber, string $test private function addCoverageBranchHit(string $file, string $functionName, int $branchIndex, int $hit): void { $this->initializeFileCoverageData($file); + if (!$this->determineBranchCoverage) { + return; + } if (!\array_key_exists($functionName, $this->data[$file]['branches'])) { $this->data[$file]['branches'][$functionName] = []; @@ -716,6 +728,10 @@ private function addCoverageBranchLineStart( ): void { $this->initializeFileCoverageData($file); + if (!$this->determineBranchCoverage) { + return; + } + if (!\array_key_exists($functionName, $this->data[$file]['branches'])) { $this->data[$file]['branches'][$functionName] = []; } @@ -740,6 +756,10 @@ private function addCoverageBranchLineEnd( ): void { $this->initializeFileCoverageData($file); + if (!$this->determineBranchCoverage) { + return; + } + if (!\array_key_exists($functionName, $this->data[$file]['branches'])) { $this->data[$file]['branches'][$functionName] = []; } @@ -764,6 +784,10 @@ private function addCoverageBranchTest( ): void { $this->initializeFileCoverageData($file); + if (!$this->determineBranchCoverage) { + return; + } + if (!\array_key_exists($functionName, $this->data[$file]['branches'])) { $this->data[$file]['branches'][$functionName] = []; } @@ -790,6 +814,10 @@ private function addCoveragePathHit( ): void { $this->initializeFileCoverageData($file); + if (!$this->determineBranchCoverage) { + return; + } + if (!\array_key_exists($functionName, $this->data[$file]['paths'])) { $this->data[$file]['paths'][$functionName] = []; } From 0f1bddd9697b50f442b7891cf8c00ee8bb918ca9 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Wed, 12 Jun 2019 16:14:48 -0400 Subject: [PATCH 27/31] Implement setDetermineBranchCoverage method in PCOV driver --- src/Driver/PCOV.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Driver/PCOV.php b/src/Driver/PCOV.php index 7a6a3b6c8..87c6295c9 100644 --- a/src/Driver/PCOV.php +++ b/src/Driver/PCOV.php @@ -9,6 +9,8 @@ */ namespace SebastianBergmann\CodeCoverage\Driver; +use SebastianBergmann\CodeCoverage\RuntimeException; + /** * Driver for PCOV code coverage functionality. * @@ -16,6 +18,14 @@ */ final class PCOV implements Driver { + /** + * Specify that branch coverage should be included with collected code coverage information. + */ + public function setDetermineBranchCoverage(bool $flag): void + { + throw new RuntimeException('Branch coverage is not supported in PHPDBG'); + } + /** * Start collection of code coverage information. */ From 428876f608d7b671563bbfa2e9612a62d366f5ee Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Thu, 13 Jun 2019 09:23:30 -0400 Subject: [PATCH 28/31] Make Text non-breaking --- src/Report/Text.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Report/Text.php b/src/Report/Text.php index fc6913aeb..1a35cbe0b 100644 --- a/src/Report/Text.php +++ b/src/Report/Text.php @@ -73,15 +73,14 @@ final class Text /** * @var bool */ - private $determineBranchCoverage; + private $determineBranchCoverage = false; - public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, bool $showUncoveredFiles = false, bool $showOnlySummary = false, bool $determineBranchCoverage = false) + public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, bool $showUncoveredFiles = false, bool $showOnlySummary = false) { - $this->lowUpperBound = $lowUpperBound; - $this->highLowerBound = $highLowerBound; - $this->showUncoveredFiles = $showUncoveredFiles; - $this->showOnlySummary = $showOnlySummary; - $this->determineBranchCoverage = $determineBranchCoverage; + $this->lowUpperBound = $lowUpperBound; + $this->highLowerBound = $highLowerBound; + $this->showUncoveredFiles = $showUncoveredFiles; + $this->showOnlySummary = $showOnlySummary; } public function process(CodeCoverage $coverage, bool $showColors = false): string @@ -351,6 +350,11 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin return $output . \PHP_EOL; } + public function setDetermineBranchCoverage(bool $determineBranchCoverage): void + { + $this->determineBranchCoverage = $determineBranchCoverage; + } + private function getCoverageColor(int $numberOfCoveredElements, int $totalNumberOfElements): string { $coverage = Util::percent( From d894967aa1bfc1ca4c0d7c5d6d9db7b09a1fe4de Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Thu, 13 Jun 2019 10:08:38 -0400 Subject: [PATCH 29/31] Start HTML reporter branch support --- src/Report/Html/Facade.php | 13 +++++++++++++ src/Report/Html/Renderer.php | 10 ++++++++++ src/Report/Html/Renderer/File.php | 30 ++++++++++++++++-------------- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/Report/Html/Facade.php b/src/Report/Html/Facade.php index 318b49a5a..5120fa72b 100644 --- a/src/Report/Html/Facade.php +++ b/src/Report/Html/Facade.php @@ -38,6 +38,11 @@ final class Facade */ private $highLowerBound; + /** + * @var bool + */ + private $determineBranchCoverage = false; + public function __construct(int $lowUpperBound = 50, int $highLowerBound = 90, string $generator = '') { $this->generator = $generator; @@ -69,6 +74,7 @@ public function process(CodeCoverage $coverage, string $target): void $this->lowUpperBound, $this->highLowerBound ); + $dashboard->setDetermineBranchCoverage($this->determineBranchCoverage); $directory = new Directory( $this->templatePath, @@ -77,6 +83,7 @@ public function process(CodeCoverage $coverage, string $target): void $this->lowUpperBound, $this->highLowerBound ); + $directory->setDetermineBranchCoverage($this->determineBranchCoverage); $file = new File( $this->templatePath, @@ -85,6 +92,7 @@ public function process(CodeCoverage $coverage, string $target): void $this->lowUpperBound, $this->highLowerBound ); + $file->setDetermineBranchCoverage($this->determineBranchCoverage); $directory->render($report, $target . 'index.html'); $dashboard->render($report, $target . 'dashboard.html'); @@ -113,6 +121,11 @@ public function process(CodeCoverage $coverage, string $target): void $this->copyFiles($target); } + public function setDetermineBranchCoverage(bool $determineBranchCoverage): void + { + $this->determineBranchCoverage = $determineBranchCoverage; + } + /** * @throws RuntimeException */ diff --git a/src/Report/Html/Renderer.php b/src/Report/Html/Renderer.php index 2a9024c0e..956fd3390 100644 --- a/src/Report/Html/Renderer.php +++ b/src/Report/Html/Renderer.php @@ -50,6 +50,11 @@ abstract class Renderer */ protected $version; + /** + * @var bool + */ + protected $determineBranchCoverage = false; + public function __construct(string $templatePath, string $generator, string $date, int $lowUpperBound, int $highLowerBound) { $this->templatePath = $templatePath; @@ -60,6 +65,11 @@ public function __construct(string $templatePath, string $generator, string $dat $this->version = Version::id(); } + public function setDetermineBranchCoverage(bool $determineBranchCoverage): void + { + $this->determineBranchCoverage = $determineBranchCoverage; + } + protected function renderItemTemplate(\Text_Template $template, array $data): string { $numSeparator = ' / '; diff --git a/src/Report/Html/Renderer/File.php b/src/Report/Html/Renderer/File.php index f0604bf21..83cef865e 100644 --- a/src/Report/Html/Renderer/File.php +++ b/src/Report/Html/Renderer/File.php @@ -54,20 +54,22 @@ protected function renderItems(FileNode $node): string $items = $this->renderItemTemplate( $template, [ - 'name' => 'Total', - 'numClasses' => $node->getNumClassesAndTraits(), - 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), - 'numMethods' => $node->getNumFunctionsAndMethods(), - 'numTestedMethods' => $node->getNumTestedFunctionsAndMethods(), - 'linesExecutedPercent' => $node->getLineExecutedPercent(false), - 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), - 'numExecutedLines' => $node->getNumExecutedLines(), - 'numExecutableLines' => $node->getNumExecutableLines(), - 'testedMethodsPercent' => $node->getTestedFunctionsAndMethodsPercent(false), - 'testedMethodsPercentAsString' => $node->getTestedFunctionsAndMethodsPercent(), - 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), - 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), - 'crap' => 'CRAP', + 'name' => 'Total', + 'numClasses' => $node->getNumClassesAndTraits(), + 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), + 'numMethods' => $node->getNumFunctionsAndMethods(), + 'numTestedMethods' => $node->getNumTestedFunctionsAndMethods(), + 'linesExecutedPercent' => $node->getLineExecutedPercent(false), + 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), + 'numExecutedLines' => $node->getNumExecutedLines(), + 'numExecutableLines' => $node->getNumExecutableLines(), + 'testedMethodsPercent' => $node->getTestedFunctionsAndMethodsPercent(false), + 'testedMethodsPercentAsString' => $node->getTestedFunctionsAndMethodsPercent(), + 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), + 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), + 'testedBranchesPercent' => $node->getTestedBranchesPercent(false), + 'testedBranchesPercentAsString' => $node->getTestedBranchesPercent(), + 'crap' => 'CRAP', ] ); From e7480c9568dce1d5f1cfea92265e3a4284595f6a Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Thu, 13 Jun 2019 12:32:08 -0400 Subject: [PATCH 30/31] Finish HTML reporter functionality --- src/Node/File.php | 3 +- src/Report/Html/Renderer.php | 47 ++++--- src/Report/Html/Renderer/Directory.php | 34 +++-- src/Report/Html/Renderer/File.php | 124 +++++++++++++----- .../Renderer/Template/directory.html.dist | 3 +- .../Template/directory_item.html.dist | 3 + .../Html/Renderer/Template/file.html.dist | 3 +- .../Renderer/Template/file_item.html.dist | 3 + .../Renderer/Template/method_item.html.dist | 3 + 9 files changed, 157 insertions(+), 66 deletions(-) diff --git a/src/Node/File.php b/src/Node/File.php index 496261bf2..c7886c29a 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -579,7 +579,8 @@ private function calculatePathsAggregate(array $paths, &$functionExecutablePaths $paths, static function ($carry, $value) { return ($value['hit'] > 0) ? $carry + 1 : $carry; - } + }, + 0 ); } diff --git a/src/Report/Html/Renderer.php b/src/Report/Html/Renderer.php index 956fd3390..22678a8ef 100644 --- a/src/Report/Html/Renderer.php +++ b/src/Report/Html/Renderer.php @@ -122,23 +122,40 @@ protected function renderItemTemplate(\Text_Template $template, array $data): st $data['linesExecutedPercentAsString'] = 'n/a'; } + $branchesLevel = ''; + $branchesNumber = '0' . $numSeparator . '0'; + $branchesBar = ''; + + // @todo - Remove the null coalesce + if (($data['numExecutableBranches'] ?? 0) > 0) { + $branchesLevel = $this->getColorLevel($data['testedBranchesPercent']); + $branchesBar = $this->getCoverageBar($data['testedBranchesPercent']); + $branchesNumber = $data['numExecutedBranches'] . $numSeparator . $data['numExecutableBranches']; + } else { + $data['testedBranchesPercentAsString'] = 'n/a'; + } + $template->setVar( [ - 'icon' => $data['icon'] ?? '', - 'crap' => $data['crap'] ?? '', - 'name' => $data['name'], - 'lines_bar' => $linesBar, - 'lines_executed_percent' => $data['linesExecutedPercentAsString'], - 'lines_level' => $linesLevel, - 'lines_number' => $linesNumber, - 'methods_bar' => $methodsBar, - 'methods_tested_percent' => $data['testedMethodsPercentAsString'], - 'methods_level' => $methodsLevel, - 'methods_number' => $methodsNumber, - 'classes_bar' => $classesBar, - 'classes_tested_percent' => $data['testedClassesPercentAsString'] ?? '', - 'classes_level' => $classesLevel, - 'classes_number' => $classesNumber, + 'icon' => $data['icon'] ?? '', + 'crap' => $data['crap'] ?? '', + 'name' => $data['name'], + 'lines_bar' => $linesBar, + 'lines_executed_percent' => $data['linesExecutedPercentAsString'], + 'lines_level' => $linesLevel, + 'lines_number' => $linesNumber, + 'methods_bar' => $methodsBar, + 'methods_tested_percent' => $data['testedMethodsPercentAsString'], + 'methods_level' => $methodsLevel, + 'methods_number' => $methodsNumber, + 'classes_bar' => $classesBar, + 'classes_tested_percent' => $data['testedClassesPercentAsString'] ?? '', + 'classes_level' => $classesLevel, + 'classes_number' => $classesNumber, + 'branches_bar' => $branchesBar, + 'branches_tested_percent' => $data['testedBranchesPercentAsString'] ?? '', + 'branches_level' => $branchesLevel, + 'branches_number' => $branchesNumber, ] ); diff --git a/src/Report/Html/Renderer/Directory.php b/src/Report/Html/Renderer/Directory.php index c2f086079..b44cb3201 100644 --- a/src/Report/Html/Renderer/Directory.php +++ b/src/Report/Html/Renderer/Directory.php @@ -47,21 +47,29 @@ public function render(DirectoryNode $node, string $file): void $template->renderTo($file); } - protected function renderItem(Node $node, bool $total = false): string + private function renderItem(Node $node, bool $total = false): string { $data = [ - 'numClasses' => $node->getNumClassesAndTraits(), - 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), - 'numMethods' => $node->getNumFunctionsAndMethods(), - 'numTestedMethods' => $node->getNumTestedFunctionsAndMethods(), - 'linesExecutedPercent' => $node->getLineExecutedPercent(false), - 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), - 'numExecutedLines' => $node->getNumExecutedLines(), - 'numExecutableLines' => $node->getNumExecutableLines(), - 'testedMethodsPercent' => $node->getTestedFunctionsAndMethodsPercent(false), - 'testedMethodsPercentAsString' => $node->getTestedFunctionsAndMethodsPercent(), - 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), - 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), + 'numClasses' => $node->getNumClassesAndTraits(), + 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), + 'numMethods' => $node->getNumFunctionsAndMethods(), + 'numTestedMethods' => $node->getNumTestedFunctionsAndMethods(), + 'linesExecutedPercent' => $node->getLineExecutedPercent(false), + 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), + 'numExecutedLines' => $node->getNumExecutedLines(), + 'numExecutableLines' => $node->getNumExecutableLines(), + 'testedMethodsPercent' => $node->getTestedFunctionsAndMethodsPercent(false), + 'testedMethodsPercentAsString' => $node->getTestedFunctionsAndMethodsPercent(), + 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), + 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), + 'testedBranchesPercent' => $node->getTestedBranchesPercent(false), + 'testedBranchesPercentAsString' => $node->getTestedBranchesPercent(), + 'testedPathsPercent' => $node->getTestedPathsPercent(false), + 'testedPathsPercentAsString' => $node->getTestedPathsPercent(), + 'numExecutablePaths' => $node->getNumPaths(), + 'numExecutedPaths' => $node->getNumTestedPaths(), + 'numExecutableBranches' => $node->getNumBranches(), + 'numExecutedBranches' => $node->getNumTestedBranches(), ]; if ($total) { diff --git a/src/Report/Html/Renderer/File.php b/src/Report/Html/Renderer/File.php index 83cef865e..eb91e48a5 100644 --- a/src/Report/Html/Renderer/File.php +++ b/src/Report/Html/Renderer/File.php @@ -69,6 +69,12 @@ protected function renderItems(FileNode $node): string 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), 'testedBranchesPercent' => $node->getTestedBranchesPercent(false), 'testedBranchesPercentAsString' => $node->getTestedBranchesPercent(), + 'testedPathsPercent' => $node->getTestedPathsPercent(false), + 'testedPathsPercentAsString' => $node->getTestedPathsPercent(), + 'numExecutablePaths' => $node->getNumPaths(), + 'numExecutedPaths' => $node->getNumTestedPaths(), + 'numExecutableBranches' => $node->getNumBranches(), + 'numExecutedBranches' => $node->getNumTestedBranches(), 'crap' => 'CRAP', ] ); @@ -123,47 +129,73 @@ protected function renderTraitOrClassItems(array $items, \Text_Template $templat $item['executableLines'], true ); + $testedBranchesPercentAsString = Util::percent( + $item['executedBranches'], + $item['executableBranches'], + true + ); + $testedPathsPercentAsString = Util::percent( + $item['executedPaths'], + $item['executablePaths'], + true + ); } else { - $numClasses = 'n/a'; - $numTestedClasses = 'n/a'; - $linesExecutedPercentAsString = 'n/a'; + $numClasses = 'n/a'; + $numTestedClasses = 'n/a'; + $linesExecutedPercentAsString = 'n/a'; + $testedBranchesPercentAsString = 'n/a'; + $testedPathsPercentAsString = 'n/a'; } $buffer .= $this->renderItemTemplate( $template, [ - 'name' => $this->abbreviateClassName($name), - 'numClasses' => $numClasses, - 'numTestedClasses' => $numTestedClasses, - 'numMethods' => $numMethods, - 'numTestedMethods' => $numTestedMethods, - 'linesExecutedPercent' => Util::percent( + 'name' => $this->abbreviateClassName($name), + 'numClasses' => $numClasses, + 'numTestedClasses' => $numTestedClasses, + 'numMethods' => $numMethods, + 'numTestedMethods' => $numTestedMethods, + 'linesExecutedPercent' => Util::percent( $item['executedLines'], $item['executableLines'], false ), - 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, - 'numExecutedLines' => $item['executedLines'], - 'numExecutableLines' => $item['executableLines'], - 'testedMethodsPercent' => Util::percent( + 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, + 'numExecutedLines' => $item['executedLines'], + 'numExecutableLines' => $item['executableLines'], + 'testedMethodsPercent' => Util::percent( $numTestedMethods, $numMethods ), - 'testedMethodsPercentAsString' => Util::percent( + 'testedMethodsPercentAsString' => Util::percent( $numTestedMethods, $numMethods, true ), - 'testedClassesPercent' => Util::percent( - $numTestedMethods == $numMethods ? 1 : 0, + 'testedClassesPercent' => Util::percent( + $numTestedMethods === $numMethods ? 1 : 0, 1 ), 'testedClassesPercentAsString' => Util::percent( - $numTestedMethods == $numMethods ? 1 : 0, + $numTestedMethods === $numMethods ? 1 : 0, 1, true ), - 'crap' => $item['crap'], + 'crap' => $item['crap'], + 'testedBranchesPercent' => Util::percent( + $item['executedBranches'], + $item['executableBranches'] + ), + 'testedBranchesPercentAsString' => $testedBranchesPercentAsString, + 'testedPathsPercent' => Util::percent( + $item['executedPaths'], + $item['executablePaths'] + ), + 'testedPathsPercentAsString' => $testedPathsPercentAsString, + 'numExecutablePaths' => $item['executablePaths'], + 'numExecutedPaths' => $item['executedPaths'], + 'numExecutableBranches' => $item['executableBranches'], + 'numExecutedBranches' => $item['executedBranches'], ] ); @@ -213,36 +245,58 @@ protected function renderFunctionOrMethodItem(\Text_Template $template, array $i return $this->renderItemTemplate( $template, [ - 'name' => \sprintf( + 'name' => \sprintf( '%s%s', $indent, $item['startLine'], \htmlspecialchars($item['signature'], $this->htmlSpecialCharsFlags), $item['functionName'] ?? $item['methodName'] ), - 'numMethods' => $numMethods, - 'numTestedMethods' => $numTestedMethods, - 'linesExecutedPercent' => Util::percent( + 'numMethods' => $numMethods, + 'numTestedMethods' => $numTestedMethods, + 'linesExecutedPercent' => Util::percent( $item['executedLines'], $item['executableLines'] ), - 'linesExecutedPercentAsString' => Util::percent( + 'linesExecutedPercentAsString' => Util::percent( $item['executedLines'], $item['executableLines'], true ), - 'numExecutedLines' => $item['executedLines'], - 'numExecutableLines' => $item['executableLines'], - 'testedMethodsPercent' => Util::percent( + 'numExecutedLines' => $item['executedLines'], + 'numExecutableLines' => $item['executableLines'], + 'testedMethodsPercent' => Util::percent( $numTestedMethods, 1 ), - 'testedMethodsPercentAsString' => Util::percent( + 'testedMethodsPercentAsString' => Util::percent( $numTestedMethods, 1, true ), - 'crap' => $item['crap'], + 'crap' => $item['crap'], + 'testedBranchesPercent' => Util::percent( + $item['executedBranches'], + $item['executableBranches'] + ), + 'testedBranchesPercentAsString' => Util::percent( + $item['executedBranches'], + $item['executableBranches'], + true + ), + 'testedPathsPercent' => Util::percent( + $item['executedPaths'], + $item['executablePaths'] + ), + 'testedPathsPercentAsString' => Util::percent( + $item['executedPaths'], + $item['executablePaths'], + true + ), + 'numExecutablePaths' => $item['executablePaths'], + 'numExecutedPaths' => $item['executedPaths'], + 'numExecutableBranches' => $item['executableBranches'], + 'numExecutedBranches' => $item['executedBranches'], ] ); } @@ -260,12 +314,12 @@ protected function renderSource(FileNode $node): string $popoverContent = ''; $popoverTitle = ''; - if (\array_key_exists($i, $coverageData)) { - $numTests = ($coverageData[$i] ? \count($coverageData[$i]) : 0); + if (\array_key_exists($i, $coverageData['lines'])) { + $numTests = ($coverageData['lines'][$i] ? \count($coverageData['lines'][$i]['tests']) : 0); - if ($coverageData[$i] === null) { + if ($coverageData['lines'][$i]['tests'] === null) { $trClass = ' class="warning"'; - } elseif ($numTests == 0) { + } elseif ($numTests === 0) { $trClass = ' class="danger"'; } else { $lineCss = 'covered-by-large-tests'; @@ -277,10 +331,10 @@ protected function renderSource(FileNode $node): string $popoverTitle = '1 test covers line ' . $i; } - foreach ($coverageData[$i] as $test) { - if ($lineCss == 'covered-by-large-tests' && $testData[$test]['size'] == 'medium') { + foreach ($coverageData['lines'][$i]['tests'] as $test) { + if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') { $lineCss = 'covered-by-medium-tests'; - } elseif ($testData[$test]['size'] == 'small') { + } elseif ($testData[$test]['size'] === 'small') { $lineCss = 'covered-by-small-tests'; } diff --git a/src/Report/Html/Renderer/Template/directory.html.dist b/src/Report/Html/Renderer/Template/directory.html.dist index a263463a6..3d957580f 100644 --- a/src/Report/Html/Renderer/Template/directory.html.dist +++ b/src/Report/Html/Renderer/Template/directory.html.dist @@ -29,11 +29,12 @@   -
Code Coverage
+
Code Coverage
 
Lines
+
Branches
Functions and Methods
Classes and Traits
diff --git a/src/Report/Html/Renderer/Template/directory_item.html.dist b/src/Report/Html/Renderer/Template/directory_item.html.dist index f6941a437..7491840ad 100644 --- a/src/Report/Html/Renderer/Template/directory_item.html.dist +++ b/src/Report/Html/Renderer/Template/directory_item.html.dist @@ -3,6 +3,9 @@ {{lines_bar}}
{{lines_executed_percent}}
{{lines_number}}
+ {{branches_bar}} +
{{branches_tested_percent}}
+
{{branches_number}}
{{methods_bar}}
{{methods_tested_percent}}
{{methods_number}}
diff --git a/src/Report/Html/Renderer/Template/file.html.dist b/src/Report/Html/Renderer/Template/file.html.dist index 0ca65edf9..07108516a 100644 --- a/src/Report/Html/Renderer/Template/file.html.dist +++ b/src/Report/Html/Renderer/Template/file.html.dist @@ -29,12 +29,13 @@   -
Code Coverage
+
Code Coverage
 
Classes and Traits
Functions and Methods
+
Branches
Lines
diff --git a/src/Report/Html/Renderer/Template/file_item.html.dist b/src/Report/Html/Renderer/Template/file_item.html.dist index dc754b3c6..837c95a11 100644 --- a/src/Report/Html/Renderer/Template/file_item.html.dist +++ b/src/Report/Html/Renderer/Template/file_item.html.dist @@ -7,6 +7,9 @@
{{methods_tested_percent}}
{{methods_number}}
{{crap}} + {{branches_bar}} +
{{branches_tested_percent}}
+
{{branches_number}}
{{lines_bar}}
{{lines_executed_percent}}
{{lines_number}}
diff --git a/src/Report/Html/Renderer/Template/method_item.html.dist b/src/Report/Html/Renderer/Template/method_item.html.dist index d8890ed27..b4f2d59cf 100644 --- a/src/Report/Html/Renderer/Template/method_item.html.dist +++ b/src/Report/Html/Renderer/Template/method_item.html.dist @@ -4,6 +4,9 @@
{{methods_tested_percent}}
{{methods_number}}
{{crap}} + {{branches_bar}} +
{{branches_tested_percent}}
+
{{branches_number}}
{{lines_bar}}
{{lines_executed_percent}}
{{lines_number}}
From 31ab8097fba3dd801efa18ffc513e9b5e99ecad4 Mon Sep 17 00:00:00 2001 From: Joe Rieger Date: Thu, 13 Jun 2019 15:07:49 -0400 Subject: [PATCH 31/31] Make template files honor branch coverage feature flag --- src/Report/Html/Renderer/Directory.php | 14 +++- src/Report/Html/Renderer/File.php | 22 ++++-- .../Renderer/Template/directory.html.dist | 3 +- .../Template/directory_branch.html.dist | 61 ++++++++++++++++ .../Template/directory_item.html.dist | 3 - .../Template/directory_item_branch.html.dist | 16 ++++ .../Html/Renderer/Template/file.html.dist | 3 +- .../Renderer/Template/file_branch.html.dist | 73 +++++++++++++++++++ .../Renderer/Template/file_item.html.dist | 3 - .../Template/file_item_branch.html.dist | 17 +++++ .../Renderer/Template/method_item.html.dist | 3 - .../Template/method_item_branch.html.dist | 14 ++++ 12 files changed, 210 insertions(+), 22 deletions(-) create mode 100644 src/Report/Html/Renderer/Template/directory_branch.html.dist create mode 100644 src/Report/Html/Renderer/Template/directory_item_branch.html.dist create mode 100644 src/Report/Html/Renderer/Template/file_branch.html.dist create mode 100644 src/Report/Html/Renderer/Template/file_item_branch.html.dist create mode 100644 src/Report/Html/Renderer/Template/method_item_branch.html.dist diff --git a/src/Report/Html/Renderer/Directory.php b/src/Report/Html/Renderer/Directory.php index b44cb3201..f31d894b3 100644 --- a/src/Report/Html/Renderer/Directory.php +++ b/src/Report/Html/Renderer/Directory.php @@ -23,7 +23,12 @@ final class Directory extends Renderer */ public function render(DirectoryNode $node, string $file): void { - $template = new \Text_Template($this->templatePath . 'directory.html', '{{', '}}'); + $templateName = $this->templatePath . 'directory.html'; + if ($this->determineBranchCoverage) { + $templateName = $this->templatePath . 'directory_branch.html'; + } + + $template = new \Text_Template($templateName, '{{', '}}'); $this->setCommonTemplateVariables($template, $node); @@ -98,8 +103,13 @@ private function renderItem(Node $node, bool $total = false): string } } + $templateName = $this->templatePath . 'directory_item.html'; + if ($this->determineBranchCoverage) { + $templateName = $this->templatePath . 'directory_item_branch.html'; + } + return $this->renderItemTemplate( - new \Text_Template($this->templatePath . 'directory_item.html', '{{', '}}'), + new \Text_Template($templateName, '{{', '}}'), $data ); } diff --git a/src/Report/Html/Renderer/File.php b/src/Report/Html/Renderer/File.php index eb91e48a5..e9a0b85b9 100644 --- a/src/Report/Html/Renderer/File.php +++ b/src/Report/Html/Renderer/File.php @@ -27,7 +27,12 @@ final class File extends Renderer */ public function render(FileNode $node, string $file): void { - $template = new \Text_Template($this->templatePath . 'file.html', '{{', '}}'); + $templateName = $this->templatePath . 'file.html'; + if ($this->determineBranchCoverage) { + $templateName = $this->templatePath . 'file_branch.html'; + } + + $template = new \Text_Template($templateName, '{{', '}}'); $template->setVar( [ @@ -43,13 +48,16 @@ public function render(FileNode $node, string $file): void protected function renderItems(FileNode $node): string { - $template = new \Text_Template($this->templatePath . 'file_item.html', '{{', '}}'); + $fileTemplateName = $this->templatePath . 'file_item.html'; + $methodItemTemplateName = $this->templatePath . 'method_item.html'; - $methodItemTemplate = new \Text_Template( - $this->templatePath . 'method_item.html', - '{{', - '}}' - ); + if ($this->determineBranchCoverage) { + $fileTemplateName = $this->templatePath . 'file_item_branch.html'; + $methodItemTemplateName = $this->templatePath . 'method_item_branch.html'; + } + + $template = new \Text_Template($fileTemplateName, '{{', '}}'); + $methodItemTemplate = new \Text_Template($methodItemTemplateName, '{{', '}}'); $items = $this->renderItemTemplate( $template, diff --git a/src/Report/Html/Renderer/Template/directory.html.dist b/src/Report/Html/Renderer/Template/directory.html.dist index 3d957580f..a263463a6 100644 --- a/src/Report/Html/Renderer/Template/directory.html.dist +++ b/src/Report/Html/Renderer/Template/directory.html.dist @@ -29,12 +29,11 @@   -
Code Coverage
+
Code Coverage
 
Lines
-
Branches
Functions and Methods
Classes and Traits
diff --git a/src/Report/Html/Renderer/Template/directory_branch.html.dist b/src/Report/Html/Renderer/Template/directory_branch.html.dist new file mode 100644 index 000000000..3d957580f --- /dev/null +++ b/src/Report/Html/Renderer/Template/directory_branch.html.dist @@ -0,0 +1,61 @@ + + + + + Code Coverage for {{full_path}} + + + + + + + +
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + +{{items}} + +
 
Code Coverage
 
Lines
Branches
Functions and Methods
Classes and Traits
+
+
+
+

Legend

+

+ Low: 0% to {{low_upper_bound}}% + Medium: {{low_upper_bound}}% to {{high_lower_bound}}% + High: {{high_lower_bound}}% to 100% +

+

+ Generated by php-code-coverage {{version}} using {{runtime}}{{generator}} at {{date}}. +

+
+
+ + diff --git a/src/Report/Html/Renderer/Template/directory_item.html.dist b/src/Report/Html/Renderer/Template/directory_item.html.dist index 7491840ad..f6941a437 100644 --- a/src/Report/Html/Renderer/Template/directory_item.html.dist +++ b/src/Report/Html/Renderer/Template/directory_item.html.dist @@ -3,9 +3,6 @@ {{lines_bar}}
{{lines_executed_percent}}
{{lines_number}}
- {{branches_bar}} -
{{branches_tested_percent}}
-
{{branches_number}}
{{methods_bar}}
{{methods_tested_percent}}
{{methods_number}}
diff --git a/src/Report/Html/Renderer/Template/directory_item_branch.html.dist b/src/Report/Html/Renderer/Template/directory_item_branch.html.dist new file mode 100644 index 000000000..7491840ad --- /dev/null +++ b/src/Report/Html/Renderer/Template/directory_item_branch.html.dist @@ -0,0 +1,16 @@ + + {{icon}}{{name}} + {{lines_bar}} +
{{lines_executed_percent}}
+
{{lines_number}}
+ {{branches_bar}} +
{{branches_tested_percent}}
+
{{branches_number}}
+ {{methods_bar}} +
{{methods_tested_percent}}
+
{{methods_number}}
+ {{classes_bar}} +
{{classes_tested_percent}}
+
{{classes_number}}
+ + diff --git a/src/Report/Html/Renderer/Template/file.html.dist b/src/Report/Html/Renderer/Template/file.html.dist index 07108516a..0ca65edf9 100644 --- a/src/Report/Html/Renderer/Template/file.html.dist +++ b/src/Report/Html/Renderer/Template/file.html.dist @@ -29,13 +29,12 @@   -
Code Coverage
+
Code Coverage
 
Classes and Traits
Functions and Methods
-
Branches
Lines
diff --git a/src/Report/Html/Renderer/Template/file_branch.html.dist b/src/Report/Html/Renderer/Template/file_branch.html.dist new file mode 100644 index 000000000..07108516a --- /dev/null +++ b/src/Report/Html/Renderer/Template/file_branch.html.dist @@ -0,0 +1,73 @@ + + + + + Code Coverage for {{full_path}} + + + + + + + +
+
+
+
+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + +{{items}} + +
 
Code Coverage
 
Classes and Traits
Functions and Methods
Branches
Lines
+
+ + +{{lines}} + +
+ +
+ + + + + + diff --git a/src/Report/Html/Renderer/Template/file_item.html.dist b/src/Report/Html/Renderer/Template/file_item.html.dist index 837c95a11..dc754b3c6 100644 --- a/src/Report/Html/Renderer/Template/file_item.html.dist +++ b/src/Report/Html/Renderer/Template/file_item.html.dist @@ -7,9 +7,6 @@
{{methods_tested_percent}}
{{methods_number}}
{{crap}} - {{branches_bar}} -
{{branches_tested_percent}}
-
{{branches_number}}
{{lines_bar}}
{{lines_executed_percent}}
{{lines_number}}
diff --git a/src/Report/Html/Renderer/Template/file_item_branch.html.dist b/src/Report/Html/Renderer/Template/file_item_branch.html.dist new file mode 100644 index 000000000..837c95a11 --- /dev/null +++ b/src/Report/Html/Renderer/Template/file_item_branch.html.dist @@ -0,0 +1,17 @@ + + {{name}} + {{classes_bar}} +
{{classes_tested_percent}}
+
{{classes_number}}
+ {{methods_bar}} +
{{methods_tested_percent}}
+
{{methods_number}}
+ {{crap}} + {{branches_bar}} +
{{branches_tested_percent}}
+
{{branches_number}}
+ {{lines_bar}} +
{{lines_executed_percent}}
+
{{lines_number}}
+ + diff --git a/src/Report/Html/Renderer/Template/method_item.html.dist b/src/Report/Html/Renderer/Template/method_item.html.dist index b4f2d59cf..d8890ed27 100644 --- a/src/Report/Html/Renderer/Template/method_item.html.dist +++ b/src/Report/Html/Renderer/Template/method_item.html.dist @@ -4,9 +4,6 @@
{{methods_tested_percent}}
{{methods_number}}
{{crap}} - {{branches_bar}} -
{{branches_tested_percent}}
-
{{branches_number}}
{{lines_bar}}
{{lines_executed_percent}}
{{lines_number}}
diff --git a/src/Report/Html/Renderer/Template/method_item_branch.html.dist b/src/Report/Html/Renderer/Template/method_item_branch.html.dist new file mode 100644 index 000000000..b4f2d59cf --- /dev/null +++ b/src/Report/Html/Renderer/Template/method_item_branch.html.dist @@ -0,0 +1,14 @@ + + {{name}} + {{methods_bar}} +
{{methods_tested_percent}}
+
{{methods_number}}
+ {{crap}} + {{branches_bar}} +
{{branches_tested_percent}}
+
{{branches_number}}
+ {{lines_bar}} +
{{lines_executed_percent}}
+
{{lines_number}}
+ +