PageRenderTime 24ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/script/lib/PHP/CodeCoverage.php

https://bitbucket.org/chamilo/chamilo-dev/
PHP | 664 lines | 376 code | 82 blank | 206 comment | 71 complexity | 7cdf5d5087a17d7adbc27162ab7c33fa MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, LGPL-2.1, LGPL-3.0, GPL-3.0, MIT
  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. require_once 'PHP/CodeCoverage/Driver/Xdebug.php';
  46. require_once 'PHP/CodeCoverage/Filter.php';
  47. require_once 'PHP/CodeCoverage/Util.php';
  48. /**
  49. * Provides collection functionality for PHP code coverage information.
  50. *
  51. * @category PHP
  52. * @package CodeCoverage
  53. * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
  54. * @copyright 2009-2011 Sebastian Bergmann <sb@sebastian-bergmann.de>
  55. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  56. * @version Release: 1.0.3
  57. * @link http://github.com/sebastianbergmann/php-code-coverage
  58. * @since Class available since Release 1.0.0
  59. */
  60. class PHP_CodeCoverage
  61. {
  62. /**
  63. * @var PHP_CodeCoverage_Driver
  64. */
  65. protected $driver;
  66. /**
  67. * @var PHP_CodeCoverage_Filter
  68. */
  69. protected $filter;
  70. /**
  71. * @var boolean
  72. */
  73. protected $forceCoversAnnotation = FALSE;
  74. /**
  75. * @var boolean
  76. */
  77. protected $mapTestClassNameToCoveredClassName = FALSE;
  78. /**
  79. * @var boolean
  80. */
  81. protected $processUncoveredFilesFromWhitelist = TRUE;
  82. /**
  83. * @var mixed
  84. */
  85. protected $currentId;
  86. /**
  87. * List of covered files.
  88. *
  89. * @var array
  90. */
  91. protected $coveredFiles = array();
  92. /**
  93. * Raw code coverage data.
  94. *
  95. * @var array
  96. */
  97. protected $data = array();
  98. /**
  99. * Summarized code coverage data.
  100. *
  101. * @var array
  102. */
  103. protected $summary = array();
  104. /**
  105. * Test data.
  106. *
  107. * @var array
  108. */
  109. protected $tests = array();
  110. /**
  111. * @var boolean
  112. */
  113. protected $isCodeCoverageTestSuite = FALSE;
  114. /**
  115. * @var boolean
  116. */
  117. protected $isFileIteratorTestSuite = FALSE;
  118. /**
  119. * @var boolean
  120. */
  121. protected $isTimerTestSuite = FALSE;
  122. /**
  123. * @var boolean
  124. */
  125. protected $isTokenStreamTestSuite = FALSE;
  126. /**
  127. * Default PHP_CodeCoverage object.
  128. *
  129. * @var PHP_CodeCoverage
  130. */
  131. protected static $instance;
  132. /**
  133. * Constructor.
  134. *
  135. * @param PHP_CodeCoverage_Driver $driver
  136. * @param PHP_CodeCoverage_Filter $filter
  137. * @throws InvalidArgumentException
  138. */
  139. public function __construct(PHP_CodeCoverage_Driver $driver = NULL, PHP_CodeCoverage_Filter $filter = NULL)
  140. {
  141. if ($driver === NULL)
  142. {
  143. $driver = new PHP_CodeCoverage_Driver_Xdebug();
  144. }
  145. if ($filter === NULL)
  146. {
  147. $filter = PHP_CodeCoverage_Filter :: getInstance();
  148. }
  149. $this->driver = $driver;
  150. $this->filter = $filter;
  151. if (defined('PHP_CODECOVERAGE_TESTSUITE'))
  152. {
  153. $this->isCodeCoverageTestSuite = TRUE;
  154. }
  155. if (defined('FILE_ITERATOR_TESTSUITE'))
  156. {
  157. $this->isFileIteratorTestSuite = TRUE;
  158. }
  159. if (defined('PHP_TIMER_TESTSUITE'))
  160. {
  161. $this->isTimerTestSuite = TRUE;
  162. }
  163. if (defined('PHP_TOKENSTREAM_TESTSUITE'))
  164. {
  165. $this->isTokenStreamTestSuite = TRUE;
  166. }
  167. }
  168. /**
  169. * Returns the default instance.
  170. *
  171. * @return PHP_CodeCoverage
  172. */
  173. public static function getInstance()
  174. {
  175. if (self :: $instance === NULL)
  176. {
  177. // @codeCoverageIgnoreStart
  178. self :: $instance = new PHP_CodeCoverage();
  179. }
  180. // @codeCoverageIgnoreEnd
  181. return self :: $instance;
  182. }
  183. /**
  184. * Start collection of code coverage information.
  185. *
  186. * @param mixed $id
  187. * @param boolean $clear
  188. * @throws InvalidArgumentException
  189. */
  190. public function start($id, $clear = FALSE)
  191. {
  192. if (! is_bool($clear))
  193. {
  194. throw new InvalidArgumentException();
  195. }
  196. if ($clear)
  197. {
  198. $this->clear();
  199. }
  200. $this->currentId = $id;
  201. $this->driver->start();
  202. }
  203. /**
  204. * Stop collection of code coverage information.
  205. *
  206. * @param boolean $append
  207. * @return array
  208. * @throws InvalidArgumentException
  209. */
  210. public function stop($append = TRUE)
  211. {
  212. if (! is_bool($append))
  213. {
  214. throw new InvalidArgumentException();
  215. }
  216. $data = $this->driver->stop();
  217. if ($append)
  218. {
  219. $this->append($data);
  220. }
  221. $this->currentId = NULL;
  222. return $data;
  223. }
  224. /**
  225. * Appends code coverage data.
  226. *
  227. * @param array $data
  228. * @param mixed $id
  229. * @param array $filterGroups
  230. */
  231. public function append(array $data, $id = NULL, array $filterGroups = array('DEFAULT'))
  232. {
  233. if ($id === NULL)
  234. {
  235. $id = $this->currentId;
  236. }
  237. if ($id === NULL)
  238. {
  239. throw new InvalidArgumentException();
  240. }
  241. $this->applySelfFilter($data);
  242. $this->applyListsFilter($data, $filterGroups);
  243. $raw = $data;
  244. $this->applyCoversAnnotationFilter($data, $id);
  245. if (! empty($data))
  246. {
  247. if ($id instanceof PHPUnit_Framework_TestCase)
  248. {
  249. $status = $id->getStatus();
  250. $id = get_class($id) . '::' . $id->getName();
  251. $this->tests[$id] = $status;
  252. }
  253. else
  254. if ($id instanceof PHPUnit_Extensions_PhptTestCase)
  255. {
  256. $id = $id->getName();
  257. }
  258. $this->coveredFiles = array_unique(array_merge($this->coveredFiles, array_keys($data)));
  259. $this->data[$id] = array('filtered' => $data, 'raw' => $raw);
  260. $this->summary = array();
  261. }
  262. }
  263. /**
  264. * Merges the data from another instance of PHP_CodeCoverage.
  265. *
  266. * @param PHP_CodeCoverage $that
  267. */
  268. public function merge(PHP_CodeCoverage $that)
  269. {
  270. foreach ($that->data as $id => $data)
  271. {
  272. if (! isset($this->data[$id]))
  273. {
  274. $this->data[$id] = $data;
  275. }
  276. else
  277. {
  278. foreach (array('filtered', 'raw') as $type)
  279. {
  280. foreach ($data[$type] as $file => $lines)
  281. {
  282. if (! isset($this->data[$id][$type][$file]))
  283. {
  284. $this->data[$id][$type][$file] = $lines;
  285. }
  286. else
  287. {
  288. foreach ($lines as $line => $flag)
  289. {
  290. if (! isset($this->data[$id][$type][$file][$line]) || $flag > $this->data[$id][$type][$file][$line])
  291. {
  292. $this->data[$id][$type][$file][$line] = $flag;
  293. }
  294. }
  295. }
  296. }
  297. }
  298. }
  299. }
  300. foreach ($that->tests as $id => $status)
  301. {
  302. if (! isset($this->tests[$id]) || $status > $this->tests[$id])
  303. {
  304. $this->tests[$id] = $status;
  305. }
  306. }
  307. $this->coveredFiles = array_unique(array_merge($this->coveredFiles, $that->coveredFiles));
  308. $this->summary = array();
  309. }
  310. /**
  311. * Returns summarized code coverage data.
  312. *
  313. * Format of the result array:
  314. *
  315. * <code>
  316. * array(
  317. * "/tested/code.php" => array(
  318. * linenumber => array(tests that executed the line)
  319. * )
  320. * )
  321. * </code>
  322. *
  323. * @return array
  324. */
  325. public function getSummary()
  326. {
  327. if (empty($this->summary))
  328. {
  329. if ($this->processUncoveredFilesFromWhitelist)
  330. {
  331. $this->processUncoveredFilesFromWhitelist();
  332. }
  333. foreach ($this->data as $test => $coverage)
  334. {
  335. foreach ($coverage['filtered'] as $file => $lines)
  336. {
  337. foreach ($lines as $line => $flag)
  338. {
  339. if ($flag == 1)
  340. {
  341. if (! isset($this->summary[$file][$line][0]))
  342. {
  343. $this->summary[$file][$line] = array();
  344. }
  345. if (isset($this->tests[$test]))
  346. {
  347. $status = $this->tests[$test];
  348. }
  349. else
  350. {
  351. $status = NULL;
  352. }
  353. $this->summary[$file][$line][] = array('id' => $test, 'status' => $status);
  354. }
  355. }
  356. }
  357. foreach ($coverage['raw'] as $file => $lines)
  358. {
  359. foreach ($lines as $line => $flag)
  360. {
  361. if ($flag != 1 && ! isset($this->summary[$file][$line][0]))
  362. {
  363. $this->summary[$file][$line] = $flag;
  364. }
  365. }
  366. }
  367. }
  368. foreach ($this->summary as &$file)
  369. {
  370. ksort($file);
  371. }
  372. ksort($this->summary);
  373. }
  374. return $this->summary;
  375. }
  376. /**
  377. * Clears collected code coverage data.
  378. */
  379. public function clear()
  380. {
  381. $this->data = array();
  382. $this->coveredFiles = array();
  383. $this->summary = array();
  384. $this->currentId = NULL;
  385. }
  386. /**
  387. * Returns the PHP_CodeCoverage_Filter used.
  388. *
  389. * @return PHP_CodeCoverage_Filter
  390. */
  391. public function filter()
  392. {
  393. return $this->filter;
  394. }
  395. /**
  396. * @param boolean $flag
  397. * @throws InvalidArgumentException
  398. */
  399. public function setForceCoversAnnotation($flag)
  400. {
  401. if (! is_bool($flag))
  402. {
  403. throw new InvalidArgumentException();
  404. }
  405. $this->forceCoversAnnotation = $flag;
  406. }
  407. /**
  408. * @param boolean $flag
  409. * @throws InvalidArgumentException
  410. */
  411. public function setMapTestClassNameToCoveredClassName($flag)
  412. {
  413. if (! is_bool($flag))
  414. {
  415. throw new InvalidArgumentException();
  416. }
  417. $this->mapTestClassNameToCoveredClassName = $flag;
  418. }
  419. /**
  420. * @param boolean $flag
  421. * @throws InvalidArgumentException
  422. */
  423. public function setProcessUncoveredFilesFromWhitelist($flag)
  424. {
  425. if (! is_bool($flag))
  426. {
  427. throw new InvalidArgumentException();
  428. }
  429. $this->processUncoveredFilesFromWhitelist = $flag;
  430. }
  431. /**
  432. * Filters sourcecode files from PHP_CodeCoverage, PHP_TokenStream,
  433. * Text_Template, and File_Iterator.
  434. *
  435. * @param array $data
  436. */
  437. protected function applySelfFilter(&$data)
  438. {
  439. foreach (array_keys($data) as $filename)
  440. {
  441. if (! $this->filter->isFile($filename))
  442. {
  443. unset($data[$filename]);
  444. continue;
  445. }
  446. if (! $this->isCodeCoverageTestSuite && strpos($filename, dirname(__FILE__)) === 0)
  447. {
  448. unset($data[$filename]);
  449. continue;
  450. }
  451. if (! $this->isFileIteratorTestSuite && (substr($filename, - 17) == 'File/Iterator.php' || substr($filename, - 25) == 'File/Iterator/Factory.php'))
  452. {
  453. unset($data[$filename]);
  454. continue;
  455. }
  456. if (! $this->isTimerTestSuite && (substr($filename, - 13) == 'PHP/Timer.php'))
  457. {
  458. unset($data[$filename]);
  459. continue;
  460. }
  461. if (! $this->isTokenStreamTestSuite && (substr($filename, - 13) == 'PHP/Token.php' || substr($filename, - 20) == 'PHP/Token/Stream.php' || substr($filename, - 35) == 'PHP/Token/Stream/CachingFactory.php'))
  462. {
  463. unset($data[$filename]);
  464. continue;
  465. }
  466. if (substr($filename, - 17) == 'Text/Template.php')
  467. {
  468. unset($data[$filename]);
  469. }
  470. }
  471. }
  472. /**
  473. * Applies the blacklist/whitelist filtering.
  474. *
  475. * @param array $data
  476. * @param array $filterGroups
  477. */
  478. protected function applyListsFilter(&$data, $filterGroups)
  479. {
  480. foreach (array_keys($data) as $filename)
  481. {
  482. if ($this->filter->isFiltered($filename, $filterGroups))
  483. {
  484. unset($data[$filename]);
  485. }
  486. }
  487. }
  488. /**
  489. * Applies the @covers annotation filtering.
  490. *
  491. * @param array $data
  492. * @param mixed $id
  493. */
  494. protected function applyCoversAnnotationFilter(&$data, $id)
  495. {
  496. if ($id instanceof PHPUnit_Framework_TestCase)
  497. {
  498. $testClassName = get_class($id);
  499. $linesToBeCovered = PHP_CodeCoverage_Util :: getLinesToBeCovered($testClassName, $id->getName());
  500. if ($this->mapTestClassNameToCoveredClassName && empty($linesToBeCovered))
  501. {
  502. $testedClass = substr($testClassName, 0, - 4);
  503. if (class_exists($testedClass))
  504. {
  505. $class = new ReflectionClass($testedClass);
  506. $linesToBeCovered = array(
  507. $class->getFileName() => range($class->getStartLine(), $class->getEndLine()));
  508. }
  509. }
  510. }
  511. else
  512. {
  513. $linesToBeCovered = array();
  514. }
  515. if (! empty($linesToBeCovered))
  516. {
  517. $data = array_intersect_key($data, $linesToBeCovered);
  518. foreach (array_keys($data) as $filename)
  519. {
  520. $data[$filename] = array_intersect_key($data[$filename], array_flip($linesToBeCovered[$filename]));
  521. }
  522. }
  523. else
  524. if ($this->forceCoversAnnotation)
  525. {
  526. $data = array();
  527. }
  528. }
  529. /**
  530. * Processes whitelisted files that are not covered.
  531. */
  532. protected function processUncoveredFilesFromWhitelist()
  533. {
  534. $data = array();
  535. $includedFiles = array_flip(get_included_files());
  536. $uncoveredFiles = array_diff($this->filter->getWhitelist(), $this->coveredFiles);
  537. foreach ($uncoveredFiles as $uncoveredFile)
  538. {
  539. if (isset($includedFiles[$uncoveredFile]))
  540. {
  541. foreach (array_keys($this->data) as $test)
  542. {
  543. if (isset($this->data[$test]['raw'][$uncoveredFile]))
  544. {
  545. $coverage = $this->data[$test]['raw'][$uncoveredFile];
  546. foreach (array_keys($coverage) as $key)
  547. {
  548. if ($coverage[$key] == 1)
  549. {
  550. $coverage[$key] = - 1;
  551. }
  552. }
  553. $data[$uncoveredFile] = $coverage;
  554. break;
  555. }
  556. }
  557. }
  558. else
  559. {
  560. $this->driver->start();
  561. include_once $uncoveredFile;
  562. $coverage = $this->driver->stop();
  563. foreach ($coverage as $file => $fileCoverage)
  564. {
  565. if (! isset($data[$file]) && in_array($file, $uncoveredFiles))
  566. {
  567. foreach (array_keys($fileCoverage) as $key)
  568. {
  569. if ($fileCoverage[$key] == 1)
  570. {
  571. $fileCoverage[$key] = - 1;
  572. }
  573. }
  574. $data[$file] = $fileCoverage;
  575. $includedFiles[$file] = TRUE;
  576. }
  577. }
  578. }
  579. }
  580. $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
  581. }
  582. }