Skip to content

Commit 3670996

Browse files
committed
Add Cobertura report format
1 parent c9394cb commit 3670996

File tree

6 files changed

+393
-0
lines changed

6 files changed

+393
-0
lines changed

src/Report/Cobertura.php

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of phpunit/php-code-coverage.
4+
*
5+
* (c) Sebastian Bergmann <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace SebastianBergmann\CodeCoverage\Report;
11+
12+
use function count;
13+
use function dirname;
14+
use function file_put_contents;
15+
use function max;
16+
use function range;
17+
use function time;
18+
use DOMImplementation;
19+
use SebastianBergmann\CodeCoverage\CodeCoverage;
20+
use SebastianBergmann\CodeCoverage\Directory;
21+
use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException;
22+
use SebastianBergmann\CodeCoverage\Node\File;
23+
24+
/**
25+
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
26+
*/
27+
final class Cobertura
28+
{
29+
/**
30+
* @throws WriteOperationFailedException
31+
*/
32+
public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string
33+
{
34+
$time = (string) time();
35+
36+
$report = $coverage->getReport();
37+
38+
$impl = new DOMImplementation();
39+
$dtd = $impl->createDocumentType(
40+
'coverage',
41+
'',
42+
'http://cobertura.sourceforge.net/xml/coverage-04.dtd'
43+
);
44+
45+
$xmlDocument = $impl->createDocument('', '', $dtd);
46+
$xmlDocument->xmlVersion = '1.0';
47+
$xmlDocument->encoding = 'UTF-8';
48+
$xmlDocument->formatOutput = true;
49+
50+
$xmlCoverage = $xmlDocument->createElement('coverage');
51+
52+
// Line rate.
53+
$linesValid = $report->numberOfExecutedLines();
54+
$linesCovered = $report->numberOfExecutableLines();
55+
$lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
56+
$xmlCoverage->setAttribute('line-rate', (string) $lineRate);
57+
58+
// Branch rate.
59+
$branchesValid = $report->numberOfExecutedBranches();
60+
$branchesCovered = $report->numberOfExecutableBranches();
61+
$branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
62+
$xmlCoverage->setAttribute('branch-rate', (string) $branchRate);
63+
64+
$xmlCoverage->setAttribute('lines-covered', (string) $report->numberOfExecutedLines());
65+
$xmlCoverage->setAttribute('lines-valid', (string) $report->numberOfExecutableLines());
66+
$xmlCoverage->setAttribute('branches-covered', (string) $report->numberOfExecutedBranches());
67+
$xmlCoverage->setAttribute('branches-valid', (string) $report->numberOfExecutableBranches());
68+
$xmlCoverage->setAttribute('complexity', '');
69+
$xmlCoverage->setAttribute('version', '0.4');
70+
$xmlCoverage->setAttribute('timestamp', $time);
71+
$xmlDocument->appendChild($xmlCoverage);
72+
73+
$xmlSources = $xmlDocument->createElement('sources');
74+
$xmlCoverage->appendChild($xmlSources);
75+
76+
$xmlSource = $xmlDocument->createElement('source', $report->pathAsString());
77+
$xmlSources->appendChild($xmlSource);
78+
79+
$xmlPackages = $xmlDocument->createElement('packages');
80+
$xmlCoverage->appendChild($xmlPackages);
81+
82+
$complexity = 0;
83+
84+
foreach ($report as $item) {
85+
if (!$item instanceof File) {
86+
continue;
87+
}
88+
89+
$xmlPackage = $xmlDocument->createElement('package');
90+
91+
$packageName = '';
92+
93+
if ($name !== null) {
94+
$packageName = $name;
95+
}
96+
$xmlPackage->setAttribute('name', $packageName);
97+
$xmlPackages->appendChild($xmlPackage);
98+
99+
$classes = $item->classesAndTraits();
100+
$coverageData = $item->lineCoverageData();
101+
102+
foreach ($classes as $className => $class) {
103+
$complexity += $class['ccn'];
104+
105+
if (!empty($class['package']['namespace'])) {
106+
$className = $class['package']['namespace'] . '\\' . $className;
107+
}
108+
109+
$linesValid = $class['executableLines'];
110+
$linesCovered = $class['executedLines'];
111+
$lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
112+
113+
$branchesValid = $class['executableBranches'];
114+
$branchesCovered = $class['executedBranches'];
115+
$branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
116+
117+
$xmlClass = $xmlDocument->createElement('class');
118+
$xmlClass->setAttribute('name', $className);
119+
$xmlClass->setAttribute('filename', str_replace($report->pathAsString() . '/', '', $item->pathAsString()));
120+
$xmlClass->setAttribute('line-rate', (string) $lineRate);
121+
$xmlClass->setAttribute('branch-rate', (string) $branchRate);
122+
$xmlClass->setAttribute('complexity', (string) $class['ccn']);
123+
$xmlPackage->appendChild($xmlClass);
124+
125+
$xmlMethods = $xmlDocument->createElement('methods');
126+
$xmlClass->appendChild($xmlMethods);
127+
128+
$xmlClassLines = $xmlDocument->createElement('lines');
129+
$xmlClass->appendChild($xmlClassLines);
130+
131+
foreach ($class['methods'] as $methodName => $method) {
132+
if ($method['executableLines'] == 0) {
133+
continue;
134+
}
135+
136+
$methodCount = 0;
137+
138+
foreach (range($method['startLine'], $method['endLine']) as $line) {
139+
if (isset($coverageData[$line]) && $coverageData[$line] !== null) {
140+
$methodCount = max($methodCount, count($coverageData[$line]));
141+
142+
$xmlClassLine = $xmlDocument->createElement('line');
143+
$xmlClassLine->setAttribute('number', (string) $line);
144+
$xmlClassLine->setAttribute('hits', (string) count($coverageData[$line]));
145+
$xmlClassLines->appendChild($xmlClassLine);
146+
}
147+
}
148+
149+
$linesValid = $method['executableLines'];
150+
$linesCovered = $method['executedLines'];
151+
$lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
152+
153+
$branchesValid = $method['executableBranches'];
154+
$branchesCovered = $method['executedBranches'];
155+
$branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
156+
157+
$xmlMethod = $xmlDocument->createElement('method');
158+
$xmlMethod->setAttribute('name', $methodName);
159+
$xmlMethod->setAttribute('signature', $method['signature']);
160+
$xmlMethod->setAttribute('line-rate', (string) $lineRate);
161+
$xmlMethod->setAttribute('branch-rate', (string) $branchRate);
162+
$xmlMethod->setAttribute('complexity', (string) $method['ccn']);
163+
164+
$xmlLines = $xmlDocument->createElement('lines');
165+
$xmlMethod->appendChild($xmlLines);
166+
167+
$xmlLine = $xmlDocument->createElement('line');
168+
$xmlLine->setAttribute('number', (string) $method['startLine']);
169+
$xmlLine->setAttribute('hits', (string) $methodCount);
170+
$xmlLines->appendChild($xmlLine);
171+
172+
$xmlMethods->appendChild($xmlMethod);
173+
}
174+
}
175+
}
176+
177+
$xmlCoverage->setAttribute('complexity', (string) $complexity);
178+
179+
$buffer = $xmlDocument->saveXML();
180+
181+
if ($target !== null) {
182+
Directory::create(dirname($target));
183+
184+
if (@file_put_contents($target, $buffer) === false) {
185+
throw new WriteOperationFailedException($target);
186+
}
187+
}
188+
189+
return $buffer;
190+
}
191+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
3+
<coverage line-rate="2" branch-rate="0" lines-covered="5" lines-valid="10" branches-covered="0" branches-valid="0" complexity="5" version="0.4" timestamp="%i">
4+
<sources>
5+
<source>%s</source>
6+
</sources>
7+
<packages>
8+
<package name="BankAccount">
9+
<class name="BankAccount" filename="BankAccount.php" line-rate="0.5" branch-rate="0" complexity="5">
10+
<methods>
11+
<method name="getBalance" signature="getBalance()" line-rate="1" branch-rate="0" complexity="1">
12+
<lines>
13+
<line number="6" hits="2"/>
14+
</lines>
15+
</method>
16+
<method name="setBalance" signature="setBalance($balance)" line-rate="0" branch-rate="0" complexity="2">
17+
<lines>
18+
<line number="11" hits="0"/>
19+
</lines>
20+
</method>
21+
<method name="depositMoney" signature="depositMoney($balance)" line-rate="1" branch-rate="0" complexity="1">
22+
<lines>
23+
<line number="20" hits="2"/>
24+
</lines>
25+
</method>
26+
<method name="withdrawMoney" signature="withdrawMoney($balance)" line-rate="1" branch-rate="0" complexity="1">
27+
<lines>
28+
<line number="27" hits="2"/>
29+
</lines>
30+
</method>
31+
</methods>
32+
<lines>
33+
<line number="8" hits="2"/>
34+
<line number="13" hits="0"/>
35+
<line number="14" hits="0"/>
36+
<line number="15" hits="0"/>
37+
<line number="16" hits="0"/>
38+
<line number="18" hits="0"/>
39+
<line number="22" hits="2"/>
40+
<line number="24" hits="1"/>
41+
<line number="29" hits="2"/>
42+
<line number="31" hits="1"/>
43+
</lines>
44+
</class>
45+
</package>
46+
</packages>
47+
</coverage>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
3+
<coverage line-rate="2" branch-rate="2.3333333333333" lines-covered="5" lines-valid="10" branches-covered="3" branches-valid="7" complexity="5" version="0.4" timestamp="%i">
4+
<sources>
5+
<source>%s</source>
6+
</sources>
7+
<packages>
8+
<package name="BankAccount">
9+
<class name="BankAccount" filename="BankAccount.php" line-rate="0.5" branch-rate="0.42857142857143" complexity="5">
10+
<methods>
11+
<method name="getBalance" signature="getBalance()" line-rate="1" branch-rate="1" complexity="1">
12+
<lines>
13+
<line number="6" hits="2"/>
14+
</lines>
15+
</method>
16+
<method name="setBalance" signature="setBalance($balance)" line-rate="0" branch-rate="0" complexity="2">
17+
<lines>
18+
<line number="11" hits="0"/>
19+
</lines>
20+
</method>
21+
<method name="depositMoney" signature="depositMoney($balance)" line-rate="1" branch-rate="1" complexity="1">
22+
<lines>
23+
<line number="20" hits="2"/>
24+
</lines>
25+
</method>
26+
<method name="withdrawMoney" signature="withdrawMoney($balance)" line-rate="1" branch-rate="1" complexity="1">
27+
<lines>
28+
<line number="27" hits="2"/>
29+
</lines>
30+
</method>
31+
</methods>
32+
<lines>
33+
<line number="8" hits="2"/>
34+
<line number="13" hits="0"/>
35+
<line number="14" hits="0"/>
36+
<line number="15" hits="0"/>
37+
<line number="16" hits="0"/>
38+
<line number="18" hits="0"/>
39+
<line number="22" hits="2"/>
40+
<line number="24" hits="1"/>
41+
<line number="29" hits="2"/>
42+
<line number="31" hits="1"/>
43+
</lines>
44+
</class>
45+
</package>
46+
</packages>
47+
</coverage>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
3+
<coverage line-rate="1.125" branch-rate="0" lines-covered="8" lines-valid="9" branches-covered="0" branches-valid="0" complexity="1" version="0.4" timestamp="%i">
4+
<sources>
5+
<source>%s</source>
6+
</sources>
7+
<packages>
8+
<package name="">
9+
<class name="CoveredClassWithAnonymousFunctionInStaticMethod" filename="source_with_class_and_anonymous_function.php" line-rate="0.88888888888889" branch-rate="0" complexity="1">
10+
<methods>
11+
<method name="runAnonymous" signature="runAnonymous()" line-rate="0.88888888888889" branch-rate="0" complexity="1">
12+
<lines>
13+
<line number="5" hits="1"/>
14+
</lines>
15+
</method>
16+
</methods>
17+
<lines>
18+
<line number="7" hits="1"/>
19+
<line number="9" hits="1"/>
20+
<line number="10" hits="0"/>
21+
<line number="11" hits="1"/>
22+
<line number="12" hits="1"/>
23+
<line number="13" hits="1"/>
24+
<line number="14" hits="1"/>
25+
<line number="17" hits="1"/>
26+
<line number="18" hits="1"/>
27+
</lines>
28+
</class>
29+
</package>
30+
</packages>
31+
</coverage>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
3+
<coverage line-rate="2" branch-rate="0" lines-covered="1" lines-valid="2" branches-covered="0" branches-valid="0" complexity="2" version="0.4" timestamp="%i">
4+
<sources>
5+
<source>%s</source>
6+
</sources>
7+
<packages>
8+
<package name="">
9+
<class name="Foo" filename="source_with_ignore.php" line-rate="0" branch-rate="0" complexity="1">
10+
<methods/>
11+
<lines/>
12+
</class>
13+
<class name="Bar" filename="source_with_ignore.php" line-rate="0" branch-rate="0" complexity="1">
14+
<methods/>
15+
<lines/>
16+
</class>
17+
</package>
18+
</packages>
19+
</coverage>

tests/tests/CoberturaTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of phpunit/php-code-coverage.
4+
*
5+
* (c) Sebastian Bergmann <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace SebastianBergmann\CodeCoverage\Report;
11+
12+
use SebastianBergmann\CodeCoverage\TestCase;
13+
14+
/**
15+
* @covers \SebastianBergmann\CodeCoverage\Report\Cobertura
16+
*/
17+
final class CoberturaTest extends TestCase
18+
{
19+
public function testLineCoverageForBankAccountTest(): void
20+
{
21+
$cobertura = new Cobertura;
22+
23+
$this->assertStringMatchesFormatFile(
24+
TEST_FILES_PATH . 'BankAccount-cobertura-line.xml',
25+
$cobertura->process($this->getLineCoverageForBankAccount(), null, 'BankAccount')
26+
);
27+
}
28+
29+
public function testPathCoverageForBankAccountTest(): void
30+
{
31+
$cobertura = new Cobertura;
32+
33+
$this->assertStringMatchesFormatFile(
34+
TEST_FILES_PATH . 'BankAccount-cobertura-path.xml',
35+
$cobertura->process($this->getPathCoverageForBankAccount(), null, 'BankAccount')
36+
);
37+
}
38+
39+
public function testCoberturaForFileWithIgnoredLines(): void
40+
{
41+
$cobertura = new Cobertura;
42+
43+
$this->assertStringMatchesFormatFile(
44+
TEST_FILES_PATH . 'ignored-lines-cobertura.xml',
45+
$cobertura->process($this->getCoverageForFileWithIgnoredLines())
46+
);
47+
}
48+
49+
public function testCoberturaForClassWithAnonymousFunction(): void
50+
{
51+
$cobertura = new Cobertura;
52+
53+
$this->assertStringMatchesFormatFile(
54+
TEST_FILES_PATH . 'class-with-anonymous-function-cobertura.xml',
55+
$cobertura->process($this->getCoverageForClassWithAnonymousFunction())
56+
);
57+
}
58+
}

0 commit comments

Comments
 (0)