Add enable_clock_in_out toggle for branches

This commit is contained in:
2026-07-06 11:51:11 +08:00
parent 3872cabda2
commit 0c43ac406b
5629 changed files with 289948 additions and 53147 deletions

View File

@@ -2,6 +2,19 @@
All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [11.0.12] - 2025-12-24
### Fixed
* [#1092](https://github.com/sebastianbergmann/php-code-coverage/issues/1092): Error in `DOMDocument::saveXML()` is not handled
* [#1131](https://github.com/sebastianbergmann/php-code-coverage/issues/1131): Invalid XML generated when both PCOV and Xdebug are loaded
## [11.0.11] - 2025-08-27
### Changed
* [#1085](https://github.com/sebastianbergmann/php-code-coverage/pull/1085): Improve performance by skipping empty lines after filter has been applied
## [11.0.10] - 2025-06-18
### Changed
@@ -81,6 +94,8 @@ All notable changes are documented in this file using the [Keep a CHANGELOG](htt
* This component now requires PHP-Parser 5
* This component is no longer supported on PHP 8.1
[11.0.12]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.11...11.0.12
[11.0.11]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.10...11.0.11
[11.0.10]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.9...11.0.10
[11.0.9]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.8...11.0.9
[11.0.8]: https://github.com/sebastianbergmann/php-code-coverage/compare/11.0.7...11.0.8

View File

@@ -33,18 +33,18 @@
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^5.4.0",
"nikic/php-parser": "^5.7.0",
"phpunit/php-file-iterator": "^5.1.0",
"phpunit/php-text-template": "^4.0.1",
"sebastian/code-unit-reverse-lookup": "^4.0.1",
"sebastian/complexity": "^4.0.1",
"sebastian/environment": "^7.2.0",
"sebastian/environment": "^7.2.1",
"sebastian/lines-of-code": "^3.0.1",
"sebastian/version": "^5.0.2",
"theseer/tokenizer": "^1.2.3"
"theseer/tokenizer": "^1.3.1"
},
"require-dev": {
"phpunit/phpunit": "^11.5.2"
"phpunit/phpunit": "^11.5.46"
},
"suggest": {
"ext-pcov": "PHP extension that provides line coverage",

View File

@@ -377,6 +377,22 @@ final class CodeCoverage
return $this->driver->detectsDeadCode();
}
/**
* @internal
*/
public function driverIsPcov(): bool
{
return $this->driver->isPcov();
}
/**
* @internal
*/
public function driverIsXdebug(): bool
{
return $this->driver->isXdebug();
}
/**
* @throws ReflectionException
* @throws UnintentionallyCoveredCodeException
@@ -414,15 +430,15 @@ final class CodeCoverage
private function applyFilter(RawCodeCoverageData $data): void
{
if ($this->filter->isEmpty()) {
return;
}
foreach (array_keys($data->lineCoverage()) as $filename) {
if ($this->filter->isExcluded($filename)) {
$data->removeCoverageDataForFile($filename);
if (!$this->filter->isEmpty()) {
foreach (array_keys($data->lineCoverage()) as $filename) {
if ($this->filter->isExcluded($filename)) {
$data->removeCoverageDataForFile($filename);
}
}
}
$data->skipEmptyLines();
}
private function applyExecutableLinesFilter(RawCodeCoverageData $data): void

View File

@@ -103,8 +103,6 @@ final class RawCodeCoverageData
{
$this->lineCoverage = $lineCoverage;
$this->functionCoverage = $functionCoverage;
$this->skipEmptyLines();
}
public function clear(): void
@@ -251,7 +249,7 @@ final class RawCodeCoverageData
*
* @see https://github.com/sebastianbergmann/php-code-coverage/issues/799
*/
private function skipEmptyLines(): void
public function skipEmptyLines(): void
{
foreach ($this->lineCoverage as $filename => $coverage) {
foreach ($this->getEmptyLinesForFile($filename) as $emptyLine) {

View File

@@ -120,6 +120,16 @@ abstract class Driver
$this->detectDeadCode = false;
}
public function isPcov(): bool
{
return false;
}
public function isXdebug(): bool
{
return false;
}
abstract public function nameAndVersion(): string;
abstract public function start(): void;

View File

@@ -68,6 +68,11 @@ final class PcovDriver extends Driver
return 'PCOV ' . phpversion('pcov');
}
public function isPcov(): true
{
return true;
}
/**
* @throws PcovNotAvailableException
*/

View File

@@ -125,6 +125,11 @@ final class XdebugDriver extends Driver
return 'Xdebug ' . phpversion('xdebug');
}
public function isXdebug(): true
{
return true;
}
/**
* @throws XdebugNotAvailableException
*/

View File

@@ -9,6 +9,7 @@
*/
namespace SebastianBergmann\CodeCoverage;
use function rtrim;
use RuntimeException;
final class UnintentionallyCoveredCodeException extends RuntimeException implements Exception
@@ -44,6 +45,6 @@ final class UnintentionallyCoveredCodeException extends RuntimeException impleme
$message .= '- ' . $unit . "\n";
}
return $message;
return rtrim($message);
}
}

View File

@@ -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\Driver\WriteOperationFailedException;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\CodeCoverage\Util\Xml;
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);
@@ -214,16 +214,10 @@ final class Clover
$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;

View File

@@ -12,11 +12,8 @@ namespace SebastianBergmann\CodeCoverage\Report;
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;
@@ -24,10 +21,13 @@ use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\CodeCoverage\Util\Xml;
final class Cobertura
{
/**
* @param null|non-empty-string $target
*
* @throws WriteOperationFailedException
*/
public function process(CodeCoverage $coverage, ?string $target = null): string
@@ -44,10 +44,9 @@ final class Cobertura
'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');
@@ -289,16 +288,10 @@ final class Cobertura
$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;

View File

@@ -10,17 +10,15 @@
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\Driver\WriteOperationFailedException;
use SebastianBergmann\CodeCoverage\Node\File;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\CodeCoverage\Util\Xml;
final class Crap4j
{
@@ -32,12 +30,14 @@ final class Crap4j
}
/**
* @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);
@@ -119,16 +119,10 @@ final class Crap4j
$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;

View File

@@ -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\Driver\WriteOperationFailedException;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
final class PHP
{
/**
* @param null|non-empty-string $target
*
* @throws WriteOperationFailedException
*/
public function process(CodeCoverage $coverage, ?string $target = null): string
{
$coverage->clearCache();
@@ -28,13 +30,7 @@ final class PHP
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;

View File

@@ -13,6 +13,7 @@ use function assert;
use function phpversion;
use DateTimeImmutable;
use DOMElement;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\Environment\Runtime;
/**
@@ -27,7 +28,7 @@ final class BuildInformation
$this->contextNode = $contextNode;
}
public function setRuntimeInformation(Runtime $runtime): void
public function setRuntimeInformation(Runtime $runtime, CodeCoverage $coverage): void
{
$runtimeNode = $this->nodeByName('runtime');
@@ -37,14 +38,12 @@ final class BuildInformation
$driverNode = $this->nodeByName('driver');
if ($runtime->hasXdebug()) {
$driverNode->setAttribute('name', 'xdebug');
$driverNode->setAttribute('version', phpversion('xdebug'));
}
if ($runtime->hasPCOV()) {
if ($coverage->driverIsPcov()) {
$driverNode->setAttribute('name', 'pcov');
$driverNode->setAttribute('version', phpversion('pcov'));
} elseif ($coverage->driverIsXdebug()) {
$driverNode->setAttribute('name', 'xdebug');
$driverNode->setAttribute('version', phpversion('xdebug'));
}
}

View File

@@ -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;
@@ -33,7 +28,9 @@ 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;
use SebastianBergmann\CodeCoverage\Util\Filesystem as DirectoryUtil;
use SebastianBergmann\CodeCoverage\Util\Xml;
use SebastianBergmann\CodeCoverage\Version;
use SebastianBergmann\CodeCoverage\XmlException;
use SebastianBergmann\Environment\Runtime;
@@ -67,17 +64,17 @@ final class Facade
$coverage->getReport()->name(),
);
$this->setBuildInformation();
$this->setBuildInformation($coverage);
$this->processTests($coverage->getTests());
$this->processDirectory($report, $this->project);
$this->saveDocument($this->project->asDom(), 'index');
}
private function setBuildInformation(): void
private function setBuildInformation(CodeCoverage $coverage): void
{
$buildNode = $this->project->buildInformation();
$buildNode->setRuntimeInformation(new Runtime);
$buildNode->setRuntimeInformation(new Runtime, $coverage);
$buildNode->setBuildTime(new DateTimeImmutable);
$buildNode->setGeneratorVersions($this->phpUnitVersion, Version::id());
}
@@ -269,36 +266,8 @@ final class Facade
{
$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;
Filesystem::write($filename, Xml::asString($document));
}
}

View File

@@ -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\Driver\WriteOperationFailedException;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
@@ -34,4 +38,20 @@ final class Filesystem
);
}
}
/**
* @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);
}
}
}

View File

@@ -0,0 +1,53 @@
<?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\Util;
use const PHP_EOL;
use function libxml_clear_errors;
use function libxml_get_errors;
use function libxml_use_internal_errors;
use DOMDocument;
use SebastianBergmann\CodeCoverage\XmlException;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final readonly class Xml
{
/**
* @throws XmlException
*
* @see https://bugs.php.net/bug.php?id=79191
*/
public static function asString(DOMDocument $document): string
{
$xmlErrorHandling = libxml_use_internal_errors(true);
$document->formatOutput = true;
$document->preserveWhiteSpace = false;
$buffer = $document->saveXML();
if ($buffer === 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 $buffer;
}
}

View File

@@ -19,7 +19,7 @@ final class Version
public static function id(): string
{
if (self::$version === '') {
self::$version = (new VersionId('11.0.10', dirname(__DIR__)))->asString();
self::$version = (new VersionId('11.0.12', dirname(__DIR__)))->asString();
}
return self::$version;