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