Skip to content

Commit

Permalink
Rework how code coverage settings are propagated to the driver
Browse files Browse the repository at this point in the history
  • Loading branch information
dvdoug committed May 22, 2020
1 parent 76e16f3 commit ae99808
Show file tree
Hide file tree
Showing 11 changed files with 353 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .psalm/baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</PossiblyUndefinedMethod>
</file>
<file src="src/Driver/Xdebug.php">
<UndefinedConstant occurrences="4">
<UndefinedConstant occurrences="5">
<code>\XDEBUG_FILTER_CODE_COVERAGE</code>
<code>\XDEBUG_PATH_WHITELIST</code>
<code>\XDEBUG_CC_UNUSED</code>
Expand Down
25 changes: 16 additions & 9 deletions src/CodeCoverage.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,6 @@ final class CodeCoverage
*/
private $isInitialized = false;

/**
* Determine whether we need to check for dead and unused code on each test
*
* @var bool
*/
private $shouldCheckForDeadAndUnused = true;

/**
* @var Directory
*/
Expand Down Expand Up @@ -242,7 +235,7 @@ public function start($id, bool $clear = false): void

$this->currentId = $id;

$this->driver->start($this->shouldCheckForDeadAndUnused);
$this->driver->start();
}

/**
Expand Down Expand Up @@ -417,6 +410,11 @@ public function setUnintentionallyCoveredSubclassesWhitelist(array $whitelist):
$this->unintentionallyCoveredSubclassesWhitelist = $whitelist;
}

public function setBranchAndPathCollection(bool $flag): void
{
$this->driver->collectBranchAndPathCoverage($flag);
}

/**
* Applies the @covers annotation filtering.
*
Expand Down Expand Up @@ -861,7 +859,11 @@ private function initializeData(): void
$this->isInitialized = true;

if ($this->processUncoveredFilesFromWhitelist) {
$this->shouldCheckForDeadAndUnused = false;

// by collecting dead code data here on an initial pass, future runs with test data do not need to
if ($this->driver->canDetectDeadCode()) {
$this->driver->detectDeadCode(true);
}

$this->driver->start();

Expand All @@ -888,6 +890,11 @@ private function initializeData(): void
}

$this->append(RawCodeCoverageData::fromXdebugWithoutPathCoverage($data), 'UNCOVERED_FILES_FROM_WHITELIST');

// having now collected dead code for the entire whitelist, we can safely skip this data on subsequent runs
if ($this->driver->canDetectDeadCode()) {
$this->driver->detectDeadCode(false);
}
}
}

Expand Down
62 changes: 59 additions & 3 deletions src/Driver/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
*/
namespace SebastianBergmann\CodeCoverage\Driver;

use SebastianBergmann\CodeCoverage\BranchAndPathCoverageNotSupportedException;
use SebastianBergmann\CodeCoverage\DeadCodeDetectionNotSupportedException;
use SebastianBergmann\CodeCoverage\RawCodeCoverageData;

/**
* Interface for code coverage drivers.
*/
interface Driver
abstract class Driver
{
/**
* @var int
Expand All @@ -37,13 +39,67 @@ interface Driver
*/
public const LINE_NOT_EXECUTABLE = -2;

protected $detectDeadCode = false;

protected $collectBranchAndPathCoverage = false;

/**
* Does this driver support detecting dead code?
*/
abstract public function canDetectDeadCode(): bool;

/**
* Does this driver support collecting branch and path coverage?
*/
abstract public function canCollectBranchAndPathCoverage(): bool;

/**
* Detect dead code
*/
public function detectDeadCode(bool $flag): void
{
if ($flag && !$this->canDetectDeadCode()) {
throw new DeadCodeDetectionNotSupportedException;
}

$this->detectDeadCode = $flag;
}

/**
* Collecting path coverage
*/
public function collectBranchAndPathCoverage(bool $flag): void
{
if ($flag && !$this->canCollectBranchAndPathCoverage()) {
throw new BranchAndPathCoverageNotSupportedException;
}

$this->collectBranchAndPathCoverage = $flag;
}

/**
* Is this driver detecting dead code?
*/
public function detectingDeadCode(): bool
{
return $this->detectDeadCode;
}

/**
* Is this driver collecting branch and path coverage?
*/
public function collectingBranchAndPathCoverage(): bool
{
return $this->collectBranchAndPathCoverage;
}

/**
* Start collection of code coverage information.
*/
public function start(bool $determineUnusedAndDead = true): void;
abstract public function start(): void;

/**
* Stop collection of code coverage information.
*/
public function stop(): RawCodeCoverageData;
abstract public function stop(): RawCodeCoverageData;
}
20 changes: 18 additions & 2 deletions src/Driver/PCOV.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* Driver for PCOV code coverage functionality.
*/
final class PCOV implements Driver
final class PCOV extends Driver
{
/**
* @var Filter
Expand All @@ -27,10 +27,26 @@ public function __construct(Filter $filter)
$this->filter = $filter;
}

/**
* Does this driver support detecting dead code?
*/
public function canDetectDeadCode(): bool
{
return false;
}

/**
* Does this driver support collecting path coverage?
*/
public function canCollectBranchAndPathCoverage(): bool
{
return false;
}

/**
* Start collection of code coverage information.
*/
public function start(bool $determineUnusedAndDead = true): void
public function start(): void
{
\pcov\start();
}
Expand Down
20 changes: 18 additions & 2 deletions src/Driver/PHPDBG.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* Driver for PHPDBG's code coverage functionality.
*/
final class PHPDBG implements Driver
final class PHPDBG extends Driver
{
/**
* @throws RuntimeException
Expand All @@ -35,10 +35,26 @@ public function __construct()
}
}

/**
* Does this driver support detecting dead code?
*/
public function canDetectDeadCode(): bool
{
return false;
}

/**
* Does this driver support collecting path coverage?
*/
public function canCollectBranchAndPathCoverage(): bool
{
return false;
}

/**
* Start collection of code coverage information.
*/
public function start(bool $determineUnusedAndDead = true): void
public function start(): void
{
\phpdbg_start_oplog();
}
Expand Down
39 changes: 33 additions & 6 deletions src/Driver/Xdebug.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/**
* Driver for Xdebug's code coverage functionality.
*/
final class Xdebug implements Driver
final class Xdebug extends Driver
{
/**
* @throws RuntimeException
Expand All @@ -32,18 +32,41 @@ public function __construct(Filter $filter)
}

\xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_WHITELIST, $filter->getWhitelist());
$this->detectDeadCode = true;
}

/**
* Does this driver support detecting dead code?
*/
public function canDetectDeadCode(): bool
{
return true;
}

/**
* Does this driver support collecting path coverage?
*/
public function canCollectBranchAndPathCoverage(): bool
{
return true;
}

/**
* Start collection of code coverage information.
*/
public function start(bool $determineUnusedAndDead = true): void
public function start(): void
{
if ($determineUnusedAndDead) {
\xdebug_start_code_coverage(\XDEBUG_CC_UNUSED | \XDEBUG_CC_DEAD_CODE);
} else {
\xdebug_start_code_coverage();
$flags = \XDEBUG_CC_UNUSED;

if ($this->detectDeadCode || $this->collectBranchAndPathCoverage) { // branch/path collection requires enabling dead code checks
$flags |= \XDEBUG_CC_DEAD_CODE;
}

if ($this->collectBranchAndPathCoverage) {
$flags |= \XDEBUG_CC_BRANCH_CHECK;
}

\xdebug_start_code_coverage($flags);
}

/**
Expand All @@ -55,6 +78,10 @@ public function stop(): RawCodeCoverageData

\xdebug_stop_code_coverage();

if ($this->collectBranchAndPathCoverage) {
return RawCodeCoverageData::fromXdebugWithPathCoverage($data);
}

return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data);
}
}
17 changes: 17 additions & 0 deletions src/Exception/BranchAndPathCoverageNotSupportedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php declare(strict_types=1);
/*
* This file is part of phpunit/php-code-coverage.
*
* (c) Sebastian Bergmann <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeCoverage;

/**
* Exception that is raised when branch and path coverage is not supported by the driver but is attempted to be used.
*/
final class BranchAndPathCoverageNotSupportedException extends RuntimeException
{
}
17 changes: 17 additions & 0 deletions src/Exception/DeadCodeDetectionNotSupportedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php declare(strict_types=1);
/*
* This file is part of phpunit/php-code-coverage.
*
* (c) Sebastian Bergmann <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeCoverage;

/**
* Exception that is raised when dead code detection is not supported by the driver but is attempted to be used.
*/
final class DeadCodeDetectionNotSupportedException extends RuntimeException
{
}
67 changes: 67 additions & 0 deletions tests/tests/Driver/PCOVTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php declare(strict_types=1);
/*
* This file is part of phpunit/php-code-coverage.
*
* (c) Sebastian Bergmann <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SebastianBergmann\CodeCoverage;

use SebastianBergmann\CodeCoverage\Driver\PCOV;
use SebastianBergmann\Environment\Runtime;

class PCOVTest extends TestCase
{
protected function setUp(): void
{
$runtime = new Runtime;

if (!$runtime->hasPCOV()) {
$this->markTestSkipped('This test is only applicable to PCOV');
}
}

public function testDefaultValueOfDeadCodeDetection(): void
{
$driver = new PCOV(new Filter());

$this->assertFalse($driver->detectingDeadCode());
}

public function testEnablingDeadCodeDetection(): void
{
$this->expectException(DeadCodeDetectionNotSupportedException::class);

$driver = new PCOV(new Filter());

$driver->detectDeadCode(true);
}

public function testDisablingDeadCodeDetection(): void
{
$driver = new PCOV(new Filter());

$driver->detectDeadCode(false);
$this->assertFalse($driver->detectingDeadCode());
}

public function testEnablingBranchAndPathCoverage(): void
{
$this->expectException(BranchAndPathCoverageNotSupportedException::class);

$driver = new PCOV(new Filter());

$driver->collectBranchAndPathCoverage(true);
$this->assertTrue($driver->collectingBranchAndPathCoverage());
}

public function testDisablingBranchAndPathCoverage(): void
{
$driver = new PCOV(new Filter());

$driver->collectBranchAndPathCoverage(false);
$this->assertFalse($driver->collectingBranchAndPathCoverage());
}
}
Loading

0 comments on commit ae99808

Please sign in to comment.