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
5 changes: 5 additions & 0 deletions ChangeLog-12.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.

## [12.3.8] - 2025-MM-DD

* [#1092](https://github.com/sebastianbergmann/php-code-coverage/issues/1092): Error in `DOMDocument::saveXML()` is not handled

## [12.3.7] - 2025-09-10

### Changed
Expand Down Expand Up @@ -56,6 +60,7 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt

* [#1080](https://github.com/sebastianbergmann/php-code-coverage/pull/1080): Support for reporting code coverage information in OpenClover XML format; unlike the existing Clover XML reporter, which remains unchanged, the XML documents generated by this new reporter validate against the OpenClover project's XML schema definition, with one exception: we do not generate the `<testproject>` element. This feature is experimental and the generated XML might change in order to improve compliance with the OpenClover project's XML schema definition further. Such changes will be made in bugfix and/or minor releases even if they break backward compatibility.

[12.3.8]: https://github.com/sebastianbergmann/php-code-coverage/compare/12.3.7...main
[12.3.7]: https://github.com/sebastianbergmann/php-code-coverage/compare/12.3.6...12.3.7
[12.3.6]: https://github.com/sebastianbergmann/php-code-coverage/compare/12.3.5...12.3.6
[12.3.5]: https://github.com/sebastianbergmann/php-code-coverage/compare/12.3.4...12.3.5
Expand Down
20 changes: 7 additions & 13 deletions src/Report/Clover.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,31 @@
namespace SebastianBergmann\CodeCoverage\Report;

use function count;
use function dirname;
use function file_put_contents;
use function is_string;
use function ksort;
use function max;
use function range;
use function str_contains;
use function time;
use DOMDocument;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\CodeCoverage\Util\Xml;
use SebastianBergmann\CodeCoverage\WriteOperationFailedException;

final class Clover
{
/**
* @param null|non-empty-string $target
* @param null|non-empty-string $name
*
* @throws WriteOperationFailedException
*/
public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string
{
$time = (string) time();

$xmlDocument = new DOMDocument('1.0', 'UTF-8');
$xmlDocument->formatOutput = true;
$xmlDocument = new DOMDocument('1.0', 'UTF-8');

$xmlCoverage = $xmlDocument->createElement('coverage');
$xmlCoverage->setAttribute('generated', $time);
Expand Down Expand Up @@ -216,16 +216,10 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string
$xmlMetrics->setAttribute('coveredelements', (string) ($report->numberOfTestedMethods() + $report->numberOfExecutedLines() + $report->numberOfExecutedBranches()));
$xmlProject->appendChild($xmlMetrics);

$buffer = $xmlDocument->saveXML();
$buffer = Xml::asString($xmlDocument);

if ($target !== null) {
if (!str_contains($target, '://')) {
Filesystem::createDirectory(dirname($target));
}

if (@file_put_contents($target, $buffer) === false) {
throw new WriteOperationFailedException($target);
}
Filesystem::write($target, $buffer);
}

return $buffer;
Expand Down
23 changes: 8 additions & 15 deletions src/Report/Cobertura.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@
use const DIRECTORY_SEPARATOR;
use function basename;
use function count;
use function dirname;
use function file_put_contents;
use function preg_match;
use function range;
use function str_contains;
use function str_replace;
use function time;
use DOMImplementation;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\CodeCoverage\Util\Xml;
use SebastianBergmann\CodeCoverage\WriteOperationFailedException;

final class Cobertura
{
/**
* @param null|non-empty-string $target
*
* @throws WriteOperationFailedException
*/
public function process(CodeCoverage $coverage, ?string $target = null): string
Expand All @@ -44,10 +44,9 @@ public function process(CodeCoverage $coverage, ?string $target = null): string
'http://cobertura.sourceforge.net/xml/coverage-04.dtd',
);

$document = $implementation->createDocument('', '', $documentType);
$document->xmlVersion = '1.0';
$document->encoding = 'UTF-8';
$document->formatOutput = true;
$document = $implementation->createDocument('', '', $documentType);
$document->xmlVersion = '1.0';
$document->encoding = 'UTF-8';

$coverageElement = $document->createElement('coverage');

Expand Down Expand Up @@ -289,16 +288,10 @@ public function process(CodeCoverage $coverage, ?string $target = null): string

$coverageElement->setAttribute('complexity', (string) $complexity);

$buffer = $document->saveXML();
$buffer = Xml::asString($document);

if ($target !== null) {
if (!str_contains($target, '://')) {
Filesystem::createDirectory(dirname($target));
}

if (@file_put_contents($target, $buffer) === false) {
throw new WriteOperationFailedException($target);
}
Filesystem::write($target, $buffer);
}

return $buffer;
Expand Down
20 changes: 7 additions & 13 deletions src/Report/Crap4j.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@
namespace SebastianBergmann\CodeCoverage\Report;

use function date;
use function dirname;
use function file_put_contents;
use function htmlspecialchars;
use function is_string;
use function round;
use function str_contains;
use DOMDocument;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\CodeCoverage\Util\Xml;
use SebastianBergmann\CodeCoverage\WriteOperationFailedException;

final readonly class Crap4j
Expand All @@ -32,12 +30,14 @@ public function __construct(int $threshold = 30)
}

/**
* @param null|non-empty-string $target
* @param null|non-empty-string $name
*
* @throws WriteOperationFailedException
*/
public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string
{
$document = new DOMDocument('1.0', 'UTF-8');
$document->formatOutput = true;
$document = new DOMDocument('1.0', 'UTF-8');

$root = $document->createElement('crap_result');
$document->appendChild($root);
Expand Down Expand Up @@ -119,16 +119,10 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string
$root->appendChild($stats);
$root->appendChild($methodsNode);

$buffer = $document->saveXML();
$buffer = Xml::asString($document);

if ($target !== null) {
if (!str_contains($target, '://')) {
Filesystem::createDirectory(dirname($target));
}

if (@file_put_contents($target, $buffer) === false) {
throw new WriteOperationFailedException($target);
}
Filesystem::write($target, $buffer);
}

return $buffer;
Expand Down
14 changes: 3 additions & 11 deletions src/Report/OpenClover.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,18 @@
use function assert;
use function basename;
use function count;
use function dirname;
use function file_put_contents;
use function is_string;
use function ksort;
use function max;
use function range;
use function str_contains;
use function str_replace;
use function time;
use DOMDocument;
use DOMElement;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\CodeCoverage\Util\Xml;
use SebastianBergmann\CodeCoverage\Version;
use SebastianBergmann\CodeCoverage\WriteOperationFailedException;

Expand Down Expand Up @@ -240,16 +238,10 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string
$xmlMetrics->setAttribute('coveredmethods', (string) $report->numberOfTestedMethods());
$xmlProject->insertBefore($xmlMetrics, $xmlProject->firstChild);

$buffer = $xmlDocument->saveXML();
$buffer = Xml::asString($xmlDocument);

if ($target !== null) {
if (!str_contains($target, '://')) {
Filesystem::createDirectory(dirname($target));
}

if (@file_put_contents($target, $buffer) === false) {
throw new WriteOperationFailedException($target);
}
Filesystem::write($target, $buffer);
}

return $buffer;
Expand Down
16 changes: 6 additions & 10 deletions src/Report/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@
namespace SebastianBergmann\CodeCoverage\Report;

use const PHP_EOL;
use function dirname;
use function file_put_contents;
use function serialize;
use function str_contains;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\CodeCoverage\WriteOperationFailedException;

final class PHP
{
/**
* @param null|non-empty-string $target
*
* @throws WriteOperationFailedException
*/
public function process(CodeCoverage $coverage, ?string $target = null): string
{
$coverage->clearCache();
Expand All @@ -28,13 +30,7 @@ public function process(CodeCoverage $coverage, ?string $target = null): string
return \unserialize(<<<'END_OF_COVERAGE_SERIALIZATION'" . PHP_EOL . serialize($coverage) . PHP_EOL . 'END_OF_COVERAGE_SERIALIZATION' . PHP_EOL . ');';

if ($target !== null) {
if (!str_contains($target, '://')) {
Filesystem::createDirectory(dirname($target));
}

if (@file_put_contents($target, $buffer) === false) {
throw new WriteOperationFailedException($target);
}
Filesystem::write($target, $buffer);
}

return $buffer;
Expand Down
42 changes: 4 additions & 38 deletions src/Report/Xml/Facade.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,13 @@
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;
Expand All @@ -33,7 +28,8 @@
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\Node\File as FileNode;
use SebastianBergmann\CodeCoverage\PathExistsButIsNotDirectoryException;
use SebastianBergmann\CodeCoverage\Util\Filesystem as DirectoryUtil;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\CodeCoverage\Util\Xml;
use SebastianBergmann\CodeCoverage\Version;
use SebastianBergmann\CodeCoverage\WriteOperationFailedException;
use SebastianBergmann\CodeCoverage\XmlException;
Expand Down Expand Up @@ -107,7 +103,7 @@ private function initTargetDirectory(string $directory): void
// @codeCoverageIgnoreEnd
}

DirectoryUtil::createDirectory($directory);
Filesystem::createDirectory($directory);
}

/**
Expand Down Expand Up @@ -287,38 +283,8 @@ 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) {
// @codeCoverageIgnoreStart
$message = 'Unable to generate the XML';

foreach (libxml_get_errors() as $error) {
$message .= PHP_EOL . $error->message;
}

throw new XmlException($message);
// @codeCoverageIgnoreEnd
}

libxml_clear_errors();
libxml_use_internal_errors($xmlErrorHandling);

return $xml;
Filesystem::write($filename, Xml::asString($document));
}
}
20 changes: 20 additions & 0 deletions src/Util/Filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
*/
namespace SebastianBergmann\CodeCoverage\Util;

use function dirname;
use function file_put_contents;
use function is_dir;
use function mkdir;
use function sprintf;
use function str_contains;
use SebastianBergmann\CodeCoverage\WriteOperationFailedException;

/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
Expand All @@ -34,4 +38,20 @@ public static function createDirectory(string $directory): void
);
}
}

/**
* @param non-empty-string $target
*
* @throws WriteOperationFailedException
*/
public static function write(string $target, string $buffer): void
{
if (!str_contains($target, '://')) {
self::createDirectory(dirname($target));
}

if (@file_put_contents($target, $buffer) === false) {
throw new WriteOperationFailedException($target);
}
}
}
Loading
Loading