]> granicus.if.org Git - php/commitdiff
Add --repeat testing mode
authorNikita Popov <nikita.ppv@gmail.com>
Wed, 21 Oct 2020 13:01:47 +0000 (15:01 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Fri, 30 Oct 2020 16:29:33 +0000 (17:29 +0100)
This testing mode executes the test multiple times in the same
process (but in different requests). It is primarily intended to
catch tracing JIT bugs, but also catches state leaks across
requests.

Closes GH-6365.

18 files changed:
azure-pipelines.yml
ext/opcache/tests/bug65915.phpt
ext/opcache/tests/zzz_basic_logging.phpt
ext/pcntl/tests/002.phpt
ext/pcntl/tests/pcntl_unshare_01.phpt
ext/pcntl/tests/pcntl_unshare_02.phpt
ext/pcntl/tests/pcntl_unshare_03.phpt
ext/pgsql/tests/skipif.inc
ext/readline/tests/libedit_info_001.phpt
ext/readline/tests/libedit_write_history_001.phpt
ext/readline/tests/readline_info_001.phpt
ext/readline/tests/readline_read_history_001.phpt
ext/readline/tests/readline_write_history_001.phpt
ext/standard/tests/file/realpath_bug77484.phpt
ext/standard/tests/general_functions/http_response_code.phpt
ext/standard/tests/general_functions/proc_nice_basic.phpt
run-tests.php
sapi/cli/php_cli.c

index dabf35b3d20cb6bf8a5ac61f8c754bbfdc3fe07a..1932b1cdeeebbc069a4bf5cb41f7a5dbb43950ba 100644 (file)
@@ -103,3 +103,8 @@ jobs:
         configurationName: DEBUG_NTS_FILE_CACHE
         configurationParameters: '--enable-debug --disable-zts'
         timeoutInMinutes: 90
+    - template: azure/job.yml
+      parameters:
+        configurationName: DEBUG_NTS_REPEAT
+        configurationParameters: '--enable-debug --disable-zts'
+        runTestsParameters: '--repeat 2'
index c616c4fb5f622793f1e60a678b5a0fb55d5ea6be..b62dbd8ea74fb6055d806e4402b05badcd9720a7 100644 (file)
@@ -5,7 +5,11 @@ opcache.enable=1
 opcache.enable_cli=1
 opcache.file_cache_only=0
 --SKIPIF--
-<?php require_once('skipif.inc'); ?>
+<?php
+require_once('skipif.inc');
+// We don't invalidate the file after the second write.
+if (getenv('SKIP_REPEAT')) die("skip Not repeatable");
+?>
 --FILE--
 <?php
 $tmp = __DIR__ . "/bug65915.inc.php";
index bf04b50861a557307f23b16cd7ad45836e574c64..0fbf308a51a775929dccce2c6a8c9a5097faff01 100644 (file)
@@ -12,7 +12,11 @@ opcache.log_verbosity_level=4
 opcache.huge_code_pages=0
 opcache.preload=
 --SKIPIF--
-<?php require_once('skipif.inc'); ?>
+<?php
+require_once('skipif.inc');
+// Prints "Debug Restarting!" message on next request.
+if (getenv('SKIP_REPEAT')) die("skip Not repeatable");
+?>
 --FILE--
 <?php
 echo "Foo Bar\n";
index 68b264309268ae9e3c088fdbb8de17caaca13411..254da8613606dd1788be81459332b90fabab9a75 100644 (file)
@@ -7,6 +7,7 @@ pcntl: pcntl_sigprocmask(), pcntl_sigwaitinfo(), pcntl_sigtimedwait()
     elseif (!function_exists('pcntl_sigwaitinfo') or !function_exists('pcntl_sigtimedwait')) die('skip required functionality is not available');
     elseif (!defined('CLD_EXITED')) die('skip CLD_EXITED not defined');
     elseif (getenv('SKIP_ASAN')) die('skip Fails intermittently under asan/msan');
+    elseif (getenv("SKIP_REPEAT")) die("skip cannot be repeated");
 ?>
 --FILE--
 <?php
index fcbf112e4d6f4457eff4831504e2102a030dbf00..6eb5a20bdddba24224945ea0ce1f8580fb3362b8 100644 (file)
@@ -9,6 +9,7 @@ if (!defined("CLONE_NEWUSER")) die("skip flag unavailable");
 if (@pcntl_unshare(CLONE_NEWUSER) == false && pcntl_get_last_error() == PCNTL_EPERM) {
     die("skip Insufficient privileges to use CLONE_NEWUSER");
 }
+if (getenv("SKIP_REPEAT")) die("skip cannot be repeated");
 ?>
 --FILE--
 <?php
index fdf07bc81cc435ef7f3a93153d5c63531e773841..3e90a3920c9d3d0b0731041d1c76b16acd470092 100644 (file)
@@ -15,6 +15,7 @@ if (posix_getuid() !== 0 &&
 if (@pcntl_unshare(CLONE_NEWPID) == false && pcntl_get_last_error() == PCNTL_EPERM) {
     die("skip Insufficient privileges for CLONE_NEWPID");
 }
+if (getenv("SKIP_REPEAT")) die("skip cannot be repeated");
 ?>
 --FILE--
 <?php
index f82f27b71936fe18ec8c786a22c6bea7c05835c0..8e9bd793e6089480ca5fd037555cd369920924aa 100644 (file)
@@ -15,6 +15,7 @@ if (@pcntl_unshare(CLONE_NEWNET) == false && pcntl_get_last_error() == PCNTL_EPE
     die("skip Insufficient privileges for CLONE_NEWPID");
 }
 if (getenv("SKIP_ONLINE_TESTS")) die("skip online test");
+if (getenv("SKIP_REPEAT")) die("skip cannot be repeated");
 ?>
 --FILE--
 <?php
index 38c2888afa5fb32c1a4b9f11aa75568cad587961..92712f3e05ea752f9f78e9f2622fe3441fd6acdf 100644 (file)
@@ -12,6 +12,11 @@ include("lcmess.inc");
 if (!extension_loaded("pgsql")) {
     die("skip pgsql extension not loaded\n");
 }
+if (getenv("SKIP_REPEAT")) {
+    // pgsql tests are order-dependent.
+    // We should probably change that, but in the meantime do not allow repetition.
+    die("skip Cannot repeat pgsql tests");
+}
 $conn = @pg_connect($conn_str);
 if (!is_resource($conn)) {
     die("skip could not connect\n");
index 46618120fba8a21f8d19cbd51f5d470fc13e288d..8b6fb80671914f562bb8bf07ddfd022e3d55a3b6 100644 (file)
@@ -6,6 +6,7 @@ if (READLINE_LIB != "libedit") die("skip libedit only");
 if(substr(PHP_OS, 0, 3) == 'WIN' ) {
     die('skip not for windows');
 }
+if (getenv('SKIP_REPEAT')) die("skip readline has global state");
 ?>
 --FILE--
 <?php
index 96424e232f6b932db03ca44f9a19a1c9f55ab8cb..69739c488e7bab3679965812c06f5db3bbe2780c 100644 (file)
@@ -6,6 +6,7 @@ if (READLINE_LIB != "libedit") die("skip libedit only");
 if(substr(PHP_OS, 0, 3) == 'WIN' ) {
     die('skip not for windows');
 }
+if (getenv('SKIP_REPEAT')) die("skip readline has global state");
 ?>
 --FILE--
 <?php
index 8f5d641d94bd4eed836aaef2302db0f2fc33a6e2..f0d4e67ca571cf2092c39091c8f652bf33d92e0a 100644 (file)
@@ -3,6 +3,7 @@ readline_info(): Basic test
 --SKIPIF--
 <?php if (!extension_loaded("readline")) die("skip");
 if (READLINE_LIB == "libedit") die("skip readline only");
+if (getenv('SKIP_REPEAT')) die("skip readline has global state");
 ?>
 --FILE--
 <?php
index 6cabaf369b727a379be32cb13ee780bbf36726b5..8fbcaf1a27722f044891f30ebb953f7ab8f8028b 100644 (file)
@@ -1,7 +1,10 @@
 --TEST--
 readline_read_history(): Basic test
 --SKIPIF--
-<?php if (!extension_loaded("readline") || !function_exists('readline_list_history')) die("skip"); ?>
+<?php
+if (!extension_loaded("readline") || !function_exists('readline_list_history')) die("skip");
+if (getenv('SKIP_REPEAT')) die("skip readline has global state");
+?>
 --FILE--
 <?php
 
index 95c34e3e89d978258c8286e50b9383ba490a4a01..bcbeb5efa45a3879c30d88057f48d4562492a0d0 100644 (file)
@@ -3,6 +3,7 @@ readline_write_history(): Basic test
 --SKIPIF--
 <?php if (!extension_loaded("readline") || !function_exists('readline_add_history')) die("skip");
 if (READLINE_LIB == "libedit") die("skip readline only");
+if (getenv('SKIP_REPEAT')) die("skip readline has global state");
 ?>
 --FILE--
 <?php
index 85595d09e3a55f22c9feeb709c7a06162a5e9139..9aa3a335c2a962b68d5f6800b007b3f6dfe23c4f 100644 (file)
@@ -9,6 +9,10 @@ if (PHP_ZTS) {
     /* TODO eliminate difference in TS build. */
     die("skip Not for ZTS");
 }
+if (getenv('SKIP_REPEAT')) {
+    /* The cwd is persistent across repeats */
+    die("skip Not repeatable");
+}
 ?>
 --FILE--
 <?php
index bc2775f3d331a4856fe44af7184d869dde931f24..ab290c3cefe1970136697504f8c19f60b60b6f3c 100644 (file)
@@ -2,6 +2,15 @@
 Test http_response_code basic functionality
 --CREDITS--
 Gabriel Caruso (carusogabriel@php.net)
+--SKIPIF--
+<?php
+if (getenv('SKIP_REPEAT')) {
+    /* The http_response_code leaks across repeats. Technically that's a bug, but it's something
+     * that only affects the CLI repeat option, where the response code is not meaningful in the
+     * first place. Other SAPIs will properly reset the response code for each request. */
+    die('skip Not repeatable');
+}
+?>
 --FILE--
 <?php
 var_dump(
index b8cc3cd128edd761248de4c4cdfc99efe6f12013..17bca0f7b75fe7db4f288cdc7722537dc02e93f7 100644 (file)
@@ -25,7 +25,7 @@ if ($exit_code !== 0) {
         else
             return -1;
     }
-    $delta = 10;
+    $delta = 5;
     $pid = getmypid();
     $niceBefore = getNice($pid);
     proc_nice($delta);
index 00881cf7797bfefafe17cd0aa5e0c244a73b2fa7..4beafa90b287ed26d8369501c859c05c618a817d 100755 (executable)
@@ -147,7 +147,7 @@ function main(): void
            $repeat, $result_tests_file, $slow_min_ms, $start_time, $switch,
            $temp_source, $temp_target, $test_cnt, $test_dirs,
            $test_files, $test_idx, $test_list, $test_results, $testfile,
-           $user_tests, $valgrind, $sum_results, $shuffle, $file_cache;
+           $user_tests, $valgrind, $sum_results, $shuffle, $file_cache, $num_repeats;
     // Parallel testing
     global $workers, $workerID;
     global $context_line_count;
@@ -405,6 +405,7 @@ function main(): void
     $shuffle = false;
     $workers = null;
     $context_line_count = 3;
+    $num_repeats = 1;
 
     $cfgtypes = ['show', 'keep'];
     $cfgfiles = ['skip', 'php', 'clean', 'out', 'diff', 'exp', 'mem'];
@@ -623,6 +624,10 @@ function main(): void
                             . ':print_suppressions=0';
                     }
                     break;
+                case '--repeat':
+                    $num_repeats = (int) $argv[++$i];
+                    $environment['SKIP_REPEAT'] = 1;
+                    break;
                 //case 'w'
                 case '-':
                     // repeat check with full switch
@@ -1802,6 +1807,13 @@ function show_file_block(string $file, string $block, ?string $section = null):
     }
 }
 
+function skip_test(string $tested, string $tested_file, string $shortname, string $reason) {
+    show_result('SKIP', $tested, $tested_file, "reason: $reason");
+    junit_init_suite(junit_get_suitename_for($shortname));
+    junit_mark_test_as('SKIP', $shortname, $tested, 0, $reason);
+    return 'SKIPPED';
+}
+
 //
 //  Run an individual test case.
 //
@@ -1818,6 +1830,7 @@ function run_test(string $php, $file, array $env): string
     global $no_file_cache;
     global $slow_min_ms;
     global $preload, $file_cache;
+    global $num_repeats;
     // Parallel testing
     global $workerID;
     $temp_filenames = null;
@@ -1911,6 +1924,10 @@ TEST $file
         }
     }
 
+    $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) {
@@ -1928,6 +1945,10 @@ TEST $file
                 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';
 
@@ -1951,9 +1972,6 @@ TEST $file
     }
     fclose($fp);
 
-    $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file);
-    $tested_file = $shortname;
-
     if ($bork_info !== null) {
         show_result("BORK", $bork_info, $tested_file);
         $PHP_FAILED_TESTS['BORKED'][] = [
@@ -1983,8 +2001,6 @@ TEST $file
         $cmdRedirect = '';
     }
 
-    $tested = trim($section_text['TEST']);
-
     /* 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 (isset($php_cgi)) {
@@ -1999,13 +2015,12 @@ TEST $file
             } elseif (file_exists(dirname($php) . "/php-cgi")) {
                 $php = realpath(dirname($php) . "/php-cgi") . ' -C ';
             } else {
-                show_result('SKIP', $tested, $tested_file, "reason: CGI not available");
-
-                junit_init_suite(junit_get_suitename_for($shortname));
-                junit_mark_test_as('SKIP', $shortname, $tested, 0, 'CGI not available');
-                return 'SKIPPED';
+                return skip_test($tested, $tested_file, $shortname, 'CGI not available');
             }
         }
+        if ($num_repeats > 1) {
+            return skip_test($tested, $tested_file, $shortname, 'CGI does not support --repeat');
+        }
         $uses_cgi = true;
     }
 
@@ -2023,11 +2038,22 @@ TEST $file
             // be run straight away. For example, EXTENSIONS, SKIPIF, CLEAN.
             $extra_options = '-rr';
         } else {
-            show_result('SKIP', $tested, $tested_file, "reason: phpdbg not available");
+            return skip_test($tested, $tested_file, $shortname, 'phpdbg not available');
+        }
+        if ($num_repeats > 1) {
+            return skip_test($tested, $tested_file, $shortname, 'phpdbg does not support --repeat');
+        }
+    }
 
-            junit_init_suite(junit_get_suitename_for($shortname));
-            junit_mark_test_as('SKIP', $shortname, $tested, 0, 'phpdbg not available');
-            return 'SKIPPED';
+    if ($num_repeats > 1) {
+        if (array_key_exists('CLEAN', $section_text)) {
+            return skip_test($tested, $tested_file, $shortname, 'Test with CLEAN might not be repeatable');
+        }
+        if (array_key_exists('STDIN', $section_text)) {
+            return skip_test($tested, $tested_file, $shortname, 'Test with STDIN might not be repeatable');
+        }
+        if (array_key_exists('CAPTURE_STDIO', $section_text)) {
+            return skip_test($tested, $tested_file, $shortname, 'Test with CAPTURE_STDIO might not be repeatable');
         }
     }
 
@@ -2173,6 +2199,9 @@ TEST $file
             // even though all the files are re-created.
             $ini_settings['opcache.validate_timestamps'] = '0';
         }
+    } else if ($num_repeats > 1) {
+        // Make sure warnings still show up on the second run.
+        $ini_settings['opcache.record_warnings'] = '1';
     }
 
     // Any special ini settings
@@ -2183,6 +2212,10 @@ TEST $file
         $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);
+
+        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');
+        }
     }
 
     $ini_settings = settings2params($ini_settings);
@@ -2484,7 +2517,8 @@ TEST $file
         $env['CONTENT_TYPE'] = '';
         $env['CONTENT_LENGTH'] = '';
 
-        $cmd = "$php $pass_options $ini_settings -f \"$test_file\" $args$cmdRedirect";
+        $repeat_option = $num_repeats > 1 ? "--repeat $num_repeats" : "";
+        $cmd = "$php $pass_options $repeat_option $ini_settings -f \"$test_file\" $args$cmdRedirect";
     }
 
     if ($valgrind) {
@@ -2557,6 +2591,25 @@ COMMAND $cmd
         }
     }
 
+    if ($num_repeats > 1) {
+        // In repeat mode, retain the output before the first execution,
+        // and of the last execution. Do this early, because the trimming below
+        // makes the newline handling complicated.
+        $separator1 = "Executing for the first time...\n";
+        $separator1_pos = strpos($out, $separator1);
+        if ($separator1_pos !== false) {
+            $separator2 = "Finished execution, repeating...\n";
+            $separator2_pos = strrpos($out, $separator2);
+            if ($separator2_pos !== false) {
+                $out = substr($out, 0, $separator1_pos)
+                     . substr($out, $separator2_pos + strlen($separator2));
+            } else {
+                $out = substr($out, 0, $separator1_pos)
+                     . substr($out, $separator1_pos + strlen($separator1));
+            }
+        }
+    }
+
     // Does the output match what is expected?
     $output = preg_replace("/\r\n/", "\n", trim($out));
 
index d28f5a5378b6e1e4dcb21d45b088536388555a0a..5092fb0ffd68143f9d54dc20be3ba7d59b8e40d1 100644 (file)
@@ -172,6 +172,9 @@ const opt_struct OPTIONS[] = {
        {14,  1, "ri"},
        {14,  1, "rextinfo"},
        {15,  0, "ini"},
+       /* Internal testing option -- may be changed or removed without notice,
+        * including in patch releases. */
+       {16,  1, "repeat"},
        {'-', 0, NULL} /* end of args */
 };
 
@@ -529,7 +532,7 @@ static void php_cli_usage(char *argv0)
 
 static php_stream *s_in_process = NULL;
 
-static void cli_register_file_handles(void) /* {{{ */
+static void cli_register_file_handles(zend_bool no_close) /* {{{ */
 {
        php_stream *s_in, *s_out, *s_err;
        php_stream_context *sc_in=NULL, *sc_out=NULL, *sc_err=NULL;
@@ -546,11 +549,11 @@ static void cli_register_file_handles(void) /* {{{ */
                return;
        }
 
-#if PHP_DEBUG
-       /* do not close stdout and stderr */
-       s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE;
-       s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE;
-#endif
+       if (no_close) {
+               s_in->flags |= PHP_STREAM_FLAG_NO_CLOSE;
+               s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE;
+               s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE;
+       }
 
        s_in_process = s_in;
 
@@ -614,6 +617,8 @@ static int do_cli(int argc, char **argv) /* {{{ */
        int interactive=0;
        const char *param_error=NULL;
        int hide_argv = 0;
+       int num_repeats = 1;
+       pid_t pid = getpid();
 
        zend_try {
 
@@ -839,6 +844,9 @@ static int do_cli(int argc, char **argv) /* {{{ */
                        case 15:
                                behavior = PHP_MODE_SHOW_INI_CONFIG;
                                break;
+                       case 16:
+                               num_repeats = atoi(php_optarg);
+                               break;
                        default:
                                break;
                        }
@@ -873,6 +881,12 @@ static int do_cli(int argc, char **argv) /* {{{ */
                        fflush(stdout);
                }
 
+               if (num_repeats > 1) {
+                       fprintf(stdout, "Executing for the first time...\n");
+                       fflush(stdout);
+               }
+
+do_repeat:
                /* only set script_file if not set already and not in direct mode and not at end of parameter list */
                if (argc > php_optind
                  && !script_file
@@ -940,7 +954,7 @@ static int do_cli(int argc, char **argv) /* {{{ */
                switch (behavior) {
                case PHP_MODE_STANDARD:
                        if (strcmp(file_handle.filename, "Standard input code")) {
-                               cli_register_file_handles();
+                               cli_register_file_handles(/* no_close */ PHP_DEBUG || num_repeats > 1);
                        }
 
                        if (interactive && cli_shell_callbacks.cli_shell_run) {
@@ -975,7 +989,7 @@ static int do_cli(int argc, char **argv) /* {{{ */
                        }
                        break;
                case PHP_MODE_CLI_DIRECT:
-                       cli_register_file_handles();
+                       cli_register_file_handles(/* no_close */ PHP_DEBUG || num_repeats > 1);
                        zend_eval_string_ex(exec_direct, NULL, "Command line code", 1);
                        break;
 
@@ -985,7 +999,7 @@ static int do_cli(int argc, char **argv) /* {{{ */
                                size_t len, index = 0;
                                zval argn, argi;
 
-                               cli_register_file_handles();
+                               cli_register_file_handles(/* no_close */ PHP_DEBUG || num_repeats > 1);
 
                                if (exec_begin) {
                                        zend_eval_string_ex(exec_begin, NULL, "Command line begin code", 1);
@@ -1112,6 +1126,12 @@ out:
        if (translated_path) {
                free(translated_path);
        }
+       /* Don't repeat fork()ed processes. */
+       if (--num_repeats && pid == getpid()) {
+               fprintf(stdout, "Finished execution, repeating...\n");
+               fflush(stdout);
+               goto do_repeat;
+       }
        return EG(exit_status);
 err:
        sapi_deactivate();