Skip to content

Commit a6c5f4e

Browse files
Add test size filter to the class-oriented HTML report
1 parent ab2d7d1 commit a6c5f4e

38 files changed

Lines changed: 937 additions & 385 deletions

File tree

src/Report/Html/ClassView/Node/ClassNode.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@
1414
use SebastianBergmann\CodeCoverage\Data\ProcessedClassType;
1515
use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType;
1616
use SebastianBergmann\CodeCoverage\Node\File as FileNode;
17+
use SebastianBergmann\CodeCoverage\Test\TestSizes;
1718
use SebastianBergmann\CodeCoverage\Util\Percentage;
1819

1920
/**
2021
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
2122
*
2223
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
24+
*
25+
* @phpstan-import-type TestSizeSet from TestSizes
26+
* @phpstan-import-type TestSizeCounts from TestSizes
2327
*/
2428
final class ClassNode
2529
{
@@ -51,6 +55,16 @@ final class ClassNode
5155
private ?int $numMethods = null;
5256
private ?int $numTestedMethods = null;
5357

58+
/**
59+
* @var ?TestSizeCounts
60+
*/
61+
private ?array $numExecutedLinesByTestSize = null;
62+
63+
/**
64+
* @var ?TestSizeCounts
65+
*/
66+
private ?array $numTestedMethodsByTestSize = null;
67+
5468
/**
5569
* @param non-empty-string $className
5670
* @param non-empty-string $filePath
@@ -196,6 +210,34 @@ public function numberOfExecutedLines(): int
196210
return $lines;
197211
}
198212

213+
/**
214+
* @param TestSizeSet $testSizes
215+
*/
216+
public function numberOfExecutedLinesByTestSize(int $testSizes): int
217+
{
218+
if ($this->numExecutedLinesByTestSize === null) {
219+
$this->numExecutedLinesByTestSize = TestSizes::ZERO_COUNTS;
220+
221+
foreach (TestSizes::COMBINATIONS as $combination) {
222+
$lines = $this->class->executedLinesByTestSize[$combination];
223+
224+
foreach ($this->traitSections as $section) {
225+
$lines += $section->trait->executedLinesByTestSize[$combination];
226+
}
227+
228+
foreach ($this->parentSections as $section) {
229+
foreach ($section->methods as $method) {
230+
$lines += $method->executedLinesByTestSize[$combination];
231+
}
232+
}
233+
234+
$this->numExecutedLinesByTestSize[$combination] = $lines;
235+
}
236+
}
237+
238+
return $this->numExecutedLinesByTestSize[$testSizes];
239+
}
240+
199241
public function numberOfExecutableBranches(): int
200242
{
201243
$branches = $this->class->executableBranches;
@@ -294,6 +336,30 @@ public function numberOfTestedMethods(): int
294336
return $this->numTestedMethods;
295337
}
296338

339+
/**
340+
* @param TestSizeSet $testSizes
341+
*/
342+
public function numberOfTestedMethodsByTestSize(int $testSizes): int
343+
{
344+
if ($this->numTestedMethodsByTestSize === null) {
345+
$this->numTestedMethodsByTestSize = TestSizes::ZERO_COUNTS;
346+
347+
foreach ($this->allMethods() as $method) {
348+
if ($method->executableLines === 0) {
349+
continue;
350+
}
351+
352+
foreach (TestSizes::COMBINATIONS as $combination) {
353+
if ($method->executedLinesByTestSize[$combination] === $method->executableLines) {
354+
$this->numTestedMethodsByTestSize[$combination]++;
355+
}
356+
}
357+
}
358+
}
359+
360+
return $this->numTestedMethodsByTestSize[$testSizes];
361+
}
362+
297363
public function percentageOfExecutedLines(): Percentage
298364
{
299365
return Percentage::fromFractionAndTotal(

src/Report/Html/ClassView/Node/NamespaceNode.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use SebastianBergmann\CodeCoverage\Data\ProcessedClassType;
1616
use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType;
1717
use SebastianBergmann\CodeCoverage\Data\ProcessedTraitType;
18+
use SebastianBergmann\CodeCoverage\Test\TestSizes;
1819
use SebastianBergmann\CodeCoverage\Util\Percentage;
1920

2021
/**
@@ -28,6 +29,9 @@
2829
* @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
2930
*
3031
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for phpunit/php-code-coverage
32+
*
33+
* @phpstan-import-type TestSizeSet from TestSizes
34+
* @phpstan-import-type TestSizeCounts from TestSizes
3135
*/
3236
final class NamespaceNode
3337
{
@@ -55,6 +59,21 @@ final class NamespaceNode
5559
private int $numMethods = -1;
5660
private int $numTestedMethods = -1;
5761

62+
/**
63+
* @var ?TestSizeCounts
64+
*/
65+
private ?array $numExecutedLinesByTestSize = null;
66+
67+
/**
68+
* @var ?TestSizeCounts
69+
*/
70+
private ?array $numTestedClassesByTestSize = null;
71+
72+
/**
73+
* @var ?TestSizeCounts
74+
*/
75+
private ?array $numTestedMethodsByTestSize = null;
76+
5877
public function __construct(string $name, string $namespace, ?self $parent = null)
5978
{
6079
$this->name = $name;
@@ -174,6 +193,28 @@ public function numberOfExecutedLines(): int
174193
return $this->numExecutedLines;
175194
}
176195

196+
/**
197+
* @param TestSizeSet $testSizes
198+
*/
199+
public function numberOfExecutedLinesByTestSize(int $testSizes): int
200+
{
201+
if ($this->numExecutedLinesByTestSize === null) {
202+
$this->numExecutedLinesByTestSize = TestSizes::ZERO_COUNTS;
203+
204+
foreach (TestSizes::COMBINATIONS as $combination) {
205+
foreach ($this->classesInSubtree() as $class) {
206+
$this->numExecutedLinesByTestSize[$combination] += $class->class_()->executedLinesByTestSize[$combination];
207+
}
208+
209+
foreach ($this->traitsInSubtree() as $trait) {
210+
$this->numExecutedLinesByTestSize[$combination] += $trait->executedLinesByTestSize[$combination];
211+
}
212+
}
213+
}
214+
215+
return $this->numExecutedLinesByTestSize[$testSizes];
216+
}
217+
177218
public function numberOfExecutableBranches(): int
178219
{
179220
if ($this->numExecutableBranches === -1) {
@@ -280,6 +321,30 @@ public function numberOfTestedClasses(): int
280321
return $this->numTestedClasses;
281322
}
282323

324+
/**
325+
* @param TestSizeSet $testSizes
326+
*/
327+
public function numberOfTestedClassesByTestSize(int $testSizes): int
328+
{
329+
if ($this->numTestedClassesByTestSize === null) {
330+
$this->numTestedClassesByTestSize = TestSizes::ZERO_COUNTS;
331+
332+
foreach (TestSizes::COMBINATIONS as $combination) {
333+
foreach ($this->classes as $class) {
334+
if ($class->numberOfMethods() > 0 && $class->numberOfTestedMethodsByTestSize($combination) === $class->numberOfMethods()) {
335+
$this->numTestedClassesByTestSize[$combination]++;
336+
}
337+
}
338+
339+
foreach ($this->childNamespaces as $ns) {
340+
$this->numTestedClassesByTestSize[$combination] += $ns->numberOfTestedClassesByTestSize($combination);
341+
}
342+
}
343+
}
344+
345+
return $this->numTestedClassesByTestSize[$testSizes];
346+
}
347+
283348
public function numberOfMethods(): int
284349
{
285350
if ($this->numMethods === -1) {
@@ -310,6 +375,30 @@ public function numberOfTestedMethods(): int
310375
return $this->numTestedMethods;
311376
}
312377

378+
/**
379+
* @param TestSizeSet $testSizes
380+
*/
381+
public function numberOfTestedMethodsByTestSize(int $testSizes): int
382+
{
383+
if ($this->numTestedMethodsByTestSize === null) {
384+
$this->numTestedMethodsByTestSize = TestSizes::ZERO_COUNTS;
385+
386+
foreach ($this->methodsInSubtree() as $method) {
387+
if ($method->executableLines === 0) {
388+
continue;
389+
}
390+
391+
foreach (TestSizes::COMBINATIONS as $combination) {
392+
if ($method->executedLinesByTestSize[$combination] === $method->executableLines) {
393+
$this->numTestedMethodsByTestSize[$combination]++;
394+
}
395+
}
396+
}
397+
}
398+
399+
return $this->numTestedMethodsByTestSize[$testSizes];
400+
}
401+
313402
public function percentageOfExecutedLines(): Percentage
314403
{
315404
return Percentage::fromFractionAndTotal(
@@ -450,5 +539,9 @@ private function resetCounters(): void
450539
$this->numTestedClasses = -1;
451540
$this->numMethods = -1;
452541
$this->numTestedMethods = -1;
542+
543+
$this->numExecutedLinesByTestSize = null;
544+
$this->numTestedClassesByTestSize = null;
545+
$this->numTestedMethodsByTestSize = null;
453546
}
454547
}

src/Report/Html/ClassView/Renderer/Class_.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ private function renderItems(ClassNode $node): string
143143
'testedClassesPercent' => $node->percentageOfTestedClasses()->asFloat(),
144144
'testedClassesPercentAsString' => $node->percentageOfTestedClasses()->asString(),
145145
'crap' => '<abbr title="Change Risk Anti-Patterns (CRAP) Index">CRAP</abbr>',
146+
'coverageDataJson' => $this->coverageDataJsonForClassNode($node),
146147
],
147148
);
148149

@@ -239,6 +240,7 @@ private function renderMethodItem(Template $template, ProcessedMethodType $metho
239240
'testedMethodsPercent' => $testedMethodsPercentage->asFloat(),
240241
'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(),
241242
'crap' => $method->crap,
243+
'coverageDataJson' => $this->coverageDataJsonForFunctionOrMethod($method),
242244
],
243245
);
244246
}

src/Report/Html/ClassView/Renderer/Namespace_.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ private function renderItem(NamespaceNode $currentPage, ?NamespaceNode $child =
139139
'testedMethodsPercentAsString' => $statsNode->percentageOfTestedMethods()->asString(),
140140
'testedClassesPercent' => $statsNode->percentageOfTestedClasses()->asFloat(),
141141
'testedClassesPercentAsString' => $statsNode->percentageOfTestedClasses()->asString(),
142+
'coverageDataJson' => $this->coverageDataJsonForNamespace($statsNode),
142143
];
143144

144145
if ($child === null) {
@@ -186,6 +187,7 @@ private function renderClassItem(ClassNode $class): string
186187
'testedMethodsPercentAsString' => $class->percentageOfTestedMethods()->asString(),
187188
'testedClassesPercent' => $class->percentageOfTestedClasses()->asFloat(),
188189
'testedClassesPercentAsString' => $class->percentageOfTestedClasses()->asString(),
190+
'coverageDataJson' => $this->coverageDataJsonForClassNode($class),
189191
'icon' => sprintf(
190192
'<img src="%s_icons/file-code.svg" class="octicon" />',
191193
$this->pathToRootForNamespace($class->parent()),
@@ -205,6 +207,26 @@ private function renderClassItem(ClassNode $class): string
205207
);
206208
}
207209

210+
private function coverageDataJsonForNamespace(NamespaceNode $node): string
211+
{
212+
$data = [
213+
'linesTotal' => $node->numberOfExecutableLines(),
214+
'linesAll' => $node->numberOfExecutedLines(),
215+
'methodsTotal' => $node->numberOfMethods(),
216+
'methodsAll' => $node->numberOfTestedMethods(),
217+
'classesTotal' => $node->numberOfClasses(),
218+
'classesAll' => $node->numberOfTestedClasses(),
219+
];
220+
221+
foreach (self::TEST_SIZE_JSON_KEY_SUFFIXES as $combination => $suffix) {
222+
$data['lines' . $suffix] = $node->numberOfExecutedLinesByTestSize($combination);
223+
$data['methods' . $suffix] = $node->numberOfTestedMethodsByTestSize($combination);
224+
$data['classes' . $suffix] = $node->numberOfTestedClassesByTestSize($combination);
225+
}
226+
227+
return $this->buildCoverageDataJson($data);
228+
}
229+
208230
private function pathToRootForNamespace(NamespaceNode $node): string
209231
{
210232
$id = $node->id();

src/Report/Html/Renderer.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
use function sprintf;
2222
use function str_repeat;
2323
use function substr_count;
24+
use SebastianBergmann\CodeCoverage\Data\ProcessedFunctionType;
25+
use SebastianBergmann\CodeCoverage\Data\ProcessedMethodType;
2426
use SebastianBergmann\CodeCoverage\Node\AbstractNode;
2527
use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;
2628
use SebastianBergmann\CodeCoverage\Node\File as FileNode;
29+
use SebastianBergmann\CodeCoverage\Report\Html\ClassView\Node\ClassNode;
2730
use SebastianBergmann\CodeCoverage\Report\Thresholds;
2831
use SebastianBergmann\CodeCoverage\Test\TestSizes;
2932
use SebastianBergmann\CodeCoverage\Version;
@@ -444,6 +447,65 @@ protected function coverageDataJsonFor(AbstractNode $node): string
444447
return $this->buildCoverageDataJson($data);
445448
}
446449

450+
protected function coverageDataJsonForClassNode(ClassNode $node): string
451+
{
452+
$numMethods = $node->numberOfMethods();
453+
$numClasses = $numMethods > 0 ? 1 : 0;
454+
455+
$data = [
456+
'linesTotal' => $node->numberOfExecutableLines(),
457+
'linesAll' => $node->numberOfExecutedLines(),
458+
'methodsTotal' => $numMethods,
459+
'methodsAll' => $node->numberOfTestedMethods(),
460+
'classesTotal' => $numClasses,
461+
'classesAll' => ($numClasses === 1 && $node->numberOfTestedMethods() === $numMethods) ? 1 : 0,
462+
];
463+
464+
foreach (self::TEST_SIZE_JSON_KEY_SUFFIXES as $combination => $suffix) {
465+
$numTestedMethodsByTestSize = $node->numberOfTestedMethodsByTestSize($combination);
466+
467+
$data['lines' . $suffix] = $node->numberOfExecutedLinesByTestSize($combination);
468+
$data['methods' . $suffix] = $numTestedMethodsByTestSize;
469+
$data['classes' . $suffix] = ($numClasses === 1 && $numTestedMethodsByTestSize === $numMethods) ? 1 : 0;
470+
}
471+
472+
return $this->buildCoverageDataJson($data);
473+
}
474+
475+
protected function coverageDataJsonForFunctionOrMethod(ProcessedFunctionType|ProcessedMethodType $item): string
476+
{
477+
$numMethods = 0;
478+
$numTestedMethods = 0;
479+
480+
if ($item->executableLines > 0) {
481+
$numMethods = 1;
482+
483+
if ($item->executedLines === $item->executableLines) {
484+
$numTestedMethods = 1;
485+
}
486+
}
487+
488+
$data = [
489+
'linesTotal' => $item->executableLines,
490+
'linesAll' => $item->executedLines,
491+
'methodsTotal' => $numMethods,
492+
'methodsAll' => $numTestedMethods,
493+
];
494+
495+
foreach (self::TEST_SIZE_JSON_KEY_SUFFIXES as $combination => $suffix) {
496+
$numTestedMethodsByTestSize = 0;
497+
498+
if ($numMethods === 1 && $item->executedLinesByTestSize[$combination] === $item->executableLines) {
499+
$numTestedMethodsByTestSize = 1;
500+
}
501+
502+
$data['lines' . $suffix] = $item->executedLinesByTestSize[$combination];
503+
$data['methods' . $suffix] = $numTestedMethodsByTestSize;
504+
}
505+
506+
return $this->buildCoverageDataJson($data);
507+
}
508+
447509
protected function renderLine(Template $template, int $lineNumber, string $lineContent, string $class, string $popover, string $anchorPrefix = '', string $coverageCount = '', string $coverageCountClass = 'col-0'): string
448510
{
449511
$template->setVar(

0 commit comments

Comments
 (0)