Skip to content

Commit

Permalink
Encapsulate processed coverage data
Browse files Browse the repository at this point in the history
  • Loading branch information
dvdoug authored and sebastianbergmann committed May 16, 2020
1 parent 3699d42 commit 659ef5c
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 139 deletions.
105 changes: 10 additions & 95 deletions src/CodeCoverage.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ final class CodeCoverage
/**
* Code coverage data.
*
* @var array
* @var ProcessedCodeCoverageData
*/
private $data = [];
private $data;

/**
* @var array
Expand Down Expand Up @@ -151,6 +151,7 @@ public function __construct(Driver $driver = null, Filter $filter = null)
$this->filter = $filter;

$this->wizard = new Wizard;
$this->data = new ProcessedCodeCoverageData();
}

/**
Expand All @@ -172,7 +173,7 @@ public function clear(): void
{
$this->isInitialized = false;
$this->currentId = null;
$this->data = [];
$this->data = new ProcessedCodeCoverageData();
$this->tests = [];
$this->report = null;
}
Expand All @@ -188,7 +189,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();
Expand All @@ -200,7 +201,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;
Expand Down Expand Up @@ -297,7 +298,7 @@ public function append(RawCodeCoverageData $rawData, $id = null, bool $append =

$this->applyWhitelistFilter($rawData);
$this->applyIgnoredLinesFilter($rawData);
$this->initializeFilesThatAreSeenTheFirstTime($rawData);
$this->data->initializeFilesThatAreSeenTheFirstTime($rawData);

if (!$append) {
return;
Expand Down Expand Up @@ -339,19 +340,7 @@ public function append(RawCodeCoverageData $rawData, $id = null, bool $append =

$this->tests[$id] = ['size' => $size, 'status' => $status];

foreach ($rawData->getLineCoverage() 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;
}
Expand All @@ -367,36 +356,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;
Expand Down Expand Up @@ -457,38 +417,6 @@ 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)
{
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;
}

/**
* Applies the @covers annotation filtering.
*
Expand Down Expand Up @@ -559,19 +487,6 @@ private function applyIgnoredLinesFilter(RawCodeCoverageData $data): void
}
}

private function initializeFilesThatAreSeenTheFirstTime(RawCodeCoverageData $data): void
{
foreach ($data->getLineCoverage() 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 : [];
}
}
}
}

/**
* @throws CoveredCodeNotExecutedException
* @throws InvalidArgumentException
Expand All @@ -585,7 +500,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) {
Expand Down
29 changes: 13 additions & 16 deletions src/Node/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@
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
);

$this->addItems(
$root,
$this->buildDirectoryStructure($files),
$this->buildDirectoryStructure($data),
$coverage->getTests(),
$coverage->getCacheTokens()
);
Expand Down Expand Up @@ -90,8 +91,9 @@ private function addItems(Directory $root, array $items, array $tests, bool $cac
* )
* </code>
*/
private function buildDirectoryStructure(array $files): array
private function buildDirectoryStructure(ProcessedCodeCoverageData $data): array
{
$files = $data->getLineCoverage();
$result = [];

foreach ($files as $path => $file) {
Expand Down Expand Up @@ -152,20 +154,18 @@ private function buildDirectoryStructure(array $files): array
* )
* </code>
*/
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;
}
Expand Down Expand Up @@ -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);
}
}
134 changes: 134 additions & 0 deletions src/ProcessedCodeCoverageData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?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\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;
}
}
Loading

0 comments on commit 659ef5c

Please sign in to comment.