diff --git a/PHP/CodeCoverage/Autoload.php b/PHP/CodeCoverage/Autoload.php index dfc810774..6c3b8abc4 100644 --- a/PHP/CodeCoverage/Autoload.php +++ b/PHP/CodeCoverage/Autoload.php @@ -80,6 +80,17 @@ function ($class) 'php_codecoverage_report_node_iterator' => '/CodeCoverage/Report/Node/Iterator.php', 'php_codecoverage_report_php' => '/CodeCoverage/Report/PHP.php', 'php_codecoverage_report_text' => '/CodeCoverage/Report/Text.php', + 'php_codecoverage_report_xml' => '/CodeCoverage/Report/XML.php', + 'php_codecoverage_report_xml_directory' => '/CodeCoverage/Report/XML/Directory.php', + 'php_codecoverage_report_xml_file' => '/CodeCoverage/Report/XML/File.php', + 'php_codecoverage_report_xml_file_coverage' => '/CodeCoverage/Report/XML/File/Coverage.php', + 'php_codecoverage_report_xml_file_method' => '/CodeCoverage/Report/XML/File/Method.php', + 'php_codecoverage_report_xml_file_report' => '/CodeCoverage/Report/XML/File/Report.php', + 'php_codecoverage_report_xml_file_unit' => '/CodeCoverage/Report/XML/File/Unit.php', + 'php_codecoverage_report_xml_node' => '/CodeCoverage/Report/XML/Node.php', + 'php_codecoverage_report_xml_project' => '/CodeCoverage/Report/XML/Project.php', + 'php_codecoverage_report_xml_tests' => '/CodeCoverage/Report/XML/Tests.php', + 'php_codecoverage_report_xml_totals' => '/CodeCoverage/Report/XML/Totals.php', 'php_codecoverage_util' => '/CodeCoverage/Util.php', 'php_codecoverage_util_invalidargumenthelper' => '/CodeCoverage/Util/InvalidArgumentHelper.php' ); diff --git a/PHP/CodeCoverage/Report/XML.php b/PHP/CodeCoverage/Report/XML.php new file mode 100644 index 000000000..3174e7630 --- /dev/null +++ b/PHP/CodeCoverage/Report/XML.php @@ -0,0 +1,189 @@ +target = $target; + $this->initTargetDirectory($target); + $report = $coverage->getReport(); + + $this->project = new PHP_CodeCoverage_Report_XML_Project( + $coverage->getReport()->getName() + ); + $this->processTests($coverage->getTests()); + + $this->processDirectory($report, $this->project); + + $index = $this->project->asDom(); + $index->formatOutput = TRUE; + $index->preserveWhiteSpace = FALSE; + $index->save($target . '/index.xml'); + } + + private function initTargetDirectory($dir) + { + if (file_exists($dir)) { + + if (!is_dir($dir)) { + throw new PHP_CodeCoverage_Exception("'$dir' exists but is not a directory."); + } + + if (!is_writable($dir)) { + throw new PHP_CodeCoverage_Exception("'$dir' exists but is not writable."); + } + } + else if (!@mkdir($dir, 0777, TRUE)) { + throw new PHP_CodeCoverage_Exception("'$dir' could not be created."); + } + } + + private function processDirectory(PHP_CodeCoverage_Report_Node_Directory $directory, PHP_CodeCoverage_Report_XML_Node $context) + { + $dirObject = $context->addDirectory($directory->getName()); + $this->setTotals($directory, $dirObject->getTotals()); + foreach ($directory as $node) { + if ($node instanceof PHP_CodeCoverage_Report_Node_Directory) { + $this->processDirectory($node, $dirObject); + continue; + } + if ($node instanceof PHP_CodeCoverage_Report_Node_File) { + $this->processFile($node, $dirObject); + continue; + } + throw new PHP_CodeCoverage_Exception('Unknown node type for XML Report'); + } + } + + private function processFile(PHP_CodeCoverage_Report_Node_File $file, PHP_CodeCoverage_Report_XML_Directory $context) + { + $fileObject = $context->addFile($file->getName(), $file->getId() . '.xml'); + $this->setTotals($file, $fileObject->getTotals()); + + $fileReport = new PHP_CodeCoverage_Report_XML_File_Report($file->getName()); + $this->setTotals($file, $fileReport->getTotals()); + + foreach ($file->getClassesAndTraits() as $unit) { + $this->processUnit($unit, $fileReport); + } + + foreach ($file->getFunctions() as $function) { + $this->processFunction($function, $fileReport); + } + + foreach ($file->getCoverageData() as $line => $tests) { + if (!is_array($tests) || count($tests) == 0) { + continue; + } + $coverage = $fileReport->getLineCoverage($line); + foreach ($tests as $test) { + $coverage->addTest($test); + } + } + + $this->initTargetDirectory($this->target . dirname($file->getId()) . '/'); + $fileDom = $fileReport->asDom(); + $fileDom->formatOutput = TRUE; + $fileDom->preserveWhiteSpace = FALSE; + $fileDom->save($this->target . $file->getId() . '.xml'); + } + + private function processUnit($unit, PHP_CodeCoverage_Report_XML_File_Report $report) + { + if (isset($unit['className'])) { + $unitObject = $report->getClassObject($unit['className']); + } + else { + $unitObject = $report->getTraitObject($unit['traitName']); + } + + $unitObject->setLines( + $unit['startLine'], + $unit['executableLines'], + $unit['executedLines'] + ); + $unitObject->setCrap($unit['crap']); + + $unitObject->setPackage( + $unit['package']['fullPackage'], + $unit['package']['package'], + $unit['package']['subpackage'], + $unit['package']['category'] + ); + + $unitObject->setNamespace($unit['package']['namespace']); + + foreach ($unit['methods'] as $method) { + $methodObject = $unitObject->addMethod($method['methodName']); + $methodObject->setSignature($method['signature']); + $methodObject->setLines($method['startLine'], $method['endLine']); + $methodObject->setCrap($method['crap']); + $methodObject->setTotals($method['executableLines'], $method['executedLines'], $method['coverage']); + } + } + + private function processFunction($function, PHP_CodeCoverage_Report_XML_File_Report $report) + { + $functionObject = $report->getFunctionObject($function['functionName']); + $functionObject->setSignature($function['signature']); + $functionObject->setLines($function['startLine']); + $functionObject->setCrap($function['crap']); + $functionObject->setTotals($function['executableLines'], $function['executedLines'], $function['coverage']); + } + + private function processTests(array $tests) + { + $testsObject = $this->project->getTests(); + foreach ($tests as $test => $result) { + if ($test == 'UNCOVERED_FILES_FROM_WHITELIST') { + continue; + } + $testsObject->addTest($test, $result); + } + } + + private function setTotals(PHP_CodeCoverage_Report_Node $node, PHP_CodeCoverage_Report_XML_Totals $totals) + { + $loc = $node->getLinesOfCode(); + $totals->setNumLines( + $loc['loc'], + $loc['cloc'], + $loc['ncloc'], + $node->getNumExecutableLines(), + $node->getNumExecutedLines() + ); + + $totals->setNumClasses( + $node->getNumClasses(), + $node->getNumTestedClasses() + ); + + $totals->setNumTraits( + $node->getNumTraits(), + $node->getNumTestedTraits() + ); + + $totals->setNumMethods( + $node->getNumMethods(), + $node->getNumTestedMethods() + ); + + $totals->setNumFunctions( + $node->getNumFunctions(), + $node->getNumTestedFunctions() + ); + } + +} \ No newline at end of file diff --git a/PHP/CodeCoverage/Report/XML/Directory.php b/PHP/CodeCoverage/Report/XML/Directory.php new file mode 100644 index 000000000..bad00ed51 --- /dev/null +++ b/PHP/CodeCoverage/Report/XML/Directory.php @@ -0,0 +1,3 @@ +contextNode = $context; + $this->dom = $context->ownerDocument; + } + + public function getTotals() + { + $totalsContainer = $this->contextNode->firstChild; + if (!$totalsContainer) { + $totalsContainer = $this->contextNode->appendChild( + $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'totals') + ); + } + return new PHP_CodeCoverage_Report_XML_Totals($totalsContainer); + } + + public function getLineCoverage($line) + { + $coverage = $this->contextNode->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'coverage')->item(0); + if (!$coverage) { + $coverage = $this->contextNode->appendChild( + $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'coverage') + ); + } + $lineNode = $coverage->appendChild( + $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'line') + ); + return new PHP_CodeCoverage_Report_XML_File_Coverage($lineNode, $line); + } + +} \ No newline at end of file diff --git a/PHP/CodeCoverage/Report/XML/File/Coverage.php b/PHP/CodeCoverage/Report/XML/File/Coverage.php new file mode 100644 index 000000000..3528c5a6d --- /dev/null +++ b/PHP/CodeCoverage/Report/XML/File/Coverage.php @@ -0,0 +1,25 @@ +contextNode = $context; + $this->setLine($line); + } + + private function setLine($line) + { + $this->contextNode->setAttribute('nr', $line); + } + + public function addTest($test) + { + $covered = $this->contextNode->appendChild( + $this->contextNode->ownerDocument->createElementNS('http://xml.phpunit.de/coverage/1.0', 'covered') + ); + $covered->setAttribute('by', $test); + } + +} \ No newline at end of file diff --git a/PHP/CodeCoverage/Report/XML/File/Method.php b/PHP/CodeCoverage/Report/XML/File/Method.php new file mode 100644 index 000000000..c6dd897c0 --- /dev/null +++ b/PHP/CodeCoverage/Report/XML/File/Method.php @@ -0,0 +1,42 @@ +contextNode = $context; + $this->setName($name); + } + + private function setName($name) + { + $this->contextNode->setAttribute('name', $name); + } + + public function setSignature($signature) + { + $this->contextNode->setAttribute('signature', $signature); + } + + public function setLines($start, $end = NULL) + { + $this->contextNode->setAttribute('start', $start); + if ($end !== NULL) { + $this->contextNode->setAttribute('end', $end); + } + } + + public function setTotals($executable, $executed, $coverage) + { + $this->contextNode->setAttribute('executable', $executable); + $this->contextNode->setAttribute('executed', $executed); + $this->contextNode->setAttribute('coverage', $coverage); + } + + public function setCrap($crap) + { + $this->contextNode->setAttribute('crap', $crap); + } + +} \ No newline at end of file diff --git a/PHP/CodeCoverage/Report/XML/File/Report.php b/PHP/CodeCoverage/Report/XML/File/Report.php new file mode 100644 index 000000000..32a16cb6e --- /dev/null +++ b/PHP/CodeCoverage/Report/XML/File/Report.php @@ -0,0 +1,48 @@ +dom = new \DOMDocument(); + $this->dom->loadXML(''); + $this->contextNode = $this->dom->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'file')->item(0); + $this->setName($name); + } + + private function setName($name) + { + $this->contextNode->setAttribute('name', $name); + } + + public function asDom() + { + return $this->dom; + } + + public function getFunctionObject($name) + { + $node = $this->contextNode->appendChild( + $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'function') + ); + return new PHP_CodeCoverage_Report_XML_File_Method($node, $name); + } + + public function getClassObject($name) + { + return $this->getUnitObject('class', $name); + } + + public function getTraitObject($name) + { + return $this->getUnitObject('trait', $name); + } + + private function getUnitObject($tagName, $name) + { + $node = $this->contextNode->appendChild( + $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', $tagName) + ); + return new PHP_CodeCoverage_Report_XML_File_Unit($node, $name); + } + +} diff --git a/PHP/CodeCoverage/Report/XML/File/Unit.php b/PHP/CodeCoverage/Report/XML/File/Unit.php new file mode 100644 index 000000000..6ef09987e --- /dev/null +++ b/PHP/CodeCoverage/Report/XML/File/Unit.php @@ -0,0 +1,62 @@ +contextNode = $context; + $this->setName($name); + } + + private function setName($name) + { + $this->contextNode->setAttribute('name', $name); + } + + public function setLines($start, $executable, $executed) + { + $this->contextNode->setAttribute('start', $start); + $this->contextNode->setAttribute('executable', $executable); + $this->contextNode->setAttribute('executed', $executed); + } + + public function setCrap($crap) + { + $this->contextNode->setAttribute('crap', $crap); + } + + public function setPackage($full, $package, $sub, $category) + { + $node = $this->contextNode->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'package')->item(0); + if (!$node) { + $node = $this->contextNode->appendChild( + $this->contextNode->ownerDocument->createElementNS('http://xml.phpunit.de/coverage/1.0', 'package') + ); + } + $node->setAttribute('full', $full); + $node->setAttribute('name', $package); + $node->setAttribute('sub', $sub); + $node->setAttribute('category', $category); + } + + public function setNamespace($namespace) + { + $node = $this->contextNode->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'namespace')->item(0); + if (!$node) { + $node = $this->contextNode->appendChild( + $this->contextNode->ownerDocument->createElementNS('http://xml.phpunit.de/coverage/1.0', 'namespace') + ); + } + $node->setAttribute('name', $namespace); + } + + public function addMethod($name) + { + $node = $this->contextNode->appendChild( + $this->contextNode->ownerDocument->createElementNS('http://xml.phpunit.de/coverage/1.0', 'method') + ); + return new PHP_CodeCoverage_Report_XML_File_Method($node, $name); + } + +} \ No newline at end of file diff --git a/PHP/CodeCoverage/Report/XML/Node.php b/PHP/CodeCoverage/Report/XML/Node.php new file mode 100644 index 000000000..a802d89c8 --- /dev/null +++ b/PHP/CodeCoverage/Report/XML/Node.php @@ -0,0 +1,63 @@ +setContextNode($context); + } + + protected function setContextNode(\DOMElement $context) + { + $this->contextNode = $context; + $this->dom = $context->ownerDocument; + } + + public function getDom() + { + return $this->dom; + } + + protected function getContextNode() + { + return $this->contextNode; + } + + public function getTotals() + { + $totalsContainer = $this->getContextNode()->firstChild; + if (!$totalsContainer) { + $totalsContainer = $this->getContextNode()->appendChild( + $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'totals') + ); + } + return new PHP_CodeCoverage_Report_XML_Totals($totalsContainer); + } + + public function addDirectory($name) + { + $dirNode = $this->getDom()->createElementNS('http://xml.phpunit.de/coverage/1.0', 'directory'); + $dirNode->setAttribute('name', $name); + $this->getContextNode()->appendChild($dirNode); + return new PHP_CodeCoverage_Report_XML_Directory($dirNode); + } + + public function addFile($name, $href) + { + $fileNode = $this->getDom()->createElementNS('http://xml.phpunit.de/coverage/1.0', 'file'); + $fileNode->setAttribute('name', $name); + $fileNode->setAttribute('href', $href); + $this->getContextNode()->appendChild($fileNode); + return new PHP_CodeCoverage_Report_XML_File($fileNode); + } + +} \ No newline at end of file diff --git a/PHP/CodeCoverage/Report/XML/Project.php b/PHP/CodeCoverage/Report/XML/Project.php new file mode 100644 index 000000000..27581df72 --- /dev/null +++ b/PHP/CodeCoverage/Report/XML/Project.php @@ -0,0 +1,37 @@ +init(); + $this->setProjectName($name); + } + + private function init() + { + $dom = new DOMDocument(); + $dom->loadXML(''); + $this->setContextNode($dom->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'project')->item(0)); + } + + private function setProjectName($name) + { + $this->getContextNode()->setAttribute('name', $name); + } + + public function getTests() + { + $testsNode = $this->getContextNode()->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'tests')->item(0); + if (!$testsNode) { + $testsNode = $this->getContextNode()->appendChild( + $this->getDom()->createElementNS('http://xml.phpunit.de/coverage/1.0', 'tests') + ); + } + return new PHP_CodeCoverage_Report_XML_Tests($testsNode); + } + + public function asDom() + { + return $this->getDom(); + } +} \ No newline at end of file diff --git a/PHP/CodeCoverage/Report/XML/Tests.php b/PHP/CodeCoverage/Report/XML/Tests.php new file mode 100644 index 000000000..fe16d5b31 --- /dev/null +++ b/PHP/CodeCoverage/Report/XML/Tests.php @@ -0,0 +1,20 @@ +contextNode = $context; + } + + public function addTest($test, $result) + { + $node = $this->contextNode->appendChild( + $this->contextNode->ownerDocument->createElementNS('http://xml.phpunit.de/coverage/1.0', 'test') + ); + $node->setAttribute('name', $test); + $node->setAttribute('result', $result); + } + +} \ No newline at end of file diff --git a/PHP/CodeCoverage/Report/XML/Totals.php b/PHP/CodeCoverage/Report/XML/Totals.php new file mode 100644 index 000000000..466f64c64 --- /dev/null +++ b/PHP/CodeCoverage/Report/XML/Totals.php @@ -0,0 +1,94 @@ +container = $container; + $dom = $container->ownerDocument; + $this->linesNode = $dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'lines'); + $this->methodsNode = $dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'methods'); + $this->functionsNode = $dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'functions'); + $this->classesNode = $dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'classes'); + $this->traitsNode = $dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'traits'); + + $container->appendChild($this->linesNode); + $container->appendChild($this->methodsNode); + $container->appendChild($this->functionsNode); + $container->appendChild($this->classesNode); + $container->appendChild($this->traitsNode); + } + + public function getContainer() + { + return $this->container; + } + + public function setNumLines($loc, $cloc, $ncloc, $executable, $executed) + { + $this->linesNode->setAttribute('total', $loc); + $this->linesNode->setAttribute('comments', $cloc); + $this->linesNode->setAttribute('code', $ncloc); + $this->linesNode->setAttribute('executable', $executable); + $this->linesNode->setAttribute('executed', $executed); + $this->linesNode->setAttribute('percent', PHP_CodeCoverage_Util::percent($executed,$executable, TRUE)); + } + + public function setNumClasses($count, $tested) + { + $this->classesNode->setAttribute('count', $count); + $this->classesNode->setAttribute('tested', $tested); + $this->classesNode->setAttribute('percent', PHP_CodeCoverage_Util::percent($tested,$count, TRUE)); + } + + public function setNumTraits($count, $tested) + { + $this->traitsNode->setAttribute('count', $count); + $this->traitsNode->setAttribute('tested', $tested); + $this->traitsNode->setAttribute('percent', PHP_CodeCoverage_Util::percent($tested,$count, TRUE)); + } + + public function setNumMethods($count, $tested) + { + $this->methodsNode->setAttribute('count', $count); + $this->methodsNode->setAttribute('tested', $tested); + $this->methodsNode->setAttribute('percent', PHP_CodeCoverage_Util::percent($tested,$count, TRUE)); + } + + public function setNumFunctions($count, $tested) + { + $this->functionsNode->setAttribute('count', $count); + $this->functionsNode->setAttribute('tested', $tested); + $this->functionsNode->setAttribute('percent', PHP_CodeCoverage_Util::percent($tested,$count, TRUE)); + } + +}