Chris@14
|
1 <?php
|
Chris@14
|
2 /*
|
Chris@14
|
3 * This file is part of the php-code-coverage package.
|
Chris@14
|
4 *
|
Chris@14
|
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
|
Chris@14
|
6 *
|
Chris@14
|
7 * For the full copyright and license information, please view the LICENSE
|
Chris@14
|
8 * file that was distributed with this source code.
|
Chris@14
|
9 */
|
Chris@14
|
10
|
Chris@14
|
11 namespace SebastianBergmann\CodeCoverage\Report\Html;
|
Chris@14
|
12
|
Chris@14
|
13 use SebastianBergmann\CodeCoverage\Node\AbstractNode;
|
Chris@14
|
14 use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;
|
Chris@14
|
15
|
Chris@14
|
16 /**
|
Chris@14
|
17 * Renders the dashboard for a directory node.
|
Chris@14
|
18 */
|
Chris@14
|
19 class Dashboard extends Renderer
|
Chris@14
|
20 {
|
Chris@14
|
21 /**
|
Chris@14
|
22 * @param DirectoryNode $node
|
Chris@14
|
23 * @param string $file
|
Chris@14
|
24 *
|
Chris@14
|
25 * @throws \InvalidArgumentException
|
Chris@14
|
26 */
|
Chris@14
|
27 public function render(DirectoryNode $node, $file)
|
Chris@14
|
28 {
|
Chris@14
|
29 $classes = $node->getClassesAndTraits();
|
Chris@14
|
30 $template = new \Text_Template(
|
Chris@14
|
31 $this->templatePath . 'dashboard.html',
|
Chris@14
|
32 '{{',
|
Chris@14
|
33 '}}'
|
Chris@14
|
34 );
|
Chris@14
|
35
|
Chris@14
|
36 $this->setCommonTemplateVariables($template, $node);
|
Chris@14
|
37
|
Chris@14
|
38 $baseLink = $node->getId() . '/';
|
Chris@14
|
39 $complexity = $this->complexity($classes, $baseLink);
|
Chris@14
|
40 $coverageDistribution = $this->coverageDistribution($classes);
|
Chris@14
|
41 $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink);
|
Chris@14
|
42 $projectRisks = $this->projectRisks($classes, $baseLink);
|
Chris@14
|
43
|
Chris@14
|
44 $template->setVar(
|
Chris@14
|
45 [
|
Chris@14
|
46 'insufficient_coverage_classes' => $insufficientCoverage['class'],
|
Chris@14
|
47 'insufficient_coverage_methods' => $insufficientCoverage['method'],
|
Chris@14
|
48 'project_risks_classes' => $projectRisks['class'],
|
Chris@14
|
49 'project_risks_methods' => $projectRisks['method'],
|
Chris@14
|
50 'complexity_class' => $complexity['class'],
|
Chris@14
|
51 'complexity_method' => $complexity['method'],
|
Chris@14
|
52 'class_coverage_distribution' => $coverageDistribution['class'],
|
Chris@14
|
53 'method_coverage_distribution' => $coverageDistribution['method']
|
Chris@14
|
54 ]
|
Chris@14
|
55 );
|
Chris@14
|
56
|
Chris@14
|
57 $template->renderTo($file);
|
Chris@14
|
58 }
|
Chris@14
|
59
|
Chris@14
|
60 /**
|
Chris@14
|
61 * Returns the data for the Class/Method Complexity charts.
|
Chris@14
|
62 *
|
Chris@14
|
63 * @param array $classes
|
Chris@14
|
64 * @param string $baseLink
|
Chris@14
|
65 *
|
Chris@14
|
66 * @return array
|
Chris@14
|
67 */
|
Chris@14
|
68 protected function complexity(array $classes, $baseLink)
|
Chris@14
|
69 {
|
Chris@14
|
70 $result = ['class' => [], 'method' => []];
|
Chris@14
|
71
|
Chris@14
|
72 foreach ($classes as $className => $class) {
|
Chris@14
|
73 foreach ($class['methods'] as $methodName => $method) {
|
Chris@14
|
74 if ($className !== '*') {
|
Chris@14
|
75 $methodName = $className . '::' . $methodName;
|
Chris@14
|
76 }
|
Chris@14
|
77
|
Chris@14
|
78 $result['method'][] = [
|
Chris@14
|
79 $method['coverage'],
|
Chris@14
|
80 $method['ccn'],
|
Chris@14
|
81 \sprintf(
|
Chris@14
|
82 '<a href="%s">%s</a>',
|
Chris@14
|
83 \str_replace($baseLink, '', $method['link']),
|
Chris@14
|
84 $methodName
|
Chris@14
|
85 )
|
Chris@14
|
86 ];
|
Chris@14
|
87 }
|
Chris@14
|
88
|
Chris@14
|
89 $result['class'][] = [
|
Chris@14
|
90 $class['coverage'],
|
Chris@14
|
91 $class['ccn'],
|
Chris@14
|
92 \sprintf(
|
Chris@14
|
93 '<a href="%s">%s</a>',
|
Chris@14
|
94 \str_replace($baseLink, '', $class['link']),
|
Chris@14
|
95 $className
|
Chris@14
|
96 )
|
Chris@14
|
97 ];
|
Chris@14
|
98 }
|
Chris@14
|
99
|
Chris@14
|
100 return [
|
Chris@14
|
101 'class' => \json_encode($result['class']),
|
Chris@14
|
102 'method' => \json_encode($result['method'])
|
Chris@14
|
103 ];
|
Chris@14
|
104 }
|
Chris@14
|
105
|
Chris@14
|
106 /**
|
Chris@14
|
107 * Returns the data for the Class / Method Coverage Distribution chart.
|
Chris@14
|
108 *
|
Chris@14
|
109 * @param array $classes
|
Chris@14
|
110 *
|
Chris@14
|
111 * @return array
|
Chris@14
|
112 */
|
Chris@14
|
113 protected function coverageDistribution(array $classes)
|
Chris@14
|
114 {
|
Chris@14
|
115 $result = [
|
Chris@14
|
116 'class' => [
|
Chris@14
|
117 '0%' => 0,
|
Chris@14
|
118 '0-10%' => 0,
|
Chris@14
|
119 '10-20%' => 0,
|
Chris@14
|
120 '20-30%' => 0,
|
Chris@14
|
121 '30-40%' => 0,
|
Chris@14
|
122 '40-50%' => 0,
|
Chris@14
|
123 '50-60%' => 0,
|
Chris@14
|
124 '60-70%' => 0,
|
Chris@14
|
125 '70-80%' => 0,
|
Chris@14
|
126 '80-90%' => 0,
|
Chris@14
|
127 '90-100%' => 0,
|
Chris@14
|
128 '100%' => 0
|
Chris@14
|
129 ],
|
Chris@14
|
130 'method' => [
|
Chris@14
|
131 '0%' => 0,
|
Chris@14
|
132 '0-10%' => 0,
|
Chris@14
|
133 '10-20%' => 0,
|
Chris@14
|
134 '20-30%' => 0,
|
Chris@14
|
135 '30-40%' => 0,
|
Chris@14
|
136 '40-50%' => 0,
|
Chris@14
|
137 '50-60%' => 0,
|
Chris@14
|
138 '60-70%' => 0,
|
Chris@14
|
139 '70-80%' => 0,
|
Chris@14
|
140 '80-90%' => 0,
|
Chris@14
|
141 '90-100%' => 0,
|
Chris@14
|
142 '100%' => 0
|
Chris@14
|
143 ]
|
Chris@14
|
144 ];
|
Chris@14
|
145
|
Chris@14
|
146 foreach ($classes as $class) {
|
Chris@14
|
147 foreach ($class['methods'] as $methodName => $method) {
|
Chris@14
|
148 if ($method['coverage'] === 0) {
|
Chris@14
|
149 $result['method']['0%']++;
|
Chris@14
|
150 } elseif ($method['coverage'] === 100) {
|
Chris@14
|
151 $result['method']['100%']++;
|
Chris@14
|
152 } else {
|
Chris@14
|
153 $key = \floor($method['coverage'] / 10) * 10;
|
Chris@14
|
154 $key = $key . '-' . ($key + 10) . '%';
|
Chris@14
|
155 $result['method'][$key]++;
|
Chris@14
|
156 }
|
Chris@14
|
157 }
|
Chris@14
|
158
|
Chris@14
|
159 if ($class['coverage'] === 0) {
|
Chris@14
|
160 $result['class']['0%']++;
|
Chris@14
|
161 } elseif ($class['coverage'] === 100) {
|
Chris@14
|
162 $result['class']['100%']++;
|
Chris@14
|
163 } else {
|
Chris@14
|
164 $key = \floor($class['coverage'] / 10) * 10;
|
Chris@14
|
165 $key = $key . '-' . ($key + 10) . '%';
|
Chris@14
|
166 $result['class'][$key]++;
|
Chris@14
|
167 }
|
Chris@14
|
168 }
|
Chris@14
|
169
|
Chris@14
|
170 return [
|
Chris@14
|
171 'class' => \json_encode(\array_values($result['class'])),
|
Chris@14
|
172 'method' => \json_encode(\array_values($result['method']))
|
Chris@14
|
173 ];
|
Chris@14
|
174 }
|
Chris@14
|
175
|
Chris@14
|
176 /**
|
Chris@14
|
177 * Returns the classes / methods with insufficient coverage.
|
Chris@14
|
178 *
|
Chris@14
|
179 * @param array $classes
|
Chris@14
|
180 * @param string $baseLink
|
Chris@14
|
181 *
|
Chris@14
|
182 * @return array
|
Chris@14
|
183 */
|
Chris@14
|
184 protected function insufficientCoverage(array $classes, $baseLink)
|
Chris@14
|
185 {
|
Chris@14
|
186 $leastTestedClasses = [];
|
Chris@14
|
187 $leastTestedMethods = [];
|
Chris@14
|
188 $result = ['class' => '', 'method' => ''];
|
Chris@14
|
189
|
Chris@14
|
190 foreach ($classes as $className => $class) {
|
Chris@14
|
191 foreach ($class['methods'] as $methodName => $method) {
|
Chris@14
|
192 if ($method['coverage'] < $this->highLowerBound) {
|
Chris@14
|
193 $key = $methodName;
|
Chris@14
|
194
|
Chris@14
|
195 if ($className !== '*') {
|
Chris@14
|
196 $key = $className . '::' . $methodName;
|
Chris@14
|
197 }
|
Chris@14
|
198
|
Chris@14
|
199 $leastTestedMethods[$key] = $method['coverage'];
|
Chris@14
|
200 }
|
Chris@14
|
201 }
|
Chris@14
|
202
|
Chris@14
|
203 if ($class['coverage'] < $this->highLowerBound) {
|
Chris@14
|
204 $leastTestedClasses[$className] = $class['coverage'];
|
Chris@14
|
205 }
|
Chris@14
|
206 }
|
Chris@14
|
207
|
Chris@14
|
208 \asort($leastTestedClasses);
|
Chris@14
|
209 \asort($leastTestedMethods);
|
Chris@14
|
210
|
Chris@14
|
211 foreach ($leastTestedClasses as $className => $coverage) {
|
Chris@14
|
212 $result['class'] .= \sprintf(
|
Chris@14
|
213 ' <tr><td><a href="%s">%s</a></td><td class="text-right">%d%%</td></tr>' . "\n",
|
Chris@14
|
214 \str_replace($baseLink, '', $classes[$className]['link']),
|
Chris@14
|
215 $className,
|
Chris@14
|
216 $coverage
|
Chris@14
|
217 );
|
Chris@14
|
218 }
|
Chris@14
|
219
|
Chris@14
|
220 foreach ($leastTestedMethods as $methodName => $coverage) {
|
Chris@14
|
221 list($class, $method) = \explode('::', $methodName);
|
Chris@14
|
222
|
Chris@14
|
223 $result['method'] .= \sprintf(
|
Chris@14
|
224 ' <tr><td><a href="%s"><abbr title="%s">%s</abbr></a></td><td class="text-right">%d%%</td></tr>' . "\n",
|
Chris@14
|
225 \str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']),
|
Chris@14
|
226 $methodName,
|
Chris@14
|
227 $method,
|
Chris@14
|
228 $coverage
|
Chris@14
|
229 );
|
Chris@14
|
230 }
|
Chris@14
|
231
|
Chris@14
|
232 return $result;
|
Chris@14
|
233 }
|
Chris@14
|
234
|
Chris@14
|
235 /**
|
Chris@14
|
236 * Returns the project risks according to the CRAP index.
|
Chris@14
|
237 *
|
Chris@14
|
238 * @param array $classes
|
Chris@14
|
239 * @param string $baseLink
|
Chris@14
|
240 *
|
Chris@14
|
241 * @return array
|
Chris@14
|
242 */
|
Chris@14
|
243 protected function projectRisks(array $classes, $baseLink)
|
Chris@14
|
244 {
|
Chris@14
|
245 $classRisks = [];
|
Chris@14
|
246 $methodRisks = [];
|
Chris@14
|
247 $result = ['class' => '', 'method' => ''];
|
Chris@14
|
248
|
Chris@14
|
249 foreach ($classes as $className => $class) {
|
Chris@14
|
250 foreach ($class['methods'] as $methodName => $method) {
|
Chris@14
|
251 if ($method['coverage'] < $this->highLowerBound &&
|
Chris@14
|
252 $method['ccn'] > 1) {
|
Chris@14
|
253 if ($className !== '*') {
|
Chris@14
|
254 $key = $className . '::' . $methodName;
|
Chris@14
|
255 } else {
|
Chris@14
|
256 $key = $methodName;
|
Chris@14
|
257 }
|
Chris@14
|
258
|
Chris@14
|
259 $methodRisks[$key] = $method['crap'];
|
Chris@14
|
260 }
|
Chris@14
|
261 }
|
Chris@14
|
262
|
Chris@14
|
263 if ($class['coverage'] < $this->highLowerBound &&
|
Chris@14
|
264 $class['ccn'] > \count($class['methods'])) {
|
Chris@14
|
265 $classRisks[$className] = $class['crap'];
|
Chris@14
|
266 }
|
Chris@14
|
267 }
|
Chris@14
|
268
|
Chris@14
|
269 \arsort($classRisks);
|
Chris@14
|
270 \arsort($methodRisks);
|
Chris@14
|
271
|
Chris@14
|
272 foreach ($classRisks as $className => $crap) {
|
Chris@14
|
273 $result['class'] .= \sprintf(
|
Chris@14
|
274 ' <tr><td><a href="%s">%s</a></td><td class="text-right">%d</td></tr>' . "\n",
|
Chris@14
|
275 \str_replace($baseLink, '', $classes[$className]['link']),
|
Chris@14
|
276 $className,
|
Chris@14
|
277 $crap
|
Chris@14
|
278 );
|
Chris@14
|
279 }
|
Chris@14
|
280
|
Chris@14
|
281 foreach ($methodRisks as $methodName => $crap) {
|
Chris@14
|
282 list($class, $method) = \explode('::', $methodName);
|
Chris@14
|
283
|
Chris@14
|
284 $result['method'] .= \sprintf(
|
Chris@14
|
285 ' <tr><td><a href="%s"><abbr title="%s">%s</abbr></a></td><td class="text-right">%d</td></tr>' . "\n",
|
Chris@14
|
286 \str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']),
|
Chris@14
|
287 $methodName,
|
Chris@14
|
288 $method,
|
Chris@14
|
289 $crap
|
Chris@14
|
290 );
|
Chris@14
|
291 }
|
Chris@14
|
292
|
Chris@14
|
293 return $result;
|
Chris@14
|
294 }
|
Chris@14
|
295
|
Chris@14
|
296 protected function getActiveBreadcrumb(AbstractNode $node)
|
Chris@14
|
297 {
|
Chris@14
|
298 return \sprintf(
|
Chris@14
|
299 ' <li><a href="index.html">%s</a></li>' . "\n" .
|
Chris@14
|
300 ' <li class="active">(Dashboard)</li>' . "\n",
|
Chris@14
|
301 $node->getName()
|
Chris@14
|
302 );
|
Chris@14
|
303 }
|
Chris@14
|
304 }
|