]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - tests/PHPUnit/PHP/CodeCoverage/Util.php
Release 6.2.0
[Github/sugarcrm.git] / tests / PHPUnit / PHP / CodeCoverage / Util.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 if (!defined('T_NAMESPACE')) {
47     define('T_NAMESPACE', 377);
48 }
49
50 require_once 'PHP/Token/Stream/CachingFactory.php';
51
52 /**
53  * Utility methods.
54  *
55  * @category   PHP
56  * @package    CodeCoverage
57  * @author     Sebastian Bergmann <sb@sebastian-bergmann.de>
58  * @copyright  2009-2011 Sebastian Bergmann <sb@sebastian-bergmann.de>
59  * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
60  * @version    Release: 1.0.4
61  * @link       http://github.com/sebastianbergmann/php-code-coverage
62  * @since      Class available since Release 1.0.0
63  */
64 class PHP_CodeCoverage_Util
65 {
66     /**
67      * @var string
68      */
69     const REGEX = '(@covers\s+(?P<coveredElement>.*?)\s*$)m';
70
71     /**
72      * @var array
73      */
74     protected static $ignoredLines = array();
75
76     /**
77      * @var array
78      */
79     protected static $templateMethods = array(
80       'setUp', 'assertPreConditions', 'assertPostConditions', 'tearDown'
81     );
82
83     /**
84      * Builds an array representation of the directory structure.
85      *
86      * For instance,
87      *
88      * <code>
89      * Array
90      * (
91      *     [Money.php] => Array
92      *         (
93      *             ...
94      *         )
95      *
96      *     [MoneyBag.php] => Array
97      *         (
98      *             ...
99      *         )
100      * )
101      * </code>
102      *
103      * is transformed into
104      *
105      * <code>
106      * Array
107      * (
108      *     [.] => Array
109      *         (
110      *             [Money.php] => Array
111      *                 (
112      *                     ...
113      *                 )
114      *
115      *             [MoneyBag.php] => Array
116      *                 (
117      *                     ...
118      *                 )
119      *         )
120      * )
121      * </code>
122      *
123      * @param  array $files
124      * @return array
125      */
126     public static function buildDirectoryStructure($files)
127     {
128         $result = array();
129
130         foreach ($files as $path => $file) {
131             $path    = explode('/', $path);
132             $pointer = &$result;
133             $max     = count($path);
134
135             for ($i = 0; $i < $max; $i++) {
136                 if ($i == ($max - 1)) {
137                     $type = '/f';
138                 } else {
139                     $type = '';
140                 }
141
142                 $pointer = &$pointer[$path[$i] . $type];
143             }
144
145             $pointer = $file;
146         }
147
148         return $result;
149     }
150
151     /**
152      * Calculates the Change Risk Anti-Patterns (CRAP) index for a unit of code
153      * based on its cyclomatic complexity and percentage of code coverage.
154      *
155      * @param  integer $ccn
156      * @param  float   $coverage
157      * @return string
158      */
159     public static function crap($ccn, $coverage)
160     {
161         if ($coverage == 0) {
162             return (string)pow($ccn, 2) + $ccn;
163         }
164
165         if ($coverage >= 95) {
166             return (string)$ccn;
167         }
168
169         return sprintf(
170           '%01.2F', pow($ccn, 2) * pow(1 - $coverage/100, 3) + $ccn
171         );
172     }
173
174     /**
175      * @param  string $directory
176      * @return string
177      * @throws RuntimeException
178      */
179     public static function getDirectory($directory)
180     {
181         if (substr($directory, -1, 1) != DIRECTORY_SEPARATOR) {
182             $directory .= DIRECTORY_SEPARATOR;
183         }
184
185         if (is_dir($directory)) {
186             return $directory;
187         }
188
189         if (mkdir($directory, 0777, TRUE)) {
190             return $directory;
191         }
192
193         throw new RuntimeException(
194           sprintf(
195             'Directory "%s" does not exist.',
196             $directory
197           )
198         );
199     }
200
201     /**
202      * Returns the files and lines a test method wants to cover.
203      *
204      * @param  string $className
205      * @param  string $methodName
206      * @return array
207      */
208     public static function getLinesToBeCovered($className, $methodName)
209     {
210         $codeToCoverList = array();
211         $result          = array();
212         // @codeCoverageIgnoreStart
213         if (($pos = strpos($methodName, ' ')) !== FALSE) {
214             $methodName = substr($methodName, 0, $pos);
215         }
216         // @codeCoverageIgnoreEnd
217         $class      = new ReflectionClass($className);
218         $method     = new ReflectionMethod($className, $methodName);
219         $docComment = $class->getDocComment() . $method->getDocComment();
220
221         foreach (self::$templateMethods as $templateMethod) {
222             if ($class->hasMethod($templateMethod)) {
223                 $reflector   = $class->getMethod($templateMethod);
224                 $docComment .= $reflector->getDocComment();
225                 unset($reflector);
226             }
227         }
228
229         if (preg_match_all(self::REGEX, $docComment, $matches)) {
230             foreach ($matches['coveredElement'] as $coveredElement) {
231                 $codeToCoverList = array_merge(
232                   $codeToCoverList,
233                   self::resolveCoversToReflectionObjects($coveredElement)
234                 );
235             }
236
237             foreach ($codeToCoverList as $codeToCover) {
238                 $fileName = $codeToCover->getFileName();
239
240                 if (!isset($result[$fileName])) {
241                     $result[$fileName] = array();
242                 }
243
244                 $result[$fileName] = array_unique(
245                   array_merge(
246                     $result[$fileName],
247                     range(
248                       $codeToCover->getStartLine(), $codeToCover->getEndLine()
249                     )
250                   )
251                 );
252             }
253         }
254
255         return $result;
256     }
257
258     /**
259      * Returns the lines of a source file that should be ignored.
260      *
261      * @param  string $filename
262      * @return array
263      */
264     public static function getLinesToBeIgnored($filename)
265     {
266         if (!isset(self::$ignoredLines[$filename])) {
267             self::$ignoredLines[$filename] = array();
268
269             $ignore = FALSE;
270             $stop   = FALSE;
271             $tokens = PHP_Token_Stream_CachingFactory::get($filename)->tokens();
272
273             foreach ($tokens as $token) {
274                 switch (get_class($token)) {
275                     case 'PHP_Token_CLASS':
276                     case 'PHP_Token_FUNCTION': {
277                         $docblock = $token->getDocblock();
278                         $endLine  = $token->getEndLine();
279
280                         if (strpos($docblock, '@codeCoverageIgnore')) {
281                             for ($i = $token->getLine(); $i <= $endLine; $i++) {
282                                 self::$ignoredLines[$filename][$i] = TRUE;
283                             }
284                         }
285                     }
286                     break;
287
288                     case 'PHP_Token_COMMENT': {
289                         $_token = trim($token);
290
291                         if ($_token == '// @codeCoverageIgnoreStart' ||
292                             $_token == '//@codeCoverageIgnoreStart') {
293                             $ignore = TRUE;
294                         }
295
296                         else if ($_token == '// @codeCoverageIgnoreEnd' ||
297                                  $_token == '//@codeCoverageIgnoreEnd') {
298                             $stop = TRUE;
299                         }
300                     }
301                     break;
302                 }
303
304                 if ($ignore) {
305                     self::$ignoredLines[$filename][$token->getLine()] = TRUE;
306
307                     if ($stop) {
308                         $ignore = FALSE;
309                         $stop   = FALSE;
310                     }
311                 }
312             }
313         }
314
315         return self::$ignoredLines[$filename];
316     }
317
318     /**
319      * Returns the package information of a user-defined class.
320      *
321      * @param  string $className
322      * @param  string $docComment
323      * @return array
324      */
325     public static function getPackageInformation($className, $docComment)
326     {
327         $result = array(
328           'namespace'   => '',
329           'fullPackage' => '',
330           'category'    => '',
331           'package'     => '',
332           'subpackage'  => ''
333         );
334
335         if (strpos($className, '\\') !== FALSE) {
336             $result['namespace'] = self::arrayToName(
337               explode('\\', $className)
338             );
339         }
340
341         if (preg_match('/@category[\s]+([\.\w]+)/', $docComment, $matches)) {
342             $result['category'] = $matches[1];
343         }
344
345         if (preg_match('/@package[\s]+([\.\w]+)/', $docComment, $matches)) {
346             $result['package']     = $matches[1];
347             $result['fullPackage'] = $matches[1];
348         }
349
350         if (preg_match('/@subpackage[\s]+([\.\w]+)/', $docComment, $matches)) {
351             $result['subpackage']   = $matches[1];
352             $result['fullPackage'] .= '.' . $matches[1];
353         }
354
355         if (empty($result['fullPackage'])) {
356             $result['fullPackage'] = self::arrayToName(
357               explode('_', str_replace('\\', '_', $className)), '.'
358             );
359         }
360
361         return $result;
362     }
363
364     /**
365      * Returns a filesystem safe version of the passed filename.
366      * This function does not operate on full paths, just filenames.
367      *
368      * @param  string $filename
369      * @return string
370      * @author Michael Lively Jr. <m@digitalsandwich.com>
371      */
372     public static function getSafeFilename($filename)
373     {
374         /* characters allowed: A-Z, a-z, 0-9, _ and . */
375         return preg_replace('#[^\w.]#', '_', $filename);
376     }
377
378     /**
379      * @param  float $a
380      * @param  float $b
381      * @return float ($a / $b) * 100
382      */
383     public static function percent($a, $b, $asString = FALSE)
384     {
385         if ($b > 0) {
386             $percent = ($a / $b) * 100;
387         } else {
388             $percent = 100;
389         }
390
391         if ($asString) {
392             return sprintf('%01.2F', $percent);
393         } else {
394             return $percent;
395         }
396     }
397
398     /**
399      * Reduces the paths by cutting the longest common start path.
400      *
401      * For instance,
402      *
403      * <code>
404      * Array
405      * (
406      *     [/home/sb/Money/Money.php] => Array
407      *         (
408      *             ...
409      *         )
410      *
411      *     [/home/sb/Money/MoneyBag.php] => Array
412      *         (
413      *             ...
414      *         )
415      * )
416      * </code>
417      *
418      * is reduced to
419      *
420      * <code>
421      * Array
422      * (
423      *     [Money.php] => Array
424      *         (
425      *             ...
426      *         )
427      *
428      *     [MoneyBag.php] => Array
429      *         (
430      *             ...
431      *         )
432      * )
433      * </code>
434      *
435      * @param  array $files
436      * @return string
437      */
438     public static function reducePaths(&$files)
439     {
440         if (empty($files)) {
441             return '.';
442         }
443
444         $commonPath = '';
445         $paths      = array_keys($files);
446
447         if (count($files) == 1) {
448             $commonPath                 = dirname($paths[0]) . '/';
449             $files[basename($paths[0])] = $files[$paths[0]];
450
451             unset($files[$paths[0]]);
452
453             return $commonPath;
454         }
455
456         $max = count($paths);
457
458         for ($i = 0; $i < $max; $i++) {
459             $paths[$i] = explode(DIRECTORY_SEPARATOR, $paths[$i]);
460
461             if (empty($paths[$i][0])) {
462                 $paths[$i][0] = DIRECTORY_SEPARATOR;
463             }
464         }
465
466         $done = FALSE;
467         $max  = count($paths);
468
469         while (!$done) {
470             for ($i = 0; $i < $max - 1; $i++) {
471                 if (!isset($paths[$i][0]) ||
472                     !isset($paths[$i+1][0]) ||
473                     $paths[$i][0] != $paths[$i+1][0]) {
474                     $done = TRUE;
475                     break;
476                 }
477             }
478
479             if (!$done) {
480                 $commonPath .= $paths[0][0];
481
482                 if ($paths[0][0] != DIRECTORY_SEPARATOR) {
483                     $commonPath .= DIRECTORY_SEPARATOR;
484                 }
485
486                 for ($i = 0; $i < $max; $i++) {
487                     array_shift($paths[$i]);
488                 }
489             }
490         }
491
492         $original = array_keys($files);
493         $max      = count($original);
494
495         for ($i = 0; $i < $max; $i++) {
496             $files[join('/', $paths[$i])] = $files[$original[$i]];
497             unset($files[$original[$i]]);
498         }
499
500         ksort($files);
501
502         return $commonPath;
503     }
504
505     /**
506      * Returns the package information of a user-defined class.
507      *
508      * @param  array  $parts
509      * @param  string $join
510      * @return string
511      */
512     protected static function arrayToName(array $parts, $join = '\\')
513     {
514         $result = '';
515
516         if (count($parts) > 1) {
517             array_pop($parts);
518
519             $result = join($join, $parts);
520         }
521
522         return $result;
523     }
524
525     /**
526      * @param  string $coveredElement
527      * @return array
528      */
529     protected static function resolveCoversToReflectionObjects($coveredElement)
530     {
531         $codeToCoverList = array();
532
533         if (strpos($coveredElement, '::') !== FALSE) {
534             list($className, $methodName) = explode('::', $coveredElement);
535
536             if ($methodName[0] == '<') {
537                 $classes = array($className);
538
539                 foreach ($classes as $className) {
540                     if (!class_exists($className) &&
541                         !interface_exists($className)) {
542                         throw new RuntimeException(
543                           sprintf(
544                             'Trying to @cover not existing class or ' .
545                             'interface "%s".',
546                             $className
547                           )
548                         );
549                     }
550
551                     $class   = new ReflectionClass($className);
552                     $methods = $class->getMethods();
553                     $inverse = isset($methodName[1]) && $methodName[1] == '!';
554
555                     if (strpos($methodName, 'protected')) {
556                         $visibility = 'isProtected';
557                     }
558
559                     else if (strpos($methodName, 'private')) {
560                         $visibility = 'isPrivate';
561                     }
562
563                     else if (strpos($methodName, 'public')) {
564                         $visibility = 'isPublic';
565                     }
566
567                     foreach ($methods as $method) {
568                         if ($inverse && !$method->$visibility()) {
569                             $codeToCoverList[] = $method;
570                         }
571
572                         else if (!$inverse && $method->$visibility()) {
573                             $codeToCoverList[] = $method;
574                         }
575                     }
576                 }
577             } else {
578                 $classes = array($className);
579
580                 foreach ($classes as $className) {
581                     if ($className == '' && function_exists($methodName)) {
582                         $codeToCoverList[] = new ReflectionFunction(
583                           $methodName
584                         );
585                     } else {
586                         if (!((class_exists($className) ||
587                                interface_exists($className)) &&
588                               method_exists($className, $methodName))) {
589                             throw new RuntimeException(
590                               sprintf(
591                                 'Trying to @cover not existing method "%s::%s".',
592                                 $className,
593                                 $methodName
594                               )
595                             );
596                         }
597
598                         $codeToCoverList[] = new ReflectionMethod(
599                           $className, $methodName
600                         );
601                     }
602                 }
603             }
604         } else {
605             $extended = FALSE;
606
607             if (strpos($coveredElement, '<extended>') !== FALSE) {
608                 $coveredElement = str_replace(
609                   '<extended>', '', $coveredElement
610                 );
611
612                 $extended = TRUE;
613             }
614
615             $classes = array($coveredElement);
616
617             if ($extended) {
618                 $classes = array_merge(
619                   $classes,
620                   class_implements($coveredElement),
621                   class_parents($coveredElement)
622                 );
623             }
624
625             foreach ($classes as $className) {
626                 if (!class_exists($className) &&
627                     !interface_exists($className)) {
628                     throw new RuntimeException(
629                       sprintf(
630                         'Trying to @cover not existing class or ' .
631                         'interface "%s".',
632                         $className
633                       )
634                     );
635                 }
636
637                 $codeToCoverList[] = new ReflectionClass($className);
638             }
639         }
640
641         return $codeToCoverList;
642     }
643 }