PageRenderTime 1471ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/lib/other/PHPUnit/PerDirTextUI/Command.php

https://github.com/expolit/FTemplate
PHP | 352 lines | 327 code | 4 blank | 21 comment | 6 complexity | 7b6bd181905e6a3826cc511f321b52f9 MD5 | raw file
  1. <?php
  2. /**
  3. * A TestRunner for the Command Line Interface (CLI)
  4. * with support of recurrend directory scan.
  5. *
  6. * @version 0.30
  7. */
  8. class PHPUnit_PerDirTextUI_Command
  9. {
  10. private static $_testDirs = array();
  11. private static $_testManualDirs = array();
  12. private static $_testFilesCache = null;
  13. private static $_testFiles = array();
  14. /**
  15. * Main entry point.
  16. * This method is called by default when this file is included.
  17. *
  18. * @return void
  19. */
  20. public static function main()
  21. {
  22. // Very early initialization.
  23. if (!defined('PHPUnit_MAIN_METHOD')) {
  24. define('PHPUnit_MAIN_METHOD', 'PHPUnit_PerDirTextUI_Command::doNothing');
  25. }
  26. require_once 'PHPUnit/TextUI/Command.php';
  27. PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT');
  28. // Initialize include_path (default pathes - at the end!).
  29. set_include_path(implode(PATH_SEPARATOR, array_merge(array_keys(self::$_testDirs), array(get_include_path()))));
  30. ob_implicit_flush(1);
  31. // Hack for Windows: allow to find php.exe correctly.
  32. if (getenv('COMSPEC')) {
  33. $bin = str_replace('/', '\\', dirname(ini_get('extension_dir')) . "/php.exe");
  34. if (@is_file($bin)) putenv("PHP_PEAR_PHP_BIN=$bin");
  35. } else {
  36. require_once "PEAR/Config.php";
  37. $conf = &PEAR_Config::singleton();
  38. putenv("PHP_PEAR_PHP_BIN=" . $conf->get('php_bin'));
  39. }
  40. // Extract arguments.
  41. $filterMasks = array();
  42. $singleMode = false;
  43. while (@$_SERVER['argv'][1]) {
  44. if ($_SERVER['argv'][1] == "--single") {
  45. // Single mode argument?
  46. $singleMode = true;
  47. array_splice($_SERVER['argv'], 1, 1);
  48. } else {
  49. if (!preg_match('/^--/', $_SERVER['argv'][1])) {
  50. $filterMasks[] = $_SERVER['argv'][1];
  51. array_splice($_SERVER['argv'], 1, 1);
  52. } else {
  53. // Found unknown "--" option. Exit parsing.
  54. break;
  55. }
  56. }
  57. }
  58. // Process arguments.
  59. $testFiles = array();
  60. $list = self::getTestFiles($filterMasks);
  61. foreach ($list as $path => $info) {
  62. $name = $info['name'];
  63. $isManual = $info['isManual'];
  64. $group = $info['group'];
  65. if ($filterMasks) {
  66. foreach ($filterMasks as $mask) {
  67. if ($group == $mask || self::matchMask($mask, $name) || self::matchMask($mask, $path)) {
  68. $testFiles[] = $path;
  69. break;
  70. }
  71. }
  72. } else if (!$isManual) {
  73. $testFiles[] = $path;
  74. }
  75. }
  76. // Print message if we run tests not for all files.
  77. if ($filterMasks) {
  78. if ($testFiles) {
  79. echo "Running tests only in:\n" . join("\n", $testFiles) . "\n\n";
  80. } else {
  81. die("No files matched specified filter masks: " . join(" ", $filterMasks) . "\n");
  82. }
  83. } else {
  84. echo sprintf("Running tests in %d files...\n", count($testFiles));
  85. }
  86. self::$_testFiles = $testFiles;
  87. // Run tests.
  88. if (!$singleMode) {
  89. self::_runTestFiles($testFiles);
  90. } else {
  91. $results = array();
  92. $lineLen = 0;
  93. foreach ($testFiles as $path) {
  94. $result = self::_runSingleTest($path);
  95. $graph = $result[0];
  96. for ($i = 0; $i < strlen($graph); $i++) {
  97. echo $graph[$i];
  98. $lineLen++;
  99. if ($lineLen >= 60) {
  100. $lineLen = 0;
  101. echo "\n";
  102. }
  103. }
  104. flush();
  105. if ($result[1]) {
  106. $results[] = $result[1];
  107. }
  108. }
  109. echo "\n";
  110. if ($results) {
  111. echo "\nThere was failures:\n\n";
  112. echo trim(join("", $results));
  113. echo "\n\nFAILURES!\n";
  114. }
  115. }
  116. }
  117. /**
  118. * Run a single test in the separate process and parse its result.
  119. *
  120. * @param string $path
  121. * @return array array(dotGraph, failureText)
  122. */
  123. private static function _runSingleTest($path)
  124. {
  125. $cmd = getenv('PHP_PEAR_PHP_BIN') // do not escapeshellarg() for php_bin - windows bug!
  126. . ' ' . escapeshellarg($_SERVER['argv'][0])
  127. . ' ' . escapeshellarg($path);
  128. $result = shell_exec($cmd);
  129. $m = $p = null;
  130. if (!preg_match('/^PHPUnit \d+.*\n\s*(\S+)\s*([\s\S]*)/m', $result, $m)) {
  131. return array("?", "");
  132. }
  133. if (preg_match('/.*?(^\d+\)[\s\S]*)^FAILURES![\s\S]*/m', $m[2], $p)) {
  134. return array($m[1], $p[1]);
  135. } else {
  136. return array($m[1], null);
  137. }
  138. }
  139. /**
  140. * Run multiple tests in current process.
  141. *
  142. * @param array $testFiles
  143. * @return void
  144. */
  145. private static function _runTestFiles($testFiles)
  146. {
  147. // Create temporary config file.
  148. $xml = array();
  149. foreach ($testFiles as $file) {
  150. $xml[] = "<file>" . htmlspecialchars($file) . "</file>";
  151. }
  152. $xml = "<phpunit><testsuite name=\"Overall\">" . join("", $xml) . "</testsuite></phpunit>";
  153. $tmp = tempnam('non-existent', 'phpunit');
  154. file_put_contents($tmp, $xml);
  155. // Run all the tests.
  156. //$_SERVER['argv'][] = '--no-syntax-check'; // speedup!
  157. $_SERVER['argv'][] = '--configuration';
  158. $_SERVER['argv'][] = $tmp;
  159. PHPUnit_TextUI_Command::main();
  160. }
  161. /**
  162. * Add a new test directory to the list.
  163. *
  164. * @param string $dir Directory to scan
  165. * @param string $fnSuffix Required test filename suffix.
  166. * @return void
  167. */
  168. public static function addTestDir($dir, $fnSuffix = "")
  169. {
  170. self::$_testDirs[realpath($dir)] = array('suffix' => $fnSuffix, 'isManual' => false);
  171. self::$_testFilesCache = null;
  172. }
  173. /**
  174. * Add a new test directory which is NOT used while running all the tests.
  175. *
  176. * @param string $dir Directory to scan
  177. * @param string $fnSuffix Required test filename suffix.
  178. * @return void
  179. */
  180. public static function addManualTestDir($dir, $fnSuffix = "")
  181. {
  182. self::$_testDirs[realpath($dir)] = array('suffix' => $fnSuffix, 'isManual' => true);
  183. self::$_testFilesCache = null;
  184. }
  185. /**
  186. * Return all test files found in added directories.
  187. *
  188. * @return array Key - full pathname, value - array('name' => <test filename
  189. * with path relative to a directory added by addTestDir()>,
  190. * 'isManual' => <is this file for manual mash filtering>).
  191. */
  192. public static function getTestFiles($filterMasks)
  193. {
  194. if (self::$_testFilesCache) {
  195. return self::$_testFilesCache;
  196. }
  197. if (count($filterMasks) == 1 && preg_match('{^(\w:)?[/\\\\][^*?]+$}s', $filterMasks[0])) {
  198. // Speed optimization to run a single test file specified
  199. // by an absolute path.
  200. $files[$filterMasks[0]] = $filterMasks[0];
  201. } else {
  202. // Use the full directory scan.
  203. $files = array();
  204. foreach (self::$_testDirs as $dir => $info) {
  205. $fnSuffix = $info['suffix'];
  206. $isManual = $info['isManual'];
  207. $dir = str_replace('\\', '/', $dir);
  208. $cwd = getcwd();
  209. chdir($dir);
  210. $elements = array();
  211. self::globRecurrent(".", $elements);
  212. foreach ($elements as $e) {
  213. if (!is_file($e) || !preg_match('/' . $fnSuffix . '\.phpt?$/i', $e)) continue;
  214. $path = preg_replace('{^\.[/\\\\]}si', '', (string)$e);
  215. $path = str_replace('\\', '/', $path);
  216. $name = preg_replace('{\.phpt?$}s', '', $path);
  217. $files[$dir . '/' . $path] = array('name' => $name, 'isManual' => $isManual, 'group' => basename($dir));
  218. }
  219. chdir($cwd);
  220. }
  221. }
  222. return self::$_testFilesCache = $files;
  223. }
  224. /**
  225. * Builds recurrent directory listing. RecursiveDirectoryIterator
  226. * is bad, because it throws exception on "permission denied" error.
  227. *
  228. * @param string $dir
  229. * @param array &$files
  230. * @return void
  231. */
  232. public static function globRecurrent($dir, &$files)
  233. {
  234. foreach (glob("$dir/*") as $e) {
  235. $files[] = $e;
  236. if (is_dir($e) && !is_link($e) && @file_exists("$e/.")) {
  237. self::globRecurrent($e, $files);
  238. }
  239. }
  240. }
  241. /**
  242. * Returns the list of all filtered test classes
  243. *
  244. * @return array
  245. */
  246. public static function getFilteredClasses()
  247. {
  248. $classes = array();
  249. foreach (self::$_testFiles as $file) {
  250. $file = str_replace('\\', '/', preg_replace('/\..*$/s', '', realpath($file)));
  251. if (!$file) continue;
  252. $bestClass = null;
  253. $bestInc = null;
  254. foreach (explode(PATH_SEPARATOR, get_include_path()) as $inc) {
  255. $inc = str_replace('\\', '/', realpath($inc));
  256. if (strlen($inc) < strlen($bestInc)) {
  257. // Select most long include_path directory.
  258. continue;
  259. }
  260. if ("$inc/" == substr($file, 0, strlen($inc) + 1)) {
  261. $bestClass = str_replace('/', '_', substr($file, strlen($inc) + 1));
  262. $bestInc = $inc;
  263. }
  264. }
  265. if ($bestClass) {
  266. $classes[] = $bestClass;
  267. }
  268. }
  269. return $classes;
  270. }
  271. /**
  272. * Return the maximum common namespace used by a class list
  273. * or NULL if no common namespace exists.
  274. *
  275. * @param array $classes
  276. * @return string
  277. */
  278. public static function getMaxCommonNamespace($classes)
  279. {
  280. if (!$classes) {
  281. return null;
  282. }
  283. $listOfParts = array();
  284. foreach ($classes as $class) {
  285. $parts = array();
  286. foreach (explode("_", $class) as $part) {
  287. if (strtolower($part) == "test") continue;
  288. $parts[] = preg_replace('/Test$/s', '', $part);
  289. }
  290. $listOfParts[] = $parts;
  291. }
  292. for ($col = 0; $col < count($listOfParts[0]); $col++) {
  293. $sameColumn = true;
  294. foreach ($listOfParts as $part) {
  295. if (@$part[$col] != @$listOfParts[0][$col]) {
  296. $sameColumn = false;
  297. break;
  298. }
  299. }
  300. if (!$sameColumn) {
  301. return $col > 0? join("_", array_slice($listOfParts[0], 0, $col)) : null;
  302. }
  303. }
  304. return null;
  305. }
  306. /**
  307. * Returns true if a given filename matches the specified mask.
  308. * "*" in the mask may match not only pathname character, but "/" too.
  309. *
  310. * @param string $pattern
  311. * @param string $string
  312. * @return bool
  313. */
  314. public function matchMask($pattern, $string)
  315. {
  316. if (false === strpos($pattern, '/') && false === strpos($pattern, '\\') && false === strpos($pattern, '**')) {
  317. $string = basename($string);
  318. }
  319. $re = strtr(
  320. addcslashes($pattern, '/\\.+^$(){}=!<>|'),
  321. array('**' => '.*', '*' => '[^/\\\\]*', '?' => '.?')
  322. );
  323. return @preg_match('{^(?:' . $re . ')$}i', $string);
  324. }
  325. /**
  326. * A stub function to avoid PHPUnit_TextUI_Command::main()
  327. * automatic calling.
  328. *
  329. * @return void
  330. */
  331. public function doNothing()
  332. {
  333. }
  334. }