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\Xml;use const DIRECTORY_SEPARATOR;use const PHP_EOL;use function count;use function dirname;use function file_get_contents;use function file_put_contents;use function is_array;use function is_dir;use function is_file;use function is_writable;use function libxml_clear_errors;use function libxml_get_errors;use function libxml_use_internal_errors;use function sprintf;use function strlen;use function substr;use DateTimeImmutable;use DOMDocument;use SebastianBergmann\CodeCoverage\CodeCoverage;use SebastianBergmann\CodeCoverage\Driver\PathExistsButIsNotDirectoryException;use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException;use SebastianBergmann\CodeCoverage\Node\AbstractNode;use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;use SebastianBergmann\CodeCoverage\Node\File as FileNode;use SebastianBergmann\CodeCoverage\Util\Filesystem as DirectoryUtil;use SebastianBergmann\CodeCoverage\Version;use SebastianBergmann\CodeCoverage\XmlException;use SebastianBergmann\Environment\Runtime;final class Facade{/*** @var string*/private $target;/*** @var Project*/private $project;/*** @var string*/private $phpUnitVersion;public function __construct(string $version){$this->phpUnitVersion = $version;}/*** @throws XmlException*/public function process(CodeCoverage $coverage, string $target): void{if (substr($target, -1, 1) !== DIRECTORY_SEPARATOR) {$target .= DIRECTORY_SEPARATOR;}$this->target = $target;$this->initTargetDirectory($target);$report = $coverage->getReport();$this->project = new Project($coverage->getReport()->name());$this->setBuildInformation();$this->processTests($coverage->getTests());$this->processDirectory($report, $this->project);$this->saveDocument($this->project->asDom(), 'index');}private function setBuildInformation(): void{$buildNode = $this->project->buildInformation();$buildNode->setRuntimeInformation(new Runtime);$buildNode->setBuildTime(new DateTimeImmutable);$buildNode->setGeneratorVersions($this->phpUnitVersion, Version::id());}/*** @throws PathExistsButIsNotDirectoryException* @throws WriteOperationFailedException*/private function initTargetDirectory(string $directory): void{if (is_file($directory)) {if (!is_dir($directory)) {throw new PathExistsButIsNotDirectoryException($directory);}if (!is_writable($directory)) {throw new WriteOperationFailedException($directory);}}DirectoryUtil::createDirectory($directory);}/*** @throws XmlException*/private function processDirectory(DirectoryNode $directory, Node $context): void{$directoryName = $directory->name();if ($this->project->projectSourceDirectory() === $directoryName) {$directoryName = '/';}$directoryObject = $context->addDirectory($directoryName);$this->setTotals($directory, $directoryObject->totals());foreach ($directory->directories() as $node) {$this->processDirectory($node, $directoryObject);}foreach ($directory->files() as $node) {$this->processFile($node, $directoryObject);}}/*** @throws XmlException*/private function processFile(FileNode $file, Directory $context): void{$fileObject = $context->addFile($file->name(),$file->id() . '.xml');$this->setTotals($file, $fileObject->totals());$path = substr($file->pathAsString(),strlen($this->project->projectSourceDirectory()));$fileReport = new Report($path);$this->setTotals($file, $fileReport->totals());foreach ($file->classesAndTraits() as $unit) {$this->processUnit($unit, $fileReport);}foreach ($file->functions() as $function) {$this->processFunction($function, $fileReport);}foreach ($file->lineCoverageData() as $line => $tests) {if (!is_array($tests) || count($tests) === 0) {continue;}$coverage = $fileReport->lineCoverage((string) $line);foreach ($tests as $test) {$coverage->addTest($test);}$coverage->finalize();}$fileReport->source()->setSourceCode(file_get_contents($file->pathAsString()));$this->saveDocument($fileReport->asDom(), $file->id());}private function processUnit(array $unit, Report $report): void{if (isset($unit['className'])) {$unitObject = $report->classObject($unit['className']);} else {$unitObject = $report->traitObject($unit['traitName']);}$unitObject->setLines($unit['startLine'],$unit['executableLines'],$unit['executedLines']);$unitObject->setCrap((float) $unit['crap']);$unitObject->setNamespace($unit['namespace']);foreach ($unit['methods'] as $method) {$methodObject = $unitObject->addMethod($method['methodName']);$methodObject->setSignature($method['signature']);$methodObject->setLines((string) $method['startLine'], (string) $method['endLine']);$methodObject->setCrap($method['crap']);$methodObject->setTotals((string) $method['executableLines'],(string) $method['executedLines'],(string) $method['coverage']);}}private function processFunction(array $function, Report $report): void{$functionObject = $report->functionObject($function['functionName']);$functionObject->setSignature($function['signature']);$functionObject->setLines((string) $function['startLine']);$functionObject->setCrap($function['crap']);$functionObject->setTotals((string) $function['executableLines'], (string) $function['executedLines'], (string) $function['coverage']);}private function processTests(array $tests): void{$testsObject = $this->project->tests();foreach ($tests as $test => $result) {$testsObject->addTest($test, $result);}}private function setTotals(AbstractNode $node, Totals $totals): void{$loc = $node->linesOfCode();$totals->setNumLines($loc['linesOfCode'],$loc['commentLinesOfCode'],$loc['nonCommentLinesOfCode'],$node->numberOfExecutableLines(),$node->numberOfExecutedLines());$totals->setNumClasses($node->numberOfClasses(),$node->numberOfTestedClasses());$totals->setNumTraits($node->numberOfTraits(),$node->numberOfTestedTraits());$totals->setNumMethods($node->numberOfMethods(),$node->numberOfTestedMethods());$totals->setNumFunctions($node->numberOfFunctions(),$node->numberOfTestedFunctions());}private function targetDirectory(): string{return $this->target;}/*** @throws XmlException*/private function saveDocument(DOMDocument $document, string $name): void{$filename = sprintf('%s/%s.xml', $this->targetDirectory(), $name);$document->formatOutput = true;$document->preserveWhiteSpace = false;$this->initTargetDirectory(dirname($filename));file_put_contents($filename, $this->documentAsString($document));}/*** @throws XmlException** @see https://bugs.php.net/bug.php?id=79191*/private function documentAsString(DOMDocument $document): string{$xmlErrorHandling = libxml_use_internal_errors(true);$xml = $document->saveXML();if ($xml === false) {$message = 'Unable to generate the XML';foreach (libxml_get_errors() as $error) {$message .= PHP_EOL . $error->message;}throw new XmlException($message);}libxml_clear_errors();libxml_use_internal_errors($xmlErrorHandling);return $xml;}}