Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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