From abbc59006de0595be12fd82b227ee58214aa264e Mon Sep 17 00:00:00 2001 From: Bastien Philippe Date: Thu, 1 Oct 2020 09:47:51 +0200 Subject: [PATCH] Add support for non class based lines in cobertura --- src/Node/File.php | 1 + src/Report/Cobertura.php | 93 +++++++++++++++++++ tests/TestCase.php | 35 +++++++ .../class-with-outside-function-cobertura.xml | 41 ++++++++ ...source_with_class_and_outside_function.php | 17 ++++ tests/tests/CoberturaTest.php | 10 ++ 6 files changed, 197 insertions(+) create mode 100644 tests/_files/class-with-outside-function-cobertura.xml create mode 100644 tests/_files/source_with_class_and_outside_function.php diff --git a/src/Node/File.php b/src/Node/File.php index c0ee09390..290ae731a 100644 --- a/src/Node/File.php +++ b/src/Node/File.php @@ -532,6 +532,7 @@ private function processFunctions(array $functions): void 'namespace' => $function['namespace'], 'signature' => $function['signature'], 'startLine' => $function['startLine'], + 'endLine' => $function['endLine'], 'executableLines' => 0, 'executedLines' => 0, 'executableBranches' => 0, diff --git a/src/Report/Cobertura.php b/src/Report/Cobertura.php index fa38b64f2..6ec0e1c66 100644 --- a/src/Report/Cobertura.php +++ b/src/Report/Cobertura.php @@ -192,7 +192,100 @@ public function process(CodeCoverage $coverage, ?string $target = null, ?string } } + if ($report->numberOfFunctions() === 0) { + $packageElement->setAttribute('complexity', (string) $packageComplexity); + + continue; + } + + $functionsComplexity = 0; + $functionsLinesValid = 0; + $functionsLinesCovered = 0; + $functionsBranchesValid = 0; + $functionsBranchesCovered = 0; + + $classElement = $document->createElement('class'); + $classElement->setAttribute('name', basename($item->pathAsString())); + $classElement->setAttribute('filename', str_replace($report->pathAsString() . '/', '', $item->pathAsString())); + + $methodsElement = $document->createElement('methods'); + + $classElement->appendChild($methodsElement); + + $classLinesElement = $document->createElement('lines'); + + $classElement->appendChild($classLinesElement); + + $functions = $report->functions(); + + foreach ($functions as $functionName => $function) { + if ($function['executableLines'] === 0) { + continue; + } + + $complexity += $function['ccn']; + $packageComplexity += $function['ccn']; + $functionsComplexity += $function['ccn']; + + $linesValid = $function['executableLines']; + $linesCovered = $function['executedLines']; + $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid); + + $functionsLinesValid += $linesValid; + $functionsLinesCovered += $linesCovered; + + $branchesValid = $function['executableBranches']; + $branchesCovered = $function['executedBranches']; + $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid); + + $functionsBranchesValid += $branchesValid; + $functionsBranchesCovered += $branchesValid; + + $methodElement = $document->createElement('method'); + + $methodElement->setAttribute('name', $functionName); + $methodElement->setAttribute('signature', $function['signature']); + $methodElement->setAttribute('line-rate', (string) $lineRate); + $methodElement->setAttribute('branch-rate', (string) $branchRate); + $methodElement->setAttribute('complexity', (string) $function['ccn']); + + $methodLinesElement = $document->createElement('lines'); + + $methodElement->appendChild($methodLinesElement); + + foreach (range($function['startLine'], $function['endLine']) as $line) { + if (!isset($coverageData[$line]) || $coverageData[$line] === null) { + continue; + } + $methodLineElement = $document->createElement('line'); + + $methodLineElement->setAttribute('number', (string) $line); + $methodLineElement->setAttribute('hits', (string) count($coverageData[$line])); + + $methodLinesElement->appendChild($methodLineElement); + + $classLineElement = $methodLineElement->cloneNode(); + + $classLinesElement->appendChild($classLineElement); + } + + $methodsElement->appendChild($methodElement); + } + $packageElement->setAttribute('complexity', (string) $packageComplexity); + + if ($functionsLinesValid === 0) { + continue; + } + + $lineRate = $functionsLinesCovered / $functionsLinesValid; + $branchRate = $functionsBranchesValid === 0 ? 0 : ($functionsBranchesCovered / $functionsBranchesValid); + + $classElement->setAttribute('line-rate', (string) $lineRate); + $classElement->setAttribute('branch-rate', (string) $branchRate); + $classElement->setAttribute('complexity', (string) $functionsComplexity); + + $classesElement->appendChild($classElement); } $coverageElement->setAttribute('complexity', (string) $complexity); diff --git a/tests/TestCase.php b/tests/TestCase.php index 52e8f042b..5a9d7b4cd 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1728,6 +1728,41 @@ protected function setUpXdebugStubForClassWithAnonymousFunction(): Driver return $stub; } + protected function getCoverageForClassWithOutsideFunction(): CodeCoverage + { + $filter = new Filter; + $filter->includeFile(TEST_FILES_PATH . 'source_with_class_and_outside_function.php'); + + $coverage = new CodeCoverage( + $this->setUpXdebugStubForClassWithOutsideFunction(), + $filter + ); + + $coverage->start('ClassWithOutsideFunction', true); + $coverage->stop(); + + return $coverage; + } + + protected function setUpXdebugStubForClassWithOutsideFunction(): Driver + { + $stub = $this->createStub(Driver::class); + + $stub->method('stop') + ->willReturn(RawCodeCoverageData::fromXdebugWithoutPathCoverage( + [ + TEST_FILES_PATH . 'source_with_class_and_outside_function.php' => [ + 6 => 1, + 12 => 1, + 13 => 1, + 16 => -1, + ], + ] + )); + + return $stub; + } + protected function removeTemporaryFiles(): void { $tmpFilesIterator = new RecursiveIteratorIterator( diff --git a/tests/_files/class-with-outside-function-cobertura.xml b/tests/_files/class-with-outside-function-cobertura.xml new file mode 100644 index 000000000..63bc57765 --- /dev/null +++ b/tests/_files/class-with-outside-function-cobertura.xml @@ -0,0 +1,41 @@ + + + + + %s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/_files/source_with_class_and_outside_function.php b/tests/_files/source_with_class_and_outside_function.php new file mode 100644 index 000000000..e04ac8dc6 --- /dev/null +++ b/tests/_files/source_with_class_and_outside_function.php @@ -0,0 +1,17 @@ +process($this->getCoverageForClassWithAnonymousFunction()) ); } + + public function testCoberturaForClassAndOutsideFunction(): void + { + $cobertura = new Cobertura; + + $this->assertStringMatchesFormatFile( + TEST_FILES_PATH . 'class-with-outside-function-cobertura.xml', + $cobertura->process($this->getCoverageForClassWithOutsideFunction()) + ); + } }