-
-
Notifications
You must be signed in to change notification settings - Fork 46
/
CoverageListener.php
119 lines (92 loc) · 3.59 KB
/
CoverageListener.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\PhpUnit;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestListener;
use PHPUnit\Framework\TestListenerDefaultImplementation;
use PHPUnit\Framework\Warning;
use PHPUnit\Util\Annotation\Registry;
use PHPUnit\Util\Test as TestUtil;
class CoverageListener implements TestListener
{
use TestListenerDefaultImplementation;
private $sutFqcnResolver;
private $warningOnSutNotFound;
public function __construct(?callable $sutFqcnResolver = null, bool $warningOnSutNotFound = false)
{
$this->sutFqcnResolver = $sutFqcnResolver ?? static function (Test $test): ?string {
$class = \get_class($test);
$sutFqcn = str_replace('\\Tests\\', '\\', $class);
$sutFqcn = preg_replace('{Test$}', '', $sutFqcn);
return class_exists($sutFqcn) ? $sutFqcn : null;
};
$this->warningOnSutNotFound = $warningOnSutNotFound;
}
public function startTest(Test $test): void
{
if (!$test instanceof TestCase) {
return;
}
$annotations = TestUtil::parseTestMethodAnnotations(\get_class($test), $test->getName(false));
$ignoredAnnotations = ['covers', 'coversDefaultClass', 'coversNothing'];
foreach ($ignoredAnnotations as $annotation) {
if (isset($annotations['class'][$annotation]) || isset($annotations['method'][$annotation])) {
return;
}
}
$sutFqcn = ($this->sutFqcnResolver)($test);
if (!$sutFqcn) {
if ($this->warningOnSutNotFound) {
$test->getTestResultObject()->addWarning($test, new Warning('Could not find the tested class.'), 0);
}
return;
}
$covers = $sutFqcn;
if (!\is_array($sutFqcn)) {
$covers = [$sutFqcn];
while ($parent = get_parent_class($sutFqcn)) {
$covers[] = $parent;
$sutFqcn = $parent;
}
}
if (class_exists(Registry::class)) {
$this->addCoversForDocBlockInsideRegistry($test, $covers);
return;
}
$this->addCoversForClassToAnnotationCache($test, $covers);
}
private function addCoversForClassToAnnotationCache(Test $test, array $covers): void
{
$r = new \ReflectionProperty(TestUtil::class, 'annotationCache');
$r->setAccessible(true);
$cache = $r->getValue();
$cache = array_replace_recursive($cache, [
\get_class($test) => [
'covers' => $covers,
],
]);
$r->setValue(TestUtil::class, $cache);
}
private function addCoversForDocBlockInsideRegistry(Test $test, array $covers): void
{
$docBlock = Registry::getInstance()->forClassName(\get_class($test));
$symbolAnnotations = new \ReflectionProperty($docBlock, 'symbolAnnotations');
$symbolAnnotations->setAccessible(true);
// Exclude internal classes; PHPUnit 9.1+ is picky about tests covering, say, a \RuntimeException
$covers = array_filter($covers, function (string $class) {
$reflector = new \ReflectionClass($class);
return $reflector->isUserDefined();
});
$symbolAnnotations->setValue($docBlock, array_replace($docBlock->symbolAnnotations(), [
'covers' => $covers,
]));
}
}