diff 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
line wrap: on
line diff
--- a/vendor/phpunit/php-code-coverage/src/CodeCoverage.php	Mon Apr 23 09:33:26 2018 +0100
+++ b/vendor/phpunit/php-code-coverage/src/CodeCoverage.php	Mon Apr 23 09:46:53 2018 +0100
@@ -1,6 +1,6 @@
 <?php
 /*
- * This file is part of the PHP_CodeCoverage package.
+ * This file is part of the php-code-coverage package.
  *
  * (c) Sebastian Bergmann <sebastian@phpunit.de>
  *
@@ -8,26 +8,40 @@
  * file that was distributed with this source code.
  */
 
+namespace SebastianBergmann\CodeCoverage;
+
+use PHPUnit\Framework\TestCase;
+use PHPUnit\Runner\PhptTestCase;
+use SebastianBergmann\CodeCoverage\Driver\Driver;
+use SebastianBergmann\CodeCoverage\Driver\HHVM;
+use SebastianBergmann\CodeCoverage\Driver\PHPDBG;
+use SebastianBergmann\CodeCoverage\Driver\Xdebug;
+use SebastianBergmann\CodeCoverage\Node\Builder;
+use SebastianBergmann\CodeCoverage\Node\Directory;
+use SebastianBergmann\CodeUnitReverseLookup\Wizard;
 use SebastianBergmann\Environment\Runtime;
 
 /**
  * Provides collection functionality for PHP code coverage information.
- *
- * @since Class available since Release 1.0.0
  */
-class PHP_CodeCoverage
+class CodeCoverage
 {
     /**
-     * @var PHP_CodeCoverage_Driver
+     * @var Driver
      */
     private $driver;
 
     /**
-     * @var PHP_CodeCoverage_Filter
+     * @var Filter
      */
     private $filter;
 
     /**
+     * @var Wizard
+     */
+    private $wizard;
+
+    /**
      * @var bool
      */
     private $cacheTokens = false;
@@ -45,7 +59,12 @@
     /**
      * @var bool
      */
-    private $mapTestClassNameToCoveredClassName = false;
+    private $checkForUnexecutedCoveredCode = false;
+
+    /**
+     * @var bool
+     */
+    private $checkForMissingCoversAnnotation = false;
 
     /**
      * @var bool
@@ -58,6 +77,11 @@
     private $processUncoveredFilesFromWhitelist = false;
 
     /**
+     * @var bool
+     */
+    private $ignoreDeprecatedCode = false;
+
+    /**
      * @var mixed
      */
     private $currentId;
@@ -67,12 +91,12 @@
      *
      * @var array
      */
-    private $data = array();
+    private $data = [];
 
     /**
      * @var array
      */
-    private $ignoredLines = array();
+    private $ignoredLines = [];
 
     /**
      * @var bool
@@ -84,41 +108,70 @@
      *
      * @var array
      */
-    private $tests = array();
+    private $tests = [];
+
+    /**
+     * @var string[]
+     */
+    private $unintentionallyCoveredSubclassesWhitelist = [];
+
+    /**
+     * Determine if the data has been initialized or not
+     *
+     * @var bool
+     */
+    private $isInitialized = false;
+
+    /**
+     * Determine whether we need to check for dead and unused code on each test
+     *
+     * @var bool
+     */
+    private $shouldCheckForDeadAndUnused = true;
+
+    /**
+     * @var Directory
+     */
+    private $report;
 
     /**
      * Constructor.
      *
-     * @param  PHP_CodeCoverage_Driver    $driver
-     * @param  PHP_CodeCoverage_Filter    $filter
-     * @throws PHP_CodeCoverage_Exception
+     * @param Driver $driver
+     * @param Filter $filter
+     *
+     * @throws RuntimeException
      */
-    public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null)
+    public function __construct(Driver $driver = null, Filter $filter = null)
     {
         if ($driver === null) {
             $driver = $this->selectDriver();
         }
 
         if ($filter === null) {
-            $filter = new PHP_CodeCoverage_Filter;
+            $filter = new Filter;
         }
 
         $this->driver = $driver;
         $this->filter = $filter;
+
+        $this->wizard = new Wizard;
     }
 
     /**
-     * Returns the PHP_CodeCoverage_Report_Node_* object graph
-     * for this PHP_CodeCoverage object.
+     * Returns the code coverage information as a graph of node objects.
      *
-     * @return PHP_CodeCoverage_Report_Node_Directory
-     * @since  Method available since Release 1.1.0
+     * @return Directory
      */
     public function getReport()
     {
-        $factory = new PHP_CodeCoverage_Report_Factory;
+        if ($this->report === null) {
+            $builder = new Builder;
 
-        return $factory->create($this);
+            $this->report = $builder->build($this);
+        }
+
+        return $this->report;
     }
 
     /**
@@ -126,15 +179,17 @@
      */
     public function clear()
     {
-        $this->currentId = null;
-        $this->data      = array();
-        $this->tests     = array();
+        $this->isInitialized = false;
+        $this->currentId     = null;
+        $this->data          = [];
+        $this->tests         = [];
+        $this->report        = null;
     }
 
     /**
-     * Returns the PHP_CodeCoverage_Filter used.
+     * Returns the filter object used.
      *
-     * @return PHP_CodeCoverage_Filter
+     * @return Filter
      */
     public function filter()
     {
@@ -145,9 +200,9 @@
      * Returns the collected code coverage data.
      * Set $raw = true to bypass all filters.
      *
-     * @param  bool  $raw
+     * @param bool $raw
+     *
      * @return array
-     * @since  Method available since Release 1.1.0
      */
     public function getData($raw = false)
     {
@@ -155,12 +210,6 @@
             $this->addUncoveredFilesFromWhitelist();
         }
 
-        // We need to apply the blacklist filter a second time
-        // when no whitelist is used.
-        if (!$raw && !$this->filter->hasWhitelist()) {
-            $this->applyListsFilter($this->data);
-        }
-
         return $this->data;
     }
 
@@ -168,18 +217,17 @@
      * Sets the coverage data.
      *
      * @param array $data
-     * @since Method available since Release 2.0.0
      */
     public function setData(array $data)
     {
-        $this->data = $data;
+        $this->data   = $data;
+        $this->report = null;
     }
 
     /**
      * Returns the test data.
      *
      * @return array
-     * @since  Method available since Release 1.1.0
      */
     public function getTests()
     {
@@ -190,7 +238,6 @@
      * Sets the test data.
      *
      * @param array $tests
-     * @since Method available since Release 2.0.0
      */
     public function setTests(array $tests)
     {
@@ -200,14 +247,15 @@
     /**
      * Start collection of code coverage information.
      *
-     * @param  mixed                      $id
-     * @param  bool                       $clear
-     * @throws PHP_CodeCoverage_Exception
+     * @param mixed $id
+     * @param bool  $clear
+     *
+     * @throws InvalidArgumentException
      */
     public function start($id, $clear = false)
     {
-        if (!is_bool($clear)) {
-            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+        if (!\is_bool($clear)) {
+            throw InvalidArgumentException::create(
                 1,
                 'boolean'
             );
@@ -217,38 +265,46 @@
             $this->clear();
         }
 
+        if ($this->isInitialized === false) {
+            $this->initializeData();
+        }
+
         $this->currentId = $id;
 
-        $this->driver->start();
+        $this->driver->start($this->shouldCheckForDeadAndUnused);
     }
 
     /**
      * Stop collection of code coverage information.
      *
-     * @param  bool                       $append
-     * @param  mixed                      $linesToBeCovered
-     * @param  array                      $linesToBeUsed
+     * @param bool  $append
+     * @param mixed $linesToBeCovered
+     * @param array $linesToBeUsed
+     * @param bool  $ignoreForceCoversAnnotation
+     *
      * @return array
-     * @throws PHP_CodeCoverage_Exception
+     *
+     * @throws \SebastianBergmann\CodeCoverage\RuntimeException
+     * @throws InvalidArgumentException
      */
-    public function stop($append = true, $linesToBeCovered = array(), array $linesToBeUsed = array())
+    public function stop($append = true, $linesToBeCovered = [], array $linesToBeUsed = [], $ignoreForceCoversAnnotation = false)
     {
-        if (!is_bool($append)) {
-            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+        if (!\is_bool($append)) {
+            throw InvalidArgumentException::create(
                 1,
                 'boolean'
             );
         }
 
-        if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) {
-            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+        if (!\is_array($linesToBeCovered) && $linesToBeCovered !== false) {
+            throw InvalidArgumentException::create(
                 2,
                 'array or false'
             );
         }
 
         $data = $this->driver->stop();
-        $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed);
+        $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed, $ignoreForceCoversAnnotation);
 
         $this->currentId = null;
 
@@ -258,21 +314,28 @@
     /**
      * Appends code coverage data.
      *
-     * @param  array                      $data
-     * @param  mixed                      $id
-     * @param  bool                       $append
-     * @param  mixed                      $linesToBeCovered
-     * @param  array                      $linesToBeUsed
-     * @throws PHP_CodeCoverage_Exception
+     * @param array $data
+     * @param mixed $id
+     * @param bool  $append
+     * @param mixed $linesToBeCovered
+     * @param array $linesToBeUsed
+     * @param bool  $ignoreForceCoversAnnotation
+     *
+     * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException
+     * @throws \SebastianBergmann\CodeCoverage\MissingCoversAnnotationException
+     * @throws \SebastianBergmann\CodeCoverage\CoveredCodeNotExecutedException
+     * @throws \ReflectionException
+     * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
+     * @throws RuntimeException
      */
-    public function append(array $data, $id = null, $append = true, $linesToBeCovered = array(), array $linesToBeUsed = array())
+    public function append(array $data, $id = null, $append = true, $linesToBeCovered = [], array $linesToBeUsed = [], $ignoreForceCoversAnnotation = false)
     {
         if ($id === null) {
             $id = $this->currentId;
         }
 
         if ($id === null) {
-            throw new PHP_CodeCoverage_Exception;
+            throw new RuntimeException;
         }
 
         $this->applyListsFilter($data);
@@ -283,11 +346,12 @@
             return;
         }
 
-        if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') {
+        if ($id !== 'UNCOVERED_FILES_FROM_WHITELIST') {
             $this->applyCoversAnnotationFilter(
                 $data,
                 $linesToBeCovered,
-                $linesToBeUsed
+                $linesToBeUsed,
+                $ignoreForceCoversAnnotation
             );
         }
 
@@ -298,25 +362,25 @@
         $size   = 'unknown';
         $status = null;
 
-        if ($id instanceof PHPUnit_Framework_TestCase) {
+        if ($id instanceof TestCase) {
             $_size = $id->getSize();
 
-            if ($_size == PHPUnit_Util_Test::SMALL) {
+            if ($_size === \PHPUnit\Util\Test::SMALL) {
                 $size = 'small';
-            } elseif ($_size == PHPUnit_Util_Test::MEDIUM) {
+            } elseif ($_size === \PHPUnit\Util\Test::MEDIUM) {
                 $size = 'medium';
-            } elseif ($_size == PHPUnit_Util_Test::LARGE) {
+            } elseif ($_size === \PHPUnit\Util\Test::LARGE) {
                 $size = 'large';
             }
 
             $status = $id->getStatus();
-            $id     = get_class($id) . '::' . $id->getName();
-        } elseif ($id instanceof PHPUnit_Extensions_PhptTestCase) {
+            $id     = \get_class($id) . '::' . $id->getName();
+        } elseif ($id instanceof PhptTestCase) {
             $size = 'large';
             $id   = $id->getName();
         }
 
-        $this->tests[$id] = array('size' => $size, 'status' => $status);
+        $this->tests[$id] = ['size' => $size, 'status' => $status];
 
         foreach ($data as $file => $lines) {
             if (!$this->filter->isFile($file)) {
@@ -324,28 +388,26 @@
             }
 
             foreach ($lines as $k => $v) {
-                if ($v == PHP_CodeCoverage_Driver::LINE_EXECUTED) {
-                    if (empty($this->data[$file][$k]) || !in_array($id, $this->data[$file][$k])) {
+                if ($v === Driver::LINE_EXECUTED) {
+                    if (empty($this->data[$file][$k]) || !\in_array($id, $this->data[$file][$k])) {
                         $this->data[$file][$k][] = $id;
                     }
                 }
             }
         }
+
+        $this->report = null;
     }
 
     /**
-     * Merges the data from another instance of PHP_CodeCoverage.
+     * Merges the data from another instance.
      *
-     * @param PHP_CodeCoverage $that
+     * @param CodeCoverage $that
      */
-    public function merge(PHP_CodeCoverage $that)
+    public function merge(self $that)
     {
-        $this->filter->setBlacklistedFiles(
-            array_merge($this->filter->getBlacklistedFiles(), $that->filter()->getBlacklistedFiles())
-        );
-
         $this->filter->setWhitelistedFiles(
-            array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles())
+            \array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles())
         );
 
         foreach ($that->data as $file => $lines) {
@@ -362,27 +424,27 @@
                     if (!isset($this->data[$file][$line])) {
                         $this->data[$file][$line] = $data;
                     } else {
-                        $this->data[$file][$line] = array_unique(
-                            array_merge($this->data[$file][$line], $data)
+                        $this->data[$file][$line] = \array_unique(
+                            \array_merge($this->data[$file][$line], $data)
                         );
                     }
                 }
             }
         }
 
-        $this->tests = array_merge($this->tests, $that->getTests());
-
+        $this->tests  = \array_merge($this->tests, $that->getTests());
+        $this->report = null;
     }
 
     /**
-     * @param  bool                       $flag
-     * @throws PHP_CodeCoverage_Exception
-     * @since  Method available since Release 1.1.0
+     * @param bool $flag
+     *
+     * @throws InvalidArgumentException
      */
     public function setCacheTokens($flag)
     {
-        if (!is_bool($flag)) {
-            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+        if (!\is_bool($flag)) {
+            throw InvalidArgumentException::create(
                 1,
                 'boolean'
             );
@@ -392,7 +454,7 @@
     }
 
     /**
-     * @since Method available since Release 1.1.0
+     * @return bool
      */
     public function getCacheTokens()
     {
@@ -400,14 +462,14 @@
     }
 
     /**
-     * @param  bool                       $flag
-     * @throws PHP_CodeCoverage_Exception
-     * @since  Method available since Release 2.0.0
+     * @param bool $flag
+     *
+     * @throws InvalidArgumentException
      */
     public function setCheckForUnintentionallyCoveredCode($flag)
     {
-        if (!is_bool($flag)) {
-            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+        if (!\is_bool($flag)) {
+            throw InvalidArgumentException::create(
                 1,
                 'boolean'
             );
@@ -417,13 +479,14 @@
     }
 
     /**
-     * @param  bool                       $flag
-     * @throws PHP_CodeCoverage_Exception
+     * @param bool $flag
+     *
+     * @throws InvalidArgumentException
      */
     public function setForceCoversAnnotation($flag)
     {
-        if (!is_bool($flag)) {
-            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+        if (!\is_bool($flag)) {
+            throw InvalidArgumentException::create(
                 1,
                 'boolean'
             );
@@ -433,29 +496,59 @@
     }
 
     /**
-     * @param  bool                       $flag
-     * @throws PHP_CodeCoverage_Exception
+     * @param bool $flag
+     *
+     * @throws InvalidArgumentException
      */
-    public function setMapTestClassNameToCoveredClassName($flag)
+    public function setCheckForMissingCoversAnnotation($flag)
     {
-        if (!is_bool($flag)) {
-            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+        if (!\is_bool($flag)) {
+            throw InvalidArgumentException::create(
                 1,
                 'boolean'
             );
         }
 
-        $this->mapTestClassNameToCoveredClassName = $flag;
+        $this->checkForMissingCoversAnnotation = $flag;
     }
 
     /**
-     * @param  bool                       $flag
-     * @throws PHP_CodeCoverage_Exception
+     * @param bool $flag
+     *
+     * @throws InvalidArgumentException
+     */
+    public function setCheckForUnexecutedCoveredCode($flag)
+    {
+        if (!\is_bool($flag)) {
+            throw InvalidArgumentException::create(
+                1,
+                'boolean'
+            );
+        }
+
+        $this->checkForUnexecutedCoveredCode = $flag;
+    }
+
+    /**
+     * @deprecated
+     *
+     * @param bool $flag
+     *
+     * @throws InvalidArgumentException
+     */
+    public function setMapTestClassNameToCoveredClassName($flag)
+    {
+    }
+
+    /**
+     * @param bool $flag
+     *
+     * @throws InvalidArgumentException
      */
     public function setAddUncoveredFilesFromWhitelist($flag)
     {
-        if (!is_bool($flag)) {
-            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+        if (!\is_bool($flag)) {
+            throw InvalidArgumentException::create(
                 1,
                 'boolean'
             );
@@ -465,13 +558,14 @@
     }
 
     /**
-     * @param  bool                       $flag
-     * @throws PHP_CodeCoverage_Exception
+     * @param bool $flag
+     *
+     * @throws InvalidArgumentException
      */
     public function setProcessUncoveredFilesFromWhitelist($flag)
     {
-        if (!is_bool($flag)) {
-            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+        if (!\is_bool($flag)) {
+            throw InvalidArgumentException::create(
                 1,
                 'boolean'
             );
@@ -481,13 +575,14 @@
     }
 
     /**
-     * @param  bool                       $flag
-     * @throws PHP_CodeCoverage_Exception
+     * @param bool $flag
+     *
+     * @throws InvalidArgumentException
      */
     public function setDisableIgnoredLines($flag)
     {
-        if (!is_bool($flag)) {
-            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+        if (!\is_bool($flag)) {
+            throw InvalidArgumentException::create(
                 1,
                 'boolean'
             );
@@ -497,18 +592,52 @@
     }
 
     /**
+     * @param bool $flag
+     *
+     * @throws InvalidArgumentException
+     */
+    public function setIgnoreDeprecatedCode($flag)
+    {
+        if (!\is_bool($flag)) {
+            throw InvalidArgumentException::create(
+                1,
+                'boolean'
+            );
+        }
+
+        $this->ignoreDeprecatedCode = $flag;
+    }
+
+    /**
+     * @param array $whitelist
+     */
+    public function setUnintentionallyCoveredSubclassesWhitelist(array $whitelist)
+    {
+        $this->unintentionallyCoveredSubclassesWhitelist = $whitelist;
+    }
+
+    /**
      * Applies the @covers annotation filtering.
      *
-     * @param  array                                                 $data
-     * @param  mixed                                                 $linesToBeCovered
-     * @param  array                                                 $linesToBeUsed
-     * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode
+     * @param array $data
+     * @param mixed $linesToBeCovered
+     * @param array $linesToBeUsed
+     * @param bool  $ignoreForceCoversAnnotation
+     *
+     * @throws \SebastianBergmann\CodeCoverage\CoveredCodeNotExecutedException
+     * @throws \ReflectionException
+     * @throws MissingCoversAnnotationException
+     * @throws UnintentionallyCoveredCodeException
      */
-    private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed)
+    private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed, $ignoreForceCoversAnnotation)
     {
         if ($linesToBeCovered === false ||
-            ($this->forceCoversAnnotation && empty($linesToBeCovered))) {
-            $data = array();
+            ($this->forceCoversAnnotation && empty($linesToBeCovered) && !$ignoreForceCoversAnnotation)) {
+            if ($this->checkForMissingCoversAnnotation) {
+                throw new MissingCoversAnnotationException;
+            }
+
+            $data = [];
 
             return;
         }
@@ -517,34 +646,32 @@
             return;
         }
 
-        if ($this->checkForUnintentionallyCoveredCode) {
-            $this->performUnintentionallyCoveredCodeCheck(
-                $data,
-                $linesToBeCovered,
-                $linesToBeUsed
-            );
+        if ($this->checkForUnintentionallyCoveredCode &&
+            (!$this->currentId instanceof TestCase ||
+            (!$this->currentId->isMedium() && !$this->currentId->isLarge()))) {
+            $this->performUnintentionallyCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed);
         }
 
-        $data = array_intersect_key($data, $linesToBeCovered);
+        if ($this->checkForUnexecutedCoveredCode) {
+            $this->performUnexecutedCoveredCodeCheck($data, $linesToBeCovered, $linesToBeUsed);
+        }
 
-        foreach (array_keys($data) as $filename) {
-            $_linesToBeCovered = array_flip($linesToBeCovered[$filename]);
+        $data = \array_intersect_key($data, $linesToBeCovered);
 
-            $data[$filename] = array_intersect_key(
-                $data[$filename],
-                $_linesToBeCovered
-            );
+        foreach (\array_keys($data) as $filename) {
+            $_linesToBeCovered = \array_flip($linesToBeCovered[$filename]);
+            $data[$filename]   = \array_intersect_key($data[$filename], $_linesToBeCovered);
         }
     }
 
     /**
-     * Applies the blacklist/whitelist filtering.
+     * Applies the whitelist filtering.
      *
      * @param array $data
      */
     private function applyListsFilter(array &$data)
     {
-        foreach (array_keys($data) as $filename) {
+        foreach (\array_keys($data) as $filename) {
             if ($this->filter->isFiltered($filename)) {
                 unset($data[$filename]);
             }
@@ -555,10 +682,12 @@
      * Applies the "ignored lines" filtering.
      *
      * @param array $data
+     *
+     * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
      */
     private function applyIgnoredLinesFilter(array &$data)
     {
-        foreach (array_keys($data) as $filename) {
+        foreach (\array_keys($data) as $filename) {
             if (!$this->filter->isFile($filename)) {
                 continue;
             }
@@ -571,16 +700,15 @@
 
     /**
      * @param array $data
-     * @since Method available since Release 1.1.0
      */
     private function initializeFilesThatAreSeenTheFirstTime(array $data)
     {
         foreach ($data as $file => $lines) {
-            if ($this->filter->isFile($file) && !isset($this->data[$file])) {
-                $this->data[$file] = array();
+            if (!isset($this->data[$file]) && $this->filter->isFile($file)) {
+                $this->data[$file] = [];
 
                 foreach ($lines as $k => $v) {
-                    $this->data[$file][$k] = $v == -2 ? null : array();
+                    $this->data[$file][$k] = $v === -2 ? null : [];
                 }
             }
         }
@@ -591,31 +719,23 @@
      */
     private function addUncoveredFilesFromWhitelist()
     {
-        $data           = array();
-        $uncoveredFiles = array_diff(
+        $data           = [];
+        $uncoveredFiles = \array_diff(
             $this->filter->getWhitelist(),
-            array_keys($this->data)
+            \array_keys($this->data)
         );
 
         foreach ($uncoveredFiles as $uncoveredFile) {
-            if (!file_exists($uncoveredFile)) {
+            if (!\file_exists($uncoveredFile)) {
                 continue;
             }
 
-            if ($this->processUncoveredFilesFromWhitelist) {
-                $this->processUncoveredFileFromWhitelist(
-                    $uncoveredFile,
-                    $data,
-                    $uncoveredFiles
-                );
-            } else {
-                $data[$uncoveredFile] = array();
+            $data[$uncoveredFile] = [];
 
-                $lines = count(file($uncoveredFile));
+            $lines = \count(\file($uncoveredFile));
 
-                for ($i = 1; $i <= $lines; $i++) {
-                    $data[$uncoveredFile][$i] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED;
-                }
+            for ($i = 1; $i <= $lines; $i++) {
+                $data[$uncoveredFile][$i] = Driver::LINE_NOT_EXECUTED;
             }
         }
 
@@ -623,208 +743,205 @@
     }
 
     /**
-     * @param string $uncoveredFile
-     * @param array  $data
-     * @param array  $uncoveredFiles
-     */
-    private function processUncoveredFileFromWhitelist($uncoveredFile, array &$data, array $uncoveredFiles)
-    {
-        $this->driver->start();
-        include_once $uncoveredFile;
-        $coverage = $this->driver->stop();
-
-        foreach ($coverage as $file => $fileCoverage) {
-            if (!isset($data[$file]) &&
-                in_array($file, $uncoveredFiles)) {
-                foreach (array_keys($fileCoverage) as $key) {
-                    if ($fileCoverage[$key] == PHP_CodeCoverage_Driver::LINE_EXECUTED) {
-                        $fileCoverage[$key] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED;
-                    }
-                }
-
-                $data[$file] = $fileCoverage;
-            }
-        }
-    }
-
-    /**
      * Returns the lines of a source file that should be ignored.
      *
-     * @param  string                     $filename
+     * @param string $filename
+     *
      * @return array
-     * @throws PHP_CodeCoverage_Exception
-     * @since  Method available since Release 2.0.0
+     *
+     * @throws InvalidArgumentException
      */
     private function getLinesToBeIgnored($filename)
     {
-        if (!is_string($filename)) {
-            throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
+        if (!\is_string($filename)) {
+            throw InvalidArgumentException::create(
                 1,
                 'string'
             );
         }
 
-        if (!isset($this->ignoredLines[$filename])) {
-            $this->ignoredLines[$filename] = array();
+        if (isset($this->ignoredLines[$filename])) {
+            return $this->ignoredLines[$filename];
+        }
 
-            if ($this->disableIgnoredLines) {
-                return $this->ignoredLines[$filename];
+        $this->ignoredLines[$filename] = [];
+
+        $lines = \file($filename);
+
+        foreach ($lines as $index => $line) {
+            if (!\trim($line)) {
+                $this->ignoredLines[$filename][] = $index + 1;
+            }
+        }
+
+        if ($this->cacheTokens) {
+            $tokens = \PHP_Token_Stream_CachingFactory::get($filename);
+        } else {
+            $tokens = new \PHP_Token_Stream($filename);
+        }
+
+        foreach ($tokens->getInterfaces() as $interface) {
+            $interfaceStartLine = $interface['startLine'];
+            $interfaceEndLine   = $interface['endLine'];
+
+            foreach (\range($interfaceStartLine, $interfaceEndLine) as $line) {
+                $this->ignoredLines[$filename][] = $line;
+            }
+        }
+
+        foreach (\array_merge($tokens->getClasses(), $tokens->getTraits()) as $classOrTrait) {
+            $classOrTraitStartLine = $classOrTrait['startLine'];
+            $classOrTraitEndLine   = $classOrTrait['endLine'];
+
+            if (empty($classOrTrait['methods'])) {
+                foreach (\range($classOrTraitStartLine, $classOrTraitEndLine) as $line) {
+                    $this->ignoredLines[$filename][] = $line;
+                }
+
+                continue;
             }
 
-            $ignore   = false;
-            $stop     = false;
-            $lines    = file($filename);
-            $numLines = count($lines);
+            $firstMethod          = \array_shift($classOrTrait['methods']);
+            $firstMethodStartLine = $firstMethod['startLine'];
+            $firstMethodEndLine   = $firstMethod['endLine'];
+            $lastMethodEndLine    = $firstMethodEndLine;
 
-            foreach ($lines as $index => $line) {
-                if (!trim($line)) {
-                    $this->ignoredLines[$filename][] = $index + 1;
+            do {
+                $lastMethod = \array_pop($classOrTrait['methods']);
+            } while ($lastMethod !== null && 0 === \strpos($lastMethod['signature'], 'anonymousFunction'));
+
+            if ($lastMethod !== null) {
+                $lastMethodEndLine = $lastMethod['endLine'];
+            }
+
+            foreach (\range($classOrTraitStartLine, $firstMethodStartLine) as $line) {
+                $this->ignoredLines[$filename][] = $line;
+            }
+
+            foreach (\range($lastMethodEndLine + 1, $classOrTraitEndLine) as $line) {
+                $this->ignoredLines[$filename][] = $line;
+            }
+        }
+
+        if ($this->disableIgnoredLines) {
+            $this->ignoredLines[$filename] = array_unique($this->ignoredLines[$filename]);
+            \sort($this->ignoredLines[$filename]);
+
+            return $this->ignoredLines[$filename];
+        }
+
+        $ignore = false;
+        $stop   = false;
+
+        foreach ($tokens->tokens() as $token) {
+            switch (\get_class($token)) {
+                case \PHP_Token_COMMENT::class:
+                case \PHP_Token_DOC_COMMENT::class:
+                    $_token = \trim($token);
+                    $_line  = \trim($lines[$token->getLine() - 1]);
+
+                    if ($_token === '// @codeCoverageIgnore' ||
+                        $_token === '//@codeCoverageIgnore') {
+                        $ignore = true;
+                        $stop   = true;
+                    } elseif ($_token === '// @codeCoverageIgnoreStart' ||
+                        $_token === '//@codeCoverageIgnoreStart') {
+                        $ignore = true;
+                    } elseif ($_token === '// @codeCoverageIgnoreEnd' ||
+                        $_token === '//@codeCoverageIgnoreEnd') {
+                        $stop = true;
+                    }
+
+                    if (!$ignore) {
+                        $start = $token->getLine();
+                        $end   = $start + \substr_count($token, "\n");
+
+                        // Do not ignore the first line when there is a token
+                        // before the comment
+                        if (0 !== \strpos($_token, $_line)) {
+                            $start++;
+                        }
+
+                        for ($i = $start; $i < $end; $i++) {
+                            $this->ignoredLines[$filename][] = $i;
+                        }
+
+                        // A DOC_COMMENT token or a COMMENT token starting with "/*"
+                        // does not contain the final \n character in its text
+                        if (isset($lines[$i - 1]) && 0 === \strpos($_token, '/*') && '*/' === \substr(\trim($lines[$i - 1]), -2)) {
+                            $this->ignoredLines[$filename][] = $i;
+                        }
+                    }
+
+                    break;
+
+                case \PHP_Token_INTERFACE::class:
+                case \PHP_Token_TRAIT::class:
+                case \PHP_Token_CLASS::class:
+                case \PHP_Token_FUNCTION::class:
+                    /* @var \PHP_Token_Interface $token */
+
+                    $docblock = $token->getDocblock();
+
+                    $this->ignoredLines[$filename][] = $token->getLine();
+
+                    if (\strpos($docblock, '@codeCoverageIgnore') || ($this->ignoreDeprecatedCode && \strpos($docblock, '@deprecated'))) {
+                        $endLine = $token->getEndLine();
+
+                        for ($i = $token->getLine(); $i <= $endLine; $i++) {
+                            $this->ignoredLines[$filename][] = $i;
+                        }
+                    }
+
+                    break;
+
+                case \PHP_Token_ENUM::class:
+                    $this->ignoredLines[$filename][] = $token->getLine();
+
+                    break;
+
+                case \PHP_Token_NAMESPACE::class:
+                    $this->ignoredLines[$filename][] = $token->getEndLine();
+
+                // Intentional fallthrough
+                case \PHP_Token_DECLARE::class:
+                case \PHP_Token_OPEN_TAG::class:
+                case \PHP_Token_CLOSE_TAG::class:
+                case \PHP_Token_USE::class:
+                    $this->ignoredLines[$filename][] = $token->getLine();
+
+                    break;
+            }
+
+            if ($ignore) {
+                $this->ignoredLines[$filename][] = $token->getLine();
+
+                if ($stop) {
+                    $ignore = false;
+                    $stop   = false;
                 }
             }
+        }
 
-            if ($this->cacheTokens) {
-                $tokens = PHP_Token_Stream_CachingFactory::get($filename);
-            } else {
-                $tokens = new PHP_Token_Stream($filename);
-            }
+        $this->ignoredLines[$filename][] = \count($lines) + 1;
 
-            $classes = array_merge($tokens->getClasses(), $tokens->getTraits());
-            $tokens  = $tokens->tokens();
+        $this->ignoredLines[$filename] = \array_unique(
+            $this->ignoredLines[$filename]
+        );
 
-            foreach ($tokens as $token) {
-                switch (get_class($token)) {
-                    case 'PHP_Token_COMMENT':
-                    case 'PHP_Token_DOC_COMMENT':
-                        $_token = trim($token);
-                        $_line  = trim($lines[$token->getLine() - 1]);
-
-                        if ($_token == '// @codeCoverageIgnore' ||
-                            $_token == '//@codeCoverageIgnore') {
-                            $ignore = true;
-                            $stop   = true;
-                        } elseif ($_token == '// @codeCoverageIgnoreStart' ||
-                            $_token == '//@codeCoverageIgnoreStart') {
-                            $ignore = true;
-                        } elseif ($_token == '// @codeCoverageIgnoreEnd' ||
-                            $_token == '//@codeCoverageIgnoreEnd') {
-                            $stop = true;
-                        }
-
-                        if (!$ignore) {
-                            $start = $token->getLine();
-                            $end   = $start + substr_count($token, "\n");
-
-                            // Do not ignore the first line when there is a token
-                            // before the comment
-                            if (0 !== strpos($_token, $_line)) {
-                                $start++;
-                            }
-
-                            for ($i = $start; $i < $end; $i++) {
-                                $this->ignoredLines[$filename][] = $i;
-                            }
-
-                            // A DOC_COMMENT token or a COMMENT token starting with "/*"
-                            // does not contain the final \n character in its text
-                            if (isset($lines[$i-1]) && 0 === strpos($_token, '/*') && '*/' === substr(trim($lines[$i-1]), -2)) {
-                                $this->ignoredLines[$filename][] = $i;
-                            }
-                        }
-                        break;
-
-                    case 'PHP_Token_INTERFACE':
-                    case 'PHP_Token_TRAIT':
-                    case 'PHP_Token_CLASS':
-                    case 'PHP_Token_FUNCTION':
-                        $docblock = $token->getDocblock();
-
-                        $this->ignoredLines[$filename][] = $token->getLine();
-
-                        if (strpos($docblock, '@codeCoverageIgnore') || strpos($docblock, '@deprecated')) {
-                            $endLine = $token->getEndLine();
-
-                            for ($i = $token->getLine(); $i <= $endLine; $i++) {
-                                $this->ignoredLines[$filename][] = $i;
-                            }
-                        } elseif ($token instanceof PHP_Token_INTERFACE ||
-                            $token instanceof PHP_Token_TRAIT ||
-                            $token instanceof PHP_Token_CLASS) {
-                            if (empty($classes[$token->getName()]['methods'])) {
-                                for ($i = $token->getLine();
-                                     $i <= $token->getEndLine();
-                                     $i++) {
-                                    $this->ignoredLines[$filename][] = $i;
-                                }
-                            } else {
-                                $firstMethod = array_shift(
-                                    $classes[$token->getName()]['methods']
-                                );
-
-                                do {
-                                    $lastMethod = array_pop(
-                                        $classes[$token->getName()]['methods']
-                                    );
-                                } while ($lastMethod !== null &&
-                                    substr($lastMethod['signature'], 0, 18) == 'anonymous function');
-
-                                if ($lastMethod === null) {
-                                    $lastMethod = $firstMethod;
-                                }
-
-                                for ($i = $token->getLine();
-                                     $i < $firstMethod['startLine'];
-                                     $i++) {
-                                    $this->ignoredLines[$filename][] = $i;
-                                }
-
-                                for ($i = $token->getEndLine();
-                                     $i > $lastMethod['endLine'];
-                                     $i--) {
-                                    $this->ignoredLines[$filename][] = $i;
-                                }
-                            }
-                        }
-                        break;
-
-                    case 'PHP_Token_NAMESPACE':
-                        $this->ignoredLines[$filename][] = $token->getEndLine();
-
-                    // Intentional fallthrough
-                    case 'PHP_Token_OPEN_TAG':
-                    case 'PHP_Token_CLOSE_TAG':
-                    case 'PHP_Token_USE':
-                        $this->ignoredLines[$filename][] = $token->getLine();
-                        break;
-                }
-
-                if ($ignore) {
-                    $this->ignoredLines[$filename][] = $token->getLine();
-
-                    if ($stop) {
-                        $ignore = false;
-                        $stop   = false;
-                    }
-                }
-            }
-
-            $this->ignoredLines[$filename][] = $numLines + 1;
-
-            $this->ignoredLines[$filename] = array_unique(
-                $this->ignoredLines[$filename]
-            );
-
-            sort($this->ignoredLines[$filename]);
-        }
+        $this->ignoredLines[$filename] = array_unique($this->ignoredLines[$filename]);
+        \sort($this->ignoredLines[$filename]);
 
         return $this->ignoredLines[$filename];
     }
 
     /**
-     * @param  array                                                 $data
-     * @param  array                                                 $linesToBeCovered
-     * @param  array                                                 $linesToBeUsed
-     * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode
-     * @since Method available since Release 2.0.0
+     * @param array $data
+     * @param array $linesToBeCovered
+     * @param array $linesToBeUsed
+     *
+     * @throws \ReflectionException
+     * @throws UnintentionallyCoveredCodeException
      */
     private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed)
     {
@@ -833,64 +950,95 @@
             $linesToBeUsed
         );
 
-        $message = '';
+        $unintentionallyCoveredUnits = [];
 
         foreach ($data as $file => $_data) {
             foreach ($_data as $line => $flag) {
-                if ($flag == 1 &&
-                    (!isset($allowedLines[$file]) ||
-                        !isset($allowedLines[$file][$line]))) {
-                    $message .= sprintf(
-                        '- %s:%d' . PHP_EOL,
-                        $file,
-                        $line
-                    );
+                if ($flag === 1 && !isset($allowedLines[$file][$line])) {
+                    $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line);
                 }
             }
         }
 
-        if (!empty($message)) {
-            throw new PHP_CodeCoverage_Exception_UnintentionallyCoveredCode(
-                $message
+        $unintentionallyCoveredUnits = $this->processUnintentionallyCoveredUnits($unintentionallyCoveredUnits);
+
+        if (!empty($unintentionallyCoveredUnits)) {
+            throw new UnintentionallyCoveredCodeException(
+                $unintentionallyCoveredUnits
             );
         }
     }
 
     /**
-     * @param  array $linesToBeCovered
-     * @param  array $linesToBeUsed
+     * @param array $data
+     * @param array $linesToBeCovered
+     * @param array $linesToBeUsed
+     *
+     * @throws CoveredCodeNotExecutedException
+     */
+    private function performUnexecutedCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed)
+    {
+        $executedCodeUnits = $this->coverageToCodeUnits($data);
+        $message           = '';
+
+        foreach ($this->linesToCodeUnits($linesToBeCovered) as $codeUnit) {
+            if (!\in_array($codeUnit, $executedCodeUnits)) {
+                $message .= \sprintf(
+                    '- %s is expected to be executed (@covers) but was not executed' . "\n",
+                    $codeUnit
+                );
+            }
+        }
+
+        foreach ($this->linesToCodeUnits($linesToBeUsed) as $codeUnit) {
+            if (!\in_array($codeUnit, $executedCodeUnits)) {
+                $message .= \sprintf(
+                    '- %s is expected to be executed (@uses) but was not executed' . "\n",
+                    $codeUnit
+                );
+            }
+        }
+
+        if (!empty($message)) {
+            throw new CoveredCodeNotExecutedException($message);
+        }
+    }
+
+    /**
+     * @param array $linesToBeCovered
+     * @param array $linesToBeUsed
+     *
      * @return array
-     * @since Method available since Release 2.0.0
      */
     private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed)
     {
-        $allowedLines = array();
+        $allowedLines = [];
 
-        foreach (array_keys($linesToBeCovered) as $file) {
+        foreach (\array_keys($linesToBeCovered) as $file) {
             if (!isset($allowedLines[$file])) {
-                $allowedLines[$file] = array();
+                $allowedLines[$file] = [];
             }
 
-            $allowedLines[$file] = array_merge(
+            $allowedLines[$file] = \array_merge(
                 $allowedLines[$file],
                 $linesToBeCovered[$file]
             );
         }
 
-        foreach (array_keys($linesToBeUsed) as $file) {
+        foreach (\array_keys($linesToBeUsed) as $file) {
             if (!isset($allowedLines[$file])) {
-                $allowedLines[$file] = array();
+                $allowedLines[$file] = [];
             }
 
-            $allowedLines[$file] = array_merge(
+            $allowedLines[$file] = \array_merge(
                 $allowedLines[$file],
                 $linesToBeUsed[$file]
             );
         }
 
-        foreach (array_keys($allowedLines) as $file) {
-            $allowedLines[$file] = array_flip(
-                array_unique($allowedLines[$file])
+        foreach (\array_keys($allowedLines) as $file) {
+            $allowedLines[$file] = \array_flip(
+                \array_unique($allowedLines[$file])
             );
         }
 
@@ -898,23 +1046,139 @@
     }
 
     /**
-     * @return PHP_CodeCoverage_Driver
-     * @throws PHP_CodeCoverage_Exception
+     * @return Driver
+     *
+     * @throws RuntimeException
      */
     private function selectDriver()
     {
         $runtime = new Runtime;
 
         if (!$runtime->canCollectCodeCoverage()) {
-            throw new PHP_CodeCoverage_Exception('No code coverage driver available');
+            throw new RuntimeException('No code coverage driver available');
         }
 
         if ($runtime->isHHVM()) {
-            return new PHP_CodeCoverage_Driver_HHVM;
-        } elseif ($runtime->isPHPDBG()) {
-            return new PHP_CodeCoverage_Driver_PHPDBG;
-        } else {
-            return new PHP_CodeCoverage_Driver_Xdebug;
+            return new HHVM;
+        }
+
+        if ($runtime->isPHPDBG()) {
+            return new PHPDBG;
+        }
+
+        return new Xdebug;
+    }
+
+    /**
+     * @param array $unintentionallyCoveredUnits
+     *
+     * @return array
+     *
+     * @throws \ReflectionException
+     */
+    private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits)
+    {
+        $unintentionallyCoveredUnits = \array_unique($unintentionallyCoveredUnits);
+        \sort($unintentionallyCoveredUnits);
+
+        foreach (\array_keys($unintentionallyCoveredUnits) as $k => $v) {
+            $unit = \explode('::', $unintentionallyCoveredUnits[$k]);
+
+            if (\count($unit) !== 2) {
+                continue;
+            }
+
+            $class = new \ReflectionClass($unit[0]);
+
+            foreach ($this->unintentionallyCoveredSubclassesWhitelist as $whitelisted) {
+                if ($class->isSubclassOf($whitelisted)) {
+                    unset($unintentionallyCoveredUnits[$k]);
+
+                    break;
+                }
+            }
+        }
+
+        return \array_values($unintentionallyCoveredUnits);
+    }
+
+    /**
+     * If we are processing uncovered files from whitelist,
+     * we can initialize the data before we start to speed up the tests
+     *
+     * @throws \SebastianBergmann\CodeCoverage\RuntimeException
+     */
+    protected function initializeData()
+    {
+        $this->isInitialized = true;
+
+        if ($this->processUncoveredFilesFromWhitelist) {
+            $this->shouldCheckForDeadAndUnused = false;
+
+            $this->driver->start(true);
+
+            foreach ($this->filter->getWhitelist() as $file) {
+                if ($this->filter->isFile($file)) {
+                    include_once($file);
+                }
+            }
+
+            $data     = [];
+            $coverage = $this->driver->stop();
+
+            foreach ($coverage as $file => $fileCoverage) {
+                if ($this->filter->isFiltered($file)) {
+                    continue;
+                }
+
+                foreach (\array_keys($fileCoverage) as $key) {
+                    if ($fileCoverage[$key] === Driver::LINE_EXECUTED) {
+                        $fileCoverage[$key] = Driver::LINE_NOT_EXECUTED;
+                    }
+                }
+
+                $data[$file] = $fileCoverage;
+            }
+
+            $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
         }
     }
+
+    /**
+     * @param array $data
+     *
+     * @return array
+     */
+    private function coverageToCodeUnits(array $data)
+    {
+        $codeUnits = [];
+
+        foreach ($data as $filename => $lines) {
+            foreach ($lines as $line => $flag) {
+                if ($flag === 1) {
+                    $codeUnits[] = $this->wizard->lookup($filename, $line);
+                }
+            }
+        }
+
+        return \array_unique($codeUnits);
+    }
+
+    /**
+     * @param array $data
+     *
+     * @return array
+     */
+    private function linesToCodeUnits(array $data)
+    {
+        $codeUnits = [];
+
+        foreach ($data as $filename => $lines) {
+            foreach ($lines as $line) {
+                $codeUnits[] = $this->wizard->lookup($filename, $line);
+            }
+        }
+
+        return \array_unique($codeUnits);
+    }
 }