Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php declare(strict_types=1);/** This file is part of phpunit/php-code-coverage.** (c) Sebastian Bergmann <sebastian@phpunit.de>** For the full copyright and license information, please view the LICENSE* file that was distributed with this source code.*/namespace SebastianBergmann\CodeCoverage\Report;use const PHP_EOL;use function array_map;use function date;use function ksort;use function max;use function sprintf;use function str_pad;use function strlen;use SebastianBergmann\CodeCoverage\CodeCoverage;use SebastianBergmann\CodeCoverage\Node\File;use SebastianBergmann\CodeCoverage\Util\Percentage;final class Text{/*** @var string*/private const COLOR_GREEN = "\x1b[30;42m";/*** @var string*/private const COLOR_YELLOW = "\x1b[30;43m";/*** @var string*/private const COLOR_RED = "\x1b[37;41m";/*** @var string*/private const COLOR_HEADER = "\x1b[1;37;40m";/*** @var string*/private const COLOR_RESET = "\x1b[0m";/*** @var string*/private const COLOR_EOL = "\x1b[2K";/*** @var int*/private $lowUpperBound;/*** @var int*/private $highLowerBound;/*** @var bool*/private $showUncoveredFiles;/*** @var bool*/private $showOnlySummary;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;}public function process(CodeCoverage $coverage, bool $showColors = false): string{$hasBranchCoverage = !empty($coverage->getData(true)->functionCoverage());$output = PHP_EOL . PHP_EOL;$report = $coverage->getReport();$colors = ['header' => '','classes' => '','methods' => '','lines' => '','branches' => '','paths' => '','reset' => '','eol' => '',];if ($showColors) {$colors['classes'] = $this->coverageColor($report->numberOfTestedClassesAndTraits(),$report->numberOfClassesAndTraits());$colors['methods'] = $this->coverageColor($report->numberOfTestedMethods(),$report->numberOfMethods());$colors['lines'] = $this->coverageColor($report->numberOfExecutedLines(),$report->numberOfExecutableLines());$colors['branches'] = $this->coverageColor($report->numberOfExecutedBranches(),$report->numberOfExecutableBranches());$colors['paths'] = $this->coverageColor($report->numberOfExecutedPaths(),$report->numberOfExecutablePaths());$colors['reset'] = self::COLOR_RESET;$colors['header'] = self::COLOR_HEADER;$colors['eol'] = self::COLOR_EOL;}$classes = sprintf(' Classes: %6s (%d/%d)',Percentage::fromFractionAndTotal($report->numberOfTestedClassesAndTraits(),$report->numberOfClassesAndTraits())->asString(),$report->numberOfTestedClassesAndTraits(),$report->numberOfClassesAndTraits());$methods = sprintf(' Methods: %6s (%d/%d)',Percentage::fromFractionAndTotal($report->numberOfTestedMethods(),$report->numberOfMethods(),)->asString(),$report->numberOfTestedMethods(),$report->numberOfMethods());$paths = '';$branches = '';if ($hasBranchCoverage) {$paths = sprintf(' Paths: %6s (%d/%d)',Percentage::fromFractionAndTotal($report->numberOfExecutedPaths(),$report->numberOfExecutablePaths(),)->asString(),$report->numberOfExecutedPaths(),$report->numberOfExecutablePaths());$branches = sprintf(' Branches: %6s (%d/%d)',Percentage::fromFractionAndTotal($report->numberOfExecutedBranches(),$report->numberOfExecutableBranches(),)->asString(),$report->numberOfExecutedBranches(),$report->numberOfExecutableBranches());}$lines = sprintf(' Lines: %6s (%d/%d)',Percentage::fromFractionAndTotal($report->numberOfExecutedLines(),$report->numberOfExecutableLines(),)->asString(),$report->numberOfExecutedLines(),$report->numberOfExecutableLines());$padding = max(array_map('strlen', [$classes, $methods, $lines]));if ($this->showOnlySummary) {$title = 'Code Coverage Report Summary:';$padding = max($padding, strlen($title));$output .= $this->format($colors['header'], $padding, $title);} else {$date = date(' Y-m-d H:i:s');$title = 'Code Coverage Report:';$output .= $this->format($colors['header'], $padding, $title);$output .= $this->format($colors['header'], $padding, $date);$output .= $this->format($colors['header'], $padding, '');$output .= $this->format($colors['header'], $padding, ' Summary:');}$output .= $this->format($colors['classes'], $padding, $classes);$output .= $this->format($colors['methods'], $padding, $methods);if ($hasBranchCoverage) {$output .= $this->format($colors['paths'], $padding, $paths);$output .= $this->format($colors['branches'], $padding, $branches);}$output .= $this->format($colors['lines'], $padding, $lines);if ($this->showOnlySummary) {return $output . PHP_EOL;}$classCoverage = [];foreach ($report as $item) {if (!$item instanceof File) {continue;}$classes = $item->classesAndTraits();foreach ($classes as $className => $class) {$classExecutableLines = 0;$classExecutedLines = 0;$classExecutableBranches = 0;$classExecutedBranches = 0;$classExecutablePaths = 0;$classExecutedPaths = 0;$coveredMethods = 0;$classMethods = 0;foreach ($class['methods'] as $method) {if ($method['executableLines'] == 0) {continue;}$classMethods++;$classExecutableLines += $method['executableLines'];$classExecutedLines += $method['executedLines'];$classExecutableBranches += $method['executableBranches'];$classExecutedBranches += $method['executedBranches'];$classExecutablePaths += $method['executablePaths'];$classExecutedPaths += $method['executedPaths'];if ($method['coverage'] == 100) {$coveredMethods++;}}$classCoverage[$className] = ['namespace' => $class['namespace'],'className' => $className,'methodsCovered' => $coveredMethods,'methodCount' => $classMethods,'statementsCovered' => $classExecutedLines,'statementCount' => $classExecutableLines,'branchesCovered' => $classExecutedBranches,'branchesCount' => $classExecutableBranches,'pathsCovered' => $classExecutedPaths,'pathsCount' => $classExecutablePaths,];}}ksort($classCoverage);$methodColor = '';$pathsColor = '';$branchesColor = '';$linesColor = '';$resetColor = '';foreach ($classCoverage as $fullQualifiedPath => $classInfo) {if ($this->showUncoveredFiles || $classInfo['statementsCovered'] != 0) {if ($showColors) {$methodColor = $this->coverageColor($classInfo['methodsCovered'], $classInfo['methodCount']);$pathsColor = $this->coverageColor($classInfo['pathsCovered'], $classInfo['pathsCount']);$branchesColor = $this->coverageColor($classInfo['branchesCovered'], $classInfo['branchesCount']);$linesColor = $this->coverageColor($classInfo['statementsCovered'], $classInfo['statementCount']);$resetColor = $colors['reset'];}$output .= PHP_EOL . $fullQualifiedPath . PHP_EOL. ' ' . $methodColor . 'Methods: ' . $this->printCoverageCounts($classInfo['methodsCovered'], $classInfo['methodCount'], 2) . $resetColor . ' ';if ($hasBranchCoverage) {$output .= ' ' . $pathsColor . 'Paths: ' . $this->printCoverageCounts($classInfo['pathsCovered'], $classInfo['pathsCount'], 3) . $resetColor . ' '. ' ' . $branchesColor . 'Branches: ' . $this->printCoverageCounts($classInfo['branchesCovered'], $classInfo['branchesCount'], 3) . $resetColor . ' ';}$output .= ' ' . $linesColor . 'Lines: ' . $this->printCoverageCounts($classInfo['statementsCovered'], $classInfo['statementCount'], 3) . $resetColor;}}return $output . PHP_EOL;}private function coverageColor(int $numberOfCoveredElements, int $totalNumberOfElements): string{$coverage = Percentage::fromFractionAndTotal($numberOfCoveredElements,$totalNumberOfElements);if ($coverage->asFloat() >= $this->highLowerBound) {return self::COLOR_GREEN;}if ($coverage->asFloat() > $this->lowUpperBound) {return self::COLOR_YELLOW;}return self::COLOR_RED;}private function printCoverageCounts(int $numberOfCoveredElements, int $totalNumberOfElements, int $precision): string{$format = '%' . $precision . 's';return Percentage::fromFractionAndTotal($numberOfCoveredElements,$totalNumberOfElements)->asFixedWidthString() .' (' . sprintf($format, $numberOfCoveredElements) . '/' .sprintf($format, $totalNumberOfElements) . ')';}/*** @param false|string $string*/private function format(string $color, int $padding, $string): string{$reset = $color ? self::COLOR_RESET : '';return $color . str_pad((string) $string, $padding) . $reset . PHP_EOL;}}