]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - tests/PHPUnit/PHP/CodeCoverage.php
Release 6.2.0
[Github/sugarcrm.git] / tests / PHPUnit / PHP / CodeCoverage.php
1 <?php
2 /**
3  * PHP_CodeCoverage
4  *
5  * Copyright (c) 2009-2011, Sebastian Bergmann <sb@sebastian-bergmann.de>.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  *   * Redistributions of source code must retain the above copyright
13  *     notice, this list of conditions and the following disclaimer.
14  *
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
18  *     distribution.
19  *
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.
23  *
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.
36  *
37  * @category   PHP
38  * @package    CodeCoverage
39  * @author     Sebastian Bergmann <sb@sebastian-bergmann.de>
40  * @copyright  2009-2011 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
44  */
45
46 require_once 'PHP/CodeCoverage/Driver/Xdebug.php';
47 require_once 'PHP/CodeCoverage/Filter.php';
48 require_once 'PHP/CodeCoverage/Util.php';
49
50 /**
51  * Provides collection functionality for PHP code coverage information.
52  *
53  * @category   PHP
54  * @package    CodeCoverage
55  * @author     Sebastian Bergmann <sb@sebastian-bergmann.de>
56  * @copyright  2009-2011 Sebastian Bergmann <sb@sebastian-bergmann.de>
57  * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
58  * @version    Release: 1.0.4
59  * @link       http://github.com/sebastianbergmann/php-code-coverage
60  * @since      Class available since Release 1.0.0
61  */
62 class PHP_CodeCoverage
63 {
64     /**
65      * @var PHP_CodeCoverage_Driver
66      */
67     protected $driver;
68
69     /**
70      * @var PHP_CodeCoverage_Filter
71      */
72     protected $filter;
73
74     /**
75      * @var boolean
76      */
77     protected $forceCoversAnnotation = FALSE;
78
79     /**
80      * @var boolean
81      */
82     protected $mapTestClassNameToCoveredClassName = FALSE;
83
84     /**
85      * @var boolean
86      */
87     protected $processUncoveredFilesFromWhitelist = TRUE;
88
89     /**
90      * @var mixed
91      */
92     protected $currentId;
93
94     /**
95      * List of covered files.
96      *
97      * @var array
98      */
99     protected $coveredFiles = array();
100
101     /**
102      * Raw code coverage data.
103      *
104      * @var array
105      */
106     protected $data = array();
107
108     /**
109      * Summarized code coverage data.
110      *
111      * @var array
112      */
113     protected $summary = array();
114
115     /**
116      * Test data.
117      *
118      * @var array
119      */
120     protected $tests = array();
121
122     /**
123      * @var boolean
124      */
125     protected $isCodeCoverageTestSuite = FALSE;
126
127     /**
128      * @var boolean
129      */
130     protected $isFileIteratorTestSuite = FALSE;
131
132     /**
133      * @var boolean
134      */
135     protected $isTimerTestSuite = FALSE;
136
137     /**
138      * @var boolean
139      */
140     protected $isTokenStreamTestSuite = FALSE;
141
142     /**
143      * Default PHP_CodeCoverage object.
144      *
145      * @var PHP_CodeCoverage
146      */
147     protected static $instance;
148
149     /**
150      * Constructor.
151      *
152      * @param  PHP_CodeCoverage_Driver $driver
153      * @param  PHP_CodeCoverage_Filter $filter
154      * @throws InvalidArgumentException
155      */
156     public function __construct(PHP_CodeCoverage_Driver $driver = NULL, PHP_CodeCoverage_Filter $filter = NULL)
157     {
158         if ($driver === NULL) {
159             $driver = new PHP_CodeCoverage_Driver_Xdebug;
160         }
161
162         if ($filter === NULL) {
163             $filter = PHP_CodeCoverage_Filter::getInstance();
164         }
165
166         $this->driver = $driver;
167         $this->filter = $filter;
168
169         if (defined('PHP_CODECOVERAGE_TESTSUITE')) {
170             $this->isCodeCoverageTestSuite = TRUE;
171         }
172
173         if (defined('FILE_ITERATOR_TESTSUITE')) {
174             $this->isFileIteratorTestSuite = TRUE;
175         }
176
177         if (defined('PHP_TIMER_TESTSUITE')) {
178             $this->isTimerTestSuite = TRUE;
179         }
180
181         if (defined('PHP_TOKENSTREAM_TESTSUITE')) {
182             $this->isTokenStreamTestSuite = TRUE;
183         }
184     }
185
186     /**
187      * Returns the default instance.
188      *
189      * @return PHP_CodeCoverage
190      */
191     public static function getInstance()
192     {
193         if (self::$instance === NULL) {
194             // @codeCoverageIgnoreStart
195             self::$instance = new PHP_CodeCoverage;
196         }
197         // @codeCoverageIgnoreEnd
198         return self::$instance;
199     }
200
201     /**
202      * Start collection of code coverage information.
203      *
204      * @param  mixed   $id
205      * @param  boolean $clear
206      * @throws InvalidArgumentException
207      */
208     public function start($id, $clear = FALSE)
209     {
210         if (!is_bool($clear)) {
211             throw new InvalidArgumentException;
212         }
213
214         if ($clear) {
215             $this->clear();
216         }
217
218         $this->currentId = $id;
219
220         $this->driver->start();
221     }
222
223     /**
224      * Stop collection of code coverage information.
225      *
226      * @param  boolean $append
227      * @return array
228      * @throws InvalidArgumentException
229      */
230     public function stop($append = TRUE)
231     {
232         if (!is_bool($append)) {
233             throw new InvalidArgumentException;
234         }
235
236         $data = $this->driver->stop();
237
238         if ($append) {
239             $this->append($data);
240         }
241
242         $this->currentId = NULL;
243
244         return $data;
245     }
246
247     /**
248      * Appends code coverage data.
249      *
250      * @param array $data
251      * @param mixed $id
252      * @param array $filterGroups
253      */
254     public function append(array $data, $id = NULL, array $filterGroups = array('DEFAULT'))
255     {
256         if ($id === NULL) {
257             $id = $this->currentId;
258         }
259
260         if ($id === NULL) {
261             throw new InvalidArgumentException;
262         }
263
264         $this->applySelfFilter($data);
265         $this->applyListsFilter($data, $filterGroups);
266         $raw = $data;
267         $this->applyCoversAnnotationFilter($data, $id);
268
269         if (!empty($data)) {
270             if ($id instanceof PHPUnit_Framework_TestCase) {
271                 $status           = $id->getStatus();
272                 $id               = get_class($id) . '::' . $id->getName();
273                 $this->tests[$id] = $status;
274             }
275
276             else if ($id instanceof PHPUnit_Extensions_PhptTestCase) {
277                 $id = $id->getName();
278             }
279
280             $this->coveredFiles = array_unique(
281               array_merge($this->coveredFiles, array_keys($data))
282             );
283
284             $this->data[$id] = array('filtered' => $data, 'raw' => $raw);
285             $this->summary   = array();
286         }
287     }
288
289     /**
290      * Merges the data from another instance of PHP_CodeCoverage.
291      *
292      * @param PHP_CodeCoverage $that
293      */
294     public function merge(PHP_CodeCoverage $that)
295     {
296         foreach ($that->data as $id => $data) {
297             if (!isset($this->data[$id])) {
298                 $this->data[$id] = $data;
299             } else {
300                 foreach (array('filtered', 'raw') as $type) {
301                     foreach ($data[$type] as $file => $lines) {
302                         if (!isset($this->data[$id][$type][$file])) {
303                             $this->data[$id][$type][$file] = $lines;
304                         } else {
305                             foreach ($lines as $line => $flag) {
306                                 if (!isset($this->data[$id][$type][$file][$line]) ||
307                                     $flag > $this->data[$id][$type][$file][$line]) {
308                                     $this->data[$id][$type][$file][$line] = $flag;
309                                 }
310                             }
311                         }
312                     }
313                 }
314             }
315         }
316
317         foreach ($that->tests as $id => $status) {
318             if (!isset($this->tests[$id]) || $status > $this->tests[$id]) {
319                 $this->tests[$id] = $status;
320             }
321         }
322
323         $this->coveredFiles = array_unique(
324           array_merge($this->coveredFiles, $that->coveredFiles)
325         );
326
327         $this->summary = array();
328     }
329
330     /**
331      * Returns summarized code coverage data.
332      *
333      * Format of the result array:
334      *
335      * <code>
336      * array(
337      *   "/tested/code.php" => array(
338      *     linenumber => array(tests that executed the line)
339      *   )
340      * )
341      * </code>
342      *
343      * @return array
344      */
345     public function getSummary()
346     {
347         if (empty($this->summary)) {
348             if ($this->processUncoveredFilesFromWhitelist) {
349                 $this->processUncoveredFilesFromWhitelist();
350             }
351
352             foreach ($this->data as $test => $coverage) {
353                 foreach ($coverage['filtered'] as $file => $lines) {
354                     foreach ($lines as $line => $flag) {
355                         if ($flag == 1) {
356                             if (!isset($this->summary[$file][$line][0])) {
357                                 $this->summary[$file][$line] = array();
358                             }
359
360                             if (isset($this->tests[$test])) {
361                                 $status = $this->tests[$test];
362                             } else {
363                                 $status = NULL;
364                             }
365
366                             $this->summary[$file][$line][] = array(
367                               'id' => $test, 'status' => $status
368                             );
369                         }
370                     }
371                 }
372
373                 foreach ($coverage['raw'] as $file => $lines) {
374                     foreach ($lines as $line => $flag) {
375                         if ($flag != 1 &&
376                             !isset($this->summary[$file][$line][0])) {
377                             $this->summary[$file][$line] = $flag;
378                         }
379                     }
380                 }
381             }
382
383             foreach ($this->summary as &$file) {
384                 ksort($file);
385             }
386
387             ksort($this->summary);
388         }
389
390         return $this->summary;
391     }
392
393     /**
394      * Clears collected code coverage data.
395      */
396     public function clear()
397     {
398         $this->data         = array();
399         $this->coveredFiles = array();
400         $this->summary      = array();
401         $this->currentId    = NULL;
402     }
403
404     /**
405      * Returns the PHP_CodeCoverage_Filter used.
406      *
407      * @return PHP_CodeCoverage_Filter
408      */
409     public function filter()
410     {
411         return $this->filter;
412     }
413
414     /**
415      * @param  boolean $flag
416      * @throws InvalidArgumentException
417      */
418     public function setForceCoversAnnotation($flag)
419     {
420         if (!is_bool($flag)) {
421             throw new InvalidArgumentException;
422         }
423
424         $this->forceCoversAnnotation = $flag;
425     }
426
427     /**
428      * @param  boolean $flag
429      * @throws InvalidArgumentException
430      */
431     public function setMapTestClassNameToCoveredClassName($flag)
432     {
433         if (!is_bool($flag)) {
434             throw new InvalidArgumentException;
435         }
436
437         $this->mapTestClassNameToCoveredClassName = $flag;
438     }
439
440     /**
441      * @param  boolean $flag
442      * @throws InvalidArgumentException
443      */
444     public function setProcessUncoveredFilesFromWhitelist($flag)
445     {
446         if (!is_bool($flag)) {
447             throw new InvalidArgumentException;
448         }
449
450         $this->processUncoveredFilesFromWhitelist = $flag;
451     }
452
453     /**
454      * Filters sourcecode files from PHP_CodeCoverage, PHP_TokenStream,
455      * Text_Template, and File_Iterator.
456      *
457      * @param array $data
458      */
459     protected function applySelfFilter(&$data)
460     {
461         foreach (array_keys($data) as $filename) {
462             if (!$this->filter->isFile($filename)) {
463                 unset($data[$filename]);
464                 continue;
465             }
466
467             if (!$this->isCodeCoverageTestSuite &&
468                 strpos($filename, dirname(__FILE__)) === 0) {
469                 unset($data[$filename]);
470                 continue;
471             }
472
473             if (!$this->isFileIteratorTestSuite &&
474                 (substr($filename, -17) == 'File/Iterator.php' ||
475                  substr($filename, -25) == 'File/Iterator/Factory.php')) {
476                 unset($data[$filename]);
477                 continue;
478             }
479
480             if (!$this->isTimerTestSuite &&
481                 (substr($filename, -13) == 'PHP/Timer.php')) {
482                 unset($data[$filename]);
483                 continue;
484             }
485
486             if (!$this->isTokenStreamTestSuite &&
487                 (substr($filename, -13) == 'PHP/Token.php' ||
488                  substr($filename, -20) == 'PHP/Token/Stream.php' ||
489                  substr($filename, -35) == 'PHP/Token/Stream/CachingFactory.php')) {
490                 unset($data[$filename]);
491                 continue;
492             }
493
494             if (substr($filename, -17) == 'Text/Template.php') {
495                 unset($data[$filename]);
496             }
497         }
498     }
499
500     /**
501      * Applies the blacklist/whitelist filtering.
502      *
503      * @param array $data
504      * @param array $filterGroups
505      */
506     protected function applyListsFilter(&$data, $filterGroups)
507     {
508         foreach (array_keys($data) as $filename) {
509             if ($this->filter->isFiltered($filename, $filterGroups)) {
510                 unset($data[$filename]);
511             }
512         }
513     }
514
515     /**
516      * Applies the @covers annotation filtering.
517      *
518      * @param array $data
519      * @param mixed $id
520      */
521     protected function applyCoversAnnotationFilter(&$data, $id)
522     {
523         if ($id instanceof PHPUnit_Framework_TestCase) {
524             $testClassName    = get_class($id);
525             $linesToBeCovered = PHP_CodeCoverage_Util::getLinesToBeCovered(
526               $testClassName, $id->getName()
527             );
528
529             if ($this->mapTestClassNameToCoveredClassName &&
530                 empty($linesToBeCovered)) {
531                 $testedClass = substr($testClassName, 0, -4);
532
533                 if (class_exists($testedClass)) {
534                     $class = new ReflectionClass($testedClass);
535
536                     $linesToBeCovered = array(
537                       $class->getFileName() => range(
538                         $class->getStartLine(), $class->getEndLine()
539                       )
540                     );
541                 }
542             }
543         } else {
544             $linesToBeCovered = array();
545         }
546
547         if (!empty($linesToBeCovered)) {
548             $data = array_intersect_key($data, $linesToBeCovered);
549
550             foreach (array_keys($data) as $filename) {
551                 $data[$filename] = array_intersect_key(
552                   $data[$filename], array_flip($linesToBeCovered[$filename])
553                 );
554             }
555         }
556
557         else if ($this->forceCoversAnnotation) {
558             $data = array();
559         }
560     }
561
562     /**
563      * Processes whitelisted files that are not covered.
564      */
565     protected function processUncoveredFilesFromWhitelist()
566     {
567         $data           = array();
568         $includedFiles  = array_flip(get_included_files());
569         $uncoveredFiles = array_diff(
570           $this->filter->getWhitelist(), $this->coveredFiles
571         );
572
573         foreach ($uncoveredFiles as $uncoveredFile) {
574             if (isset($includedFiles[$uncoveredFile])) {
575                 foreach (array_keys($this->data) as $test) {
576                     if (isset($this->data[$test]['raw'][$uncoveredFile])) {
577                         $coverage = $this->data[$test]['raw'][$uncoveredFile];
578
579                         foreach (array_keys($coverage) as $key) {
580                             if ($coverage[$key] == 1) {
581                                 $coverage[$key] = -1;
582                             }
583                         }
584
585                         $data[$uncoveredFile] = $coverage;
586
587                         break;
588                     }
589                 }
590             } else {
591                 $this->driver->start();
592                 include_once $uncoveredFile;
593                 $coverage = $this->driver->stop();
594
595                 foreach ($coverage as $file => $fileCoverage) {
596                     if (!isset($data[$file]) &&
597                         in_array($file, $uncoveredFiles)) {
598                         foreach (array_keys($fileCoverage) as $key) {
599                             if ($fileCoverage[$key] == 1) {
600                                 $fileCoverage[$key] = -1;
601                             }
602                         }
603
604                         $data[$file]          = $fileCoverage;
605                         $includedFiles[$file] = TRUE;
606                     }
607                 }
608             }
609         }
610
611         $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
612     }
613 }