]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - tests/PHPUnit/Util/Metrics/Function.php
Added unit tests.
[Github/sugarcrm.git] / tests / PHPUnit / Util / Metrics / Function.php
1 <?php
2 /**
3  * PHPUnit
4  *
5  * Copyright (c) 2002-2009, 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   Testing
38  * @package    PHPUnit
39  * @author     Sebastian Bergmann <sb@sebastian-bergmann.de>
40  * @copyright  2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
41  * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
42
43  * @link       http://www.phpunit.de/
44  * @since      File available since Release 3.2.0
45  */
46
47 require_once 'PHPUnit/Util/Class.php';
48 require_once 'PHPUnit/Util/Filter.php';
49
50 PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT');
51
52 /**
53  * Function- and Method-Level Metrics.
54  *
55  * @category   Testing
56  * @package    PHPUnit
57  * @author     Sebastian Bergmann <sb@sebastian-bergmann.de>
58  * @copyright  2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
59  * @license    http://www.opensource.org/licenses/bsd-license.php  BSD License
60  * @version    Release: 3.3.17
61  * @link       http://www.phpunit.de/
62  * @since      Class available since Release 3.2.0
63  */
64 class PHPUnit_Util_Metrics_Function extends PHPUnit_Util_Metrics
65 {
66     protected $ccn           = 1;
67     protected $npath         = 1;
68     protected $coverage      = 0;
69     protected $crap;
70     protected $loc           = 0;
71     protected $locExecutable = 0;
72     protected $locExecuted   = 0;
73     protected $parameters    = 0;
74
75     protected $function;
76     protected $scope;
77     protected $tokens;
78
79     protected $dependencies = array();
80
81     protected static $cache = array();
82
83     /**
84      * Constructor.
85      *
86      * @param  string                              $scope
87      * @param  ReflectionFunction|ReflectionMethod $function
88      * @param  array                               $codeCoverage
89      */
90     protected function __construct($scope, $function, &$codeCoverage = array())
91     {
92         $this->scope    = $scope;
93         $this->function = $function;
94
95         $source = PHPUnit_Util_Class::getMethodSource(
96           $scope, $function->getName()
97         );
98
99         if ($source !== FALSE) {
100             $this->tokens     = token_get_all('<?php' . $source . '?>');
101             $this->parameters = $function->getNumberOfParameters();
102
103             $this->calculateCCN();
104             $this->calculateNPath();
105             $this->calculateDependencies();
106         }
107
108         $this->setCoverage($codeCoverage);
109     }
110
111     /**
112      * Factory.
113      *
114      * @param  ReflectionFunction|ReflectionMethod $function
115      * @param  array                               $codeCoverage
116      * @return PHPUnit_Util_Metrics_Method
117      */
118     public static function factory($function, &$codeCoverage = array())
119     {
120         if ($function instanceof ReflectionMethod) {
121             $scope = $function->getDeclaringClass()->getName();
122         } else {
123             $scope = 'global';
124         }
125
126         $name = $function->getName();
127
128         if (!isset(self::$cache[$scope][$name])) {
129             self::$cache[$scope][$name] = new PHPUnit_Util_Metrics_Function($scope, $function, $codeCoverage);
130         }
131
132         else if (!empty($codeCoverage) && self::$cache[$scope][$name]->getCoverage() == 0) {
133             self::$cache[$scope][$name]->setCoverage($codeCoverage);
134         }
135
136         return self::$cache[$scope][$name];
137     }
138
139     /**
140      * @param  array $codeCoverage
141      */
142     public function setCoverage(array &$codeCoverage)
143     {
144         if (!empty($codeCoverage)) {
145             $this->calculateCodeCoverage($codeCoverage);
146             $this->calculateCrapIndex();
147         }
148     }
149
150     /**
151      * Returns the function.
152      *
153      * @return ReflectionFunction
154      */
155     public function getFunction()
156     {
157         return $this->function;
158     }
159
160     /**
161      * Returns the method.
162      * Alias for getFunction().
163      *
164      * @return ReflectionMethod
165      */
166     public function getMethod()
167     {
168         return $this->function;
169     }
170
171     /**
172      * Returns the names of the classes this function or method depends on.
173      *
174      * @return array
175      */
176     public function getDependencies()
177     {
178         return $this->dependencies;
179     }
180
181     /**
182      * Lines of Code (LOC).
183      *
184      * @return int
185      */
186     public function getLoc()
187     {
188         return $this->loc;
189     }
190
191     /**
192      * Executable Lines of Code (ELOC).
193      *
194      * @return int
195      */
196     public function getLocExecutable()
197     {
198         return $this->locExecutable;
199     }
200
201     /**
202      * Executed Lines of Code.
203      *
204      * @return int
205      */
206     public function getLocExecuted()
207     {
208         return $this->locExecuted;
209     }
210
211     /**
212      * Number of Parameters.
213      *
214      * @return int
215      */
216     public function getParameters()
217     {
218         return $this->parameters;
219     }
220
221     /**
222      * Returns the Cyclomatic Complexity Number (CCN) for the method.
223      * This is also known as the McCabe metric.
224      *
225      * Each method has a minimum value of 1 per default. For each of the
226      * following PHP keywords/statements this value gets incremented by one:
227      *
228      *   - if
229      *   - elseif
230      *   - for
231      *   - foreach
232      *   - while
233      *   - case
234      *   - catch
235      *   - AND, &&
236      *   - OR, ||
237      *   - ?
238      *
239      * Note that 'else', 'default', and 'finally' don't increment the value
240      * any further. On the other hand, a simple method with a 'switch'
241      * statement and a huge block of 'case 'statements can have a surprisingly
242      * high value (still it has the same value when converting a 'switch'
243      * block to an equivalent sequence of 'if' statements).
244      *
245      * @return integer
246      * @see    http://en.wikipedia.org/wiki/Cyclomatic_complexity
247      */
248     public function getCCN()
249     {
250         return $this->ccn;
251     }
252
253     /**
254      * Returns the Change Risk Analysis and Predictions (CRAP) index for the
255      * method.
256      *
257      * @return float
258      * @see    http://www.artima.com/weblogs/viewpost.jsp?thread=210575
259      */
260     public function getCrapIndex()
261     {
262         return $this->crap;
263     }
264
265     /**
266      * Returns the Code Coverage for the method.
267      *
268      * @return float
269      */
270     public function getCoverage()
271     {
272         return $this->coverage;
273     }
274
275     /**
276      * Returns the NPath Complexity for the method.
277      *
278      * @return integer
279      */
280     public function getNPath()
281     {
282         return $this->npath;
283     }
284
285     /**
286      * Calculates the Cyclomatic Complexity Number (CCN) for the method.
287      *
288      */
289     protected function calculateCCN()
290     {
291         foreach ($this->tokens as $token) {
292             if (is_string($token)) {
293                 $token = trim($token);
294
295                 if ($token == '?') {
296                     $this->ccn++;
297                 }
298
299                 continue;
300             }
301
302             list ($token, $value) = $token;
303
304             switch ($token) {
305                 case T_IF:
306                 case T_ELSEIF:
307                 case T_FOR:
308                 case T_FOREACH:
309                 case T_WHILE:
310                 case T_CASE:
311                 case T_CATCH:
312                 case T_BOOLEAN_AND:
313                 case T_LOGICAL_AND:
314                 case T_BOOLEAN_OR:
315                 case T_LOGICAL_OR: {
316                     $this->ccn++;
317                 }
318                 break;
319             }
320         }
321     }
322
323     /**
324      * Calculates the NPath Complexity for the method.
325      *
326      */
327     protected function calculateNPath()
328     {
329         $npathStack = array();
330         $stack      = array();
331
332         foreach ($this->tokens as $token) {
333             if (is_string($token)) {
334                 $token = trim($token);
335
336                 if ($token == '?') {
337                     $this->npath = ($this->npath + 1) * $this->npath;
338                 }
339
340                 if ($token == '{') {
341                     if (isset($scope)) {
342                         array_push($stack, $scope);
343                         array_push($npathStack, $this->npath);
344                         $this->npath = 1;
345                     } else {
346                         array_push($stack, NULL);
347                     }
348                 }
349
350                 if ($token == '}') {
351                     $scope = array_pop($stack);
352
353                     if ($scope !== NULL) {
354                         switch ($scope) {
355                             case T_WHILE:
356                             case T_DO:
357                             case T_FOR:
358                             case T_FOREACH:
359                             case T_IF:
360                             case T_TRY:
361                             case T_SWITCH: {
362                                 $this->npath = ($this->npath + 1) * array_pop($npathStack);
363                             }
364                             break;
365
366                             case T_ELSE:
367                             case T_CATCH:
368                             case T_CASE: {
369                                 $this->npath = ($this->npath - 1) + array_pop($npathStack);
370                             }
371                             break;
372                         }
373                     }
374                 }
375
376                 continue;
377             }
378
379             list ($token, $value) = $token;
380
381             switch ($token) {
382                 case T_WHILE:
383                 case T_DO:
384                 case T_FOR:
385                 case T_FOREACH:
386                 case T_IF:
387                 case T_TRY:
388                 case T_SWITCH:
389                 case T_ELSE:
390                 case T_CATCH:
391                 case T_CASE: {
392                     $scope = $token;
393                 }
394                 break;
395             }
396         }
397     }
398
399     /**
400      * Calculates the Code Coverage for the method.
401      *
402      * @param  array $codeCoverage
403      */
404     protected function calculateCodeCoverage(&$codeCoverage)
405     {
406         $statistics = PHPUnit_Util_CodeCoverage::getStatistics(
407           $codeCoverage,
408           $this->function->getFileName(),
409           $this->function->getStartLine(),
410           $this->function->getEndLine()
411         );
412
413         $this->coverage      = $statistics['coverage'];
414         $this->loc           = $statistics['loc'];
415         $this->locExecutable = $statistics['locExecutable'];
416         $this->locExecuted   = $statistics['locExecuted'];
417     }
418
419     /**
420      * Calculates the Change Risk Analysis and Predictions (CRAP) index for the
421      * method.
422      *
423      */
424     protected function calculateCrapIndex()
425     {
426         if ($this->coverage == 0) {
427             $this->crap = pow($this->ccn, 2) + $this->ccn;
428         }
429
430         else if ($this->coverage >= 95) {
431             $this->crap = $this->ccn;
432         }
433
434         else {
435             $this->crap = pow($this->ccn, 2) * pow(1 - $this->coverage/100, 3) + $this->ccn;
436         }
437     }
438
439     /**
440      * Calculates the dependencies for this function or method.
441      *
442      */
443     protected function calculateDependencies()
444     {
445         foreach ($this->function->getParameters() as $parameter) {
446             try {
447                 $class = $parameter->getClass();
448
449                 if ($class) {
450                     $className = $class->getName();
451
452                     if ($className != $this->scope && !in_array($className, $this->dependencies)) {
453                         $this->dependencies[] = $className;
454                     }
455                 }
456             }
457
458             catch (ReflectionException $e) {
459             }
460         }
461
462         $inNew = FALSE;
463
464         foreach ($this->tokens as $token) {
465             if (is_string($token)) {
466                 if (trim($token) == ';') {
467                     $inNew = FALSE;
468                 }
469
470                 continue;
471             }
472
473             list ($token, $value) = $token;
474
475             switch ($token) {
476                 case T_NEW: {
477                     $inNew = TRUE;
478                 }
479                 break;
480
481                 case T_STRING: {
482                     if ($inNew) {
483                         if ($value != $this->scope && class_exists($value, FALSE)) {
484                             try {
485                                 $class = new ReflectionClass($value);
486
487                                 if ($class->isUserDefined() && !in_array($value, $this->dependencies)) {
488                                     $this->dependencies[] = $value;
489                                 }
490                             }
491
492                             catch (ReflectionException $e) {
493                             }
494                         }
495                     }
496
497                     $inNew = FALSE;
498                 }
499                 break;
500             }
501         }
502     }
503 }
504 ?>