CodeCoverage.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  1. <?php
  2. /*
  3. * This file is part of the PHP_CodeCoverage package.
  4. *
  5. * (c) Sebastian Bergmann <sebastian@phpunit.de>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. use SebastianBergmann\Environment\Runtime;
  11. /**
  12. * Provides collection functionality for PHP code coverage information.
  13. *
  14. * @since Class available since Release 1.0.0
  15. */
  16. class PHP_CodeCoverage
  17. {
  18. /**
  19. * @var PHP_CodeCoverage_Driver
  20. */
  21. private $driver;
  22. /**
  23. * @var PHP_CodeCoverage_Filter
  24. */
  25. private $filter;
  26. /**
  27. * @var bool
  28. */
  29. private $cacheTokens = false;
  30. /**
  31. * @var bool
  32. */
  33. private $checkForUnintentionallyCoveredCode = false;
  34. /**
  35. * @var bool
  36. */
  37. private $forceCoversAnnotation = false;
  38. /**
  39. * @var bool
  40. */
  41. private $mapTestClassNameToCoveredClassName = false;
  42. /**
  43. * @var bool
  44. */
  45. private $addUncoveredFilesFromWhitelist = true;
  46. /**
  47. * @var bool
  48. */
  49. private $processUncoveredFilesFromWhitelist = false;
  50. /**
  51. * @var mixed
  52. */
  53. private $currentId;
  54. /**
  55. * Code coverage data.
  56. *
  57. * @var array
  58. */
  59. private $data = array();
  60. /**
  61. * @var array
  62. */
  63. private $ignoredLines = array();
  64. /**
  65. * @var bool
  66. */
  67. private $disableIgnoredLines = false;
  68. /**
  69. * Test data.
  70. *
  71. * @var array
  72. */
  73. private $tests = array();
  74. /**
  75. * Constructor.
  76. *
  77. * @param PHP_CodeCoverage_Driver $driver
  78. * @param PHP_CodeCoverage_Filter $filter
  79. * @throws PHP_CodeCoverage_Exception
  80. */
  81. public function __construct(PHP_CodeCoverage_Driver $driver = null, PHP_CodeCoverage_Filter $filter = null)
  82. {
  83. if ($driver === null) {
  84. $driver = $this->selectDriver();
  85. }
  86. if ($filter === null) {
  87. $filter = new PHP_CodeCoverage_Filter;
  88. }
  89. $this->driver = $driver;
  90. $this->filter = $filter;
  91. }
  92. /**
  93. * Returns the PHP_CodeCoverage_Report_Node_* object graph
  94. * for this PHP_CodeCoverage object.
  95. *
  96. * @return PHP_CodeCoverage_Report_Node_Directory
  97. * @since Method available since Release 1.1.0
  98. */
  99. public function getReport()
  100. {
  101. $factory = new PHP_CodeCoverage_Report_Factory;
  102. return $factory->create($this);
  103. }
  104. /**
  105. * Clears collected code coverage data.
  106. */
  107. public function clear()
  108. {
  109. $this->currentId = null;
  110. $this->data = array();
  111. $this->tests = array();
  112. }
  113. /**
  114. * Returns the PHP_CodeCoverage_Filter used.
  115. *
  116. * @return PHP_CodeCoverage_Filter
  117. */
  118. public function filter()
  119. {
  120. return $this->filter;
  121. }
  122. /**
  123. * Returns the collected code coverage data.
  124. * Set $raw = true to bypass all filters.
  125. *
  126. * @param bool $raw
  127. * @return array
  128. * @since Method available since Release 1.1.0
  129. */
  130. public function getData($raw = false)
  131. {
  132. if (!$raw && $this->addUncoveredFilesFromWhitelist) {
  133. $this->addUncoveredFilesFromWhitelist();
  134. }
  135. // We need to apply the blacklist filter a second time
  136. // when no whitelist is used.
  137. if (!$raw && !$this->filter->hasWhitelist()) {
  138. $this->applyListsFilter($this->data);
  139. }
  140. return $this->data;
  141. }
  142. /**
  143. * Sets the coverage data.
  144. *
  145. * @param array $data
  146. * @since Method available since Release 2.0.0
  147. */
  148. public function setData(array $data)
  149. {
  150. $this->data = $data;
  151. }
  152. /**
  153. * Returns the test data.
  154. *
  155. * @return array
  156. * @since Method available since Release 1.1.0
  157. */
  158. public function getTests()
  159. {
  160. return $this->tests;
  161. }
  162. /**
  163. * Sets the test data.
  164. *
  165. * @param array $tests
  166. * @since Method available since Release 2.0.0
  167. */
  168. public function setTests(array $tests)
  169. {
  170. $this->tests = $tests;
  171. }
  172. /**
  173. * Start collection of code coverage information.
  174. *
  175. * @param mixed $id
  176. * @param bool $clear
  177. * @throws PHP_CodeCoverage_Exception
  178. */
  179. public function start($id, $clear = false)
  180. {
  181. if (!is_bool($clear)) {
  182. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  183. 1,
  184. 'boolean'
  185. );
  186. }
  187. if ($clear) {
  188. $this->clear();
  189. }
  190. $this->currentId = $id;
  191. $this->driver->start();
  192. }
  193. /**
  194. * Stop collection of code coverage information.
  195. *
  196. * @param bool $append
  197. * @param mixed $linesToBeCovered
  198. * @param array $linesToBeUsed
  199. * @return array
  200. * @throws PHP_CodeCoverage_Exception
  201. */
  202. public function stop($append = true, $linesToBeCovered = array(), array $linesToBeUsed = array())
  203. {
  204. if (!is_bool($append)) {
  205. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  206. 1,
  207. 'boolean'
  208. );
  209. }
  210. if (!is_array($linesToBeCovered) && $linesToBeCovered !== false) {
  211. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  212. 2,
  213. 'array or false'
  214. );
  215. }
  216. $data = $this->driver->stop();
  217. $this->append($data, null, $append, $linesToBeCovered, $linesToBeUsed);
  218. $this->currentId = null;
  219. return $data;
  220. }
  221. /**
  222. * Appends code coverage data.
  223. *
  224. * @param array $data
  225. * @param mixed $id
  226. * @param bool $append
  227. * @param mixed $linesToBeCovered
  228. * @param array $linesToBeUsed
  229. * @throws PHP_CodeCoverage_Exception
  230. */
  231. public function append(array $data, $id = null, $append = true, $linesToBeCovered = array(), array $linesToBeUsed = array())
  232. {
  233. if ($id === null) {
  234. $id = $this->currentId;
  235. }
  236. if ($id === null) {
  237. throw new PHP_CodeCoverage_Exception;
  238. }
  239. $this->applyListsFilter($data);
  240. $this->applyIgnoredLinesFilter($data);
  241. $this->initializeFilesThatAreSeenTheFirstTime($data);
  242. if (!$append) {
  243. return;
  244. }
  245. if ($id != 'UNCOVERED_FILES_FROM_WHITELIST') {
  246. $this->applyCoversAnnotationFilter(
  247. $data,
  248. $linesToBeCovered,
  249. $linesToBeUsed
  250. );
  251. }
  252. if (empty($data)) {
  253. return;
  254. }
  255. $size = 'unknown';
  256. $status = null;
  257. if ($id instanceof PHPUnit_Framework_TestCase) {
  258. $_size = $id->getSize();
  259. if ($_size == PHPUnit_Util_Test::SMALL) {
  260. $size = 'small';
  261. } elseif ($_size == PHPUnit_Util_Test::MEDIUM) {
  262. $size = 'medium';
  263. } elseif ($_size == PHPUnit_Util_Test::LARGE) {
  264. $size = 'large';
  265. }
  266. $status = $id->getStatus();
  267. $id = get_class($id) . '::' . $id->getName();
  268. } elseif ($id instanceof PHPUnit_Extensions_PhptTestCase) {
  269. $size = 'large';
  270. $id = $id->getName();
  271. }
  272. $this->tests[$id] = array('size' => $size, 'status' => $status);
  273. foreach ($data as $file => $lines) {
  274. if (!$this->filter->isFile($file)) {
  275. continue;
  276. }
  277. foreach ($lines as $k => $v) {
  278. if ($v == PHP_CodeCoverage_Driver::LINE_EXECUTED) {
  279. if (empty($this->data[$file][$k]) || !in_array($id, $this->data[$file][$k])) {
  280. $this->data[$file][$k][] = $id;
  281. }
  282. }
  283. }
  284. }
  285. }
  286. /**
  287. * Merges the data from another instance of PHP_CodeCoverage.
  288. *
  289. * @param PHP_CodeCoverage $that
  290. */
  291. public function merge(PHP_CodeCoverage $that)
  292. {
  293. $this->filter->setBlacklistedFiles(
  294. array_merge($this->filter->getBlacklistedFiles(), $that->filter()->getBlacklistedFiles())
  295. );
  296. $this->filter->setWhitelistedFiles(
  297. array_merge($this->filter->getWhitelistedFiles(), $that->filter()->getWhitelistedFiles())
  298. );
  299. foreach ($that->data as $file => $lines) {
  300. if (!isset($this->data[$file])) {
  301. if (!$this->filter->isFiltered($file)) {
  302. $this->data[$file] = $lines;
  303. }
  304. continue;
  305. }
  306. foreach ($lines as $line => $data) {
  307. if ($data !== null) {
  308. if (!isset($this->data[$file][$line])) {
  309. $this->data[$file][$line] = $data;
  310. } else {
  311. $this->data[$file][$line] = array_unique(
  312. array_merge($this->data[$file][$line], $data)
  313. );
  314. }
  315. }
  316. }
  317. }
  318. $this->tests = array_merge($this->tests, $that->getTests());
  319. }
  320. /**
  321. * @param bool $flag
  322. * @throws PHP_CodeCoverage_Exception
  323. * @since Method available since Release 1.1.0
  324. */
  325. public function setCacheTokens($flag)
  326. {
  327. if (!is_bool($flag)) {
  328. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  329. 1,
  330. 'boolean'
  331. );
  332. }
  333. $this->cacheTokens = $flag;
  334. }
  335. /**
  336. * @since Method available since Release 1.1.0
  337. */
  338. public function getCacheTokens()
  339. {
  340. return $this->cacheTokens;
  341. }
  342. /**
  343. * @param bool $flag
  344. * @throws PHP_CodeCoverage_Exception
  345. * @since Method available since Release 2.0.0
  346. */
  347. public function setCheckForUnintentionallyCoveredCode($flag)
  348. {
  349. if (!is_bool($flag)) {
  350. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  351. 1,
  352. 'boolean'
  353. );
  354. }
  355. $this->checkForUnintentionallyCoveredCode = $flag;
  356. }
  357. /**
  358. * @param bool $flag
  359. * @throws PHP_CodeCoverage_Exception
  360. */
  361. public function setForceCoversAnnotation($flag)
  362. {
  363. if (!is_bool($flag)) {
  364. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  365. 1,
  366. 'boolean'
  367. );
  368. }
  369. $this->forceCoversAnnotation = $flag;
  370. }
  371. /**
  372. * @param bool $flag
  373. * @throws PHP_CodeCoverage_Exception
  374. */
  375. public function setMapTestClassNameToCoveredClassName($flag)
  376. {
  377. if (!is_bool($flag)) {
  378. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  379. 1,
  380. 'boolean'
  381. );
  382. }
  383. $this->mapTestClassNameToCoveredClassName = $flag;
  384. }
  385. /**
  386. * @param bool $flag
  387. * @throws PHP_CodeCoverage_Exception
  388. */
  389. public function setAddUncoveredFilesFromWhitelist($flag)
  390. {
  391. if (!is_bool($flag)) {
  392. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  393. 1,
  394. 'boolean'
  395. );
  396. }
  397. $this->addUncoveredFilesFromWhitelist = $flag;
  398. }
  399. /**
  400. * @param bool $flag
  401. * @throws PHP_CodeCoverage_Exception
  402. */
  403. public function setProcessUncoveredFilesFromWhitelist($flag)
  404. {
  405. if (!is_bool($flag)) {
  406. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  407. 1,
  408. 'boolean'
  409. );
  410. }
  411. $this->processUncoveredFilesFromWhitelist = $flag;
  412. }
  413. /**
  414. * @param bool $flag
  415. * @throws PHP_CodeCoverage_Exception
  416. */
  417. public function setDisableIgnoredLines($flag)
  418. {
  419. if (!is_bool($flag)) {
  420. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  421. 1,
  422. 'boolean'
  423. );
  424. }
  425. $this->disableIgnoredLines = $flag;
  426. }
  427. /**
  428. * Applies the @covers annotation filtering.
  429. *
  430. * @param array $data
  431. * @param mixed $linesToBeCovered
  432. * @param array $linesToBeUsed
  433. * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode
  434. */
  435. private function applyCoversAnnotationFilter(array &$data, $linesToBeCovered, array $linesToBeUsed)
  436. {
  437. if ($linesToBeCovered === false ||
  438. ($this->forceCoversAnnotation && empty($linesToBeCovered))) {
  439. $data = array();
  440. return;
  441. }
  442. if (empty($linesToBeCovered)) {
  443. return;
  444. }
  445. if ($this->checkForUnintentionallyCoveredCode) {
  446. $this->performUnintentionallyCoveredCodeCheck(
  447. $data,
  448. $linesToBeCovered,
  449. $linesToBeUsed
  450. );
  451. }
  452. $data = array_intersect_key($data, $linesToBeCovered);
  453. foreach (array_keys($data) as $filename) {
  454. $_linesToBeCovered = array_flip($linesToBeCovered[$filename]);
  455. $data[$filename] = array_intersect_key(
  456. $data[$filename],
  457. $_linesToBeCovered
  458. );
  459. }
  460. }
  461. /**
  462. * Applies the blacklist/whitelist filtering.
  463. *
  464. * @param array $data
  465. */
  466. private function applyListsFilter(array &$data)
  467. {
  468. foreach (array_keys($data) as $filename) {
  469. if ($this->filter->isFiltered($filename)) {
  470. unset($data[$filename]);
  471. }
  472. }
  473. }
  474. /**
  475. * Applies the "ignored lines" filtering.
  476. *
  477. * @param array $data
  478. */
  479. private function applyIgnoredLinesFilter(array &$data)
  480. {
  481. foreach (array_keys($data) as $filename) {
  482. if (!$this->filter->isFile($filename)) {
  483. continue;
  484. }
  485. foreach ($this->getLinesToBeIgnored($filename) as $line) {
  486. unset($data[$filename][$line]);
  487. }
  488. }
  489. }
  490. /**
  491. * @param array $data
  492. * @since Method available since Release 1.1.0
  493. */
  494. private function initializeFilesThatAreSeenTheFirstTime(array $data)
  495. {
  496. foreach ($data as $file => $lines) {
  497. if ($this->filter->isFile($file) && !isset($this->data[$file])) {
  498. $this->data[$file] = array();
  499. foreach ($lines as $k => $v) {
  500. $this->data[$file][$k] = $v == -2 ? null : array();
  501. }
  502. }
  503. }
  504. }
  505. /**
  506. * Processes whitelisted files that are not covered.
  507. */
  508. private function addUncoveredFilesFromWhitelist()
  509. {
  510. $data = array();
  511. $uncoveredFiles = array_diff(
  512. $this->filter->getWhitelist(),
  513. array_keys($this->data)
  514. );
  515. foreach ($uncoveredFiles as $uncoveredFile) {
  516. if (!file_exists($uncoveredFile)) {
  517. continue;
  518. }
  519. if ($this->processUncoveredFilesFromWhitelist) {
  520. $this->processUncoveredFileFromWhitelist(
  521. $uncoveredFile,
  522. $data,
  523. $uncoveredFiles
  524. );
  525. } else {
  526. $data[$uncoveredFile] = array();
  527. $lines = count(file($uncoveredFile));
  528. for ($i = 1; $i <= $lines; $i++) {
  529. $data[$uncoveredFile][$i] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED;
  530. }
  531. }
  532. }
  533. $this->append($data, 'UNCOVERED_FILES_FROM_WHITELIST');
  534. }
  535. /**
  536. * @param string $uncoveredFile
  537. * @param array $data
  538. * @param array $uncoveredFiles
  539. */
  540. private function processUncoveredFileFromWhitelist($uncoveredFile, array &$data, array $uncoveredFiles)
  541. {
  542. $this->driver->start();
  543. include_once $uncoveredFile;
  544. $coverage = $this->driver->stop();
  545. foreach ($coverage as $file => $fileCoverage) {
  546. if (!isset($data[$file]) &&
  547. in_array($file, $uncoveredFiles)) {
  548. foreach (array_keys($fileCoverage) as $key) {
  549. if ($fileCoverage[$key] == PHP_CodeCoverage_Driver::LINE_EXECUTED) {
  550. $fileCoverage[$key] = PHP_CodeCoverage_Driver::LINE_NOT_EXECUTED;
  551. }
  552. }
  553. $data[$file] = $fileCoverage;
  554. }
  555. }
  556. }
  557. /**
  558. * Returns the lines of a source file that should be ignored.
  559. *
  560. * @param string $filename
  561. * @return array
  562. * @throws PHP_CodeCoverage_Exception
  563. * @since Method available since Release 2.0.0
  564. */
  565. private function getLinesToBeIgnored($filename)
  566. {
  567. if (!is_string($filename)) {
  568. throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory(
  569. 1,
  570. 'string'
  571. );
  572. }
  573. if (!isset($this->ignoredLines[$filename])) {
  574. $this->ignoredLines[$filename] = array();
  575. if ($this->disableIgnoredLines) {
  576. return $this->ignoredLines[$filename];
  577. }
  578. $ignore = false;
  579. $stop = false;
  580. $lines = file($filename);
  581. $numLines = count($lines);
  582. foreach ($lines as $index => $line) {
  583. if (!trim($line)) {
  584. $this->ignoredLines[$filename][] = $index + 1;
  585. }
  586. }
  587. if ($this->cacheTokens) {
  588. $tokens = PHP_Token_Stream_CachingFactory::get($filename);
  589. } else {
  590. $tokens = new PHP_Token_Stream($filename);
  591. }
  592. $classes = array_merge($tokens->getClasses(), $tokens->getTraits());
  593. $tokens = $tokens->tokens();
  594. foreach ($tokens as $token) {
  595. switch (get_class($token)) {
  596. case 'PHP_Token_COMMENT':
  597. case 'PHP_Token_DOC_COMMENT':
  598. $_token = trim($token);
  599. $_line = trim($lines[$token->getLine() - 1]);
  600. if ($_token == '// @codeCoverageIgnore' ||
  601. $_token == '//@codeCoverageIgnore') {
  602. $ignore = true;
  603. $stop = true;
  604. } elseif ($_token == '// @codeCoverageIgnoreStart' ||
  605. $_token == '//@codeCoverageIgnoreStart') {
  606. $ignore = true;
  607. } elseif ($_token == '// @codeCoverageIgnoreEnd' ||
  608. $_token == '//@codeCoverageIgnoreEnd') {
  609. $stop = true;
  610. }
  611. if (!$ignore) {
  612. $start = $token->getLine();
  613. $end = $start + substr_count($token, "\n");
  614. // Do not ignore the first line when there is a token
  615. // before the comment
  616. if (0 !== strpos($_token, $_line)) {
  617. $start++;
  618. }
  619. for ($i = $start; $i < $end; $i++) {
  620. $this->ignoredLines[$filename][] = $i;
  621. }
  622. // A DOC_COMMENT token or a COMMENT token starting with "/*"
  623. // does not contain the final \n character in its text
  624. if (isset($lines[$i-1]) && 0 === strpos($_token, '/*') && '*/' === substr(trim($lines[$i-1]), -2)) {
  625. $this->ignoredLines[$filename][] = $i;
  626. }
  627. }
  628. break;
  629. case 'PHP_Token_INTERFACE':
  630. case 'PHP_Token_TRAIT':
  631. case 'PHP_Token_CLASS':
  632. case 'PHP_Token_FUNCTION':
  633. $docblock = $token->getDocblock();
  634. $this->ignoredLines[$filename][] = $token->getLine();
  635. if (strpos($docblock, '@codeCoverageIgnore') || strpos($docblock, '@deprecated')) {
  636. $endLine = $token->getEndLine();
  637. for ($i = $token->getLine(); $i <= $endLine; $i++) {
  638. $this->ignoredLines[$filename][] = $i;
  639. }
  640. } elseif ($token instanceof PHP_Token_INTERFACE ||
  641. $token instanceof PHP_Token_TRAIT ||
  642. $token instanceof PHP_Token_CLASS) {
  643. if (empty($classes[$token->getName()]['methods'])) {
  644. for ($i = $token->getLine();
  645. $i <= $token->getEndLine();
  646. $i++) {
  647. $this->ignoredLines[$filename][] = $i;
  648. }
  649. } else {
  650. $firstMethod = array_shift(
  651. $classes[$token->getName()]['methods']
  652. );
  653. do {
  654. $lastMethod = array_pop(
  655. $classes[$token->getName()]['methods']
  656. );
  657. } while ($lastMethod !== null &&
  658. substr($lastMethod['signature'], 0, 18) == 'anonymous function');
  659. if ($lastMethod === null) {
  660. $lastMethod = $firstMethod;
  661. }
  662. for ($i = $token->getLine();
  663. $i < $firstMethod['startLine'];
  664. $i++) {
  665. $this->ignoredLines[$filename][] = $i;
  666. }
  667. for ($i = $token->getEndLine();
  668. $i > $lastMethod['endLine'];
  669. $i--) {
  670. $this->ignoredLines[$filename][] = $i;
  671. }
  672. }
  673. }
  674. break;
  675. case 'PHP_Token_NAMESPACE':
  676. $this->ignoredLines[$filename][] = $token->getEndLine();
  677. // Intentional fallthrough
  678. case 'PHP_Token_OPEN_TAG':
  679. case 'PHP_Token_CLOSE_TAG':
  680. case 'PHP_Token_USE':
  681. $this->ignoredLines[$filename][] = $token->getLine();
  682. break;
  683. }
  684. if ($ignore) {
  685. $this->ignoredLines[$filename][] = $token->getLine();
  686. if ($stop) {
  687. $ignore = false;
  688. $stop = false;
  689. }
  690. }
  691. }
  692. $this->ignoredLines[$filename][] = $numLines + 1;
  693. $this->ignoredLines[$filename] = array_unique(
  694. $this->ignoredLines[$filename]
  695. );
  696. sort($this->ignoredLines[$filename]);
  697. }
  698. return $this->ignoredLines[$filename];
  699. }
  700. /**
  701. * @param array $data
  702. * @param array $linesToBeCovered
  703. * @param array $linesToBeUsed
  704. * @throws PHP_CodeCoverage_Exception_UnintentionallyCoveredCode
  705. * @since Method available since Release 2.0.0
  706. */
  707. private function performUnintentionallyCoveredCodeCheck(array &$data, array $linesToBeCovered, array $linesToBeUsed)
  708. {
  709. $allowedLines = $this->getAllowedLines(
  710. $linesToBeCovered,
  711. $linesToBeUsed
  712. );
  713. $message = '';
  714. foreach ($data as $file => $_data) {
  715. foreach ($_data as $line => $flag) {
  716. if ($flag == 1 &&
  717. (!isset($allowedLines[$file]) ||
  718. !isset($allowedLines[$file][$line]))) {
  719. $message .= sprintf(
  720. '- %s:%d' . PHP_EOL,
  721. $file,
  722. $line
  723. );
  724. }
  725. }
  726. }
  727. if (!empty($message)) {
  728. throw new PHP_CodeCoverage_Exception_UnintentionallyCoveredCode(
  729. $message
  730. );
  731. }
  732. }
  733. /**
  734. * @param array $linesToBeCovered
  735. * @param array $linesToBeUsed
  736. * @return array
  737. * @since Method available since Release 2.0.0
  738. */
  739. private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed)
  740. {
  741. $allowedLines = array();
  742. foreach (array_keys($linesToBeCovered) as $file) {
  743. if (!isset($allowedLines[$file])) {
  744. $allowedLines[$file] = array();
  745. }
  746. $allowedLines[$file] = array_merge(
  747. $allowedLines[$file],
  748. $linesToBeCovered[$file]
  749. );
  750. }
  751. foreach (array_keys($linesToBeUsed) as $file) {
  752. if (!isset($allowedLines[$file])) {
  753. $allowedLines[$file] = array();
  754. }
  755. $allowedLines[$file] = array_merge(
  756. $allowedLines[$file],
  757. $linesToBeUsed[$file]
  758. );
  759. }
  760. foreach (array_keys($allowedLines) as $file) {
  761. $allowedLines[$file] = array_flip(
  762. array_unique($allowedLines[$file])
  763. );
  764. }
  765. return $allowedLines;
  766. }
  767. /**
  768. * @return PHP_CodeCoverage_Driver
  769. * @throws PHP_CodeCoverage_Exception
  770. */
  771. private function selectDriver()
  772. {
  773. $runtime = new Runtime;
  774. if (!$runtime->canCollectCodeCoverage()) {
  775. throw new PHP_CodeCoverage_Exception('No code coverage driver available');
  776. }
  777. if ($runtime->isHHVM()) {
  778. return new PHP_CodeCoverage_Driver_HHVM;
  779. } elseif ($runtime->isPHPDBG()) {
  780. return new PHP_CodeCoverage_Driver_PHPDBG;
  781. } else {
  782. return new PHP_CodeCoverage_Driver_Xdebug;
  783. }
  784. }
  785. }