LimeCoverage.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. <?php
  2. /*
  3. * This file is part of the Lime framework.
  4. *
  5. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  6. * (c) Bernhard Schussek <bernhard.schussek@symfony-project.com>
  7. *
  8. * This source file is subject to the MIT license that is bundled
  9. * with this source code in the file LICENSE.
  10. */
  11. class LimeCoverage extends LimeRegistration
  12. {
  13. const
  14. COVERED = 1,
  15. UNCOVERED = -1;
  16. protected
  17. $options = array(),
  18. $files = array(),
  19. $suite = null,
  20. $coverage = array(),
  21. $coveredLines = 0,
  22. $uncoveredLines = 0,
  23. $coveredCode = array(),
  24. $uncoveredCode = array(),
  25. $actualCode = array();
  26. public function __construct(LimeTestSuite $suite, array $options = array())
  27. {
  28. $this->suite = $suite;
  29. $this->options = array_merge(array(
  30. 'base_dir' => null,
  31. 'extension' => '.php',
  32. 'verbose' => false,
  33. ), $options);
  34. // temporary solution, LimeRegistration needs to be modified
  35. $this->setBaseDir($this->options['base_dir']);
  36. $this->setExtension($this->options['extension']);
  37. if (!function_exists('xdebug_start_code_coverage'))
  38. {
  39. throw new Exception('You must install and enable xdebug before using lime coverage.');
  40. }
  41. if (!ini_get('xdebug.extended_info'))
  42. {
  43. throw new Exception('You must set xdebug.extended_info to 1 in your php.ini to use lime coverage.');
  44. }
  45. }
  46. public function setFiles($files)
  47. {
  48. if (!is_array($files))
  49. {
  50. $files = array($files);
  51. }
  52. $this->files = $files;
  53. }
  54. public function run()
  55. {
  56. if (!count($this->suite->files))
  57. {
  58. throw new Exception('You must register some test files before running coverage!');
  59. }
  60. if (!count($this->files))
  61. {
  62. throw new Exception('You must register some files to cover!');
  63. }
  64. $this->coverage = array();
  65. $this->process($this->suite->files);
  66. $this->parseCoverage($this->coverage);
  67. $this->render();
  68. }
  69. protected function process(array $files)
  70. {
  71. $this->output = new LimeOutput();
  72. foreach ($files as $file)
  73. {
  74. $command = new LimeShellCommand($file, array('coverage' => true));
  75. $command->execute();
  76. // script failed
  77. if ($command->getStatus() != LimeShell::SUCCESS)
  78. {
  79. $this->output->echoln(sprintf('Warning: %s returned status %d, results may be inaccurate', $file, $command->getStatus()), LimeOutput::ERROR);
  80. }
  81. // script succeeded, coverage not readable
  82. if (false === $coverage = @unserialize($command->getOutput()))
  83. {
  84. if ($command->getStatus() == LimeShell::SUCCESS)
  85. {
  86. throw new Exception(sprintf('Unable to unserialize coverage for file "%s"', $file));
  87. }
  88. }
  89. else
  90. {
  91. foreach ($coverage as $file => $lines)
  92. {
  93. if (!isset($this->coverage[$file]))
  94. {
  95. $this->coverage[$file] = $lines;
  96. }
  97. else
  98. {
  99. foreach ($lines as $line => $flag)
  100. {
  101. if ($flag == self::COVERED)
  102. {
  103. $this->coverage[$file][$line] = 1;
  104. }
  105. }
  106. }
  107. }
  108. }
  109. }
  110. }
  111. protected function parseCoverage(array $coverage)
  112. {
  113. $this->coveredLines = 0;
  114. $this->uncoveredLines = 0;
  115. ksort($coverage);
  116. foreach ($coverage as $file => $lines)
  117. {
  118. $this->coveredCode[$file] = array();
  119. $this->uncoveredCode[$file] = array();
  120. foreach ($lines as $line => $flag)
  121. {
  122. if ($flag == self::COVERED)
  123. {
  124. $this->coveredCode[$file][] = $line;
  125. $this->coveredLines++;
  126. }
  127. else
  128. {
  129. $this->uncoveredCode[$file][] = $line;
  130. $this->uncoveredLines++;
  131. }
  132. }
  133. }
  134. $lexer = new LimeLexerCodeLines();
  135. foreach ($this->files as $file)
  136. {
  137. if (!array_key_exists($file, $this->coveredCode))
  138. {
  139. $this->coveredCode[$file] = array();
  140. $this->uncoveredCode[$file] = $lexer->parse($file);
  141. $this->uncoveredLines += count($this->uncoveredCode[$file]);
  142. }
  143. }
  144. }
  145. protected function render()
  146. {
  147. $printer = new LimePrinter(new LimeColorizer());
  148. foreach ($this->files as $file)
  149. {
  150. $totalLines = count($this->coveredCode[$file]) + count($this->uncoveredCode[$file]);
  151. $percent = count($this->coveredCode[$file]) * 100 / max($totalLines, 1);
  152. $relativeFile = $this->getRelativeFile($file);
  153. if ($percent == 100)
  154. {
  155. $style = LimePrinter::OK;
  156. }
  157. else if ($percent >= 90)
  158. {
  159. $style = LimePrinter::INFO;
  160. }
  161. else if ($percent <= 20)
  162. {
  163. $style = LimePrinter::NOT_OK;
  164. }
  165. else
  166. {
  167. $style = null;
  168. }
  169. $printer->printLine(sprintf("%-76s%3.0f%%", $relativeFile, $percent), $style);
  170. if ($this->options['verbose'] && $percent > 0 && $percent < 100)
  171. {
  172. $printer->printLine(sprintf("missing: %s", $this->formatRange($this->uncoveredCode[$file])), LimePrinter::COMMENT);
  173. }
  174. }
  175. $totalLines = $this->coveredLines + $this->uncoveredLines;
  176. $percent = $this->coveredLines * 100 / max($totalLines, 1);
  177. if ($percent <= 20)
  178. {
  179. $style = LimePrinter::NOT_OK;
  180. }
  181. else
  182. {
  183. $style = LimePrinter::HAPPY;
  184. }
  185. $printer->printLine(str_pad(sprintf(" Total Coverage: %3.0f%%", $percent), 80), $style);
  186. }
  187. protected function formatRange(array $lines)
  188. {
  189. sort($lines);
  190. $formatted = '';
  191. $first = -1;
  192. $last = -1;
  193. foreach ($lines as $line)
  194. {
  195. if ($last + 1 != $line)
  196. {
  197. if ($first != -1)
  198. {
  199. $formatted .= $first == $last ? "$first " : "[$first - $last] ";
  200. }
  201. $first = $line;
  202. $last = $line;
  203. }
  204. else
  205. {
  206. $last = $line;
  207. }
  208. }
  209. if ($first != -1)
  210. {
  211. $formatted .= $first == $last ? "$first " : "[$first - $last] ";
  212. }
  213. return $formatted;
  214. }
  215. }