/hphp/test/run
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
- #!/usr/bin/env php
- <?php
- /**
- * Run the test suites in various configurations.
- */
- function is_testing_dso_extension() {
- // detecting if we're running outside of the hhvm codebase.
- return !is_file(__DIR__ . "/../../hphp/test/run");
- }
- function get_expect_file_and_type($test, $options) {
- // .typechecker files are for typechecker (hh_server --check) test runs.
- $types = null;
- if (isset($options['typechecker'])) {
- $types = array('typechecker.expect', 'typechecker.expectf');
- } else {
- $types = array('expect', 'hhvm.expect', 'expectf', 'hhvm.expectf',
- 'expectregex');
- }
- if (isset($options['repo'])) {
- foreach ($types as $type) {
- $fname = "$test.$type-repo";
- if (file_exists($fname)) {
- return array($fname, $type);
- }
- }
- }
- foreach ($types as $type) {
- $fname = "$test.$type";
- if (file_exists($fname)) {
- return array($fname, $type);
- }
- }
- return array(null, null);
- }
- function usage() {
- global $argv;
- return "usage: $argv[0] [-m jit|interp] [-r] <test/directories>";
- }
- function help() {
- global $argv;
- $ztestexample = 'test/zend/good/*/*z*.php'; // sep. for syntax highlighting.
- $help = <<<EOT
- This is the hhvm test-suite runner. For more detailed documentation,
- see hphp/test/README.md.
- The test argument may be a path to a php test file, a directory name, or
- one of a few pre-defined suite names that this script knows about.
- If you work with hhvm a lot, you might consider a bash alias:
- alias ht="path/to/hphp/test/run"
- Examples:
- # Quick tests in JIT mode:
- % $argv[0] test/quick
- # Slow tests in interp mode:
- % $argv[0] -m interp test/slow
- # PHP specificaion tests in JIT mode:
- % $argv[0] test/spec
- # Slow closure tests in JIT mode:
- % $argv[0] test/slow/closure
- # Slow closure tests in JIT mode with RepoAuthoritative:
- % $argv[0] -r test/slow/closure
- # Slow array tests, in RepoAuthoritative:
- % $argv[0] -r test/slow/array
- # Zend tests with a "z" in their name:
- % $argv[0] $ztestexample
- # Quick tests in JIT mode with some extra runtime options:
- % $argv[0] test/quick -a '-vEval.JitMaxTranslations=120 -vEval.HHIRRefcountOpts=0'
- # All quick tests except debugger
- % $argv[0] -e debugger test/quick
- # All tests except those containing a string of 3 digits
- % $argv[0] -E '/\d{3}/' all
- # All tests whose name containing pdo_mysql
- % $argv[0] -i pdo_mysql -m jit -r zend
- # Print all the standard tests
- % $argv[0] --list-tests
- # Use a specific HHVM binary
- % $argv[0] -b ~/code/hhvm/hphp/hhvm/hhvm
- % $argv[0] --hhvm-binary-path ~/code/hhvm/hphp/hhvm/hhvm
- # Use relocation to run tests in the same thread. e.g, 6 times in the same thread,
- # where the 3 specifies a random relocation for the 3rd request and the test is
- # run 3 * 2 times.
- % $argv[0] --relocate 3 test/quick/silencer.php
- # Run the Hack typechecker against quick typechecker.expect[f] files
- # Could explcitly use quick here too
- # $argv[0] --typechecker
- # Run the Hack typechecker against typechecker.expect[f] files in the slow
- # directory
- # $argv[0] --typechecker slow
- # Run the Hack typechecker against the typechecker.expect[f] file in this test
- # $argv[0] --typechecker test/slow/test_runner_typechecker_mode/basic.php
- # Use a specific typechecker binary
- # $argv[0] --hhserver-binary-path ~/code/hhvm/hphp/hack/bin/hh_server --typechecker .
- EOT;
- return usage().$help;
- }
- function error($message) {
- print "$message\n";
- exit(1);
- }
- // If a user-supplied path is provided, let's make sure we have a valid
- // executable.
- function check_executable($path, $typechecker) {
- $type = $typechecker ? "HH_SERVER" : "HHVM";
- $rpath = realpath($path);
- $msg = "Provided ".$type." executable (".$path.") is not a file.\n"
- . "If using ".$type."_BIN, make sure that is set correctly.";
- if (!is_file($rpath)) {
- error($msg);
- }
- $output = array();
- exec($rpath . " --help", $output);
- $str = implode($output);
- $msg = "Provided file (".$rpath.") is not a/an ".$type." executable.\n"
- . "If using ".$type."_BIN, make sure that is set correctly.";
- if (strpos($str, "Usage") !== 0) {
- error($msg);
- }
- }
- function hhvm_binary_routes() {
- $routes = array(
- "fbbuild" => "/_bin/hphp/hhvm",
- "buck" => "/buck-out/gen/hphp/hhvm/hhvm",
- "cmake" => "/hphp/hhvm"
- );
- $env_root = getenv("FBMAKE_BIN_ROOT");
- if ($env_root !== false) {
- $routes["fbbuild"] = "/" . $env_root . "/hphp/hhvm";
- }
- return $routes;
- }
- function hh_server_binary_routes() {
- return array(
- "fbbuild" => "/_bin/hphp/hack/src",
- "buck" => "/buck-out/gen/hphp/hack/src/hh_server",
- "cmake" => "/hphp/hack/bin"
- );
- $env_root = getenv("FBMAKE_BIN_ROOT");
- if ($env_root !== false) {
- $routes["fbbuild"] = "/" . $env_root . "/hphp/hack/src";
- }
- return $routes;
- }
- // For Facebook: We have several build systems, and we can use any of them in
- // the same code repo. If multiple binaries exist, we want the onus to be on
- // the user to specify a particular one because before we chose the fbmake one
- // by default and that could cause unexpected results.
- function check_for_multiple_default_binaries($typechecker) {
- // Env var we use in testing that'll pick which build system to use.
- if (getenv("FBCODE_BUILD_TOOL") !== false) {
- return;
- }
- $home = hphp_home();
- $routes = $typechecker ? hh_server_binary_routes() : hhvm_binary_routes();
- $binary = $typechecker ? "hh_server" : "hhvm";
- $found = array();
- foreach ($routes as $_ => $path) {
- $abs_path = $home . $path . "/" . $binary;
- if (file_exists($abs_path)) {
- $found[] = $abs_path;
- }
- }
- if (count($found) <= 1) {
- return;
- }
- $path_option = $typechecker ? "--hhserver-binary-path" : "--hhvm-binary-path";
- $msg = "Multiple binaries exist in this repo. \n";
- foreach ($found as $bin) {
- $msg .= " - " . $bin . "\n";
- }
- $msg .= "Are you in fbcode? If so, remove a binary \n"
- . "or use the " . $path_option . " option to the test runner. \n"
- . "e.g., test/run ";
- if ($typechecker) {
- $msg .= "--typechecker";
- }
- $msg .= " " . $path_option . " /path/to/binary slow\n";
- error($msg);
- }
- function hphp_home() {
- if (is_testing_dso_extension()) {
- return realpath(__DIR__);
- }
- return realpath(__DIR__.'/../..');
- }
- function idx($array, $key, $default = null) {
- return isset($array[$key]) ? $array[$key] : $default;
- }
- function hhvm_path() {
- $file = "";
- if (getenv("HHVM_BIN") !== false) {
- $file = realpath(getenv("HHVM_BIN"));
- } else {
- $file = bin_root().'/hhvm';
- }
- if (!is_file($file)) {
- if (is_testing_dso_extension()) {
- exec("which hhvm", $output);
- if (isset($output[0]) && $output[0]) {
- return $output[0];
- }
- error("You need to specify hhvm bin with env HHVM_BIN");
- }
- error("$file doesn't exist. Did you forget to build first?");
- }
- return rel_path($file);
- }
- function bin_root() {
- if (getenv("HHVM_BIN") !== false) {
- return dirname(realpath(getenv("HHVM_BIN")));
- }
- $home = hphp_home();
- $env_tool = getenv("FBCODE_BUILD_TOOL");
- $routes = hhvm_binary_routes();
- if ($env_tool !== false) {
- return $home . $routes[$env_tool];
- }
- foreach ($routes as $_ => $path) {
- $dir = $home . $path;
- if (is_dir($dir)) {
- return $dir;
- }
- }
- return $home . $routes["cmake"];
- }
- function hh_server_path() {
- $file = "";
- if (getenv("HH_SERVER_BIN") !== false) {
- $file = realpath(getenv("HH_SERVER_BIN"));
- } else {
- $file = hh_server_bin_root().'/hh_server';
- }
- if (!is_file($file)) {
- error("$file doesn't exist. Did you forget to build first?");
- }
- return rel_path($file);
- }
- function hh_server_bin_root() {
- if (getenv("HH_SERVER_BIN") !== false) {
- return dirname(realpath(getenv("HH_SERVER_BIN")));
- }
- $home = hphp_home();
- $env_tool = getenv("FBCODE_BUILD_TOOL");
- $routes = hh_server_binary_routes();
- if ($env_tool !== false) {
- return $home . $routes[$env_tool];
- }
- foreach ($routes as $_ => $path) {
- $dir = $home . $path;
- if (is_dir($dir)) {
- return $dir;
- }
- }
- return $home . $routes["cmake"];
- }
- function verify_hhbc() {
- if (getenv("VERIFY_HHBC") !== false) {
- return getenv($env_hhbc);
- }
- return bin_root().'/verify.hhbc';
- }
- function read_opts_file($file) {
- if (!file_exists($file)) {
- return "";
- }
- $fp = fopen($file, "r");
- $contents = "";
- while ($line = fgets($fp)) {
- // Compress out white space.
- $line = preg_replace('/\s+/', ' ', $line);
- // Discard simple line oriented ; and # comments to end of line
- // Comments at end of line (after payload) are not allowed.
- $line = preg_replace('/^ *;.*$/', ' ', $line);
- $line = preg_replace('/^ *#.*$/', ' ', $line);
- // Substitute in the directory name
- $line = str_replace('__DIR__', dirname($file), $line);
- $contents .= $line;
- }
- fclose($fp);
- return $contents;
- }
- // http://stackoverflow.com/questions/2637945/
- function rel_path($to) {
- $from = explode('/', getcwd().'/');
- $to = explode('/', $to);
- $relPath = $to;
- foreach ($from as $depth => $dir) {
- // find first non-matching dir.
- if ($dir === $to[$depth]) {
- // ignore this directory.
- array_shift($relPath);
- } else {
- // get number of remaining dirs to $from.
- $remaining = count($from) - $depth;
- if ($remaining > 1) {
- // add traversals up to first matching dir.
- $padLength = (count($relPath) + $remaining - 1) * -1;
- $relPath = array_pad($relPath, $padLength, '..');
- break;
- } else {
- $relPath[0] = './' . $relPath[0];
- }
- }
- }
- return implode('/', $relPath);
- }
- function get_options($argv) {
- $parameters = array(
- 'exclude:' => 'e:',
- 'exclude-pattern:' => 'E:',
- 'include:' => 'i:',
- 'include-pattern:' => 'I:',
- 'repo' => 'r',
- 'mode:' => 'm:',
- 'server' => 's',
- 'shuffle' => '',
- 'help' => 'h',
- 'verbose' => 'v',
- 'fbmake' => '',
- 'testpilot' => '',
- 'threads:' => '',
- 'args:' => 'a:',
- 'log' => 'l',
- 'failure-file:' => '',
- 'arm' => '',
- 'wholecfg' => '',
- 'hhas-round-trip' => '',
- 'color' => 'c',
- 'no-fun' => '',
- 'cores' => '',
- 'no-clean' => '',
- 'list-tests' => '',
- 'relocate:' => '',
- 'recycle-tc:' => '',
- 'hhvm-binary-path:' => 'b:',
- 'typechecker' => '',
- 'hhserver-binary-path:' => '',
- );
- $options = array();
- $files = array();
- /*
- * '-' argument causes all future arguments to be treated as filenames, even
- * if they would otherwise match a valid option. Otherwise, arguments starting
- * with '-' MUST match a valid option.
- */
- $force_file = false;
- for ($i = 1; $i < count($argv); $i++) {
- $arg = $argv[$i];
- if (strlen($arg) == 0) {
- continue;
- } else if ($force_file) {
- $files[] = $arg;
- } else if ($arg === '-') {
- $forcefile = true;
- } else if ($arg[0] === '-') {
- $found = false;
- foreach ($parameters as $long => $short) {
- if ($arg == '-'.str_replace(':', '', $short) ||
- $arg == '--'.str_replace(':', '', $long)) {
- if (substr($long, -1, 1) == ':') {
- $value = $argv[++$i];
- } else {
- $value = true;
- }
- $options[str_replace(':', '', $long)] = $value;
- $found = true;
- break;
- }
- }
- if (!$found) {
- error(sprintf("Invalid argument: '%s'\nSee $argv[0] --help", $arg));
- }
- } else {
- $files[] = $arg;
- }
- }
- if (isset($options['repo']) && isset($options['hhas-round-trip'])) {
- echo "repo and hhas-round-trip are mutually exclusive options\n";
- exit(1);
- }
- if (isset($options['relocate']) && isset($options['recycle-tc'])) {
- echo "relocate and recycle-tc are mutually exclusive options\n";
- exit(1);
- }
- return array($options, $files);
- }
- /*
- * We support some 'special' file names, that just know where the test
- * suites are, to avoid typing 'hphp/test/foo'.
- */
- function find_test_files($file) {
- $mappage = array(
- 'quick' => 'hphp/test/quick',
- 'slow' => 'hphp/test/slow',
- 'spec' => 'hphp/test/spec',
- 'debugger' => 'hphp/test/server/debugger/tests',
- 'http' => 'hphp/test/server/http/tests',
- 'fastcgi' => 'hphp/test/server/fastcgi/tests',
- 'zend' => 'hphp/test/zend/good',
- 'facebook' => 'hphp/facebook/test',
- // Subsets of zend tests.
- 'zend_ext' => 'hphp/test/zend/good/ext',
- 'zend_ext_am' => 'hphp/test/zend/good/ext/[a-m]*',
- 'zend_ext_nz' => 'hphp/test/zend/good/ext/[n-z]*',
- 'zend_Zend' => 'hphp/test/zend/good/Zend',
- 'zend_tests' => 'hphp/test/zend/good/tests',
- 'zend_bad' => 'hphp/test/zend/bad',
- );
- if (isset($mappage[$file])) {
- $matches = glob(hphp_home().'/'.$mappage[$file]);
- if (count($matches) == 0) {
- error(sprintf(
- "Convenience test name '%s' is recognized but does not match any test ".
- "files (pattern = '%s', hphp_home = '%s')",
- $file, $mappage[$file], hphp_home()));
- }
- return $matches;
- } else {
- return array($file, );
- }
- }
- // Some tests have to be run together in the same test bucket, serially, one
- // after other in order to avoid races and other collisions.
- function serial_only_tests($tests) {
- if (is_testing_dso_extension()) {
- return array();
- }
- // Add a <testname>.php.serial file to make your test run in the serial
- // bucket.
- $serial_tests = array_filter(
- $tests,
- function($test) {
- return file_exists($test . '.serial');
- }
- );
- return $serial_tests;
- }
- function find_tests($files, array $options = null) {
- if (!$files) {
- $files = array('quick');
- }
- if ($files == array('all')) {
- $files = array('quick', 'slow', 'spec', 'zend', 'fastcgi');
- }
- $ft = array();
- foreach ($files as $file) {
- $ft = array_merge($ft, find_test_files($file));
- }
- $files = $ft;
- foreach ($files as &$file) {
- if (!@stat($file)) {
- error("Not valid file or directory: '$file'");
- }
- $file = preg_replace(',//+,', '/', realpath($file));
- $file = preg_replace(',^'.getcwd().'/,', '', $file);
- }
- $files = array_map('escapeshellarg', $files);
- $files = implode(' ', $files);
- if (isset($options['typechecker'])) {
- $tests = explode("\n", shell_exec(
- "find $files -name '*.php' -o -name '*.php.type-errors'"
- ));
- // The above will get all the php files. Now filter out only the ones
- // that have a .hhconfig associated with it.
- $tests = array_filter(
- $tests,
- function($test) {
- return (file_exists($test . '.typechecker.expect') ||
- file_exists($test . '.typechecker.expectf')) &&
- file_exists($test . '.hhconfig');
- }
- );
- } else {
- $tests = explode("\n", shell_exec(
- "find $files -name '*.php' -o -name '*.php.type-errors' " .
- "-o -name '*.hhas' | grep -v round_trip.hhas"
- ));
- $tests = array_filter(
- $tests,
- function($test) {
- return file_exists($test . '.expect') ||
- file_exists($test . '.expectf') ||
- file_exists($test . '.hhvm.expect') ||
- file_exists($test . '.hhvm.expectf');
- }
- );
- }
- if (!$tests) {
- error("Could not find any tests associated with your options.\n" .
- "Make sure your test path is correct and that you have " .
- "the right expect files for the tests you are trying to run.\n" .
- usage());
- }
- asort($tests);
- $tests = array_filter($tests);
- if (!empty($options['exclude'])) {
- $exclude = $options['exclude'];
- $tests = array_filter($tests, function($test) use ($exclude) {
- return (false === strpos($test, $exclude));
- });
- }
- if (!empty($options['exclude-pattern'])) {
- $exclude = $options['exclude-pattern'];
- $tests = array_filter($tests, function($test) use ($exclude) {
- return !preg_match($exclude, $test);
- });
- }
- if (!empty($options['include'])) {
- $include = $options['include'];
- $tests = array_filter($tests, function($test) use ($include) {
- return (false !== strpos($test, $include));
- });
- }
- if (!empty($options['include-pattern'])) {
- $include = $options['include-pattern'];
- $tests = array_filter($tests, function($test) use ($include) {
- return preg_match($include, $test);
- });
- }
- return $tests;
- }
- function list_tests($files, $options) {
- $args = array();
- $mode = idx($options, 'mode', '');
- switch ($mode) {
- case '':
- case 'jit':
- $args[] = '-m jit';
- break;
- case 'interp';
- $args[] = '-m interp';
- break;
- default:
- throw new Exception("Unsupported mode for listing tests: ".$mode);
- }
- if (isset($options['repo'])) {
- $args[] = '-r';
- }
- if (isset($options['relocate'])) {
- $args[] = '--relocate';
- $args[] = $options['relocate'];
- }
- if (isset($options['recycle-tc'])) {
- $args[] = '--recycle-tc';
- $args[] = $options['recycle-tc'];
- }
- foreach (find_tests($files, $options) as $test) {
- print Status::jsonEncode(array(
- 'args' => implode(' ', $args),
- 'name' => $test,
- ))."\n";
- }
- }
- function find_test_ext($test, $ext) {
- if (is_file("{$test}.{$ext}")) {
- return "{$test}.{$ext}";
- }
- return find_file_for_dir(dirname($test), "config.{$ext}");
- }
- function find_file($test, $name) {
- return find_file_for_dir(dirname($test), $name);
- }
- function find_file_for_dir($dir, $name) {
- // Handle the case where the $dir might come in as '.' because you
- // are running the test runner on a file from the same directory as
- // the test e.g., './mytest.php'. dirname() will give you the '.' when
- // you actually have a lot of path to traverse upwards like
- // /home/you/code/tests/mytest.php. Use realpath() to get that.
- $dir = realpath($dir);
- while ($dir !== '/' && is_dir($dir)) {
- $file = "$dir/$name";
- if (is_file($file)) {
- return $file;
- }
- $dir = dirname($dir);
- }
- $file = __DIR__.'/'.$name;
- if (file_exists($file)) {
- return $file;
- }
- return null;
- }
- function find_debug_config($test, $name) {
- $debug_config = find_file_for_dir(dirname($test), $name);
- if ($debug_config !== null) {
- return "-m debug --debug-config ".$debug_config;
- }
- return "";
- }
- function mode_cmd($options) {
- $repo_args = '';
- if (!isset($options['repo'])) {
- // Set the non-repo-mode shared repo.
- // When in repo mode, we set our own central path.
- $repo_args = "-vRepo.Local.Mode=-- -vRepo.Central.Path=".verify_hhbc();
- }
- $jit_args = "$repo_args -vEval.Jit=true";
- $mode = idx($options, 'mode', '');
- switch ($mode) {
- case '':
- case 'jit':
- return "$jit_args";
- case 'pgo':
- return $jit_args.
- ' -vEval.JitPGO=1'.
- ' -vEval.JitPGORegionSelector=hottrace'.
- ' -vEval.JitPGOHotOnly=0';
- case 'interp':
- return "$repo_args -vEval.Jit=0";
- default:
- error("-m must be one of jit | pgo | interp. Got: '$mode'");
- }
- }
- function extra_args($options) {
- return idx($options, 'args', '');
- }
- function hhvm_cmd_impl() {
- $args = func_get_args();
- $options = array_shift($args);
- $config = array_shift($args);
- $extra_args = $args;
- $args = array(
- hhvm_path(),
- '-c',
- $config,
- '-vEval.EnableArgsInBacktraces=true',
- '-vEval.EnableIntrinsicsExtension=true',
- mode_cmd($options),
- isset($options['arm']) ? '-vEval.SimulateARM=1' : '',
- isset($options['wholecfg']) ? '-vEval.JitPGORegionSelector=wholecfg' : '',
- extra_args($options),
- );
- if (isset($options['relocate'])) {
- $args[] = '--count='.($options['relocate'] * 2);
- $args[] = '-vEval.JitAHotSize=6000000';
- $args[] = '-vEval.HotFuncCount=0';
- $args[] = '-vEval.PerfRelocate='.$options['relocate'];
- }
- if (isset($options['recycle-tc'])) {
- $args[] = '--count='.$options['recycle-tc'];
- $args[] = '-vEval.StressUnitCacheFreq=1';
- $args[] = '-vEval.EnableReusableTC=true';
- }
- if (!isset($options['cores'])) {
- $args[] = '-vResourceLimit.CoreFileSize=0';
- }
- return implode(' ', array_merge($args, $extra_args));
- }
- // Return the command and the env to run it in.
- function hhvm_cmd($options, $test, $test_run = null) {
- if ($test_run === null) {
- $test_run = $test;
- }
- // hdf support is only temporary until we fully migrate to ini
- // Discourage broad use.
- $hdf_suffix = ".use.for.ini.migration.testing.only.hdf";
- $hdf = file_exists($test.$hdf_suffix)
- ? '-c ' . $test . $hdf_suffix
- : "";
- $cmd = hhvm_cmd_impl(
- $options,
- find_test_ext($test, 'ini'),
- $hdf,
- find_debug_config($test, 'hphpd.ini'),
- read_opts_file(find_test_ext($test, 'opts')),
- '--file',
- escapeshellarg($test_run)
- );
- // Special support for tests that require a path to the current
- // test directory for things like prepend_file and append_file
- // testing.
- if (file_exists($test.'.ini')) {
- $contents = file_get_contents($test.'.ini');
- if (strpos($contents, '{PWD}') !== false) {
- $test_ini = tempnam('/tmp', $test).'.ini';
- file_put_contents($test_ini,
- str_replace('{PWD}', dirname($test), $contents));
- $cmd .= " -c $test_ini";
- }
- }
- if ($hdf !== "") {
- $contents = file_get_contents($test.$hdf_suffix);
- if (strpos($contents, '{PWD}') !== false) {
- $test_hdf = tempnam('/tmp', $test).$hdf_suffix;
- file_put_contents($test_hdf,
- str_replace('{PWD}', dirname($test), $contents));
- $cmd .= " -c $test_hdf";
- }
- }
- if (isset($options['repo'])) {
- $hhbbc_repo = "\"$test.repo/hhvm.hhbbc\"";
- $cmd .= ' -vRepo.Authoritative=true -vRepo.Commit=0';
- $cmd .= " -vRepo.Central.Path=$hhbbc_repo";
- }
- // Command line arguments
- $cli_args = find_test_ext($test, 'cli_args');
- if ($cli_args !== null) {
- $cmd .= " " . file_get_contents($cli_args);
- }
- $env = $_ENV;
- // If there's an <test name>.env file then inject the contents of that into
- // the test environment.
- $env_file = find_test_ext($test, 'env');
- if ($env_file !== null) {
- $extra_env = explode("\n", trim(file_get_contents($env_file)));
- foreach ($extra_env as $arg) {
- $i = strpos($arg, '=');
- if ($i) {
- $key = substr($arg, 0, $i);
- $val = substr($arg, $i + 1);
- $env[$key] = $val;
- } else {
- unset($env[$arg]);
- }
- }
- }
- $in = find_test_ext($test, 'in');
- if ($in !== null) {
- $cmd .= ' < ' . escapeshellarg($in);
- // If we're piping the input into the command then setup a simple
- // dumb terminal so hhvm doesn't try to control it and pollute the
- // output with control characters, which could change depending on
- // a wide variety of terminal settings.
- $env["TERM"] = "dumb";
- }
- return array($cmd, $env);
- }
- function hphp_cmd($options, $test) {
- $extra_args = preg_replace("/-v\s*/", "-vRuntime.", extra_args($options));
- return implode(" ", array(
- "HHVM_DISABLE_HHBBC2=1",
- hhvm_path(),
- '--hphp',
- '--config',
- find_file($test, 'hphp_config.ini'),
- read_opts_file("$test.hphp_opts"),
- "-thhbc -l0 -k1 -o \"$test.repo\" \"$test\"",
- $extra_args
- ));
- }
- function hhbbc_cmd($options, $test) {
- return implode(" ", array(
- hhvm_path(),
- '--hhbbc',
- '--no-logging',
- '--parallel-num-threads=1',
- read_opts_file("$test.hhbbc_opts"),
- "-o \"$test.repo/hhvm.hhbbc\" \"$test.repo/hhvm.hhbc\"",
- ));
- }
- function hh_server_cmd($options, $test) {
- // In order to run hh_server --check on only one file, we copy all of the
- // files associated with the test to a temporary directory, rename the
- // basename($test_file).hhconfig file to just .hhconfig and set the command
- // appropriately.
- $temp_dir = '/tmp/hh-test-runner-'.bin2hex(random_bytes(16));
- mkdir($temp_dir);
- foreach (glob($test . '*') as $test_file) {
- copy($test_file, $temp_dir . '/' . basename($test_file));
- if (strpos($test_file, '.hhconfig') !== false) {
- rename(
- $temp_dir . '/' . basename($test) . '.hhconfig',
- $temp_dir . '/.hhconfig'
- );
- } else if (strpos($test_file, '.type-errors') !== false) {
- // In order to actually run hh_server --check successfully, all files
- // named *.php.type-errors have to be renamed *.php
- rename(
- $temp_dir . '/' . basename($test_file),
- $temp_dir . '/' . str_replace('.type-errors', '', basename($test_file))
- );
- }
- }
- // Just copy all the .php.inc files, even if they are not related since
- // unrelated ones will be ignored anyway. This just makes it easier to
- // start with instead of doing a search inside the test file for requires
- // and includes and extracting it.
- foreach (glob(dirname($test) . "/*.inc.php") as $inc_file) {
- copy($inc_file, $temp_dir . '/' . basename($inc_file));
- }
- $cmd = hh_server_path() . ' --check ' . $temp_dir;
- return array($cmd, ' ', $temp_dir);
- }
- class Status {
- private static $results = array();
- private static $mode = 0;
- private static $use_color = false;
- private static $queue = null;
- private static $killed = false;
- public static $key;
- private static $overall_start_time = 0;
- private static $overall_end_time = 0;
- private static $tempdir = "";
- const MODE_NORMAL = 0;
- const MODE_VERBOSE = 1;
- const MODE_FBMAKE = 2;
- const MODE_TESTPILOT = 3;
- const MSG_STARTED = 7;
- const MSG_FINISHED = 1;
- const MSG_TEST_PASS = 2;
- const MSG_TEST_FAIL = 4;
- const MSG_TEST_SKIP = 5;
- const MSG_SERVER_RESTARTED = 6;
- const RED = 31;
- const GREEN = 32;
- const YELLOW = 33;
- const BLUE = 34;
- const PASS_SERVER = 0;
- const SKIP_SERVER = 1;
- const PASS_CLI = 2;
- private static function getTempDir() {
- self::$tempdir = sys_get_temp_dir();
- // Apparently some systems might not put the trailing slash
- if (substr(self::$tempdir, -1) !== "/") {
- self::$tempdir .= "/";
- }
- self::$tempdir .= substr( md5(rand()), 0, 8);
- mkdir(self::$tempdir);
- }
- // Since we run the tests in forked processes, state is not shared
- // So we cannot keep a static variable adding individual test times.
- // But we can put the times files and add the values later.
- public static function setTestTime($time) {
- file_put_contents(tempnam(self::$tempdir, "trun"), $time);
- }
- // The total time running the tests if they were run serially.
- public static function addTestTimesSerial() {
- $time = 0;
- $files = scandir(self::$tempdir);
- foreach ($files as $file) {
- if (strpos($file, 'trun') === 0) {
- $time += floatval(file_get_contents(self::$tempdir . "/" . $file));
- unlink(self::$tempdir . "/" . $file);
- }
- }
- return $time;
- }
- public static function setMode($mode) {
- self::$mode = $mode;
- }
- public static function setUseColor($use) {
- self::$use_color = $use;
- }
- public static function getMode() {
- return self::$mode;
- }
- public static function getOverallStartTime() {
- return self::$overall_start_time;
- }
- public static function getOverallEndTime() {
- return self::$overall_end_time;
- }
- public static function started() {
- self::getTempDir();
- self::send(self::MSG_STARTED, null);
- self::$overall_start_time = microtime(true);
- }
- public static function finished() {
- self::$overall_end_time = microtime(true);
- self::send(self::MSG_FINISHED, null);
- }
- public static function killQueue() {
- if (!self::$killed) {
- msg_remove_queue(self::$queue);
- self::$queue = null;
- self::$killed = true;
- }
- }
- public static function pass($test, $detail, $time, $stime, $etime) {
- array_push(self::$results, array('name' => $test,
- 'status' => 'passed',
- 'start_time' => $stime,
- 'end_time' => $etime,
- 'time' => $time));
- $how = $detail === 'pass-server' ? self::PASS_SERVER :
- ($detail === 'skip-server' ? self::SKIP_SERVER : self::PASS_CLI);
- self::send(self::MSG_TEST_PASS, array($test, $how, $time, $stime, $etime));
- }
- public static function skip($test, $reason, $time, $stime, $etime) {
- if (self::getMode() === self::MODE_FBMAKE) {
- /* Intentionally supress skips */
- } elseif (self::getMode() === self::MODE_TESTPILOT) {
- /* testpilot needs a positive response for every test run, report
- * that this test isn't relevant so it can silently drop. */
- array_push(self::$results, array('name' => $test,
- 'status' => 'not_relevant',
- 'start_time' => $stime,
- 'end_time' => $etime,
- 'time' => $time));
- } else {
- array_push(self::$results, array('name' => $test,
- 'status' => 'skipped',
- 'start_time' => $stime,
- 'end_time' => $etime,
- 'time' => $time));
- }
- self::send(self::MSG_TEST_SKIP,
- array($test, $reason, $time, $stime, $etime));
- }
- public static function fail($test, $time, $stime, $etime) {
- array_push(self::$results, array(
- 'name' => $test,
- 'status' => 'failed',
- 'details' => self::utf8Sanitize(@file_get_contents("$test.diff")),
- 'start_time' => $stime,
- 'end_time' => $etime,
- 'time' => $time
- ));
- self::send(self::MSG_TEST_FAIL, array($test, $time, $stime, $etime));
- }
- public static function serverRestarted() {
- self::send(self::MSG_SERVER_RESTARTED, null);
- }
- private static function send($type, $msg) {
- if (self::$killed) {
- return;
- }
- msg_send(self::getQueue(), $type, $msg);
- }
- /**
- * Takes a variable number of string arguments. If color output is enabled
- * and any one of the arguments is preceded by an integer (see the color
- * constants above), that argument will be given the indicated color.
- */
- public static function sayColor() {
- $args = func_get_args();
- while (count($args)) {
- $color = null;
- $str = array_shift($args);
- if (is_integer($str)) {
- $color = $str;
- if (self::$use_color) {
- print "\033[0;${color}m";
- }
- $str = array_shift($args);
- }
- print $str;
- if (self::$use_color && !is_null($color)) {
- print "\033[0m";
- }
- }
- }
- public static function sayFBMake($test, $status, $stime, $etime) {
- $start = array('op' => 'start', 'test' => $test);
- $end = array('op' => 'test_done', 'test' => $test, 'status' => $status,
- 'start_time' => $stime, 'end_time' => $etime);
- if ($status == 'failed') {
- $end['details'] = self::utf8Sanitize(@file_get_contents("$test.diff"));
- }
- self::say($start, $end);
- }
- public static function getResults() {
- return self::$results;
- }
- /** Output is in the format expected by JsonTestRunner. */
- public static function say(/* ... */) {
- $data = array_map(function($row) {
- return self::jsonEncode($row) . "\n";
- }, func_get_args());
- fwrite(STDERR, implode("", $data));
- }
- public static function hasCursorControl() {
- // for runs on hudson-ci.org (aka jenkins).
- if (getenv("HUDSON_URL")) {
- return false;
- }
- // for runs on travis-ci.org
- if (getenv("TRAVIS")) {
- return false;
- }
- $stty = self::getSTTY();
- if (!$stty) {
- return false;
- }
- return strpos($stty, 'erase = <undef>') === false;
- }
- public static function getSTTY() {
- $descriptorspec = array(1 => array("pipe", "w"), 2 => array("pipe", "w"));
- $process = proc_open(
- 'stty -a', $descriptorspec, $pipes, null, null,
- array('suppress_errors' => true)
- );
- $stty = stream_get_contents($pipes[1]);
- proc_close($process);
- return $stty;
- }
- public static function utf8Sanitize($str) {
- if (!is_string($str)) {
- // We sometimes get called with the
- // return value of file_get_contents()
- // when fgc() has failed.
- return '';
- }
- if (class_exists('UConverter')) {
- return UConverter::transcode($str, 'UTF-8', 'UTF-8');
- }
- // UConverter is PHP5.5 or later.
- // Do the equivalent using a slower user-space implementation.
- $ret = '';
- $len = strlen($str);
- $pos = 0;
- while ($pos < $len) {
- $c = $str[$pos];
- $co = ord($c);
- if ($co < 0x80) {
- // U+0000 - U+007F ASCII
- $ret .= $c;
- ++$pos;
- } elseif ((($co & 0xE0) == 0xC0) &&
- (($len - $pos) > 1) &&
- ((ord($str[$pos+1]) & 0xC0) == 0x80)) {
- // U+0080 - U+07FF Lower Basic Multilingual Plane
- $ret .= substr($str, $pos, 2);
- $pos += 2;
- } elseif ((($co & 0xF0) == 0xE0) &&
- (($len - $pos) > 2) &&
- ((ord($str[$pos+1]) & 0xC0) == 0x80) &&
- ((ord($str[$pos+2]) & 0xC0) == 0x80)) {
- // U+0800 - U+FFFF Upper Basic Multilingual Plane
- $ret .= substr($str, $pos, 3);
- $pos += 3;
- } elseif ((($co & 0xF8) == 0xF0) &&
- (($len - $pos) > 3) &&
- ((ord($str[$pos+1]) & 0xC0) == 0x80) &&
- ((ord($str[$pos+2]) & 0xC0) == 0x80) &&
- ((ord($str[$pos+3]) & 0xC0) == 0x80)) {
- // U+010000 - U+10FFFF Supplementary Multilingual Planes
- $ret .= substr($str, $pos, 4);
- $pos += 4;
- } else {
- // Invalid UTF8
- $ret .= "\xEF\xBF\xBD"; // U+FFFD REPLACEMENT CHARACTER
- ++$pos;
- }
- }
- return $ret;
- }
- public static function jsonEncode($data) {
- // JSON_UNESCAPED_SLASHES is Zend 5.4+.
- if (defined("JSON_UNESCAPED_SLASHES")) {
- return json_encode($data, JSON_UNESCAPED_SLASHES);
- }
- $json = json_encode($data);
- return str_replace('\\/', '/', $json);
- }
- public static function getQueue() {
- if (!self::$queue) {
- self::$queue = msg_get_queue(self::$key);
- }
- return self::$queue;
- }
- }
- function clean_intermediate_files($test, $options) {
- if (isset($options['no-clean'])) {
- return;
- }
- $exts = array('out', 'diff', 'repo');
- foreach ($exts as $ext) {
- $file = "$test.$ext";
- if (file_exists($file)) {
- if (is_dir($file)) {
- foreach(new RecursiveIteratorIterator(new
- RecursiveDirectoryIterator($file, FilesystemIterator::SKIP_DOTS),
- RecursiveIteratorIterator::CHILD_FIRST) as $path) {
- $path->isDir()
- ? rmdir($path->getPathname())
- : unlink($path->getPathname());
- }
- rmdir($file);
- } else {
- unlink($file);
- }
- }
- }
- }
- function run($options, $tests, $bad_test_file) {
- foreach ($tests as $test) {
- $stime = time();
- $time = microtime(true);
- $status = run_and_lock_test($options, $test);
- $time = microtime(true) - $time;
- $etime = time();
- Status::setTestTime($time);
- if ($status === 'skip') {
- Status::skip($test, null, $time, $stime, $etime);
- clean_intermediate_files($test, $options);
- } else if ($status === 'skip-norepo') {
- Status::skip($test, 'norepo', $time, $stime, $etime);
- clean_intermediate_files($test, $options);
- } else if ($status === 'skip-onlyrepo') {
- Status::skip($test, 'onlyrepo', $time, $stime, $etime);
- clean_intermediate_files($test, $options);
- } else if ($status) {
- Status::pass($test, $status, $time, $stime, $etime);
- clean_intermediate_files($test, $options);
- } else {
- Status::fail($test, $time, $stime, $etime);
- }
- }
- file_put_contents($bad_test_file, json_encode(Status::getResults()));
- foreach (Status::getResults() as $result) {
- if ($result['status'] == 'failed') {
- return 1;
- }
- }
- return 0;
- }
- function skip_test($options, $test) {
- $skipif_test = find_test_ext($test, 'skipif');
- if (!$skipif_test) {
- return false;
- }
- // For now, run the .skipif in non-repo since building a repo for it is hard.
- $options_without_repo = $options;
- unset($options_without_repo['repo']);
- list($hhvm, $_) = hhvm_cmd($options_without_repo, $test, $skipif_test);
- $descriptorspec = array(
- 0 => array("pipe", "r"),
- 1 => array("pipe", "w"),
- 2 => array("pipe", "w"),
- );
- $pipes = null;
- $process = proc_open("$hhvm $test 2>&1", $descriptorspec, $pipes);
- if (!is_resource($process)) {
- // This is weird. We can't run HHVM but we probably shouldn't skip the test
- // since on a broken build everything will show up as skipped and give you a
- // SHIPIT.
- return false;
- }
- fclose($pipes[0]);
- $output = stream_get_contents($pipes[1]);
- fclose($pipes[1]);
- proc_close($process);
- // The standard php5 .skipif semantics is if the .skipif outputs ANYTHING
- // then it should be skipped. This is a poor design, but I'll just add a
- // small blacklist of things that are really bad if they are output so we
- // surface the errors in the tests themselves.
- if (stripos($output, 'segmentation fault') !== false) {
- return false;
- }
- return strlen($output) != 0;
- }
- function comp_line($l1, $l2, $is_reg) {
- if ($is_reg) {
- return preg_match('/^'. $l1 . '$/s', $l2);
- } else {
- return !strcmp($l1, $l2);
- }
- }
- function count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2, $cnt1, $cnt2,
- $steps) {
- $equal = 0;
- while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2],
- $is_reg)) {
- $idx1++;
- $idx2++;
- $equal++;
- $steps--;
- }
- if (--$steps > 0) {
- $eq1 = 0;
- $st = $steps / 2;
- for ($ofs1 = $idx1 + 1; $ofs1 < $cnt1 && $st-- > 0; $ofs1++) {
- $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $ofs1, $idx2, $cnt1,
- $cnt2, $st);
- if ($eq > $eq1) {
- $eq1 = $eq;
- }
- }
- $eq2 = 0;
- $st = $steps;
- for ($ofs2 = $idx2 + 1; $ofs2 < $cnt2 && $st-- > 0; $ofs2++) {
- $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $ofs2, $cnt1, $cnt2, $st);
- if ($eq > $eq2) {
- $eq2 = $eq;
- }
- }
- if ($eq1 > $eq2) {
- $equal += $eq1;
- } else if ($eq2 > 0) {
- $equal += $eq2;
- }
- }
- return $equal;
- }
- function generate_array_diff($ar1, $ar2, $is_reg, $w) {
- $idx1 = 0; $ofs1 = 0; $cnt1 = @count($ar1);
- $idx2 = 0; $ofs2 = 0; $cnt2 = @count($ar2);
- $diff = array();
- $old1 = array();
- $old2 = array();
- while ($idx1 < $cnt1 && $idx2 < $cnt2) {
- if (comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) {
- $idx1++;
- $idx2++;
- continue;
- } else {
- $c1 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1+1, $idx2, $cnt1,
- $cnt2, 10);
- $c2 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2+1, $cnt1,
- $cnt2, 10);
- if ($c1 > $c2) {
- $old1[$idx1] = sprintf("%03d- ", $idx1+1) . $w[$idx1++];
- $last = 1;
- } else if ($c2 > 0) {
- $old2[$idx2] = sprintf("%03d+ ", $idx2+1) . $ar2[$idx2++];
- $last = 2;
- } else {
- $old1[$idx1] = sprintf("%03d- ", $idx1+1) . $w[$idx1++];
- $old2[$idx2] = sprintf("%03d+ ", $idx2+1) . $ar2[$idx2++];
- }
- }
- }
- reset($old1); $k1 = key($old1); $l1 = -2;
- reset($old2); $k2 = key($old2); $l2 = -2;
- while ($k1 !== null || $k2 !== null) {
- if ($k1 == $l1 + 1 || $k2 === null) {
- $l1 = $k1;
- $diff[] = current($old1);
- $k1 = next($old1) ? key($old1) : null;
- } else if ($k2 == $l2 + 1 || $k1 === null) {
- $l2 = $k2;
- $diff[] = current($old2);
- $k2 = next($old2) ? key($old2) : null;
- } else if ($k1 < $k2) {
- $l1 = $k1;
- $diff[] = current($old1);
- $k1 = next($old1) ? key($old1) : null;
- } else {
- $l2 = $k2;
- $diff[] = current($old2);
- $k2 = next($old2) ? key($old2) : null;
- }
- }
- while ($idx1 < $cnt1) {
- $diff[] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++];
- }
- while ($idx2 < $cnt2) {
- $diff[] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++];
- }
- return $diff;
- }
- function generate_diff($wanted, $wanted_re, $output)
- {
- $w = explode("\n", $wanted);
- $o = explode("\n", $output);
- if (is_null($wanted_re)) {
- $r = $w;
- } else {
- if (preg_match('/^\((.*)\)\{(\d+)\}$/s', $wanted_re, $m)) {
- $t = explode("\n", $m[1]);
- $r = array();
- $w2 = array();
- for ($i = 0; $i < $m[2]; $i++) {
- foreach ($t as $v) {
- $r[] = $v;
- }
- foreach ($w as $v) {
- $w2[] = $v;
- }
- }
- $w = $wanted === $wanted_re ? $r : $w2;
- } else {
- $r = explode("\n", $wanted_re);
- }
- }
- $diff = generate_array_diff($r, $o, !is_null($wanted_re), $w);
- return implode("\r\n", $diff);
- }
- function dump_hhas_to_temp($hhvm_cmd, $test) {
- $tmp_file = $test . '.round_trip.hhas';
- system("$hhvm_cmd -vEval.DumpHhas=1 > $tmp_file", $ret);
- if ($ret) { echo "system failed\n"; exit(1); }
- return $tmp_file;
- }
- const HHAS_EXT = '.hhas';
- function can_run_server_test($test) {
- return
- !is_file("$test.noserver") &&
- !find_test_ext($test, 'opts') &&
- !is_file("$test.ini") &&
- !is_file("$test.onlyrepo") &&
- strpos($test, 'quick/debugger') === false &&
- strpos($test, 'quick/xenon') === false &&
- strpos($test, 'slow/streams/') === false &&
- strpos($test, 'slow/ext_mongo/') === false &&
- strpos($test, 'slow/ext_oauth/') === false &&
- strpos($test, 'slow/ext_yaml/') === false &&
- strpos($test, 'slow/debugger/') === false &&
- strpos($test, 'slow/type_profiler/debugger/') === false &&
- strpos($test, 'zend/good/ext/standard/tests/array/') === false &&
- strpos($test, 'zend/good/ext/ftp') === false &&
- strrpos($test, HHAS_EXT) !== (strlen($test) - strlen(HHAS_EXT))
- ;
- }
- const SERVER_TIMEOUT = 45;
- function run_config_server($options, $test) {
- if (!isset($options['server']) || !can_run_server_test($test)) {
- return null;
- }
- $config = find_file_for_dir(dirname($test), 'config.ini');
- $port = $options['servers']['configs'][$config]['port'];
- $ch = curl_init("localhost:$port/$test");
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($ch, CURLOPT_TIMEOUT, SERVER_TIMEOUT);
- curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
- $output = curl_exec($ch);
- if ($output === false) {
- // The server probably crashed so fall back to cli to determine if this was
- // the test that caused the crash. Our parent process will see that the
- // server died and restart it.
- if (getenv('HHVM_TEST_SERVER_LOG')) {
- printf("Curl failed: %d\n", curl_errno($ch));
- }
- return null;
- }
- curl_close($ch);
- $output = trim($output);
- return array($output, '');
- }
- function run_config_cli($options, $test, $cmd, $cmd_env) {
- if (isset($options['log']) && !isset($options['typechecker'])) {
- $cmd_env['TRACE'] = 'printir:1';
- $cmd_env['HPHP_TRACE_FILE'] = $test . '.log';
- }
- $descriptorspec = array(
- 0 => array("pipe", "r"),
- 1 => array("pipe", "w"),
- 2 => array("pipe", "w"),
- );
- $pipes = null;
- if (isset($options['typechecker'])) {
- $process = proc_open(
- "$cmd 2>/dev/null", $descriptorspec, $pipes, null, $cmd_env
- );
- } else {
- $process = proc_open("$cmd 2>&1", $descriptorspec, $pipes, null, $cmd_env);
- }
- if (!is_resource($process)) {
- file_put_contents("$test.diff", "Couldn't invoke $cmd");
- return false;
- }
- fclose($pipes[0]);
- $output = stream_get_contents($pipes[1]);
- $output = trim($output);
- $stderr = stream_get_contents($pipes[2]);
- fclose($pipes[1]);
- fclose($pipes[2]);
- proc_close($process);
- return array($output, $stderr);
- }
- function run_config_post($outputs, $test, $options) {
- $output = $outputs[0];
- $stderr = $outputs[1];
- file_put_contents("$test.out", $output);
- // hhvm redirects errors to stdout, so anything on stderr is really bad.
- if ($stderr) {
- file_put_contents(
- "$test.diff",
- "Test failed because the process wrote on stderr:\n$stderr"
- );
- return false;
- }
- // Needed for testing non-hhvm binaries that don't actually run the code
- // e.g. parser/test/parse_tester.cpp.
- if ($output == "FORCE PASS") {
- return true;
- }
- $repeats = 0;
- if (isset($options['relocate'])) {
- $repeats = $options['relocate'] * 2;
- }
- if (isset($options['recycle-tc'])) {
- $repeats = $options['recycle-tc'];
- }
- list($file, $type) = get_expect_file_and_type($test, $options);
- if ($file === null || $type === null) {
- file_put_contents(
- "$test.diff", "No $test.expect, $test.expectf, " .
- "$test.hhvm.expect, $test.hhvm.expectf, " .
- "$test.typechecker.expect, $test.typechecker.expectf, " .
- "nor $test.expectregex. Is $test even a test?"
- );
- return false;
- }
- $is_tc = isset($options['typechecker']);
- if ((!$is_tc && ($type === 'expect' || $type === 'hhvm.expect')) ||
- ($is_tc && $type === 'typechecker.expect')) {
- $wanted = trim(file_get_contents($file));
- if (!$repeats) {
- $passed = !strcmp($output, $wanted);
- if (!$passed) {
- file_put_contents("$test.diff", generate_diff($wanted, null, $output));
- }
- return $passed;
- }
- $wanted_re = preg_quote($wanted);
- } else if ((!$is_tc && ($type === 'expectf' || $type === 'hhvm.expectf')) ||
- ($is_tc && $type === 'typechecker.expectf')) {
- $wanted = trim(file_get_contents($file));
- $wanted_re = $wanted;
- // do preg_quote, but miss out any %r delimited sections.
- $temp = "";
- $r = "%r";
- $startOffset = 0;
- $length = strlen($wanted_re);
- while($startOffset < $length) {
- $start = strpos($wanted_re, $r, $startOffset);
- if ($start !== false) {
- // we have found a start tag.
- $end = strpos($wanted_re, $r, $start+2);
- if ($end === false) {
- // unbalanced tag, ignore it.
- $end = $start = $length;
- }
- } else {
- // no more %r sections.
- $start = $end = $length;
- }
- // quote a non re portion of the string.
- $temp = $temp.preg_quote(substr($wanted_re, $startOffset,
- ($start - $startOffset)), '/');
- // add the re unquoted.
- if ($end > $start) {
- $temp = $temp.'('.substr($wanted_re, $start+2, ($end - $start-2)).')';
- }
- $startOffset = $end + 2;
- }
- $wanted_re = $temp;
- $wanted_re = str_replace(
- array('%binary_string_optional%'),
- 'string',
- $wanted_re
- );
- $wanted_re = str_replace(
- array('%unicode_string_optional%'),
- 'string',
- $wanted_re
- );
- $wanted_re = str_replace(
- array('%unicode\|string%', '%string\|unicode%'),
- 'string',
- $wanted_re
- );
- $wanted_re = str_replace(
- array('%u\|b%', '%b\|u%'),
- '',
- $wanted_re
- );
- // Stick to basics.
- $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re);
- $wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re);
- $wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re);
- $wanted_re = str_replace('%a', '.+', $wanted_re);
- $wanted_re = str_replace('%A', '.*', $wanted_re);
- $wanted_re = str_replace('%w', '\s*', $wanted_re);
- $wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re);
- $wanted_re = str_replace('%d', '\d+', $wanted_re);
- $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re);
- // %f allows two points "-.0.0" but that is the best *simple* expression.
- $wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?',
- $wanted_re);
- $wanted_re = str_replace('%c', '.', $wanted_re);
- // must be last.
- $wanted_re = str_replace('%%', '%%?', $wanted_re);
- // Normalize newlines.
- $wanted_re = preg_replace("/(\r\n?|\n)/", "\n", $wanted_re);
- $output = preg_replace("/(\r\n?|\n)/", "\n", $output);
- } else if (!$is_tc && $type === 'expectregex') {
- $wanted_re = trim(file_get_contents($file));
- } else {
- throw new Exception("Unsupported expect file type: ".$type);
- }
- if ($repeats) {
- $wanted_re = "($wanted_re\s*)".'{'.$repeats.'}';
- }
- if (!isset($wanted)) $wanted = $wanted_re;
- $passed = @preg_match("/^$wanted_re\$/s", $output);
- if ($passed === false && $repeats) {
- // $repeats can cause the regex to become too big, and fail
- // to compile.
- return 'skip';
- }
- if (!$passed) {
- file_put_contents("$test.diff",
- generate_diff($wanted_re, $wanted_re, $output));
- }
- return $passed;
- }
- function run_one_config($options, $test, $cmd, $cmd_env) {
- $outputs = run_config_cli($options, $test, $cmd, $cmd_env);
- if ($outputs === false) return false;
- return run_config_post($outputs, $test, $options);
- }
- function run_and_lock_test($options, $test) {
- $lock = fopen($test, 'r');
- if (!flock($lock, LOCK_EX)) return false;
- if (isset($options['typechecker'])) {
- $result = run_typechecker_test($options, $test);
- } else {
- $result = run_test($options, $test);
- }
- if (!flock($lock, LOCK_UN)) return false;
- fclose($lock);
- return $result;
- }
- function run_typechecker_test($options, $test) {
- if (skip_test($options, $test)) return 'skip';
- if (!file_exists($test . ".hhconfig")) return 'skip';
- list($hh_server, $hh_server_env, $temp_dir) = hh_server_cmd($options, $test);
- if (is_executable('/usr/bin/timeout')) {
- $hh_server = '/usr/bin/timeout 300 ' . $hh_server;
- } else {
- $hh_server = __DIR__.'/../tools/timeout.sh -t 300 '. $hh_server;
- }
- $result = run_one_config($options, $test, $hh_server, $hh_server_env);
- // Remove the temporary directory.
- if (!isset($options['no-clean'])) {
- shell_exec('rm -rf ' . $temp_dir);
- }
- return $result;
- }
- function run_test($options, $test) {
- if (skip_test($options, $test…
Large files files are truncated, but you can click here to view the full file