PageRenderTime 54ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/script/lib/PHP/CodeCoverage/Util.php

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