PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/hphp/test/frameworks/utils.php

https://gitlab.com/iranjith4/hhvm
PHP | 455 lines | 377 code | 32 blank | 46 comment | 32 complexity | 2c40fc6b362e4f86e18e7d058acf0469 MD5 | raw file
  1. <?hh
  2. require_once __DIR__.'/SortedIterator.php';
  3. require_once __DIR__.'/Options.php';
  4. class TimeoutException extends Exception {
  5. }
  6. # There's an outer timeout of 300s; this number must be less than that (with
  7. # fudge factor)
  8. const INSTALL_TIMEOUT_SECS = 240;
  9. const NETWORK_RETRIES = 1;
  10. // For determining number of processes
  11. function num_cpus() {
  12. switch(PHP_OS) {
  13. case 'Linux':
  14. $data = file('/proc/stat');
  15. $cores = 0;
  16. foreach($data as $line) {
  17. if (preg_match('/^cpu[0-9]/', $line)) {
  18. $cores++;
  19. }
  20. }
  21. return $cores;
  22. case 'Darwin':
  23. case 'FreeBSD':
  24. return exec('sysctl -n hw.ncpu');
  25. }
  26. return 2; // default when we don't know how to detect
  27. }
  28. function remove_dir_recursive(string $root_dir) {
  29. $files = new RecursiveIteratorIterator(
  30. new RecursiveDirectoryIterator(
  31. $root_dir,
  32. RecursiveDirectoryIterator::SKIP_DOTS),
  33. RecursiveIteratorIterator::CHILD_FIRST);
  34. foreach ($files as $fileinfo) {
  35. if ($fileinfo->isDir()) {
  36. rmdir($fileinfo->getPathname());
  37. } else {
  38. unlink($fileinfo->getPathname());
  39. }
  40. }
  41. rmdir($root_dir);
  42. }
  43. function any_dir_empty_one_level(string $dir): bool {
  44. $files = scandir($dir);
  45. // Get rid of any "." and ".." to check
  46. unset($files[array_search(".",$files)]);
  47. unset($files[array_search("..",$files)]);
  48. foreach ($files as $file) {
  49. if (is_dir($dir."/".$file)) {
  50. // Empty dir will have . and ..
  51. if (count(scandir($dir."/".$file)) <= 2) {
  52. return true;
  53. }
  54. }
  55. }
  56. return false;
  57. }
  58. // Start from self and work down tree. Find first occurrence closest to root
  59. // Works on files or directories.
  60. //
  61. // Note: Wanted to use builtin SPL for this, but it seems like the order cannot
  62. // be guaranteed with their iterators. So found and used a sorted iterator class
  63. // and sorted by the full path including file name.
  64. function find_first_file_recursive(Set $filenames, string $root_dir,
  65. bool $just_path_to_file): ?string {
  66. $dit = new RecursiveDirectoryIterator($root_dir,
  67. RecursiveDirectoryIterator::SKIP_DOTS);
  68. $rit = new RecursiveIteratorIterator($dit);
  69. $sit = new SortedIterator($rit);
  70. foreach ($sit as $fileinfo) {
  71. if ($filenames->contains($fileinfo->getFilename())) {
  72. return $just_path_to_file
  73. ? $fileinfo->getPath()
  74. : $fileinfo->getPathname();
  75. }
  76. }
  77. return null;
  78. }
  79. function find_all_files(string $pattern, string $root_dir,
  80. string $exclude_file_pattern,
  81. ?Set<string> $exclude_dirs = null): ?Set<string> {
  82. if (!file_exists($root_dir)) {
  83. return null;
  84. }
  85. $files = Set {};
  86. $dit = new RecursiveDirectoryIterator($root_dir,
  87. RecursiveDirectoryIterator::SKIP_DOTS);
  88. $rit = new RecursiveIteratorIterator($dit);
  89. $sit = new SortedIterator($rit);
  90. foreach ($sit as $fileinfo) {
  91. if (preg_match($pattern, $fileinfo->getFilename()) === 1 &&
  92. preg_match($exclude_file_pattern, $fileinfo->getFilename()) === 0 &&
  93. strstr($fileinfo->getPath(), '/vendor/') === false &&
  94. !nullthrows($exclude_dirs)->contains(dirname($fileinfo->getPath()))) {
  95. $files[] = $fileinfo->getPathname();
  96. }
  97. }
  98. return $files;
  99. }
  100. function find_all_files_containing_text(
  101. string $text,
  102. string $root_dir,
  103. string $exclude_file_pattern,
  104. ?Set<string> $exclude_dirs = null,
  105. ): ?Set<string> {
  106. if (!file_exists($root_dir)) {
  107. return null;
  108. }
  109. $files = Set {};
  110. $dit = new RecursiveDirectoryIterator($root_dir,
  111. RecursiveDirectoryIterator::SKIP_DOTS);
  112. $rit = new RecursiveIteratorIterator($dit);
  113. $sit = new SortedIterator($rit);
  114. foreach ($sit as $fileinfo) {
  115. if (strpos(file_get_contents($fileinfo->getPathname()), $text) !== false &&
  116. preg_match($exclude_file_pattern, $fileinfo->getFilename()) === 0 &&
  117. strstr($fileinfo->getPath(), '/vendor/') === false &&
  118. !nullthrows($exclude_dirs)->contains(dirname($fileinfo->getPath()))) {
  119. $files[] = $fileinfo->getPathname();
  120. }
  121. }
  122. return $files;
  123. }
  124. function command_exists(string $cmd): bool {
  125. $ret = shell_exec("which $cmd");
  126. return !empty($ret);
  127. }
  128. /**
  129. * Print if output format is for humans
  130. */
  131. function human(string $msg): void {
  132. if (
  133. (Options::$output_format === OutputFormat::HUMAN) ||
  134. (Options::$output_format === OutputFormat::HUMAN_VERBOSE)
  135. ) {
  136. print $msg;
  137. }
  138. }
  139. function fbmake_json(Map<string, mixed> $data) {
  140. if (Options::$output_format === OutputFormat::FBMAKE) {
  141. // Yep, really. STDERR. If you put it on STDOUT instead, 'All tests passed.'
  142. fprintf(STDERR, "%s\n", json_encode($data));
  143. }
  144. }
  145. function fbmake_test_name(Framework $framework, string $test) {
  146. return $framework->getName().'/'.$test;
  147. }
  148. function fbmake_result_json(
  149. Framework $framework,
  150. string $test,
  151. string $status
  152. ): Map<string, mixed> {
  153. if (Options::$output_format !== OutputFormat::FBMAKE) {
  154. return Map { };
  155. }
  156. $expected = $framework->getCurrentTestStatuses();
  157. if ($expected && $expected->containsKey($test)) {
  158. $expected = $expected[$test];
  159. if ($expected === $status) {
  160. return Map {
  161. 'status' => 'passed',
  162. 'details' => 'Matched expected status: '.$status,
  163. };
  164. }
  165. return Map {
  166. 'status' => 'failed',
  167. 'details' => 'Expected '.$expected.', got '.$status,
  168. };
  169. }
  170. return Map {
  171. 'status' => 'failed',
  172. 'details' => 'Unknown test - updated expect file needed?',
  173. };
  174. }
  175. /**
  176. * Print output if verbose mode is on. This implies that the output format
  177. * is human-readable.
  178. */
  179. function verbose(string $msg): void {
  180. if (Options::$output_format === OutputFormat::HUMAN_VERBOSE) {
  181. print $msg;
  182. }
  183. }
  184. /**
  185. * Print output if format is human readable, but not not verbose.
  186. */
  187. function not_verbose(string $msg): void {
  188. if (Options::$output_format === OutputFormat::HUMAN) {
  189. print $msg;
  190. }
  191. }
  192. function remove_color_codes(string $line): string {
  193. // Get rid of codes like ^[[31;31m that may get output to the results file.
  194. // 0x1B is the hex code for the escape sequence ^[
  195. $color_escape_code_pattern = "/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]/";
  196. return preg_replace($color_escape_code_pattern, "", $line);
  197. }
  198. /*
  199. e.g., remove_string_from_text($dir, __DIR__, null);
  200. /data/users/joelm/fbcode/hphp/test/frameworks/frameworks/pear-core/tests
  201. /PEAR_Command_Channels/channel-update/test_remotefile.phpt
  202. becomes
  203. frameworks/pear-core/tests//PEAR_Command_Channels/
  204. channel-update/test_remotefile.phpt
  205. */
  206. function remove_string_from_text(string $text, string $str,
  207. ?string $replace = null): string {
  208. if (($pos = strpos($text, $str)) !== false) {
  209. return $replace === null
  210. ? substr($text, $pos + strlen(__DIR__) + 1)
  211. : substr_replace($text, $replace, $pos, strlen(__DIR__) + 1);
  212. }
  213. return $text;
  214. }
  215. function get_subclasses_of(string $parent): Vector {
  216. $result = Vector {};
  217. foreach (get_declared_classes() as $class) {
  218. if (is_subclass_of($class, $parent)) {
  219. $result[] = strtolower($class);
  220. }
  221. }
  222. sort($result);
  223. return $result;
  224. }
  225. function get_runtime_build(bool $use_php = false): string {
  226. $executable = '';
  227. $command = '';
  228. // FIX: Should we try to install a vanilla php binary here instead of
  229. // relying on user to specify a path? Should we try to determine if php
  230. // is already installed via a $PATH variable?
  231. if (Options::$php_path !== null) {
  232. if (!file_exists(Options::$php_path)) {
  233. error_and_exit("PHP build does not exists. Are you sure your path is ".
  234. "right?");
  235. }
  236. $executable = Options::$php_path;
  237. $command = $executable;
  238. } else {
  239. $fbcode_root_dir = __DIR__.'/../../..';
  240. $oss_root_dir = __DIR__.'/../..';
  241. // See if we are using an internal development build
  242. if ((file_exists($fbcode_root_dir."/_bin"))) {
  243. $executable = $fbcode_root_dir;
  244. $executable .= $use_php ? "/_bin/hphp/hhvm/php" : "/_bin/hphp/hhvm/hhvm";
  245. // Maybe we are in OSS land trying this script
  246. } else if (file_exists($oss_root_dir."/hhvm")) {
  247. // Pear won't run correctly unless a 'php' executable exists.
  248. // This may be a Pear thing, a PHPUnit running phpt thing, or
  249. // or something else. Until we know for sure, let's just create
  250. // a php symlink to hhvm
  251. if (!file_exists($oss_root_dir.'/hhvm/php')) {
  252. symlink($oss_root_dir."/hhvm/hhvm", $oss_root_dir."/hhvm/php");
  253. }
  254. $executable = $oss_root_dir."/hhvm";
  255. $executable .= $use_php ? "/php" : "/hhvm";
  256. } else {
  257. error_and_exit("HHVM build doesn't exist. Did you build yet?");
  258. }
  259. $command = $executable;
  260. if (!$use_php) {
  261. $repo_loc = tempnam('/tmp', 'framework-test');
  262. $repo_args = " -v Repo.Local.Mode=-- -v Repo.Central.Path=".$repo_loc;
  263. $command .= $repo_args.
  264. " --config ".__DIR__."/php.ini";
  265. }
  266. }
  267. invariant(
  268. file_exists($executable),
  269. $executable.' does not exist'
  270. );
  271. invariant(
  272. is_executable($executable),
  273. $executable.' is not executable'
  274. );
  275. return nullthrows($command);
  276. }
  277. function error_and_exit(
  278. string $message,
  279. string $fbmake_action = 'skipped',
  280. ): void {
  281. if (Options::$output_format === OutputFormat::FBMAKE) {
  282. fprintf(
  283. STDERR,
  284. "%s\n",
  285. json_encode(
  286. [
  287. 'op' => 'test_done',
  288. 'test' => 'framework test setup',
  289. 'status' => $fbmake_action,
  290. 'details' => 'ERROR: '.$message,
  291. ],
  292. /* assoc array = */ true,
  293. )
  294. );
  295. exit(0);
  296. }
  297. fprintf(STDERR, "ERROR: %s\n", $message);
  298. exit(1);
  299. }
  300. // Include all PHP files in a directory
  301. function include_all_php($folder){
  302. foreach (glob("{$folder}/*.php") as $filename) {
  303. require_once $filename;
  304. }
  305. }
  306. // This will run processes that will get the test infra dependencies
  307. // (e.g. PHPUnit), frameworks and framework dependencies.
  308. function run_install(
  309. string $proc,
  310. string $path,
  311. ?Map $env = null,
  312. int $retries = NETWORK_RETRIES
  313. ): ?int {
  314. // We need to output something every once in a while - if we go quiet, fbmake
  315. // kills us.
  316. for ($try = 1; $try <= $retries; ++$try) {
  317. $test_name = $proc.' - attempt '.$try;
  318. try {
  319. fbmake_json(Map {'op' => 'start', 'test' => $test_name});
  320. $result = run_install_impl($proc, $path, $env);
  321. fbmake_json(
  322. Map {'op' => 'test_done', 'test' => $test_name, 'status' => 'passed' }
  323. );
  324. return $result;
  325. } catch (TimeoutException $e) {
  326. verbose((string) $e);
  327. remove_dir_recursive(nullthrows($path));
  328. fbmake_json(
  329. Map {'op' => 'test_done', 'test' => $test_name, 'status' => 'skipped' }
  330. );
  331. }
  332. }
  333. error_and_exit('Retries exceeded: '.$proc);
  334. return null; // unrechable, but make the typechecker happy.
  335. }
  336. function run_install_impl(string $proc, string $path, ?Map $env): ?int
  337. {
  338. verbose("Running: $proc\n");
  339. $descriptorspec = array(
  340. 0 => array("pipe", "r"),
  341. 1 => array("pipe", "w"),
  342. 2 => array("pipe", "w"),
  343. );
  344. $env_arr = null; // $_ENV will passed in by default if this is null
  345. if ($env !== null) {
  346. $env_arr = array_merge($_ENV, $env->toArray());
  347. }
  348. // If you have this set, it probably points to hhvm objects, not OSS
  349. // objects. Misses here seem to be a huge slowdown, causing problems with
  350. // fbmake timeouts.
  351. if (
  352. $env_arr !== null &&
  353. array_key_exists('GIT_ALTERNATE_OBJECT_DIRECTORIES', $env_arr)
  354. ) {
  355. unset($env_arr['GIT_ALTERNATE_OBJECT_DIRECTORIES']);
  356. }
  357. $pipes = null;
  358. $process = proc_open($proc, $descriptorspec, $pipes, $path, $env_arr);
  359. assert($pipes !== null);
  360. if (is_resource($process)) {
  361. fclose($pipes[0]);
  362. $start_time = microtime(true);
  363. $read = [$pipes[1]];
  364. $write = [];
  365. $except = $read;
  366. $ready = null;
  367. $done_by = time() + INSTALL_TIMEOUT_SECS;
  368. while ($done_by > time()) {
  369. $remaining = $done_by - time();
  370. $ready = stream_select(
  371. $read, $write, $except,
  372. $remaining > 0 ? $remaining : 1
  373. );
  374. if ($ready === 0) {
  375. proc_terminate($process);
  376. throw new TimeoutException("Hit timeout reading from proc: ".$proc);
  377. }
  378. if (feof($pipes[1])) {
  379. break;
  380. }
  381. $block = fread($pipes[1], 8096);
  382. verbose($block);
  383. if ((microtime(true) - $start_time) > 1) {
  384. not_verbose('.');
  385. $start_time = microtime(true);
  386. }
  387. }
  388. verbose(stream_get_contents($pipes[2]));
  389. fclose($pipes[1]);
  390. $ret = proc_close($process);
  391. verbose("Returned status $ret\n");
  392. return $ret;
  393. }
  394. verbose("Couldn't proc_open: $proc\n");
  395. return null;
  396. }
  397. function nullthrows<T>(?T $x, ?string $message = null): T {
  398. if ($x !== null) {
  399. return $x;
  400. }
  401. if ($message === null) {
  402. $message = 'Unexpected null';
  403. }
  404. throw new Exception($message);
  405. }
  406. // Use this instead of unlink to avoid warnings
  407. function delete_file(?string $path): void {
  408. if ($path !== null && file_exists($path)) {
  409. unlink($path);
  410. }
  411. }