Run hhbbc repo tests whenever we run repo tests
[hiphop-php.git] / hphp / test / run
blobd6c959eb72b3fc9dc4edef33a9336c854f902f41
1 #!/usr/bin/env php
2 <?php
3 /**
4 * Run the test suites in various configurations.
5 */
7 function usage() {
8 global $argv;
9 return "usage: $argv[0] [-m jit|interp] [-r] <test/directories>";
12 function help() {
13 global $argv;
14 $ztestexample = 'test/zend/good/*/*z*.php'; // sep. for syntax highlighting
15 $help = <<<EOT
18 This is the hhvm test-suite runner. For more detailed documentation,
19 see hphp/test/README.md.
21 The test argument may be a path to a php test file, a directory name, or
22 one of a few pre-defined suite names that this script knows about.
24 If you work with hhvm a lot, you might consider a bash alias:
26 alias ht="path/to/fbcode/hphp/test/run"
28 Examples:
30 # Quick tests in JIT mode:
31 % $argv[0] test/quick
33 # Slow tests in interp mode:
34 % $argv[0] -m interp test/slow
36 # Slow closure tests in JIT mode:
37 % $argv[0] test/slow/closure
39 # Slow closure tests in JIT mode with RepoAuthoritative:
40 % $argv[0] -r test/slow/closure
42 # Slow array tests, in RepoAuthoritative:
43 % $argv[0] -r test/slow/array
45 # Zend tests with a "z" in their name:
46 % $argv[0] $ztestexample
48 # Quick tests in JIT mode with some extra runtime options:
49 % $argv[0] test/quick -a '-vEval.JitMaxTranslations=120 -vEval.HHIRJumpOpts=0'
51 # All quick tests except debugger
52 % $argv[0] -e debugger test/quick
54 # All tests except those containing a string of 3 digits
55 % $argv[0] -E '/\d{3}/' all
57 # All tests whose name containing pdo_mysql
58 % $argv[0] -i pdo_mysql -m jit -r zend
60 EOT;
61 return usage().$help;
64 function error($message) {
65 print "$message\n";
66 exit(1);
69 function hphp_home() {
70 return realpath(__DIR__.'/../..');
73 function idx($array, $key, $default = null) {
74 return isset($array[$key]) ? $array[$key] : $default;
77 function idx_file($array, $key, $default = null) {
78 $file = is_file(idx($array, $key)) ? realpath($array[$key]) : $default;
79 if (!is_file($file)) {
80 error("$file doesn't exist. Did you forget to build first?");
82 return rel_path($file);
85 function bin_root() {
86 $dir = hphp_home() . '/' . idx($_ENV, 'FBMAKE_BIN_ROOT', '_bin');
87 return is_dir($dir) ?
88 $dir : # fbmake
89 hphp_home() # github
93 function verify_hhbc() {
94 return idx($_ENV, 'VERIFY_HHBC', bin_root().'/verify.hhbc');
97 function read_file($file) {
98 return file_exists($file) ?
99 str_replace('__DIR__', dirname($file),
100 preg_replace('/\s+/', ' ', (file_get_contents($file))))
101 : "";
104 // http://stackoverflow.com/questions/2637945/
105 function rel_path($to) {
106 $from = explode('/', getcwd().'/');
107 $to = explode('/', $to);
108 $relPath = $to;
110 foreach($from as $depth => $dir) {
111 // find first non-matching dir
112 if($dir === $to[$depth]) {
113 // ignore this directory
114 array_shift($relPath);
115 } else {
116 // get number of remaining dirs to $from
117 $remaining = count($from) - $depth;
118 if($remaining > 1) {
119 // add traversals up to first matching dir
120 $padLength = (count($relPath) + $remaining - 1) * -1;
121 $relPath = array_pad($relPath, $padLength, '..');
122 break;
123 } else {
124 $relPath[0] = './' . $relPath[0];
128 return implode('/', $relPath);
131 function get_options($argv) {
132 $parameters = array(
133 'exclude:' => 'e:',
134 'exclude-pattern:' => 'E:',
135 'include:' => 'i:',
136 'include-pattern:' => 'I:',
137 'repo' => 'r',
138 'mode:' => 'm:',
139 'server' => '',
140 'help' => 'h',
141 'verbose' => 'v',
142 'fbmake' => '',
143 'threads:' => '',
144 'args:' => 'a:',
145 'log' => 'l',
146 'failure-file:' => '',
147 'arm' => '',
148 'hhas-round-trip' => '',
150 $options = array();
151 $files = array();
152 for ($i = 1; $i < count($argv); $i++) {
153 $arg = $argv[$i];
154 $found = false;
155 if ($arg && $arg[0] == '-') {
156 foreach ($parameters as $long => $short) {
157 if ($arg == '-'.str_replace(':', '', $short) ||
158 $arg == '--'.str_replace(':', '', $long)) {
159 if (substr($long, -1, 1) == ':') {
160 $value = $argv[++$i];
161 } else {
162 $value = true;
164 $options[str_replace(':', '', $long)] = $value;
165 $found = true;
166 break;
170 if (!$found && $arg) {
171 $files[] = $arg;
175 if (isset($options['repo']) && isset($options['hhas-round-trip'])) {
176 echo "repo and hhas-round-trip are mutually exclusive options\n";
177 exit(1);
180 return array($options, $files);
184 * We support some 'special' file names, that just know where the test
185 * suites are, to avoid typing 'hphp/test/foo'.
187 function map_convenience_filename($file) {
188 $mappage = array(
189 'quick' => 'hphp/test/quick',
190 'slow' => 'hphp/test/slow',
191 'debugger' => 'hphp/test/server/debugger/tests',
192 'zend' => 'hphp/test/zend/good',
193 'facebook' => 'hphp/facebook/test',
195 // Subsets of zend tests.
196 'zend_ext' => 'hphp/test/zend/good/ext',
197 'zend_Zend' => 'hphp/test/zend/good/Zend',
198 'zend_tests' => 'hphp/test/zend/good/tests',
199 'zend_bad' => 'hphp/test/zend/bad',
202 if (!isset($mappage[$file])) {
203 return $file;
205 return hphp_home().'/'.$mappage[$file];
208 function find_tests($files, array $options = null) {
209 if (!$files) {
210 $files = array('quick');
212 if ($files == array('all')) {
213 $files = array('quick', 'slow', 'zend');
215 foreach ($files as &$file) {
216 $file = map_convenience_filename($file);
217 if (!@stat($file)) {
218 error("Not valid file or directory: '$file'");
220 $file = preg_replace(',//+,', '/', realpath($file));
221 $file = preg_replace(',^'.getcwd().'/,', '', $file);
223 $files = implode(' ', $files);
224 $tests = explode("\n", shell_exec(
225 "find $files -name '*.php' -o -name '*.hhas' | grep -v round_trip.hhas"
227 if (!$tests) {
228 error(usage());
230 asort($tests);
231 $tests = array_filter($tests);
232 if (!empty($options['exclude'])) {
233 $exclude = $options['exclude'];
234 $tests = array_filter($tests, function($test) use ($exclude) {
235 return (false === strpos($test, $exclude));
238 if (!empty($options['exclude-pattern'])) {
239 $exclude = $options['exclude-pattern'];
240 $tests = array_filter($tests, function($test) use ($exclude) {
241 return !preg_match($exclude, $test);
244 if (!empty($options['include'])) {
245 $include = $options['include'];
246 $tests = array_filter($tests, function($test) use ($include) {
247 return (false !== strpos($test, $include));
250 if (!empty($options['include-pattern'])) {
251 $include = $options['include-pattern'];
252 $tests = array_filter($tests, function($test) use ($include) {
253 return preg_match($include, $test);
256 return $tests;
259 function find_test_ext($test, $ext) {
260 if (is_file("{$test}.{$ext}")) {
261 return "{$test}.{$ext}";
263 return find_file_for_dir(dirname($test), "config.{$ext}");
266 function find_file($test, $name) {
267 return find_file_for_dir(dirname($test), $name);
270 function find_file_for_dir($dir, $name) {
271 while (($dir !== '.' && $dir !== '/') && is_dir($dir)) {
272 $file = "$dir/$name";
273 if (is_file($file)) {
274 return $file;
276 $dir = dirname($dir);
278 $file = __DIR__.'/'.$name;
279 if (file_exists($file)) {
280 return $file;
282 return null;
285 function find_debug_config($test, $name) {
286 $debug_config = find_file_for_dir(dirname($test), $name);
287 if ($debug_config !== null) {
288 return "-m debug --debug-config ".$debug_config;
290 return "";
293 function mode_cmd($options) {
294 $repo_args = "-vRepo.Local.Mode=-- -vRepo.Central.Path=".verify_hhbc();
295 $jit_args = "$repo_args -vEval.Jit=true";
296 $mode = idx($options, 'mode', '');
297 switch ($mode) {
298 case '':
299 case 'jit':
300 case 'automain':
301 return "$jit_args";
302 case 'pgo':
303 return "$jit_args -vEval.JitPGO=1 -vEval.JitRegionSelector=hottrace ".
304 "-vEval.JitPGOHotOnly=0";
305 case 'interp':
306 return "$repo_args -vEval.Jit=0";
307 default:
308 error("-m must be one of jit | pgo | interp | automain. Got: '$mode'");
312 function extra_args($options) {
313 return idx($options, 'args', '');
316 function hhvm_path() {
317 return idx_file($_ENV, 'HHVM_BIN', bin_root().'/hphp/hhvm/hhvm');
320 // Return the command and the env to run it in.
321 function hhvm_cmd($options, $test, $test_run = null) {
322 $use_automain = false;
323 if ($test_run === null) {
324 $use_automain = 'automain' === idx($options, 'mode')
325 && 'php' === pathinfo($test, PATHINFO_EXTENSION);
326 $test_run = $use_automain
327 ? __DIR__.'/pseudomain_wrapper.php'
328 : $test;
330 $cmd = implode(' ', array(
331 hhvm_path(),
332 '--config',
333 find_test_ext($test, 'hdf'),
334 find_debug_config($test, 'hphpd.hdf'),
335 mode_cmd($options),
336 '-vEval.EnableArgsInBacktraces=true',
337 read_file(find_test_ext($test, 'opts')),
338 isset($options['arm']) ? '-vEval.SimulateARM=1' : '',
339 extra_args($options),
340 '-vResourceLimit.CoreFileSize=0',
341 '--file',
342 escapeshellarg($test_run),
343 $use_automain ? escapeshellarg($test) : ''
345 if (file_exists($test.'.ini')) {
346 $cmd .= " -vServer.IniFile=$test.ini";
348 $env = $_ENV;
349 $in = find_test_ext($test, 'in');
350 if ($in !== null) {
351 $cmd .= ' < ' . escapeshellarg($in);
352 // If we're piping the input into the command then setup a simple
353 // dumb terminal so hhvm doesn't try to control it and pollute the
354 // output with control characters, which could change depending on
355 // a wide variety of terminal settings.
356 $env["TERM"] = "dumb";
358 return array($cmd, $env);
361 function hphp_cmd($options, $test) {
362 return implode(" ", array(
363 hhvm_path(),
364 '--hphp',
365 '--config',
366 find_file($test, 'hphp_config.hdf'),
367 read_file("$test.hphp_opts"),
368 "-thhbc -l0 -k1 -o $test.repo $test",
372 function hhbbc_cmd($options, $test) {
373 return implode(" ", array(
374 hhvm_path(),
375 '--hhbbc',
376 '--no-logging',
377 '--parallel-num-threads=1',
378 read_file("$test.hhbbc_opts"),
379 "-o $test.repo/hhvm.hhbbc $test.repo/hhvm.hhbc",
383 class Status {
384 private static $results = array();
385 private static $mode = 0;
387 const MODE_NORMAL = 0;
388 const MODE_VERBOSE = 1;
389 const MODE_FBMAKE = 2;
391 public static function setMode($mode) {
392 self::$mode = $mode;
395 public static function pass($test) {
396 array_push(self::$results, array('name' => $test, 'status' => 'passed'));
397 switch (self::$mode) {
398 case self::MODE_NORMAL:
399 if (self::hasColor()) {
400 print "\033[1;32m.\033[0m";
401 } else {
402 print '.';
404 break;
405 case self::MODE_VERBOSE:
406 if (self::hasColor()) {
407 print "$test \033[1;32mpassed\033[0m\n";
408 } else {
409 print "$test passed\n";
411 break;
412 case self::MODE_FBMAKE:
413 self::sayFBMake($test, 'passed');
414 break;
418 public static function skip($test, $reason = null) {
419 switch (self::$mode) {
420 case self::MODE_NORMAL:
421 if (self::hasColor()) {
422 print "\033[1;33ms\033[0m";
423 } else {
424 print 's';
426 break;
427 case self::MODE_VERBOSE:
428 if (self::hasColor()) {
429 if ($reason !== null) {
430 print "$test \033[1;33mskipped\033[0m ";
431 print "\033[1;31m$reason\033[0m\n";
432 } else {
433 print "$test \033[1;33mskipped\033[0m\n";
435 } else {
436 if ($reason !== null) {
437 print "$test skipped\n";
438 } else {
439 print "$test skipped - $reason\n";
442 break;
443 case self::MODE_FBMAKE:
444 // Say nothing...
445 break;
449 public static function fail($test) {
450 array_push(self::$results, array(
451 'name' => $test,
452 'status' => 'failed',
453 'details' => (string)@file_get_contents("$test.diff")
455 switch (self::$mode) {
456 case self::MODE_NORMAL:
457 $diff = (string)@file_get_contents($test.'.diff');
458 if (self::hasColor()) {
459 print "\n\033[0;31m$test\033[0m\n$diff";
460 } else {
461 print "\nFAILED: $test\n$diff";
463 break;
464 case self::MODE_VERBOSE:
465 if (self::hasColor()) {
466 print "$test \033[0;31mFAILED\033[0m\n";
467 } else {
468 print "$test FAILED\n";
470 break;
471 case self::MODE_FBMAKE:
472 self::sayFBMake($test, 'failed');
473 break;
477 private static function sayFBMake($test, $status) {
478 $start = array('op' => 'start', 'test' => $test);
479 $end = array('op' => 'test_done', 'test' => $test, 'status' => $status);
480 if ($status == 'failed') {
481 $end['details'] = (string)@file_get_contents("$test.diff");
483 self::say($start, $end);
486 public static function getResults() {
487 return self::$results;
490 /** Output is in the format expected by JsonTestRunner. */
491 public static function say(/* ... */) {
492 $data = array_map(function($row) {
493 return self::jsonEncode($row) . "\n";
494 }, func_get_args());
495 fwrite(STDERR, implode("", $data));
498 private static function hasColor() {
499 return posix_isatty(STDOUT);
502 public static function jsonEncode($data) {
503 // JSON_UNESCAPED_SLASHES is Zend 5.4+
504 if (defined("JSON_UNESCAPED_SLASHES")) {
505 return json_encode($data, JSON_UNESCAPED_SLASHES);
508 $json = json_encode($data);
509 return str_replace('\\/', '/', $json);
513 function run($options, $tests, $bad_test_file) {
514 if (isset($options['verbose'])) {
515 Status::setMode(Status::MODE_VERBOSE);
517 if (isset($options['fbmake'])) {
518 Status::setMode(Status::MODE_FBMAKE);
520 foreach ($tests as $test) {
521 $status = run_test($options, $test);
522 if ($status === 'skip') {
523 Status::skip($test);
524 } else if ($status === 'skip-norepo') {
525 Status::skip($test, 'norepo');
526 } else if ($status === 'skip-onlyrepo') {
527 Status::skip($test, 'onlyrepo');
528 } else if ($status) {
529 Status::pass($test);
530 } else {
531 Status::fail($test);
534 file_put_contents($bad_test_file, json_encode(Status::getResults()));
535 foreach (Status::getResults() as $result) {
536 if ($result['status'] == 'failed') {
537 return 1;
540 return 0;
543 function skip_test($options, $test) {
544 $skipif_test = find_test_ext($test, 'skipif');
545 if (!$skipif_test) {
546 return false;
549 list($hhvm, $_) = hhvm_cmd($options, $test, $skipif_test);
550 $out = shell_exec($hhvm . ' 2> /dev/null');
551 $out = trim($out);
552 return (bool)strlen($out);
555 function dump_hhas_to_temp($hhvm_cmd, $test) {
556 $tmp_file = $test . '.round_trip.hhas';
557 system("$hhvm_cmd -vEval.DumpHhas=1 > $tmp_file", $ret);
558 if ($ret) { echo "system failed\n"; exit(1); }
559 return $tmp_file;
562 function run_one_config($options, $test, $hhvm, $hhvm_env) {
563 $descriptorspec = array(
564 0 => array("pipe", "r"),
565 1 => array("pipe", "w"),
566 2 => array("pipe", "w"),
568 if (isset($options['log'])) {
569 $hhvm_env['TRACE'] = 'printir:1';
570 $hhvm_env['HPHP_TRACE_FILE'] = $test . '.log';
572 $pipes = null;
573 $process = proc_open("$hhvm 2>&1", $descriptorspec, $pipes, null, $hhvm_env);
574 if (!is_resource($process)) {
575 file_put_contents("$test.diff", "Couldn't invoke $hhvm");
576 return false;
579 fclose($pipes[0]);
580 $output .= trim(stream_get_contents($pipes[1]));
581 file_put_contents("$test.out", $output);
582 fclose($pipes[1]);
584 // hhvm redirects errors to stdout, so anything on stderr is really bad
585 $stderr = stream_get_contents($pipes[2]);
586 if ($stderr) {
587 file_put_contents(
588 "$test.diff",
589 "Test failed because the process wrote on stderr:\n$stderr"
591 return false;
593 fclose($pipes[2]);
594 proc_close($process);
596 // Needed for testing non-hhvm binaries that don't actually run the code
597 // e.g. parser/test/parse_tester.cpp
598 if ($output == "FORCE PASS") {
599 return true;
602 if (file_exists("$test.expect")) {
603 $diff_cmds = "--text -u";
604 file_put_contents("$test.expect-trimmed",
605 trim(file_get_contents("$test.expect")));
606 exec("diff --text -u $test.expect-trimmed $test.out > $test.diff 2>&1",
607 $_, $status);
608 // unix 0 == success
609 unlink("$test.expect-trimmed");
610 return !$status;
611 } else if (file_exists("$test.expectf")) {
612 $wanted_re = trim(file_get_contents("$test.expectf"));
614 // do preg_quote, but miss out any %r delimited sections
615 $temp = "";
616 $r = "%r";
617 $startOffset = 0;
618 $length = strlen($wanted_re);
619 while($startOffset < $length) {
620 $start = strpos($wanted_re, $r, $startOffset);
621 if ($start !== false) {
622 // we have found a start tag
623 $end = strpos($wanted_re, $r, $start+2);
624 if ($end === false) {
625 // unbalanced tag, ignore it.
626 $end = $start = $length;
628 } else {
629 // no more %r sections
630 $start = $end = $length;
632 // quote a non re portion of the string
633 $temp = $temp.preg_quote(substr($wanted_re, $startOffset,
634 ($start - $startOffset)), '/');
635 // add the re unquoted.
636 if ($end > $start) {
637 $temp = $temp.'('.substr($wanted_re, $start+2, ($end - $start-2)).')';
639 $startOffset = $end + 2;
641 $wanted_re = $temp;
643 $wanted_re = str_replace(
644 array('%binary_string_optional%'),
645 'string',
646 $wanted_re
648 $wanted_re = str_replace(
649 array('%unicode_string_optional%'),
650 'string',
651 $wanted_re
653 $wanted_re = str_replace(
654 array('%unicode\|string%', '%string\|unicode%'),
655 'string',
656 $wanted_re
658 $wanted_re = str_replace(
659 array('%u\|b%', '%b\|u%'),
661 $wanted_re
663 // Stick to basics
664 $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re);
665 $wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re);
666 $wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re);
667 $wanted_re = str_replace('%a', '.+', $wanted_re);
668 $wanted_re = str_replace('%A', '.*', $wanted_re);
669 $wanted_re = str_replace('%w', '\s*', $wanted_re);
670 $wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re);
671 $wanted_re = str_replace('%d', '\d+', $wanted_re);
672 $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re);
673 $wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?',
674 $wanted_re);
675 $wanted_re = str_replace('%c', '.', $wanted_re);
676 // %f allows two points "-.0.0" but that is the best *simple* expression
678 # a poor man's aide for debugging
679 shell_exec("diff --text -u $test.expectf $test.out > $test.diff 2>&1");
681 // Normalize newlines
682 $wanted_re = preg_replace("/(\r\n?|\n)/", "\n", $wanted_re);
683 $output = preg_replace("/(\r\n?|\n)/", "\n", $output);
685 return preg_match("/^$wanted_re\$/s", $output);
687 } else if (file_exists("$test.expectregex")) {
688 $wanted_re = trim(file_get_contents("$test.expectregex"));
690 # a poor man's aide for debugging
691 shell_exec("diff --text -u $test.expectregex $test.out > $test.diff 2>&1");
693 return preg_match("/^$wanted_re\$/s", $output);
697 function run_test($options, $test) {
698 if (skip_test($options, $test)) return 'skip';
700 $test_ext = pathinfo($test, PATHINFO_EXTENSION);
701 list($hhvm, $hhvm_env) = hhvm_cmd($options, $test);
703 $hhvm = __DIR__.'/../tools/timeout.sh -t 300 '.$hhvm;
704 $output = '';
706 if (isset($options['repo'])) {
707 if ($test_ext === 'hhas' ||
708 strpos($hhvm, '-m debug') !== false ||
709 file_exists($test.'.norepo')) {
710 return 'skip-norepo';
713 $repodb1 = "$test.repo/hhvm.hhbc";
714 $repodb2 = "$test.repo/hhvm.hhbbc";
715 if (file_exists($repodb1)) unlink($repodb1);
716 if (file_exists($repodb2)) unlink($repodb2);
717 $hphp = hphp_cmd($options, $test);
718 $hhbbc = hhbbc_cmd($options, $test);
719 $output .= shell_exec("$hphp 2>&1");
720 $output .= shell_exec("$hhbbc 2>&1");
721 $hhvm .= ' -vRepo.Authoritative=true -vRepo.Commit=0 ';
722 $hhvm1 = $hhvm."-vRepo.Central.Path=$repodb1";
723 $hhvm2 = $hhvm."-vRepo.Central.Path=$repodb2";
725 if (!($ret = run_one_config($options, $test, $hhvm1, $hhvm_env))) {
726 return $ret;
728 return run_one_config($options, $test, $hhvm2, $hhvm_env);
731 if (file_exists($test.'.onlyrepo')) {
732 return 'skip-onlyrepo';
734 if (isset($options['hhas-round-trip'])) {
735 $hhas_temp = dump_hhas_to_temp($hhvm, $test);
736 list($hhvm, $hhvm_env) = hhvm_cmd($options, $hhas_temp);
739 return run_one_config($options, $test, $hhvm, $hhvm_env);
742 function num_cpus() {
743 switch(PHP_OS) {
744 case 'Linux':
745 $data = file('/proc/stat');
746 $cores = 0;
747 foreach($data as $line) {
748 if (preg_match('/^cpu[0-9]/', $line)) {
749 $cores++;
752 return $cores;
753 case 'Darwin':
754 case 'FreeBSD':
755 return exec('sysctl -n hw.ncpu');
757 return 2; // default when we don't know how to detect
760 function make_header($str) {
761 return "\n\033[0;33m".$str."\033[0m\n";
764 function print_commands($tests, $options) {
765 print make_header("Run these by hand:");
767 foreach ($tests as $test) {
768 list($command, $_) = hhvm_cmd($options, $test);
769 if (!isset($options['repo'])) {
770 print "$command\n";
771 continue;
774 // How to run without hhbbc:
775 $command .= " -vRepo.Authoritative=true ";
776 $hphpc_hhvm = str_replace(verify_hhbc(), "$test.repo/hhvm.hhbc",
777 $command);
778 $hphpc_cmds = hphp_cmd($options, $test)."\n";
779 $hphpc_cmds .= $hphpc_hhvm."\n";
780 print "$hphpc_cmds\n\n";
782 // How to run it with hhbbc:
783 $hhbbc_hhvm = str_replace(verify_hhbc(), "$test.repo/hhvm.hhbbc",
784 $command);
785 $hhbbc_cmds = hphp_cmd($options, $test)."\n";
786 $hhbbc_cmds .= hhbbc_cmd($options, $test)."\n";
787 $hhbbc_cmds .= $hhbbc_hhvm."\n";
788 print "$hhbbc_cmds\n";
793 function print_success($tests, $options) {
794 print "\nAll tests passed.\n\n".<<<SHIP
795 | | |
796 )_) )_) )_)
797 )___))___))___)\
798 )____)____)_____)\\
799 _____|____|____|____\\\__
800 ---------\ SHIP IT /---------
801 ^^^^^ ^^^^^^^^^^^^^^^^^^^^^
802 ^^^^ ^^^^ ^^^ ^^
803 ^^^^ ^^^
804 SHIP
805 ."\n";
806 if (isset($options['verbose'])) {
807 print_commands($tests, $options);
811 function print_failure($argv, $results, $options) {
812 $failed = array();
813 foreach ($results as $result) {
814 if ($result['status'] == 'failed') {
815 $failed[] = $result['name'];
818 asort($failed);
819 print "\n".count($failed)." tests failed\n";
821 print make_header("See the diffs:").
822 implode("\n", array_map(
823 function($test) { return 'cat '.$test.'.diff'; },
824 $failed))."\n";
826 $failing_tests_file = !empty($options['failure-file'])
827 ? $options['failure-file']
828 : tempnam('/tmp', 'test-failures');
829 file_put_contents($failing_tests_file, implode("\n", $failed)."\n");
830 print make_header('For xargs, list of failures is available using:').
831 'cat '.$failing_tests_file."\n";
833 print_commands($failed, $options);
835 print make_header("Re-run just the failing tests:").
836 $argv[0].' '.implode(' ', $failed)."\n";
838 if (idx($options, 'mode') == 'automain') {
839 print make_header(
840 'Automain caveat: wrapper script may change semantics');
841 print 'The automain wrapper script ('.__DIR__.'/pseudomain_wrapper.php)'
842 .' may have changed behavior'."\n"
843 .'(e.g. extra frames in backtraces) by moving code from '
844 .'pseudo-main to file top-level and adding a wrapper function.'
845 ."\n";
849 function main($argv) {
850 ini_set('pcre.backtrack_limit', PHP_INT_MAX);
852 list($options, $files) = get_options($argv);
853 if (isset($options['help'])) {
854 error(help());
856 $tests = find_tests($files, $options);
858 hhvm_path(); // check that binary exists
860 $threads = min(count($tests), idx($options, 'threads', num_cpus() + 1));
862 if (!isset($options['fbmake'])) {
863 print "Running ".count($tests)." tests in $threads threads\n";
866 // Try to construct the buckets so the test results are ready in
867 // approximately alphabetical order
868 $test_buckets = array();
869 $i = 0;
870 foreach ($tests as $test) {
871 $test_buckets[$i][] = $test;
872 $i = ($i + 1) % $threads;
875 // Spawn off worker threads
876 $children = array();
877 // A poor man's shared memory
878 $bad_test_files = array();
879 for ($i = 0; $i < $threads; $i++) {
880 $bad_test_file = tempnam('/tmp', 'test-run-');
881 $bad_test_files[] = $bad_test_file;
882 $pid = pcntl_fork();
883 if ($pid == -1) {
884 error('could not fork');
885 } else if ($pid) {
886 $children[] = $pid;
887 } else {
888 exit(run($options, $test_buckets[$i], $bad_test_file));
892 // Wait for the kids
893 $return_value = 0;
894 foreach ($children as $child) {
895 pcntl_waitpid($child, $status);
896 $return_value |= pcntl_wexitstatus($status);
899 $results = array();
900 foreach ($bad_test_files as $bad_test_file) {
901 $json = json_decode(file_get_contents($bad_test_file), true);
902 if (!is_array($json)) {
903 error(
904 "No JSON output was received from a test thread. ".
905 "This might be a bug in the test script."
908 $results = array_merge($results, $json);
911 if (isset($options['fbmake'])) {
912 Status::say(array('op' => 'all_done', 'results' => $results));
913 return $return_value;
916 if (!$return_value) {
917 print_success($tests, $options);
918 return $return_value;
921 print_failure($argv, $results, $options);
922 return $return_value;
925 exit(main($argv));