Chris@0
|
1 <?php
|
Chris@0
|
2 /*
|
Chris@14
|
3 * This file is part of the php-code-coverage package.
|
Chris@0
|
4 *
|
Chris@0
|
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
|
Chris@0
|
6 *
|
Chris@0
|
7 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
8 * file that was distributed with this source code.
|
Chris@0
|
9 */
|
Chris@0
|
10
|
Chris@14
|
11 namespace SebastianBergmann\CodeCoverage;
|
Chris@14
|
12
|
Chris@14
|
13 use PHPUnit\Framework\TestCase;
|
Chris@14
|
14 use PHPUnit\Runner\PhptTestCase;
|
Chris@14
|
15 use SebastianBergmann\CodeCoverage\Driver\Driver;
|
Chris@14
|
16 use SebastianBergmann\CodeCoverage\Driver\HHVM;
|
Chris@14
|
17 use SebastianBergmann\CodeCoverage\Driver\PHPDBG;
|
Chris@14
|
18 use SebastianBergmann\CodeCoverage\Driver\Xdebug;
|
Chris@14
|
19 use SebastianBergmann\CodeCoverage\Node\Builder;
|
Chris@14
|
20 use SebastianBergmann\CodeCoverage\Node\Directory;
|
Chris@14
|
21 use SebastianBergmann\CodeUnitReverseLookup\Wizard;
|
Chris@0
|
22 use SebastianBergmann\Environment\Runtime;
|
Chris@0
|
23
|
Chris@0
|
24 /**
|
Chris@0
|
25 * Provides collection functionality for PHP code coverage information.
|
Chris@0
|
26 */
|
Chris@14
|
27 class CodeCoverage
|
Chris@0
|
28 {
|
Chris@0
|
29 /**
|
Chris@14
|
30 * @var Driver
|
Chris@0
|
31 */
|
Chris@0
|
32 private $driver;
|
Chris@0
|
33
|
Chris@0
|
34 /**
|
Chris@14
|
35 * @var Filter
|
Chris@0
|
36 */
|
Chris@0
|
37 private $filter;
|
Chris@0
|
38
|
Chris@0
|
39 /**
|
Chris@14
|
40 * @var Wizard
|
Chris@14
|
41 */
|
Chris@14
|
42 private $wizard;
|
Chris@14
|
43
|
Chris@14
|
44 /**
|
Chris@0
|
45 * @var bool
|
Chris@0
|
46 */
|
Chris@0
|
47 private $cacheTokens = false;
|
Chris@0
|
48
|
Chris@0
|
49 /**
|
Chris@0
|
50 * @var bool
|
Chris@0
|
51 */
|
Chris@0
|
52 private $checkForUnintentionallyCoveredCode = false;
|
Chris@0
|
53
|
Chris@0
|
54 /**
|
Chris@0
|
55 * @var bool
|
Chris@0
|
56 */
|
Chris@0
|
57 private $forceCoversAnnotation = false;
|
Chris@0
|
58
|
Chris@0
|
59 /**
|
Chris@0
|
60 * @var bool
|
Chris@0
|
61 */
|
Chris@14
|
62 private $checkForUnexecutedCoveredCode = false;
|
Chris@14
|
63
|
Chris@14
|
64 /**
|
Chris@14
|
65 * @var bool
|
Chris@14
|
66 */
|
Chris@14
|
67 private $checkForMissingCoversAnnotation = false;
|
Chris@0
|
68
|
Chris@0
|
69 /**
|
Chris@0
|
70 * @var bool
|
Chris@0
|
71 */
|
Chris@0
|
72 private $addUncoveredFilesFromWhitelist = true;
|
Chris@0
|
73
|
Chris@0
|
74 /**
|
Chris@0
|
75 * @var bool
|
Chris@0
|
76 */
|
Chris@0
|
77 private $processUncoveredFilesFromWhitelist = false;
|
Chris@0
|
78
|
Chris@0
|
79 /**
|
Chris@14
|
80 * @var bool
|
Chris@14
|
81 */
|
Chris@14
|
82 private $ignoreDeprecatedCode = false;
|
Chris@14
|
83
|
Chris@14
|
84 /**
|
Chris@0
|
85 * @var mixed
|
Chris@0
|
86 */
|
Chris@0
|
87 private $currentId;
|
Chris@0
|
88
|
Chris@0
|
89 /**
|
Chris@0
|
90 * Code coverage data.
|
Chris@0
|
91 *
|
Chris@0
|
92 * @var array
|
Chris@0
|
93 */
|
Chris@14
|
94 private $data = [];
|
Chris@0
|
95
|
Chris@0
|
96 /**
|
Chris@0
|
97 * @var array
|
Chris@0
|
98 */
|
Chris@14
|
99 private $ignoredLines = [];
|
Chris@0
|
100
|
Chris@0
|
101 /**
|
Chris@0
|
102 * @var bool
|
Chris@0
|
103 */
|
Chris@0
|
104 private $disableIgnoredLines = false;
|
Chris@0
|
105
|
Chris@0
|
106 /**
|
Chris@0
|
107 * Test data.
|
Chris@0
|
108 *
|
Chris@0
|
109 * @var array
|
Chris@0
|
110 */
|
Chris@14
|
111 private $tests = [];
|
Chris@14
|
112
|
Chris@14
|
113 /**
|
Chris@14
|
114 * @var string[]
|
Chris@14
|
115 */
|
Chris@14
|
116 private $unintentionallyCoveredSubclassesWhitelist = [];
|
Chris@14
|
117
|
Chris@14
|
118 /**
|
Chris@14
|
119 * Determine if the data has been initialized or not
|
Chris@14
|
120 *
|
Chris@14
|
121 * @var bool
|
Chris@14
|
122 */
|
Chris@14
|
123 private $isInitialized = false;
|
Chris@14
|
124
|
Chris@14
|
125 /**
|
Chris@14
|
126 * Determine whether we need to check for dead and unused code on each test
|
Chris@14
|
127 *
|
Chris@14
|
128 * @var bool
|
Chris@14
|
129 */
|
Chris@14
|
130 private $shouldCheckForDeadAndUnused = true;
|
Chris@14
|
131
|
Chris@14
|
132 /**
|
Chris@14
|
133 * @var Directory
|
Chris@14
|
134 */
|
Chris@14
|
135 private $report;
|
Chris@0
|
136
|
Chris@0
|
137 /**
|
Chris@0
|
138 * Constructor.
|
Chris@0
|
139 *
|
Chris@14
|
140 * @param Driver $driver
|
Chris@14
|
141 * @param Filter $filter
|
Chris@14
|
142 *
|
Chris@14
|
143 * @throws RuntimeException
|
Chris@0
|
144 */
|
Chris@14
|
145 public function __construct(Driver $driver = null, Filter $filter = null)
|
Chris@0
|
146 {
|
Chris@0
|
147 if ($driver === null) {
|
Chris@0
|
148 $driver = $this->selectDriver();
|
Chris@0
|
149 }
|
Chris@0
|
150
|
Chris@0
|
151 if ($filter === null) {
|
Chris@14
|
152 $filter = new Filter;
|
Chris@0
|
153 }
|
Chris@0
|
154
|
Chris@0
|
155 $this->driver = $driver;
|
Chris@0
|
156 $this->filter = $filter;
|
Chris@14
|
157
|
Chris@14
|
158 $this->wizard = new Wizard;
|
Chris@0
|
159 }
|
Chris@0
|
160
|
Chris@0
|
161 /**
|
Chris@14
|
162 * Returns the code coverage information as a graph of node objects.
|
Chris@0
|
163 *
|
Chris@14
|
164 * @return Directory
|
Chris@0
|
165 */
|
Chris@0
|
166 public function getReport()
|
Chris@0
|
167 {
|
Chris@14
|
168 if ($this->report === null) {
|
Chris@14
|
169 $builder = new Builder;
|
Chris@0
|
170
|
Chris@14
|
171 $this->report = $builder->build($this);
|
Chris@14
|
172 }
|
Chris@14
|
173
|
Chris@14
|
174 return $this->report;
|
Chris@0
|
175 }
|
Chris@0
|
176
|
Chris@0
|
177 /**
|
Chris@0
|
178 * Clears collected code coverage data.
|
Chris@0
|
179 */
|
Chris@0
|
180 public function clear()
|
Chris@0
|
181 {
|
Chris@14
|
182 $this->isInitialized = false;
|
Chris@14
|
183 $this->currentId = null;
|
Chris@14
|
184 $this->data = [];
|
Chris@14
|
185 $this->tests = [];
|
Chris@14
|
186 $this->report = null;
|
Chris@0
|
187 }
|
Chris@0
|
188
|
Chris@0
|
189 /**
|
Chris@14
|
190 * Returns the filter object used.
|
Chris@0
|
191 *
|
Chris@14
|
192 * @return Filter
|
Chris@0
|
193 */
|
Chris@0
|
194 public function filter()
|
Chris@0
|
195 {
|
Chris@0
|
196 return $this->filter;
|
Chris@0
|
197 }
|
Chris@0
|
198
|
Chris@0
|
199 /**
|
Chris@0
|
200 * Returns the collected code coverage data.
|
Chris@0
|
201 * Set $raw = true to bypass all filters.
|
Chris@0
|
202 *
|
Chris@14
|
203 * @param bool $raw
|
Chris@14
|
204 *
|
Chris@0
|
205 * @return array
|
Chris@0
|
206 */
|
Chris@0
|
207 public function getData($raw = false)
|
Chris@0
|
208 {
|
Chris@0
|
209 if (!$raw && $this->addUncoveredFilesFromWhitelist) {
|
Chris@0
|
210 $this->addUncoveredFilesFromWhitelist();
|
Chris@0
|
211 }
|
Chris@0
|
212
|
Chris@0
|
213 return $this->data;
|
Chris@0
|
214 }
|
Chris@0
|
215
|
Chris@0
|
216 /**
|
Chris@0
|
217 * Sets the coverage data.
|
Chris@0
|
218 *
|
Chris@0
|
219 * @param array $data
|
Chris@0
|
220 */
|
Chris@0
|
221 public function setData(array $data)
|
Chris@0
|
222 {
|
Chris@14
|
223 $this->data = $data;
|
Chris@14
|
224 $this->report = null;
|
Chris@0
|
225 }
|
Chris@0
|
226
|
Chris@0
|
227 /**
|
Chris@0
|
228 * Returns the test data.
|
Chris@0
|
229 *
|
Chris@0
|
230 * @return array
|
Chris@0
|
231 */
|
Chris@0
|
232 public function getTests()
|
Chris@0
|
233 {
|
Chris@0
|
234 return $this->tests;
|
Chris@0
|
235 }
|
Chris@0
|
236
|
Chris@0
|
237 /**
|
Chris@0
|
238 * Sets the test data.
|
Chris@0
|
239 *
|
Chris@0
|
240 * @param array $tests
|
Chris@0
|
241 */
|
Chris@0
|
242 public function setTests(array $tests)
|
Chris@0
|
243 {
|
Chris@0
|
244 $this->tests = $tests;
|
Chris@0
|
245 }
|
Chris@0
|
246
|
Chris@0
|
247 /**
|
Chris@0
|
248 * Start collection of code coverage information.
|
Chris@0
|
249 *
|
Chris@14
|
250 * @param mixed $id
|
Chris@14
|
251 * @param bool $clear
|
Chris@14
|
252 *
|
Chris@14
|
253 * @throws InvalidArgumentException
|
Chris@0
|
254 */
|
Chris@0
|
255 public function start($id, $clear = false)
|
Chris@0
|
256 {
|
Chris@14
|
257 if (!\is_bool($clear)) {
|
Chris@14
|
258 throw InvalidArgumentException::create(
|
Chris@0
|
259 1,
|
Chris@0
|
260 'boolean'
|
Chris@0
|
261 );
|
Chris@0
|
262 }
|
Chris@0
|
263
|
Chris@0
|
264 if ($clear) {
|
Chris@0
|
265 $this->clear();
|
Chris@0
|
266 }
|
Chris@0
|
267
|
Chris@14
|
268 if ($this->isInitialized === false) {
|
Chris@14
|
269 $this->initializeData();
|
Chris@14
|
270 }
|
Chris@14
|
271
|
Chris@0
|
272 $this->currentId = $id;
|
Chris@0
|
273
|
Chris@14
|
274 $this->driver->start($this->shouldCheckForDeadAndUnused);
|
Chris@0
|
275 }
|
Chris@0
|
276
|
Chris@0
|
277 /**
|
Chris@0
|
278 * Stop collection of code coverage information.
|
Chris@0
|
279 *
|
Chris@14
|
280 * @param bool $append
|
Chris@14
|
281 * @param mixed $linesToBeCovered
|
Chris@14
|
282 * @param array $linesToBeUsed
|
Chris@14
|
283 * @param bool $ignoreForceCoversAnnotation
|
Chris@14
|
284 *
|
Chris@0
|
285 * @return array
|
Chris@14
|
286 *
|
Chris@14
|
287 * @throws \SebastianBergmann\CodeCoverage\RuntimeException
|
Chris@14
|
288 * @throws InvalidArgumentException
|
Chris@0
|
289 */
|
Chris@14
|
290 public function stop($append = true, $linesToBeCovered = [], array $linesToBeUsed = [], $ignoreForceCoversAnnotation = false)
|
Chris@0
|
291 {
|
Chris@14
|
292 if (!\is_bool($append)) {
|
Chris@14
|
293 throw InvalidArgumentException::create(
|
Chris@0
|
294 1,
|
Chris@0
|
295 'boolean'
|
Chris@0
|
296 );
|
Chris@0
|
297 }
|
Chris@0
|
298
|
Chris@14
|
299 if (!\is_array($linesToBeCovered) && $linesToBeCovered !== false) {
|
Chris@14
|
300 throw InvalidArgumentException::create(
|
Chris@0
|
301 2,
|
Chris@0
|
302 'array or false'
|
Chris@0
|
303 );
|
Chris@0
|
304 }
|
Chris@0
|
305
|
Chris@0
|
306 $data = $this->driver->stop();
|
Chris@14
|
307 $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed, $ignoreForceCoversAnnotation);
|
Chris@0
|
308
|
Chris@0
|
309 $this->currentId = null;
|
Chris@0
|
310
|
Chris@0
|
311 return $data;
|
Chris@0
|
312 }
|
Chris@0
|
313
|
Chris@0
|
314 /**
|
Chris@0
|
315 * Appends code coverage data.
|
Chris@0
|
316 *
|
Chris@14
|
317 * @param array $data
|
Chris@14
|
318 * @param mixed $id
|
Chris@14
|
319 * @param bool $append
|
Chris@14
|
320 * @param mixed $linesToBeCovered
|
Chris@14
|
321 * @param array $linesToBeUsed
|
Chris@14
|
322 * @param bool $ignoreForceCoversAnnotation
|
Chris@14
|
323 *
|
Chris@14
|
324 * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException
|
Chris@14
|
325 * @throws \SebastianBergmann\CodeCoverage\MissingCoversAnnotationException
|
Chris@14
|
326 * @throws \SebastianBergmann\CodeCoverage\CoveredCodeNotExecutedException
|
Chris@14
|
327 * @throws \ReflectionException
|
Chris@14
|
328 * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
|
Chris@14
|
329 * @throws RuntimeException
|
Chris@0
|
330 */
|
Chris@14
|
331 public function append(array $data, $id = null, $append = true, $linesToBeCovered = [], array $linesToBeUsed = [], $ignoreForceCoversAnnotation = false)
|
Chris@0
|
332 {
|
Chris@0
|
333 if ($id === null) {
|
Chris@0
|
334 $id = $this->currentId;
|
Chris@0
|
335 }
|
Chris@0
|
336
|
Chris@0
|
337 if ($id === null) {
|
Chris@14
|
338 throw new RuntimeException;
|
Chris@0
|
339 }
|
Chris@0
|
340
|
Chris@0
|
341 $this->applyListsFilter($data);
|
Chris@0
|
342 $this->applyIgnoredLinesFilter($data);
|
Chris@0
|
343 $this->initializeFilesThatAreSeenTheFirstTime($data);
|
Chris@0
|
344
|
Chris@0
|
345 if (!$append) {
|
Chris@0
|
346 return;
|
Chris@0
|
347 }
|
Chris@0
|
348
|
Chris@14
|
349 if ($id !== 'UNCOVERED_FILES_FROM_WHITELIST') {
|
Chris@0
|
350 $this->applyCoversAnnotationFilter(
|
Chris@0
|
351 $data,
|
Chris@0
|
352 $linesToBeCovered,
|
Chris@14
|
353 $linesToBeUsed,
|
Chris@14
|
354 $ignoreForceCoversAnnotation
|
Chris@0
|
355 );
|
Chris@0
|
356 }
|
Chris@0
|
357
|
Chris@0
|
358 if (empty($data)) {
|
Chris@0
|
359 return;
|
Chris@0
|
360 }
|
Chris@0
|
361
|
Chris@0
|
362 $size = 'unknown';
|
Chris@0
|
363 $status = null;
|
Chris@0
|
364
|
Chris@14
|
365 if ($id instanceof TestCase) {
|
Chris@0
|
366 $_size = $id->getSize();
|
Chris@0
|
367
|
Chris@14
|
368 if ($_size === \PHPUnit\Util\Test::SMALL) {
|
Chris@0
|
369 $size = 'small';
|
Chris@14
|
370 } elseif ($_size === \PHPUnit\Util\Test::MEDIUM) {
|
Chris@0
|
371 $size = 'medium';
|
Chris@14
|
372 } elseif ($_size === \PHPUnit\Util\Test::LARGE) {
|
Chris@0
|
373 $size = 'large';
|
Chris@0
|
374 }
|
Chris@0
|
375
|
Chris@0
|
376 $status = $id->getStatus();
|
Chris@14
|
377 $id = \get_class($id) . '::' . $id->getName();
|
Chris@14
|
378 } elseif ($id instanceof PhptTestCase) {
|
Chris@0
|
379 $size = 'large';
|
Chris@0
|
380 $id = $id->getName();
|
Chris@0
|
381 }
|
Chris@0
|
382
|
Chris@14
|
383 $this->tests[$id] = ['size' => $size, 'status' => $status];
|
Chris@0
|
384
|
Chris@0
|
385 foreach ($data as $file => $lines) {
|
Chris@0
|
386 if (!$this->filter->isFile($file)) {
|
Chris@0
|
387 continue;
|
Chris@0
|
388 }
|
Chris@0
|
389
|
Chris@0
|
390 foreach ($lines as $k => $v) {
|
Chris@14
|
391 if ($v === Driver::LINE_EXECUTED) {
|
Chris@14
|
392 if (empty($this->data[$file][$k]) || !\in_array($id, $this->data[$file][$k])) {
|
Chris@0
|
393 $this->data[$file][$k][] = $id;
|
Chris@0
|
394 }
|
Chris@0
|
395 }
|
Chris@0
|
396 }
|
Chris@0
|
397 }
|
Chris@14
|
398
|
Chris@14
|
399 $this->report = null;
|
Chris@0
|
400 }
|
Chris@0
|
401
|
Chris@0
|
402 /**
|
Chris@14
|
403 * Merges the data from another instance.
|
Chris@0
|
404 *
|
Chris@14
|
405 * @param CodeCoverage $that
|
Chris@0
|
406 */
|
Chris@14
|
407 public function merge(self $that)
|
Chris@0
|
408 {
|
Chris@0
|
409 $this->filter->setWhitelistedFiles(
|
Chris@14
|
410 \array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles())
|
Chris@0
|
411 );
|
Chris@0
|
412
|
Chris@0
|
413 foreach ($that->data as $file => $lines) {
|
Chris@0
|
414 if (!isset($this->data[$file])) {
|
Chris@0
|
415 if (!$this->filter->isFiltered($file)) {
|
Chris@0
|
416 $this->data[$file] = $lines;
|
Chris@0
|
417 }
|
Chris@0
|
418
|
Chris@0
|
419 continue;
|
Chris@0
|
420 }
|
Chris@0
|
421
|
Chris@0
|
422 foreach ($lines as $line => $data) {
|
Chris@0
|
423 if ($data !== null) {
|
Chris@0
|
424 if (!isset($this->data[$file][$line])) {
|
Chris@0
|
425 $this->data[$file][$line] = $data;
|
Chris@0
|
426 } else {
|
Chris@14
|
427 $this->data[$file][$line] = \array_unique(
|
Chris@14
|
428 \array_merge($this->data[$file][$line], $data)
|
Chris@0
|
429 );
|
Chris@0
|
430 }
|
Chris@0
|
431 }
|
Chris@0
|
432 }
|
Chris@0
|
433 }
|
Chris@0
|
434
|
Chris@14
|
435 $this->tests = \array_merge($this->tests, $that->getTests());
|
Chris@14
|
436 $this->report = null;
|
Chris@0
|
437 }
|
Chris@0
|
438
|
Chris@0
|
439 /**
|
Chris@14
|
440 * @param bool $flag
|
Chris@14
|
441 *
|
Chris@14
|
442 * @throws InvalidArgumentException
|
Chris@0
|
443 */
|
Chris@0
|
444 public function setCacheTokens($flag)
|
Chris@0
|
445 {
|
Chris@14
|
446 if (!\is_bool($flag)) {
|
Chris@14
|
447 throw InvalidArgumentException::create(
|
Chris@0
|
448 1,
|
Chris@0
|
449 'boolean'
|
Chris@0
|
450 );
|
Chris@0
|
451 }
|
Chris@0
|
452
|
Chris@0
|
453 $this->cacheTokens = $flag;
|
Chris@0
|
454 }
|
Chris@0
|
455
|
Chris@0
|
456 /**
|
Chris@14
|
457 * @return bool
|
Chris@0
|
458 */
|
Chris@0
|
459 public function getCacheTokens()
|
Chris@0
|
460 {
|
Chris@0
|
461 return $this->cacheTokens;
|
Chris@0
|
462 }
|
Chris@0
|
463
|
Chris@0
|
464 /**
|
Chris@14
|
465 * @param bool $flag
|
Chris@14
|
466 *
|
Chris@14
|
467 * @throws InvalidArgumentException
|
Chris@0
|
468 */
|
Chris@0
|
469 public function setCheckForUnintentionallyCoveredCode($flag)
|
Chris@0
|
470 {
|
Chris@14
|
471 if (!\is_bool($flag)) {
|
Chris@14
|
472 throw InvalidArgumentException::create(
|
Chris@0
|
473 1,
|
Chris@0
|
474 'boolean'
|
Chris@0
|
475 );
|
Chris@0
|
476 }
|
Chris@0
|
477
|
Chris@0
|
478 $this->checkForUnintentionallyCoveredCode = $flag;
|
Chris@0
|
479 }
|
Chris@0
|
480
|
Chris@0
|
481 /**
|
Chris@14
|
482 * @param bool $flag
|
Chris@14
|
483 *
|
Chris@14
|
484 * @throws InvalidArgumentException
|
Chris@0
|
485 */
|
Chris@0
|
486 public function setForceCoversAnnotation($flag)
|
Chris@0
|
487 {
|
Chris@14
|
488 if (!\is_bool($flag)) {
|
Chris@14
|
489 throw InvalidArgumentException::create(
|
Chris@0
|
490 1,
|
Chris@0
|
491 'boolean'
|
Chris@0
|
492 );
|
Chris@0
|
493 }
|
Chris@0
|
494
|
Chris@0
|
495 $this->forceCoversAnnotation = $flag;
|
Chris@0
|
496 }
|
Chris@0
|
497
|
Chris@0
|
498 /**
|
Chris@14
|
499 * @param bool $flag
|
Chris@14
|
500 *
|
Chris@14
|
501 * @throws InvalidArgumentException
|
Chris@0
|
502 */
|
Chris@14
|
503 public function setCheckForMissingCoversAnnotation($flag)
|
Chris@0
|
504 {
|
Chris@14
|
505 if (!\is_bool($flag)) {
|
Chris@14
|
506 throw InvalidArgumentException::create(
|
Chris@0
|
507 1,
|
Chris@0
|
508 'boolean'
|
Chris@0
|
509 );
|
Chris@0
|
510 }
|
Chris@0
|
511
|
Chris@14
|
512 $this->checkForMissingCoversAnnotation = $flag;
|
Chris@0
|
513 }
|
Chris@0
|
514
|
Chris@0
|
515 /**
|
Chris@14
|
516 * @param bool $flag
|
Chris@14
|
517 *
|
Chris@14
|
518 * @throws InvalidArgumentException
|
Chris@14
|
519 */
|
Chris@14
|
520 public function setCheckForUnexecutedCoveredCode($flag)
|
Chris@14
|
521 {
|
Chris@14
|
522 if (!\is_bool($flag)) {
|
Chris@14
|
523 throw InvalidArgumentException::create(
|
Chris@14
|
524 1,
|
Chris@14
|
525 'boolean'
|
Chris@14
|
526 );
|
Chris@14
|
527 }
|
Chris@14
|
528
|
Chris@14
|
529 $this->checkForUnexecutedCoveredCode = $flag;
|
Chris@14
|
530 }
|
Chris@14
|
531
|
Chris@14
|
532 /**
|
Chris@14
|
533 * @deprecated
|
Chris@14
|
534 *
|
Chris@14
|
535 * @param bool $flag
|
Chris@14
|
536 *
|
Chris@14
|
537 * @throws InvalidArgumentException
|
Chris@14
|
538 */
|
Chris@14
|
539 public function setMapTestClassNameToCoveredClassName($flag)
|
Chris@14
|
540 {
|
Chris@14
|
541 }
|
Chris@14
|
542
|
Chris@14
|
543 /**
|
Chris@14
|
544 * @param bool $flag
|
Chris@14
|
545 *
|
Chris@14
|
546 * @throws InvalidArgumentException
|
Chris@0
|
547 */
|
Chris@0
|
548 public function setAddUncoveredFilesFromWhitelist($flag)
|
Chris@0
|
549 {
|
Chris@14
|
550 if (!\is_bool($flag)) {
|
Chris@14
|
551 throw InvalidArgumentException::create(
|
Chris@0
|
552 1,
|
Chris@0
|
553 'boolean'
|
Chris@0
|
554 );
|
Chris@0
|
555 }
|
Chris@0
|
556
|
Chris@0
|
557 $this->addUncoveredFilesFromWhitelist = $flag;
|
Chris@0
|
558 }
|
Chris@0
|
559
|
Chris@0
|
560 /**
|
Chris@14
|
561 * @param bool $flag
|
Chris@14
|
562 *
|
Chris@14
|
563 * @throws InvalidArgumentException
|
Chris@0
|
564 */
|
Chris@0
|
565 public function setProcessUncoveredFilesFromWhitelist($flag)
|
Chris@0
|
566 {
|
Chris@14
|
567 if (!\is_bool($flag)) {
|
Chris@14
|
568 throw InvalidArgumentException::create(
|
Chris@0
|
569 1,
|
Chris@0
|
570 'boolean'
|
Chris@0
|
571 );
|
Chris@0
|
572 }
|
Chris@0
|
573
|
Chris@0
|
574 $this->processUncoveredFilesFromWhitelist = $flag;
|
Chris@0
|
575 }
|
Chris@0
|
576
|
Chris@0
|
577 /**
|
Chris@14
|
578 * @param bool $flag
|
Chris@14
|
579 *
|
Chris@14
|
580 * @throws InvalidArgumentException
|
Chris@0
|
581 */
|
Chris@0
|
582 public function setDisableIgnoredLines($flag)
|
Chris@0
|
583 {
|
Chris@14
|
584 if (!\is_bool($flag)) {
|
Chris@14
|
585 throw InvalidArgumentException::create(
|
Chris@0
|
586 1,
|
Chris@0
|
587 'boolean'
|
Chris@0
|
588 );
|
Chris@0
|
589 }
|
Chris@0
|
590
|
Chris@0
|
591 $this->disableIgnoredLines = $flag;
|
Chris@0
|
592 }
|
Chris@0
|
593
|
Chris@0
|
594 /**
|
Chris@14
|
595 * @param bool $flag
|
Chris@14
|
596 *
|
Chris@14
|
597 * @throws InvalidArgumentException
|
Chris@14
|
598 */
|
Chris@14
|
599 public function setIgnoreDeprecatedCode($flag)
|
Chris@14
|
600 {
|
Chris@14
|
601 if (!\is_bool($flag)) {
|
Chris@14
|
602 throw InvalidArgumentException::create(
|
Chris@14
|
603 1,
|
Chris@14
|
604 'boolean'
|
Chris@14
|
605 );
|
Chris@14
|
606 }
|
Chris@14
|
607
|
Chris@14
|
608 $this->ignoreDeprecatedCode = $flag;
|
Chris@14
|
609 }
|
Chris@14
|
610
|
Chris@14
|
611 /**
|
Chris@14
|
612 * @param array $whitelist
|
Chris@14
|
613 */
|
Chris@14
|
614 public function setUnintentionallyCoveredSubclassesWhitelist(array $whitelist)
|
Chris@14
|
615 {
|
Chris@14
|
616 $this->unintentionallyCoveredSubclassesWhitelist = $whitelist;
|
Chris@14
|
617 }
|
Chris@14
|
618
|
Chris@14
|
619 /**
|
Chris@0
|
620 * Applies the @covers annotation filtering.
|
Chris@0
|
621 *
|
Chris@14
|
622 * @param array $data
|
Chris@14
|
623 * @param mixed $linesToBeCovered
|
Chris@14
|
624 * @param array $linesToBeUsed
|
Chris@14
|
625 * @param bool $ignoreForceCoversAnnotation
|
Chris@14
|
626 *
|
Chris@14
|
627 * @throws \SebastianBergmann\CodeCoverage\CoveredCodeNotExecutedException
|
Chris@14
|
628 * @throws \ReflectionException
|
Chris@14
|
629 * @throws MissingCoversAnnotationException
|
Chris@14
|
630 * @throws UnintentionallyCoveredCodeException
|
Chris@0
|
631 */
|
Chris@14
|
632 private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed, $ignoreForceCoversAnnotation)
|
Chris@0
|
633 {
|
Chris@0
|
634 if ($linesToBeCovered === false ||
|
Chris@14
|
635 ($this->forceCoversAnnotation && empty($linesToBeCovered) && !$ignoreForceCoversAnnotation)) {
|
Chris@14
|
636 if ($this->checkForMissingCoversAnnotation) {
|
Chris@14
|
637 throw new MissingCoversAnnotationException;
|
Chris@14
|
638 }
|
Chris@14
|
639
|
Chris@14
|
640 $data = [];
|
Chris@0
|
641
|
Chris@0
|
642 return;
|
Chris@0
|
643 }
|
Chris@0
|
644
|
Chris@0
|
645 if (empty($linesToBeCovered)) {
|
Chris@0
|
646 return;
|
Chris@0
|
647 }
|
Chris@0
|
648
|
Chris@14
|
649 if ($this->checkForUnintentionallyCoveredCode &&
|
Chris@14
|
650 (!$this->currentId instanceof TestCase ||
|
Chris@14
|
651 (!$this->currentId->isMedium() && !$this->currentId->isLarge()))) {
|
Chris@14
|
652 $this->performUnintentionallyCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed);
|
Chris@0
|
653 }
|
Chris@0
|
654
|
Chris@14
|
655 if ($this->checkForUnexecutedCoveredCode) {
|
Chris@14
|
656 $this->performUnexecutedCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed);
|
Chris@14
|
657 }
|
Chris@0
|
658
|
Chris@14
|
659 $data = \array_intersect_key($data, $linesToBeCovered);
|
Chris@0
|
660
|
Chris@14
|
661 foreach (\array_keys($data) as $filename) {
|
Chris@14
|
662 $_linesToBeCovered = \array_flip($linesToBeCovered[$filename]);
|
Chris@14
|
663 $data[$filename] = \array_intersect_key($data[$filename], $_linesToBeCovered);
|
Chris@0
|
664 }
|
Chris@0
|
665 }
|
Chris@0
|
666
|
Chris@0
|
667 /**
|
Chris@14
|
668 * Applies the whitelist filtering.
|
Chris@0
|
669 *
|
Chris@0
|
670 * @param array $data
|
Chris@0
|
671 */
|
Chris@0
|
672 private function applyListsFilter(array &$data)
|
Chris@0
|
673 {
|
Chris@14
|
674 foreach (\array_keys($data) as $filename) {
|
Chris@0
|
675 if ($this->filter->isFiltered($filename)) {
|
Chris@0
|
676 unset($data[$filename]);
|
Chris@0
|
677 }
|
Chris@0
|
678 }
|
Chris@0
|
679 }
|
Chris@0
|
680
|
Chris@0
|
681 /**
|
Chris@0
|
682 * Applies the "ignored lines" filtering.
|
Chris@0
|
683 *
|
Chris@0
|
684 * @param array $data
|
Chris@14
|
685 *
|
Chris@14
|
686 * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
|
Chris@0
|
687 */
|
Chris@0
|
688 private function applyIgnoredLinesFilter(array &$data)
|
Chris@0
|
689 {
|
Chris@14
|
690 foreach (\array_keys($data) as $filename) {
|
Chris@0
|
691 if (!$this->filter->isFile($filename)) {
|
Chris@0
|
692 continue;
|
Chris@0
|
693 }
|
Chris@0
|
694
|
Chris@0
|
695 foreach ($this->getLinesToBeIgnored($filename) as $line) {
|
Chris@0
|
696 unset($data[$filename][$line]);
|
Chris@0
|
697 }
|
Chris@0
|
698 }
|
Chris@0
|
699 }
|
Chris@0
|
700
|
Chris@0
|
701 /**
|
Chris@0
|
702 * @param array $data
|
Chris@0
|
703 */
|
Chris@0
|
704 private function initializeFilesThatAreSeenTheFirstTime(array $data)
|
Chris@0
|
705 {
|
Chris@0
|
706 foreach ($data as $file => $lines) {
|
Chris@14
|
707 if (!isset($this->data[$file]) && $this->filter->isFile($file)) {
|
Chris@14
|
708 $this->data[$file] = [];
|
Chris@0
|
709
|
Chris@0
|
710 foreach ($lines as $k => $v) {
|
Chris@14
|
711 $this->data[$file][$k] = $v === -2 ? null : [];
|
Chris@0
|
712 }
|
Chris@0
|
713 }
|
Chris@0
|
714 }
|
Chris@0
|
715 }
|
Chris@0
|
716
|
Chris@0
|
717 /**
|
Chris@0
|
718 * Processes whitelisted files that are not covered.
|
Chris@0
|
719 */
|
Chris@0
|
720 private function addUncoveredFilesFromWhitelist()
|
Chris@0
|
721 {
|
Chris@14
|
722 $data = [];
|
Chris@14
|
723 $uncoveredFiles = \array_diff(
|
Chris@0
|
724 $this->filter->getWhitelist(),
|
Chris@14
|
725 \array_keys($this->data)
|
Chris@0
|
726 );
|
Chris@0
|
727
|
Chris@0
|
728 foreach ($uncoveredFiles as $uncoveredFile) {
|
Chris@14
|
729 if (!\file_exists($uncoveredFile)) {
|
Chris@0
|
730 continue;
|
Chris@0
|
731 }
|
Chris@0
|
732
|
Chris@14
|
733 $data[$uncoveredFile] = [];
|
Chris@0
|
734
|
Chris@14
|
735 $lines = \count(\file($uncoveredFile));
|
Chris@0
|
736
|
Chris@14
|
737 for ($i = 1; $i <= $lines; $i++) {
|
Chris@14
|
738 $data[$uncoveredFile][$i] = Driver::LINE_NOT_EXECUTED;
|
Chris@0
|
739 }
|
Chris@0
|
740 }
|
Chris@0
|
741
|
Chris@0
|
742 $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
|
Chris@0
|
743 }
|
Chris@0
|
744
|
Chris@0
|
745 /**
|
Chris@0
|
746 * Returns the lines of a source file that should be ignored.
|
Chris@0
|
747 *
|
Chris@14
|
748 * @param string $filename
|
Chris@14
|
749 *
|
Chris@0
|
750 * @return array
|
Chris@14
|
751 *
|
Chris@14
|
752 * @throws InvalidArgumentException
|
Chris@0
|
753 */
|
Chris@0
|
754 private function getLinesToBeIgnored($filename)
|
Chris@0
|
755 {
|
Chris@14
|
756 if (!\is_string($filename)) {
|
Chris@14
|
757 throw InvalidArgumentException::create(
|
Chris@0
|
758 1,
|
Chris@0
|
759 'string'
|
Chris@0
|
760 );
|
Chris@0
|
761 }
|
Chris@0
|
762
|
Chris@14
|
763 if (isset($this->ignoredLines[$filename])) {
|
Chris@14
|
764 return $this->ignoredLines[$filename];
|
Chris@14
|
765 }
|
Chris@0
|
766
|
Chris@14
|
767 $this->ignoredLines[$filename] = [];
|
Chris@14
|
768
|
Chris@14
|
769 $lines = \file($filename);
|
Chris@14
|
770
|
Chris@14
|
771 foreach ($lines as $index => $line) {
|
Chris@14
|
772 if (!\trim($line)) {
|
Chris@14
|
773 $this->ignoredLines[$filename][] = $index + 1;
|
Chris@14
|
774 }
|
Chris@14
|
775 }
|
Chris@14
|
776
|
Chris@14
|
777 if ($this->cacheTokens) {
|
Chris@14
|
778 $tokens = \PHP_Token_Stream_CachingFactory::get($filename);
|
Chris@14
|
779 } else {
|
Chris@14
|
780 $tokens = new \PHP_Token_Stream($filename);
|
Chris@14
|
781 }
|
Chris@14
|
782
|
Chris@14
|
783 foreach ($tokens->getInterfaces() as $interface) {
|
Chris@14
|
784 $interfaceStartLine = $interface['startLine'];
|
Chris@14
|
785 $interfaceEndLine = $interface['endLine'];
|
Chris@14
|
786
|
Chris@14
|
787 foreach (\range($interfaceStartLine, $interfaceEndLine) as $line) {
|
Chris@14
|
788 $this->ignoredLines[$filename][] = $line;
|
Chris@14
|
789 }
|
Chris@14
|
790 }
|
Chris@14
|
791
|
Chris@14
|
792 foreach (\array_merge($tokens->getClasses(), $tokens->getTraits()) as $classOrTrait) {
|
Chris@14
|
793 $classOrTraitStartLine = $classOrTrait['startLine'];
|
Chris@14
|
794 $classOrTraitEndLine = $classOrTrait['endLine'];
|
Chris@14
|
795
|
Chris@14
|
796 if (empty($classOrTrait['methods'])) {
|
Chris@14
|
797 foreach (\range($classOrTraitStartLine, $classOrTraitEndLine) as $line) {
|
Chris@14
|
798 $this->ignoredLines[$filename][] = $line;
|
Chris@14
|
799 }
|
Chris@14
|
800
|
Chris@14
|
801 continue;
|
Chris@0
|
802 }
|
Chris@0
|
803
|
Chris@14
|
804 $firstMethod = \array_shift($classOrTrait['methods']);
|
Chris@14
|
805 $firstMethodStartLine = $firstMethod['startLine'];
|
Chris@14
|
806 $firstMethodEndLine = $firstMethod['endLine'];
|
Chris@14
|
807 $lastMethodEndLine = $firstMethodEndLine;
|
Chris@0
|
808
|
Chris@14
|
809 do {
|
Chris@14
|
810 $lastMethod = \array_pop($classOrTrait['methods']);
|
Chris@14
|
811 } while ($lastMethod !== null && 0 === \strpos($lastMethod['signature'], 'anonymousFunction'));
|
Chris@14
|
812
|
Chris@14
|
813 if ($lastMethod !== null) {
|
Chris@14
|
814 $lastMethodEndLine = $lastMethod['endLine'];
|
Chris@14
|
815 }
|
Chris@14
|
816
|
Chris@14
|
817 foreach (\range($classOrTraitStartLine, $firstMethodStartLine) as $line) {
|
Chris@14
|
818 $this->ignoredLines[$filename][] = $line;
|
Chris@14
|
819 }
|
Chris@14
|
820
|
Chris@14
|
821 foreach (\range($lastMethodEndLine + 1, $classOrTraitEndLine) as $line) {
|
Chris@14
|
822 $this->ignoredLines[$filename][] = $line;
|
Chris@14
|
823 }
|
Chris@14
|
824 }
|
Chris@14
|
825
|
Chris@14
|
826 if ($this->disableIgnoredLines) {
|
Chris@14
|
827 $this->ignoredLines[$filename] = array_unique($this->ignoredLines[$filename]);
|
Chris@14
|
828 \sort($this->ignoredLines[$filename]);
|
Chris@14
|
829
|
Chris@14
|
830 return $this->ignoredLines[$filename];
|
Chris@14
|
831 }
|
Chris@14
|
832
|
Chris@14
|
833 $ignore = false;
|
Chris@14
|
834 $stop = false;
|
Chris@14
|
835
|
Chris@14
|
836 foreach ($tokens->tokens() as $token) {
|
Chris@14
|
837 switch (\get_class($token)) {
|
Chris@14
|
838 case \PHP_Token_COMMENT::class:
|
Chris@14
|
839 case \PHP_Token_DOC_COMMENT::class:
|
Chris@14
|
840 $_token = \trim($token);
|
Chris@14
|
841 $_line = \trim($lines[$token->getLine() - 1]);
|
Chris@14
|
842
|
Chris@14
|
843 if ($_token === '// @codeCoverageIgnore' ||
|
Chris@14
|
844 $_token === '//@codeCoverageIgnore') {
|
Chris@14
|
845 $ignore = true;
|
Chris@14
|
846 $stop = true;
|
Chris@14
|
847 } elseif ($_token === '// @codeCoverageIgnoreStart' ||
|
Chris@14
|
848 $_token === '//@codeCoverageIgnoreStart') {
|
Chris@14
|
849 $ignore = true;
|
Chris@14
|
850 } elseif ($_token === '// @codeCoverageIgnoreEnd' ||
|
Chris@14
|
851 $_token === '//@codeCoverageIgnoreEnd') {
|
Chris@14
|
852 $stop = true;
|
Chris@14
|
853 }
|
Chris@14
|
854
|
Chris@14
|
855 if (!$ignore) {
|
Chris@14
|
856 $start = $token->getLine();
|
Chris@14
|
857 $end = $start + \substr_count($token, "\n");
|
Chris@14
|
858
|
Chris@14
|
859 // Do not ignore the first line when there is a token
|
Chris@14
|
860 // before the comment
|
Chris@14
|
861 if (0 !== \strpos($_token, $_line)) {
|
Chris@14
|
862 $start++;
|
Chris@14
|
863 }
|
Chris@14
|
864
|
Chris@14
|
865 for ($i = $start; $i < $end; $i++) {
|
Chris@14
|
866 $this->ignoredLines[$filename][] = $i;
|
Chris@14
|
867 }
|
Chris@14
|
868
|
Chris@14
|
869 // A DOC_COMMENT token or a COMMENT token starting with "/*"
|
Chris@14
|
870 // does not contain the final \n character in its text
|
Chris@14
|
871 if (isset($lines[$i - 1]) && 0 === \strpos($_token, '/*') && '*/' === \substr(\trim($lines[$i - 1]), -2)) {
|
Chris@14
|
872 $this->ignoredLines[$filename][] = $i;
|
Chris@14
|
873 }
|
Chris@14
|
874 }
|
Chris@14
|
875
|
Chris@14
|
876 break;
|
Chris@14
|
877
|
Chris@14
|
878 case \PHP_Token_INTERFACE::class:
|
Chris@14
|
879 case \PHP_Token_TRAIT::class:
|
Chris@14
|
880 case \PHP_Token_CLASS::class:
|
Chris@14
|
881 case \PHP_Token_FUNCTION::class:
|
Chris@14
|
882 /* @var \PHP_Token_Interface $token */
|
Chris@14
|
883
|
Chris@14
|
884 $docblock = $token->getDocblock();
|
Chris@14
|
885
|
Chris@14
|
886 $this->ignoredLines[$filename][] = $token->getLine();
|
Chris@14
|
887
|
Chris@14
|
888 if (\strpos($docblock, '@codeCoverageIgnore') || ($this->ignoreDeprecatedCode && \strpos($docblock, '@deprecated'))) {
|
Chris@14
|
889 $endLine = $token->getEndLine();
|
Chris@14
|
890
|
Chris@14
|
891 for ($i = $token->getLine(); $i <= $endLine; $i++) {
|
Chris@14
|
892 $this->ignoredLines[$filename][] = $i;
|
Chris@14
|
893 }
|
Chris@14
|
894 }
|
Chris@14
|
895
|
Chris@14
|
896 break;
|
Chris@14
|
897
|
Chris@14
|
898 case \PHP_Token_ENUM::class:
|
Chris@14
|
899 $this->ignoredLines[$filename][] = $token->getLine();
|
Chris@14
|
900
|
Chris@14
|
901 break;
|
Chris@14
|
902
|
Chris@14
|
903 case \PHP_Token_NAMESPACE::class:
|
Chris@14
|
904 $this->ignoredLines[$filename][] = $token->getEndLine();
|
Chris@14
|
905
|
Chris@14
|
906 // Intentional fallthrough
|
Chris@14
|
907 case \PHP_Token_DECLARE::class:
|
Chris@14
|
908 case \PHP_Token_OPEN_TAG::class:
|
Chris@14
|
909 case \PHP_Token_CLOSE_TAG::class:
|
Chris@14
|
910 case \PHP_Token_USE::class:
|
Chris@14
|
911 $this->ignoredLines[$filename][] = $token->getLine();
|
Chris@14
|
912
|
Chris@14
|
913 break;
|
Chris@14
|
914 }
|
Chris@14
|
915
|
Chris@14
|
916 if ($ignore) {
|
Chris@14
|
917 $this->ignoredLines[$filename][] = $token->getLine();
|
Chris@14
|
918
|
Chris@14
|
919 if ($stop) {
|
Chris@14
|
920 $ignore = false;
|
Chris@14
|
921 $stop = false;
|
Chris@0
|
922 }
|
Chris@0
|
923 }
|
Chris@14
|
924 }
|
Chris@0
|
925
|
Chris@14
|
926 $this->ignoredLines[$filename][] = \count($lines) + 1;
|
Chris@0
|
927
|
Chris@14
|
928 $this->ignoredLines[$filename] = \array_unique(
|
Chris@14
|
929 $this->ignoredLines[$filename]
|
Chris@14
|
930 );
|
Chris@0
|
931
|
Chris@14
|
932 $this->ignoredLines[$filename] = array_unique($this->ignoredLines[$filename]);
|
Chris@14
|
933 \sort($this->ignoredLines[$filename]);
|
Chris@0
|
934
|
Chris@0
|
935 return $this->ignoredLines[$filename];
|
Chris@0
|
936 }
|
Chris@0
|
937
|
Chris@0
|
938 /**
|
Chris@14
|
939 * @param array $data
|
Chris@14
|
940 * @param array $linesToBeCovered
|
Chris@14
|
941 * @param array $linesToBeUsed
|
Chris@14
|
942 *
|
Chris@14
|
943 * @throws \ReflectionException
|
Chris@14
|
944 * @throws UnintentionallyCoveredCodeException
|
Chris@0
|
945 */
|
Chris@0
|
946 private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed)
|
Chris@0
|
947 {
|
Chris@0
|
948 $allowedLines = $this->getAllowedLines(
|
Chris@0
|
949 $linesToBeCovered,
|
Chris@0
|
950 $linesToBeUsed
|
Chris@0
|
951 );
|
Chris@0
|
952
|
Chris@14
|
953 $unintentionallyCoveredUnits = [];
|
Chris@0
|
954
|
Chris@0
|
955 foreach ($data as $file => $_data) {
|
Chris@0
|
956 foreach ($_data as $line => $flag) {
|
Chris@14
|
957 if ($flag === 1 && !isset($allowedLines[$file][$line])) {
|
Chris@14
|
958 $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line);
|
Chris@0
|
959 }
|
Chris@0
|
960 }
|
Chris@0
|
961 }
|
Chris@0
|
962
|
Chris@14
|
963 $unintentionallyCoveredUnits = $this->processUnintentionallyCoveredUnits($unintentionallyCoveredUnits);
|
Chris@14
|
964
|
Chris@14
|
965 if (!empty($unintentionallyCoveredUnits)) {
|
Chris@14
|
966 throw new UnintentionallyCoveredCodeException(
|
Chris@14
|
967 $unintentionallyCoveredUnits
|
Chris@0
|
968 );
|
Chris@0
|
969 }
|
Chris@0
|
970 }
|
Chris@0
|
971
|
Chris@0
|
972 /**
|
Chris@14
|
973 * @param array $data
|
Chris@14
|
974 * @param array $linesToBeCovered
|
Chris@14
|
975 * @param array $linesToBeUsed
|
Chris@14
|
976 *
|
Chris@14
|
977 * @throws CoveredCodeNotExecutedException
|
Chris@14
|
978 */
|
Chris@14
|
979 private function performUnexecutedCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed)
|
Chris@14
|
980 {
|
Chris@14
|
981 $executedCodeUnits = $this->coverageToCodeUnits($data);
|
Chris@14
|
982 $message = '';
|
Chris@14
|
983
|
Chris@14
|
984 foreach ($this->linesToCodeUnits($linesToBeCovered) as $codeUnit) {
|
Chris@14
|
985 if (!\in_array($codeUnit, $executedCodeUnits)) {
|
Chris@14
|
986 $message .= \sprintf(
|
Chris@14
|
987 '- %s is expected to be executed (@covers) but was not executed' . "\n",
|
Chris@14
|
988 $codeUnit
|
Chris@14
|
989 );
|
Chris@14
|
990 }
|
Chris@14
|
991 }
|
Chris@14
|
992
|
Chris@14
|
993 foreach ($this->linesToCodeUnits($linesToBeUsed) as $codeUnit) {
|
Chris@14
|
994 if (!\in_array($codeUnit, $executedCodeUnits)) {
|
Chris@14
|
995 $message .= \sprintf(
|
Chris@14
|
996 '- %s is expected to be executed (@uses) but was not executed' . "\n",
|
Chris@14
|
997 $codeUnit
|
Chris@14
|
998 );
|
Chris@14
|
999 }
|
Chris@14
|
1000 }
|
Chris@14
|
1001
|
Chris@14
|
1002 if (!empty($message)) {
|
Chris@14
|
1003 throw new CoveredCodeNotExecutedException($message);
|
Chris@14
|
1004 }
|
Chris@14
|
1005 }
|
Chris@14
|
1006
|
Chris@14
|
1007 /**
|
Chris@14
|
1008 * @param array $linesToBeCovered
|
Chris@14
|
1009 * @param array $linesToBeUsed
|
Chris@14
|
1010 *
|
Chris@0
|
1011 * @return array
|
Chris@0
|
1012 */
|
Chris@0
|
1013 private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed)
|
Chris@0
|
1014 {
|
Chris@14
|
1015 $allowedLines = [];
|
Chris@0
|
1016
|
Chris@14
|
1017 foreach (\array_keys($linesToBeCovered) as $file) {
|
Chris@0
|
1018 if (!isset($allowedLines[$file])) {
|
Chris@14
|
1019 $allowedLines[$file] = [];
|
Chris@0
|
1020 }
|
Chris@0
|
1021
|
Chris@14
|
1022 $allowedLines[$file] = \array_merge(
|
Chris@0
|
1023 $allowedLines[$file],
|
Chris@0
|
1024 $linesToBeCovered[$file]
|
Chris@0
|
1025 );
|
Chris@0
|
1026 }
|
Chris@0
|
1027
|
Chris@14
|
1028 foreach (\array_keys($linesToBeUsed) as $file) {
|
Chris@0
|
1029 if (!isset($allowedLines[$file])) {
|
Chris@14
|
1030 $allowedLines[$file] = [];
|
Chris@0
|
1031 }
|
Chris@0
|
1032
|
Chris@14
|
1033 $allowedLines[$file] = \array_merge(
|
Chris@0
|
1034 $allowedLines[$file],
|
Chris@0
|
1035 $linesToBeUsed[$file]
|
Chris@0
|
1036 );
|
Chris@0
|
1037 }
|
Chris@0
|
1038
|
Chris@14
|
1039 foreach (\array_keys($allowedLines) as $file) {
|
Chris@14
|
1040 $allowedLines[$file] = \array_flip(
|
Chris@14
|
1041 \array_unique($allowedLines[$file])
|
Chris@0
|
1042 );
|
Chris@0
|
1043 }
|
Chris@0
|
1044
|
Chris@0
|
1045 return $allowedLines;
|
Chris@0
|
1046 }
|
Chris@0
|
1047
|
Chris@0
|
1048 /**
|
Chris@14
|
1049 * @return Driver
|
Chris@14
|
1050 *
|
Chris@14
|
1051 * @throws RuntimeException
|
Chris@0
|
1052 */
|
Chris@0
|
1053 private function selectDriver()
|
Chris@0
|
1054 {
|
Chris@0
|
1055 $runtime = new Runtime;
|
Chris@0
|
1056
|
Chris@0
|
1057 if (!$runtime->canCollectCodeCoverage()) {
|
Chris@14
|
1058 throw new RuntimeException('No code coverage driver available');
|
Chris@0
|
1059 }
|
Chris@0
|
1060
|
Chris@0
|
1061 if ($runtime->isHHVM()) {
|
Chris@14
|
1062 return new HHVM;
|
Chris@14
|
1063 }
|
Chris@14
|
1064
|
Chris@14
|
1065 if ($runtime->isPHPDBG()) {
|
Chris@14
|
1066 return new PHPDBG;
|
Chris@14
|
1067 }
|
Chris@14
|
1068
|
Chris@14
|
1069 return new Xdebug;
|
Chris@14
|
1070 }
|
Chris@14
|
1071
|
Chris@14
|
1072 /**
|
Chris@14
|
1073 * @param array $unintentionallyCoveredUnits
|
Chris@14
|
1074 *
|
Chris@14
|
1075 * @return array
|
Chris@14
|
1076 *
|
Chris@14
|
1077 * @throws \ReflectionException
|
Chris@14
|
1078 */
|
Chris@14
|
1079 private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits)
|
Chris@14
|
1080 {
|
Chris@14
|
1081 $unintentionallyCoveredUnits = \array_unique($unintentionallyCoveredUnits);
|
Chris@14
|
1082 \sort($unintentionallyCoveredUnits);
|
Chris@14
|
1083
|
Chris@14
|
1084 foreach (\array_keys($unintentionallyCoveredUnits) as $k => $v) {
|
Chris@14
|
1085 $unit = \explode('::', $unintentionallyCoveredUnits[$k]);
|
Chris@14
|
1086
|
Chris@14
|
1087 if (\count($unit) !== 2) {
|
Chris@14
|
1088 continue;
|
Chris@14
|
1089 }
|
Chris@14
|
1090
|
Chris@14
|
1091 $class = new \ReflectionClass($unit[0]);
|
Chris@14
|
1092
|
Chris@14
|
1093 foreach ($this->unintentionallyCoveredSubclassesWhitelist as $whitelisted) {
|
Chris@14
|
1094 if ($class->isSubclassOf($whitelisted)) {
|
Chris@14
|
1095 unset($unintentionallyCoveredUnits[$k]);
|
Chris@14
|
1096
|
Chris@14
|
1097 break;
|
Chris@14
|
1098 }
|
Chris@14
|
1099 }
|
Chris@14
|
1100 }
|
Chris@14
|
1101
|
Chris@14
|
1102 return \array_values($unintentionallyCoveredUnits);
|
Chris@14
|
1103 }
|
Chris@14
|
1104
|
Chris@14
|
1105 /**
|
Chris@14
|
1106 * If we are processing uncovered files from whitelist,
|
Chris@14
|
1107 * we can initialize the data before we start to speed up the tests
|
Chris@14
|
1108 *
|
Chris@14
|
1109 * @throws \SebastianBergmann\CodeCoverage\RuntimeException
|
Chris@14
|
1110 */
|
Chris@14
|
1111 protected function initializeData()
|
Chris@14
|
1112 {
|
Chris@14
|
1113 $this->isInitialized = true;
|
Chris@14
|
1114
|
Chris@14
|
1115 if ($this->processUncoveredFilesFromWhitelist) {
|
Chris@14
|
1116 $this->shouldCheckForDeadAndUnused = false;
|
Chris@14
|
1117
|
Chris@14
|
1118 $this->driver->start(true);
|
Chris@14
|
1119
|
Chris@14
|
1120 foreach ($this->filter->getWhitelist() as $file) {
|
Chris@14
|
1121 if ($this->filter->isFile($file)) {
|
Chris@14
|
1122 include_once($file);
|
Chris@14
|
1123 }
|
Chris@14
|
1124 }
|
Chris@14
|
1125
|
Chris@14
|
1126 $data = [];
|
Chris@14
|
1127 $coverage = $this->driver->stop();
|
Chris@14
|
1128
|
Chris@14
|
1129 foreach ($coverage as $file => $fileCoverage) {
|
Chris@14
|
1130 if ($this->filter->isFiltered($file)) {
|
Chris@14
|
1131 continue;
|
Chris@14
|
1132 }
|
Chris@14
|
1133
|
Chris@14
|
1134 foreach (\array_keys($fileCoverage) as $key) {
|
Chris@14
|
1135 if ($fileCoverage[$key] === Driver::LINE_EXECUTED) {
|
Chris@14
|
1136 $fileCoverage[$key] = Driver::LINE_NOT_EXECUTED;
|
Chris@14
|
1137 }
|
Chris@14
|
1138 }
|
Chris@14
|
1139
|
Chris@14
|
1140 $data[$file] = $fileCoverage;
|
Chris@14
|
1141 }
|
Chris@14
|
1142
|
Chris@14
|
1143 $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
|
Chris@0
|
1144 }
|
Chris@0
|
1145 }
|
Chris@14
|
1146
|
Chris@14
|
1147 /**
|
Chris@14
|
1148 * @param array $data
|
Chris@14
|
1149 *
|
Chris@14
|
1150 * @return array
|
Chris@14
|
1151 */
|
Chris@14
|
1152 private function coverageToCodeUnits(array $data)
|
Chris@14
|
1153 {
|
Chris@14
|
1154 $codeUnits = [];
|
Chris@14
|
1155
|
Chris@14
|
1156 foreach ($data as $filename => $lines) {
|
Chris@14
|
1157 foreach ($lines as $line => $flag) {
|
Chris@14
|
1158 if ($flag === 1) {
|
Chris@14
|
1159 $codeUnits[] = $this->wizard->lookup($filename, $line);
|
Chris@14
|
1160 }
|
Chris@14
|
1161 }
|
Chris@14
|
1162 }
|
Chris@14
|
1163
|
Chris@14
|
1164 return \array_unique($codeUnits);
|
Chris@14
|
1165 }
|
Chris@14
|
1166
|
Chris@14
|
1167 /**
|
Chris@14
|
1168 * @param array $data
|
Chris@14
|
1169 *
|
Chris@14
|
1170 * @return array
|
Chris@14
|
1171 */
|
Chris@14
|
1172 private function linesToCodeUnits(array $data)
|
Chris@14
|
1173 {
|
Chris@14
|
1174 $codeUnits = [];
|
Chris@14
|
1175
|
Chris@14
|
1176 foreach ($data as $filename => $lines) {
|
Chris@14
|
1177 foreach ($lines as $line) {
|
Chris@14
|
1178 $codeUnits[] = $this->wizard->lookup($filename, $line);
|
Chris@14
|
1179 }
|
Chris@14
|
1180 }
|
Chris@14
|
1181
|
Chris@14
|
1182 return \array_unique($codeUnits);
|
Chris@14
|
1183 }
|
Chris@0
|
1184 }
|