class PHP_CodeCoverage

Provides collection functionality for PHP code coverage information.

@category PHP @package CodeCoverage @author Sebastian Bergmann <sebastian@phpunit.de> @copyright 2009-2013 Sebastian Bergmann <sebastian@phpunit.de> @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License @link http://github.com/sebastianbergmann/php-code-coverage @since Class available since Release 1.0.0

Hierarchy

Expanded class hierarchy of PHP_CodeCoverage

4 string references to 'PHP_CodeCoverage'
PHPUnit_Util_GlobalState::backupStaticAttributes in drupal/core/vendor/phpunit/phpunit/PHPUnit/Util/GlobalState.php
PHPUnit_Util_GlobalState::phpunitFiles in drupal/core/vendor/phpunit/phpunit/PHPUnit/Util/GlobalState.php
@since Method available since Release 3.6.0
PHP_CodeCoverageTest::setUp in drupal/core/vendor/phpunit/php-code-coverage/Tests/PHP/CodeCoverageTest.php
Sets up the fixture, for example, open a network connection. This method is called before a test is executed.
PHP_CodeCoverage_Filter::prefillBlacklist in drupal/core/vendor/phpunit/php-code-coverage/PHP/CodeCoverage/Filter.php
@since Method available since Release 1.2.3

File

drupal/core/vendor/phpunit/php-code-coverage/PHP/CodeCoverage.php, line 71

View source
class PHP_CodeCoverage {

  /**
   * @var PHP_CodeCoverage_Driver
   */
  protected $driver;

  /**
   * @var PHP_CodeCoverage_Filter
   */
  protected $filter;

  /**
   * @var boolean
   */
  protected $cacheTokens = FALSE;

  /**
   * @var boolean
   */
  protected $forceCoversAnnotation = FALSE;

  /**
   * @var boolean
   */
  protected $mapTestClassNameToCoveredClassName = FALSE;

  /**
   * @var boolean
   */
  protected $addUncoveredFilesFromWhitelist = TRUE;

  /**
   * @var boolean
   */
  protected $processUncoveredFilesFromWhitelist = FALSE;

  /**
   * @var mixed
   */
  protected $currentId;

  /**
   * Code coverage data.
   *
   * @var array
   */
  protected $data = array();

  /**
   * Test data.
   *
   * @var array
   */
  protected $tests = array();

  /**
   * Constructor.
   *
   * @param PHP_CodeCoverage_Driver $driver
   * @param PHP_CodeCoverage_Filter $filter
   */
  public function __construct(PHP_CodeCoverage_Driver $driver = NULL, PHP_CodeCoverage_Filter $filter = NULL) {
    if ($driver === NULL) {
      $driver = new PHP_CodeCoverage_Driver_Xdebug();
    }
    if ($filter === NULL) {
      $filter = new PHP_CodeCoverage_Filter();
    }
    $this->driver = $driver;
    $this->filter = $filter;
  }

  /**
   * Returns the PHP_CodeCoverage_Report_Node_* object graph
   * for this PHP_CodeCoverage object.
   *
   * @return PHP_CodeCoverage_Report_Node_Directory
   * @since  Method available since Release 1.1.0
   */
  public function getReport() {
    $factory = new PHP_CodeCoverage_Report_Factory();
    return $factory
      ->create($this);
  }

  /**
   * Clears collected code coverage data.
   */
  public function clear() {
    $this->currentId = NULL;
    $this->data = array();
    $this->tests = array();
  }

  /**
   * Returns the PHP_CodeCoverage_Filter used.
   *
   * @return PHP_CodeCoverage_Filter
   */
  public function filter() {
    return $this->filter;
  }

  /**
   * Returns the collected code coverage data.
   *
   * @return array
   * @since  Method available since Release 1.1.0
   */
  public function getData() {
    if ($this->addUncoveredFilesFromWhitelist) {
      $this
        ->addUncoveredFilesFromWhitelist();
    }

    // We need to apply the blacklist filter a second time
    // when no whitelist is used.
    if (!$this->filter
      ->hasWhitelist()) {
      $this
        ->applyListsFilter($this->data);
    }
    return $this->data;
  }

  /**
   * Returns the test data.
   *
   * @return array
   * @since  Method available since Release 1.1.0
   */
  public function getTests() {
    return $this->tests;
  }

  /**
   * Start collection of code coverage information.
   *
   * @param  mixed   $id
   * @param  boolean $clear
   * @throws PHP_CodeCoverage_Exception
   */
  public function start($id, $clear = FALSE) {
    if (!is_bool($clear)) {
      throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(1, 'boolean');
    }
    if ($clear) {
      $this
        ->clear();
    }
    $this->currentId = $id;
    $this->driver
      ->start();
  }

  /**
   * Stop collection of code coverage information.
   *
   * @param  boolean $append
   * @return array
   * @throws PHP_CodeCoverage_Exception
   */
  public function stop($append = TRUE) {
    if (!is_bool($append)) {
      throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(1, 'boolean');
    }
    $data = $this->driver
      ->stop();
    $this
      ->append($data, NULL, $append);
    $this->currentId = NULL;
    return $data;
  }

  /**
   * Appends code coverage data.
   *
   * @param array   $data
   * @param mixed   $id
   * @param boolean $append
   */
  public function append(array $data, $id = NULL, $append = TRUE) {
    if ($id === NULL) {
      $id = $this->currentId;
    }
    if ($id === NULL) {
      throw new PHP_CodeCoverage_Exception();
    }
    $this
      ->applyListsFilter($data);
    $this
      ->initializeFilesThatAreSeenTheFirstTime($data);
    if (!$append) {
      return;
    }
    if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') {
      $this
        ->applyCoversAnnotationFilter($data, $id);
    }
    if (empty($data)) {
      return;
    }
    $status = NULL;
    if ($id instanceof PHPUnit_Framework_TestCase) {
      $status = $id
        ->getStatus();
      $id = get_class($id) . '::' . $id
        ->getName();
    }
    else {
      if ($id instanceof PHPUnit_Extensions_PhptTestCase) {
        $id = $id
          ->getName();
      }
    }
    $this->tests[$id] = $status;
    foreach ($data as $file => $lines) {
      if (!$this->filter
        ->isFile($file)) {
        continue;
      }
      foreach ($lines as $k => $v) {
        if ($v == 1) {
          $this->data[$file][$k][] = $id;
        }
      }
    }
  }

  /**
   * Merges the data from another instance of PHP_CodeCoverage.
   *
   * @param PHP_CodeCoverage $that
   */
  public function merge(PHP_CodeCoverage $that) {
    foreach ($that->data as $file => $lines) {
      if (!isset($this->data[$file])) {
        if (!$this->filter
          ->isFiltered($file)) {
          $this->data[$file] = $lines;
        }
        continue;
      }
      foreach ($lines as $line => $data) {
        if ($data !== NULL) {
          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->tests = array_merge($this->tests, $that
      ->getTests());
  }

  /**
   * @param  boolean $flag
   * @throws PHP_CodeCoverage_Exception
   * @since  Method available since Release 1.1.0
   */
  public function setCacheTokens($flag) {
    if (!is_bool($flag)) {
      throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(1, 'boolean');
    }
    $this->cacheTokens = $flag;
  }

  /**
   * @param boolean $flag
   * @since Method available since Release 1.1.0
   */
  public function getCacheTokens() {
    return $this->cacheTokens;
  }

  /**
   * @param  boolean $flag
   * @throws PHP_CodeCoverage_Exception
   */
  public function setForceCoversAnnotation($flag) {
    if (!is_bool($flag)) {
      throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(1, 'boolean');
    }
    $this->forceCoversAnnotation = $flag;
  }

  /**
   * @param  boolean $flag
   * @throws PHP_CodeCoverage_Exception
   */
  public function setMapTestClassNameToCoveredClassName($flag) {
    if (!is_bool($flag)) {
      throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(1, 'boolean');
    }
    $this->mapTestClassNameToCoveredClassName = $flag;
  }

  /**
   * @param  boolean $flag
   * @throws PHP_CodeCoverage_Exception
   */
  public function setAddUncoveredFilesFromWhitelist($flag) {
    if (!is_bool($flag)) {
      throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(1, 'boolean');
    }
    $this->addUncoveredFilesFromWhitelist = $flag;
  }

  /**
   * @param  boolean $flag
   * @throws PHP_CodeCoverage_Exception
   */
  public function setProcessUncoveredFilesFromWhitelist($flag) {
    if (!is_bool($flag)) {
      throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(1, 'boolean');
    }
    $this->processUncoveredFilesFromWhitelist = $flag;
  }

  /**
   * Applies the @covers annotation filtering.
   *
   * @param array $data
   * @param mixed $id
   */
  protected function applyCoversAnnotationFilter(&$data, $id) {
    if ($id instanceof PHPUnit_Framework_TestCase) {
      $testClassName = get_class($id);
      $linesToBeCovered = $this
        ->getLinesToBeCovered($testClassName, $id
        ->getName());
      if ($linesToBeCovered === FALSE) {
        $data = array();
        return;
      }
      if ($this->mapTestClassNameToCoveredClassName && empty($linesToBeCovered)) {
        $testedClass = substr($testClassName, 0, -4);
        if (class_exists($testedClass)) {
          $class = new ReflectionClass($testedClass);
          $linesToBeCovered = array(
            $class
              ->getFileName() => range($class
              ->getStartLine(), $class
              ->getEndLine()),
          );
        }
      }
    }
    else {
      $linesToBeCovered = array();
    }
    if (!empty($linesToBeCovered)) {
      $data = array_intersect_key($data, $linesToBeCovered);
      foreach (array_keys($data) as $filename) {
        $data[$filename] = array_intersect_key($data[$filename], array_flip($linesToBeCovered[$filename]));
      }
    }
    else {
      if ($this->forceCoversAnnotation) {
        $data = array();
      }
    }
  }

  /**
   * Applies the blacklist/whitelist filtering.
   *
   * @param array $data
   */
  protected function applyListsFilter(&$data) {
    foreach (array_keys($data) as $filename) {
      if ($this->filter
        ->isFiltered($filename)) {
        unset($data[$filename]);
      }
    }
  }

  /**
   * @since Method available since Release 1.1.0
   */
  protected function initializeFilesThatAreSeenTheFirstTime($data) {
    foreach ($data as $file => $lines) {
      if ($this->filter
        ->isFile($file) && !isset($this->data[$file])) {
        $this->data[$file] = array();
        foreach ($lines as $k => $v) {
          $this->data[$file][$k] = $v == -2 ? NULL : array();
        }
      }
    }
  }

  /**
   * Processes whitelisted files that are not covered.
   */
  protected function addUncoveredFilesFromWhitelist() {
    $data = array();
    $uncoveredFiles = array_diff($this->filter
      ->getWhitelist(), array_keys($this->data));
    foreach ($uncoveredFiles as $uncoveredFile) {
      if (!file_exists($uncoveredFile)) {
        continue;
      }
      if ($this->processUncoveredFilesFromWhitelist) {
        $this
          ->processUncoveredFileFromWhitelist($uncoveredFile, $data, $uncoveredFiles);
      }
      else {
        $data[$uncoveredFile] = array();
        $lines = count(file($uncoveredFile));
        for ($i = 1; $i <= $lines; $i++) {
          $data[$uncoveredFile][$i] = -1;
        }
      }
    }
    $this
      ->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
  }

  /**
   * @param string $uncoveredFile
   * @param array  $data
   * @param array  $uncoveredFiles
   */
  protected 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] == 1) {
            $fileCoverage[$key] = -1;
          }
        }
        $data[$file] = $fileCoverage;
      }
    }
  }

  /**
   * Returns the files and lines a test method wants to cover.
   *
   * @param  string $className
   * @param  string $methodName
   * @return array
   * @since  Method available since Release 1.2.0
   */
  protected function getLinesToBeCovered($className, $methodName) {
    $codeToCoverList = array();
    $result = array();

    // @codeCoverageIgnoreStart
    if (($pos = strpos($methodName, ' ')) !== FALSE) {
      $methodName = substr($methodName, 0, $pos);
    }

    // @codeCoverageIgnoreEnd
    $class = new ReflectionClass($className);
    try {
      $method = new ReflectionMethod($className, $methodName);
    } catch (ReflectionException $e) {
      return array();
    }
    $docComment = substr($class
      ->getDocComment(), 3, -2) . PHP_EOL . substr($method
      ->getDocComment(), 3, -2);
    $templateMethods = array(
      'setUp',
      'assertPreConditions',
      'assertPostConditions',
      'tearDown',
    );
    foreach ($templateMethods as $templateMethod) {
      if ($class
        ->hasMethod($templateMethod)) {
        $reflector = $class
          ->getMethod($templateMethod);
        $docComment .= PHP_EOL . substr($reflector
          ->getDocComment(), 3, -2);
        unset($reflector);
      }
    }
    if (strpos($docComment, '@coversNothing') !== FALSE) {
      return FALSE;
    }
    $classShortcut = preg_match_all('(@coversDefaultClass\\s+(?P<coveredClass>[^\\s]++)\\s*$)m', $class
      ->getDocComment(), $matches);
    if ($classShortcut) {
      if ($classShortcut > 1) {
        throw new PHP_CodeCoverage_Exception(sprintf('More than one @coversClass annotation in class or interface "%s".', $className));
      }
      $classShortcut = $matches['coveredClass'][0];
    }
    $match = preg_match_all('(@covers\\s+(?P<coveredElement>[^\\s()]++)[\\s()]*$)m', $docComment, $matches);
    if ($match) {
      foreach ($matches['coveredElement'] as $coveredElement) {
        if ($classShortcut && strncmp($coveredElement, '::', 2) === 0) {
          $coveredElement = $classShortcut . $coveredElement;
        }
        $codeToCoverList = array_merge($codeToCoverList, $this
          ->resolveCoversToReflectionObjects($coveredElement));
      }
      foreach ($codeToCoverList as $codeToCover) {
        $fileName = $codeToCover
          ->getFileName();
        if (!isset($result[$fileName])) {
          $result[$fileName] = array();
        }
        $result[$fileName] = array_unique(array_merge($result[$fileName], range($codeToCover
          ->getStartLine(), $codeToCover
          ->getEndLine())));
      }
    }
    return $result;
  }

  /**
   * @param  string $coveredElement
   * @return array
   * @since  Method available since Release 1.2.0
   */
  protected function resolveCoversToReflectionObjects($coveredElement) {
    $codeToCoverList = array();
    if (strpos($coveredElement, '::') !== FALSE) {
      list($className, $methodName) = explode('::', $coveredElement);
      if (isset($methodName[0]) && $methodName[0] == '<') {
        $classes = array(
          $className,
        );
        foreach ($classes as $className) {
          if (!class_exists($className) && !interface_exists($className)) {
            throw new PHP_CodeCoverage_Exception(sprintf('Trying to @cover not existing class or ' . 'interface "%s".', $className));
          }
          $class = new ReflectionClass($className);
          $methods = $class
            ->getMethods();
          $inverse = isset($methodName[1]) && $methodName[1] == '!';
          if (strpos($methodName, 'protected')) {
            $visibility = 'isProtected';
          }
          else {
            if (strpos($methodName, 'private')) {
              $visibility = 'isPrivate';
            }
            else {
              if (strpos($methodName, 'public')) {
                $visibility = 'isPublic';
              }
            }
          }
          foreach ($methods as $method) {
            if ($inverse && !$method
              ->{$visibility}()) {
              $codeToCoverList[] = $method;
            }
            else {
              if (!$inverse && $method
                ->{$visibility}()) {
                $codeToCoverList[] = $method;
              }
            }
          }
        }
      }
      else {
        $classes = array(
          $className,
        );
        foreach ($classes as $className) {
          if ($className == '' && function_exists($methodName)) {
            $codeToCoverList[] = new ReflectionFunction($methodName);
          }
          else {
            if (!((class_exists($className) || interface_exists($className) || trait_exists($className)) && method_exists($className, $methodName))) {
              throw new PHP_CodeCoverage_Exception(sprintf('Trying to @cover not existing method "%s::%s".', $className, $methodName));
            }
            $codeToCoverList[] = new ReflectionMethod($className, $methodName);
          }
        }
      }
    }
    else {
      $extended = FALSE;
      if (strpos($coveredElement, '<extended>') !== FALSE) {
        $coveredElement = str_replace('<extended>', '', $coveredElement);
        $extended = TRUE;
      }
      $classes = array(
        $coveredElement,
      );
      if ($extended) {
        $classes = array_merge($classes, class_implements($coveredElement), class_parents($coveredElement));
      }
      foreach ($classes as $className) {
        if (!class_exists($className) && !interface_exists($className) && !trait_exists($className)) {
          throw new PHP_CodeCoverage_Exception(sprintf('Trying to @cover not existing class or ' . 'interface "%s".', $className));
        }
        $codeToCoverList[] = new ReflectionClass($className);
      }
    }
    return $codeToCoverList;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
PHP_CodeCoverage::$addUncoveredFilesFromWhitelist protected property
PHP_CodeCoverage::$cacheTokens protected property
PHP_CodeCoverage::$currentId protected property
PHP_CodeCoverage::$data protected property Code coverage data.
PHP_CodeCoverage::$driver protected property
PHP_CodeCoverage::$filter protected property
PHP_CodeCoverage::$forceCoversAnnotation protected property
PHP_CodeCoverage::$mapTestClassNameToCoveredClassName protected property
PHP_CodeCoverage::$processUncoveredFilesFromWhitelist protected property
PHP_CodeCoverage::$tests protected property Test data.
PHP_CodeCoverage::addUncoveredFilesFromWhitelist protected function Processes whitelisted files that are not covered.
PHP_CodeCoverage::append public function Appends code coverage data.
PHP_CodeCoverage::applyCoversAnnotationFilter protected function Applies the @covers annotation filtering.
PHP_CodeCoverage::applyListsFilter protected function Applies the blacklist/whitelist filtering.
PHP_CodeCoverage::clear public function Clears collected code coverage data.
PHP_CodeCoverage::filter public function Returns the PHP_CodeCoverage_Filter used.
PHP_CodeCoverage::getCacheTokens public function @since Method available since Release 1.1.0
PHP_CodeCoverage::getData public function Returns the collected code coverage data.
PHP_CodeCoverage::getLinesToBeCovered protected function Returns the files and lines a test method wants to cover.
PHP_CodeCoverage::getReport public function Returns the PHP_CodeCoverage_Report_Node_* object graph for this PHP_CodeCoverage object.
PHP_CodeCoverage::getTests public function Returns the test data.
PHP_CodeCoverage::initializeFilesThatAreSeenTheFirstTime protected function @since Method available since Release 1.1.0
PHP_CodeCoverage::merge public function Merges the data from another instance of PHP_CodeCoverage.
PHP_CodeCoverage::processUncoveredFileFromWhitelist protected function
PHP_CodeCoverage::resolveCoversToReflectionObjects protected function @since Method available since Release 1.2.0
PHP_CodeCoverage::setAddUncoveredFilesFromWhitelist public function
PHP_CodeCoverage::setCacheTokens public function @since Method available since Release 1.1.0
PHP_CodeCoverage::setForceCoversAnnotation public function
PHP_CodeCoverage::setMapTestClassNameToCoveredClassName public function
PHP_CodeCoverage::setProcessUncoveredFilesFromWhitelist public function
PHP_CodeCoverage::start public function Start collection of code coverage information.
PHP_CodeCoverage::stop public function Stop collection of code coverage information.
PHP_CodeCoverage::__construct public function Constructor.