/script/lib/PHP/CodeCoverage.php
PHP | 664 lines | 376 code | 82 blank | 206 comment | 71 complexity | 7cdf5d5087a17d7adbc27162ab7c33fa MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, LGPL-2.1, LGPL-3.0, GPL-3.0, MIT
- <?php
- /**
- * PHP_CodeCoverage
- *
- * Copyright (c) 2009-2011, Sebastian Bergmann <sb@sebastian-bergmann.de>.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * * Neither the name of Sebastian Bergmann nor the names of his
- * contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
- * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @category PHP
- * @package CodeCoverage
- * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
- * @copyright 2009-2011 Sebastian Bergmann <sb@sebastian-bergmann.de>
- * @license http://www.opensource.org/licenses/bsd-license.php BSD License
- * @link http://github.com/sebastianbergmann/php-code-coverage
- * @since File available since Release 1.0.0
- */
-
- require_once 'PHP/CodeCoverage/Driver/Xdebug.php';
- require_once 'PHP/CodeCoverage/Filter.php';
- require_once 'PHP/CodeCoverage/Util.php';
-
- /**
- * Provides collection functionality for PHP code coverage information.
- *
- * @category PHP
- * @package CodeCoverage
- * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
- * @copyright 2009-2011 Sebastian Bergmann <sb@sebastian-bergmann.de>
- * @license http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version Release: 1.0.3
- * @link http://github.com/sebastianbergmann/php-code-coverage
- * @since Class available since Release 1.0.0
- */
- class PHP_CodeCoverage
- {
- /**
- * @var PHP_CodeCoverage_Driver
- */
- protected $driver;
-
- /**
- * @var PHP_CodeCoverage_Filter
- */
- protected $filter;
-
- /**
- * @var boolean
- */
- protected $forceCoversAnnotation = FALSE;
-
- /**
- * @var boolean
- */
- protected $mapTestClassNameToCoveredClassName = FALSE;
-
- /**
- * @var boolean
- */
- protected $processUncoveredFilesFromWhitelist = TRUE;
-
- /**
- * @var mixed
- */
- protected $currentId;
-
- /**
- * List of covered files.
- *
- * @var array
- */
- protected $coveredFiles = array();
-
- /**
- * Raw code coverage data.
- *
- * @var array
- */
- protected $data = array();
-
- /**
- * Summarized code coverage data.
- *
- * @var array
- */
- protected $summary = array();
-
- /**
- * Test data.
- *
- * @var array
- */
- protected $tests = array();
-
- /**
- * @var boolean
- */
- protected $isCodeCoverageTestSuite = FALSE;
-
- /**
- * @var boolean
- */
- protected $isFileIteratorTestSuite = FALSE;
-
- /**
- * @var boolean
- */
- protected $isTimerTestSuite = FALSE;
-
- /**
- * @var boolean
- */
- protected $isTokenStreamTestSuite = FALSE;
-
- /**
- * Default PHP_CodeCoverage object.
- *
- * @var PHP_CodeCoverage
- */
- protected static $instance;
-
- /**
- * Constructor.
- *
- * @param PHP_CodeCoverage_Driver $driver
- * @param PHP_CodeCoverage_Filter $filter
- * @throws InvalidArgumentException
- */
- 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 = PHP_CodeCoverage_Filter :: getInstance();
- }
-
- $this->driver = $driver;
- $this->filter = $filter;
-
- if (defined('PHP_CODECOVERAGE_TESTSUITE'))
- {
- $this->isCodeCoverageTestSuite = TRUE;
- }
-
- if (defined('FILE_ITERATOR_TESTSUITE'))
- {
- $this->isFileIteratorTestSuite = TRUE;
- }
-
- if (defined('PHP_TIMER_TESTSUITE'))
- {
- $this->isTimerTestSuite = TRUE;
- }
-
- if (defined('PHP_TOKENSTREAM_TESTSUITE'))
- {
- $this->isTokenStreamTestSuite = TRUE;
- }
- }
-
- /**
- * Returns the default instance.
- *
- * @return PHP_CodeCoverage
- */
- public static function getInstance()
- {
- if (self :: $instance === NULL)
- {
- // @codeCoverageIgnoreStart
- self :: $instance = new PHP_CodeCoverage();
- }
- // @codeCoverageIgnoreEnd
- return self :: $instance;
- }
-
- /**
- * Start collection of code coverage information.
- *
- * @param mixed $id
- * @param boolean $clear
- * @throws InvalidArgumentException
- */
- public function start($id, $clear = FALSE)
- {
- if (! is_bool($clear))
- {
- throw new InvalidArgumentException();
- }
-
- if ($clear)
- {
- $this->clear();
- }
-
- $this->currentId = $id;
-
- $this->driver->start();
- }
-
- /**
- * Stop collection of code coverage information.
- *
- * @param boolean $append
- * @return array
- * @throws InvalidArgumentException
- */
- public function stop($append = TRUE)
- {
- if (! is_bool($append))
- {
- throw new InvalidArgumentException();
- }
-
- $data = $this->driver->stop();
-
- if ($append)
- {
- $this->append($data);
- }
-
- $this->currentId = NULL;
-
- return $data;
- }
-
- /**
- * Appends code coverage data.
- *
- * @param array $data
- * @param mixed $id
- * @param array $filterGroups
- */
- public function append(array $data, $id = NULL, array $filterGroups = array('DEFAULT'))
- {
- if ($id === NULL)
- {
- $id = $this->currentId;
- }
-
- if ($id === NULL)
- {
- throw new InvalidArgumentException();
- }
-
- $this->applySelfFilter($data);
- $this->applyListsFilter($data, $filterGroups);
- $raw = $data;
- $this->applyCoversAnnotationFilter($data, $id);
-
- if (! empty($data))
- {
- if ($id instanceof PHPUnit_Framework_TestCase)
- {
- $status = $id->getStatus();
- $id = get_class($id) . '::' . $id->getName();
- $this->tests[$id] = $status;
- }
-
- else
- if ($id instanceof PHPUnit_Extensions_PhptTestCase)
- {
- $id = $id->getName();
- }
-
- $this->coveredFiles = array_unique(array_merge($this->coveredFiles, array_keys($data)));
-
- $this->data[$id] = array('filtered' => $data, 'raw' => $raw);
- $this->summary = array();
- }
- }
-
- /**
- * Merges the data from another instance of PHP_CodeCoverage.
- *
- * @param PHP_CodeCoverage $that
- */
- public function merge(PHP_CodeCoverage $that)
- {
- foreach ($that->data as $id => $data)
- {
- if (! isset($this->data[$id]))
- {
- $this->data[$id] = $data;
- }
- else
- {
- foreach (array('filtered', 'raw') as $type)
- {
- foreach ($data[$type] as $file => $lines)
- {
- if (! isset($this->data[$id][$type][$file]))
- {
- $this->data[$id][$type][$file] = $lines;
- }
- else
- {
- foreach ($lines as $line => $flag)
- {
- if (! isset($this->data[$id][$type][$file][$line]) || $flag > $this->data[$id][$type][$file][$line])
- {
- $this->data[$id][$type][$file][$line] = $flag;
- }
- }
- }
- }
- }
- }
- }
-
- foreach ($that->tests as $id => $status)
- {
- if (! isset($this->tests[$id]) || $status > $this->tests[$id])
- {
- $this->tests[$id] = $status;
- }
- }
-
- $this->coveredFiles = array_unique(array_merge($this->coveredFiles, $that->coveredFiles));
-
- $this->summary = array();
- }
-
- /**
- * Returns summarized code coverage data.
- *
- * Format of the result array:
- *
- * <code>
- * array(
- * "/tested/code.php" => array(
- * linenumber => array(tests that executed the line)
- * )
- * )
- * </code>
- *
- * @return array
- */
- public function getSummary()
- {
- if (empty($this->summary))
- {
- if ($this->processUncoveredFilesFromWhitelist)
- {
- $this->processUncoveredFilesFromWhitelist();
- }
-
- foreach ($this->data as $test => $coverage)
- {
- foreach ($coverage['filtered'] as $file => $lines)
- {
- foreach ($lines as $line => $flag)
- {
- if ($flag == 1)
- {
- if (! isset($this->summary[$file][$line][0]))
- {
- $this->summary[$file][$line] = array();
- }
-
- if (isset($this->tests[$test]))
- {
- $status = $this->tests[$test];
- }
- else
- {
- $status = NULL;
- }
-
- $this->summary[$file][$line][] = array('id' => $test, 'status' => $status);
- }
- }
- }
-
- foreach ($coverage['raw'] as $file => $lines)
- {
- foreach ($lines as $line => $flag)
- {
- if ($flag != 1 && ! isset($this->summary[$file][$line][0]))
- {
- $this->summary[$file][$line] = $flag;
- }
- }
- }
- }
-
- foreach ($this->summary as &$file)
- {
- ksort($file);
- }
-
- ksort($this->summary);
- }
-
- return $this->summary;
- }
-
- /**
- * Clears collected code coverage data.
- */
- public function clear()
- {
- $this->data = array();
- $this->coveredFiles = array();
- $this->summary = array();
- $this->currentId = NULL;
- }
-
- /**
- * Returns the PHP_CodeCoverage_Filter used.
- *
- * @return PHP_CodeCoverage_Filter
- */
- public function filter()
- {
- return $this->filter;
- }
-
- /**
- * @param boolean $flag
- * @throws InvalidArgumentException
- */
- public function setForceCoversAnnotation($flag)
- {
- if (! is_bool($flag))
- {
- throw new InvalidArgumentException();
- }
-
- $this->forceCoversAnnotation = $flag;
- }
-
- /**
- * @param boolean $flag
- * @throws InvalidArgumentException
- */
- public function setMapTestClassNameToCoveredClassName($flag)
- {
- if (! is_bool($flag))
- {
- throw new InvalidArgumentException();
- }
-
- $this->mapTestClassNameToCoveredClassName = $flag;
- }
-
- /**
- * @param boolean $flag
- * @throws InvalidArgumentException
- */
- public function setProcessUncoveredFilesFromWhitelist($flag)
- {
- if (! is_bool($flag))
- {
- throw new InvalidArgumentException();
- }
-
- $this->processUncoveredFilesFromWhitelist = $flag;
- }
-
- /**
- * Filters sourcecode files from PHP_CodeCoverage, PHP_TokenStream,
- * Text_Template, and File_Iterator.
- *
- * @param array $data
- */
- protected function applySelfFilter(&$data)
- {
- foreach (array_keys($data) as $filename)
- {
- if (! $this->filter->isFile($filename))
- {
- unset($data[$filename]);
- continue;
- }
-
- if (! $this->isCodeCoverageTestSuite && strpos($filename, dirname(__FILE__)) === 0)
- {
- unset($data[$filename]);
- continue;
- }
-
- if (! $this->isFileIteratorTestSuite && (substr($filename, - 17) == 'File/Iterator.php' || substr($filename, - 25) == 'File/Iterator/Factory.php'))
- {
- unset($data[$filename]);
- continue;
- }
-
- if (! $this->isTimerTestSuite && (substr($filename, - 13) == 'PHP/Timer.php'))
- {
- unset($data[$filename]);
- continue;
- }
-
- if (! $this->isTokenStreamTestSuite && (substr($filename, - 13) == 'PHP/Token.php' || substr($filename, - 20) == 'PHP/Token/Stream.php' || substr($filename, - 35) == 'PHP/Token/Stream/CachingFactory.php'))
- {
- unset($data[$filename]);
- continue;
- }
-
- if (substr($filename, - 17) == 'Text/Template.php')
- {
- unset($data[$filename]);
- }
- }
- }
-
- /**
- * Applies the blacklist/whitelist filtering.
- *
- * @param array $data
- * @param array $filterGroups
- */
- protected function applyListsFilter(&$data, $filterGroups)
- {
- foreach (array_keys($data) as $filename)
- {
- if ($this->filter->isFiltered($filename, $filterGroups))
- {
- unset($data[$filename]);
- }
- }
- }
-
- /**
- * 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 = PHP_CodeCoverage_Util :: getLinesToBeCovered($testClassName, $id->getName());
-
- 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();
- }
- }
-
- /**
- * Processes whitelisted files that are not covered.
- */
- protected function processUncoveredFilesFromWhitelist()
- {
- $data = array();
- $includedFiles = array_flip(get_included_files());
- $uncoveredFiles = array_diff($this->filter->getWhitelist(), $this->coveredFiles);
-
- foreach ($uncoveredFiles as $uncoveredFile)
- {
- if (isset($includedFiles[$uncoveredFile]))
- {
- foreach (array_keys($this->data) as $test)
- {
- if (isset($this->data[$test]['raw'][$uncoveredFile]))
- {
- $coverage = $this->data[$test]['raw'][$uncoveredFile];
-
- foreach (array_keys($coverage) as $key)
- {
- if ($coverage[$key] == 1)
- {
- $coverage[$key] = - 1;
- }
- }
-
- $data[$uncoveredFile] = $coverage;
-
- break;
- }
- }
- }
- else
- {
- $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;
- $includedFiles[$file] = TRUE;
- }
- }
- }
- }
-
- $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
- }
- }