/tests/simpletest/extensions/coverage/coverage.php

https://github.com/welaika/wordless · PHP · 211 lines · 173 code · 22 blank · 16 comment · 25 complexity · 2b49981614e4d124ec5fb957a5ebf54f MD5 · raw file

  1. <?php
  2. /**
  3. * @package SimpleTest
  4. * @subpackage Extensions
  5. */
  6. /**
  7. * load coverage data handle
  8. */
  9. require_once dirname(__FILE__) . '/coverage_data_handler.php';
  10. /**
  11. * Orchestrates code coverage both in this thread and in subthread under apache
  12. * Assumes this is running on same machine as apache.
  13. * @package SimpleTest
  14. * @subpackage Extensions
  15. */
  16. class CodeCoverage
  17. {
  18. public $log;
  19. public $root;
  20. public $includes;
  21. public $excludes;
  22. public $directoryDepth;
  23. public $maxDirectoryDepth = 20; // reasonable, otherwise arbitrary
  24. public $title = "Code Coverage";
  25. # NOTE: This assumes all code shares the same current working directory.
  26. public $settingsFile = './code-coverage-settings.dat';
  27. public static $instance;
  28. public function writeUntouched()
  29. {
  30. $touched = array_flip($this->getTouchedFiles());
  31. $untouched = array();
  32. $this->getUntouchedFiles($untouched, $touched, '.', '.');
  33. $this->includeUntouchedFiles($untouched);
  34. }
  35. public function &getTouchedFiles()
  36. {
  37. $handler = new CoverageDataHandler($this->log);
  38. $touched = $handler->getFilenames();
  39. return $touched;
  40. }
  41. public function includeUntouchedFiles($untouched)
  42. {
  43. $handler = new CoverageDataHandler($this->log);
  44. foreach ($untouched as $file) {
  45. $handler->writeUntouchedFile($file);
  46. }
  47. }
  48. public function getUntouchedFiles(&$untouched, $touched, $parentPath, $rootPath, $directoryDepth = 1)
  49. {
  50. $parent = opendir($parentPath);
  51. while ($file = readdir($parent)) {
  52. $path = "$parentPath/$file";
  53. if (is_dir($path)) {
  54. if ($file != '.' && $file != '..') {
  55. if ($this->isDirectoryIncluded($path, $directoryDepth)) {
  56. $this->getUntouchedFiles($untouched, $touched, $path, $rootPath, $directoryDepth + 1);
  57. }
  58. }
  59. } elseif ($this->isFileIncluded($path)) {
  60. $relativePath = CoverageDataHandler::ltrim($rootPath .'/', $path);
  61. if (!array_key_exists($relativePath, $touched)) {
  62. $untouched[] = $relativePath;
  63. }
  64. }
  65. }
  66. closedir($parent);
  67. }
  68. public function resetLog()
  69. {
  70. error_log('reseting log');
  71. $new_file = fopen($this->log, "w");
  72. if (!$new_file) {
  73. throw new Exception("Could not create ". $this->log);
  74. }
  75. fclose($new_file);
  76. if (!chmod($this->log, 0666)) {
  77. throw new Exception("Could not change ownership on file ". $this->log);
  78. }
  79. $handler = new CoverageDataHandler($this->log);
  80. $handler->createSchema();
  81. }
  82. public function startCoverage()
  83. {
  84. $this->root = getcwd();
  85. if (!extension_loaded("xdebug")) {
  86. throw new Exception("Could not load xdebug extension");
  87. };
  88. xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
  89. }
  90. public function stopCoverage()
  91. {
  92. $cov = xdebug_get_code_coverage();
  93. $this->filter($cov);
  94. $data = new CoverageDataHandler($this->log);
  95. chdir($this->root);
  96. $data->write($cov);
  97. unset($data); // release sqlite connection
  98. xdebug_stop_code_coverage();
  99. // make sure we wind up on same current working directory, otherwise
  100. // coverage handler writer doesn't know what directory to chop off
  101. chdir($this->root);
  102. }
  103. public function readSettings()
  104. {
  105. if (file_exists($this->settingsFile)) {
  106. $this->setSettings(file_get_contents($this->settingsFile));
  107. } else {
  108. error_log("could not find file ". $this->settingsFile);
  109. }
  110. }
  111. public function writeSettings()
  112. {
  113. file_put_contents($this->settingsFile, $this->getSettings());
  114. }
  115. public function getSettings()
  116. {
  117. $data = array(
  118. 'log' => realpath($this->log),
  119. 'includes' => $this->includes,
  120. 'excludes' => $this->excludes);
  121. return serialize($data);
  122. }
  123. public function setSettings($settings)
  124. {
  125. $data = unserialize($settings);
  126. $this->log = $data['log'];
  127. $this->includes = $data['includes'];
  128. $this->excludes = $data['excludes'];
  129. }
  130. public function filter(&$coverage)
  131. {
  132. foreach ($coverage as $file => $line) {
  133. if (!$this->isFileIncluded($file)) {
  134. unset($coverage[$file]);
  135. }
  136. }
  137. }
  138. public function isFileIncluded($file)
  139. {
  140. if (!empty($this->excludes)) {
  141. foreach ($this->excludes as $path) {
  142. if (preg_match('|' . $path . '|', $file)) {
  143. return false;
  144. }
  145. }
  146. }
  147. if (!empty($this->includes)) {
  148. foreach ($this->includes as $path) {
  149. if (preg_match('|' . $path . '|', $file)) {
  150. return true;
  151. }
  152. }
  153. return false;
  154. }
  155. return true;
  156. }
  157. public function isDirectoryIncluded($dir, $directoryDepth)
  158. {
  159. if ($directoryDepth >= $this->maxDirectoryDepth) {
  160. return false;
  161. }
  162. if (isset($this->excludes)) {
  163. foreach ($this->excludes as $path) {
  164. if (preg_match('|' . $path . '|', $dir)) {
  165. return false;
  166. }
  167. }
  168. }
  169. return true;
  170. }
  171. public static function isCoverageOn()
  172. {
  173. $coverage = self::getInstance();
  174. $coverage->readSettings();
  175. if (empty($coverage->log) || !file_exists($coverage->log)) {
  176. trigger_error('No coverage log');
  177. return false;
  178. }
  179. return true;
  180. }
  181. public static function getInstance()
  182. {
  183. if (self::$instance == null) {
  184. self::$instance = new CodeCoverage();
  185. self::$instance->readSettings();
  186. }
  187. return self::$instance;
  188. }
  189. }