PageRenderTime 173ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 1ms

/hphp/test/run

http://github.com/facebook/hiphop-php
PHP | 2563 lines | 1986 code | 250 blank | 327 comment | 340 complexity | 01170663f231c4a4d7d55fde22cf6d9f MD5 | raw file
Possible License(s): LGPL-2.1, BSD-2-Clause, BSD-3-Clause, MPL-2.0-no-copyleft-exception, MIT, LGPL-2.0, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. #!/usr/bin/env php
  2. <?php
  3. /**
  4. * Run the test suites in various configurations.
  5. */
  6. function is_testing_dso_extension() {
  7. // detecting if we're running outside of the hhvm codebase.
  8. return !is_file(__DIR__ . "/../../hphp/test/run");
  9. }
  10. function get_expect_file_and_type($test, $options) {
  11. // .typechecker files are for typechecker (hh_server --check) test runs.
  12. $types = null;
  13. if (isset($options['typechecker'])) {
  14. $types = array('typechecker.expect', 'typechecker.expectf');
  15. } else {
  16. $types = array('expect', 'hhvm.expect', 'expectf', 'hhvm.expectf',
  17. 'expectregex');
  18. }
  19. if (isset($options['repo'])) {
  20. foreach ($types as $type) {
  21. $fname = "$test.$type-repo";
  22. if (file_exists($fname)) {
  23. return array($fname, $type);
  24. }
  25. }
  26. }
  27. foreach ($types as $type) {
  28. $fname = "$test.$type";
  29. if (file_exists($fname)) {
  30. return array($fname, $type);
  31. }
  32. }
  33. return array(null, null);
  34. }
  35. function usage() {
  36. global $argv;
  37. return "usage: $argv[0] [-m jit|interp] [-r] <test/directories>";
  38. }
  39. function help() {
  40. global $argv;
  41. $ztestexample = 'test/zend/good/*/*z*.php'; // sep. for syntax highlighting.
  42. $help = <<<EOT
  43. This is the hhvm test-suite runner. For more detailed documentation,
  44. see hphp/test/README.md.
  45. The test argument may be a path to a php test file, a directory name, or
  46. one of a few pre-defined suite names that this script knows about.
  47. If you work with hhvm a lot, you might consider a bash alias:
  48. alias ht="path/to/hphp/test/run"
  49. Examples:
  50. # Quick tests in JIT mode:
  51. % $argv[0] test/quick
  52. # Slow tests in interp mode:
  53. % $argv[0] -m interp test/slow
  54. # PHP specificaion tests in JIT mode:
  55. % $argv[0] test/spec
  56. # Slow closure tests in JIT mode:
  57. % $argv[0] test/slow/closure
  58. # Slow closure tests in JIT mode with RepoAuthoritative:
  59. % $argv[0] -r test/slow/closure
  60. # Slow array tests, in RepoAuthoritative:
  61. % $argv[0] -r test/slow/array
  62. # Zend tests with a "z" in their name:
  63. % $argv[0] $ztestexample
  64. # Quick tests in JIT mode with some extra runtime options:
  65. % $argv[0] test/quick -a '-vEval.JitMaxTranslations=120 -vEval.HHIRRefcountOpts=0'
  66. # All quick tests except debugger
  67. % $argv[0] -e debugger test/quick
  68. # All tests except those containing a string of 3 digits
  69. % $argv[0] -E '/\d{3}/' all
  70. # All tests whose name containing pdo_mysql
  71. % $argv[0] -i pdo_mysql -m jit -r zend
  72. # Print all the standard tests
  73. % $argv[0] --list-tests
  74. # Use a specific HHVM binary
  75. % $argv[0] -b ~/code/hhvm/hphp/hhvm/hhvm
  76. % $argv[0] --hhvm-binary-path ~/code/hhvm/hphp/hhvm/hhvm
  77. # Use relocation to run tests in the same thread. e.g, 6 times in the same thread,
  78. # where the 3 specifies a random relocation for the 3rd request and the test is
  79. # run 3 * 2 times.
  80. % $argv[0] --relocate 3 test/quick/silencer.php
  81. # Run the Hack typechecker against quick typechecker.expect[f] files
  82. # Could explcitly use quick here too
  83. # $argv[0] --typechecker
  84. # Run the Hack typechecker against typechecker.expect[f] files in the slow
  85. # directory
  86. # $argv[0] --typechecker slow
  87. # Run the Hack typechecker against the typechecker.expect[f] file in this test
  88. # $argv[0] --typechecker test/slow/test_runner_typechecker_mode/basic.php
  89. # Use a specific typechecker binary
  90. # $argv[0] --hhserver-binary-path ~/code/hhvm/hphp/hack/bin/hh_server --typechecker .
  91. EOT;
  92. return usage().$help;
  93. }
  94. function error($message) {
  95. print "$message\n";
  96. exit(1);
  97. }
  98. // If a user-supplied path is provided, let's make sure we have a valid
  99. // executable.
  100. function check_executable($path, $typechecker) {
  101. $type = $typechecker ? "HH_SERVER" : "HHVM";
  102. $rpath = realpath($path);
  103. $msg = "Provided ".$type." executable (".$path.") is not a file.\n"
  104. . "If using ".$type."_BIN, make sure that is set correctly.";
  105. if (!is_file($rpath)) {
  106. error($msg);
  107. }
  108. $output = array();
  109. exec($rpath . " --help", $output);
  110. $str = implode($output);
  111. $msg = "Provided file (".$rpath.") is not a/an ".$type." executable.\n"
  112. . "If using ".$type."_BIN, make sure that is set correctly.";
  113. if (strpos($str, "Usage") !== 0) {
  114. error($msg);
  115. }
  116. }
  117. function hhvm_binary_routes() {
  118. $routes = array(
  119. "fbbuild" => "/_bin/hphp/hhvm",
  120. "buck" => "/buck-out/gen/hphp/hhvm/hhvm",
  121. "cmake" => "/hphp/hhvm"
  122. );
  123. $env_root = getenv("FBMAKE_BIN_ROOT");
  124. if ($env_root !== false) {
  125. $routes["fbbuild"] = "/" . $env_root . "/hphp/hhvm";
  126. }
  127. return $routes;
  128. }
  129. function hh_server_binary_routes() {
  130. return array(
  131. "fbbuild" => "/_bin/hphp/hack/src",
  132. "buck" => "/buck-out/gen/hphp/hack/src/hh_server",
  133. "cmake" => "/hphp/hack/bin"
  134. );
  135. $env_root = getenv("FBMAKE_BIN_ROOT");
  136. if ($env_root !== false) {
  137. $routes["fbbuild"] = "/" . $env_root . "/hphp/hack/src";
  138. }
  139. return $routes;
  140. }
  141. // For Facebook: We have several build systems, and we can use any of them in
  142. // the same code repo. If multiple binaries exist, we want the onus to be on
  143. // the user to specify a particular one because before we chose the fbmake one
  144. // by default and that could cause unexpected results.
  145. function check_for_multiple_default_binaries($typechecker) {
  146. // Env var we use in testing that'll pick which build system to use.
  147. if (getenv("FBCODE_BUILD_TOOL") !== false) {
  148. return;
  149. }
  150. $home = hphp_home();
  151. $routes = $typechecker ? hh_server_binary_routes() : hhvm_binary_routes();
  152. $binary = $typechecker ? "hh_server" : "hhvm";
  153. $found = array();
  154. foreach ($routes as $_ => $path) {
  155. $abs_path = $home . $path . "/" . $binary;
  156. if (file_exists($abs_path)) {
  157. $found[] = $abs_path;
  158. }
  159. }
  160. if (count($found) <= 1) {
  161. return;
  162. }
  163. $path_option = $typechecker ? "--hhserver-binary-path" : "--hhvm-binary-path";
  164. $msg = "Multiple binaries exist in this repo. \n";
  165. foreach ($found as $bin) {
  166. $msg .= " - " . $bin . "\n";
  167. }
  168. $msg .= "Are you in fbcode? If so, remove a binary \n"
  169. . "or use the " . $path_option . " option to the test runner. \n"
  170. . "e.g., test/run ";
  171. if ($typechecker) {
  172. $msg .= "--typechecker";
  173. }
  174. $msg .= " " . $path_option . " /path/to/binary slow\n";
  175. error($msg);
  176. }
  177. function hphp_home() {
  178. if (is_testing_dso_extension()) {
  179. return realpath(__DIR__);
  180. }
  181. return realpath(__DIR__.'/../..');
  182. }
  183. function idx($array, $key, $default = null) {
  184. return isset($array[$key]) ? $array[$key] : $default;
  185. }
  186. function hhvm_path() {
  187. $file = "";
  188. if (getenv("HHVM_BIN") !== false) {
  189. $file = realpath(getenv("HHVM_BIN"));
  190. } else {
  191. $file = bin_root().'/hhvm';
  192. }
  193. if (!is_file($file)) {
  194. if (is_testing_dso_extension()) {
  195. exec("which hhvm", $output);
  196. if (isset($output[0]) && $output[0]) {
  197. return $output[0];
  198. }
  199. error("You need to specify hhvm bin with env HHVM_BIN");
  200. }
  201. error("$file doesn't exist. Did you forget to build first?");
  202. }
  203. return rel_path($file);
  204. }
  205. function bin_root() {
  206. if (getenv("HHVM_BIN") !== false) {
  207. return dirname(realpath(getenv("HHVM_BIN")));
  208. }
  209. $home = hphp_home();
  210. $env_tool = getenv("FBCODE_BUILD_TOOL");
  211. $routes = hhvm_binary_routes();
  212. if ($env_tool !== false) {
  213. return $home . $routes[$env_tool];
  214. }
  215. foreach ($routes as $_ => $path) {
  216. $dir = $home . $path;
  217. if (is_dir($dir)) {
  218. return $dir;
  219. }
  220. }
  221. return $home . $routes["cmake"];
  222. }
  223. function hh_server_path() {
  224. $file = "";
  225. if (getenv("HH_SERVER_BIN") !== false) {
  226. $file = realpath(getenv("HH_SERVER_BIN"));
  227. } else {
  228. $file = hh_server_bin_root().'/hh_server';
  229. }
  230. if (!is_file($file)) {
  231. error("$file doesn't exist. Did you forget to build first?");
  232. }
  233. return rel_path($file);
  234. }
  235. function hh_server_bin_root() {
  236. if (getenv("HH_SERVER_BIN") !== false) {
  237. return dirname(realpath(getenv("HH_SERVER_BIN")));
  238. }
  239. $home = hphp_home();
  240. $env_tool = getenv("FBCODE_BUILD_TOOL");
  241. $routes = hh_server_binary_routes();
  242. if ($env_tool !== false) {
  243. return $home . $routes[$env_tool];
  244. }
  245. foreach ($routes as $_ => $path) {
  246. $dir = $home . $path;
  247. if (is_dir($dir)) {
  248. return $dir;
  249. }
  250. }
  251. return $home . $routes["cmake"];
  252. }
  253. function verify_hhbc() {
  254. if (getenv("VERIFY_HHBC") !== false) {
  255. return getenv($env_hhbc);
  256. }
  257. return bin_root().'/verify.hhbc';
  258. }
  259. function read_opts_file($file) {
  260. if (!file_exists($file)) {
  261. return "";
  262. }
  263. $fp = fopen($file, "r");
  264. $contents = "";
  265. while ($line = fgets($fp)) {
  266. // Compress out white space.
  267. $line = preg_replace('/\s+/', ' ', $line);
  268. // Discard simple line oriented ; and # comments to end of line
  269. // Comments at end of line (after payload) are not allowed.
  270. $line = preg_replace('/^ *;.*$/', ' ', $line);
  271. $line = preg_replace('/^ *#.*$/', ' ', $line);
  272. // Substitute in the directory name
  273. $line = str_replace('__DIR__', dirname($file), $line);
  274. $contents .= $line;
  275. }
  276. fclose($fp);
  277. return $contents;
  278. }
  279. // http://stackoverflow.com/questions/2637945/
  280. function rel_path($to) {
  281. $from = explode('/', getcwd().'/');
  282. $to = explode('/', $to);
  283. $relPath = $to;
  284. foreach ($from as $depth => $dir) {
  285. // find first non-matching dir.
  286. if ($dir === $to[$depth]) {
  287. // ignore this directory.
  288. array_shift($relPath);
  289. } else {
  290. // get number of remaining dirs to $from.
  291. $remaining = count($from) - $depth;
  292. if ($remaining > 1) {
  293. // add traversals up to first matching dir.
  294. $padLength = (count($relPath) + $remaining - 1) * -1;
  295. $relPath = array_pad($relPath, $padLength, '..');
  296. break;
  297. } else {
  298. $relPath[0] = './' . $relPath[0];
  299. }
  300. }
  301. }
  302. return implode('/', $relPath);
  303. }
  304. function get_options($argv) {
  305. $parameters = array(
  306. 'exclude:' => 'e:',
  307. 'exclude-pattern:' => 'E:',
  308. 'include:' => 'i:',
  309. 'include-pattern:' => 'I:',
  310. 'repo' => 'r',
  311. 'mode:' => 'm:',
  312. 'server' => 's',
  313. 'shuffle' => '',
  314. 'help' => 'h',
  315. 'verbose' => 'v',
  316. 'fbmake' => '',
  317. 'testpilot' => '',
  318. 'threads:' => '',
  319. 'args:' => 'a:',
  320. 'log' => 'l',
  321. 'failure-file:' => '',
  322. 'arm' => '',
  323. 'wholecfg' => '',
  324. 'hhas-round-trip' => '',
  325. 'color' => 'c',
  326. 'no-fun' => '',
  327. 'cores' => '',
  328. 'no-clean' => '',
  329. 'list-tests' => '',
  330. 'relocate:' => '',
  331. 'recycle-tc:' => '',
  332. 'hhvm-binary-path:' => 'b:',
  333. 'typechecker' => '',
  334. 'hhserver-binary-path:' => '',
  335. );
  336. $options = array();
  337. $files = array();
  338. /*
  339. * '-' argument causes all future arguments to be treated as filenames, even
  340. * if they would otherwise match a valid option. Otherwise, arguments starting
  341. * with '-' MUST match a valid option.
  342. */
  343. $force_file = false;
  344. for ($i = 1; $i < count($argv); $i++) {
  345. $arg = $argv[$i];
  346. if (strlen($arg) == 0) {
  347. continue;
  348. } else if ($force_file) {
  349. $files[] = $arg;
  350. } else if ($arg === '-') {
  351. $forcefile = true;
  352. } else if ($arg[0] === '-') {
  353. $found = false;
  354. foreach ($parameters as $long => $short) {
  355. if ($arg == '-'.str_replace(':', '', $short) ||
  356. $arg == '--'.str_replace(':', '', $long)) {
  357. if (substr($long, -1, 1) == ':') {
  358. $value = $argv[++$i];
  359. } else {
  360. $value = true;
  361. }
  362. $options[str_replace(':', '', $long)] = $value;
  363. $found = true;
  364. break;
  365. }
  366. }
  367. if (!$found) {
  368. error(sprintf("Invalid argument: '%s'\nSee $argv[0] --help", $arg));
  369. }
  370. } else {
  371. $files[] = $arg;
  372. }
  373. }
  374. if (isset($options['repo']) && isset($options['hhas-round-trip'])) {
  375. echo "repo and hhas-round-trip are mutually exclusive options\n";
  376. exit(1);
  377. }
  378. if (isset($options['relocate']) && isset($options['recycle-tc'])) {
  379. echo "relocate and recycle-tc are mutually exclusive options\n";
  380. exit(1);
  381. }
  382. return array($options, $files);
  383. }
  384. /*
  385. * We support some 'special' file names, that just know where the test
  386. * suites are, to avoid typing 'hphp/test/foo'.
  387. */
  388. function find_test_files($file) {
  389. $mappage = array(
  390. 'quick' => 'hphp/test/quick',
  391. 'slow' => 'hphp/test/slow',
  392. 'spec' => 'hphp/test/spec',
  393. 'debugger' => 'hphp/test/server/debugger/tests',
  394. 'http' => 'hphp/test/server/http/tests',
  395. 'fastcgi' => 'hphp/test/server/fastcgi/tests',
  396. 'zend' => 'hphp/test/zend/good',
  397. 'facebook' => 'hphp/facebook/test',
  398. // Subsets of zend tests.
  399. 'zend_ext' => 'hphp/test/zend/good/ext',
  400. 'zend_ext_am' => 'hphp/test/zend/good/ext/[a-m]*',
  401. 'zend_ext_nz' => 'hphp/test/zend/good/ext/[n-z]*',
  402. 'zend_Zend' => 'hphp/test/zend/good/Zend',
  403. 'zend_tests' => 'hphp/test/zend/good/tests',
  404. 'zend_bad' => 'hphp/test/zend/bad',
  405. );
  406. if (isset($mappage[$file])) {
  407. $matches = glob(hphp_home().'/'.$mappage[$file]);
  408. if (count($matches) == 0) {
  409. error(sprintf(
  410. "Convenience test name '%s' is recognized but does not match any test ".
  411. "files (pattern = '%s', hphp_home = '%s')",
  412. $file, $mappage[$file], hphp_home()));
  413. }
  414. return $matches;
  415. } else {
  416. return array($file, );
  417. }
  418. }
  419. // Some tests have to be run together in the same test bucket, serially, one
  420. // after other in order to avoid races and other collisions.
  421. function serial_only_tests($tests) {
  422. if (is_testing_dso_extension()) {
  423. return array();
  424. }
  425. // Add a <testname>.php.serial file to make your test run in the serial
  426. // bucket.
  427. $serial_tests = array_filter(
  428. $tests,
  429. function($test) {
  430. return file_exists($test . '.serial');
  431. }
  432. );
  433. return $serial_tests;
  434. }
  435. function find_tests($files, array $options = null) {
  436. if (!$files) {
  437. $files = array('quick');
  438. }
  439. if ($files == array('all')) {
  440. $files = array('quick', 'slow', 'spec', 'zend', 'fastcgi');
  441. }
  442. $ft = array();
  443. foreach ($files as $file) {
  444. $ft = array_merge($ft, find_test_files($file));
  445. }
  446. $files = $ft;
  447. foreach ($files as &$file) {
  448. if (!@stat($file)) {
  449. error("Not valid file or directory: '$file'");
  450. }
  451. $file = preg_replace(',//+,', '/', realpath($file));
  452. $file = preg_replace(',^'.getcwd().'/,', '', $file);
  453. }
  454. $files = array_map('escapeshellarg', $files);
  455. $files = implode(' ', $files);
  456. if (isset($options['typechecker'])) {
  457. $tests = explode("\n", shell_exec(
  458. "find $files -name '*.php' -o -name '*.php.type-errors'"
  459. ));
  460. // The above will get all the php files. Now filter out only the ones
  461. // that have a .hhconfig associated with it.
  462. $tests = array_filter(
  463. $tests,
  464. function($test) {
  465. return (file_exists($test . '.typechecker.expect') ||
  466. file_exists($test . '.typechecker.expectf')) &&
  467. file_exists($test . '.hhconfig');
  468. }
  469. );
  470. } else {
  471. $tests = explode("\n", shell_exec(
  472. "find $files -name '*.php' -o -name '*.php.type-errors' " .
  473. "-o -name '*.hhas' | grep -v round_trip.hhas"
  474. ));
  475. $tests = array_filter(
  476. $tests,
  477. function($test) {
  478. return file_exists($test . '.expect') ||
  479. file_exists($test . '.expectf') ||
  480. file_exists($test . '.hhvm.expect') ||
  481. file_exists($test . '.hhvm.expectf');
  482. }
  483. );
  484. }
  485. if (!$tests) {
  486. error("Could not find any tests associated with your options.\n" .
  487. "Make sure your test path is correct and that you have " .
  488. "the right expect files for the tests you are trying to run.\n" .
  489. usage());
  490. }
  491. asort($tests);
  492. $tests = array_filter($tests);
  493. if (!empty($options['exclude'])) {
  494. $exclude = $options['exclude'];
  495. $tests = array_filter($tests, function($test) use ($exclude) {
  496. return (false === strpos($test, $exclude));
  497. });
  498. }
  499. if (!empty($options['exclude-pattern'])) {
  500. $exclude = $options['exclude-pattern'];
  501. $tests = array_filter($tests, function($test) use ($exclude) {
  502. return !preg_match($exclude, $test);
  503. });
  504. }
  505. if (!empty($options['include'])) {
  506. $include = $options['include'];
  507. $tests = array_filter($tests, function($test) use ($include) {
  508. return (false !== strpos($test, $include));
  509. });
  510. }
  511. if (!empty($options['include-pattern'])) {
  512. $include = $options['include-pattern'];
  513. $tests = array_filter($tests, function($test) use ($include) {
  514. return preg_match($include, $test);
  515. });
  516. }
  517. return $tests;
  518. }
  519. function list_tests($files, $options) {
  520. $args = array();
  521. $mode = idx($options, 'mode', '');
  522. switch ($mode) {
  523. case '':
  524. case 'jit':
  525. $args[] = '-m jit';
  526. break;
  527. case 'interp';
  528. $args[] = '-m interp';
  529. break;
  530. default:
  531. throw new Exception("Unsupported mode for listing tests: ".$mode);
  532. }
  533. if (isset($options['repo'])) {
  534. $args[] = '-r';
  535. }
  536. if (isset($options['relocate'])) {
  537. $args[] = '--relocate';
  538. $args[] = $options['relocate'];
  539. }
  540. if (isset($options['recycle-tc'])) {
  541. $args[] = '--recycle-tc';
  542. $args[] = $options['recycle-tc'];
  543. }
  544. foreach (find_tests($files, $options) as $test) {
  545. print Status::jsonEncode(array(
  546. 'args' => implode(' ', $args),
  547. 'name' => $test,
  548. ))."\n";
  549. }
  550. }
  551. function find_test_ext($test, $ext) {
  552. if (is_file("{$test}.{$ext}")) {
  553. return "{$test}.{$ext}";
  554. }
  555. return find_file_for_dir(dirname($test), "config.{$ext}");
  556. }
  557. function find_file($test, $name) {
  558. return find_file_for_dir(dirname($test), $name);
  559. }
  560. function find_file_for_dir($dir, $name) {
  561. // Handle the case where the $dir might come in as '.' because you
  562. // are running the test runner on a file from the same directory as
  563. // the test e.g., './mytest.php'. dirname() will give you the '.' when
  564. // you actually have a lot of path to traverse upwards like
  565. // /home/you/code/tests/mytest.php. Use realpath() to get that.
  566. $dir = realpath($dir);
  567. while ($dir !== '/' && is_dir($dir)) {
  568. $file = "$dir/$name";
  569. if (is_file($file)) {
  570. return $file;
  571. }
  572. $dir = dirname($dir);
  573. }
  574. $file = __DIR__.'/'.$name;
  575. if (file_exists($file)) {
  576. return $file;
  577. }
  578. return null;
  579. }
  580. function find_debug_config($test, $name) {
  581. $debug_config = find_file_for_dir(dirname($test), $name);
  582. if ($debug_config !== null) {
  583. return "-m debug --debug-config ".$debug_config;
  584. }
  585. return "";
  586. }
  587. function mode_cmd($options) {
  588. $repo_args = '';
  589. if (!isset($options['repo'])) {
  590. // Set the non-repo-mode shared repo.
  591. // When in repo mode, we set our own central path.
  592. $repo_args = "-vRepo.Local.Mode=-- -vRepo.Central.Path=".verify_hhbc();
  593. }
  594. $jit_args = "$repo_args -vEval.Jit=true";
  595. $mode = idx($options, 'mode', '');
  596. switch ($mode) {
  597. case '':
  598. case 'jit':
  599. return "$jit_args";
  600. case 'pgo':
  601. return $jit_args.
  602. ' -vEval.JitPGO=1'.
  603. ' -vEval.JitPGORegionSelector=hottrace'.
  604. ' -vEval.JitPGOHotOnly=0';
  605. case 'interp':
  606. return "$repo_args -vEval.Jit=0";
  607. default:
  608. error("-m must be one of jit | pgo | interp. Got: '$mode'");
  609. }
  610. }
  611. function extra_args($options) {
  612. return idx($options, 'args', '');
  613. }
  614. function hhvm_cmd_impl() {
  615. $args = func_get_args();
  616. $options = array_shift($args);
  617. $config = array_shift($args);
  618. $extra_args = $args;
  619. $args = array(
  620. hhvm_path(),
  621. '-c',
  622. $config,
  623. '-vEval.EnableArgsInBacktraces=true',
  624. '-vEval.EnableIntrinsicsExtension=true',
  625. mode_cmd($options),
  626. isset($options['arm']) ? '-vEval.SimulateARM=1' : '',
  627. isset($options['wholecfg']) ? '-vEval.JitPGORegionSelector=wholecfg' : '',
  628. extra_args($options),
  629. );
  630. if (isset($options['relocate'])) {
  631. $args[] = '--count='.($options['relocate'] * 2);
  632. $args[] = '-vEval.JitAHotSize=6000000';
  633. $args[] = '-vEval.HotFuncCount=0';
  634. $args[] = '-vEval.PerfRelocate='.$options['relocate'];
  635. }
  636. if (isset($options['recycle-tc'])) {
  637. $args[] = '--count='.$options['recycle-tc'];
  638. $args[] = '-vEval.StressUnitCacheFreq=1';
  639. $args[] = '-vEval.EnableReusableTC=true';
  640. }
  641. if (!isset($options['cores'])) {
  642. $args[] = '-vResourceLimit.CoreFileSize=0';
  643. }
  644. return implode(' ', array_merge($args, $extra_args));
  645. }
  646. // Return the command and the env to run it in.
  647. function hhvm_cmd($options, $test, $test_run = null) {
  648. if ($test_run === null) {
  649. $test_run = $test;
  650. }
  651. // hdf support is only temporary until we fully migrate to ini
  652. // Discourage broad use.
  653. $hdf_suffix = ".use.for.ini.migration.testing.only.hdf";
  654. $hdf = file_exists($test.$hdf_suffix)
  655. ? '-c ' . $test . $hdf_suffix
  656. : "";
  657. $cmd = hhvm_cmd_impl(
  658. $options,
  659. find_test_ext($test, 'ini'),
  660. $hdf,
  661. find_debug_config($test, 'hphpd.ini'),
  662. read_opts_file(find_test_ext($test, 'opts')),
  663. '--file',
  664. escapeshellarg($test_run)
  665. );
  666. // Special support for tests that require a path to the current
  667. // test directory for things like prepend_file and append_file
  668. // testing.
  669. if (file_exists($test.'.ini')) {
  670. $contents = file_get_contents($test.'.ini');
  671. if (strpos($contents, '{PWD}') !== false) {
  672. $test_ini = tempnam('/tmp', $test).'.ini';
  673. file_put_contents($test_ini,
  674. str_replace('{PWD}', dirname($test), $contents));
  675. $cmd .= " -c $test_ini";
  676. }
  677. }
  678. if ($hdf !== "") {
  679. $contents = file_get_contents($test.$hdf_suffix);
  680. if (strpos($contents, '{PWD}') !== false) {
  681. $test_hdf = tempnam('/tmp', $test).$hdf_suffix;
  682. file_put_contents($test_hdf,
  683. str_replace('{PWD}', dirname($test), $contents));
  684. $cmd .= " -c $test_hdf";
  685. }
  686. }
  687. if (isset($options['repo'])) {
  688. $hhbbc_repo = "\"$test.repo/hhvm.hhbbc\"";
  689. $cmd .= ' -vRepo.Authoritative=true -vRepo.Commit=0';
  690. $cmd .= " -vRepo.Central.Path=$hhbbc_repo";
  691. }
  692. // Command line arguments
  693. $cli_args = find_test_ext($test, 'cli_args');
  694. if ($cli_args !== null) {
  695. $cmd .= " " . file_get_contents($cli_args);
  696. }
  697. $env = $_ENV;
  698. // If there's an <test name>.env file then inject the contents of that into
  699. // the test environment.
  700. $env_file = find_test_ext($test, 'env');
  701. if ($env_file !== null) {
  702. $extra_env = explode("\n", trim(file_get_contents($env_file)));
  703. foreach ($extra_env as $arg) {
  704. $i = strpos($arg, '=');
  705. if ($i) {
  706. $key = substr($arg, 0, $i);
  707. $val = substr($arg, $i + 1);
  708. $env[$key] = $val;
  709. } else {
  710. unset($env[$arg]);
  711. }
  712. }
  713. }
  714. $in = find_test_ext($test, 'in');
  715. if ($in !== null) {
  716. $cmd .= ' < ' . escapeshellarg($in);
  717. // If we're piping the input into the command then setup a simple
  718. // dumb terminal so hhvm doesn't try to control it and pollute the
  719. // output with control characters, which could change depending on
  720. // a wide variety of terminal settings.
  721. $env["TERM"] = "dumb";
  722. }
  723. return array($cmd, $env);
  724. }
  725. function hphp_cmd($options, $test) {
  726. $extra_args = preg_replace("/-v\s*/", "-vRuntime.", extra_args($options));
  727. return implode(" ", array(
  728. "HHVM_DISABLE_HHBBC2=1",
  729. hhvm_path(),
  730. '--hphp',
  731. '--config',
  732. find_file($test, 'hphp_config.ini'),
  733. read_opts_file("$test.hphp_opts"),
  734. "-thhbc -l0 -k1 -o \"$test.repo\" \"$test\"",
  735. $extra_args
  736. ));
  737. }
  738. function hhbbc_cmd($options, $test) {
  739. return implode(" ", array(
  740. hhvm_path(),
  741. '--hhbbc',
  742. '--no-logging',
  743. '--parallel-num-threads=1',
  744. read_opts_file("$test.hhbbc_opts"),
  745. "-o \"$test.repo/hhvm.hhbbc\" \"$test.repo/hhvm.hhbc\"",
  746. ));
  747. }
  748. function hh_server_cmd($options, $test) {
  749. // In order to run hh_server --check on only one file, we copy all of the
  750. // files associated with the test to a temporary directory, rename the
  751. // basename($test_file).hhconfig file to just .hhconfig and set the command
  752. // appropriately.
  753. $temp_dir = '/tmp/hh-test-runner-'.bin2hex(random_bytes(16));
  754. mkdir($temp_dir);
  755. foreach (glob($test . '*') as $test_file) {
  756. copy($test_file, $temp_dir . '/' . basename($test_file));
  757. if (strpos($test_file, '.hhconfig') !== false) {
  758. rename(
  759. $temp_dir . '/' . basename($test) . '.hhconfig',
  760. $temp_dir . '/.hhconfig'
  761. );
  762. } else if (strpos($test_file, '.type-errors') !== false) {
  763. // In order to actually run hh_server --check successfully, all files
  764. // named *.php.type-errors have to be renamed *.php
  765. rename(
  766. $temp_dir . '/' . basename($test_file),
  767. $temp_dir . '/' . str_replace('.type-errors', '', basename($test_file))
  768. );
  769. }
  770. }
  771. // Just copy all the .php.inc files, even if they are not related since
  772. // unrelated ones will be ignored anyway. This just makes it easier to
  773. // start with instead of doing a search inside the test file for requires
  774. // and includes and extracting it.
  775. foreach (glob(dirname($test) . "/*.inc.php") as $inc_file) {
  776. copy($inc_file, $temp_dir . '/' . basename($inc_file));
  777. }
  778. $cmd = hh_server_path() . ' --check ' . $temp_dir;
  779. return array($cmd, ' ', $temp_dir);
  780. }
  781. class Status {
  782. private static $results = array();
  783. private static $mode = 0;
  784. private static $use_color = false;
  785. private static $queue = null;
  786. private static $killed = false;
  787. public static $key;
  788. private static $overall_start_time = 0;
  789. private static $overall_end_time = 0;
  790. private static $tempdir = "";
  791. const MODE_NORMAL = 0;
  792. const MODE_VERBOSE = 1;
  793. const MODE_FBMAKE = 2;
  794. const MODE_TESTPILOT = 3;
  795. const MSG_STARTED = 7;
  796. const MSG_FINISHED = 1;
  797. const MSG_TEST_PASS = 2;
  798. const MSG_TEST_FAIL = 4;
  799. const MSG_TEST_SKIP = 5;
  800. const MSG_SERVER_RESTARTED = 6;
  801. const RED = 31;
  802. const GREEN = 32;
  803. const YELLOW = 33;
  804. const BLUE = 34;
  805. const PASS_SERVER = 0;
  806. const SKIP_SERVER = 1;
  807. const PASS_CLI = 2;
  808. private static function getTempDir() {
  809. self::$tempdir = sys_get_temp_dir();
  810. // Apparently some systems might not put the trailing slash
  811. if (substr(self::$tempdir, -1) !== "/") {
  812. self::$tempdir .= "/";
  813. }
  814. self::$tempdir .= substr( md5(rand()), 0, 8);
  815. mkdir(self::$tempdir);
  816. }
  817. // Since we run the tests in forked processes, state is not shared
  818. // So we cannot keep a static variable adding individual test times.
  819. // But we can put the times files and add the values later.
  820. public static function setTestTime($time) {
  821. file_put_contents(tempnam(self::$tempdir, "trun"), $time);
  822. }
  823. // The total time running the tests if they were run serially.
  824. public static function addTestTimesSerial() {
  825. $time = 0;
  826. $files = scandir(self::$tempdir);
  827. foreach ($files as $file) {
  828. if (strpos($file, 'trun') === 0) {
  829. $time += floatval(file_get_contents(self::$tempdir . "/" . $file));
  830. unlink(self::$tempdir . "/" . $file);
  831. }
  832. }
  833. return $time;
  834. }
  835. public static function setMode($mode) {
  836. self::$mode = $mode;
  837. }
  838. public static function setUseColor($use) {
  839. self::$use_color = $use;
  840. }
  841. public static function getMode() {
  842. return self::$mode;
  843. }
  844. public static function getOverallStartTime() {
  845. return self::$overall_start_time;
  846. }
  847. public static function getOverallEndTime() {
  848. return self::$overall_end_time;
  849. }
  850. public static function started() {
  851. self::getTempDir();
  852. self::send(self::MSG_STARTED, null);
  853. self::$overall_start_time = microtime(true);
  854. }
  855. public static function finished() {
  856. self::$overall_end_time = microtime(true);
  857. self::send(self::MSG_FINISHED, null);
  858. }
  859. public static function killQueue() {
  860. if (!self::$killed) {
  861. msg_remove_queue(self::$queue);
  862. self::$queue = null;
  863. self::$killed = true;
  864. }
  865. }
  866. public static function pass($test, $detail, $time, $stime, $etime) {
  867. array_push(self::$results, array('name' => $test,
  868. 'status' => 'passed',
  869. 'start_time' => $stime,
  870. 'end_time' => $etime,
  871. 'time' => $time));
  872. $how = $detail === 'pass-server' ? self::PASS_SERVER :
  873. ($detail === 'skip-server' ? self::SKIP_SERVER : self::PASS_CLI);
  874. self::send(self::MSG_TEST_PASS, array($test, $how, $time, $stime, $etime));
  875. }
  876. public static function skip($test, $reason, $time, $stime, $etime) {
  877. if (self::getMode() === self::MODE_FBMAKE) {
  878. /* Intentionally supress skips */
  879. } elseif (self::getMode() === self::MODE_TESTPILOT) {
  880. /* testpilot needs a positive response for every test run, report
  881. * that this test isn't relevant so it can silently drop. */
  882. array_push(self::$results, array('name' => $test,
  883. 'status' => 'not_relevant',
  884. 'start_time' => $stime,
  885. 'end_time' => $etime,
  886. 'time' => $time));
  887. } else {
  888. array_push(self::$results, array('name' => $test,
  889. 'status' => 'skipped',
  890. 'start_time' => $stime,
  891. 'end_time' => $etime,
  892. 'time' => $time));
  893. }
  894. self::send(self::MSG_TEST_SKIP,
  895. array($test, $reason, $time, $stime, $etime));
  896. }
  897. public static function fail($test, $time, $stime, $etime) {
  898. array_push(self::$results, array(
  899. 'name' => $test,
  900. 'status' => 'failed',
  901. 'details' => self::utf8Sanitize(@file_get_contents("$test.diff")),
  902. 'start_time' => $stime,
  903. 'end_time' => $etime,
  904. 'time' => $time
  905. ));
  906. self::send(self::MSG_TEST_FAIL, array($test, $time, $stime, $etime));
  907. }
  908. public static function serverRestarted() {
  909. self::send(self::MSG_SERVER_RESTARTED, null);
  910. }
  911. private static function send($type, $msg) {
  912. if (self::$killed) {
  913. return;
  914. }
  915. msg_send(self::getQueue(), $type, $msg);
  916. }
  917. /**
  918. * Takes a variable number of string arguments. If color output is enabled
  919. * and any one of the arguments is preceded by an integer (see the color
  920. * constants above), that argument will be given the indicated color.
  921. */
  922. public static function sayColor() {
  923. $args = func_get_args();
  924. while (count($args)) {
  925. $color = null;
  926. $str = array_shift($args);
  927. if (is_integer($str)) {
  928. $color = $str;
  929. if (self::$use_color) {
  930. print "\033[0;${color}m";
  931. }
  932. $str = array_shift($args);
  933. }
  934. print $str;
  935. if (self::$use_color && !is_null($color)) {
  936. print "\033[0m";
  937. }
  938. }
  939. }
  940. public static function sayFBMake($test, $status, $stime, $etime) {
  941. $start = array('op' => 'start', 'test' => $test);
  942. $end = array('op' => 'test_done', 'test' => $test, 'status' => $status,
  943. 'start_time' => $stime, 'end_time' => $etime);
  944. if ($status == 'failed') {
  945. $end['details'] = self::utf8Sanitize(@file_get_contents("$test.diff"));
  946. }
  947. self::say($start, $end);
  948. }
  949. public static function getResults() {
  950. return self::$results;
  951. }
  952. /** Output is in the format expected by JsonTestRunner. */
  953. public static function say(/* ... */) {
  954. $data = array_map(function($row) {
  955. return self::jsonEncode($row) . "\n";
  956. }, func_get_args());
  957. fwrite(STDERR, implode("", $data));
  958. }
  959. public static function hasCursorControl() {
  960. // for runs on hudson-ci.org (aka jenkins).
  961. if (getenv("HUDSON_URL")) {
  962. return false;
  963. }
  964. // for runs on travis-ci.org
  965. if (getenv("TRAVIS")) {
  966. return false;
  967. }
  968. $stty = self::getSTTY();
  969. if (!$stty) {
  970. return false;
  971. }
  972. return strpos($stty, 'erase = <undef>') === false;
  973. }
  974. public static function getSTTY() {
  975. $descriptorspec = array(1 => array("pipe", "w"), 2 => array("pipe", "w"));
  976. $process = proc_open(
  977. 'stty -a', $descriptorspec, $pipes, null, null,
  978. array('suppress_errors' => true)
  979. );
  980. $stty = stream_get_contents($pipes[1]);
  981. proc_close($process);
  982. return $stty;
  983. }
  984. public static function utf8Sanitize($str) {
  985. if (!is_string($str)) {
  986. // We sometimes get called with the
  987. // return value of file_get_contents()
  988. // when fgc() has failed.
  989. return '';
  990. }
  991. if (class_exists('UConverter')) {
  992. return UConverter::transcode($str, 'UTF-8', 'UTF-8');
  993. }
  994. // UConverter is PHP5.5 or later.
  995. // Do the equivalent using a slower user-space implementation.
  996. $ret = '';
  997. $len = strlen($str);
  998. $pos = 0;
  999. while ($pos < $len) {
  1000. $c = $str[$pos];
  1001. $co = ord($c);
  1002. if ($co < 0x80) {
  1003. // U+0000 - U+007F ASCII
  1004. $ret .= $c;
  1005. ++$pos;
  1006. } elseif ((($co & 0xE0) == 0xC0) &&
  1007. (($len - $pos) > 1) &&
  1008. ((ord($str[$pos+1]) & 0xC0) == 0x80)) {
  1009. // U+0080 - U+07FF Lower Basic Multilingual Plane
  1010. $ret .= substr($str, $pos, 2);
  1011. $pos += 2;
  1012. } elseif ((($co & 0xF0) == 0xE0) &&
  1013. (($len - $pos) > 2) &&
  1014. ((ord($str[$pos+1]) & 0xC0) == 0x80) &&
  1015. ((ord($str[$pos+2]) & 0xC0) == 0x80)) {
  1016. // U+0800 - U+FFFF Upper Basic Multilingual Plane
  1017. $ret .= substr($str, $pos, 3);
  1018. $pos += 3;
  1019. } elseif ((($co & 0xF8) == 0xF0) &&
  1020. (($len - $pos) > 3) &&
  1021. ((ord($str[$pos+1]) & 0xC0) == 0x80) &&
  1022. ((ord($str[$pos+2]) & 0xC0) == 0x80) &&
  1023. ((ord($str[$pos+3]) & 0xC0) == 0x80)) {
  1024. // U+010000 - U+10FFFF Supplementary Multilingual Planes
  1025. $ret .= substr($str, $pos, 4);
  1026. $pos += 4;
  1027. } else {
  1028. // Invalid UTF8
  1029. $ret .= "\xEF\xBF\xBD"; // U+FFFD REPLACEMENT CHARACTER
  1030. ++$pos;
  1031. }
  1032. }
  1033. return $ret;
  1034. }
  1035. public static function jsonEncode($data) {
  1036. // JSON_UNESCAPED_SLASHES is Zend 5.4+.
  1037. if (defined("JSON_UNESCAPED_SLASHES")) {
  1038. return json_encode($data, JSON_UNESCAPED_SLASHES);
  1039. }
  1040. $json = json_encode($data);
  1041. return str_replace('\\/', '/', $json);
  1042. }
  1043. public static function getQueue() {
  1044. if (!self::$queue) {
  1045. self::$queue = msg_get_queue(self::$key);
  1046. }
  1047. return self::$queue;
  1048. }
  1049. }
  1050. function clean_intermediate_files($test, $options) {
  1051. if (isset($options['no-clean'])) {
  1052. return;
  1053. }
  1054. $exts = array('out', 'diff', 'repo');
  1055. foreach ($exts as $ext) {
  1056. $file = "$test.$ext";
  1057. if (file_exists($file)) {
  1058. if (is_dir($file)) {
  1059. foreach(new RecursiveIteratorIterator(new
  1060. RecursiveDirectoryIterator($file, FilesystemIterator::SKIP_DOTS),
  1061. RecursiveIteratorIterator::CHILD_FIRST) as $path) {
  1062. $path->isDir()
  1063. ? rmdir($path->getPathname())
  1064. : unlink($path->getPathname());
  1065. }
  1066. rmdir($file);
  1067. } else {
  1068. unlink($file);
  1069. }
  1070. }
  1071. }
  1072. }
  1073. function run($options, $tests, $bad_test_file) {
  1074. foreach ($tests as $test) {
  1075. $stime = time();
  1076. $time = microtime(true);
  1077. $status = run_and_lock_test($options, $test);
  1078. $time = microtime(true) - $time;
  1079. $etime = time();
  1080. Status::setTestTime($time);
  1081. if ($status === 'skip') {
  1082. Status::skip($test, null, $time, $stime, $etime);
  1083. clean_intermediate_files($test, $options);
  1084. } else if ($status === 'skip-norepo') {
  1085. Status::skip($test, 'norepo', $time, $stime, $etime);
  1086. clean_intermediate_files($test, $options);
  1087. } else if ($status === 'skip-onlyrepo') {
  1088. Status::skip($test, 'onlyrepo', $time, $stime, $etime);
  1089. clean_intermediate_files($test, $options);
  1090. } else if ($status) {
  1091. Status::pass($test, $status, $time, $stime, $etime);
  1092. clean_intermediate_files($test, $options);
  1093. } else {
  1094. Status::fail($test, $time, $stime, $etime);
  1095. }
  1096. }
  1097. file_put_contents($bad_test_file, json_encode(Status::getResults()));
  1098. foreach (Status::getResults() as $result) {
  1099. if ($result['status'] == 'failed') {
  1100. return 1;
  1101. }
  1102. }
  1103. return 0;
  1104. }
  1105. function skip_test($options, $test) {
  1106. $skipif_test = find_test_ext($test, 'skipif');
  1107. if (!$skipif_test) {
  1108. return false;
  1109. }
  1110. // For now, run the .skipif in non-repo since building a repo for it is hard.
  1111. $options_without_repo = $options;
  1112. unset($options_without_repo['repo']);
  1113. list($hhvm, $_) = hhvm_cmd($options_without_repo, $test, $skipif_test);
  1114. $descriptorspec = array(
  1115. 0 => array("pipe", "r"),
  1116. 1 => array("pipe", "w"),
  1117. 2 => array("pipe", "w"),
  1118. );
  1119. $pipes = null;
  1120. $process = proc_open("$hhvm $test 2>&1", $descriptorspec, $pipes);
  1121. if (!is_resource($process)) {
  1122. // This is weird. We can't run HHVM but we probably shouldn't skip the test
  1123. // since on a broken build everything will show up as skipped and give you a
  1124. // SHIPIT.
  1125. return false;
  1126. }
  1127. fclose($pipes[0]);
  1128. $output = stream_get_contents($pipes[1]);
  1129. fclose($pipes[1]);
  1130. proc_close($process);
  1131. // The standard php5 .skipif semantics is if the .skipif outputs ANYTHING
  1132. // then it should be skipped. This is a poor design, but I'll just add a
  1133. // small blacklist of things that are really bad if they are output so we
  1134. // surface the errors in the tests themselves.
  1135. if (stripos($output, 'segmentation fault') !== false) {
  1136. return false;
  1137. }
  1138. return strlen($output) != 0;
  1139. }
  1140. function comp_line($l1, $l2, $is_reg) {
  1141. if ($is_reg) {
  1142. return preg_match('/^'. $l1 . '$/s', $l2);
  1143. } else {
  1144. return !strcmp($l1, $l2);
  1145. }
  1146. }
  1147. function count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2, $cnt1, $cnt2,
  1148. $steps) {
  1149. $equal = 0;
  1150. while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2],
  1151. $is_reg)) {
  1152. $idx1++;
  1153. $idx2++;
  1154. $equal++;
  1155. $steps--;
  1156. }
  1157. if (--$steps > 0) {
  1158. $eq1 = 0;
  1159. $st = $steps / 2;
  1160. for ($ofs1 = $idx1 + 1; $ofs1 < $cnt1 && $st-- > 0; $ofs1++) {
  1161. $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $ofs1, $idx2, $cnt1,
  1162. $cnt2, $st);
  1163. if ($eq > $eq1) {
  1164. $eq1 = $eq;
  1165. }
  1166. }
  1167. $eq2 = 0;
  1168. $st = $steps;
  1169. for ($ofs2 = $idx2 + 1; $ofs2 < $cnt2 && $st-- > 0; $ofs2++) {
  1170. $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $ofs2, $cnt1, $cnt2, $st);
  1171. if ($eq > $eq2) {
  1172. $eq2 = $eq;
  1173. }
  1174. }
  1175. if ($eq1 > $eq2) {
  1176. $equal += $eq1;
  1177. } else if ($eq2 > 0) {
  1178. $equal += $eq2;
  1179. }
  1180. }
  1181. return $equal;
  1182. }
  1183. function generate_array_diff($ar1, $ar2, $is_reg, $w) {
  1184. $idx1 = 0; $ofs1 = 0; $cnt1 = @count($ar1);
  1185. $idx2 = 0; $ofs2 = 0; $cnt2 = @count($ar2);
  1186. $diff = array();
  1187. $old1 = array();
  1188. $old2 = array();
  1189. while ($idx1 < $cnt1 && $idx2 < $cnt2) {
  1190. if (comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) {
  1191. $idx1++;
  1192. $idx2++;
  1193. continue;
  1194. } else {
  1195. $c1 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1+1, $idx2, $cnt1,
  1196. $cnt2, 10);
  1197. $c2 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2+1, $cnt1,
  1198. $cnt2, 10);
  1199. if ($c1 > $c2) {
  1200. $old1[$idx1] = sprintf("%03d- ", $idx1+1) . $w[$idx1++];
  1201. $last = 1;
  1202. } else if ($c2 > 0) {
  1203. $old2[$idx2] = sprintf("%03d+ ", $idx2+1) . $ar2[$idx2++];
  1204. $last = 2;
  1205. } else {
  1206. $old1[$idx1] = sprintf("%03d- ", $idx1+1) . $w[$idx1++];
  1207. $old2[$idx2] = sprintf("%03d+ ", $idx2+1) . $ar2[$idx2++];
  1208. }
  1209. }
  1210. }
  1211. reset($old1); $k1 = key($old1); $l1 = -2;
  1212. reset($old2); $k2 = key($old2); $l2 = -2;
  1213. while ($k1 !== null || $k2 !== null) {
  1214. if ($k1 == $l1 + 1 || $k2 === null) {
  1215. $l1 = $k1;
  1216. $diff[] = current($old1);
  1217. $k1 = next($old1) ? key($old1) : null;
  1218. } else if ($k2 == $l2 + 1 || $k1 === null) {
  1219. $l2 = $k2;
  1220. $diff[] = current($old2);
  1221. $k2 = next($old2) ? key($old2) : null;
  1222. } else if ($k1 < $k2) {
  1223. $l1 = $k1;
  1224. $diff[] = current($old1);
  1225. $k1 = next($old1) ? key($old1) : null;
  1226. } else {
  1227. $l2 = $k2;
  1228. $diff[] = current($old2);
  1229. $k2 = next($old2) ? key($old2) : null;
  1230. }
  1231. }
  1232. while ($idx1 < $cnt1) {
  1233. $diff[] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++];
  1234. }
  1235. while ($idx2 < $cnt2) {
  1236. $diff[] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++];
  1237. }
  1238. return $diff;
  1239. }
  1240. function generate_diff($wanted, $wanted_re, $output)
  1241. {
  1242. $w = explode("\n", $wanted);
  1243. $o = explode("\n", $output);
  1244. if (is_null($wanted_re)) {
  1245. $r = $w;
  1246. } else {
  1247. if (preg_match('/^\((.*)\)\{(\d+)\}$/s', $wanted_re, $m)) {
  1248. $t = explode("\n", $m[1]);
  1249. $r = array();
  1250. $w2 = array();
  1251. for ($i = 0; $i < $m[2]; $i++) {
  1252. foreach ($t as $v) {
  1253. $r[] = $v;
  1254. }
  1255. foreach ($w as $v) {
  1256. $w2[] = $v;
  1257. }
  1258. }
  1259. $w = $wanted === $wanted_re ? $r : $w2;
  1260. } else {
  1261. $r = explode("\n", $wanted_re);
  1262. }
  1263. }
  1264. $diff = generate_array_diff($r, $o, !is_null($wanted_re), $w);
  1265. return implode("\r\n", $diff);
  1266. }
  1267. function dump_hhas_to_temp($hhvm_cmd, $test) {
  1268. $tmp_file = $test . '.round_trip.hhas';
  1269. system("$hhvm_cmd -vEval.DumpHhas=1 > $tmp_file", $ret);
  1270. if ($ret) { echo "system failed\n"; exit(1); }
  1271. return $tmp_file;
  1272. }
  1273. const HHAS_EXT = '.hhas';
  1274. function can_run_server_test($test) {
  1275. return
  1276. !is_file("$test.noserver") &&
  1277. !find_test_ext($test, 'opts') &&
  1278. !is_file("$test.ini") &&
  1279. !is_file("$test.onlyrepo") &&
  1280. strpos($test, 'quick/debugger') === false &&
  1281. strpos($test, 'quick/xenon') === false &&
  1282. strpos($test, 'slow/streams/') === false &&
  1283. strpos($test, 'slow/ext_mongo/') === false &&
  1284. strpos($test, 'slow/ext_oauth/') === false &&
  1285. strpos($test, 'slow/ext_yaml/') === false &&
  1286. strpos($test, 'slow/debugger/') === false &&
  1287. strpos($test, 'slow/type_profiler/debugger/') === false &&
  1288. strpos($test, 'zend/good/ext/standard/tests/array/') === false &&
  1289. strpos($test, 'zend/good/ext/ftp') === false &&
  1290. strrpos($test, HHAS_EXT) !== (strlen($test) - strlen(HHAS_EXT))
  1291. ;
  1292. }
  1293. const SERVER_TIMEOUT = 45;
  1294. function run_config_server($options, $test) {
  1295. if (!isset($options['server']) || !can_run_server_test($test)) {
  1296. return null;
  1297. }
  1298. $config = find_file_for_dir(dirname($test), 'config.ini');
  1299. $port = $options['servers']['configs'][$config]['port'];
  1300. $ch = curl_init("localhost:$port/$test");
  1301. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  1302. curl_setopt($ch, CURLOPT_TIMEOUT, SERVER_TIMEOUT);
  1303. curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
  1304. $output = curl_exec($ch);
  1305. if ($output === false) {
  1306. // The server probably crashed so fall back to cli to determine if this was
  1307. // the test that caused the crash. Our parent process will see that the
  1308. // server died and restart it.
  1309. if (getenv('HHVM_TEST_SERVER_LOG')) {
  1310. printf("Curl failed: %d\n", curl_errno($ch));
  1311. }
  1312. return null;
  1313. }
  1314. curl_close($ch);
  1315. $output = trim($output);
  1316. return array($output, '');
  1317. }
  1318. function run_config_cli($options, $test, $cmd, $cmd_env) {
  1319. if (isset($options['log']) && !isset($options['typechecker'])) {
  1320. $cmd_env['TRACE'] = 'printir:1';
  1321. $cmd_env['HPHP_TRACE_FILE'] = $test . '.log';
  1322. }
  1323. $descriptorspec = array(
  1324. 0 => array("pipe", "r"),
  1325. 1 => array("pipe", "w"),
  1326. 2 => array("pipe", "w"),
  1327. );
  1328. $pipes = null;
  1329. if (isset($options['typechecker'])) {
  1330. $process = proc_open(
  1331. "$cmd 2>/dev/null", $descriptorspec, $pipes, null, $cmd_env
  1332. );
  1333. } else {
  1334. $process = proc_open("$cmd 2>&1", $descriptorspec, $pipes, null, $cmd_env);
  1335. }
  1336. if (!is_resource($process)) {
  1337. file_put_contents("$test.diff", "Couldn't invoke $cmd");
  1338. return false;
  1339. }
  1340. fclose($pipes[0]);
  1341. $output = stream_get_contents($pipes[1]);
  1342. $output = trim($output);
  1343. $stderr = stream_get_contents($pipes[2]);
  1344. fclose($pipes[1]);
  1345. fclose($pipes[2]);
  1346. proc_close($process);
  1347. return array($output, $stderr);
  1348. }
  1349. function run_config_post($outputs, $test, $options) {
  1350. $output = $outputs[0];
  1351. $stderr = $outputs[1];
  1352. file_put_contents("$test.out", $output);
  1353. // hhvm redirects errors to stdout, so anything on stderr is really bad.
  1354. if ($stderr) {
  1355. file_put_contents(
  1356. "$test.diff",
  1357. "Test failed because the process wrote on stderr:\n$stderr"
  1358. );
  1359. return false;
  1360. }
  1361. // Needed for testing non-hhvm binaries that don't actually run the code
  1362. // e.g. parser/test/parse_tester.cpp.
  1363. if ($output == "FORCE PASS") {
  1364. return true;
  1365. }
  1366. $repeats = 0;
  1367. if (isset($options['relocate'])) {
  1368. $repeats = $options['relocate'] * 2;
  1369. }
  1370. if (isset($options['recycle-tc'])) {
  1371. $repeats = $options['recycle-tc'];
  1372. }
  1373. list($file, $type) = get_expect_file_and_type($test, $options);
  1374. if ($file === null || $type === null) {
  1375. file_put_contents(
  1376. "$test.diff", "No $test.expect, $test.expectf, " .
  1377. "$test.hhvm.expect, $test.hhvm.expectf, " .
  1378. "$test.typechecker.expect, $test.typechecker.expectf, " .
  1379. "nor $test.expectregex. Is $test even a test?"
  1380. );
  1381. return false;
  1382. }
  1383. $is_tc = isset($options['typechecker']);
  1384. if ((!$is_tc && ($type === 'expect' || $type === 'hhvm.expect')) ||
  1385. ($is_tc && $type === 'typechecker.expect')) {
  1386. $wanted = trim(file_get_contents($file));
  1387. if (!$repeats) {
  1388. $passed = !strcmp($output, $wanted);
  1389. if (!$passed) {
  1390. file_put_contents("$test.diff", generate_diff($wanted, null, $output));
  1391. }
  1392. return $passed;
  1393. }
  1394. $wanted_re = preg_quote($wanted);
  1395. } else if ((!$is_tc && ($type === 'expectf' || $type === 'hhvm.expectf')) ||
  1396. ($is_tc && $type === 'typechecker.expectf')) {
  1397. $wanted = trim(file_get_contents($file));
  1398. $wanted_re = $wanted;
  1399. // do preg_quote, but miss out any %r delimited sections.
  1400. $temp = "";
  1401. $r = "%r";
  1402. $startOffset = 0;
  1403. $length = strlen($wanted_re);
  1404. while($startOffset < $length) {
  1405. $start = strpos($wanted_re, $r, $startOffset);
  1406. if ($start !== false) {
  1407. // we have found a start tag.
  1408. $end = strpos($wanted_re, $r, $start+2);
  1409. if ($end === false) {
  1410. // unbalanced tag, ignore it.
  1411. $end = $start = $length;
  1412. }
  1413. } else {
  1414. // no more %r sections.
  1415. $start = $end = $length;
  1416. }
  1417. // quote a non re portion of the string.
  1418. $temp = $temp.preg_quote(substr($wanted_re, $startOffset,
  1419. ($start - $startOffset)), '/');
  1420. // add the re unquoted.
  1421. if ($end > $start) {
  1422. $temp = $temp.'('.substr($wanted_re, $start+2, ($end - $start-2)).')';
  1423. }
  1424. $startOffset = $end + 2;
  1425. }
  1426. $wanted_re = $temp;
  1427. $wanted_re = str_replace(
  1428. array('%binary_string_optional%'),
  1429. 'string',
  1430. $wanted_re
  1431. );
  1432. $wanted_re = str_replace(
  1433. array('%unicode_string_optional%'),
  1434. 'string',
  1435. $wanted_re
  1436. );
  1437. $wanted_re = str_replace(
  1438. array('%unicode\|string%', '%string\|unicode%'),
  1439. 'string',
  1440. $wanted_re
  1441. );
  1442. $wanted_re = str_replace(
  1443. array('%u\|b%', '%b\|u%'),
  1444. '',
  1445. $wanted_re
  1446. );
  1447. // Stick to basics.
  1448. $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re);
  1449. $wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re);
  1450. $wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re);
  1451. $wanted_re = str_replace('%a', '.+', $wanted_re);
  1452. $wanted_re = str_replace('%A', '.*', $wanted_re);
  1453. $wanted_re = str_replace('%w', '\s*', $wanted_re);
  1454. $wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re);
  1455. $wanted_re = str_replace('%d', '\d+', $wanted_re);
  1456. $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re);
  1457. // %f allows two points "-.0.0" but that is the best *simple* expression.
  1458. $wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?',
  1459. $wanted_re);
  1460. $wanted_re = str_replace('%c', '.', $wanted_re);
  1461. // must be last.
  1462. $wanted_re = str_replace('%%', '%%?', $wanted_re);
  1463. // Normalize newlines.
  1464. $wanted_re = preg_replace("/(\r\n?|\n)/", "\n", $wanted_re);
  1465. $output = preg_replace("/(\r\n?|\n)/", "\n", $output);
  1466. } else if (!$is_tc && $type === 'expectregex') {
  1467. $wanted_re = trim(file_get_contents($file));
  1468. } else {
  1469. throw new Exception("Unsupported expect file type: ".$type);
  1470. }
  1471. if ($repeats) {
  1472. $wanted_re = "($wanted_re\s*)".'{'.$repeats.'}';
  1473. }
  1474. if (!isset($wanted)) $wanted = $wanted_re;
  1475. $passed = @preg_match("/^$wanted_re\$/s", $output);
  1476. if ($passed === false && $repeats) {
  1477. // $repeats can cause the regex to become too big, and fail
  1478. // to compile.
  1479. return 'skip';
  1480. }
  1481. if (!$passed) {
  1482. file_put_contents("$test.diff",
  1483. generate_diff($wanted_re, $wanted_re, $output));
  1484. }
  1485. return $passed;
  1486. }
  1487. function run_one_config($options, $test, $cmd, $cmd_env) {
  1488. $outputs = run_config_cli($options, $test, $cmd, $cmd_env);
  1489. if ($outputs === false) return false;
  1490. return run_config_post($outputs, $test, $options);
  1491. }
  1492. function run_and_lock_test($options, $test) {
  1493. $lock = fopen($test, 'r');
  1494. if (!flock($lock, LOCK_EX)) return false;
  1495. if (isset($options['typechecker'])) {
  1496. $result = run_typechecker_test($options, $test);
  1497. } else {
  1498. $result = run_test($options, $test);
  1499. }
  1500. if (!flock($lock, LOCK_UN)) return false;
  1501. fclose($lock);
  1502. return $result;
  1503. }
  1504. function run_typechecker_test($options, $test) {
  1505. if (skip_test($options, $test)) return 'skip';
  1506. if (!file_exists($test . ".hhconfig")) return 'skip';
  1507. list($hh_server, $hh_server_env, $temp_dir) = hh_server_cmd($options, $test);
  1508. if (is_executable('/usr/bin/timeout')) {
  1509. $hh_server = '/usr/bin/timeout 300 ' . $hh_server;
  1510. } else {
  1511. $hh_server = __DIR__.'/../tools/timeout.sh -t 300 '. $hh_server;
  1512. }
  1513. $result = run_one_config($options, $test, $hh_server, $hh_server_env);
  1514. // Remove the temporary directory.
  1515. if (!isset($options['no-clean'])) {
  1516. shell_exec('rm -rf ' . $temp_dir);
  1517. }
  1518. return $result;
  1519. }
  1520. function run_test($options, $test) {
  1521. if (skip_test($options, $test

Large files files are truncated, but you can click here to view the full file