Cobertura.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of phpunit/php-code-coverage.
  4. *
  5. * (c) Sebastian Bergmann <sebastian@phpunit.de>
  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. use function basename;
  12. use function count;
  13. use function dirname;
  14. use function file_put_contents;
  15. use function preg_match;
  16. use function range;
  17. use function str_contains;
  18. use function str_replace;
  19. use function time;
  20. use DOMImplementation;
  21. use SebastianBergmann\CodeCoverage\CodeCoverage;
  22. use SebastianBergmann\CodeCoverage\Driver\WriteOperationFailedException;
  23. use SebastianBergmann\CodeCoverage\Node\File;
  24. use SebastianBergmann\CodeCoverage\Util\Filesystem;
  25. final class Cobertura
  26. {
  27. /**
  28. * @throws WriteOperationFailedException
  29. */
  30. public function process(CodeCoverage $coverage, ?string $target = null): string
  31. {
  32. $time = (string) time();
  33. $report = $coverage->getReport();
  34. $implementation = new DOMImplementation;
  35. $documentType = $implementation->createDocumentType(
  36. 'coverage',
  37. '',
  38. 'http://cobertura.sourceforge.net/xml/coverage-04.dtd',
  39. );
  40. $document = $implementation->createDocument('', '', $documentType);
  41. $document->xmlVersion = '1.0';
  42. $document->encoding = 'UTF-8';
  43. $document->formatOutput = true;
  44. $coverageElement = $document->createElement('coverage');
  45. $linesValid = $report->numberOfExecutableLines();
  46. $linesCovered = $report->numberOfExecutedLines();
  47. $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
  48. $coverageElement->setAttribute('line-rate', (string) $lineRate);
  49. $branchesValid = $report->numberOfExecutableBranches();
  50. $branchesCovered = $report->numberOfExecutedBranches();
  51. $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
  52. $coverageElement->setAttribute('branch-rate', (string) $branchRate);
  53. $coverageElement->setAttribute('lines-covered', (string) $report->numberOfExecutedLines());
  54. $coverageElement->setAttribute('lines-valid', (string) $report->numberOfExecutableLines());
  55. $coverageElement->setAttribute('branches-covered', (string) $report->numberOfExecutedBranches());
  56. $coverageElement->setAttribute('branches-valid', (string) $report->numberOfExecutableBranches());
  57. $coverageElement->setAttribute('complexity', '');
  58. $coverageElement->setAttribute('version', '0.4');
  59. $coverageElement->setAttribute('timestamp', $time);
  60. $document->appendChild($coverageElement);
  61. $sourcesElement = $document->createElement('sources');
  62. $coverageElement->appendChild($sourcesElement);
  63. $sourceElement = $document->createElement('source', $report->pathAsString());
  64. $sourcesElement->appendChild($sourceElement);
  65. $packagesElement = $document->createElement('packages');
  66. $coverageElement->appendChild($packagesElement);
  67. $complexity = 0;
  68. foreach ($report as $item) {
  69. if (!$item instanceof File) {
  70. continue;
  71. }
  72. $packageElement = $document->createElement('package');
  73. $packageComplexity = 0;
  74. $packageElement->setAttribute('name', str_replace($report->pathAsString() . DIRECTORY_SEPARATOR, '', $item->pathAsString()));
  75. $linesValid = $item->numberOfExecutableLines();
  76. $linesCovered = $item->numberOfExecutedLines();
  77. $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
  78. $packageElement->setAttribute('line-rate', (string) $lineRate);
  79. $branchesValid = $item->numberOfExecutableBranches();
  80. $branchesCovered = $item->numberOfExecutedBranches();
  81. $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
  82. $packageElement->setAttribute('branch-rate', (string) $branchRate);
  83. $packageElement->setAttribute('complexity', '');
  84. $packagesElement->appendChild($packageElement);
  85. $classesElement = $document->createElement('classes');
  86. $packageElement->appendChild($classesElement);
  87. $classes = $item->classesAndTraits();
  88. $coverageData = $item->lineCoverageData();
  89. foreach ($classes as $className => $class) {
  90. $complexity += $class['ccn'];
  91. $packageComplexity += $class['ccn'];
  92. if (!empty($class['package']['namespace'])) {
  93. $className = $class['package']['namespace'] . '\\' . $className;
  94. }
  95. $linesValid = $class['executableLines'];
  96. $linesCovered = $class['executedLines'];
  97. $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
  98. $branchesValid = $class['executableBranches'];
  99. $branchesCovered = $class['executedBranches'];
  100. $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
  101. $classElement = $document->createElement('class');
  102. $classElement->setAttribute('name', $className);
  103. $classElement->setAttribute('filename', str_replace($report->pathAsString() . DIRECTORY_SEPARATOR, '', $item->pathAsString()));
  104. $classElement->setAttribute('line-rate', (string) $lineRate);
  105. $classElement->setAttribute('branch-rate', (string) $branchRate);
  106. $classElement->setAttribute('complexity', (string) $class['ccn']);
  107. $classesElement->appendChild($classElement);
  108. $methodsElement = $document->createElement('methods');
  109. $classElement->appendChild($methodsElement);
  110. $classLinesElement = $document->createElement('lines');
  111. $classElement->appendChild($classLinesElement);
  112. foreach ($class['methods'] as $methodName => $method) {
  113. if ($method['executableLines'] === 0) {
  114. continue;
  115. }
  116. preg_match("/\((.*?)\)/", $method['signature'], $signature);
  117. $linesValid = $method['executableLines'];
  118. $linesCovered = $method['executedLines'];
  119. $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
  120. $branchesValid = $method['executableBranches'];
  121. $branchesCovered = $method['executedBranches'];
  122. $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
  123. $methodElement = $document->createElement('method');
  124. $methodElement->setAttribute('name', $methodName);
  125. $methodElement->setAttribute('signature', $signature[1]);
  126. $methodElement->setAttribute('line-rate', (string) $lineRate);
  127. $methodElement->setAttribute('branch-rate', (string) $branchRate);
  128. $methodElement->setAttribute('complexity', (string) $method['ccn']);
  129. $methodLinesElement = $document->createElement('lines');
  130. $methodElement->appendChild($methodLinesElement);
  131. foreach (range($method['startLine'], $method['endLine']) as $line) {
  132. if (!isset($coverageData[$line])) {
  133. continue;
  134. }
  135. $methodLineElement = $document->createElement('line');
  136. $methodLineElement->setAttribute('number', (string) $line);
  137. $methodLineElement->setAttribute('hits', (string) count($coverageData[$line]));
  138. $methodLinesElement->appendChild($methodLineElement);
  139. $classLineElement = $methodLineElement->cloneNode();
  140. $classLinesElement->appendChild($classLineElement);
  141. }
  142. $methodsElement->appendChild($methodElement);
  143. }
  144. }
  145. if ($item->numberOfFunctions() === 0) {
  146. $packageElement->setAttribute('complexity', (string) $packageComplexity);
  147. continue;
  148. }
  149. $functionsComplexity = 0;
  150. $functionsLinesValid = 0;
  151. $functionsLinesCovered = 0;
  152. $functionsBranchesValid = 0;
  153. $functionsBranchesCovered = 0;
  154. $classElement = $document->createElement('class');
  155. $classElement->setAttribute('name', basename($item->pathAsString()));
  156. $classElement->setAttribute('filename', str_replace($report->pathAsString() . DIRECTORY_SEPARATOR, '', $item->pathAsString()));
  157. $methodsElement = $document->createElement('methods');
  158. $classElement->appendChild($methodsElement);
  159. $classLinesElement = $document->createElement('lines');
  160. $classElement->appendChild($classLinesElement);
  161. $functions = $item->functions();
  162. foreach ($functions as $functionName => $function) {
  163. if ($function['executableLines'] === 0) {
  164. continue;
  165. }
  166. $complexity += $function['ccn'];
  167. $packageComplexity += $function['ccn'];
  168. $functionsComplexity += $function['ccn'];
  169. $linesValid = $function['executableLines'];
  170. $linesCovered = $function['executedLines'];
  171. $lineRate = $linesValid === 0 ? 0 : ($linesCovered / $linesValid);
  172. $functionsLinesValid += $linesValid;
  173. $functionsLinesCovered += $linesCovered;
  174. $branchesValid = $function['executableBranches'];
  175. $branchesCovered = $function['executedBranches'];
  176. $branchRate = $branchesValid === 0 ? 0 : ($branchesCovered / $branchesValid);
  177. $functionsBranchesValid += $branchesValid;
  178. $functionsBranchesCovered += $branchesValid;
  179. $methodElement = $document->createElement('method');
  180. $methodElement->setAttribute('name', $functionName);
  181. $methodElement->setAttribute('signature', $function['signature']);
  182. $methodElement->setAttribute('line-rate', (string) $lineRate);
  183. $methodElement->setAttribute('branch-rate', (string) $branchRate);
  184. $methodElement->setAttribute('complexity', (string) $function['ccn']);
  185. $methodLinesElement = $document->createElement('lines');
  186. $methodElement->appendChild($methodLinesElement);
  187. foreach (range($function['startLine'], $function['endLine']) as $line) {
  188. if (!isset($coverageData[$line])) {
  189. continue;
  190. }
  191. $methodLineElement = $document->createElement('line');
  192. $methodLineElement->setAttribute('number', (string) $line);
  193. $methodLineElement->setAttribute('hits', (string) count($coverageData[$line]));
  194. $methodLinesElement->appendChild($methodLineElement);
  195. $classLineElement = $methodLineElement->cloneNode();
  196. $classLinesElement->appendChild($classLineElement);
  197. }
  198. $methodsElement->appendChild($methodElement);
  199. }
  200. $packageElement->setAttribute('complexity', (string) $packageComplexity);
  201. if ($functionsLinesValid === 0) {
  202. continue;
  203. }
  204. $lineRate = $functionsLinesCovered / $functionsLinesValid;
  205. $branchRate = $functionsBranchesValid === 0 ? 0 : ($functionsBranchesCovered / $functionsBranchesValid);
  206. $classElement->setAttribute('line-rate', (string) $lineRate);
  207. $classElement->setAttribute('branch-rate', (string) $branchRate);
  208. $classElement->setAttribute('complexity', (string) $functionsComplexity);
  209. $classesElement->appendChild($classElement);
  210. }
  211. $coverageElement->setAttribute('complexity', (string) $complexity);
  212. $buffer = $document->saveXML();
  213. if ($target !== null) {
  214. if (!str_contains($target, '://')) {
  215. Filesystem::createDirectory(dirname($target));
  216. }
  217. if (@file_put_contents($target, $buffer) === false) {
  218. throw new WriteOperationFailedException($target);
  219. }
  220. }
  221. return $buffer;
  222. }
  223. }