From 9140c9038a83ff55a78f357f8485de086d83d94e Mon Sep 17 00:00:00 2001 From: Max Semenik Date: Wed, 10 Feb 2021 15:17:03 +0300 Subject: run-tests.php: class for test file loading This moves a bunch of code outside of run_tests(), making it a bit more manageable. Additionally, accessors provide better readability than isset() and friends. This is a minimal patch that moves the code but does not refactor much. For the sake of reviewing experience, it does not involve further refactoring which could include: * Removing setSection() * Fixing up the mess with hasSection() vs. sectionNotEmpty(), only one of which is really needed. * Moving more repetitive code into the new class. All of this will be done with later commits. Closes GH-6678. --- run-tests.php | 596 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 345 insertions(+), 251 deletions(-) (limited to 'run-tests.php') diff --git a/run-tests.php b/run-tests.php index c04d2f4a97..825718407c 100755 --- a/run-tests.php +++ b/run-tests.php @@ -1862,141 +1862,36 @@ TEST $file "; } - // Load the sections of the test file. - $section_text = ['TEST' => '']; - - $fp = fopen($file, "rb") or error("Cannot open test file: $file"); - - $bork_info = null; - - if (!feof($fp)) { - $line = fgets($fp); - - if ($line === false) { - $bork_info = "cannot read test"; - } - } else { - $bork_info = "empty test [$file]"; - } - if ($bork_info === null && strncmp('--TEST--', $line, 8)) { - $bork_info = "tests must start with --TEST-- [$file]"; - } - - $section = 'TEST'; - $secfile = false; - $secdone = false; - - while (!feof($fp)) { - $line = fgets($fp); - - if ($line === false) { - break; - } - - // Match the beginning of a section. - if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { - $section = (string) $r[1]; - - if (isset($section_text[$section]) && $section_text[$section]) { - $bork_info = "duplicated $section section"; - } - - // check for unknown sections - if (!in_array($section, [ - 'EXPECT', 'EXPECTF', 'EXPECTREGEX', 'EXPECTREGEX_EXTERNAL', 'EXPECT_EXTERNAL', 'EXPECTF_EXTERNAL', 'EXPECTHEADERS', - 'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS', - 'FILE', 'FILEEOF', 'FILE_EXTERNAL', 'REDIRECTTEST', - 'CAPTURE_STDIO', 'STDIN', 'CGI', 'PHPDBG', - 'INI', 'ENV', 'EXTENSIONS', - 'SKIPIF', 'XFAIL', 'XLEAK', 'CLEAN', - 'CREDITS', 'DESCRIPTION', 'CONFLICTS', 'WHITESPACE_SENSITIVE', - ])) { - $bork_info = 'Unknown section "' . $section . '"'; - } - - $section_text[$section] = ''; - $secfile = $section == 'FILE' || $section == 'FILEEOF' || $section == 'FILE_EXTERNAL'; - $secdone = false; - continue; - } - - // Add to the section text. - if (!$secdone) { - $section_text[$section] .= $line; - } - - // End of actual test? - if ($secfile && preg_match('/^===DONE===\s*$/', $line)) { - $secdone = true; - } - } - $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); $tested_file = $shortname; - $tested = trim($section_text['TEST']); - - // the redirect section allows a set of tests to be reused outside of - // a given test dir - if ($bork_info === null) { - if (isset($section_text['REDIRECTTEST'])) { - if ($IN_REDIRECT) { - $bork_info = "Can't redirect a test from within a redirected test"; - } - } else { - if (!isset($section_text['PHPDBG']) && isset($section_text['FILE']) + isset($section_text['FILEEOF']) + isset($section_text['FILE_EXTERNAL']) != 1) { - $bork_info = "missing section --FILE--"; - } - - if (isset($section_text['FILEEOF'])) { - $section_text['FILE'] = preg_replace("/[\r\n]+$/", '', $section_text['FILEEOF']); - unset($section_text['FILEEOF']); - } - - if ($num_repeats > 1 && isset($section_text['FILE_EXTERNAL'])) { - return skip_test($tested, $tested_file, $shortname, 'Test with FILE_EXTERNAL might not be repeatable'); - } - - foreach (['FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX'] as $prefix) { - $key = $prefix . '_EXTERNAL'; - - if (isset($section_text[$key])) { - // don't allow tests to retrieve files from anywhere but this subdirectory - $section_text[$key] = dirname($file) . '/' . trim(str_replace('..', '', $section_text[$key])); - if (file_exists($section_text[$key])) { - $section_text[$prefix] = file_get_contents($section_text[$key]); - unset($section_text[$key]); - } else { - $bork_info = "could not load --" . $key . "-- " . dirname($file) . '/' . trim($section_text[$key]); - } - } - } - - if ((isset($section_text['EXPECT']) + isset($section_text['EXPECTF']) + isset($section_text['EXPECTREGEX'])) != 1) { - $bork_info = "missing section --EXPECT--, --EXPECTF-- or --EXPECTREGEX--"; - } - } - } - fclose($fp); - - if ($bork_info !== null) { - show_result("BORK", $bork_info, $tested_file); + try { + $test = new TestFile($file, (bool)$IN_REDIRECT); + } catch (BorkageException $ex) { + show_result("BORK", $ex->getMessage(), $tested_file); $PHP_FAILED_TESTS['BORKED'][] = [ 'name' => $file, 'test_name' => '', 'output' => '', 'diff' => '', - 'info' => "$bork_info [$file]", + 'info' => "{$ex->getMessage()} [$file]", ]; - $junit->markTestAs('BORK', $shortname, $tested_file, 0, $bork_info); + $junit->markTestAs('BORK', $shortname, $tested_file, 0, $ex->getMessage()); return 'BORKED'; } - if (isset($section_text['CAPTURE_STDIO'])) { - $captureStdIn = stripos($section_text['CAPTURE_STDIO'], 'STDIN') !== false; - $captureStdOut = stripos($section_text['CAPTURE_STDIO'], 'STDOUT') !== false; - $captureStdErr = stripos($section_text['CAPTURE_STDIO'], 'STDERR') !== false; + $tested = $test->getName(); + + if ($num_repeats > 1 && $test->hasSection('FILE_EXTERNAL')) { + return skip_test($tested, $tested_file, $shortname, 'Test with FILE_EXTERNAL might not be repeatable'); + } + + if ($test->hasSection('CAPTURE_STDIO')) { + $capture = $test->getSection('CAPTURE_STDIO'); + $captureStdIn = stripos($capture, 'STDIN') !== false; + $captureStdOut = stripos($capture, 'STDOUT') !== false; + $captureStdErr = stripos($capture, 'STDERR') !== false; } else { $captureStdIn = true; $captureStdOut = true; @@ -2009,7 +1904,7 @@ TEST $file } /* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */ - if (array_key_exists('CGI', $section_text) || !empty($section_text['GET']) || !empty($section_text['POST']) || !empty($section_text['GZIP_POST']) || !empty($section_text['DEFLATE_POST']) || !empty($section_text['POST_RAW']) || !empty($section_text['PUT']) || !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) { + if ($test->isCGI()) { if (!$php_cgi) { return skip_test($tested, $tested_file, $shortname, 'CGI not available'); } @@ -2022,11 +1917,7 @@ TEST $file /* For phpdbg tests, check if phpdbg sapi is available and if it is, use it. */ $extra_options = ''; - if (array_key_exists('PHPDBG', $section_text)) { - if (!isset($section_text['STDIN'])) { - $section_text['STDIN'] = $section_text['PHPDBG'] . "\n"; - } - + if ($test->hasSection('PHPDBG')) { if (isset($phpdbg)) { $php = $phpdbg . ' -qIb'; @@ -2042,13 +1933,13 @@ TEST $file } if ($num_repeats > 1) { - if (array_key_exists('CLEAN', $section_text)) { + if ($test->hasSection('CLEAN')) { return skip_test($tested, $tested_file, $shortname, 'Test with CLEAN might not be repeatable'); } - if (array_key_exists('STDIN', $section_text)) { + if ($test->hasSection('STDIN')) { return skip_test($tested, $tested_file, $shortname, 'Test with STDIN might not be repeatable'); } - if (array_key_exists('CAPTURE_STDIO', $section_text)) { + if ($test->hasSection('CAPTURE_STDIO')) { return skip_test($tested, $tested_file, $shortname, 'Test with CAPTURE_STDIO might not be repeatable'); } } @@ -2095,8 +1986,8 @@ TEST $file mkdir(dirname($copy_file), 0777, true) or error("Cannot create output directory - " . dirname($copy_file)); } - if (isset($section_text['FILE'])) { - save_text($copy_file, $section_text['FILE']); + if ($test->hasSection('FILE')) { + save_text($copy_file, $test->getSection('FILE')); } $temp_filenames = [ @@ -2114,7 +2005,7 @@ TEST $file } if (is_array($IN_REDIRECT)) { - $tested = $IN_REDIRECT['prefix'] . ' ' . trim($section_text['TEST']); + $tested = $IN_REDIRECT['prefix'] . ' ' . $tested; $tested_file = $tmp_relative_file; $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $tested_file); } @@ -2145,8 +2036,8 @@ TEST $file $env['CONTENT_LENGTH'] = ''; $env['TZ'] = ''; - if (!empty($section_text['ENV'])) { - foreach (explode("\n", trim($section_text['ENV'])) as $e) { + if ($test->sectionNotEmpty('ENV')) { + foreach (explode("\n", $test->getSection('ENV')) as $e) { $e = explode('=', trim($e), 2); if (!empty($e[0]) && isset($e[1])) { @@ -2159,11 +2050,11 @@ TEST $file $ini_settings = $workerID ? ['opcache.cache_id' => "worker$workerID"] : []; // Additional required extensions - if (array_key_exists('EXTENSIONS', $section_text)) { + if ($test->hasSection('EXTENSIONS')) { $ext_params = []; settings2array($ini_overwrites, $ext_params); $ext_params = settings2params($ext_params); - $extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS'])); + $extensions = preg_split("/[\n\r]+/", trim($test->getSection('EXTENSIONS'))); [$ext_dir, $loaded] = $skipCache->getExtensions("$php $pass_options $extra_options $ext_params $no_file_cache"); $ext_prefix = IS_WINDOWS ? "php_" : ""; foreach ($extensions as $req_ext) { @@ -2201,12 +2092,12 @@ TEST $file // Any special ini settings // these may overwrite the test defaults... - if (array_key_exists('INI', $section_text)) { - $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']); - $section_text['INI'] = str_replace('{TMP}', sys_get_temp_dir(), $section_text['INI']); + if ($test->hasSection('INI')) { + $ini = str_replace('{PWD}', dirname($file), $test->getSection('INI')); + $ini = str_replace('{TMP}', sys_get_temp_dir(), $ini); $replacement = IS_WINDOWS ? '"' . PHP_BINARY . ' -r \"while ($in = fgets(STDIN)) echo $in;\" > $1"' : 'tee $1 >/dev/null'; - $section_text['INI'] = preg_replace('/{MAIL:(\S+)}/', $replacement, $section_text['INI']); - settings2array(preg_split("/[\n\r]+/", $section_text['INI']), $ini_settings); + $ini = preg_replace('/{MAIL:(\S+)}/', $replacement, $ini); + settings2array(preg_split("/[\n\r]+/", $ini), $ini_settings); if ($num_repeats > 1 && isset($ini_settings['opcache.opt_debug_level'])) { return skip_test($tested, $tested_file, $shortname, 'opt_debug_level tests are not repeatable'); @@ -2221,89 +2112,89 @@ TEST $file $info = ''; $warn = false; - if (array_key_exists('SKIPIF', $section_text)) { - if (trim($section_text['SKIPIF'])) { - show_file_block('skip', $section_text['SKIPIF']); - $extra = !IS_WINDOWS ? - "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; + if ($test->sectionNotEmpty('SKIPIF')) { + show_file_block('skip', $test->getSection('SKIPIF')); + $extra = !IS_WINDOWS ? + "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; - if ($valgrind) { - $env['USE_ZEND_ALLOC'] = '0'; - $env['ZEND_DONT_UNLOAD_MODULES'] = 1; - } + if ($valgrind) { + $env['USE_ZEND_ALLOC'] = '0'; + $env['ZEND_DONT_UNLOAD_MODULES'] = 1; + } - $junit->startTimer($shortname); + $junit->startTimer($shortname); - $startTime = microtime(true); - $commandLine = "$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache -d display_errors=1 -d display_startup_errors=0"; - $output = $skipCache->checkSkip($commandLine, $section_text['SKIPIF'], $test_skipif, $temp_skipif, $env); + $startTime = microtime(true); + $commandLine = "$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache -d display_errors=1 -d display_startup_errors=0"; + $output = $skipCache->checkSkip($commandLine, $test->getSection('SKIPIF'), $test_skipif, $temp_skipif, $env); - $time = microtime(true) - $startTime; + $time = microtime(true) - $startTime; + $junit->stopTimer($shortname); - $junit->stopTimer($shortname); + if ($time > $slow_min_ms / 1000) { + $PHP_FAILED_TESTS['SLOW'][] = [ + 'name' => $file, + 'test_name' => 'SKIPIF of ' . $tested . " [$tested_file]", + 'output' => '', + 'diff' => '', + 'info' => $time, + ]; + } - if ($time > $slow_min_ms / 1000) { - $PHP_FAILED_TESTS['SLOW'][] = [ - 'name' => $file, - 'test_name' => 'SKIPIF of ' . $tested . " [$tested_file]", - 'output' => '', - 'diff' => '', - 'info' => $time, - ]; + if (!$cfg['keep']['skip']) { + @unlink($test_skipif); + } + + if (!strncasecmp('skip', $output, 4)) { + if (preg_match('/^skip\s*(.+)/i', $output, $m)) { + show_result('SKIP', $tested, $tested_file, "reason: $m[1]", $temp_filenames); + } else { + show_result('SKIP', $tested, $tested_file, '', $temp_filenames); } - if (!strncasecmp('skip', $output, 4)) { - if (preg_match('/^skip\s*(.+)/i', $output, $m)) { - show_result('SKIP', $tested, $tested_file, "reason: $m[1]", $temp_filenames); - } else { - show_result('SKIP', $tested, $tested_file, '', $temp_filenames); - } + $message = !empty($m[1]) ? $m[1] : ''; + $junit->markTestAs('SKIP', $shortname, $tested, null, $message); + return 'SKIPPED'; + } - $message = !empty($m[1]) ? $m[1] : ''; - $junit->markTestAs('SKIP', $shortname, $tested, null, $message); - return 'SKIPPED'; - } - if (!strncasecmp('info', $output, 4) && preg_match('/^info\s*(.+)/i', $output, $m)) { - $info = " (info: $m[1])"; - } elseif (!strncasecmp('warn', $output, 4) && preg_match('/^warn\s+(.+)/i', $output, $m)) { - $warn = true; /* only if there is a reason */ - $info = " (warn: $m[1])"; - } elseif (!strncasecmp('xfail', $output, 5)) { - // Pretend we have an XFAIL section - $section_text['XFAIL'] = ltrim(substr($output, 5)); - } elseif ($output !== '') { - show_result("BORK", $output, $tested_file, 'reason: invalid output from SKIPIF', $temp_filenames); - $PHP_FAILED_TESTS['BORKED'][] = [ - 'name' => $file, - 'test_name' => '', - 'output' => '', - 'diff' => '', - 'info' => "$output [$file]", - ]; + if (!strncasecmp('info', $output, 4) && preg_match('/^info\s*(.+)/i', $output, $m)) { + $info = " (info: $m[1])"; + } elseif (!strncasecmp('warn', $output, 4) && preg_match('/^warn\s+(.+)/i', $output, $m)) { + $warn = true; /* only if there is a reason */ + $info = " (warn: $m[1])"; + } elseif (!strncasecmp('xfail', $output, 5)) { + // Pretend we have an XFAIL section + $test->setSection('XFAIL', ltrim(substr($output, 5))); + } elseif ($output !== '') { + show_result("BORK", $output, $tested_file, 'reason: invalid output from SKIPIF', $temp_filenames); + $PHP_FAILED_TESTS['BORKED'][] = [ + 'name' => $file, + 'test_name' => '', + 'output' => '', + 'diff' => '', + 'info' => "$output [$file]", + ]; - $junit->markTestAs('BORK', $shortname, $tested, null, $output); - return 'BORKED'; - } + $junit->markTestAs('BORK', $shortname, $tested, null, $output); + return 'BORKED'; } } - if (!extension_loaded("zlib") - && (array_key_exists("GZIP_POST", $section_text) - || array_key_exists("DEFLATE_POST", $section_text))) { + if (!extension_loaded("zlib") && $test->hasAnySections("GZIP_POST", "DEFLATE_POST")) { $message = "ext/zlib required"; show_result('SKIP', $tested, $tested_file, "reason: $message", $temp_filenames); $junit->markTestAs('SKIP', $shortname, $tested, null, $message); return 'SKIPPED'; } - if (isset($section_text['REDIRECTTEST'])) { + if ($test->hasSection('REDIRECTTEST')) { $test_files = []; - $IN_REDIRECT = eval($section_text['REDIRECTTEST']); + $IN_REDIRECT = eval($test->getSection('REDIRECTTEST')); $IN_REDIRECT['via'] = "via [$shortname]\n\t"; $IN_REDIRECT['dir'] = realpath(dirname($file)); - $IN_REDIRECT['prefix'] = trim($section_text['TEST']); + $IN_REDIRECT['prefix'] = $tested; if (!empty($IN_REDIRECT['TESTS'])) { if (is_array($org_file)) { @@ -2348,7 +2239,7 @@ TEST $file } } - if (is_array($org_file) || isset($section_text['REDIRECTTEST'])) { + if (is_array($org_file) || $test->hasSection('REDIRECTTEST')) { if (is_array($org_file)) { $file = $org_file[0]; } @@ -2369,15 +2260,15 @@ TEST $file } // We've satisfied the preconditions - run the test! - if (isset($section_text['FILE'])) { - show_file_block('php', $section_text['FILE'], 'TEST'); - save_text($test_file, $section_text['FILE'], $temp_file); + if ($test->hasSection('FILE')) { + show_file_block('php', $test->getSection('FILE'), 'TEST'); + save_text($test_file, $test->getSection('FILE'), $temp_file); } else { $test_file = $temp_file = ""; } - if (array_key_exists('GET', $section_text)) { - $query_string = trim($section_text['GET']); + if ($test->hasSection('GET')) { + $query_string = trim($test->getSection('GET')); } else { $query_string = ''; } @@ -2393,13 +2284,13 @@ TEST $file $env['SCRIPT_FILENAME'] = $test_file; } - if (array_key_exists('COOKIE', $section_text)) { - $env['HTTP_COOKIE'] = trim($section_text['COOKIE']); + if ($test->hasSection('COOKIE')) { + $env['HTTP_COOKIE'] = trim($test->getSection('COOKIE')); } else { $env['HTTP_COOKIE'] = ''; } - $args = isset($section_text['ARGS']) ? ' -- ' . $section_text['ARGS'] : ''; + $args = $test->hasSection('ARGS') ? ' -- ' . $test->getSection('ARGS') : ''; if ($preload && !empty($test_file)) { save_text($preload_filename, "sectionNotEmpty('POST_RAW')) { + $post = trim($test->getSection('POST_RAW')); $raw_lines = explode("\n", $post); $request = ''; @@ -2440,8 +2331,8 @@ TEST $file save_text($tmp_post, $request); $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; - } elseif (array_key_exists('PUT', $section_text) && !empty($section_text['PUT'])) { - $post = trim($section_text['PUT']); + } elseif ($test->sectionNotEmpty('PUT')) { + $post = trim($test->getSection('PUT')); $raw_lines = explode("\n", $post); $request = ''; @@ -2471,8 +2362,8 @@ TEST $file save_text($tmp_post, $request); $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; - } elseif (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { - $post = trim($section_text['POST']); + } elseif ($test->sectionNotEmpty('POST')) { + $post = trim($test->getSection('POST')); $content_length = strlen($post); save_text($tmp_post, $post); @@ -2486,8 +2377,8 @@ TEST $file } $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; - } elseif (array_key_exists('GZIP_POST', $section_text) && !empty($section_text['GZIP_POST'])) { - $post = trim($section_text['GZIP_POST']); + } elseif ($test->sectionNotEmpty('GZIP_POST')) { + $post = trim($test->getSection('GZIP_POST')); $post = gzencode($post, 9, FORCE_GZIP); $env['HTTP_CONTENT_ENCODING'] = 'gzip'; @@ -2499,8 +2390,8 @@ TEST $file $env['CONTENT_LENGTH'] = $content_length; $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; - } elseif (array_key_exists('DEFLATE_POST', $section_text) && !empty($section_text['DEFLATE_POST'])) { - $post = trim($section_text['DEFLATE_POST']); + } elseif ($test->sectionNotEmpty('DEFLATE_POST')) { + $post = trim($test->getSection('DEFLATE_POST')); $post = gzcompress($post, 9); $env['HTTP_CONTENT_ENCODING'] = 'deflate'; save_text($tmp_post, $post); @@ -2546,7 +2437,8 @@ COMMAND $cmd $hrtime = hrtime(); $startTime = $hrtime[0] * 1000000000 + $hrtime[1]; - $out = system_with_timeout($cmd, $env, $section_text['STDIN'] ?? null, $captureStdIn, $captureStdOut, $captureStdErr); + $stdin = $test->hasSection('STDIN') ? $test->getSection('STDIN') : null; + $out = system_with_timeout($cmd, $env, $stdin, $captureStdIn, $captureStdOut, $captureStdErr); $junit->stopTimer($shortname); $hrtime = hrtime(); @@ -2561,20 +2453,18 @@ COMMAND $cmd ]; } - if (array_key_exists('CLEAN', $section_text) && (!$no_clean || $cfg['keep']['clean'])) { - if (trim($section_text['CLEAN'])) { - show_file_block('clean', $section_text['CLEAN']); - save_text($test_clean, trim($section_text['CLEAN']), $temp_clean); + if ($test->sectionNotEmpty('CLEAN') && (!$no_clean || $cfg['keep']['clean'])) { + show_file_block('clean', $test->getSection('CLEAN')); + save_text($test_clean, trim($test->getSection('CLEAN')), $temp_clean); - if (!$no_clean) { - $extra = !IS_WINDOWS ? - "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; - system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache \"$test_clean\"", $env); - } + if (!$no_clean) { + $extra = !IS_WINDOWS ? + "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; + system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache \"$test_clean\"", $env); + } - if (!$cfg['keep']['clean']) { - @unlink($test_clean); - } + if (!$cfg['keep']['clean']) { + @unlink($test_clean); } } @@ -2630,10 +2520,10 @@ COMMAND $cmd $failed_headers = false; - if (isset($section_text['EXPECTHEADERS'])) { + if ($test->hasSection('EXPECTHEADERS')) { $want = []; $wanted_headers = []; - $lines = preg_split("/[\n\r]+/", $section_text['EXPECTHEADERS']); + $lines = preg_split("/[\n\r]+/", $test->getSection('EXPECTHEADERS')); foreach ($lines as $line) { if (strpos($line, ':') !== false) { @@ -2667,17 +2557,17 @@ COMMAND $cmd $output = trim(preg_replace("/\n?Warning: Can't preload [^\n]*\n?/", "", $output)); } - if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { - if (isset($section_text['EXPECTF'])) { - $wanted = trim($section_text['EXPECTF']); + if ($test->hasAnySections('EXPECTF', 'EXPECTREGEX')) { + if ($test->hasSection('EXPECTF')) { + $wanted = trim($test->getSection('EXPECTF')); } else { - $wanted = trim($section_text['EXPECTREGEX']); + $wanted = trim($test->getSection('EXPECTREGEX')); } show_file_block('exp', $wanted); $wanted_re = preg_replace('/\r\n/', "\n", $wanted); - if (isset($section_text['EXPECTF'])) { + if ($test->hasSection('EXPECTF')) { // do preg_quote, but miss out any %r delimited sections $temp = ""; $r = "%r"; @@ -2729,10 +2619,10 @@ COMMAND $cmd @unlink($tmp_post); if (!$leaked && !$failed_headers) { - if (isset($section_text['XFAIL'])) { + if ($test->hasSection('XFAIL')) { $warn = true; $info = " (warn: XFAIL section but test passes)"; - } elseif (isset($section_text['XLEAK'])) { + } elseif ($test->hasSection('XLEAK')) { $warn = true; $info = " (warn: XLEAK section but test passes)"; } else { @@ -2743,7 +2633,7 @@ COMMAND $cmd } } } else { - $wanted = trim($section_text['EXPECT']); + $wanted = trim($test->getSection('EXPECT')); $wanted = preg_replace('/\r\n/', "\n", $wanted); show_file_block('exp', $wanted); @@ -2757,10 +2647,10 @@ COMMAND $cmd @unlink($tmp_post); if (!$leaked && !$failed_headers) { - if (isset($section_text['XFAIL'])) { + if ($test->hasSection('XFAIL')) { $warn = true; $info = " (warn: XFAIL section but test passes)"; - } elseif (isset($section_text['XLEAK'])) { + } elseif ($test->hasSection('XLEAK')) { $warn = true; $info = " (warn: XLEAK section but test passes)"; } else { @@ -2786,7 +2676,7 @@ COMMAND $cmd } if ($leaked) { - $restype[] = isset($section_text['XLEAK']) ? + $restype[] = $test->hasSection('XLEAK') ? 'XLEAK' : 'LEAK'; } @@ -2795,12 +2685,12 @@ COMMAND $cmd } if (!$passed) { - if (isset($section_text['XFAIL'])) { + if ($test->hasSection('XFAIL')) { $restype[] = 'XFAIL'; - $info = ' XFAIL REASON: ' . rtrim($section_text['XFAIL']); - } elseif (isset($section_text['XLEAK'])) { + $info = ' XFAIL REASON: ' . rtrim($test->getSection('XFAIL')); + } elseif ($test->hasSection('XLEAK')) { $restype[] = 'XLEAK'; - $info = ' XLEAK REASON: ' . rtrim($section_text['XLEAK']); + $info = ' XLEAK REASON: ' . rtrim($test->getSection('XLEAK')); } else { $restype[] = 'FAIL'; } @@ -3431,6 +3321,10 @@ function show_result( } +class BorkageException extends Exception +{ +} + class JUnit { private bool $enabled = true; @@ -3843,6 +3737,206 @@ class RuntestsValgrind } } +class TestFile +{ + private string $fileName; + + private array $sections = ['TEST' => '']; + + private const ALLOWED_SECTIONS = [ + 'EXPECT', 'EXPECTF', 'EXPECTREGEX', 'EXPECTREGEX_EXTERNAL', 'EXPECT_EXTERNAL', 'EXPECTF_EXTERNAL', 'EXPECTHEADERS', + 'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS', + 'FILE', 'FILEEOF', 'FILE_EXTERNAL', 'REDIRECTTEST', + 'CAPTURE_STDIO', 'STDIN', 'CGI', 'PHPDBG', + 'INI', 'ENV', 'EXTENSIONS', + 'SKIPIF', 'XFAIL', 'XLEAK', 'CLEAN', + 'CREDITS', 'DESCRIPTION', 'CONFLICTS', 'WHITESPACE_SENSITIVE', + ]; + + public function __construct(string $fileName, bool $inRedirect) + { + $this->fileName = $fileName; + + $this->readFile(); + $this->validateAndProcess($inRedirect); + } + + public function hasSection(string $name): bool + { + return isset($this->sections[$name]); + } + + public function hasAllSections(string ...$names): bool + { + foreach ($names as $section) { + if (!isset($this->sections[$section])) { + return false; + } + } + + return true; + } + + public function hasAnySections(string ...$names): bool + { + foreach ($names as $section) { + if (isset($this->sections[$section])) { + return true; + } + } + + return false; + } + + public function sectionNotEmpty(string $name): bool + { + return !empty($this->sections[$name]); + } + + public function getSection(string $name): string + { + if (!isset($this->sections[$name])) { + throw new Exception("Section $name not found"); + } + return $this->sections[$name]; + } + + public function getName(): string + { + return trim($this->getSection('TEST')); + } + + public function isCGI(): bool + { + return $this->sectionNotEmpty('CGI') + || $this->sectionNotEmpty('GET') + || $this->sectionNotEmpty('POST') + || $this->sectionNotEmpty('GZIP_POST') + || $this->sectionNotEmpty('DEFLATE_POST') + || $this->sectionNotEmpty('POST_RAW') + || $this->sectionNotEmpty('PUT') + || $this->sectionNotEmpty('COOKIE') + || $this->sectionNotEmpty('EXPECTHEADERS'); + } + + /** + * TODO Refactor to make it not needed + */ + public function setSection(string $name, string $value): void + { + $this->sections[$name] = $value; + } + + /** + * Load the sections of the test file + */ + private function readFile(): void + { + $fp = fopen($this->fileName, "rb") or error("Cannot open test file: {$this->fileName}"); + + if (!feof($fp)) { + $line = fgets($fp); + + if ($line === false) { + throw new BorkageException("cannot read test"); + } + } else { + throw new BorkageException("empty test [{$this->fileName}]"); + } + if (strncmp('--TEST--', $line, 8)) { + throw new BorkageException("tests must start with --TEST-- [{$this->fileName}]"); + } + + $section = 'TEST'; + $secfile = false; + $secdone = false; + + while (!feof($fp)) { + $line = fgets($fp); + + if ($line === false) { + break; + } + + // Match the beginning of a section. + if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { + $section = (string) $r[1]; + + if (isset($this->sections[$section]) && $this->sections[$section]) { + throw new BorkageException("duplicated $section section"); + } + + // check for unknown sections + if (!in_array($section, self::ALLOWED_SECTIONS)) { + throw new BorkageException('Unknown section "' . $section . '"'); + } + + $this->sections[$section] = ''; + $secfile = $section == 'FILE' || $section == 'FILEEOF' || $section == 'FILE_EXTERNAL'; + $secdone = false; + continue; + } + + // Add to the section text. + if (!$secdone) { + $this->sections[$section] .= $line; + } + + // End of actual test? + if ($secfile && preg_match('/^===DONE===\s*$/', $line)) { + $secdone = true; + } + } + + fclose($fp); + } + + private function validateAndProcess(bool $inRedirect): void + { + // the redirect section allows a set of tests to be reused outside of + // a given test dir + if ($this->hasSection('REDIRECTTEST')) { + if ($inRedirect) { + throw new BorkageException("Can't redirect a test from within a redirected test"); + } + return; + } + if (!$this->hasSection('PHPDBG') && $this->hasSection('FILE') + $this->hasSection('FILEEOF') + $this->hasSection('FILE_EXTERNAL') != 1) { + throw new BorkageException("missing section --FILE--"); + } + + if ($this->hasSection('FILEEOF')) { + $this->sections['FILE'] = preg_replace("/[\r\n]+$/", '', $this->sections['FILEEOF']); + unset($this->sections['FILEEOF']); + } + + foreach (['FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX'] as $prefix) { + // For grepping: FILE_EXTERNAL, EXPECT_EXTERNAL, EXPECTF_EXTERNAL, EXPECTREGEX_EXTERNAL + $key = $prefix . '_EXTERNAL'; + + if ($this->hasSection($key)) { + // don't allow tests to retrieve files from anywhere but this subdirectory + $dir = dirname($this->fileName); + $fileName = $dir . '/' . trim(str_replace('..', '', $this->getSection($key))); + + if (file_exists($fileName)) { + $this->sections[$prefix] = file_get_contents($fileName); + } else { + throw new BorkageException("could not load --" . $key . "-- " . $dir . '/' . trim($fileName)); + } + } + } + + if (($this->hasSection('EXPECT') + $this->hasSection('EXPECTF') + $this->hasSection('EXPECTREGEX')) != 1) { + throw new BorkageException("missing section --EXPECT--, --EXPECTF-- or --EXPECTREGEX--"); + } + + if ($this->hasSection('PHPDBG') && !$this->hasSection('STDIN')) { + $this->sections['STDIN'] = $this->sections['PHPDBG'] . "\n"; + } + } +} + function init_output_buffers(): void { // Delete as much output buffers as possible. -- cgit v1.2.1