Skip to content
Closed
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
1 change: 1 addition & 0 deletions phpunit.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="driver" type="xs:string"/>
<xs:attribute name="pathCoverage" type="xs:boolean" default="false"/>
<xs:attribute name="branchCoverage" type="xs:boolean" default="false"/>
<xs:attribute name="includeUncoveredFiles" type="xs:boolean" default="true"/>
Expand Down
80 changes: 70 additions & 10 deletions src/Runner/CodeCoverage.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
namespace PHPUnit\Runner;

use function assert;
use function class_exists;
use function implode;
use function is_subclass_of;
use function sprintf;
use function sys_get_temp_dir;
use DateTimeImmutable;
Expand All @@ -20,6 +22,7 @@
use PHPUnit\TextUI\Configuration\Configuration;
use PHPUnit\TextUI\Output\Printer;
use PHPUnit\Util\Filesystem;
use ReflectionClass;
use SebastianBergmann\CodeCoverage\Driver\Driver;
use SebastianBergmann\CodeCoverage\Driver\Granularity;
use SebastianBergmann\CodeCoverage\Driver\Selector;
Expand Down Expand Up @@ -82,10 +85,17 @@ public function init(Configuration $configuration, CodeCoverageFilterRegistry $c
return CodeCoverageInitializationStatus::NOT_REQUESTED;
}

$coverageDriver = null;

if ($configuration->hasCoverageDriver()) {
$coverageDriver = $configuration->coverageDriver();
}

$this->activate(
$codeCoverageFilterRegistry->get(),
$configuration->branchCoverage(),
$configuration->pathCoverage(),
$coverageDriver,
);

if (!$this->isActive()) {
Expand Down Expand Up @@ -512,7 +522,7 @@ public function warnIfFilterIsNotConfigured(CodeCoverageFilterRegistry $codeCove
$this->deactivate();
}

private function activate(Filter $filter, bool $branchCoverage, bool $pathCoverage): void
private function activate(Filter $filter, bool $branchCoverage, bool $pathCoverage, ?string $driverClass = null): void
{
try {
$granularity = Granularity::Line;
Expand All @@ -522,19 +532,15 @@ private function activate(Filter $filter, bool $branchCoverage, bool $pathCovera
}

if ($pathCoverage) {
$granularity = Granularity::LineBranchAndPath;
}

/**
* @todo This needs to be removed once code coverage drivers are supported that can collect branch coverage without path coverage
*/
if ($branchCoverage || $pathCoverage) {
$branchCoverage = true;
$pathCoverage = true;
$granularity = Granularity::LineBranchAndPath;
}

$this->driver = (new Selector)->select($filter, $granularity);
if ($driverClass !== null) {
$this->driver = $this->instantiateDriver($driverClass, $filter, $granularity);
} else {
$this->driver = (new Selector)->select($filter, $granularity);
}

$this->codeCoverage = new \SebastianBergmann\CodeCoverage\CodeCoverage(
$this->driver,
Expand All @@ -554,6 +560,60 @@ private function activate(Filter $filter, bool $branchCoverage, bool $pathCovera
}
}

/**
* @phpstan-ignore return.internalClass
*/
private function instantiateDriver(string $driverClass, Filter $filter, Granularity $granularity): Driver
{
if (!class_exists($driverClass)) {
throw new CodeCoverageDriverException(
sprintf(
'Configured code coverage driver class "%s" does not exist',
$driverClass,
),
);
}

/** @phpstan-ignore classConstant.internalClass */
if (!is_subclass_of($driverClass, Driver::class)) {
throw new CodeCoverageDriverException(
sprintf(
'Configured code coverage driver class "%s" does not extend %s',
$driverClass,
/** @phpstan-ignore classConstant.internalClass */
Driver::class,
),
);
}

$reflection = new ReflectionClass($driverClass);

if (!$reflection->isInstantiable()) {
throw new CodeCoverageDriverException(
sprintf(
'Configured code coverage driver class "%s" is not instantiable',
$driverClass,
),
);
}

$constructor = $reflection->getConstructor();

if ($constructor !== null && $constructor->getNumberOfRequiredParameters() > 0) {
$driver = $reflection->newInstance($filter);
} else {
$driver = $reflection->newInstance();
}

/** @phpstan-ignore instanceof.internalClass */
assert($driver instanceof Driver);

/** @phpstan-ignore method.internalClass */
$driver->setGranularity($granularity);

return $driver;
}

private function codeCoverageGenerationStart(Printer $printer, string $format): void
{
$printer->print(
Expand Down
21 changes: 21 additions & 0 deletions src/Runner/Exception/CodeCoverageDriverException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (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 PHPUnit\Runner;

use RuntimeException;

/**
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
*
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class CodeCoverageDriverException extends RuntimeException implements Exception
{
}
24 changes: 23 additions & 1 deletion src/TextUI/Configuration/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
private Source $source;
private bool $pathCoverage;
private bool $branchCoverage;
private ?string $coverageDriver;

/**
* @var ?non-empty-string
Expand Down Expand Up @@ -553,7 +554,7 @@
* @param null|non-empty-string $generateBaseline
* @param non-negative-int $shortenArraysForExportThreshold
*/
public function __construct(array $cliArguments, ?string $testFilesFile, ?string $configurationFile, ?string $bootstrap, array $bootstrapForTestSuite, bool $cacheResult, ?string $cacheDirectory, ?string $coverageCacheDirectory, Source $source, string $testResultCacheFile, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4j, int $coverageCrap4jThreshold, ?string $coverageHtml, int $coverageHtmlLowUpperBound, int $coverageHtmlHighLowerBound, string $coverageHtmlColorSuccessLow, string $coverageHtmlColorSuccessLowDark, string $coverageHtmlColorSuccessMedium, string $coverageHtmlColorSuccessMediumDark, string $coverageHtmlColorSuccessHigh, string $coverageHtmlColorSuccessHighDark, string $coverageHtmlColorSuccessBar, string $coverageHtmlColorSuccessBarDark, string $coverageHtmlColorWarning, string $coverageHtmlColorWarningDark, string $coverageHtmlColorWarningBar, string $coverageHtmlColorWarningBarDark, string $coverageHtmlColorDanger, string $coverageHtmlColorDangerDark, string $coverageHtmlColorDangerBar, string $coverageHtmlColorDangerBarDark, string $coverageHtmlColorBreadcrumbs, string $coverageHtmlColorBreadcrumbsDark, ?string $coverageHtmlCustomCssFile, ?string $coverageOpenClover, ?string $coveragePhp, ?string $coverageText, bool $coverageTextShowUncoveredFiles, bool $coverageTextShowOnlySummary, ?string $coverageXml, bool $coverageXmlIncludeSource, bool $pathCoverage, bool $branchCoverage, bool $ignoreDeprecatedCodeUnitsFromCodeCoverage, bool $disableCodeCoverageIgnore, bool $disableCoverageTargeting, bool $failOnAllIssues, bool $failOnDeprecation, bool $failOnPhpunitDeprecation, bool $failOnPhpunitNotice, bool $failOnPhpunitWarning, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnNotice, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $doNotFailOnDeprecation, bool $doNotFailOnPhpunitDeprecation, bool $doNotFailOnPhpunitNotice, bool $doNotFailOnPhpunitWarning, bool $doNotFailOnEmptyTestSuite, bool $doNotFailOnIncomplete, bool $doNotFailOnNotice, bool $doNotFailOnRisky, bool $doNotFailOnSkipped, bool $doNotFailOnWarning, int $stopOnDefect, int $stopOnDeprecation, ?string $specificDeprecationToStopOn, int $stopOnError, int $stopOnFailure, int $stopOnIncomplete, int $stopOnNotice, int $stopOnRisky, int $stopOnSkipped, int $stopOnWarning, bool $outputToStandardErrorStream, int $columns, bool $noExtensions, ?string $pharExtensionDirectory, array $extensionBootstrappers, bool $backupGlobals, bool $backupStaticProperties, bool $beStrictAboutChangesToGlobalState, bool $colors, bool $processIsolation, bool $enforceTimeLimit, int $defaultTimeLimit, int $diffContext, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, bool $reportUselessTests, bool $strictCoverage, bool $requireCoverageContribution, bool $disallowTestOutput, bool $displayDetailsOnAllIssues, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $displayDetailsOnTestsThatTriggerDeprecations, bool $displayDetailsOnPhpunitDeprecations, bool $displayDetailsOnPhpunitNotices, bool $displayDetailsOnTestsThatTriggerErrors, bool $displayDetailsOnTestsThatTriggerNotices, bool $displayDetailsOnTestsThatTriggerWarnings, bool $reverseDefectList, bool $requireCoverageMetadata, bool $requireSealedMockObjects, bool $noProgress, bool $noResults, bool $noOutput, int $executionOrder, int $executionOrderDefects, bool $resolveDependencies, ?string $logfileTeamcity, ?string $logfileJunit, ?string $logfileOtr, bool $includeGitInformation, bool $includeGitInformationInOtrLogfile, ?string $logfileTestdoxHtml, ?string $logfileTestdoxText, ?string $logEventsText, ?string $logEventsVerboseText, bool $compactOutput, bool $teamCityOutput, bool $testDoxOutput, bool $testDoxOutputSummary, ?array $testsCovering, ?array $testsUsing, ?array $testsRequiringPhpExtension, ?string $filter, ?string $excludeFilter, ?string $testIdFilterFile, ?string $testIdFilter, array $groups, array $excludeGroups, int $randomOrderSeed, int $repeat, int $retry, bool $includeUncoveredFiles, TestSuiteCollection $testSuite, string $includeTestSuite, string $excludeTestSuite, ?string $defaultTestSuite, bool $ignoreTestSelectionInXmlConfiguration, array $testSuffixes, Php $php, bool $controlGarbageCollector, int $numberOfTestsBeforeGarbageCollection, ?string $generateBaseline, bool $debug, bool $withTelemetry, int $shortenArraysForExportThreshold)
public function __construct(array $cliArguments, ?string $testFilesFile, ?string $configurationFile, ?string $bootstrap, array $bootstrapForTestSuite, bool $cacheResult, ?string $cacheDirectory, ?string $coverageCacheDirectory, Source $source, string $testResultCacheFile, ?string $coverageClover, ?string $coverageCobertura, ?string $coverageCrap4j, int $coverageCrap4jThreshold, ?string $coverageHtml, int $coverageHtmlLowUpperBound, int $coverageHtmlHighLowerBound, string $coverageHtmlColorSuccessLow, string $coverageHtmlColorSuccessLowDark, string $coverageHtmlColorSuccessMedium, string $coverageHtmlColorSuccessMediumDark, string $coverageHtmlColorSuccessHigh, string $coverageHtmlColorSuccessHighDark, string $coverageHtmlColorSuccessBar, string $coverageHtmlColorSuccessBarDark, string $coverageHtmlColorWarning, string $coverageHtmlColorWarningDark, string $coverageHtmlColorWarningBar, string $coverageHtmlColorWarningBarDark, string $coverageHtmlColorDanger, string $coverageHtmlColorDangerDark, string $coverageHtmlColorDangerBar, string $coverageHtmlColorDangerBarDark, string $coverageHtmlColorBreadcrumbs, string $coverageHtmlColorBreadcrumbsDark, ?string $coverageHtmlCustomCssFile, ?string $coverageOpenClover, ?string $coveragePhp, ?string $coverageText, bool $coverageTextShowUncoveredFiles, bool $coverageTextShowOnlySummary, ?string $coverageXml, bool $coverageXmlIncludeSource, bool $pathCoverage, bool $branchCoverage, ?string $coverageDriver, bool $ignoreDeprecatedCodeUnitsFromCodeCoverage, bool $disableCodeCoverageIgnore, bool $disableCoverageTargeting, bool $failOnAllIssues, bool $failOnDeprecation, bool $failOnPhpunitDeprecation, bool $failOnPhpunitNotice, bool $failOnPhpunitWarning, bool $failOnEmptyTestSuite, bool $failOnIncomplete, bool $failOnNotice, bool $failOnRisky, bool $failOnSkipped, bool $failOnWarning, bool $doNotFailOnDeprecation, bool $doNotFailOnPhpunitDeprecation, bool $doNotFailOnPhpunitNotice, bool $doNotFailOnPhpunitWarning, bool $doNotFailOnEmptyTestSuite, bool $doNotFailOnIncomplete, bool $doNotFailOnNotice, bool $doNotFailOnRisky, bool $doNotFailOnSkipped, bool $doNotFailOnWarning, int $stopOnDefect, int $stopOnDeprecation, ?string $specificDeprecationToStopOn, int $stopOnError, int $stopOnFailure, int $stopOnIncomplete, int $stopOnNotice, int $stopOnRisky, int $stopOnSkipped, int $stopOnWarning, bool $outputToStandardErrorStream, int $columns, bool $noExtensions, ?string $pharExtensionDirectory, array $extensionBootstrappers, bool $backupGlobals, bool $backupStaticProperties, bool $beStrictAboutChangesToGlobalState, bool $colors, bool $processIsolation, bool $enforceTimeLimit, int $defaultTimeLimit, int $diffContext, int $timeoutForSmallTests, int $timeoutForMediumTests, int $timeoutForLargeTests, bool $reportUselessTests, bool $strictCoverage, bool $requireCoverageContribution, bool $disallowTestOutput, bool $displayDetailsOnAllIssues, bool $displayDetailsOnIncompleteTests, bool $displayDetailsOnSkippedTests, bool $displayDetailsOnTestsThatTriggerDeprecations, bool $displayDetailsOnPhpunitDeprecations, bool $displayDetailsOnPhpunitNotices, bool $displayDetailsOnTestsThatTriggerErrors, bool $displayDetailsOnTestsThatTriggerNotices, bool $displayDetailsOnTestsThatTriggerWarnings, bool $reverseDefectList, bool $requireCoverageMetadata, bool $requireSealedMockObjects, bool $noProgress, bool $noResults, bool $noOutput, int $executionOrder, int $executionOrderDefects, bool $resolveDependencies, ?string $logfileTeamcity, ?string $logfileJunit, ?string $logfileOtr, bool $includeGitInformation, bool $includeGitInformationInOtrLogfile, ?string $logfileTestdoxHtml, ?string $logfileTestdoxText, ?string $logEventsText, ?string $logEventsVerboseText, bool $compactOutput, bool $teamCityOutput, bool $testDoxOutput, bool $testDoxOutputSummary, ?array $testsCovering, ?array $testsUsing, ?array $testsRequiringPhpExtension, ?string $filter, ?string $excludeFilter, ?string $testIdFilterFile, ?string $testIdFilter, array $groups, array $excludeGroups, int $randomOrderSeed, int $repeat, int $retry, bool $includeUncoveredFiles, TestSuiteCollection $testSuite, string $includeTestSuite, string $excludeTestSuite, ?string $defaultTestSuite, bool $ignoreTestSelectionInXmlConfiguration, array $testSuffixes, Php $php, bool $controlGarbageCollector, int $numberOfTestsBeforeGarbageCollection, ?string $generateBaseline, bool $debug, bool $withTelemetry, int $shortenArraysForExportThreshold)
{
$this->cliArguments = $cliArguments;
$this->testFilesFile = $testFilesFile;
Expand Down Expand Up @@ -600,6 +601,7 @@ public function __construct(array $cliArguments, ?string $testFilesFile, ?string
$this->coverageXmlIncludeSource = $coverageXmlIncludeSource;
$this->pathCoverage = $pathCoverage;
$this->branchCoverage = $branchCoverage;
$this->coverageDriver = $coverageDriver;
$this->ignoreDeprecatedCodeUnitsFromCodeCoverage = $ignoreDeprecatedCodeUnitsFromCodeCoverage;
$this->disableCodeCoverageIgnore = $disableCodeCoverageIgnore;
$this->disableCoverageTargeting = $disableCoverageTargeting;
Expand Down Expand Up @@ -890,6 +892,26 @@ public function branchCoverage(): bool
return $this->branchCoverage;
}

/**
* @phpstan-assert-if-true !null $this->coverageDriver
*/
public function hasCoverageDriver(): bool
{
return $this->coverageDriver !== null;
}

/**
* @throws CodeCoverageDriverNotConfiguredException
*/
public function coverageDriver(): string
{
if (!$this->hasCoverageDriver()) {
throw new CodeCoverageDriverNotConfiguredException;
}

return $this->coverageDriver;
}

public function hasCoverageReport(): bool
{
return $this->hasCoverageClover() ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (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 PHPUnit\TextUI\Configuration;

use RuntimeException;

/**
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
*
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class CodeCoverageDriverNotConfiguredException extends RuntimeException implements Exception
{
}
7 changes: 7 additions & 0 deletions src/TextUI/Configuration/Merger.php
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,12 @@ public function merge(CliConfiguration $cliConfiguration, XmlConfiguration $xmlC
$branchCoverage = $xmlConfiguration->codeCoverage()->branchCoverage();
}

$coverageDriver = null;

if ($xmlConfiguration->codeCoverage()->hasDriver()) {
$coverageDriver = $xmlConfiguration->codeCoverage()->driver();
}

$defaultColors = Colors::default();
$defaultThresholds = Thresholds::default();

Expand Down Expand Up @@ -1200,6 +1206,7 @@ public function merge(CliConfiguration $cliConfiguration, XmlConfiguration $xmlC
$coverageXmlIncludeSource,
$pathCoverage,
$branchCoverage,
$coverageDriver,
$xmlConfiguration->codeCoverage()->ignoreDeprecatedCodeUnits(),
$disableCodeCoverageIgnore,
$disableCoverageTargeting,
Expand Down
26 changes: 25 additions & 1 deletion src/TextUI/Configuration/Xml/CodeCoverage/CodeCoverage.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
*/
final readonly class CodeCoverage
{
private ?string $driver;
private bool $pathCoverage;
private bool $branchCoverage;
private bool $includeUncoveredFiles;
Expand All @@ -42,8 +43,9 @@
private ?Text $text;
private ?Xml $xml;

public function __construct(bool $pathCoverage, bool $branchCoverage, bool $includeUncoveredFiles, bool $ignoreDeprecatedCodeUnits, bool $disableCodeCoverageIgnore, ?Clover $clover, ?Cobertura $cobertura, ?Crap4j $crap4j, ?Html $html, ?OpenClover $openClover, ?Php $php, ?Text $text, ?Xml $xml)
public function __construct(?string $driver, bool $pathCoverage, bool $branchCoverage, bool $includeUncoveredFiles, bool $ignoreDeprecatedCodeUnits, bool $disableCodeCoverageIgnore, ?Clover $clover, ?Cobertura $cobertura, ?Crap4j $crap4j, ?Html $html, ?OpenClover $openClover, ?Php $php, ?Text $text, ?Xml $xml)
{
$this->driver = $driver;
$this->pathCoverage = $pathCoverage;
$this->branchCoverage = $branchCoverage;
$this->includeUncoveredFiles = $includeUncoveredFiles;
Expand All @@ -59,6 +61,28 @@ public function __construct(bool $pathCoverage, bool $branchCoverage, bool $incl
$this->xml = $xml;
}

/**
* @phpstan-assert-if-true !null $this->driver
*/
public function hasDriver(): bool
{
return $this->driver !== null;
}

/**
* @throws Exception
*/
public function driver(): string
{
if (!$this->hasDriver()) {
throw new Exception(
'Code Coverage driver has not been configured',
);
}

return $this->driver;
}

public function pathCoverage(): bool
{
return $this->pathCoverage;
Expand Down
1 change: 1 addition & 0 deletions src/TextUI/Configuration/Xml/DefaultConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public static function create(): self
true,
),
new CodeCoverage(
null,
false,
false,
true,
Expand Down
Loading