From ae998084fbb6815932afb7973ebdc2fa2facd446 Mon Sep 17 00:00:00 2001 From: Doug Wright Date: Mon, 18 May 2020 21:13:51 +0100 Subject: [PATCH] Rework how code coverage settings are propagated to the driver --- .psalm/baseline.xml | 2 +- src/CodeCoverage.php | 25 ++++--- src/Driver/Driver.php | 62 ++++++++++++++++- src/Driver/PCOV.php | 20 +++++- src/Driver/PHPDBG.php | 20 +++++- src/Driver/Xdebug.php | 39 +++++++++-- ...chAndPathCoverageNotSupportedException.php | 17 +++++ ...DeadCodeDetectionNotSupportedException.php | 17 +++++ tests/tests/Driver/PCOVTest.php | 67 +++++++++++++++++++ tests/tests/Driver/PHPDBGTest.php | 67 +++++++++++++++++++ tests/tests/Driver/XdebugTest.php | 43 +++++++++++- 11 files changed, 353 insertions(+), 26 deletions(-) create mode 100644 src/Exception/BranchAndPathCoverageNotSupportedException.php create mode 100644 src/Exception/DeadCodeDetectionNotSupportedException.php create mode 100644 tests/tests/Driver/PCOVTest.php create mode 100644 tests/tests/Driver/PHPDBGTest.php diff --git a/.psalm/baseline.xml b/.psalm/baseline.xml index b43b7c5da..68ea3d6e9 100644 --- a/.psalm/baseline.xml +++ b/.psalm/baseline.xml @@ -18,7 +18,7 @@ - + \XDEBUG_FILTER_CODE_COVERAGE \XDEBUG_PATH_WHITELIST \XDEBUG_CC_UNUSED diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php index 93de90665..7884e931c 100644 --- a/src/CodeCoverage.php +++ b/src/CodeCoverage.php @@ -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 */ @@ -242,7 +235,7 @@ public function start($id, bool $clear = false): void $this->currentId = $id; - $this->driver->start($this->shouldCheckForDeadAndUnused); + $this->driver->start(); } /** @@ -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. * @@ -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(); @@ -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); + } } } diff --git a/src/Driver/Driver.php b/src/Driver/Driver.php index aba17b011..284118d59 100644 --- a/src/Driver/Driver.php +++ b/src/Driver/Driver.php @@ -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 @@ -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; } diff --git a/src/Driver/PCOV.php b/src/Driver/PCOV.php index 379625494..5e51e74c8 100644 --- a/src/Driver/PCOV.php +++ b/src/Driver/PCOV.php @@ -15,7 +15,7 @@ /** * Driver for PCOV code coverage functionality. */ -final class PCOV implements Driver +final class PCOV extends Driver { /** * @var Filter @@ -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(); } diff --git a/src/Driver/PHPDBG.php b/src/Driver/PHPDBG.php index 126e846e7..b3481b5e2 100644 --- a/src/Driver/PHPDBG.php +++ b/src/Driver/PHPDBG.php @@ -15,7 +15,7 @@ /** * Driver for PHPDBG's code coverage functionality. */ -final class PHPDBG implements Driver +final class PHPDBG extends Driver { /** * @throws RuntimeException @@ -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(); } diff --git a/src/Driver/Xdebug.php b/src/Driver/Xdebug.php index 672ce0e70..e43811cd8 100644 --- a/src/Driver/Xdebug.php +++ b/src/Driver/Xdebug.php @@ -16,7 +16,7 @@ /** * Driver for Xdebug's code coverage functionality. */ -final class Xdebug implements Driver +final class Xdebug extends Driver { /** * @throws RuntimeException @@ -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); } /** @@ -55,6 +78,10 @@ public function stop(): RawCodeCoverageData \xdebug_stop_code_coverage(); + if ($this->collectBranchAndPathCoverage) { + return RawCodeCoverageData::fromXdebugWithPathCoverage($data); + } + return RawCodeCoverageData::fromXdebugWithoutPathCoverage($data); } } diff --git a/src/Exception/BranchAndPathCoverageNotSupportedException.php b/src/Exception/BranchAndPathCoverageNotSupportedException.php new file mode 100644 index 000000000..115914490 --- /dev/null +++ b/src/Exception/BranchAndPathCoverageNotSupportedException.php @@ -0,0 +1,17 @@ + + * + * 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 +{ +} diff --git a/src/Exception/DeadCodeDetectionNotSupportedException.php b/src/Exception/DeadCodeDetectionNotSupportedException.php new file mode 100644 index 000000000..4c2462ed3 --- /dev/null +++ b/src/Exception/DeadCodeDetectionNotSupportedException.php @@ -0,0 +1,17 @@ + + * + * 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 +{ +} diff --git a/tests/tests/Driver/PCOVTest.php b/tests/tests/Driver/PCOVTest.php new file mode 100644 index 000000000..6c8354dbb --- /dev/null +++ b/tests/tests/Driver/PCOVTest.php @@ -0,0 +1,67 @@ + + * + * 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()); + } +} diff --git a/tests/tests/Driver/PHPDBGTest.php b/tests/tests/Driver/PHPDBGTest.php new file mode 100644 index 000000000..f76db0b38 --- /dev/null +++ b/tests/tests/Driver/PHPDBGTest.php @@ -0,0 +1,67 @@ + + * + * 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\PHPDBG; +use SebastianBergmann\Environment\Runtime; + +class PHPDBGTest extends TestCase +{ + protected function setUp(): void + { + $runtime = new Runtime; + + if (!$runtime->hasPHPDBGCodeCoverage()) { + $this->markTestSkipped('This test is only applicable to PHPDBG'); + } + } + + public function testDefaultValueOfDeadCodeDetection(): void + { + $driver = new PHPDBG(); + + $this->assertFalse($driver->detectingDeadCode()); + } + + public function testEnablingDeadCodeDetection(): void + { + $this->expectException(DeadCodeDetectionNotSupportedException::class); + + $driver = new PHPDBG(); + + $driver->detectDeadCode(true); + } + + public function testDisablingDeadCodeDetection(): void + { + $driver = new PHPDBG(); + + $driver->detectDeadCode(false); + $this->assertFalse($driver->detectingDeadCode()); + } + + public function testEnablingBranchAndPathCoverage(): void + { + $this->expectException(BranchAndPathCoverageNotSupportedException::class); + + $driver = new PHPDBG(); + + $driver->collectBranchAndPathCoverage(true); + $this->assertTrue($driver->collectingBranchAndPathCoverage()); + } + + public function testDisablingBranchAndPathCoverage(): void + { + $driver = new PHPDBG(); + + $driver->collectBranchAndPathCoverage(false); + $this->assertFalse($driver->collectingBranchAndPathCoverage()); + } +} diff --git a/tests/tests/Driver/XdebugTest.php b/tests/tests/Driver/XdebugTest.php index d21953524..c30d595ca 100644 --- a/tests/tests/Driver/XdebugTest.php +++ b/tests/tests/Driver/XdebugTest.php @@ -9,11 +9,9 @@ */ namespace SebastianBergmann\CodeCoverage; +use SebastianBergmann\CodeCoverage\Driver\Xdebug; use SebastianBergmann\Environment\Runtime; -/** - * @covers SebastianBergmann\CodeCoverage\Driver\Xdebug - */ class XdebugTest extends TestCase { protected function setUp(): void @@ -36,4 +34,43 @@ public function testFilterWorks(): void require $bankAccount; $this->assertArrayNotHasKey($bankAccount, \xdebug_get_code_coverage()); } + + public function testDefaultValueOfDeadCodeDetection(): void + { + $driver = new Xdebug(new Filter()); + + $this->assertTrue($driver->detectingDeadCode()); + } + + public function testEnablingDeadCodeDetection(): void + { + $driver = new Xdebug(new Filter()); + + $driver->detectDeadCode(true); + $this->assertTrue($driver->detectingDeadCode()); + } + + public function testDisablingDeadCodeDetection(): void + { + $driver = new Xdebug(new Filter()); + + $driver->detectDeadCode(false); + $this->assertFalse($driver->detectingDeadCode()); + } + + public function testEnablingBranchAndPathCoverage(): void + { + $driver = new Xdebug(new Filter()); + + $driver->collectBranchAndPathCoverage(true); + $this->assertTrue($driver->collectingBranchAndPathCoverage()); + } + + public function testDisablingBranchAndPathCoverage(): void + { + $driver = new Xdebug(new Filter()); + + $driver->collectBranchAndPathCoverage(false); + $this->assertFalse($driver->collectingBranchAndPathCoverage()); + } }