]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - tests/PHPUnit/Util/Metrics/Project.php
Added unit tests.
[Github/sugarcrm.git] / tests / PHPUnit / Util / Metrics / Project.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/Filter.php';
48 require_once 'PHPUnit/Util/Metrics.php';
49
50 PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT');
51
52 /**
53  * Project-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_Project extends PHPUnit_Util_Metrics
65 {
66     protected static $CPD_IGNORE_LIST = array(
67       T_INLINE_HTML,
68       T_COMMENT,
69       T_DOC_COMMENT,
70       T_OPEN_TAG,
71       T_OPEN_TAG_WITH_ECHO,
72       T_CLOSE_TAG,
73       T_WHITESPACE
74     );
75
76     protected $classes   = array();
77     protected $files     = array();
78     protected $functions = array();
79
80     protected $cls     = 0;
81     protected $clsa    = 0;
82     protected $clsc    = 0;
83     protected $interfs = 0;
84     protected $roots   = 0;
85     protected $leafs   = 0;
86     protected $maxDit  = 0;
87
88     protected $cpdDuplicates = array();
89     protected $cpdHashes     = array();
90     protected $dependencies  = array();
91
92     /**
93      * Constructor.
94      *
95      * @param  array   $files
96      * @param  array   $codeCoverage
97      * @param  boolean $cpd
98      * @param  integer $cpdMinLines
99      * @param  integer $cpdMinMatches
100      */
101     public function __construct(Array $files, &$codeCoverage = array(), $cpd = FALSE, $cpdMinLines = 5, $cpdMinMatches = 70)
102     {
103         foreach ($files as $file) {
104             if (file_exists($file)) {
105                 $this->files[$file] = PHPUnit_Util_Metrics_File::factory(
106                   $file, $codeCoverage
107                 );
108
109                 foreach ($this->files[$file]->getFunctions() as $function) {
110                     $this->functions[$function->getFunction()->getName()] = $function;
111                 }
112
113                 foreach ($this->files[$file]->getClasses() as $class) {
114                     $className = $class->getClass()->getName();
115                     $package   = $class->getPackage();
116
117                     $this->classes[$className] = $class;
118
119                     if ($class->getClass()->isInterface()) {
120                         $this->interfs++;
121                     } else {
122                         if ($class->getClass()->isAbstract()) {
123                             $this->clsa++;
124                         } else {
125                             $this->clsc++;
126                         }
127
128                         $this->cls++;
129                     }
130                 }
131             }
132         }
133
134         $numClasses = count($this->classes);
135
136         foreach ($this->classes as $a => $b) {
137             foreach ($this->classes as $c => $d) {
138                 $this->dependencies[$a][$c] = 0;
139             }
140         }
141
142         foreach ($this->classes as $className => $class) {
143             foreach ($class->getDependencies() as $dependency) {
144                 $this->dependencies[$className][$dependency] = 1;
145             }
146
147             $class->setProject($this);
148
149             if ($class->getNOC() == 0) {
150                 $this->leafs++;
151             }
152
153             else if ($class->getClass()->getParentClass() === FALSE) {
154                 $this->roots++;
155             }
156
157             $this->maxDit = max($this->maxDit, $class->getDit());
158         }
159
160         if ($cpd) {
161             $this->copyPasteDetection($cpdMinLines, $cpdMinMatches);
162         }
163     }
164
165     /**
166      * Returns the classes of this project.
167      *
168      * @return array
169      */
170     public function getClasses()
171     {
172         return $this->classes;
173     }
174
175     /**
176      * A class.
177      *
178      * @param  string $className
179      * @return ReflectionClass
180      */
181     public function getClass($className)
182     {
183         return $this->classes[$className];
184     }
185
186     /**
187      * Returns the dependencies between the classes of this project.
188      *
189      * @return array
190      */
191     public function getDependencies()
192     {
193         return $this->dependencies;
194     }
195
196     /**
197      * Returns the dependencies between the classes of this project
198      * as GraphViz/DOT markup.
199      *
200      * @return Image_GraphViz
201      * @since  Method available since Release 3.2.2
202      */
203     public function getDependenciesAsDOT()
204     {
205         if (PHPUnit_Util_Filesystem::fileExistsInIncludePath('Image/GraphViz.php')) {
206             require_once 'Image/GraphViz.php';
207         } else {
208             throw new RuntimeException('Image_GraphViz is not available.');
209         }
210
211         $graph = new Image_GraphViz(
212           TRUE,
213           array(
214             'overlap'  => 'scale',
215             'splines'  => 'true',
216             'sep'      => '.1',
217             'fontsize' => '8'
218           )
219         );
220
221         foreach (array_keys($this->dependencies) as $className) {
222             $graph->addNode($className);
223         }
224
225         foreach ($this->dependencies as $from => $dependencies) {
226             foreach ($dependencies as $to => $flag) {
227                 if ($flag === 1) {
228                     $graph->addEdge(array($from => $to));
229                 }
230             }
231         }
232
233         return $graph;
234     }
235
236     /**
237      * Returns the duplicates found by the Copy & Paste Detection (CPD).
238      *
239      * @return array
240      */
241     public function getDuplicates()
242     {
243         return $this->cpdDuplicates;
244     }
245
246     /**
247      * Returns the files of this project.
248      *
249      * @return array
250      */
251     public function getFiles()
252     {
253         return $this->files;
254     }
255
256     /**
257      * A file.
258      *
259      * @param  string $className
260      * @return ReflectionClass
261      */
262     public function getFile($filename)
263     {
264         return $this->files[$filename];
265     }
266
267     /**
268      * Functions.
269      *
270      * @return array
271      */
272     public function getFunctions()
273     {
274         return $this->functions;
275     }
276
277     /**
278      * A function.
279      *
280      * @param  string $functionName
281      * @return ReflectionClass
282      */
283     public function getFunction($functionName)
284     {
285         return $this->functions[$functionName];
286     }
287
288     /**
289      * Returns the Number of Classes (CLS) for the project.
290      *
291      * @return integer
292      * @see    http://www.aivosto.com/project/help/pm-oo-misc.html
293      */
294     public function getCLS()
295     {
296         return $this->cls;
297     }
298
299     /**
300      * Returns the Number of Abstract Classes (CLSa) for the project.
301      *
302      * @return integer
303      * @see    http://www.aivosto.com/project/help/pm-oo-misc.html
304      */
305     public function getCLSa()
306     {
307         return $this->clsa;
308     }
309
310     /**
311      * Returns the Number of Concrete Classes (CLSc) for the project.
312      *
313      * @return integer
314      * @see    http://www.aivosto.com/project/help/pm-oo-misc.html
315      */
316     public function getCLSc()
317     {
318         return $this->clsc;
319     }
320
321     /**
322      * Returns the Number of Root Classes (ROOTS) for the project.
323      *
324      * @return integer
325      * @see    http://www.aivosto.com/project/help/pm-oo-misc.html
326      */
327     public function getRoots()
328     {
329         return $this->roots;
330     }
331
332     /**
333      * Returns the Number of Leaf Classes (LEAFS) for the project.
334      *
335      * @return integer
336      * @see    http://www.aivosto.com/project/help/pm-oo-misc.html
337      */
338     public function getLeafs()
339     {
340         return $this->leafs;
341     }
342
343     /**
344      * Returns the Number of Interfaces (INTERFS) for the project.
345      *
346      * @return integer
347      * @see    http://www.aivosto.com/project/help/pm-oo-misc.html
348      */
349     public function getInterfs()
350     {
351         return $this->interfs;
352     }
353
354     /**
355      * Returns the Maximum Depth of Intheritance Tree (maxDIT) for the project.
356      *
357      * @return integer
358      * @see    http://www.aivosto.com/project/help/pm-oo-misc.html
359      */
360     public function getMaxDit()
361     {
362         return $this->maxDit;
363     }
364
365     /**
366      * Copy & Paste Detection (CPD).
367      *
368      * @param  integer $minLines
369      * @param  integer $minMatches
370      * @author Johann-Peter Hartmann <johann-peter.hartmann@mayflower.de>
371      */
372     protected function copyPasteDetection($minLines, $minMatches)
373     {
374         foreach ($this->files as $file) {
375             $currentTokenPositions = array();
376             $currentSignature      = '';
377             $tokens                = $file->getTokens();
378             $tokenNr               = 0;
379             $line                  = 1;
380
381             foreach (array_keys($tokens) as $key) {
382                 $token = $tokens[$key];
383
384                 if (is_string($token)) {
385                     $line += substr_count($token, "\n");
386                 } else {
387                     if (!in_array($token[0], self::$CPD_IGNORE_LIST)) {
388                         $currentTokenPositions[$tokenNr++] = $line;
389                         $currentSignature .= chr($token[0] & 255) . pack('N*', crc32($token[1]));
390                     }
391
392                     $line += substr_count($token[1], "\n");
393                 }
394             }
395
396             $tokenNr   = 0;
397             $firstLine = 0;
398             $found     = FALSE;
399
400             if (count($currentTokenPositions) > 0) {
401                 do {
402                     $line = $currentTokenPositions[$tokenNr];
403
404                     $hash = substr(
405                       md5(
406                         substr(
407                           $currentSignature, $tokenNr * 5,
408                           $minMatches * 5
409                         ),
410                         TRUE
411                       ),
412                       0,
413                       8
414                     );
415
416                     if (isset($this->cpdHashes[$hash])) {
417                         $found = TRUE;
418
419                         if ($firstLine === 0) {
420                             $firstLine  = $line;
421                             $firstHash  = $hash;
422                             $firstToken = $tokenNr;
423                         }
424                     } else {
425                         if ($found) {
426                             $fileA      = $this->cpdHashes[$firstHash][0];
427                             $firstLineA = $this->cpdHashes[$firstHash][1];
428
429                             if ($line + 1 - $firstLine > $minLines &&
430                                 ($fileA->getPath() != $file->getPath() ||
431                                  $firstLineA       != $firstLine)) {
432                                 $this->cpdDuplicates[] = array(
433                                   'fileA'      => $fileA,
434                                   'firstLineA' => $firstLineA,
435                                   'fileB'      => $file,
436                                   'firstLineB' => $firstLine,
437                                   'numLines'   => $line    + 1 - $firstLine,
438                                   'numTokens'  => $tokenNr + 1 - $firstToken
439                                 );
440                             }
441
442                             $found     = FALSE;
443                             $firstLine = 0;
444                         }
445
446                         $this->cpdHashes[$hash] = array($file, $line);
447                     }
448
449                     $tokenNr++;
450                 } while ($tokenNr <= (count($currentTokenPositions) -
451                          $minMatches )+1);
452             }
453
454             if ($found) {
455                 $fileA      = $this->cpdHashes[$firstHash][0];
456                 $firstLineA = $this->cpdHashes[$firstHash][1];
457
458                 if ($line + 1 - $firstLine > $minLines &&
459                     ($fileA->getPath() != $file->getPath() ||
460                      $firstLineA       != $firstLine)) {
461                     $this->cpdDuplicates[] = array(
462                       'fileA'      => $fileA,
463                       'firstLineA' => $firstLineA,
464                       'fileB'      => $file,
465                       'firstLineB' => $firstLine,
466                       'numLines'   => $line    + 1 - $firstLine,
467                       'numTokens'  => $tokenNr + 1 - $firstToken
468                     );
469                 }
470
471                 $found = FALSE;
472             }
473         }
474     }
475 }
476 ?>