Chris@14: Chris@14: * Chris@14: * For the full copyright and license information, please view the LICENSE Chris@14: * file that was distributed with this source code. Chris@14: */ Chris@14: Chris@14: namespace SebastianBergmann\CodeCoverage\Report; Chris@14: Chris@14: use SebastianBergmann\CodeCoverage\CodeCoverage; Chris@14: use SebastianBergmann\CodeCoverage\Node\File; Chris@14: use SebastianBergmann\CodeCoverage\RuntimeException; Chris@14: Chris@14: /** Chris@14: * Generates a Clover XML logfile from a code coverage object. Chris@14: */ Chris@14: class Clover Chris@14: { Chris@14: /** Chris@14: * @param CodeCoverage $coverage Chris@14: * @param string $target Chris@14: * @param string $name Chris@14: * Chris@14: * @return string Chris@14: * Chris@14: * @throws \SebastianBergmann\CodeCoverage\RuntimeException Chris@14: */ Chris@14: public function process(CodeCoverage $coverage, $target = null, $name = null) Chris@14: { Chris@14: $xmlDocument = new \DOMDocument('1.0', 'UTF-8'); Chris@14: $xmlDocument->formatOutput = true; Chris@14: Chris@14: $xmlCoverage = $xmlDocument->createElement('coverage'); Chris@14: $xmlCoverage->setAttribute('generated', (int) $_SERVER['REQUEST_TIME']); Chris@14: $xmlDocument->appendChild($xmlCoverage); Chris@14: Chris@14: $xmlProject = $xmlDocument->createElement('project'); Chris@14: $xmlProject->setAttribute('timestamp', (int) $_SERVER['REQUEST_TIME']); Chris@14: Chris@14: if (\is_string($name)) { Chris@14: $xmlProject->setAttribute('name', $name); Chris@14: } Chris@14: Chris@14: $xmlCoverage->appendChild($xmlProject); Chris@14: Chris@14: $packages = []; Chris@14: $report = $coverage->getReport(); Chris@14: unset($coverage); Chris@14: Chris@14: foreach ($report as $item) { Chris@14: if (!$item instanceof File) { Chris@14: continue; Chris@14: } Chris@14: Chris@14: /* @var File $item */ Chris@14: Chris@14: $xmlFile = $xmlDocument->createElement('file'); Chris@14: $xmlFile->setAttribute('name', $item->getPath()); Chris@14: Chris@14: $classes = $item->getClassesAndTraits(); Chris@14: $coverage = $item->getCoverageData(); Chris@14: $lines = []; Chris@14: $namespace = 'global'; Chris@14: Chris@14: foreach ($classes as $className => $class) { Chris@14: $classStatements = 0; Chris@14: $coveredClassStatements = 0; Chris@14: $coveredMethods = 0; Chris@14: $classMethods = 0; Chris@14: Chris@14: foreach ($class['methods'] as $methodName => $method) { Chris@14: if ($method['executableLines'] == 0) { Chris@14: continue; Chris@14: } Chris@14: Chris@14: $classMethods++; Chris@14: $classStatements += $method['executableLines']; Chris@14: $coveredClassStatements += $method['executedLines']; Chris@14: Chris@14: if ($method['coverage'] == 100) { Chris@14: $coveredMethods++; Chris@14: } Chris@14: Chris@14: $methodCount = 0; Chris@14: Chris@14: foreach (\range($method['startLine'], $method['endLine']) as $line) { Chris@14: if (isset($coverage[$line]) && ($coverage[$line] !== null)) { Chris@14: $methodCount = \max($methodCount, \count($coverage[$line])); Chris@14: } Chris@14: } Chris@14: Chris@14: $lines[$method['startLine']] = [ Chris@14: 'ccn' => $method['ccn'], Chris@14: 'count' => $methodCount, Chris@14: 'crap' => $method['crap'], Chris@14: 'type' => 'method', Chris@14: 'visibility' => $method['visibility'], Chris@14: 'name' => $methodName Chris@14: ]; Chris@14: } Chris@14: Chris@14: if (!empty($class['package']['namespace'])) { Chris@14: $namespace = $class['package']['namespace']; Chris@14: } Chris@14: Chris@14: $xmlClass = $xmlDocument->createElement('class'); Chris@14: $xmlClass->setAttribute('name', $className); Chris@14: $xmlClass->setAttribute('namespace', $namespace); Chris@14: Chris@14: if (!empty($class['package']['fullPackage'])) { Chris@14: $xmlClass->setAttribute( Chris@14: 'fullPackage', Chris@14: $class['package']['fullPackage'] Chris@14: ); Chris@14: } Chris@14: Chris@14: if (!empty($class['package']['category'])) { Chris@14: $xmlClass->setAttribute( Chris@14: 'category', Chris@14: $class['package']['category'] Chris@14: ); Chris@14: } Chris@14: Chris@14: if (!empty($class['package']['package'])) { Chris@14: $xmlClass->setAttribute( Chris@14: 'package', Chris@14: $class['package']['package'] Chris@14: ); Chris@14: } Chris@14: Chris@14: if (!empty($class['package']['subpackage'])) { Chris@14: $xmlClass->setAttribute( Chris@14: 'subpackage', Chris@14: $class['package']['subpackage'] Chris@14: ); Chris@14: } Chris@14: Chris@14: $xmlFile->appendChild($xmlClass); Chris@14: Chris@14: $xmlMetrics = $xmlDocument->createElement('metrics'); Chris@14: $xmlMetrics->setAttribute('complexity', $class['ccn']); Chris@14: $xmlMetrics->setAttribute('methods', $classMethods); Chris@14: $xmlMetrics->setAttribute('coveredmethods', $coveredMethods); Chris@14: $xmlMetrics->setAttribute('conditionals', 0); Chris@14: $xmlMetrics->setAttribute('coveredconditionals', 0); Chris@14: $xmlMetrics->setAttribute('statements', $classStatements); Chris@14: $xmlMetrics->setAttribute('coveredstatements', $coveredClassStatements); Chris@14: $xmlMetrics->setAttribute('elements', $classMethods + $classStatements /* + conditionals */); Chris@14: $xmlMetrics->setAttribute('coveredelements', $coveredMethods + $coveredClassStatements /* + coveredconditionals */); Chris@14: $xmlClass->appendChild($xmlMetrics); Chris@14: } Chris@14: Chris@14: foreach ($coverage as $line => $data) { Chris@14: if ($data === null || isset($lines[$line])) { Chris@14: continue; Chris@14: } Chris@14: Chris@14: $lines[$line] = [ Chris@14: 'count' => \count($data), 'type' => 'stmt' Chris@14: ]; Chris@14: } Chris@14: Chris@14: \ksort($lines); Chris@14: Chris@14: foreach ($lines as $line => $data) { Chris@14: $xmlLine = $xmlDocument->createElement('line'); Chris@14: $xmlLine->setAttribute('num', $line); Chris@14: $xmlLine->setAttribute('type', $data['type']); Chris@14: Chris@14: if (isset($data['name'])) { Chris@14: $xmlLine->setAttribute('name', $data['name']); Chris@14: } Chris@14: Chris@14: if (isset($data['visibility'])) { Chris@14: $xmlLine->setAttribute('visibility', $data['visibility']); Chris@14: } Chris@14: Chris@14: if (isset($data['ccn'])) { Chris@14: $xmlLine->setAttribute('complexity', $data['ccn']); Chris@14: } Chris@14: Chris@14: if (isset($data['crap'])) { Chris@14: $xmlLine->setAttribute('crap', $data['crap']); Chris@14: } Chris@14: Chris@14: $xmlLine->setAttribute('count', $data['count']); Chris@14: $xmlFile->appendChild($xmlLine); Chris@14: } Chris@14: Chris@14: $linesOfCode = $item->getLinesOfCode(); Chris@14: Chris@14: $xmlMetrics = $xmlDocument->createElement('metrics'); Chris@14: $xmlMetrics->setAttribute('loc', $linesOfCode['loc']); Chris@14: $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']); Chris@14: $xmlMetrics->setAttribute('classes', $item->getNumClassesAndTraits()); Chris@14: $xmlMetrics->setAttribute('methods', $item->getNumMethods()); Chris@14: $xmlMetrics->setAttribute('coveredmethods', $item->getNumTestedMethods()); Chris@14: $xmlMetrics->setAttribute('conditionals', 0); Chris@14: $xmlMetrics->setAttribute('coveredconditionals', 0); Chris@14: $xmlMetrics->setAttribute('statements', $item->getNumExecutableLines()); Chris@14: $xmlMetrics->setAttribute('coveredstatements', $item->getNumExecutedLines()); Chris@14: $xmlMetrics->setAttribute('elements', $item->getNumMethods() + $item->getNumExecutableLines() /* + conditionals */); Chris@14: $xmlMetrics->setAttribute('coveredelements', $item->getNumTestedMethods() + $item->getNumExecutedLines() /* + coveredconditionals */); Chris@14: $xmlFile->appendChild($xmlMetrics); Chris@14: Chris@14: if ($namespace == 'global') { Chris@14: $xmlProject->appendChild($xmlFile); Chris@14: } else { Chris@14: if (!isset($packages[$namespace])) { Chris@14: $packages[$namespace] = $xmlDocument->createElement( Chris@14: 'package' Chris@14: ); Chris@14: Chris@14: $packages[$namespace]->setAttribute('name', $namespace); Chris@14: $xmlProject->appendChild($packages[$namespace]); Chris@14: } Chris@14: Chris@14: $packages[$namespace]->appendChild($xmlFile); Chris@14: } Chris@14: } Chris@14: Chris@14: $linesOfCode = $report->getLinesOfCode(); Chris@14: Chris@14: $xmlMetrics = $xmlDocument->createElement('metrics'); Chris@14: $xmlMetrics->setAttribute('files', \count($report)); Chris@14: $xmlMetrics->setAttribute('loc', $linesOfCode['loc']); Chris@14: $xmlMetrics->setAttribute('ncloc', $linesOfCode['ncloc']); Chris@14: $xmlMetrics->setAttribute('classes', $report->getNumClassesAndTraits()); Chris@14: $xmlMetrics->setAttribute('methods', $report->getNumMethods()); Chris@14: $xmlMetrics->setAttribute('coveredmethods', $report->getNumTestedMethods()); Chris@14: $xmlMetrics->setAttribute('conditionals', 0); Chris@14: $xmlMetrics->setAttribute('coveredconditionals', 0); Chris@14: $xmlMetrics->setAttribute('statements', $report->getNumExecutableLines()); Chris@14: $xmlMetrics->setAttribute('coveredstatements', $report->getNumExecutedLines()); Chris@14: $xmlMetrics->setAttribute('elements', $report->getNumMethods() + $report->getNumExecutableLines() /* + conditionals */); Chris@14: $xmlMetrics->setAttribute('coveredelements', $report->getNumTestedMethods() + $report->getNumExecutedLines() /* + coveredconditionals */); Chris@14: $xmlProject->appendChild($xmlMetrics); Chris@14: Chris@14: $buffer = $xmlDocument->saveXML(); Chris@14: Chris@14: if ($target !== null) { Chris@14: if (!\is_dir(\dirname($target))) { Chris@14: \mkdir(\dirname($target), 0777, true); Chris@14: } Chris@14: Chris@14: if (@\file_put_contents($target, $buffer) === false) { Chris@14: throw new RuntimeException( Chris@14: \sprintf( Chris@14: 'Could not write to "%s', Chris@14: $target Chris@14: ) Chris@14: ); Chris@14: } Chris@14: } Chris@14: Chris@14: return $buffer; Chris@14: } Chris@14: }