Chris@0
|
1 <?php
|
Chris@0
|
2 /*
|
Chris@0
|
3 * This file is part of the PHP_CodeCoverage 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@0
|
11 use SebastianBergmann\Environment\Runtime;
|
Chris@0
|
12
|
Chris@0
|
13 /**
|
Chris@0
|
14 * Provides collection functionality for PHP code coverage information.
|
Chris@0
|
15 *
|
Chris@0
|
16 * @since Class available since Release 1.0.0
|
Chris@0
|
17 */
|
Chris@0
|
18 class PHP_CodeCoverage
|
Chris@0
|
19 {
|
Chris@0
|
20 /**
|
Chris@0
|
21 * @var PHP_CodeCoverage_Driver
|
Chris@0
|
22 */
|
Chris@0
|
23 private $driver;
|
Chris@0
|
24
|
Chris@0
|
25 /**
|
Chris@0
|
26 * @var PHP_CodeCoverage_Filter
|
Chris@0
|
27 */
|
Chris@0
|
28 private $filter;
|
Chris@0
|
29
|
Chris@0
|
30 /**
|
Chris@0
|
31 * @var bool
|
Chris@0
|
32 */
|
Chris@0
|
33 private $cacheTokens = false;
|
Chris@0
|
34
|
Chris@0
|
35 /**
|
Chris@0
|
36 * @var bool
|
Chris@0
|
37 */
|
Chris@0
|
38 private $checkForUnintentionallyCoveredCode = false;
|
Chris@0
|
39
|
Chris@0
|
40 /**
|
Chris@0
|
41 * @var bool
|
Chris@0
|
42 */
|
Chris@0
|
43 private $forceCoversAnnotation = false;
|
Chris@0
|
44
|
Chris@0
|
45 /**
|
Chris@0
|
46 * @var bool
|
Chris@0
|
47 */
|
Chris@0
|
48 private $mapTestClassNameToCoveredClassName = false;
|
Chris@0
|
49
|
Chris@0
|
50 /**
|
Chris@0
|
51 * @var bool
|
Chris@0
|
52 */
|
Chris@0
|
53 private $addUncoveredFilesFromWhitelist = true;
|
Chris@0
|
54
|
Chris@0
|
55 /**
|
Chris@0
|
56 * @var bool
|
Chris@0
|
57 */
|
Chris@0
|
58 private $processUncoveredFilesFromWhitelist = false;
|
Chris@0
|
59
|
Chris@0
|
60 /**
|
Chris@0
|
61 * @var mixed
|
Chris@0
|
62 */
|
Chris@0
|
63 private $currentId;
|
Chris@0
|
64
|
Chris@0
|
65 /**
|
Chris@0
|
66 * Code coverage data.
|
Chris@0
|
67 *
|
Chris@0
|
68 * @var array
|
Chris@0
|
69 */
|
Chris@0
|
70 private $data = array();
|
Chris@0
|
71
|
Chris@0
|
72 /**
|
Chris@0
|
73 * @var array
|
Chris@0
|
74 */
|
Chris@0
|
75 private $ignoredLines = array();
|
Chris@0
|
76
|
Chris@0
|
77 /**
|
Chris@0
|
78 * @var bool
|
Chris@0
|
79 */
|
Chris@0
|
80 private $disableIgnoredLines = false;
|
Chris@0
|
81
|
Chris@0
|
82 /**
|
Chris@0
|
83 * Test data.
|
Chris@0
|
84 *
|
Chris@0
|
85 * @var array
|
Chris@0
|
86 */
|
Chris@0
|
87 private $tests = array();
|
Chris@0
|
88
|
Chris@0
|
89 /**
|
Chris@0
|
90 * Constructor.
|
Chris@0
|
91 *
|
Chris@0
|
92 * @param PHP_CodeCoverage_Driver $driver
|
Chris@0
|
93 * @param PHP_CodeCoverage_Filter $filter
|
Chris@0
|
94 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
95 */
|
Chris@0
|
96 public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null)
|
Chris@0
|
97 {
|
Chris@0
|
98 if ($driver === null) {
|
Chris@0
|
99 $driver = $this->selectDriver();
|
Chris@0
|
100 }
|
Chris@0
|
101
|
Chris@0
|
102 if ($filter === null) {
|
Chris@0
|
103 $filter = new PHP_CodeCoverage_Filter;
|
Chris@0
|
104 }
|
Chris@0
|
105
|
Chris@0
|
106 $this->driver = $driver;
|
Chris@0
|
107 $this->filter = $filter;
|
Chris@0
|
108 }
|
Chris@0
|
109
|
Chris@0
|
110 /**
|
Chris@0
|
111 * Returns the PHP_CodeCoverage_Report_Node_* object graph
|
Chris@0
|
112 * for this PHP_CodeCoverage object.
|
Chris@0
|
113 *
|
Chris@0
|
114 * @return PHP_CodeCoverage_Report_Node_Directory
|
Chris@0
|
115 * @since Method available since Release 1.1.0
|
Chris@0
|
116 */
|
Chris@0
|
117 public function getReport()
|
Chris@0
|
118 {
|
Chris@0
|
119 $factory = new PHP_CodeCoverage_Report_Factory;
|
Chris@0
|
120
|
Chris@0
|
121 return $factory->create($this);
|
Chris@0
|
122 }
|
Chris@0
|
123
|
Chris@0
|
124 /**
|
Chris@0
|
125 * Clears collected code coverage data.
|
Chris@0
|
126 */
|
Chris@0
|
127 public function clear()
|
Chris@0
|
128 {
|
Chris@0
|
129 $this->currentId = null;
|
Chris@0
|
130 $this->data = array();
|
Chris@0
|
131 $this->tests = array();
|
Chris@0
|
132 }
|
Chris@0
|
133
|
Chris@0
|
134 /**
|
Chris@0
|
135 * Returns the PHP_CodeCoverage_Filter used.
|
Chris@0
|
136 *
|
Chris@0
|
137 * @return PHP_CodeCoverage_Filter
|
Chris@0
|
138 */
|
Chris@0
|
139 public function filter()
|
Chris@0
|
140 {
|
Chris@0
|
141 return $this->filter;
|
Chris@0
|
142 }
|
Chris@0
|
143
|
Chris@0
|
144 /**
|
Chris@0
|
145 * Returns the collected code coverage data.
|
Chris@0
|
146 * Set $raw = true to bypass all filters.
|
Chris@0
|
147 *
|
Chris@0
|
148 * @param bool $raw
|
Chris@0
|
149 * @return array
|
Chris@0
|
150 * @since Method available since Release 1.1.0
|
Chris@0
|
151 */
|
Chris@0
|
152 public function getData($raw = false)
|
Chris@0
|
153 {
|
Chris@0
|
154 if (!$raw && $this->addUncoveredFilesFromWhitelist) {
|
Chris@0
|
155 $this->addUncoveredFilesFromWhitelist();
|
Chris@0
|
156 }
|
Chris@0
|
157
|
Chris@0
|
158 // We need to apply the blacklist filter a second time
|
Chris@0
|
159 // when no whitelist is used.
|
Chris@0
|
160 if (!$raw && !$this->filter->hasWhitelist()) {
|
Chris@0
|
161 $this->applyListsFilter($this->data);
|
Chris@0
|
162 }
|
Chris@0
|
163
|
Chris@0
|
164 return $this->data;
|
Chris@0
|
165 }
|
Chris@0
|
166
|
Chris@0
|
167 /**
|
Chris@0
|
168 * Sets the coverage data.
|
Chris@0
|
169 *
|
Chris@0
|
170 * @param array $data
|
Chris@0
|
171 * @since Method available since Release 2.0.0
|
Chris@0
|
172 */
|
Chris@0
|
173 public function setData(array $data)
|
Chris@0
|
174 {
|
Chris@0
|
175 $this->data = $data;
|
Chris@0
|
176 }
|
Chris@0
|
177
|
Chris@0
|
178 /**
|
Chris@0
|
179 * Returns the test data.
|
Chris@0
|
180 *
|
Chris@0
|
181 * @return array
|
Chris@0
|
182 * @since Method available since Release 1.1.0
|
Chris@0
|
183 */
|
Chris@0
|
184 public function getTests()
|
Chris@0
|
185 {
|
Chris@0
|
186 return $this->tests;
|
Chris@0
|
187 }
|
Chris@0
|
188
|
Chris@0
|
189 /**
|
Chris@0
|
190 * Sets the test data.
|
Chris@0
|
191 *
|
Chris@0
|
192 * @param array $tests
|
Chris@0
|
193 * @since Method available since Release 2.0.0
|
Chris@0
|
194 */
|
Chris@0
|
195 public function setTests(array $tests)
|
Chris@0
|
196 {
|
Chris@0
|
197 $this->tests = $tests;
|
Chris@0
|
198 }
|
Chris@0
|
199
|
Chris@0
|
200 /**
|
Chris@0
|
201 * Start collection of code coverage information.
|
Chris@0
|
202 *
|
Chris@0
|
203 * @param mixed $id
|
Chris@0
|
204 * @param bool $clear
|
Chris@0
|
205 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
206 */
|
Chris@0
|
207 public function start($id, $clear = false)
|
Chris@0
|
208 {
|
Chris@0
|
209 if (!is_bool($clear)) {
|
Chris@0
|
210 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
|
Chris@0
|
211 1,
|
Chris@0
|
212 'boolean'
|
Chris@0
|
213 );
|
Chris@0
|
214 }
|
Chris@0
|
215
|
Chris@0
|
216 if ($clear) {
|
Chris@0
|
217 $this->clear();
|
Chris@0
|
218 }
|
Chris@0
|
219
|
Chris@0
|
220 $this->currentId = $id;
|
Chris@0
|
221
|
Chris@0
|
222 $this->driver->start();
|
Chris@0
|
223 }
|
Chris@0
|
224
|
Chris@0
|
225 /**
|
Chris@0
|
226 * Stop collection of code coverage information.
|
Chris@0
|
227 *
|
Chris@0
|
228 * @param bool $append
|
Chris@0
|
229 * @param mixed $linesToBeCovered
|
Chris@0
|
230 * @param array $linesToBeUsed
|
Chris@0
|
231 * @return array
|
Chris@0
|
232 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
233 */
|
Chris@0
|
234 public function stop($append = true, $linesToBeCovered = array(), array $linesToBeUsed = array())
|
Chris@0
|
235 {
|
Chris@0
|
236 if (!is_bool($append)) {
|
Chris@0
|
237 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
|
Chris@0
|
238 1,
|
Chris@0
|
239 'boolean'
|
Chris@0
|
240 );
|
Chris@0
|
241 }
|
Chris@0
|
242
|
Chris@0
|
243 if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) {
|
Chris@0
|
244 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
|
Chris@0
|
245 2,
|
Chris@0
|
246 'array or false'
|
Chris@0
|
247 );
|
Chris@0
|
248 }
|
Chris@0
|
249
|
Chris@0
|
250 $data = $this->driver->stop();
|
Chris@0
|
251 $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed);
|
Chris@0
|
252
|
Chris@0
|
253 $this->currentId = null;
|
Chris@0
|
254
|
Chris@0
|
255 return $data;
|
Chris@0
|
256 }
|
Chris@0
|
257
|
Chris@0
|
258 /**
|
Chris@0
|
259 * Appends code coverage data.
|
Chris@0
|
260 *
|
Chris@0
|
261 * @param array $data
|
Chris@0
|
262 * @param mixed $id
|
Chris@0
|
263 * @param bool $append
|
Chris@0
|
264 * @param mixed $linesToBeCovered
|
Chris@0
|
265 * @param array $linesToBeUsed
|
Chris@0
|
266 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
267 */
|
Chris@0
|
268 public function append(array $data, $id = null, $append = true, $linesToBeCovered = array(), array $linesToBeUsed = array())
|
Chris@0
|
269 {
|
Chris@0
|
270 if ($id === null) {
|
Chris@0
|
271 $id = $this->currentId;
|
Chris@0
|
272 }
|
Chris@0
|
273
|
Chris@0
|
274 if ($id === null) {
|
Chris@0
|
275 throw new PHP_CodeCoverage_Exception;
|
Chris@0
|
276 }
|
Chris@0
|
277
|
Chris@0
|
278 $this->applyListsFilter($data);
|
Chris@0
|
279 $this->applyIgnoredLinesFilter($data);
|
Chris@0
|
280 $this->initializeFilesThatAreSeenTheFirstTime($data);
|
Chris@0
|
281
|
Chris@0
|
282 if (!$append) {
|
Chris@0
|
283 return;
|
Chris@0
|
284 }
|
Chris@0
|
285
|
Chris@0
|
286 if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') {
|
Chris@0
|
287 $this->applyCoversAnnotationFilter(
|
Chris@0
|
288 $data,
|
Chris@0
|
289 $linesToBeCovered,
|
Chris@0
|
290 $linesToBeUsed
|
Chris@0
|
291 );
|
Chris@0
|
292 }
|
Chris@0
|
293
|
Chris@0
|
294 if (empty($data)) {
|
Chris@0
|
295 return;
|
Chris@0
|
296 }
|
Chris@0
|
297
|
Chris@0
|
298 $size = 'unknown';
|
Chris@0
|
299 $status = null;
|
Chris@0
|
300
|
Chris@0
|
301 if ($id instanceof PHPUnit_Framework_TestCase) {
|
Chris@0
|
302 $_size = $id->getSize();
|
Chris@0
|
303
|
Chris@0
|
304 if ($_size == PHPUnit_Util_Test::SMALL) {
|
Chris@0
|
305 $size = 'small';
|
Chris@0
|
306 } elseif ($_size == PHPUnit_Util_Test::MEDIUM) {
|
Chris@0
|
307 $size = 'medium';
|
Chris@0
|
308 } elseif ($_size == PHPUnit_Util_Test::LARGE) {
|
Chris@0
|
309 $size = 'large';
|
Chris@0
|
310 }
|
Chris@0
|
311
|
Chris@0
|
312 $status = $id->getStatus();
|
Chris@0
|
313 $id = get_class($id) . '::' . $id->getName();
|
Chris@0
|
314 } elseif ($id instanceof PHPUnit_Extensions_PhptTestCase) {
|
Chris@0
|
315 $size = 'large';
|
Chris@0
|
316 $id = $id->getName();
|
Chris@0
|
317 }
|
Chris@0
|
318
|
Chris@0
|
319 $this->tests[$id] = array('size' => $size, 'status' => $status);
|
Chris@0
|
320
|
Chris@0
|
321 foreach ($data as $file => $lines) {
|
Chris@0
|
322 if (!$this->filter->isFile($file)) {
|
Chris@0
|
323 continue;
|
Chris@0
|
324 }
|
Chris@0
|
325
|
Chris@0
|
326 foreach ($lines as $k => $v) {
|
Chris@0
|
327 if ($v == PHP_CodeCoverage_Driver::LINE_EXECUTED) {
|
Chris@0
|
328 if (empty($this->data[$file][$k]) || !in_array($id, $this->data[$file][$k])) {
|
Chris@0
|
329 $this->data[$file][$k][] = $id;
|
Chris@0
|
330 }
|
Chris@0
|
331 }
|
Chris@0
|
332 }
|
Chris@0
|
333 }
|
Chris@0
|
334 }
|
Chris@0
|
335
|
Chris@0
|
336 /**
|
Chris@0
|
337 * Merges the data from another instance of PHP_CodeCoverage.
|
Chris@0
|
338 *
|
Chris@0
|
339 * @param PHP_CodeCoverage $that
|
Chris@0
|
340 */
|
Chris@0
|
341 public function merge(PHP_CodeCoverage $that)
|
Chris@0
|
342 {
|
Chris@0
|
343 $this->filter->setBlacklistedFiles(
|
Chris@0
|
344 array_merge($this->filter->getBlacklistedFiles(), $that->filter()->getBlacklistedFiles())
|
Chris@0
|
345 );
|
Chris@0
|
346
|
Chris@0
|
347 $this->filter->setWhitelistedFiles(
|
Chris@0
|
348 array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles())
|
Chris@0
|
349 );
|
Chris@0
|
350
|
Chris@0
|
351 foreach ($that->data as $file => $lines) {
|
Chris@0
|
352 if (!isset($this->data[$file])) {
|
Chris@0
|
353 if (!$this->filter->isFiltered($file)) {
|
Chris@0
|
354 $this->data[$file] = $lines;
|
Chris@0
|
355 }
|
Chris@0
|
356
|
Chris@0
|
357 continue;
|
Chris@0
|
358 }
|
Chris@0
|
359
|
Chris@0
|
360 foreach ($lines as $line => $data) {
|
Chris@0
|
361 if ($data !== null) {
|
Chris@0
|
362 if (!isset($this->data[$file][$line])) {
|
Chris@0
|
363 $this->data[$file][$line] = $data;
|
Chris@0
|
364 } else {
|
Chris@0
|
365 $this->data[$file][$line] = array_unique(
|
Chris@0
|
366 array_merge($this->data[$file][$line], $data)
|
Chris@0
|
367 );
|
Chris@0
|
368 }
|
Chris@0
|
369 }
|
Chris@0
|
370 }
|
Chris@0
|
371 }
|
Chris@0
|
372
|
Chris@0
|
373 $this->tests = array_merge($this->tests, $that->getTests());
|
Chris@0
|
374
|
Chris@0
|
375 }
|
Chris@0
|
376
|
Chris@0
|
377 /**
|
Chris@0
|
378 * @param bool $flag
|
Chris@0
|
379 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
380 * @since Method available since Release 1.1.0
|
Chris@0
|
381 */
|
Chris@0
|
382 public function setCacheTokens($flag)
|
Chris@0
|
383 {
|
Chris@0
|
384 if (!is_bool($flag)) {
|
Chris@0
|
385 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
|
Chris@0
|
386 1,
|
Chris@0
|
387 'boolean'
|
Chris@0
|
388 );
|
Chris@0
|
389 }
|
Chris@0
|
390
|
Chris@0
|
391 $this->cacheTokens = $flag;
|
Chris@0
|
392 }
|
Chris@0
|
393
|
Chris@0
|
394 /**
|
Chris@0
|
395 * @since Method available since Release 1.1.0
|
Chris@0
|
396 */
|
Chris@0
|
397 public function getCacheTokens()
|
Chris@0
|
398 {
|
Chris@0
|
399 return $this->cacheTokens;
|
Chris@0
|
400 }
|
Chris@0
|
401
|
Chris@0
|
402 /**
|
Chris@0
|
403 * @param bool $flag
|
Chris@0
|
404 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
405 * @since Method available since Release 2.0.0
|
Chris@0
|
406 */
|
Chris@0
|
407 public function setCheckForUnintentionallyCoveredCode($flag)
|
Chris@0
|
408 {
|
Chris@0
|
409 if (!is_bool($flag)) {
|
Chris@0
|
410 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
|
Chris@0
|
411 1,
|
Chris@0
|
412 'boolean'
|
Chris@0
|
413 );
|
Chris@0
|
414 }
|
Chris@0
|
415
|
Chris@0
|
416 $this->checkForUnintentionallyCoveredCode = $flag;
|
Chris@0
|
417 }
|
Chris@0
|
418
|
Chris@0
|
419 /**
|
Chris@0
|
420 * @param bool $flag
|
Chris@0
|
421 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
422 */
|
Chris@0
|
423 public function setForceCoversAnnotation($flag)
|
Chris@0
|
424 {
|
Chris@0
|
425 if (!is_bool($flag)) {
|
Chris@0
|
426 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
|
Chris@0
|
427 1,
|
Chris@0
|
428 'boolean'
|
Chris@0
|
429 );
|
Chris@0
|
430 }
|
Chris@0
|
431
|
Chris@0
|
432 $this->forceCoversAnnotation = $flag;
|
Chris@0
|
433 }
|
Chris@0
|
434
|
Chris@0
|
435 /**
|
Chris@0
|
436 * @param bool $flag
|
Chris@0
|
437 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
438 */
|
Chris@0
|
439 public function setMapTestClassNameToCoveredClassName($flag)
|
Chris@0
|
440 {
|
Chris@0
|
441 if (!is_bool($flag)) {
|
Chris@0
|
442 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
|
Chris@0
|
443 1,
|
Chris@0
|
444 'boolean'
|
Chris@0
|
445 );
|
Chris@0
|
446 }
|
Chris@0
|
447
|
Chris@0
|
448 $this->mapTestClassNameToCoveredClassName = $flag;
|
Chris@0
|
449 }
|
Chris@0
|
450
|
Chris@0
|
451 /**
|
Chris@0
|
452 * @param bool $flag
|
Chris@0
|
453 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
454 */
|
Chris@0
|
455 public function setAddUncoveredFilesFromWhitelist($flag)
|
Chris@0
|
456 {
|
Chris@0
|
457 if (!is_bool($flag)) {
|
Chris@0
|
458 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
|
Chris@0
|
459 1,
|
Chris@0
|
460 'boolean'
|
Chris@0
|
461 );
|
Chris@0
|
462 }
|
Chris@0
|
463
|
Chris@0
|
464 $this->addUncoveredFilesFromWhitelist = $flag;
|
Chris@0
|
465 }
|
Chris@0
|
466
|
Chris@0
|
467 /**
|
Chris@0
|
468 * @param bool $flag
|
Chris@0
|
469 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
470 */
|
Chris@0
|
471 public function setProcessUncoveredFilesFromWhitelist($flag)
|
Chris@0
|
472 {
|
Chris@0
|
473 if (!is_bool($flag)) {
|
Chris@0
|
474 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
|
Chris@0
|
475 1,
|
Chris@0
|
476 'boolean'
|
Chris@0
|
477 );
|
Chris@0
|
478 }
|
Chris@0
|
479
|
Chris@0
|
480 $this->processUncoveredFilesFromWhitelist = $flag;
|
Chris@0
|
481 }
|
Chris@0
|
482
|
Chris@0
|
483 /**
|
Chris@0
|
484 * @param bool $flag
|
Chris@0
|
485 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
486 */
|
Chris@0
|
487 public function setDisableIgnoredLines($flag)
|
Chris@0
|
488 {
|
Chris@0
|
489 if (!is_bool($flag)) {
|
Chris@0
|
490 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
|
Chris@0
|
491 1,
|
Chris@0
|
492 'boolean'
|
Chris@0
|
493 );
|
Chris@0
|
494 }
|
Chris@0
|
495
|
Chris@0
|
496 $this->disableIgnoredLines = $flag;
|
Chris@0
|
497 }
|
Chris@0
|
498
|
Chris@0
|
499 /**
|
Chris@0
|
500 * Applies the @covers annotation filtering.
|
Chris@0
|
501 *
|
Chris@0
|
502 * @param array $data
|
Chris@0
|
503 * @param mixed $linesToBeCovered
|
Chris@0
|
504 * @param array $linesToBeUsed
|
Chris@0
|
505 * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode
|
Chris@0
|
506 */
|
Chris@0
|
507 private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed)
|
Chris@0
|
508 {
|
Chris@0
|
509 if ($linesToBeCovered === false ||
|
Chris@0
|
510 ($this->forceCoversAnnotation && empty($linesToBeCovered))) {
|
Chris@0
|
511 $data = array();
|
Chris@0
|
512
|
Chris@0
|
513 return;
|
Chris@0
|
514 }
|
Chris@0
|
515
|
Chris@0
|
516 if (empty($linesToBeCovered)) {
|
Chris@0
|
517 return;
|
Chris@0
|
518 }
|
Chris@0
|
519
|
Chris@0
|
520 if ($this->checkForUnintentionallyCoveredCode) {
|
Chris@0
|
521 $this->performUnintentionallyCoveredCodeCheck(
|
Chris@0
|
522 $data,
|
Chris@0
|
523 $linesToBeCovered,
|
Chris@0
|
524 $linesToBeUsed
|
Chris@0
|
525 );
|
Chris@0
|
526 }
|
Chris@0
|
527
|
Chris@0
|
528 $data = array_intersect_key($data, $linesToBeCovered);
|
Chris@0
|
529
|
Chris@0
|
530 foreach (array_keys($data) as $filename) {
|
Chris@0
|
531 $_linesToBeCovered = array_flip($linesToBeCovered[$filename]);
|
Chris@0
|
532
|
Chris@0
|
533 $data[$filename] = array_intersect_key(
|
Chris@0
|
534 $data[$filename],
|
Chris@0
|
535 $_linesToBeCovered
|
Chris@0
|
536 );
|
Chris@0
|
537 }
|
Chris@0
|
538 }
|
Chris@0
|
539
|
Chris@0
|
540 /**
|
Chris@0
|
541 * Applies the blacklist/whitelist filtering.
|
Chris@0
|
542 *
|
Chris@0
|
543 * @param array $data
|
Chris@0
|
544 */
|
Chris@0
|
545 private function applyListsFilter(array &$data)
|
Chris@0
|
546 {
|
Chris@0
|
547 foreach (array_keys($data) as $filename) {
|
Chris@0
|
548 if ($this->filter->isFiltered($filename)) {
|
Chris@0
|
549 unset($data[$filename]);
|
Chris@0
|
550 }
|
Chris@0
|
551 }
|
Chris@0
|
552 }
|
Chris@0
|
553
|
Chris@0
|
554 /**
|
Chris@0
|
555 * Applies the "ignored lines" filtering.
|
Chris@0
|
556 *
|
Chris@0
|
557 * @param array $data
|
Chris@0
|
558 */
|
Chris@0
|
559 private function applyIgnoredLinesFilter(array &$data)
|
Chris@0
|
560 {
|
Chris@0
|
561 foreach (array_keys($data) as $filename) {
|
Chris@0
|
562 if (!$this->filter->isFile($filename)) {
|
Chris@0
|
563 continue;
|
Chris@0
|
564 }
|
Chris@0
|
565
|
Chris@0
|
566 foreach ($this->getLinesToBeIgnored($filename) as $line) {
|
Chris@0
|
567 unset($data[$filename][$line]);
|
Chris@0
|
568 }
|
Chris@0
|
569 }
|
Chris@0
|
570 }
|
Chris@0
|
571
|
Chris@0
|
572 /**
|
Chris@0
|
573 * @param array $data
|
Chris@0
|
574 * @since Method available since Release 1.1.0
|
Chris@0
|
575 */
|
Chris@0
|
576 private function initializeFilesThatAreSeenTheFirstTime(array $data)
|
Chris@0
|
577 {
|
Chris@0
|
578 foreach ($data as $file => $lines) {
|
Chris@0
|
579 if ($this->filter->isFile($file) && !isset($this->data[$file])) {
|
Chris@0
|
580 $this->data[$file] = array();
|
Chris@0
|
581
|
Chris@0
|
582 foreach ($lines as $k => $v) {
|
Chris@0
|
583 $this->data[$file][$k] = $v == -2 ? null : array();
|
Chris@0
|
584 }
|
Chris@0
|
585 }
|
Chris@0
|
586 }
|
Chris@0
|
587 }
|
Chris@0
|
588
|
Chris@0
|
589 /**
|
Chris@0
|
590 * Processes whitelisted files that are not covered.
|
Chris@0
|
591 */
|
Chris@0
|
592 private function addUncoveredFilesFromWhitelist()
|
Chris@0
|
593 {
|
Chris@0
|
594 $data = array();
|
Chris@0
|
595 $uncoveredFiles = array_diff(
|
Chris@0
|
596 $this->filter->getWhitelist(),
|
Chris@0
|
597 array_keys($this->data)
|
Chris@0
|
598 );
|
Chris@0
|
599
|
Chris@0
|
600 foreach ($uncoveredFiles as $uncoveredFile) {
|
Chris@0
|
601 if (!file_exists($uncoveredFile)) {
|
Chris@0
|
602 continue;
|
Chris@0
|
603 }
|
Chris@0
|
604
|
Chris@0
|
605 if ($this->processUncoveredFilesFromWhitelist) {
|
Chris@0
|
606 $this->processUncoveredFileFromWhitelist(
|
Chris@0
|
607 $uncoveredFile,
|
Chris@0
|
608 $data,
|
Chris@0
|
609 $uncoveredFiles
|
Chris@0
|
610 );
|
Chris@0
|
611 } else {
|
Chris@0
|
612 $data[$uncoveredFile] = array();
|
Chris@0
|
613
|
Chris@0
|
614 $lines = count(file($uncoveredFile));
|
Chris@0
|
615
|
Chris@0
|
616 for ($i = 1; $i <= $lines; $i++) {
|
Chris@0
|
617 $data[$uncoveredFile][$i] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED;
|
Chris@0
|
618 }
|
Chris@0
|
619 }
|
Chris@0
|
620 }
|
Chris@0
|
621
|
Chris@0
|
622 $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
|
Chris@0
|
623 }
|
Chris@0
|
624
|
Chris@0
|
625 /**
|
Chris@0
|
626 * @param string $uncoveredFile
|
Chris@0
|
627 * @param array $data
|
Chris@0
|
628 * @param array $uncoveredFiles
|
Chris@0
|
629 */
|
Chris@0
|
630 private function processUncoveredFileFromWhitelist($uncoveredFile, array &$data, array $uncoveredFiles)
|
Chris@0
|
631 {
|
Chris@0
|
632 $this->driver->start();
|
Chris@0
|
633 include_once $uncoveredFile;
|
Chris@0
|
634 $coverage = $this->driver->stop();
|
Chris@0
|
635
|
Chris@0
|
636 foreach ($coverage as $file => $fileCoverage) {
|
Chris@0
|
637 if (!isset($data[$file]) &&
|
Chris@0
|
638 in_array($file, $uncoveredFiles)) {
|
Chris@0
|
639 foreach (array_keys($fileCoverage) as $key) {
|
Chris@0
|
640 if ($fileCoverage[$key] == PHP_CodeCoverage_Driver::LINE_EXECUTED) {
|
Chris@0
|
641 $fileCoverage[$key] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED;
|
Chris@0
|
642 }
|
Chris@0
|
643 }
|
Chris@0
|
644
|
Chris@0
|
645 $data[$file] = $fileCoverage;
|
Chris@0
|
646 }
|
Chris@0
|
647 }
|
Chris@0
|
648 }
|
Chris@0
|
649
|
Chris@0
|
650 /**
|
Chris@0
|
651 * Returns the lines of a source file that should be ignored.
|
Chris@0
|
652 *
|
Chris@0
|
653 * @param string $filename
|
Chris@0
|
654 * @return array
|
Chris@0
|
655 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
656 * @since Method available since Release 2.0.0
|
Chris@0
|
657 */
|
Chris@0
|
658 private function getLinesToBeIgnored($filename)
|
Chris@0
|
659 {
|
Chris@0
|
660 if (!is_string($filename)) {
|
Chris@0
|
661 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
|
Chris@0
|
662 1,
|
Chris@0
|
663 'string'
|
Chris@0
|
664 );
|
Chris@0
|
665 }
|
Chris@0
|
666
|
Chris@0
|
667 if (!isset($this->ignoredLines[$filename])) {
|
Chris@0
|
668 $this->ignoredLines[$filename] = array();
|
Chris@0
|
669
|
Chris@0
|
670 if ($this->disableIgnoredLines) {
|
Chris@0
|
671 return $this->ignoredLines[$filename];
|
Chris@0
|
672 }
|
Chris@0
|
673
|
Chris@0
|
674 $ignore = false;
|
Chris@0
|
675 $stop = false;
|
Chris@0
|
676 $lines = file($filename);
|
Chris@0
|
677 $numLines = count($lines);
|
Chris@0
|
678
|
Chris@0
|
679 foreach ($lines as $index => $line) {
|
Chris@0
|
680 if (!trim($line)) {
|
Chris@0
|
681 $this->ignoredLines[$filename][] = $index + 1;
|
Chris@0
|
682 }
|
Chris@0
|
683 }
|
Chris@0
|
684
|
Chris@0
|
685 if ($this->cacheTokens) {
|
Chris@0
|
686 $tokens = PHP_Token_Stream_CachingFactory::get($filename);
|
Chris@0
|
687 } else {
|
Chris@0
|
688 $tokens = new PHP_Token_Stream($filename);
|
Chris@0
|
689 }
|
Chris@0
|
690
|
Chris@0
|
691 $classes = array_merge($tokens->getClasses(), $tokens->getTraits());
|
Chris@0
|
692 $tokens = $tokens->tokens();
|
Chris@0
|
693
|
Chris@0
|
694 foreach ($tokens as $token) {
|
Chris@0
|
695 switch (get_class($token)) {
|
Chris@0
|
696 case 'PHP_Token_COMMENT':
|
Chris@0
|
697 case 'PHP_Token_DOC_COMMENT':
|
Chris@0
|
698 $_token = trim($token);
|
Chris@0
|
699 $_line = trim($lines[$token->getLine() - 1]);
|
Chris@0
|
700
|
Chris@0
|
701 if ($_token == '// @codeCoverageIgnore' ||
|
Chris@0
|
702 $_token == '//@codeCoverageIgnore') {
|
Chris@0
|
703 $ignore = true;
|
Chris@0
|
704 $stop = true;
|
Chris@0
|
705 } elseif ($_token == '// @codeCoverageIgnoreStart' ||
|
Chris@0
|
706 $_token == '//@codeCoverageIgnoreStart') {
|
Chris@0
|
707 $ignore = true;
|
Chris@0
|
708 } elseif ($_token == '// @codeCoverageIgnoreEnd' ||
|
Chris@0
|
709 $_token == '//@codeCoverageIgnoreEnd') {
|
Chris@0
|
710 $stop = true;
|
Chris@0
|
711 }
|
Chris@0
|
712
|
Chris@0
|
713 if (!$ignore) {
|
Chris@0
|
714 $start = $token->getLine();
|
Chris@0
|
715 $end = $start + substr_count($token, "\n");
|
Chris@0
|
716
|
Chris@0
|
717 // Do not ignore the first line when there is a token
|
Chris@0
|
718 // before the comment
|
Chris@0
|
719 if (0 !== strpos($_token, $_line)) {
|
Chris@0
|
720 $start++;
|
Chris@0
|
721 }
|
Chris@0
|
722
|
Chris@0
|
723 for ($i = $start; $i < $end; $i++) {
|
Chris@0
|
724 $this->ignoredLines[$filename][] = $i;
|
Chris@0
|
725 }
|
Chris@0
|
726
|
Chris@0
|
727 // A DOC_COMMENT token or a COMMENT token starting with "/*"
|
Chris@0
|
728 // does not contain the final \n character in its text
|
Chris@0
|
729 if (isset($lines[$i-1]) && 0 === strpos($_token, '/*') && '*/' === substr(trim($lines[$i-1]), -2)) {
|
Chris@0
|
730 $this->ignoredLines[$filename][] = $i;
|
Chris@0
|
731 }
|
Chris@0
|
732 }
|
Chris@0
|
733 break;
|
Chris@0
|
734
|
Chris@0
|
735 case 'PHP_Token_INTERFACE':
|
Chris@0
|
736 case 'PHP_Token_TRAIT':
|
Chris@0
|
737 case 'PHP_Token_CLASS':
|
Chris@0
|
738 case 'PHP_Token_FUNCTION':
|
Chris@0
|
739 $docblock = $token->getDocblock();
|
Chris@0
|
740
|
Chris@0
|
741 $this->ignoredLines[$filename][] = $token->getLine();
|
Chris@0
|
742
|
Chris@0
|
743 if (strpos($docblock, '@codeCoverageIgnore') || strpos($docblock, '@deprecated')) {
|
Chris@0
|
744 $endLine = $token->getEndLine();
|
Chris@0
|
745
|
Chris@0
|
746 for ($i = $token->getLine(); $i <= $endLine; $i++) {
|
Chris@0
|
747 $this->ignoredLines[$filename][] = $i;
|
Chris@0
|
748 }
|
Chris@0
|
749 } elseif ($token instanceof PHP_Token_INTERFACE ||
|
Chris@0
|
750 $token instanceof PHP_Token_TRAIT ||
|
Chris@0
|
751 $token instanceof PHP_Token_CLASS) {
|
Chris@0
|
752 if (empty($classes[$token->getName()]['methods'])) {
|
Chris@0
|
753 for ($i = $token->getLine();
|
Chris@0
|
754 $i <= $token->getEndLine();
|
Chris@0
|
755 $i++) {
|
Chris@0
|
756 $this->ignoredLines[$filename][] = $i;
|
Chris@0
|
757 }
|
Chris@0
|
758 } else {
|
Chris@0
|
759 $firstMethod = array_shift(
|
Chris@0
|
760 $classes[$token->getName()]['methods']
|
Chris@0
|
761 );
|
Chris@0
|
762
|
Chris@0
|
763 do {
|
Chris@0
|
764 $lastMethod = array_pop(
|
Chris@0
|
765 $classes[$token->getName()]['methods']
|
Chris@0
|
766 );
|
Chris@0
|
767 } while ($lastMethod !== null &&
|
Chris@0
|
768 substr($lastMethod['signature'], 0, 18) == 'anonymous function');
|
Chris@0
|
769
|
Chris@0
|
770 if ($lastMethod === null) {
|
Chris@0
|
771 $lastMethod = $firstMethod;
|
Chris@0
|
772 }
|
Chris@0
|
773
|
Chris@0
|
774 for ($i = $token->getLine();
|
Chris@0
|
775 $i < $firstMethod['startLine'];
|
Chris@0
|
776 $i++) {
|
Chris@0
|
777 $this->ignoredLines[$filename][] = $i;
|
Chris@0
|
778 }
|
Chris@0
|
779
|
Chris@0
|
780 for ($i = $token->getEndLine();
|
Chris@0
|
781 $i > $lastMethod['endLine'];
|
Chris@0
|
782 $i--) {
|
Chris@0
|
783 $this->ignoredLines[$filename][] = $i;
|
Chris@0
|
784 }
|
Chris@0
|
785 }
|
Chris@0
|
786 }
|
Chris@0
|
787 break;
|
Chris@0
|
788
|
Chris@0
|
789 case 'PHP_Token_NAMESPACE':
|
Chris@0
|
790 $this->ignoredLines[$filename][] = $token->getEndLine();
|
Chris@0
|
791
|
Chris@0
|
792 // Intentional fallthrough
|
Chris@0
|
793 case 'PHP_Token_OPEN_TAG':
|
Chris@0
|
794 case 'PHP_Token_CLOSE_TAG':
|
Chris@0
|
795 case 'PHP_Token_USE':
|
Chris@0
|
796 $this->ignoredLines[$filename][] = $token->getLine();
|
Chris@0
|
797 break;
|
Chris@0
|
798 }
|
Chris@0
|
799
|
Chris@0
|
800 if ($ignore) {
|
Chris@0
|
801 $this->ignoredLines[$filename][] = $token->getLine();
|
Chris@0
|
802
|
Chris@0
|
803 if ($stop) {
|
Chris@0
|
804 $ignore = false;
|
Chris@0
|
805 $stop = false;
|
Chris@0
|
806 }
|
Chris@0
|
807 }
|
Chris@0
|
808 }
|
Chris@0
|
809
|
Chris@0
|
810 $this->ignoredLines[$filename][] = $numLines + 1;
|
Chris@0
|
811
|
Chris@0
|
812 $this->ignoredLines[$filename] = array_unique(
|
Chris@0
|
813 $this->ignoredLines[$filename]
|
Chris@0
|
814 );
|
Chris@0
|
815
|
Chris@0
|
816 sort($this->ignoredLines[$filename]);
|
Chris@0
|
817 }
|
Chris@0
|
818
|
Chris@0
|
819 return $this->ignoredLines[$filename];
|
Chris@0
|
820 }
|
Chris@0
|
821
|
Chris@0
|
822 /**
|
Chris@0
|
823 * @param array $data
|
Chris@0
|
824 * @param array $linesToBeCovered
|
Chris@0
|
825 * @param array $linesToBeUsed
|
Chris@0
|
826 * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode
|
Chris@0
|
827 * @since Method available since Release 2.0.0
|
Chris@0
|
828 */
|
Chris@0
|
829 private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed)
|
Chris@0
|
830 {
|
Chris@0
|
831 $allowedLines = $this->getAllowedLines(
|
Chris@0
|
832 $linesToBeCovered,
|
Chris@0
|
833 $linesToBeUsed
|
Chris@0
|
834 );
|
Chris@0
|
835
|
Chris@0
|
836 $message = '';
|
Chris@0
|
837
|
Chris@0
|
838 foreach ($data as $file => $_data) {
|
Chris@0
|
839 foreach ($_data as $line => $flag) {
|
Chris@0
|
840 if ($flag == 1 &&
|
Chris@0
|
841 (!isset($allowedLines[$file]) ||
|
Chris@0
|
842 !isset($allowedLines[$file][$line]))) {
|
Chris@0
|
843 $message .= sprintf(
|
Chris@0
|
844 '- %s:%d' . PHP_EOL,
|
Chris@0
|
845 $file,
|
Chris@0
|
846 $line
|
Chris@0
|
847 );
|
Chris@0
|
848 }
|
Chris@0
|
849 }
|
Chris@0
|
850 }
|
Chris@0
|
851
|
Chris@0
|
852 if (!empty($message)) {
|
Chris@0
|
853 throw new PHP_CodeCoverage_Exception_UnintentionallyCoveredCode(
|
Chris@0
|
854 $message
|
Chris@0
|
855 );
|
Chris@0
|
856 }
|
Chris@0
|
857 }
|
Chris@0
|
858
|
Chris@0
|
859 /**
|
Chris@0
|
860 * @param array $linesToBeCovered
|
Chris@0
|
861 * @param array $linesToBeUsed
|
Chris@0
|
862 * @return array
|
Chris@0
|
863 * @since Method available since Release 2.0.0
|
Chris@0
|
864 */
|
Chris@0
|
865 private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed)
|
Chris@0
|
866 {
|
Chris@0
|
867 $allowedLines = array();
|
Chris@0
|
868
|
Chris@0
|
869 foreach (array_keys($linesToBeCovered) as $file) {
|
Chris@0
|
870 if (!isset($allowedLines[$file])) {
|
Chris@0
|
871 $allowedLines[$file] = array();
|
Chris@0
|
872 }
|
Chris@0
|
873
|
Chris@0
|
874 $allowedLines[$file] = array_merge(
|
Chris@0
|
875 $allowedLines[$file],
|
Chris@0
|
876 $linesToBeCovered[$file]
|
Chris@0
|
877 );
|
Chris@0
|
878 }
|
Chris@0
|
879
|
Chris@0
|
880 foreach (array_keys($linesToBeUsed) as $file) {
|
Chris@0
|
881 if (!isset($allowedLines[$file])) {
|
Chris@0
|
882 $allowedLines[$file] = array();
|
Chris@0
|
883 }
|
Chris@0
|
884
|
Chris@0
|
885 $allowedLines[$file] = array_merge(
|
Chris@0
|
886 $allowedLines[$file],
|
Chris@0
|
887 $linesToBeUsed[$file]
|
Chris@0
|
888 );
|
Chris@0
|
889 }
|
Chris@0
|
890
|
Chris@0
|
891 foreach (array_keys($allowedLines) as $file) {
|
Chris@0
|
892 $allowedLines[$file] = array_flip(
|
Chris@0
|
893 array_unique($allowedLines[$file])
|
Chris@0
|
894 );
|
Chris@0
|
895 }
|
Chris@0
|
896
|
Chris@0
|
897 return $allowedLines;
|
Chris@0
|
898 }
|
Chris@0
|
899
|
Chris@0
|
900 /**
|
Chris@0
|
901 * @return PHP_CodeCoverage_Driver
|
Chris@0
|
902 * @throws PHP_CodeCoverage_Exception
|
Chris@0
|
903 */
|
Chris@0
|
904 private function selectDriver()
|
Chris@0
|
905 {
|
Chris@0
|
906 $runtime = new Runtime;
|
Chris@0
|
907
|
Chris@0
|
908 if (!$runtime->canCollectCodeCoverage()) {
|
Chris@0
|
909 throw new PHP_CodeCoverage_Exception('No code coverage driver available');
|
Chris@0
|
910 }
|
Chris@0
|
911
|
Chris@0
|
912 if ($runtime->isHHVM()) {
|
Chris@0
|
913 return new PHP_CodeCoverage_Driver_HHVM;
|
Chris@0
|
914 } elseif ($runtime->isPHPDBG()) {
|
Chris@0
|
915 return new PHP_CodeCoverage_Driver_PHPDBG;
|
Chris@0
|
916 } else {
|
Chris@0
|
917 return new PHP_CodeCoverage_Driver_Xdebug;
|
Chris@0
|
918 }
|
Chris@0
|
919 }
|
Chris@0
|
920 }
|