Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
288 changes: 250 additions & 38 deletions src/CodeCoverage.php
Original file line number Diff line number Diff line change
Expand Up @@ -348,17 +348,29 @@ public function append(array $data, $id = null, bool $append = true, $linesToBeC

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

foreach ($data as $file => $lines) {
foreach ($data as $file => $fileData) {
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;
foreach ($fileData['lines'] as $line => $lineCoverage) {
if ($lineCoverage === Driver::LINE_EXECUTED) {
$this->addCoverageLinePathCovered($file, $line, true);
$this->addCoverageLineTest($file, $line, $id);
}
}

foreach ($fileData['functions'] as $function => $functionCoverage) {
foreach ($functionCoverage['branches'] as $branch => $branchCoverage) {
if (($branchCoverage['hit'] ?? 0) === 1) {
$this->addCoverageBranchHit($file, $function, $branch, $branchCoverage['hit'] ?? 0);
$this->addCoverageBranchTest($file, $function, $branch, $id);
}
}

foreach ($functionCoverage['paths'] as $path => $pathCoverage) {
$this->addCoveragePathHit($file, $function, $path, $pathCoverage['hit'] ?? 0);
}
}
}

Expand All @@ -376,10 +388,13 @@ 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])) {
$thisData = $this->getData();
$thatData = $that->getData();

foreach ($thatData as $file => $fileData) {
if (!isset($thisData[$file])) {
if (!$this->filter->isFiltered($file)) {
$this->data[$file] = $lines;
$thisData[$file] = $fileData;
}

continue;
Expand All @@ -388,27 +403,30 @@ public function merge(self $that): void
// 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])
\array_keys($thisData[$file]['lines']),
\array_keys($thatData[$file]['lines']) // can this be $fileData?
)
);

foreach ($compareLineNumbers as $line) {
$thatPriority = $this->getLinePriority($that->data[$file], $line);
$thisPriority = $this->getLinePriority($this->data[$file], $line);
$thatPriority = $this->getLinePriority($thatData[$file]['lines'], $line);
$thisPriority = $this->getLinePriority($thisData[$file]['lines'], $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])
$thisData[$file]['lines'][$line] = $thatData[$file]['lines'][$line];
} elseif ($thatPriority === $thisPriority && \is_array($thisData[$file]['lines'][$line])) {
if ($line['pathCovered'] === true) {
$thisData[$file]['lines'][$line]['pathCovered'] = $line['pathCovered'];
}
$thisData[$file]['lines'][$line] = \array_unique(
\array_merge($thisData[$file]['lines'][$line], $thatData[$file]['lines'][$line])
);
}
}
}

$this->tests = \array_merge($this->tests, $that->getTests());
$this->report = null;
$this->tests = \array_merge($this->tests, $that->getTests());
$this->setData($thisData);
}

public function setCacheTokens(bool $flag): void
Expand Down Expand Up @@ -493,12 +511,9 @@ public function setDetermineBranchCoverage(bool $flag): void
*
* During a merge, a higher number is better.
*
* @param array $data
* @param int $line
*
* @return int
*/
private function getLinePriority($data, $line)
private function getLinePriority(array $data, int $line)
{
if (!\array_key_exists($line, $data)) {
return 1;
Expand Down Expand Up @@ -533,7 +548,10 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar
throw new MissingCoversAnnotationException;
}

$data = [];
$data = [
'lines' => [],
'functions' => [],
];

return;
}
Expand All @@ -544,7 +562,7 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar

if ($this->checkForUnintentionallyCoveredCode &&
(!$this->currentId instanceof TestCase ||
(!$this->currentId->isMedium() && !$this->currentId->isLarge()))) {
(!$this->currentId->isMedium() && !$this->currentId->isLarge()))) {
$this->performUnintentionallyCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed);
}

Expand All @@ -556,7 +574,11 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar

foreach (\array_keys($data) as $filename) {
$_linesToBeCovered = \array_flip($linesToBeCovered[$filename]);
$data[$filename] = \array_intersect_key($data[$filename], $_linesToBeCovered);

$data[$filename]['lines'] = \array_intersect_key(
$data[$filename],
$_linesToBeCovered
);
}
}

Expand All @@ -580,22 +602,208 @@ private function applyIgnoredLinesFilter(array &$data): void
}

foreach ($this->getLinesToBeIgnored($filename) as $line) {
unset($data[$filename][$line]);
unset($data[$filename]['lines'][$line]);
}
}
}

private function initializeFilesThatAreSeenTheFirstTime(array $data): void
{
foreach ($data as $file => $lines) {
if (!isset($this->data[$file]) && $this->filter->isFile($file)) {
$this->data[$file] = [];
foreach ($data as $file => $fileData) {
if (isset($this->data[$file]) || !$this->filter->isFile($file)) {
continue;
}
$this->initializeFileCoverageData($file);

foreach ($lines as $k => $v) {
$this->data[$file][$k] = $v === -2 ? null : [];
// If this particular line is identified as not covered, mark it as null
foreach ($fileData['lines'] as $lineNumber => $flag) {
if ($flag === Driver::LINE_NOT_EXECUTABLE) {
$this->data[$file]['lines'][$lineNumber] = null;
}
}

foreach ($fileData['functions'] as $functionName => $functionData) {
// @todo - should this have a helper to merge covered paths?
$this->data[$file]['paths'][$functionName] = $functionData['paths'];

foreach ($functionData['branches'] as $branchIndex => $branchData) {
$this->addCoverageBranchHit($file, $functionName, $branchIndex, $branchData['hit']);
$this->addCoverageBranchLineStart($file, $functionName, $branchIndex, $branchData['line_start']);
$this->addCoverageBranchLineEnd($file, $functionName, $branchIndex, $branchData['line_end']);

for ($curLine = $branchData['line_start']; $curLine < $branchData['line_end']; $curLine++) {
if (isset($this->data[$file]['lines'][$curLine])) {
$this->addCoverageLinePathCovered($file, $curLine, (bool) $branchData['hit']);
}
}
}
}
}
}

private function initializeFileCoverageData(string $file): void
{
if (!isset($this->data[$file]) && $this->filter->isFile($file)) {
$this->data[$file] = [
'lines' => [],
'branches' => [],
'paths' => [],
];
}
}

private function addCoverageLinePathCovered(string $file, int $lineNumber, bool $isCovered): void
{
$this->initializeFileCoverageData($file);

// Initialize the data coverage array for this line
if (!isset($this->data[$file]['lines'][$lineNumber])) {
$this->data[$file]['lines'][$lineNumber] = [
'pathCovered' => false,
'tests' => [],
];
}

$this->data[$file]['lines'][$lineNumber]['pathCovered'] = $isCovered;
}

private function addCoverageLineTest(string $file, int $lineNumber, string $testId): void
{
$this->initializeFileCoverageData($file);

// Initialize the data coverage array for this line
if (!isset($this->data[$file]['lines'][$lineNumber])) {
$this->data[$file]['lines'][$lineNumber] = [
'pathCovered' => false,
'tests' => [],
];
}

if (!\in_array($testId, $this->data[$file]['lines'][$lineNumber]['tests'], true)) {
$this->data[$file]['lines'][$lineNumber]['tests'][] = $testId;
}
}

private function addCoverageBranchHit(string $file, string $functionName, int $branchIndex, int $hit): void
{
$this->initializeFileCoverageData($file);

if (!\array_key_exists($functionName, $this->data[$file]['branches'])) {
$this->data[$file]['branches'][$functionName] = [];
}

if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) {
$this->data[$file]['branches'][$functionName][$branchIndex] = [
'hit' => 0,
'line_start' => 0,
'line_end' => 0,
'tests' => [],
];
}

$this->data[$file]['branches'][$functionName][$branchIndex]['hit'] = \max(
$this->data[$file]['branches'][$functionName][$branchIndex]['hit'],
$hit
);
}

private function addCoverageBranchLineStart(
string $file,
string $functionName,
int $branchIndex,
int $lineStart
): void {
$this->initializeFileCoverageData($file);

if (!\array_key_exists($functionName, $this->data[$file]['branches'])) {
$this->data[$file]['branches'][$functionName] = [];
}

if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) {
$this->data[$file]['branches'][$functionName][$branchIndex] = [
'hit' => 0,
'line_start' => 0,
'line_end' => 0,
'tests' => [],
];
}

$this->data[$file]['branches'][$functionName][$branchIndex]['line_start'] = $lineStart;
}

private function addCoverageBranchLineEnd(
string $file,
string $functionName,
int $branchIndex,
int $lineEnd
): void {
$this->initializeFileCoverageData($file);

if (!\array_key_exists($functionName, $this->data[$file]['branches'])) {
$this->data[$file]['branches'][$functionName] = [];
}

if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) {
$this->data[$file]['branches'][$functionName][$branchIndex] = [
'hit' => 0,
'line_start' => 0,
'line_end' => 0,
'tests' => [],
];
}

$this->data[$file]['branches'][$functionName][$branchIndex]['line_end'] = $lineEnd;
}

private function addCoverageBranchTest(
string $file,
string $functionName,
int $branchIndex,
string $testId
): void {
$this->initializeFileCoverageData($file);

if (!\array_key_exists($functionName, $this->data[$file]['branches'])) {
$this->data[$file]['branches'][$functionName] = [];
}

if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) {
$this->data[$file]['branches'][$functionName][$branchIndex] = [
'hit' => 0,
'line_start' => 0,
'line_end' => 0,
'tests' => [],
];
}

if (!\in_array($testId, $this->data[$file]['branches'][$functionName][$branchIndex]['tests'], true)) {
$this->data[$file]['branches'][$functionName][$branchIndex]['tests'][] = $testId;
}
}

private function addCoveragePathHit(
string $file,
string $functionName,
int $pathId,
int $hit
): void {
$this->initializeFileCoverageData($file);

if (!\array_key_exists($functionName, $this->data[$file]['paths'])) {
$this->data[$file]['paths'][$functionName] = [];
}

if (!\array_key_exists($pathId, $this->data[$file]['paths'][$functionName])) {
$this->data[$file]['paths'][$functionName][$pathId] = [
'hit' => 0,
'path' => [],
];
}

$this->data[$file]['paths'][$functionName][$pathId]['hit'] = \max(
$this->data[$file]['paths'][$functionName][$pathId]['hit'],
$hit
);
}

/**
Expand All @@ -619,13 +827,17 @@ private function addUncoveredFilesFromWhitelist(): void
continue;
}

$data[$uncoveredFile] = [];
$data[$uncoveredFile] = [
'lines' => [],
'functions' => [],
];

$lines = \count(\file($uncoveredFile));

for ($i = 1; $i <= $lines; $i++) {
$data[$uncoveredFile][$i] = Driver::LINE_NOT_EXECUTED;
for ($line = 1; $line <= $lines; $line++) {
$data[$uncoveredFile]['lines'][$line] = Driver::LINE_NOT_EXECUTED;
}
// @todo - do the same here with functions and paths
}

$this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
Expand Down Expand Up @@ -817,10 +1029,10 @@ private function performUnintentionallyCoveredCodeCheck(array &$data, array $lin

$unintentionallyCoveredUnits = [];

foreach ($data as $file => $_data) {
foreach ($_data as $line => $flag) {
if ($flag === 1 && !isset($allowedLines[$file][$line])) {
$unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line);
foreach ($data as $file => $fileData) {
foreach ($fileData['lines'] as $lineNumber => $flag) {
if ($flag === 1 && !isset($allowedLines[$file][$lineNumber])) {
$unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $lineNumber);
}
}
}
Expand Down
Loading