This commit is contained in:
TiclemFR
2023-12-29 16:00:02 +01:00
parent 9d79d7c0c6
commit 884eb3011a
8361 changed files with 1160554 additions and 4 deletions

View File

@@ -0,0 +1,128 @@
<?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\Driver;
use function sprintf;
use SebastianBergmann\CodeCoverage\BranchAndPathCoverageNotSupportedException;
use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData;
use SebastianBergmann\CodeCoverage\DeadCodeDetectionNotSupportedException;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
abstract class Driver
{
/**
* @var int
*
* @see http://xdebug.org/docs/code_coverage
*/
public const LINE_NOT_EXECUTABLE = -2;
/**
* @var int
*
* @see http://xdebug.org/docs/code_coverage
*/
public const LINE_NOT_EXECUTED = -1;
/**
* @var int
*
* @see http://xdebug.org/docs/code_coverage
*/
public const LINE_EXECUTED = 1;
/**
* @var int
*
* @see http://xdebug.org/docs/code_coverage
*/
public const BRANCH_NOT_HIT = 0;
/**
* @var int
*
* @see http://xdebug.org/docs/code_coverage
*/
public const BRANCH_HIT = 1;
private bool $collectBranchAndPathCoverage = false;
private bool $detectDeadCode = false;
public function canCollectBranchAndPathCoverage(): bool
{
return false;
}
public function collectsBranchAndPathCoverage(): bool
{
return $this->collectBranchAndPathCoverage;
}
/**
* @throws BranchAndPathCoverageNotSupportedException
*/
public function enableBranchAndPathCoverage(): void
{
if (!$this->canCollectBranchAndPathCoverage()) {
throw new BranchAndPathCoverageNotSupportedException(
sprintf(
'%s does not support branch and path coverage',
$this->nameAndVersion()
)
);
}
$this->collectBranchAndPathCoverage = true;
}
public function disableBranchAndPathCoverage(): void
{
$this->collectBranchAndPathCoverage = false;
}
public function canDetectDeadCode(): bool
{
return false;
}
public function detectsDeadCode(): bool
{
return $this->detectDeadCode;
}
/**
* @throws DeadCodeDetectionNotSupportedException
*/
public function enableDeadCodeDetection(): void
{
if (!$this->canDetectDeadCode()) {
throw new DeadCodeDetectionNotSupportedException(
sprintf(
'%s does not support dead code detection',
$this->nameAndVersion()
)
);
}
$this->detectDeadCode = true;
}
public function disableDeadCodeDetection(): void
{
$this->detectDeadCode = false;
}
abstract public function nameAndVersion(): string;
abstract public function start(): void;
abstract public function stop(): RawCodeCoverageData;
}

View File

@@ -0,0 +1,80 @@
<?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\Driver;
use const pcov\inclusive;
use function array_intersect;
use function extension_loaded;
use function pcov\clear;
use function pcov\collect;
use function pcov\start;
use function pcov\stop;
use function pcov\waiting;
use function phpversion;
use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData;
use SebastianBergmann\CodeCoverage\Filter;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*/
final class PcovDriver extends Driver
{
private readonly Filter $filter;
/**
* @throws PcovNotAvailableException
*/
public function __construct(Filter $filter)
{
$this->ensurePcovIsAvailable();
$this->filter = $filter;
}
public function start(): void
{
start();
}
public function stop(): RawCodeCoverageData
{
stop();
$filesToCollectCoverageFor = waiting();
$collected = [];
if ($filesToCollectCoverageFor) {
if (!$this->filter->isEmpty()) {
$filesToCollectCoverageFor = array_intersect($filesToCollectCoverageFor, $this->filter->files());
}
$collected = collect(inclusive, $filesToCollectCoverageFor);
clear();
}
return RawCodeCoverageData::fromXdebugWithoutPathCoverage($collected);
}
public function nameAndVersion(): string
{
return 'PCOV ' . phpversion('pcov');
}
/**
* @throws PcovNotAvailableException
*/
private function ensurePcovIsAvailable(): void
{
if (!extension_loaded('pcov')) {
throw new PcovNotAvailableException;
}
}
}

View File

@@ -0,0 +1,62 @@
<?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\Driver;
use SebastianBergmann\CodeCoverage\Filter;
use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverAvailableException;
use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverWithPathCoverageSupportAvailableException;
use SebastianBergmann\Environment\Runtime;
final class Selector
{
/**
* @throws NoCodeCoverageDriverAvailableException
* @throws PcovNotAvailableException
* @throws XdebugNotAvailableException
* @throws XdebugNotEnabledException
*/
public function forLineCoverage(Filter $filter): Driver
{
$runtime = new Runtime;
if ($runtime->hasPCOV()) {
return new PcovDriver($filter);
}
if ($runtime->hasXdebug()) {
$driver = new XdebugDriver($filter);
$driver->enableDeadCodeDetection();
return $driver;
}
throw new NoCodeCoverageDriverAvailableException;
}
/**
* @throws NoCodeCoverageDriverWithPathCoverageSupportAvailableException
* @throws XdebugNotAvailableException
* @throws XdebugNotEnabledException
*/
public function forLineAndPathCoverage(Filter $filter): Driver
{
if ((new Runtime)->hasXdebug()) {
$driver = new XdebugDriver($filter);
$driver->enableDeadCodeDetection();
$driver->enableBranchAndPathCoverage();
return $driver;
}
throw new NoCodeCoverageDriverWithPathCoverageSupportAvailableException;
}
}

View File

@@ -0,0 +1,162 @@
<?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\Driver;
use const XDEBUG_CC_BRANCH_CHECK;
use const XDEBUG_CC_DEAD_CODE;
use const XDEBUG_CC_UNUSED;
use const XDEBUG_FILTER_CODE_COVERAGE;
use const XDEBUG_PATH_INCLUDE;
use function explode;
use function extension_loaded;
use function getenv;
use function in_array;
use function ini_get;
use function phpversion;
use function version_compare;
use function xdebug_get_code_coverage;
use function xdebug_info;
use function xdebug_set_filter;
use function xdebug_start_code_coverage;
use function xdebug_stop_code_coverage;
use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData;
use SebastianBergmann\CodeCoverage\Filter;
/**
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
*
* @see https://xdebug.org/docs/code_coverage#xdebug_get_code_coverage
*
* @psalm-type XdebugLinesCoverageType = array<int, int>
* @psalm-type XdebugBranchCoverageType = array{
* op_start: int,
* op_end: int,
* line_start: int,
* line_end: int,
* hit: int,
* out: array<int, int>,
* out_hit: array<int, int>,
* }
* @psalm-type XdebugPathCoverageType = array{
* path: array<int, int>,
* hit: int,
* }
* @psalm-type XdebugFunctionCoverageType = array{
* branches: array<int, XdebugBranchCoverageType>,
* paths: array<int, XdebugPathCoverageType>,
* }
* @psalm-type XdebugFunctionsCoverageType = array<string, XdebugFunctionCoverageType>
* @psalm-type XdebugPathAndBranchesCoverageType = array{
* lines: XdebugLinesCoverageType,
* functions: XdebugFunctionsCoverageType,
* }
* @psalm-type XdebugCodeCoverageWithoutPathCoverageType = array<string, XdebugLinesCoverageType>
* @psalm-type XdebugCodeCoverageWithPathCoverageType = array<string, XdebugPathAndBranchesCoverageType>
*/
final class XdebugDriver extends Driver
{
/**
* @throws XdebugNotAvailableException
* @throws XdebugNotEnabledException
*/
public function __construct(Filter $filter)
{
$this->ensureXdebugIsAvailable();
$this->ensureXdebugCodeCoverageFeatureIsEnabled();
if (!$filter->isEmpty()) {
xdebug_set_filter(
XDEBUG_FILTER_CODE_COVERAGE,
XDEBUG_PATH_INCLUDE,
$filter->files()
);
}
}
public function canCollectBranchAndPathCoverage(): bool
{
return true;
}
public function canDetectDeadCode(): bool
{
return true;
}
public function start(): void
{
$flags = XDEBUG_CC_UNUSED;
if ($this->detectsDeadCode() || $this->collectsBranchAndPathCoverage()) {
$flags |= XDEBUG_CC_DEAD_CODE;
}
if ($this->collectsBranchAndPathCoverage()) {
$flags |= XDEBUG_CC_BRANCH_CHECK;
}
xdebug_start_code_coverage($flags);
}
public function stop(): RawCodeCoverageData
{
$data = xdebug_get_code_coverage();
xdebug_stop_code_coverage();
if ($this->collectsBranchAndPathCoverage()) {
/* @var XdebugCodeCoverageWithPathCoverageType $data */
return RawCodeCoverageData::fromXdebugWithPathCoverage($data);
}
/* @var XdebugCodeCoverageWithoutPathCoverageType $data */
return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data);
}
public function nameAndVersion(): string
{
return 'Xdebug ' . phpversion('xdebug');
}
/**
* @throws XdebugNotAvailableException
*/
private function ensureXdebugIsAvailable(): void
{
if (!extension_loaded('xdebug')) {
throw new XdebugNotAvailableException;
}
}
/**
* @throws XdebugNotEnabledException
*/
private function ensureXdebugCodeCoverageFeatureIsEnabled(): void
{
if (version_compare(phpversion('xdebug'), '3.1', '>=')) {
if (!in_array('coverage', xdebug_info('mode'), true)) {
throw new XdebugNotEnabledException;
}
return;
}
$mode = getenv('XDEBUG_MODE');
if ($mode === false || $mode === '') {
$mode = ini_get('xdebug.mode');
}
if ($mode === false ||
!in_array('coverage', explode(',', $mode), true)) {
throw new XdebugNotEnabledException;
}
}
}