Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"sebastian/environment": "^8.0.3",
"sebastian/lines-of-code": "^4.0",
"sebastian/version": "^6.0",
"theseer/tokenizer": "^1.3.1"
"theseer/tokenizer": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^12.4.4"
Expand Down
52 changes: 18 additions & 34 deletions src/Report/Xml/BuildInformation.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,63 +9,47 @@
*/
namespace SebastianBergmann\CodeCoverage\Report\Xml;

use function assert;
use function phpversion;
use DateTimeImmutable;
use DOMElement;
use SebastianBergmann\Environment\Runtime;
use XMLWriter;

/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class BuildInformation
{
private DOMElement $contextNode;

public function __construct(
DOMElement $contextNode,
XMLWriter $xmlWriter,
Runtime $runtime,
DateTimeImmutable $buildDate,
string $phpUnitVersion,
string $coverageVersion
) {
$this->contextNode = $contextNode;

$runtimeNode = $this->nodeByName('runtime');
$xmlWriter->startElement('build');
$xmlWriter->writeAttribute('time', $buildDate->format('D M j G:i:s T Y'));
$xmlWriter->writeAttribute('phpunit', $phpUnitVersion);
$xmlWriter->writeAttribute('coverage', $coverageVersion);

$runtimeNode->setAttribute('name', $runtime->getName());
$runtimeNode->setAttribute('version', $runtime->getVersion());
$runtimeNode->setAttribute('url', $runtime->getVendorUrl());
$xmlWriter->startElement('runtime');
$xmlWriter->writeAttribute('name', $runtime->getName());
$xmlWriter->writeAttribute('version', $runtime->getVersion());
$xmlWriter->writeAttribute('url', $runtime->getVendorUrl());
$xmlWriter->endElement();

$driverNode = $this->nodeByName('driver');
$xmlWriter->startElement('driver');

if ($runtime->hasXdebug()) {
$driverNode->setAttribute('name', 'xdebug');
$driverNode->setAttribute('version', phpversion('xdebug'));
$xmlWriter->writeAttribute('name', 'xdebug');
$xmlWriter->writeAttribute('version', phpversion('xdebug'));
}

if ($runtime->hasPCOV()) {
$driverNode->setAttribute('name', 'pcov');
$driverNode->setAttribute('version', phpversion('pcov'));
$xmlWriter->writeAttribute('name', 'pcov');
$xmlWriter->writeAttribute('version', phpversion('pcov'));
}
$xmlWriter->endElement();

$this->contextNode->setAttribute('time', $buildDate->format('D M j G:i:s T Y'));

$this->contextNode->setAttribute('phpunit', $phpUnitVersion);
$this->contextNode->setAttribute('coverage', $coverageVersion);
}

private function nodeByName(string $name): DOMElement
{
$node = $this->contextNode->appendChild(
$this->contextNode->ownerDocument->createElementNS(
Facade::XML_NAMESPACE,
$name,
),
);

assert($node instanceof DOMElement);

return $node;
$xmlWriter->endElement();
}
}
26 changes: 9 additions & 17 deletions src/Report/Xml/Coverage.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@
*/
namespace SebastianBergmann\CodeCoverage\Report\Xml;

use DOMElement;
use XMLWriter;

/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class Coverage
{
private readonly DOMElement $contextNode;
private readonly XMLWriter $xmlWriter;
private readonly string $line;

public function __construct(DOMElement $context, string $line)
{
$this->contextNode = $context;
$this->line = $line;
public function __construct(
XMLWriter $xmlWriter,
string $line
) {
$this->xmlWriter = $xmlWriter;
$this->line = $line;
}

public function finalize(array $tests): void
{
$writer = new XMLWriter;
$writer->openMemory();
$writer->startElementNs(null, $this->contextNode->nodeName, Facade::XML_NAMESPACE);
$writer = $this->xmlWriter;
$writer->startElement('line');
$writer->writeAttribute('nr', $this->line);

foreach ($tests as $test) {
Expand All @@ -39,13 +39,5 @@ public function finalize(array $tests): void
$writer->endElement();
}
$writer->endElement();

$fragment = $this->contextNode->ownerDocument->createDocumentFragment();
$fragment->appendXML($writer->outputMemory());

$this->contextNode->parentNode->replaceChild(
$fragment,
$this->contextNode,
);
}
}
91 changes: 62 additions & 29 deletions src/Report/Xml/Facade.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
use function strlen;
use function substr;
use DateTimeImmutable;
use DOMDocument;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Data\ProcessedClassType;
use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType;
Expand All @@ -31,11 +30,11 @@
use SebastianBergmann\CodeCoverage\Node\File as FileNode;
use SebastianBergmann\CodeCoverage\PathExistsButIsNotDirectoryException;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\CodeCoverage\Util\Xml;
use SebastianBergmann\CodeCoverage\Version;
use SebastianBergmann\CodeCoverage\WriteOperationFailedException;
use SebastianBergmann\CodeCoverage\XmlException;
use SebastianBergmann\Environment\Runtime;
use XMLWriter;

/**
* @phpstan-import-type TestType from CodeCoverage
Expand Down Expand Up @@ -68,15 +67,21 @@ public function process(CodeCoverage $coverage, string $target): void

$report = $coverage->getReport();

$writer = new XMLWriter;
$writer->openUri($this->targetFilePath('index'));
$writer->setIndent(true);
$writer->setIndentString(' ');
$this->project = new Project(
$writer,
$coverage->getReport()->name(),
);

$this->setBuildInformation();

$this->project->startProject();
$this->processTests($coverage->getTests());
$this->processDirectory($report, $this->project);

$this->saveDocument($this->project->asDom(), 'index');
$this->project->finalize();
}

private function setBuildInformation(): void
Expand Down Expand Up @@ -121,7 +126,10 @@ private function processDirectory(DirectoryNode $directory, Node $context): void
$directoryName = '/';
}

$directoryObject = $context->addDirectory($directoryName);
$writer = $this->project->getWriter();
$writer->startElement('directory');
$writer->writeAttribute('name', $directoryName);
$directoryObject = $context->addDirectory();

$this->setTotals($directory, $directoryObject->totals());

Expand All @@ -132,27 +140,35 @@ private function processDirectory(DirectoryNode $directory, Node $context): void
foreach ($directory->files() as $node) {
$this->processFile($node, $directoryObject);
}
$writer->endElement();
}

/**
* @throws XmlException
*/
private function processFile(FileNode $file, Directory $context): void
{
$fileObject = $context->addFile(
$file->name(),
$file->id() . '.xml',
$file->sha1(),
);
$context->getWriter()->startElement('file');
$context->getWriter()->writeAttribute('name', $file->name());
$context->getWriter()->writeAttribute('href', $file->id() . '.xml');
$context->getWriter()->writeAttribute('hash', $file->sha1());

$fileObject = $context->addFile();

$this->setTotals($file, $fileObject->totals());

$context->getWriter()->endElement();

$path = substr(
$file->pathAsString(),
strlen($this->project->projectSourceDirectory()),
);

$fileReport = new Report($path, $file->sha1());
$writer = new XMLWriter;
$writer->openUri($this->targetFilePath($file->id()));
$writer->setIndent(true);
$writer->setIndentString(' ');
$fileReport = new Report($writer, $path, $file->sha1());

$this->setTotals($file, $fileReport->totals());

Expand All @@ -164,6 +180,8 @@ private function processFile(FileNode $file, Directory $context): void
$this->processFunction($function, $fileReport);
}

$fileReport->getWriter()->startElement('coverage');

foreach ($file->lineCoverageData() as $line => $tests) {
if (!is_array($tests) || count($tests) === 0) {
continue;
Expand All @@ -172,19 +190,22 @@ private function processFile(FileNode $file, Directory $context): void
$coverage = $fileReport->lineCoverage((string) $line);
$coverage->finalize($tests);
}
$fileReport->getWriter()->endElement();

if ($this->includeSource) {
$fileReport->source()->setSourceCode(
file_get_contents($file->pathAsString()),
);
}

$this->saveDocument($fileReport->asDom(), $file->id());
$fileReport->finalize();
}

private function processUnit(ProcessedClassType|ProcessedTraitType $unit, Report $report): void
{
if ($unit instanceof ProcessedClassType) {
$report->getWriter()->startElement('class');

$unitObject = $report->classObject(
$unit->className,
$unit->namespace,
Expand All @@ -194,6 +215,8 @@ private function processUnit(ProcessedClassType|ProcessedTraitType $unit, Report
(float) $unit->crap,
);
} else {
$report->getWriter()->startElement('trait');

$unitObject = $report->traitObject(
$unit->traitName,
$unit->namespace,
Expand All @@ -205,6 +228,8 @@ private function processUnit(ProcessedClassType|ProcessedTraitType $unit, Report
}

foreach ($unit->methods as $method) {
$report->getWriter()->startElement('method');

$unitObject->addMethod(
$method->methodName,
$method->signature,
Expand All @@ -215,11 +240,17 @@ private function processUnit(ProcessedClassType|ProcessedTraitType $unit, Report
(string) $method->coverage,
$method->crap,
);

$report->getWriter()->endElement();
}

$report->getWriter()->endElement();
}

private function processFunction(ProcessedFunctionType $function, Report $report): void
{
$report->getWriter()->startElement('function');

$report->functionObject(
$function->functionName,
$function->signature,
Expand All @@ -230,22 +261,30 @@ private function processFunction(ProcessedFunctionType $function, Report $report
(string) $function->coverage,
$function->crap,
);

$report->getWriter()->endElement();
}

/**
* @param array<string, TestType> $tests
*/
private function processTests(array $tests): void
{
$this->project->getWriter()->startElement('tests');

$testsObject = $this->project->tests();

foreach ($tests as $test => $result) {
$testsObject->addTest($test, $result);
}

$this->project->getWriter()->endElement();
}

private function setTotals(AbstractNode $node, Totals $totals): void
{
$totals->getWriter()->startElement('totals');

$loc = $node->linesOfCode();

$totals->setNumLines(
Expand All @@ -256,6 +295,16 @@ private function setTotals(AbstractNode $node, Totals $totals): void
$node->numberOfExecutedLines(),
);

$totals->setNumMethods(
$node->numberOfMethods(),
$node->numberOfTestedMethods(),
);

$totals->setNumFunctions(
$node->numberOfFunctions(),
$node->numberOfTestedFunctions(),
);

$totals->setNumClasses(
$node->numberOfClasses(),
$node->numberOfTestedClasses(),
Expand All @@ -266,15 +315,7 @@ private function setTotals(AbstractNode $node, Totals $totals): void
$node->numberOfTestedTraits(),
);

$totals->setNumMethods(
$node->numberOfMethods(),
$node->numberOfTestedMethods(),
);

$totals->setNumFunctions(
$node->numberOfFunctions(),
$node->numberOfTestedFunctions(),
);
$totals->getWriter()->endElement();
}

private function targetDirectory(): string
Expand All @@ -290,12 +331,4 @@ private function targetFilePath(string $name): string

return $filename;
}

/**
* @throws XmlException
*/
private function saveDocument(DOMDocument $document, string $name): void
{
Filesystem::write($this->targetFilePath($name), Xml::asString($document));
}
}
Loading