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/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..22678a8ef 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 = ' / '; @@ -112,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..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); @@ -47,21 +52,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) { @@ -90,8 +103,13 @@ protected 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 f0604bf21..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,31 +48,42 @@ 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, [ - '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(), + 'testedPathsPercent' => $node->getTestedPathsPercent(false), + 'testedPathsPercentAsString' => $node->getTestedPathsPercent(), + 'numExecutablePaths' => $node->getNumPaths(), + 'numExecutedPaths' => $node->getNumTestedPaths(), + 'numExecutableBranches' => $node->getNumBranches(), + 'numExecutedBranches' => $node->getNumTestedBranches(), + 'crap' => 'CRAP', ] ); @@ -121,47 +137,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'], ] ); @@ -211,36 +253,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'], ] ); } @@ -258,12 +322,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'; @@ -275,10 +339,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_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 |
+ |||||||||||
| + | Lines |
+ Branches |
+ Functions and Methods |
+ Classes and Traits |
+ ||||||||
| + | Code Coverage |
+ ||||||||||||
| + | Classes and Traits |
+ Functions and Methods |
+ Branches |
+ Lines |
+ |||||||||