Skip to content

Commit 86cf33d

Browse files
authored
Merge pull request #20 from jmrieger/codecoverage-contract-update
Start working on branch support in CodeCoverage
2 parents 52c47f1 + 13c8b72 commit 86cf33d

File tree

3 files changed

+254
-41
lines changed

3 files changed

+254
-41
lines changed

src/CodeCoverage.php

Lines changed: 250 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -348,17 +348,29 @@ public function append(array $data, $id = null, bool $append = true, $linesToBeC
348348

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

351-
foreach ($data as $file => $lines) {
351+
foreach ($data as $file => $fileData) {
352352
if (!$this->filter->isFile($file)) {
353353
continue;
354354
}
355355

356-
foreach ($lines as $k => $v) {
357-
if ($v === Driver::LINE_EXECUTED) {
358-
if (empty($this->data[$file][$k]) || !\in_array($id, $this->data[$file][$k])) {
359-
$this->data[$file][$k][] = $id;
356+
foreach ($fileData['lines'] as $line => $lineCoverage) {
357+
if ($lineCoverage === Driver::LINE_EXECUTED) {
358+
$this->addCoverageLinePathCovered($file, $line, true);
359+
$this->addCoverageLineTest($file, $line, $id);
360+
}
361+
}
362+
363+
foreach ($fileData['functions'] as $function => $functionCoverage) {
364+
foreach ($functionCoverage['branches'] as $branch => $branchCoverage) {
365+
if (($branchCoverage['hit'] ?? 0) === 1) {
366+
$this->addCoverageBranchHit($file, $function, $branch, $branchCoverage['hit'] ?? 0);
367+
$this->addCoverageBranchTest($file, $function, $branch, $id);
360368
}
361369
}
370+
371+
foreach ($functionCoverage['paths'] as $path => $pathCoverage) {
372+
$this->addCoveragePathHit($file, $function, $path, $pathCoverage['hit'] ?? 0);
373+
}
362374
}
363375
}
364376

@@ -376,10 +388,13 @@ public function merge(self $that): void
376388
\array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles())
377389
);
378390

379-
foreach ($that->data as $file => $lines) {
380-
if (!isset($this->data[$file])) {
391+
$thisData = $this->getData();
392+
$thatData = $that->getData();
393+
394+
foreach ($thatData as $file => $fileData) {
395+
if (!isset($thisData[$file])) {
381396
if (!$this->filter->isFiltered($file)) {
382-
$this->data[$file] = $lines;
397+
$thisData[$file] = $fileData;
383398
}
384399

385400
continue;
@@ -388,27 +403,30 @@ public function merge(self $that): void
388403
// we should compare the lines if any of two contains data
389404
$compareLineNumbers = \array_unique(
390405
\array_merge(
391-
\array_keys($this->data[$file]),
392-
\array_keys($that->data[$file])
406+
\array_keys($thisData[$file]['lines']),
407+
\array_keys($thatData[$file]['lines']) // can this be $fileData?
393408
)
394409
);
395410

396411
foreach ($compareLineNumbers as $line) {
397-
$thatPriority = $this->getLinePriority($that->data[$file], $line);
398-
$thisPriority = $this->getLinePriority($this->data[$file], $line);
412+
$thatPriority = $this->getLinePriority($thatData[$file]['lines'], $line);
413+
$thisPriority = $this->getLinePriority($thisData[$file]['lines'], $line);
399414

400415
if ($thatPriority > $thisPriority) {
401-
$this->data[$file][$line] = $that->data[$file][$line];
402-
} elseif ($thatPriority === $thisPriority && \is_array($this->data[$file][$line])) {
403-
$this->data[$file][$line] = \array_unique(
404-
\array_merge($this->data[$file][$line], $that->data[$file][$line])
416+
$thisData[$file]['lines'][$line] = $thatData[$file]['lines'][$line];
417+
} elseif ($thatPriority === $thisPriority && \is_array($thisData[$file]['lines'][$line])) {
418+
if ($line['pathCovered'] === true) {
419+
$thisData[$file]['lines'][$line]['pathCovered'] = $line['pathCovered'];
420+
}
421+
$thisData[$file]['lines'][$line] = \array_unique(
422+
\array_merge($thisData[$file]['lines'][$line], $thatData[$file]['lines'][$line])
405423
);
406424
}
407425
}
408426
}
409427

410-
$this->tests = \array_merge($this->tests, $that->getTests());
411-
$this->report = null;
428+
$this->tests = \array_merge($this->tests, $that->getTests());
429+
$this->setData($thisData);
412430
}
413431

414432
public function setCacheTokens(bool $flag): void
@@ -493,12 +511,9 @@ public function setDetermineBranchCoverage(bool $flag): void
493511
*
494512
* During a merge, a higher number is better.
495513
*
496-
* @param array $data
497-
* @param int $line
498-
*
499514
* @return int
500515
*/
501-
private function getLinePriority($data, $line)
516+
private function getLinePriority(array $data, int $line)
502517
{
503518
if (!\array_key_exists($line, $data)) {
504519
return 1;
@@ -533,7 +548,10 @@ private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, ar
533548
throw new MissingCoversAnnotationException;
534549
}
535550

536-
$data = [];
551+
$data = [
552+
'lines' => [],
553+
'functions' => [],
554+
];
537555

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

545563
if ($this->checkForUnintentionallyCoveredCode &&
546564
(!$this->currentId instanceof TestCase ||
547-
(!$this->currentId->isMedium() && !$this->currentId->isLarge()))) {
565+
(!$this->currentId->isMedium() && !$this->currentId->isLarge()))) {
548566
$this->performUnintentionallyCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed);
549567
}
550568

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

557575
foreach (\array_keys($data) as $filename) {
558576
$_linesToBeCovered = \array_flip($linesToBeCovered[$filename]);
559-
$data[$filename] = \array_intersect_key($data[$filename], $_linesToBeCovered);
577+
578+
$data[$filename]['lines'] = \array_intersect_key(
579+
$data[$filename],
580+
$_linesToBeCovered
581+
);
560582
}
561583
}
562584

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

582604
foreach ($this->getLinesToBeIgnored($filename) as $line) {
583-
unset($data[$filename][$line]);
605+
unset($data[$filename]['lines'][$line]);
584606
}
585607
}
586608
}
587609

588610
private function initializeFilesThatAreSeenTheFirstTime(array $data): void
589611
{
590-
foreach ($data as $file => $lines) {
591-
if (!isset($this->data[$file]) && $this->filter->isFile($file)) {
592-
$this->data[$file] = [];
612+
foreach ($data as $file => $fileData) {
613+
if (isset($this->data[$file]) || !$this->filter->isFile($file)) {
614+
continue;
615+
}
616+
$this->initializeFileCoverageData($file);
593617

594-
foreach ($lines as $k => $v) {
595-
$this->data[$file][$k] = $v === -2 ? null : [];
618+
// If this particular line is identified as not covered, mark it as null
619+
foreach ($fileData['lines'] as $lineNumber => $flag) {
620+
if ($flag === Driver::LINE_NOT_EXECUTABLE) {
621+
$this->data[$file]['lines'][$lineNumber] = null;
596622
}
597623
}
624+
625+
foreach ($fileData['functions'] as $functionName => $functionData) {
626+
// @todo - should this have a helper to merge covered paths?
627+
$this->data[$file]['paths'][$functionName] = $functionData['paths'];
628+
629+
foreach ($functionData['branches'] as $branchIndex => $branchData) {
630+
$this->addCoverageBranchHit($file, $functionName, $branchIndex, $branchData['hit']);
631+
$this->addCoverageBranchLineStart($file, $functionName, $branchIndex, $branchData['line_start']);
632+
$this->addCoverageBranchLineEnd($file, $functionName, $branchIndex, $branchData['line_end']);
633+
634+
for ($curLine = $branchData['line_start']; $curLine < $branchData['line_end']; $curLine++) {
635+
if (isset($this->data[$file]['lines'][$curLine])) {
636+
$this->addCoverageLinePathCovered($file, $curLine, (bool) $branchData['hit']);
637+
}
638+
}
639+
}
640+
}
641+
}
642+
}
643+
644+
private function initializeFileCoverageData(string $file): void
645+
{
646+
if (!isset($this->data[$file]) && $this->filter->isFile($file)) {
647+
$this->data[$file] = [
648+
'lines' => [],
649+
'branches' => [],
650+
'paths' => [],
651+
];
652+
}
653+
}
654+
655+
private function addCoverageLinePathCovered(string $file, int $lineNumber, bool $isCovered): void
656+
{
657+
$this->initializeFileCoverageData($file);
658+
659+
// Initialize the data coverage array for this line
660+
if (!isset($this->data[$file]['lines'][$lineNumber])) {
661+
$this->data[$file]['lines'][$lineNumber] = [
662+
'pathCovered' => false,
663+
'tests' => [],
664+
];
665+
}
666+
667+
$this->data[$file]['lines'][$lineNumber]['pathCovered'] = $isCovered;
668+
}
669+
670+
private function addCoverageLineTest(string $file, int $lineNumber, string $testId): void
671+
{
672+
$this->initializeFileCoverageData($file);
673+
674+
// Initialize the data coverage array for this line
675+
if (!isset($this->data[$file]['lines'][$lineNumber])) {
676+
$this->data[$file]['lines'][$lineNumber] = [
677+
'pathCovered' => false,
678+
'tests' => [],
679+
];
680+
}
681+
682+
if (!\in_array($testId, $this->data[$file]['lines'][$lineNumber]['tests'], true)) {
683+
$this->data[$file]['lines'][$lineNumber]['tests'][] = $testId;
684+
}
685+
}
686+
687+
private function addCoverageBranchHit(string $file, string $functionName, int $branchIndex, int $hit): void
688+
{
689+
$this->initializeFileCoverageData($file);
690+
691+
if (!\array_key_exists($functionName, $this->data[$file]['branches'])) {
692+
$this->data[$file]['branches'][$functionName] = [];
693+
}
694+
695+
if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) {
696+
$this->data[$file]['branches'][$functionName][$branchIndex] = [
697+
'hit' => 0,
698+
'line_start' => 0,
699+
'line_end' => 0,
700+
'tests' => [],
701+
];
598702
}
703+
704+
$this->data[$file]['branches'][$functionName][$branchIndex]['hit'] = \max(
705+
$this->data[$file]['branches'][$functionName][$branchIndex]['hit'],
706+
$hit
707+
);
708+
}
709+
710+
private function addCoverageBranchLineStart(
711+
string $file,
712+
string $functionName,
713+
int $branchIndex,
714+
int $lineStart
715+
): void {
716+
$this->initializeFileCoverageData($file);
717+
718+
if (!\array_key_exists($functionName, $this->data[$file]['branches'])) {
719+
$this->data[$file]['branches'][$functionName] = [];
720+
}
721+
722+
if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) {
723+
$this->data[$file]['branches'][$functionName][$branchIndex] = [
724+
'hit' => 0,
725+
'line_start' => 0,
726+
'line_end' => 0,
727+
'tests' => [],
728+
];
729+
}
730+
731+
$this->data[$file]['branches'][$functionName][$branchIndex]['line_start'] = $lineStart;
732+
}
733+
734+
private function addCoverageBranchLineEnd(
735+
string $file,
736+
string $functionName,
737+
int $branchIndex,
738+
int $lineEnd
739+
): void {
740+
$this->initializeFileCoverageData($file);
741+
742+
if (!\array_key_exists($functionName, $this->data[$file]['branches'])) {
743+
$this->data[$file]['branches'][$functionName] = [];
744+
}
745+
746+
if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) {
747+
$this->data[$file]['branches'][$functionName][$branchIndex] = [
748+
'hit' => 0,
749+
'line_start' => 0,
750+
'line_end' => 0,
751+
'tests' => [],
752+
];
753+
}
754+
755+
$this->data[$file]['branches'][$functionName][$branchIndex]['line_end'] = $lineEnd;
756+
}
757+
758+
private function addCoverageBranchTest(
759+
string $file,
760+
string $functionName,
761+
int $branchIndex,
762+
string $testId
763+
): void {
764+
$this->initializeFileCoverageData($file);
765+
766+
if (!\array_key_exists($functionName, $this->data[$file]['branches'])) {
767+
$this->data[$file]['branches'][$functionName] = [];
768+
}
769+
770+
if (!\array_key_exists($branchIndex, $this->data[$file]['branches'][$functionName])) {
771+
$this->data[$file]['branches'][$functionName][$branchIndex] = [
772+
'hit' => 0,
773+
'line_start' => 0,
774+
'line_end' => 0,
775+
'tests' => [],
776+
];
777+
}
778+
779+
if (!\in_array($testId, $this->data[$file]['branches'][$functionName][$branchIndex]['tests'], true)) {
780+
$this->data[$file]['branches'][$functionName][$branchIndex]['tests'][] = $testId;
781+
}
782+
}
783+
784+
private function addCoveragePathHit(
785+
string $file,
786+
string $functionName,
787+
int $pathId,
788+
int $hit
789+
): void {
790+
$this->initializeFileCoverageData($file);
791+
792+
if (!\array_key_exists($functionName, $this->data[$file]['paths'])) {
793+
$this->data[$file]['paths'][$functionName] = [];
794+
}
795+
796+
if (!\array_key_exists($pathId, $this->data[$file]['paths'][$functionName])) {
797+
$this->data[$file]['paths'][$functionName][$pathId] = [
798+
'hit' => 0,
799+
'path' => [],
800+
];
801+
}
802+
803+
$this->data[$file]['paths'][$functionName][$pathId]['hit'] = \max(
804+
$this->data[$file]['paths'][$functionName][$pathId]['hit'],
805+
$hit
806+
);
599807
}
600808

601809
/**
@@ -619,13 +827,17 @@ private function addUncoveredFilesFromWhitelist(): void
619827
continue;
620828
}
621829

622-
$data[$uncoveredFile] = [];
830+
$data[$uncoveredFile] = [
831+
'lines' => [],
832+
'functions' => [],
833+
];
623834

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

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

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

8181030
$unintentionallyCoveredUnits = [];
8191031

820-
foreach ($data as $file => $_data) {
821-
foreach ($_data as $line => $flag) {
822-
if ($flag === 1 && !isset($allowedLines[$file][$line])) {
823-
$unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line);
1032+
foreach ($data as $file => $fileData) {
1033+
foreach ($fileData['lines'] as $lineNumber => $flag) {
1034+
if ($flag === 1 && !isset($allowedLines[$file][$lineNumber])) {
1035+
$unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $lineNumber);
8241036
}
8251037
}
8261038
}

0 commit comments

Comments
 (0)