diff --git a/.psalm/baseline.xml b/.psalm/baseline.xml
index 646ce3a36..68ea3d6e9 100644
--- a/.psalm/baseline.xml
+++ b/.psalm/baseline.xml
@@ -18,22 +18,14 @@
-
- XDEBUG_CC_UNUSED
- XDEBUG_CC_DEAD_CODE
+
+ \XDEBUG_FILTER_CODE_COVERAGE
+ \XDEBUG_PATH_WHITELIST
+ \XDEBUG_CC_UNUSED
+ \XDEBUG_CC_DEAD_CODE
-
-
- int|string
- int|string
- int|string
- int|string
- int|string
- int|string
- int|string
-
$parent
diff --git a/src/CodeCoverage.php b/src/CodeCoverage.php
index 765629d33..7884e931c 100644
--- a/src/CodeCoverage.php
+++ b/src/CodeCoverage.php
@@ -89,9 +89,9 @@ final class CodeCoverage
/**
* Code coverage data.
*
- * @var array
+ * @var ProcessedCodeCoverageData
*/
- private $data = [];
+ private $data;
/**
* @var array
@@ -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
*/
@@ -151,6 +144,7 @@ public function __construct(Driver $driver = null, Filter $filter = null)
$this->filter = $filter;
$this->wizard = new Wizard;
+ $this->data = new ProcessedCodeCoverageData;
}
/**
@@ -172,7 +166,7 @@ public function clear(): void
{
$this->isInitialized = false;
$this->currentId = null;
- $this->data = [];
+ $this->data = new ProcessedCodeCoverageData();
$this->tests = [];
$this->report = null;
}
@@ -188,7 +182,7 @@ public function filter(): Filter
/**
* Returns the collected code coverage data.
*/
- public function getData(bool $raw = false): array
+ public function getData(bool $raw = false): ProcessedCodeCoverageData
{
if (!$raw && $this->addUncoveredFilesFromWhitelist) {
$this->addUncoveredFilesFromWhitelist();
@@ -200,7 +194,7 @@ public function getData(bool $raw = false): array
/**
* Sets the coverage data.
*/
- public function setData(array $data): void
+ public function setData(ProcessedCodeCoverageData $data): void
{
$this->data = $data;
$this->report = null;
@@ -241,7 +235,7 @@ public function start($id, bool $clear = false): void
$this->currentId = $id;
- $this->driver->start($this->shouldCheckForDeadAndUnused);
+ $this->driver->start();
}
/**
@@ -255,7 +249,7 @@ public function start($id, bool $clear = false): void
* @throws InvalidArgumentException
* @throws \ReflectionException
*/
- public function stop(bool $append = true, $linesToBeCovered = [], array $linesToBeUsed = [], bool $ignoreForceCoversAnnotation = false): array
+ public function stop(bool $append = true, $linesToBeCovered = [], array $linesToBeUsed = [], bool $ignoreForceCoversAnnotation = false): RawCodeCoverageData
{
if (!\is_array($linesToBeCovered) && $linesToBeCovered !== false) {
throw InvalidArgumentException::create(
@@ -285,7 +279,7 @@ public function stop(bool $append = true, $linesToBeCovered = [], array $linesTo
* @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
* @throws RuntimeException
*/
- public function append(array $data, $id = null, bool $append = true, $linesToBeCovered = [], array $linesToBeUsed = [], bool $ignoreForceCoversAnnotation = false): void
+ public function append(RawCodeCoverageData $rawData, $id = null, bool $append = true, $linesToBeCovered = [], array $linesToBeUsed = [], bool $ignoreForceCoversAnnotation = false): void
{
if ($id === null) {
$id = $this->currentId;
@@ -295,9 +289,9 @@ public function append(array $data, $id = null, bool $append = true, $linesToBeC
throw new RuntimeException;
}
- $this->applyWhitelistFilter($data);
- $this->applyIgnoredLinesFilter($data);
- $this->initializeFilesThatAreSeenTheFirstTime($data);
+ $this->applyWhitelistFilter($rawData);
+ $this->applyIgnoredLinesFilter($rawData);
+ $this->data->initializeFilesThatAreSeenTheFirstTime($rawData);
if (!$append) {
return;
@@ -305,14 +299,14 @@ public function append(array $data, $id = null, bool $append = true, $linesToBeC
if ($id !== 'UNCOVERED_FILES_FROM_WHITELIST') {
$this->applyCoversAnnotationFilter(
- $data,
+ $rawData,
$linesToBeCovered,
$linesToBeUsed,
$ignoreForceCoversAnnotation
);
}
- if (empty($data)) {
+ if (empty($rawData->getLineCoverage())) {
return;
}
@@ -339,19 +333,7 @@ public function append(array $data, $id = null, bool $append = true, $linesToBeC
$this->tests[$id] = ['size' => $size, 'status' => $status];
- foreach ($data as $file => $lines) {
- if (!$this->filter->isFile($file)) {
- continue;
- }
-
- foreach ($lines as $k => $v) {
- if ($v === Driver::LINE_EXECUTED) {
- if (empty($this->data[$file][$k]) || !\in_array($id, $this->data[$file][$k])) {
- $this->data[$file][$k][] = $id;
- }
- }
- }
- }
+ $this->data->markCodeAsExecutedByTestCase($id, $rawData);
$this->report = null;
}
@@ -367,36 +349,7 @@ public function merge(self $that): void
\array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles())
);
- foreach ($that->data as $file => $lines) {
- if (!isset($this->data[$file])) {
- if (!$this->filter->isFiltered($file)) {
- $this->data[$file] = $lines;
- }
-
- continue;
- }
-
- // we should compare the lines if any of two contains data
- $compareLineNumbers = \array_unique(
- \array_merge(
- \array_keys($this->data[$file]),
- \array_keys($that->data[$file])
- )
- );
-
- foreach ($compareLineNumbers as $line) {
- $thatPriority = $this->getLinePriority($that->data[$file], $line);
- $thisPriority = $this->getLinePriority($this->data[$file], $line);
-
- if ($thatPriority > $thisPriority) {
- $this->data[$file][$line] = $that->data[$file][$line];
- } elseif ($thatPriority === $thisPriority && \is_array($this->data[$file][$line])) {
- $this->data[$file][$line] = \array_unique(
- \array_merge($this->data[$file][$line], $that->data[$file][$line])
- );
- }
- }
- }
+ $this->data->merge($that->data);
$this->tests = \array_merge($this->tests, $that->getTests());
$this->report = null;
@@ -457,36 +410,9 @@ public function setUnintentionallyCoveredSubclassesWhitelist(array $whitelist):
$this->unintentionallyCoveredSubclassesWhitelist = $whitelist;
}
- /**
- * Determine the priority for a line
- *
- * 1 = the line is not set
- * 2 = the line has not been tested
- * 3 = the line is dead code
- * 4 = the line has been tested
- *
- * During a merge, a higher number is better.
- *
- * @param array $data
- * @param int $line
- *
- * @return int
- */
- private function getLinePriority($data, $line)
+ public function setBranchAndPathCollection(bool $flag): void
{
- if (!\array_key_exists($line, $data)) {
- return 1;
- }
-
- if (\is_array($data[$line]) && \count($data[$line]) === 0) {
- return 2;
- }
-
- if ($data[$line] === null) {
- return 3;
- }
-
- return 4;
+ $this->driver->collectBranchAndPathCoverage($flag);
}
/**
@@ -499,7 +425,7 @@ private function getLinePriority($data, $line)
* @throws MissingCoversAnnotationException
* @throws UnintentionallyCoveredCodeException
*/
- private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed, bool $ignoreForceCoversAnnotation): void
+ private function applyCoversAnnotationFilter(RawCodeCoverageData $rawData, $linesToBeCovered, array $linesToBeUsed, bool $ignoreForceCoversAnnotation): void
{
if ($linesToBeCovered === false ||
($this->forceCoversAnnotation && empty($linesToBeCovered) && !$ignoreForceCoversAnnotation)) {
@@ -507,7 +433,7 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar
throw new MissingCoversAnnotationException;
}
- $data = [];
+ $rawData->clear();
return;
}
@@ -519,26 +445,32 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar
if ($this->checkForUnintentionallyCoveredCode &&
(!$this->currentId instanceof TestCase ||
(!$this->currentId->isMedium() && !$this->currentId->isLarge()))) {
- $this->performUnintentionallyCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed);
+ $this->performUnintentionallyCoveredCodeCheck($rawData, $linesToBeCovered, $linesToBeUsed);
}
if ($this->checkForUnexecutedCoveredCode) {
- $this->performUnexecutedCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed);
+ $this->performUnexecutedCoveredCodeCheck($rawData, $linesToBeCovered, $linesToBeUsed);
}
- $data = \array_intersect_key($data, $linesToBeCovered);
+ $rawLineData = $rawData->getLineCoverage();
+ $filesWithNoCoverage = \array_diff_key($rawLineData, $linesToBeCovered);
- foreach (\array_keys($data) as $filename) {
- $_linesToBeCovered = \array_flip($linesToBeCovered[$filename]);
- $data[$filename] = \array_intersect_key($data[$filename], $_linesToBeCovered);
+ foreach (\array_keys($filesWithNoCoverage) as $fileWithNoCoverage) {
+ $rawData->removeCoverageDataForFile($fileWithNoCoverage);
+ }
+
+ if (\is_array($linesToBeCovered)) {
+ foreach ($linesToBeCovered as $fileToBeCovered => $includedLines) {
+ $rawData->keepCoverageDataOnlyForLines($fileToBeCovered, $includedLines);
+ }
}
}
- private function applyWhitelistFilter(array &$data): void
+ private function applyWhitelistFilter(RawCodeCoverageData $data): void
{
- foreach (\array_keys($data) as $filename) {
+ foreach (\array_keys($data->getLineCoverage()) as $filename) {
if ($this->filter->isFiltered($filename)) {
- unset($data[$filename]);
+ $data->removeCoverageDataForFile($filename);
}
}
}
@@ -546,29 +478,10 @@ private function applyWhitelistFilter(array &$data): void
/**
* @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
*/
- private function applyIgnoredLinesFilter(array &$data): void
- {
- foreach (\array_keys($data) as $filename) {
- if (!$this->filter->isFile($filename)) {
- continue;
- }
-
- foreach ($this->getLinesToBeIgnored($filename) as $line) {
- unset($data[$filename][$line]);
- }
- }
- }
-
- private function initializeFilesThatAreSeenTheFirstTime(array $data): void
+ private function applyIgnoredLinesFilter(RawCodeCoverageData $data): void
{
- foreach ($data as $file => $lines) {
- if (!isset($this->data[$file]) && $this->filter->isFile($file)) {
- $this->data[$file] = [];
-
- foreach ($lines as $k => $v) {
- $this->data[$file][$k] = $v === -2 ? null : [];
- }
- }
+ foreach (\array_keys($data->getLineCoverage()) as $filename) {
+ $data->removeCoverageDataForLines($filename, $this->getLinesToBeIgnored($filename));
}
}
@@ -585,7 +498,7 @@ private function addUncoveredFilesFromWhitelist(): void
$data = [];
$uncoveredFiles = \array_diff(
$this->filter->getWhitelist(),
- \array_keys($this->data)
+ \array_keys($this->data->getLineCoverage())
);
foreach ($uncoveredFiles as $uncoveredFile) {
@@ -602,7 +515,7 @@ private function addUncoveredFilesFromWhitelist(): void
}
}
- $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
+ $this->append(RawCodeCoverageData::fromXdebugWithoutPathCoverage($data), 'UNCOVERED_FILES_FROM_WHITELIST');
}
private function getLinesToBeIgnored(string $fileName): array
@@ -793,7 +706,7 @@ private function getLinesToBeIgnoredInner(string $fileName): array
* @throws \ReflectionException
* @throws UnintentionallyCoveredCodeException
*/
- private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed): void
+ private function performUnintentionallyCoveredCodeCheck(RawCodeCoverageData $data, array $linesToBeCovered, array $linesToBeUsed): void
{
$allowedLines = $this->getAllowedLines(
$linesToBeCovered,
@@ -802,7 +715,7 @@ private function performUnintentionallyCoveredCodeCheck(array &$data, array $lin
$unintentionallyCoveredUnits = [];
- foreach ($data as $file => $_data) {
+ foreach ($data->getLineCoverage() as $file => $_data) {
foreach ($_data as $line => $flag) {
if ($flag === 1 && !isset($allowedLines[$file][$line])) {
$unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line);
@@ -822,9 +735,9 @@ private function performUnintentionallyCoveredCodeCheck(array &$data, array $lin
/**
* @throws CoveredCodeNotExecutedException
*/
- private function performUnexecutedCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed): void
+ private function performUnexecutedCoveredCodeCheck(RawCodeCoverageData $rawData, array $linesToBeCovered, array $linesToBeUsed): void
{
- $executedCodeUnits = $this->coverageToCodeUnits($data);
+ $executedCodeUnits = $this->coverageToCodeUnits($rawData);
$message = '';
foreach ($this->linesToCodeUnits($linesToBeCovered) as $codeUnit) {
@@ -897,7 +810,7 @@ private function selectDriver(Filter $filter): Driver
}
if ($runtime->hasPCOV()) {
- return new PCOV;
+ return new PCOV($filter);
}
if ($runtime->hasXdebug()) {
@@ -946,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();
@@ -958,7 +875,7 @@ private function initializeData(): void
$data = [];
- foreach ($this->driver->stop() as $file => $fileCoverage) {
+ foreach ($this->driver->stop()->getLineCoverage() as $file => $fileCoverage) {
if ($this->filter->isFiltered($file)) {
continue;
}
@@ -972,15 +889,20 @@ private function initializeData(): void
$data[$file] = $fileCoverage;
}
- $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
+ $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);
+ }
}
}
- private function coverageToCodeUnits(array $data): array
+ private function coverageToCodeUnits(RawCodeCoverageData $rawData): array
{
$codeUnits = [];
- foreach ($data as $filename => $lines) {
+ foreach ($rawData->getLineCoverage() as $filename => $lines) {
foreach ($lines as $line => $flag) {
if ($flag === 1) {
$codeUnits[] = $this->wizard->lookup($filename, $line);
diff --git a/src/Driver/Driver.php b/src/Driver/Driver.php
index 8a5f4bf08..284118d59 100644
--- a/src/Driver/Driver.php
+++ b/src/Driver/Driver.php
@@ -9,10 +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
@@ -35,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(): array;
+ abstract public function stop(): RawCodeCoverageData;
}
diff --git a/src/Driver/PCOV.php b/src/Driver/PCOV.php
index d3d218993..5e51e74c8 100644
--- a/src/Driver/PCOV.php
+++ b/src/Driver/PCOV.php
@@ -9,15 +9,44 @@
*/
namespace SebastianBergmann\CodeCoverage\Driver;
+use SebastianBergmann\CodeCoverage\Filter;
+use SebastianBergmann\CodeCoverage\RawCodeCoverageData;
+
/**
* Driver for PCOV code coverage functionality.
*/
-final class PCOV implements Driver
+final class PCOV extends Driver
{
+ /**
+ * @var Filter
+ */
+ private $filter;
+
+ 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();
}
@@ -25,19 +54,14 @@ public function start(bool $determineUnusedAndDead = true): void
/**
* Stop collection of code coverage information.
*/
- public function stop(): array
+ public function stop(): RawCodeCoverageData
{
\pcov\stop();
- $waiting = \pcov\waiting();
- $collect = [];
-
- if ($waiting) {
- $collect = \pcov\collect(\pcov\inclusive, $waiting);
+ $collect = \pcov\collect(\pcov\inclusive, $this->filter->getWhitelist());
- \pcov\clear();
- }
+ \pcov\clear();
- return $collect;
+ return RawCodeCoverageData::fromXdebugWithoutPathCoverage($collect);
}
}
diff --git a/src/Driver/PHPDBG.php b/src/Driver/PHPDBG.php
index 4739ae41f..b3481b5e2 100644
--- a/src/Driver/PHPDBG.php
+++ b/src/Driver/PHPDBG.php
@@ -9,12 +9,13 @@
*/
namespace SebastianBergmann\CodeCoverage\Driver;
+use SebastianBergmann\CodeCoverage\RawCodeCoverageData;
use SebastianBergmann\CodeCoverage\RuntimeException;
/**
* Driver for PHPDBG's code coverage functionality.
*/
-final class PHPDBG implements Driver
+final class PHPDBG extends Driver
{
/**
* @throws RuntimeException
@@ -34,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();
}
@@ -45,7 +62,7 @@ public function start(bool $determineUnusedAndDead = true): void
/**
* Stop collection of code coverage information.
*/
- public function stop(): array
+ public function stop(): RawCodeCoverageData
{
static $fetchedLines = [];
@@ -71,7 +88,7 @@ public function stop(): array
$fetchedLines = \array_merge($fetchedLines, $sourceLines);
- return $this->detectExecutedLines($fetchedLines, $dbgData);
+ return RawCodeCoverageData::fromXdebugWithoutPathCoverage($this->detectExecutedLines($fetchedLines, $dbgData));
}
/**
diff --git a/src/Driver/Xdebug.php b/src/Driver/Xdebug.php
index 401d82b3c..e43811cd8 100644
--- a/src/Driver/Xdebug.php
+++ b/src/Driver/Xdebug.php
@@ -10,27 +10,18 @@
namespace SebastianBergmann\CodeCoverage\Driver;
use SebastianBergmann\CodeCoverage\Filter;
+use SebastianBergmann\CodeCoverage\RawCodeCoverageData;
use SebastianBergmann\CodeCoverage\RuntimeException;
/**
* Driver for Xdebug's code coverage functionality.
*/
-final class Xdebug implements Driver
+final class Xdebug extends Driver
{
- /**
- * @var array
- */
- private $cacheNumLines = [];
-
- /**
- * @var Filter
- */
- private $filter;
-
/**
* @throws RuntimeException
*/
- public function __construct(Filter $filter = null)
+ public function __construct(Filter $filter)
{
if (!\extension_loaded('xdebug')) {
throw new RuntimeException('This driver requires Xdebug');
@@ -40,71 +31,57 @@ public function __construct(Filter $filter = null)
throw new RuntimeException('xdebug.coverage_enable=On has to be set in php.ini');
}
- if ($filter === null) {
- $filter = new Filter;
- }
-
- $this->filter = $filter;
+ \xdebug_set_filter(\XDEBUG_FILTER_CODE_COVERAGE, \XDEBUG_PATH_WHITELIST, $filter->getWhitelist());
+ $this->detectDeadCode = true;
}
/**
- * Start collection of code coverage information.
+ * Does this driver support detecting dead code?
*/
- public function start(bool $determineUnusedAndDead = true): void
+ public function canDetectDeadCode(): bool
{
- if ($determineUnusedAndDead) {
- \xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
- } else {
- \xdebug_start_code_coverage();
- }
+ return true;
}
/**
- * Stop collection of code coverage information.
+ * Does this driver support collecting path coverage?
*/
- public function stop(): array
+ public function canCollectBranchAndPathCoverage(): bool
{
- $data = \xdebug_get_code_coverage();
-
- \xdebug_stop_code_coverage();
-
- return $this->cleanup($data);
+ return true;
}
- private function cleanup(array $data): array
+ /**
+ * Start collection of code coverage information.
+ */
+ public function start(): void
{
- foreach (\array_keys($data) as $file) {
- unset($data[$file][0]);
-
- if (!$this->filter->isFile($file)) {
- continue;
- }
+ $flags = \XDEBUG_CC_UNUSED;
- $numLines = $this->getNumberOfLinesInFile($file);
+ if ($this->detectDeadCode || $this->collectBranchAndPathCoverage) { // branch/path collection requires enabling dead code checks
+ $flags |= \XDEBUG_CC_DEAD_CODE;
+ }
- foreach (\array_keys($data[$file]) as $line) {
- if ($line > $numLines) {
- unset($data[$file][$line]);
- }
- }
+ if ($this->collectBranchAndPathCoverage) {
+ $flags |= \XDEBUG_CC_BRANCH_CHECK;
}
- return $data;
+ \xdebug_start_code_coverage($flags);
}
- private function getNumberOfLinesInFile(string $fileName): int
+ /**
+ * Stop collection of code coverage information.
+ */
+ public function stop(): RawCodeCoverageData
{
- if (!isset($this->cacheNumLines[$fileName])) {
- $buffer = \file_get_contents($fileName);
- $lines = \substr_count($buffer, "\n");
+ $data = \xdebug_get_code_coverage();
- if (\substr($buffer, -1) !== "\n") {
- $lines++;
- }
+ \xdebug_stop_code_coverage();
- $this->cacheNumLines[$fileName] = $lines;
+ if ($this->collectBranchAndPathCoverage) {
+ return RawCodeCoverageData::fromXdebugWithPathCoverage($data);
}
- return $this->cacheNumLines[$fileName];
+ 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/src/Exception/UnintentionallyCoveredCodeException.php b/src/Exception/UnintentionallyCoveredCodeException.php
index efeec8d9f..12c9ede72 100644
--- a/src/Exception/UnintentionallyCoveredCodeException.php
+++ b/src/Exception/UnintentionallyCoveredCodeException.php
@@ -17,7 +17,7 @@ final class UnintentionallyCoveredCodeException extends RuntimeException
/**
* @var array
*/
- private $unintentionallyCoveredUnits = [];
+ private $unintentionallyCoveredUnits;
public function __construct(array $unintentionallyCoveredUnits)
{
diff --git a/src/Node/AbstractNode.php b/src/Node/AbstractNode.php
index 411c04e4c..3c61ca7ca 100644
--- a/src/Node/AbstractNode.php
+++ b/src/Node/AbstractNode.php
@@ -43,7 +43,7 @@ abstract class AbstractNode implements \Countable
public function __construct(string $name, self $parent = null)
{
- if (\substr($name, -1) == \DIRECTORY_SEPARATOR) {
+ if (\substr($name, -1) === \DIRECTORY_SEPARATOR) {
$name = \substr($name, 0, -1);
}
@@ -113,7 +113,7 @@ public function getParent(): ?self
/**
* Returns the percentage of classes that has been tested.
*
- * @return int|string
+ * @return float|int|string
*/
public function getTestedClassesPercent(bool $asString = true)
{
@@ -127,7 +127,7 @@ public function getTestedClassesPercent(bool $asString = true)
/**
* Returns the percentage of traits that has been tested.
*
- * @return int|string
+ * @return float|int|string
*/
public function getTestedTraitsPercent(bool $asString = true)
{
@@ -141,7 +141,7 @@ public function getTestedTraitsPercent(bool $asString = true)
/**
* Returns the percentage of classes and traits that has been tested.
*
- * @return int|string
+ * @return float|int|string
*/
public function getTestedClassesAndTraitsPercent(bool $asString = true)
{
@@ -155,7 +155,7 @@ public function getTestedClassesAndTraitsPercent(bool $asString = true)
/**
* Returns the percentage of functions that has been tested.
*
- * @return int|string
+ * @return float|int|string
*/
public function getTestedFunctionsPercent(bool $asString = true)
{
@@ -169,7 +169,7 @@ public function getTestedFunctionsPercent(bool $asString = true)
/**
* Returns the percentage of methods that has been tested.
*
- * @return int|string
+ * @return float|int|string
*/
public function getTestedMethodsPercent(bool $asString = true)
{
@@ -183,7 +183,7 @@ public function getTestedMethodsPercent(bool $asString = true)
/**
* Returns the percentage of functions and methods that has been tested.
*
- * @return int|string
+ * @return float|int|string
*/
public function getTestedFunctionsAndMethodsPercent(bool $asString = true)
{
@@ -197,7 +197,7 @@ public function getTestedFunctionsAndMethodsPercent(bool $asString = true)
/**
* Returns the percentage of executed lines.
*
- * @return int|string
+ * @return float|int|string
*/
public function getLineExecutedPercent(bool $asString = true)
{
diff --git a/src/Node/Builder.php b/src/Node/Builder.php
index 7c20ba752..bd75b4f6b 100644
--- a/src/Node/Builder.php
+++ b/src/Node/Builder.php
@@ -10,13 +10,14 @@
namespace SebastianBergmann\CodeCoverage\Node;
use SebastianBergmann\CodeCoverage\CodeCoverage;
+use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData;
final class Builder
{
public function build(CodeCoverage $coverage): Directory
{
- $files = $coverage->getData();
- $commonPath = $this->reducePaths($files);
+ $data = clone $coverage->getData(); // clone because path munging is destructive to the original data
+ $commonPath = $this->reducePaths($data);
$root = new Directory(
$commonPath,
null
@@ -24,7 +25,7 @@ public function build(CodeCoverage $coverage): Directory
$this->addItems(
$root,
- $this->buildDirectoryStructure($files),
+ $this->buildDirectoryStructure($data),
$coverage->getTests(),
$coverage->getCacheTokens()
);
@@ -90,8 +91,9 @@ private function addItems(Directory $root, array $items, array $tests, bool $cac
* )
*
*/
- private function buildDirectoryStructure(array $files): array
+ private function buildDirectoryStructure(ProcessedCodeCoverageData $data): array
{
+ $files = $data->getLineCoverage();
$result = [];
foreach ($files as $path => $file) {
@@ -152,20 +154,18 @@ private function buildDirectoryStructure(array $files): array
* )
*
*/
- private function reducePaths(array &$files): string
+ private function reducePaths(ProcessedCodeCoverageData $coverage): string
{
- if (empty($files)) {
+ if (empty($coverage->getCoveredFiles())) {
return '.';
}
$commonPath = '';
- $paths = \array_keys($files);
+ $paths = $coverage->getCoveredFiles();
- if (\count($files) === 1) {
+ if (\count($paths) === 1) {
$commonPath = \dirname($paths[0]) . \DIRECTORY_SEPARATOR;
- $files[\basename($paths[0])] = $files[$paths[0]];
-
- unset($files[$paths[0]]);
+ $coverage->renameFile($paths[0], \basename($paths[0]));
return $commonPath;
}
@@ -212,16 +212,13 @@ private function reducePaths(array &$files): string
}
}
- $original = \array_keys($files);
+ $original = $coverage->getCoveredFiles();
$max = \count($original);
for ($i = 0; $i < $max; $i++) {
- $files[\implode(\DIRECTORY_SEPARATOR, $paths[$i])] = $files[$original[$i]];
- unset($files[$original[$i]]);
+ $coverage->renameFile($original[$i], \implode(\DIRECTORY_SEPARATOR, $paths[$i]));
}
- \ksort($files);
-
return \substr($commonPath, 0, -1);
}
}
diff --git a/src/Node/File.php b/src/Node/File.php
index 5268edc5a..e6e87ba53 100644
--- a/src/Node/File.php
+++ b/src/Node/File.php
@@ -518,6 +518,10 @@ private function processTraits(\PHP_Token_Stream $tokens): void
$link = $this->getId() . '.html#';
foreach ($traits as $traitName => $trait) {
+ if (!empty($trait['package']['namespace'])) {
+ $traitName = $trait['package']['namespace'] . '\\' . $traitName;
+ }
+
$this->traits[$traitName] = [
'traitName' => $traitName,
'methods' => [],
diff --git a/src/ProcessedCodeCoverageData.php b/src/ProcessedCodeCoverageData.php
new file mode 100644
index 000000000..5564ef84b
--- /dev/null
+++ b/src/ProcessedCodeCoverageData.php
@@ -0,0 +1,134 @@
+
+ *
+ * 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\Driver;
+
+/**
+ * Processed/context-added code coverage data for SUT.
+ */
+final class ProcessedCodeCoverageData
+{
+ /**
+ * Line coverage data.
+ * An array of filenames, each having an array of linenumbers, each executable line having an array of testcase ids.
+ *
+ * @var array
+ */
+ private $lineCoverage = [];
+
+ public function initializeFilesThatAreSeenTheFirstTime(RawCodeCoverageData $rawData): void
+ {
+ foreach ($rawData->getLineCoverage() as $file => $lines) {
+ if (!isset($this->lineCoverage[$file])) {
+ $this->lineCoverage[$file] = [];
+
+ foreach ($lines as $k => $v) {
+ $this->lineCoverage[$file][$k] = $v === Driver::LINE_NOT_EXECUTABLE ? null : [];
+ }
+ }
+ }
+ }
+
+ public function markCodeAsExecutedByTestCase(string $testCaseId, RawCodeCoverageData $executedCode): void
+ {
+ foreach ($executedCode->getLineCoverage() as $file => $lines) {
+ foreach ($lines as $k => $v) {
+ if ($v === Driver::LINE_EXECUTED) {
+ if (empty($this->lineCoverage[$file][$k]) || !\in_array($testCaseId, $this->lineCoverage[$file][$k], true)) {
+ $this->lineCoverage[$file][$k][] = $testCaseId;
+ }
+ }
+ }
+ }
+ }
+
+ public function setLineCoverage(array $lineCoverage): void
+ {
+ $this->lineCoverage = $lineCoverage;
+ }
+
+ public function getLineCoverage(): array
+ {
+ \ksort($this->lineCoverage);
+
+ return $this->lineCoverage;
+ }
+
+ public function getCoveredFiles(): array
+ {
+ return \array_keys($this->lineCoverage);
+ }
+
+ public function renameFile(string $oldFile, string $newFile): void
+ {
+ $this->lineCoverage[$newFile] = $this->lineCoverage[$oldFile];
+ unset($this->lineCoverage[$oldFile]);
+ }
+
+ public function merge(self $newData): void
+ {
+ foreach ($newData->lineCoverage as $file => $lines) {
+ if (!isset($this->lineCoverage[$file])) {
+ $this->lineCoverage[$file] = $lines;
+
+ continue;
+ }
+
+ // we should compare the lines if any of two contains data
+ $compareLineNumbers = \array_unique(
+ \array_merge(
+ \array_keys($this->lineCoverage[$file]),
+ \array_keys($newData->lineCoverage[$file])
+ )
+ );
+
+ foreach ($compareLineNumbers as $line) {
+ $thatPriority = $this->getLinePriority($newData->lineCoverage[$file], $line);
+ $thisPriority = $this->getLinePriority($this->lineCoverage[$file], $line);
+
+ if ($thatPriority > $thisPriority) {
+ $this->lineCoverage[$file][$line] = $newData->lineCoverage[$file][$line];
+ } elseif ($thatPriority === $thisPriority && \is_array($this->lineCoverage[$file][$line])) {
+ $this->lineCoverage[$file][$line] = \array_unique(
+ \array_merge($this->lineCoverage[$file][$line], $newData->lineCoverage[$file][$line])
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Determine the priority for a line
+ *
+ * 1 = the line is not set
+ * 2 = the line has not been tested
+ * 3 = the line is dead code
+ * 4 = the line has been tested
+ *
+ * During a merge, a higher number is better.
+ */
+ private function getLinePriority(array $data, int $line): int
+ {
+ if (!\array_key_exists($line, $data)) {
+ return 1;
+ }
+
+ if (\is_array($data[$line]) && \count($data[$line]) === 0) {
+ return 2;
+ }
+
+ if ($data[$line] === null) {
+ return 3;
+ }
+
+ return 4;
+ }
+}
diff --git a/src/RawCodeCoverageData.php b/src/RawCodeCoverageData.php
new file mode 100644
index 000000000..bd2ab653d
--- /dev/null
+++ b/src/RawCodeCoverageData.php
@@ -0,0 +1,81 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage;
+
+/**
+ * Raw context-less code coverage data for SUT.
+ */
+final class RawCodeCoverageData
+{
+ /**
+ * @var array
+ *
+ * @see https://xdebug.org/docs/code_coverage for format
+ */
+ private $lineCoverage = [];
+
+ public static function fromXdebugWithoutPathCoverage(array $rawCoverage): self
+ {
+ return new self($rawCoverage);
+ }
+
+ public static function fromXdebugWithPathCoverage(array $rawCoverage): self
+ {
+ $lineCoverage = [];
+
+ foreach ($rawCoverage as $file => $fileCoverageData) {
+ $lineCoverage[$file] = $fileCoverageData['lines'];
+ }
+
+ return new self($lineCoverage);
+ }
+
+ private function __construct(array $lineCoverage)
+ {
+ $this->lineCoverage = $lineCoverage;
+ }
+
+ public function clear(): void
+ {
+ $this->lineCoverage = [];
+ }
+
+ public function getLineCoverage(): array
+ {
+ return $this->lineCoverage;
+ }
+
+ public function removeCoverageDataForFile(string $filename): void
+ {
+ unset($this->lineCoverage[$filename]);
+ }
+
+ /**
+ * @param int[] $lines
+ */
+ public function keepCoverageDataOnlyForLines(string $filename, array $lines): void
+ {
+ $this->lineCoverage[$filename] = \array_intersect_key(
+ $this->lineCoverage[$filename],
+ \array_flip($lines)
+ );
+ }
+
+ /**
+ * @param int[] $lines
+ */
+ public function removeCoverageDataForLines(string $filename, array $lines): void
+ {
+ $this->lineCoverage[$filename] = \array_diff_key(
+ $this->lineCoverage[$filename],
+ \array_flip($lines)
+ );
+ }
+}
diff --git a/src/Report/Html/Renderer/Template/css/style.css b/src/Report/Html/Renderer/Template/css/style.css
index 6d9c21e89..c47011d34 100644
--- a/src/Report/Html/Renderer/Template/css/style.css
+++ b/src/Report/Html/Renderer/Template/css/style.css
@@ -48,7 +48,7 @@ body {
background-color: #f2dede;
}
-.table tbody td.warning, li.warning, span.warning {
+.table tbody tr.warning, .table tbody td.warning, li.warning, span.warning {
background-color: #fcf8e3;
}
diff --git a/src/Report/PHP.php b/src/Report/PHP.php
index 92926e3cf..61f00a004 100644
--- a/src/Report/PHP.php
+++ b/src/Report/PHP.php
@@ -22,21 +22,10 @@ final class PHP
*/
public function process(CodeCoverage $coverage, ?string $target = null): string
{
- $filter = $coverage->filter();
-
$buffer = \sprintf(
'setData(%s);
-$coverage->setTests(%s);
-
-$filter = $coverage->filter();
-$filter->setWhitelistedFiles(%s);
-
-return $coverage;',
- \var_export($coverage->getData(true), true),
- \var_export($coverage->getTests(), true),
- \var_export($filter->getWhitelistedFiles(), true)
+return \unserialize(\'%s\');',
+ \serialize($coverage)
);
if ($target !== null) {
diff --git a/src/Report/Text.php b/src/Report/Text.php
index 1cbbe2ab5..413a3f32d 100644
--- a/src/Report/Text.php
+++ b/src/Report/Text.php
@@ -200,16 +200,14 @@ public function process(CodeCoverage $coverage, bool $showColors = false): strin
}
}
- $namespace = '';
+ $package = '';
- if (!empty($class['package']['namespace'])) {
- $namespace = '\\' . $class['package']['namespace'] . '::';
- } elseif (!empty($class['package']['fullPackage'])) {
- $namespace = '@' . $class['package']['fullPackage'] . '::';
+ if (!empty($class['package']['fullPackage'])) {
+ $package = '@' . $class['package']['fullPackage'] . '::';
}
- $classCoverage[$namespace . $className] = [
- 'namespace' => $namespace,
+ $classCoverage[$package . $className] = [
+ 'namespace' => $class['package']['namespace'],
'className ' => $className,
'methodsCovered' => $coveredMethods,
'methodCount' => $classMethods,
diff --git a/tests/TestCase.php b/tests/TestCase.php
index a1c9efb00..34300abea 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -23,7 +23,7 @@ public static function setUpBeforeClass(): void
protected function getXdebugDataForBankAccount()
{
return [
- [
+ RawCodeCoverageData::fromXdebugWithoutPathCoverage([
TEST_FILES_PATH . 'BankAccount.php' => [
8 => 1,
9 => -2,
@@ -39,24 +39,24 @@ protected function getXdebugDataForBankAccount()
31 => -1,
32 => -2,
],
- ],
- [
+ ]),
+ RawCodeCoverageData::fromXdebugWithoutPathCoverage([
TEST_FILES_PATH . 'BankAccount.php' => [
8 => 1,
13 => 1,
16 => 1,
29 => 1,
],
- ],
- [
+ ]),
+ RawCodeCoverageData::fromXdebugWithoutPathCoverage([
TEST_FILES_PATH . 'BankAccount.php' => [
8 => 1,
13 => 1,
16 => 1,
22 => 1,
],
- ],
- [
+ ]),
+ RawCodeCoverageData::fromXdebugWithoutPathCoverage([
TEST_FILES_PATH . 'BankAccount.php' => [
8 => 1,
13 => 1,
@@ -68,7 +68,7 @@ protected function getXdebugDataForBankAccount()
29 => 1,
31 => 1,
],
- ],
+ ]),
];
}
@@ -138,6 +138,124 @@ protected function getCoverageForBankAccount(): CodeCoverage
return $coverage;
}
+ protected function getXdebugDataForNamespacedBankAccount()
+ {
+ return [
+ RawCodeCoverageData::fromXdebugWithoutPathCoverage([
+ TEST_FILES_PATH . 'NamespacedBankAccount.php' => [
+ 14 => 1,
+ 15 => -2,
+ 19 => -1,
+ 20 => -1,
+ 21 => -1,
+ 22 => -1,
+ 24 => -1,
+ 28 => -1,
+ 30 => -1,
+ 31 => -2,
+ 35 => -1,
+ 37 => -1,
+ 38 => -2,
+ ],
+ ]),
+ RawCodeCoverageData::fromXdebugWithoutPathCoverage([
+ TEST_FILES_PATH . 'NamespacedBankAccount.php' => [
+ 14 => 1,
+ 19 => 1,
+ 22 => 1,
+ 35 => 1,
+ ],
+ ]),
+ RawCodeCoverageData::fromXdebugWithoutPathCoverage([
+ TEST_FILES_PATH . 'NamespacedBankAccount.php' => [
+ 14 => 1,
+ 19 => 1,
+ 22 => 1,
+ 28 => 1,
+ ],
+ ]),
+ RawCodeCoverageData::fromXdebugWithoutPathCoverage([
+ TEST_FILES_PATH . 'NamespacedBankAccount.php' => [
+ 14 => 1,
+ 19 => 1,
+ 20 => 1,
+ 21 => 1,
+ 24 => 1,
+ 28 => 1,
+ 30 => 1,
+ 35 => 1,
+ 37 => 1,
+ ],
+ ]),
+ ];
+ }
+
+ protected function getCoverageForNamespacedBankAccount(): CodeCoverage
+ {
+ $data = $this->getXdebugDataForNamespacedBankAccount();
+
+ $stub = $this->createMock(Driver::class);
+
+ $stub->expects($this->any())
+ ->method('stop')
+ ->will($this->onConsecutiveCalls(
+ $data[0],
+ $data[1],
+ $data[2],
+ $data[3]
+ ));
+
+ $filter = new Filter;
+ $filter->addFileToWhitelist(TEST_FILES_PATH . 'NamespacedBankAccount.php');
+
+ $coverage = new CodeCoverage($stub, $filter);
+
+ $coverage->start(
+ new \BankAccountTest('testBalanceIsInitiallyZero'),
+ true
+ );
+
+ $coverage->stop(
+ true,
+ [TEST_FILES_PATH . 'NamespacedBankAccount.php' => \range(12, 15)]
+ );
+
+ $coverage->start(
+ new \BankAccountTest('testBalanceCannotBecomeNegative')
+ );
+
+ $coverage->stop(
+ true,
+ [TEST_FILES_PATH . 'NamespacedBankAccount.php' => \range(33, 38)]
+ );
+
+ $coverage->start(
+ new \BankAccountTest('testBalanceCannotBecomeNegative2')
+ );
+
+ $coverage->stop(
+ true,
+ [TEST_FILES_PATH . 'NamespacedBankAccount.php' => \range(26, 31)]
+ );
+
+ $coverage->start(
+ new \BankAccountTest('testDepositWithdrawMoney')
+ );
+
+ $coverage->stop(
+ true,
+ [
+ TEST_FILES_PATH . 'NamespacedBankAccount.php' => \array_merge(
+ \range(12, 15),
+ \range(26, 31),
+ \range(33, 38)
+ ),
+ ]
+ );
+
+ return $coverage;
+ }
+
protected function getCoverageForBankAccountForFirstTwoTests(): CodeCoverage
{
$data = $this->getXdebugDataForBankAccount();
@@ -178,7 +296,7 @@ protected function getCoverageForBankAccountForFirstTwoTests(): CodeCoverage
return $coverage;
}
- protected function getCoverageForBankAccountForLastTwoTests()
+ protected function getCoverageForBankAccountForLastTwoTests(): CodeCoverage
{
$data = $this->getXdebugDataForBankAccount();
@@ -314,14 +432,16 @@ protected function setUpXdebugStubForFileWithIgnoredLines(): Driver
$stub->expects($this->any())
->method('stop')
->will($this->returnValue(
- [
- TEST_FILES_PATH . 'source_with_ignore.php' => [
- 2 => 1,
- 4 => -1,
- 6 => -1,
- 7 => 1,
- ],
- ]
+ RawCodeCoverageData::fromXdebugWithoutPathCoverage(
+ [
+ TEST_FILES_PATH . 'source_with_ignore.php' => [
+ 2 => 1,
+ 4 => -1,
+ 6 => -1,
+ 7 => 1,
+ ],
+ ]
+ )
));
return $stub;
@@ -350,19 +470,21 @@ protected function setUpXdebugStubForClassWithAnonymousFunction(): Driver
$stub->expects($this->any())
->method('stop')
->will($this->returnValue(
- [
- TEST_FILES_PATH . 'source_with_class_and_anonymous_function.php' => [
- 7 => 1,
- 9 => 1,
- 10 => -1,
- 11 => 1,
- 12 => 1,
- 13 => 1,
- 14 => 1,
- 17 => 1,
- 18 => 1,
- ],
- ]
+ RawCodeCoverageData::fromXdebugWithoutPathCoverage(
+ [
+ TEST_FILES_PATH . 'source_with_class_and_anonymous_function.php' => [
+ 7 => 1,
+ 9 => 1,
+ 10 => -1,
+ 11 => 1,
+ 12 => 1,
+ 13 => 1,
+ 14 => 1,
+ 17 => 1,
+ 18 => 1,
+ ],
+ ]
+ )
));
return $stub;
@@ -386,8 +508,22 @@ protected function setUpXdebugStubForCrashParsing(): Driver
$stub->expects($this->any())
->method('stop')
- ->will($this->returnValue([]));
+ ->will($this->returnValue(RawCodeCoverageData::fromXdebugWithoutPathCoverage([])));
return $stub;
}
+
+ protected function removeTemporaryFiles(): void
+ {
+ $tmpFilesIterator = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator(self::$TEST_TMP_PATH, \RecursiveDirectoryIterator::SKIP_DOTS),
+ \RecursiveIteratorIterator::CHILD_FIRST
+ );
+
+ foreach ($tmpFilesIterator as $path => $fileInfo) {
+ /* @var \SplFileInfo $fileInfo */
+ $pathname = $fileInfo->getPathname();
+ $fileInfo->isDir() ? \rmdir($pathname) : \unlink($pathname);
+ }
+ }
}
diff --git a/tests/_files/BankAccount-text-summary.txt b/tests/_files/BankAccount-text-summary.txt
new file mode 100644
index 000000000..c0fb9cc7d
--- /dev/null
+++ b/tests/_files/BankAccount-text-summary.txt
@@ -0,0 +1,7 @@
+
+
+Code Coverage Report Summary:
+ Classes: 0.00% (0/1)
+ Methods: 75.00% (3/4)
+ Lines: 50.00% (5/10)
+
diff --git a/tests/_files/NamespacedBankAccount-text.txt b/tests/_files/NamespacedBankAccount-text.txt
new file mode 100644
index 000000000..fa153bd8f
--- /dev/null
+++ b/tests/_files/NamespacedBankAccount-text.txt
@@ -0,0 +1,14 @@
+
+
+Code Coverage Report:
+ %s
+
+ Summary:
+ Classes: 0.00% (0/1)
+ Methods: 75.00% (3/4)
+ Lines: 50.00% (5/10)
+
+@OldStylePackageName::SomeNamespace\BankAccount
+ Methods: ( 0/ 0) Lines: ( 0/ 0)
+SomeNamespace\BankAccountTrait
+ Methods: 75.00% ( 3/ 4) Lines: 50.00% ( 5/ 10)
diff --git a/tests/_files/NamespacedBankAccount.php b/tests/_files/NamespacedBankAccount.php
new file mode 100644
index 000000000..9a14e2421
--- /dev/null
+++ b/tests/_files/NamespacedBankAccount.php
@@ -0,0 +1,39 @@
+balance;
+ }
+
+ protected function setBalance($balance)
+ {
+ if ($balance >= 0) {
+ $this->balance = $balance;
+ } else {
+ throw new \RuntimeException;
+ }
+ }
+
+ public function depositMoney($balance)
+ {
+ $this->setBalance($this->getBalance() + $balance);
+
+ return $this->getBalance();
+ }
+
+ public function withdrawMoney($balance)
+ {
+ $this->setBalance($this->getBalance() - $balance);
+
+ return $this->getBalance();
+ }
+}
diff --git a/tests/tests/BuilderTest.php b/tests/tests/BuilderTest.php
index 11d6210af..0f7130f16 100644
--- a/tests/tests/BuilderTest.php
+++ b/tests/tests/BuilderTest.php
@@ -10,6 +10,7 @@
namespace SebastianBergmann\CodeCoverage\Report;
use SebastianBergmann\CodeCoverage\Node\Builder;
+use SebastianBergmann\CodeCoverage\ProcessedCodeCoverageData;
use SebastianBergmann\CodeCoverage\TestCase;
class BuilderTest extends TestCase
@@ -134,7 +135,7 @@ public function testNotCrashParsing(): void
$this->assertEquals($expectedPath, $root->getPath());
$this->assertEquals(2, $root->getNumExecutableLines());
$this->assertEquals(0, $root->getNumExecutedLines());
- $data = $coverage->getData();
+ $data = $coverage->getData()->getLineCoverage();
$expectedFile = $expectedPath . \DIRECTORY_SEPARATOR . 'Crash.php';
$this->assertSame([$expectedFile => [1 => [], 2 => []]], $data);
}
@@ -166,11 +167,11 @@ public function testBuildDirectoryStructure(): void
],
$method->invoke(
$this->factory,
- [
+ $this->pathsToProcessedDataObjectHelper([
"src{$s}Money.php" => [],
"src{$s}MoneyBag.php" => [],
"src{$s}Foo{$s}Bar{$s}Baz{$s}Foo.php" => [],
- ]
+ ])
)
);
}
@@ -178,7 +179,7 @@ public function testBuildDirectoryStructure(): void
/**
* @dataProvider reducePathsProvider
*/
- public function testReducePaths($reducedPaths, $commonPath, $paths): void
+ public function testReducePaths(array $reducedPaths, string $commonPath, ProcessedCodeCoverageData $paths): void
{
$method = new \ReflectionMethod(
Builder::class,
@@ -187,9 +188,9 @@ public function testReducePaths($reducedPaths, $commonPath, $paths): void
$method->setAccessible(true);
- $_commonPath = $method->invokeArgs($this->factory, [&$paths]);
+ $_commonPath = $method->invokeArgs($this->factory, [$paths]);
- $this->assertEquals($reducedPaths, $paths);
+ $this->assertEquals($reducedPaths, $paths->getLineCoverage());
$this->assertEquals($commonPath, $_commonPath);
}
@@ -200,7 +201,7 @@ public function reducePathsProvider()
yield [
[],
'.',
- [],
+ $this->pathsToProcessedDataObjectHelper([]),
];
$prefixes = ["C:$s", "$s"];
@@ -211,9 +212,9 @@ public function reducePathsProvider()
'Money.php' => [],
],
"{$p}home{$s}sb{$s}Money{$s}",
- [
+ $this->pathsToProcessedDataObjectHelper([
"{$p}home{$s}sb{$s}Money{$s}Money.php" => [],
- ],
+ ]),
];
yield [
@@ -222,10 +223,10 @@ public function reducePathsProvider()
'MoneyBag.php' => [],
],
"{$p}home{$s}sb{$s}Money",
- [
+ $this->pathsToProcessedDataObjectHelper([
"{$p}home{$s}sb{$s}Money{$s}Money.php" => [],
"{$p}home{$s}sb{$s}Money{$s}MoneyBag.php" => [],
- ],
+ ]),
];
yield [
@@ -235,12 +236,20 @@ public function reducePathsProvider()
"Cash.phar{$s}Cash.php" => [],
],
"{$p}home{$s}sb{$s}Money",
- [
+ $this->pathsToProcessedDataObjectHelper([
"{$p}home{$s}sb{$s}Money{$s}Money.php" => [],
"{$p}home{$s}sb{$s}Money{$s}MoneyBag.php" => [],
"phar://{$p}home{$s}sb{$s}Money{$s}Cash.phar{$s}Cash.php" => [],
- ],
+ ]),
];
}
}
+
+ private function pathsToProcessedDataObjectHelper(array $paths): ProcessedCodeCoverageData
+ {
+ $coverage = new ProcessedCodeCoverageData;
+ $coverage->setLineCoverage($paths);
+
+ return $coverage;
+ }
}
diff --git a/tests/tests/CodeCoverageTest.php b/tests/tests/CodeCoverageTest.php
index b76fa43d9..5574d360d 100644
--- a/tests/tests/CodeCoverageTest.php
+++ b/tests/tests/CodeCoverageTest.php
@@ -44,7 +44,7 @@ public function testCannotAppendWithInvalidArgument(): void
{
$this->expectException(Exception::class);
- $this->coverage->append([], null);
+ $this->coverage->append(RawCodeCoverageData::fromXdebugWithoutPathCoverage([]), null);
}
public function testCollect(): void
@@ -53,7 +53,7 @@ public function testCollect(): void
$this->assertEquals(
$this->getExpectedDataArrayForBankAccount(),
- $coverage->getData()
+ $coverage->getData()->getLineCoverage()
);
$this->assertEquals(
@@ -67,6 +67,26 @@ public function testCollect(): void
);
}
+ public function testWhitelistFiltering(): void
+ {
+ $this->coverage->filter()->addFileToWhitelist(TEST_FILES_PATH . 'BankAccount.php');
+
+ $data = RawCodeCoverageData::fromXdebugWithoutPathCoverage([
+ TEST_FILES_PATH . 'BankAccount.php' => [
+ 29 => -1,
+ 31 => -1,
+ ],
+ TEST_FILES_PATH . 'CoverageClassTest.php' => [
+ 29 => -1,
+ 31 => -1,
+ ],
+ ]);
+
+ $this->coverage->append($data, 'A test', true);
+ $this->assertContains(TEST_FILES_PATH . 'BankAccount.php', $this->coverage->getData()->getCoveredFiles());
+ $this->assertNotContains(TEST_FILES_PATH . 'CoverageClassTest.php', $this->coverage->getData()->getCoveredFiles());
+ }
+
public function testMerge(): void
{
$coverage = $this->getCoverageForBankAccountForFirstTwoTests();
@@ -74,7 +94,7 @@ public function testMerge(): void
$this->assertEquals(
$this->getExpectedDataArrayForBankAccount(),
- $coverage->getData()
+ $coverage->getData()->getLineCoverage()
);
}
@@ -85,7 +105,7 @@ public function testMergeReverseOrder(): void
$this->assertEquals(
$this->getExpectedDataArrayForBankAccountInReverseOrder(),
- $coverage->getData()
+ $coverage->getData()->getLineCoverage()
);
}
@@ -100,7 +120,7 @@ public function testMerge2(): void
$this->assertEquals(
$this->getExpectedDataArrayForBankAccount(),
- $coverage->getData()
+ $coverage->getData()->getLineCoverage()
);
}
@@ -290,12 +310,12 @@ public function testAppendThrowsExceptionIfCoveredCodeWasNotExecuted(): void
$this->coverage->filter()->addDirectoryToWhitelist(TEST_FILES_PATH);
$this->coverage->setCheckForUnexecutedCoveredCode(true);
- $data = [
+ $data = RawCodeCoverageData::fromXdebugWithoutPathCoverage([
TEST_FILES_PATH . 'BankAccount.php' => [
29 => -1,
31 => -1,
],
- ];
+ ]);
$linesToBeCovered = [
TEST_FILES_PATH . 'BankAccount.php' => [
@@ -316,12 +336,12 @@ public function testAppendThrowsExceptionIfUsedCodeWasNotExecuted(): void
$this->coverage->filter()->addDirectoryToWhitelist(TEST_FILES_PATH);
$this->coverage->setCheckForUnexecutedCoveredCode(true);
- $data = [
+ $data = RawCodeCoverageData::fromXdebugWithoutPathCoverage([
TEST_FILES_PATH . 'BankAccount.php' => [
29 => -1,
31 => -1,
],
- ];
+ ]);
$linesToBeCovered = [
TEST_FILES_PATH . 'BankAccount.php' => [
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
new file mode 100644
index 000000000..c30d595ca
--- /dev/null
+++ b/tests/tests/Driver/XdebugTest.php
@@ -0,0 +1,76 @@
+
+ *
+ * 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\Xdebug;
+use SebastianBergmann\Environment\Runtime;
+
+class XdebugTest extends TestCase
+{
+ protected function setUp(): void
+ {
+ $runtime = new Runtime;
+
+ if (!$runtime->hasXdebug()) {
+ $this->markTestSkipped('This test is only applicable to Xdebug');
+ }
+
+ if (!xdebug_code_coverage_started()) {
+ $this->markTestSkipped('This test requires code coverage to be running');
+ }
+ }
+
+ public function testFilterWorks(): void
+ {
+ $bankAccount = TEST_FILES_PATH . 'BankAccount.php';
+
+ 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());
+ }
+}
diff --git a/tests/tests/FilterTest.php b/tests/tests/FilterTest.php
index 807adbb80..ed8e55af3 100644
--- a/tests/tests/FilterTest.php
+++ b/tests/tests/FilterTest.php
@@ -64,6 +64,7 @@ protected function setUp(): void
TEST_FILES_PATH . 'NamespaceCoverageProtectedTest.php',
TEST_FILES_PATH . 'NamespaceCoveragePublicTest.php',
TEST_FILES_PATH . 'NamespaceCoveredClass.php',
+ TEST_FILES_PATH . 'NamespacedBankAccount.php',
TEST_FILES_PATH . 'NotExistingCoveredElementTest.php',
TEST_FILES_PATH . 'source_with_class_and_anonymous_function.php',
TEST_FILES_PATH . 'source_with_ignore.php',
diff --git a/tests/tests/HTMLTest.php b/tests/tests/HTMLTest.php
index f93cf8867..0c7e91ce6 100644
--- a/tests/tests/HTMLTest.php
+++ b/tests/tests/HTMLTest.php
@@ -26,16 +26,7 @@ protected function tearDown(): void
{
parent::tearDown();
- $tmpFilesIterator = new \RecursiveIteratorIterator(
- new \RecursiveDirectoryIterator(self::$TEST_TMP_PATH, \RecursiveDirectoryIterator::SKIP_DOTS),
- \RecursiveIteratorIterator::CHILD_FIRST
- );
-
- foreach ($tmpFilesIterator as $path => $fileInfo) {
- /* @var \SplFileInfo $fileInfo */
- $pathname = $fileInfo->getPathname();
- $fileInfo->isDir() ? \rmdir($pathname) : \unlink($pathname);
- }
+ $this->removeTemporaryFiles();
}
public function testForBankAccountTest(): void
diff --git a/tests/tests/PHPTest.php b/tests/tests/PHPTest.php
new file mode 100644
index 000000000..14ce3a9f9
--- /dev/null
+++ b/tests/tests/PHPTest.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage\Report;
+
+use SebastianBergmann\CodeCoverage\TestCase;
+
+final class PHPTest extends TestCase
+{
+ protected function tearDown(): void
+ {
+ parent::tearDown();
+
+ $this->removeTemporaryFiles();
+ }
+
+ public function testPHPSerialisationProducesValidCode(): void
+ {
+ $coverage = $this->getCoverageForBankAccount();
+
+ /* @noinspection UnusedFunctionResultInspection */
+ (new PHP())->process($coverage, self::$TEST_TMP_PATH . '/serialized.php');
+
+ $unserialized = require self::$TEST_TMP_PATH . '/serialized.php';
+
+ $this->assertEquals($coverage, $unserialized);
+ }
+}
diff --git a/tests/tests/ProcessedCodeCoverageDataTest.php b/tests/tests/ProcessedCodeCoverageDataTest.php
new file mode 100644
index 000000000..f383fb922
--- /dev/null
+++ b/tests/tests/ProcessedCodeCoverageDataTest.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage;
+
+class ProcessedCodeCoverageDataTest extends TestCase
+{
+ public function testMerge(): void
+ {
+ $coverage = $this->getCoverageForBankAccountForFirstTwoTests()->getData();
+ $coverage->merge($this->getCoverageForBankAccountForLastTwoTests()->getData());
+
+ $this->assertEquals(
+ $this->getExpectedDataArrayForBankAccount(),
+ $coverage->getLineCoverage()
+ );
+ }
+
+ public function testMergeOfAPreviouslyUnseenLine(): void
+ {
+ $newCoverage = new ProcessedCodeCoverageData();
+ $newCoverage->setLineCoverage(
+ [
+ '/some/path/SomeClass.php' => [
+ 12 => [],
+ 34 => null,
+ ],
+ ]
+ );
+
+ $existingCoverage = new ProcessedCodeCoverageData();
+ $existingCoverage->merge($newCoverage);
+ $this->assertArrayHasKey(12, $existingCoverage->getLineCoverage()['/some/path/SomeClass.php']);
+ }
+}
diff --git a/tests/tests/RawCodeCoverageDataTest.php b/tests/tests/RawCodeCoverageDataTest.php
new file mode 100644
index 000000000..8f7dfef5e
--- /dev/null
+++ b/tests/tests/RawCodeCoverageDataTest.php
@@ -0,0 +1,194 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace SebastianBergmann\CodeCoverage;
+
+class RawCodeCoverageDataTest extends TestCase
+{
+ /**
+ * In the standard XDebug format, there is only line data. Therefore output should match input.
+ */
+ public function testLineDataFromStandardXDebugFormat(): void
+ {
+ $lineDataFromDriver = [
+ '/some/path/SomeClass.php' => [
+ 8 => 1,
+ 9 => -2,
+ 13 => -1,
+ ],
+ ];
+
+ $dataObject = RawCodeCoverageData::fromXdebugWithoutPathCoverage($lineDataFromDriver);
+ $this->assertEquals($lineDataFromDriver, $dataObject->getLineCoverage());
+ }
+
+ /**
+ * In the path-coverage XDebug format, the line data exists inside a "lines" array key.
+ */
+ public function testLineDataFromPathCoverageXDebugFormat(): void
+ {
+ $rawDataFromDriver = [
+ '/some/path/SomeClass.php' => [
+ 'lines' => [
+ 8 => 1,
+ 9 => -2,
+ 13 => -1,
+ ],
+ 'functions' => [
+
+ ],
+ ],
+ ];
+
+ $lineData = [
+ '/some/path/SomeClass.php' => [
+ 8 => 1,
+ 9 => -2,
+ 13 => -1,
+ ],
+ ];
+
+ $dataObject = RawCodeCoverageData::fromXdebugWithPathCoverage($rawDataFromDriver);
+ $this->assertEquals($lineData, $dataObject->getLineCoverage());
+ }
+
+ public function testClear(): void
+ {
+ $lineDataFromDriver = [
+ '/some/path/SomeClass.php' => [
+ 8 => 1,
+ 9 => -2,
+ 13 => -1,
+ ],
+ ];
+
+ $dataObject = RawCodeCoverageData::fromXdebugWithoutPathCoverage($lineDataFromDriver);
+ $dataObject->clear();
+ $this->assertEmpty($dataObject->getLineCoverage());
+ }
+
+ public function testRemoveCoverageDataForFile(): void
+ {
+ $lineDataFromDriver = [
+ '/some/path/SomeClass.php' => [
+ 8 => 1,
+ 9 => -2,
+ 13 => -1,
+ ],
+ '/some/path/SomeOtherClass.php' => [
+ 18 => 1,
+ 19 => -2,
+ 113 => -1,
+ ],
+ '/some/path/AnotherClass.php' => [
+ 28 => 1,
+ 29 => -2,
+ 213 => -1,
+ ],
+ ];
+
+ $expectedFilterResult = [
+ '/some/path/SomeClass.php' => [
+ 8 => 1,
+ 9 => -2,
+ 13 => -1,
+ ],
+ '/some/path/AnotherClass.php' => [
+ 28 => 1,
+ 29 => -2,
+ 213 => -1,
+ ],
+ ];
+
+ $dataObject = RawCodeCoverageData::fromXdebugWithoutPathCoverage($lineDataFromDriver);
+ $dataObject->removeCoverageDataForFile('/some/path/SomeOtherClass.php');
+ $this->assertEquals($expectedFilterResult, $dataObject->getLineCoverage());
+ }
+
+ public function testKeepCoverageDataOnlyForLines(): void
+ {
+ $lineDataFromDriver = [
+ '/some/path/SomeClass.php' => [
+ 8 => 1,
+ 9 => -2,
+ 13 => -1,
+ ],
+ '/some/path/SomeOtherClass.php' => [
+ 18 => 1,
+ 19 => -2,
+ 113 => -1,
+ ],
+ '/some/path/AnotherClass.php' => [
+ 28 => 1,
+ 29 => -2,
+ 213 => -1,
+ ],
+ ];
+
+ $expectedFilterResult = [
+ '/some/path/SomeClass.php' => [
+ 9 => -2,
+ 13 => -1,
+ ],
+ '/some/path/SomeOtherClass.php' => [
+ ],
+ '/some/path/AnotherClass.php' => [
+ 28 => 1,
+ ],
+ ];
+
+ $dataObject = RawCodeCoverageData::fromXdebugWithoutPathCoverage($lineDataFromDriver);
+ $dataObject->keepCoverageDataOnlyForLines('/some/path/SomeClass.php', [9, 13]);
+ $dataObject->keepCoverageDataOnlyForLines('/some/path/SomeOtherClass.php', [999]);
+ $dataObject->keepCoverageDataOnlyForLines('/some/path/AnotherClass.php', [28]);
+ $this->assertEquals($expectedFilterResult, $dataObject->getLineCoverage());
+ }
+
+ public function testRemoveCoverageDataForLines(): void
+ {
+ $lineDataFromDriver = [
+ '/some/path/SomeClass.php' => [
+ 8 => 1,
+ 9 => -2,
+ 13 => -1,
+ ],
+ '/some/path/SomeOtherClass.php' => [
+ 18 => 1,
+ 19 => -2,
+ 113 => -1,
+ ],
+ '/some/path/AnotherClass.php' => [
+ 28 => 1,
+ 29 => -2,
+ 213 => -1,
+ ],
+ ];
+
+ $expectedFilterResult = [
+ '/some/path/SomeClass.php' => [
+ 8 => 1,
+ ],
+ '/some/path/SomeOtherClass.php' => [
+ 18 => 1,
+ 19 => -2,
+ 113 => -1,
+ ],
+ '/some/path/AnotherClass.php' => [
+ 29 => -2,
+ 213 => -1,
+ ],
+ ];
+
+ $dataObject = RawCodeCoverageData::fromXdebugWithoutPathCoverage($lineDataFromDriver);
+ $dataObject->removeCoverageDataForLines('/some/path/SomeClass.php', [9, 13]);
+ $dataObject->removeCoverageDataForLines('/some/path/SomeOtherClass.php', [999]);
+ $dataObject->removeCoverageDataForLines('/some/path/AnotherClass.php', [28]);
+ $this->assertEquals($expectedFilterResult, $dataObject->getLineCoverage());
+ }
+}
diff --git a/tests/tests/TextTest.php b/tests/tests/TextTest.php
index ce0846fa7..f3b8d5c2b 100644
--- a/tests/tests/TextTest.php
+++ b/tests/tests/TextTest.php
@@ -26,6 +26,26 @@ public function testTextForBankAccountTest(): void
);
}
+ public function testTextOnlySummaryForBankAccountTest(): void
+ {
+ $text = new Text(50, 90, false, true);
+
+ $this->assertStringMatchesFormatFile(
+ TEST_FILES_PATH . 'BankAccount-text-summary.txt',
+ \str_replace(\PHP_EOL, "\n", $text->process($this->getCoverageForBankAccount()))
+ );
+ }
+
+ public function testTextForNamespacedBankAccountTest(): void
+ {
+ $text = new Text(50, 90, true, false);
+
+ $this->assertStringMatchesFormatFile(
+ TEST_FILES_PATH . 'NamespacedBankAccount-text.txt',
+ \str_replace(\PHP_EOL, "\n", $text->process($this->getCoverageForNamespacedBankAccount()))
+ );
+ }
+
public function testTextForFileWithIgnoredLines(): void
{
$text = new Text(50, 90, false, false);