Spaces:
Runtime error
Runtime error
| 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; | |
| } | |
| } | |