5 * Copyright (c) 2009-2012, Sebastian Bergmann <sb@sebastian-bergmann.de>.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
20 * * Neither the name of Sebastian Bergmann nor the names of his
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
38 * @package CodeCoverage
39 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
40 * @copyright 2009-2012 Sebastian Bergmann <sb@sebastian-bergmann.de>
41 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
42 * @link http://github.com/sebastianbergmann/php-code-coverage
43 * @since File available since Release 1.0.0
47 * Provides collection functionality for PHP code coverage information.
50 * @package CodeCoverage
51 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
52 * @copyright 2009-2012 Sebastian Bergmann <sb@sebastian-bergmann.de>
53 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
54 * @version Release: 1.1.2
55 * @link http://github.com/sebastianbergmann/php-code-coverage
56 * @since Class available since Release 1.0.0
58 class PHP_CodeCoverage
61 * @var PHP_CodeCoverage_Driver
66 * @var PHP_CodeCoverage_Filter
73 protected $cacheTokens = TRUE;
78 protected $forceCoversAnnotation = FALSE;
83 protected $mapTestClassNameToCoveredClassName = FALSE;
88 protected $processUncoveredFilesFromWhitelist = TRUE;
100 protected $data = array();
107 protected $tests = array();
112 * @param PHP_CodeCoverage_Driver $driver
113 * @param PHP_CodeCoverage_Filter $filter
114 * @throws InvalidArgumentException
116 public function __construct(PHP_CodeCoverage_Driver
$driver = NULL, PHP_CodeCoverage_Filter
$filter = NULL)
118 if ($driver === NULL) {
119 $driver = new PHP_CodeCoverage_Driver_Xdebug
;
122 if ($filter === NULL) {
123 $filter = new PHP_CodeCoverage_Filter
;
126 $this->driver
= $driver;
127 $this->filter
= $filter;
131 * Returns the PHP_CodeCoverage_Report_Node_* object graph
132 * for this PHP_CodeCoverage object.
134 * @return PHP_CodeCoverage_Report_Node_Directory
135 * @since Method available since Release 1.1.0
137 public function getReport()
139 $factory = new PHP_CodeCoverage_Report_Factory
;
141 return $factory->create($this);
145 * Clears collected code coverage data.
147 public function clear()
149 $this->currentId
= NULL;
150 $this->data
= array();
151 $this->tests
= array();
155 * Returns the PHP_CodeCoverage_Filter used.
157 * @return PHP_CodeCoverage_Filter
159 public function filter()
161 return $this->filter
;
165 * Returns the collected code coverage data.
168 * @since Method available since Release 1.1.0
170 public function getData()
172 if ($this->processUncoveredFilesFromWhitelist
) {
173 $this->processUncoveredFilesFromWhitelist();
176 // We need to apply the blacklist filter a second time
177 // when no whitelist is used.
178 if (!$this->filter
->hasWhitelist()) {
179 $this->applyListsFilter($this->data
);
186 * Returns the test data.
189 * @since Method available since Release 1.1.0
191 public function getTests()
197 * Start collection of code coverage information.
200 * @param boolean $clear
201 * @throws InvalidArgumentException
203 public function start($id, $clear = FALSE)
205 if (!is_bool($clear)) {
206 throw new InvalidArgumentException
;
213 $this->currentId
= $id;
215 $this->driver
->start();
219 * Stop collection of code coverage information.
221 * @param boolean $append
223 * @throws InvalidArgumentException
225 public function stop($append = TRUE)
227 if (!is_bool($append)) {
228 throw new InvalidArgumentException
;
231 $data = $this->driver
->stop();
232 $this->append($data, NULL, $append);
234 $this->currentId
= NULL;
240 * Appends code coverage data.
244 * @param boolean $append
246 public function append(array $data, $id = NULL, $append = TRUE)
249 $id = $this->currentId
;
253 throw new InvalidArgumentException
;
256 $this->applyListsFilter($data);
257 $this->initializeFilesThatAreSeenTheFirstTime($data);
263 if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') {
264 $this->applyCoversAnnotationFilter($data, $id);
273 if ($id instanceof PHPUnit_Framework_TestCase
) {
274 $status = $id->getStatus();
275 $id = get_class($id) . '::' . $id->getName();
278 else if ($id instanceof PHPUnit_Extensions_PhptTestCase
) {
279 $id = $id->getName();
282 $this->tests
[$id] = $status;
284 foreach ($data as $file => $lines) {
285 if (!$this->filter
->isFile($file)) {
289 foreach ($lines as $k => $v) {
291 $this->data
[$file][$k][] = $id;
298 * Merges the data from another instance of PHP_CodeCoverage.
300 * @param PHP_CodeCoverage $that
302 public function merge(PHP_CodeCoverage
$that)
304 foreach ($that->data
as $file => $lines) {
305 if (!isset($this->data
[$file])) {
306 if (!$this->filter
->isFiltered($file)) {
307 $this->data
[$file] = $lines;
313 foreach ($lines as $line => $data) {
314 if ($data !== NULL) {
315 if (!isset($this->data
[$file][$line])) {
316 $this->data
[$file][$line] = $data;
318 $this->data
[$file][$line] = array_unique(
319 array_merge($this->data
[$file][$line], $data)
326 $this->tests
= array_merge($this->tests
, $that->getTests());
330 * @param boolean $flag
331 * @throws InvalidArgumentException
332 * @since Method available since Release 1.1.0
334 public function setCacheTokens($flag)
336 if (!is_bool($flag)) {
337 throw new InvalidArgumentException
;
340 $this->cacheTokens
= $flag;
344 * @param boolean $flag
345 * @throws InvalidArgumentException
346 * @since Method available since Release 1.1.0
348 public function getCacheTokens()
350 return $this->cacheTokens
;
354 * @param boolean $flag
355 * @throws InvalidArgumentException
357 public function setForceCoversAnnotation($flag)
359 if (!is_bool($flag)) {
360 throw new InvalidArgumentException
;
363 $this->forceCoversAnnotation
= $flag;
367 * @param boolean $flag
368 * @throws InvalidArgumentException
370 public function setMapTestClassNameToCoveredClassName($flag)
372 if (!is_bool($flag)) {
373 throw new InvalidArgumentException
;
376 $this->mapTestClassNameToCoveredClassName
= $flag;
380 * @param boolean $flag
381 * @throws InvalidArgumentException
383 public function setProcessUncoveredFilesFromWhitelist($flag)
385 if (!is_bool($flag)) {
386 throw new InvalidArgumentException
;
389 $this->processUncoveredFilesFromWhitelist
= $flag;
393 * Applies the @covers annotation filtering.
398 protected function applyCoversAnnotationFilter(&$data, $id)
400 if ($id instanceof PHPUnit_Framework_TestCase
) {
401 $testClassName = get_class($id);
402 $linesToBeCovered = PHP_CodeCoverage_Util
::getLinesToBeCovered(
403 $testClassName, $id->getName()
406 if ($this->mapTestClassNameToCoveredClassName
&&
407 empty($linesToBeCovered)) {
408 $testedClass = substr($testClassName, 0, -4);
410 if (class_exists($testedClass)) {
411 $class = new ReflectionClass($testedClass);
413 $linesToBeCovered = array(
414 $class->getFileName() => range(
415 $class->getStartLine(), $class->getEndLine()
421 $linesToBeCovered = array();
424 if (!empty($linesToBeCovered)) {
425 $data = array_intersect_key($data, $linesToBeCovered);
427 foreach (array_keys($data) as $filename) {
428 $data[$filename] = array_intersect_key(
429 $data[$filename], array_flip($linesToBeCovered[$filename])
434 else if ($this->forceCoversAnnotation
) {
440 * Applies the blacklist/whitelist filtering.
444 protected function applyListsFilter(&$data)
446 foreach (array_keys($data) as $filename) {
447 if ($this->filter
->isFiltered($filename)) {
448 unset($data[$filename]);
454 * @since Method available since Release 1.1.0
456 protected function initializeFilesThatAreSeenTheFirstTime($data)
458 foreach ($data as $file => $lines) {
459 if ($this->filter
->isFile($file) && !isset($this->data
[$file])) {
460 $this->data
[$file] = array();
462 foreach ($lines as $k => $v) {
463 $this->data
[$file][$k] = $v == -2 ?
NULL : array();
470 * Processes whitelisted files that are not covered.
472 protected function processUncoveredFilesFromWhitelist()
475 $uncoveredFiles = array_diff(
476 $this->filter
->getWhitelist(), array_keys($this->data
)
479 foreach ($uncoveredFiles as $uncoveredFile) {
480 if (!file_exists($uncoveredFile)) {
484 if ($this->cacheTokens
) {
485 $tokens = PHP_Token_Stream_CachingFactory
::get($uncoveredFile);
487 $tokens = new PHP_Token_Stream($uncoveredFile);
490 $classes = $tokens->getClasses();
491 $interfaces = $tokens->getInterfaces();
492 $functions = $tokens->getFunctions();
495 foreach (array_keys($classes) as $class) {
496 if (class_exists($class, FALSE)) {
503 foreach (array_keys($interfaces) as $interface) {
504 if (interface_exists($interface, FALSE)) {
511 foreach (array_keys($functions) as $function) {
512 if (function_exists($function)) {
519 $this->driver
->start();
520 include_once $uncoveredFile;
521 $coverage = $this->driver
->stop();
523 foreach ($coverage as $file => $fileCoverage) {
524 if (!isset($data[$file]) &&
525 in_array($file, $uncoveredFiles)) {
526 foreach (array_keys($fileCoverage) as $key) {
527 if ($fileCoverage[$key] == 1) {
528 $fileCoverage[$key] = -1;
532 $data[$file] = $fileCoverage;
537 $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');