comparison vendor/phpunit/php-code-coverage/src/CodeCoverage.php @ 14:1fec387a4317

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