summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2020-10-21 15:01:47 +0200
committerNikita Popov <nikita.ppv@gmail.com>2020-10-30 17:29:33 +0100
commit1b3b430f478c785c88655533518a66b88ece060a (patch)
treeda76d216cbf9cabf9754002705058dfa4377f9ca
parentada2a55e07bb413c71d371f77a91e9454a7d564a (diff)
downloadphp-git-1b3b430f478c785c88655533518a66b88ece060a.tar.gz
Add --repeat testing mode
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.
-rw-r--r--azure-pipelines.yml5
-rw-r--r--ext/opcache/tests/bug65915.phpt6
-rw-r--r--ext/opcache/tests/zzz_basic_logging.phpt6
-rw-r--r--ext/pcntl/tests/002.phpt1
-rw-r--r--ext/pcntl/tests/pcntl_unshare_01.phpt1
-rw-r--r--ext/pcntl/tests/pcntl_unshare_02.phpt1
-rw-r--r--ext/pcntl/tests/pcntl_unshare_03.phpt1
-rw-r--r--ext/pgsql/tests/skipif.inc5
-rw-r--r--ext/readline/tests/libedit_info_001.phpt1
-rw-r--r--ext/readline/tests/libedit_write_history_001.phpt1
-rw-r--r--ext/readline/tests/readline_info_001.phpt1
-rw-r--r--ext/readline/tests/readline_read_history_001.phpt5
-rw-r--r--ext/readline/tests/readline_write_history_001.phpt1
-rw-r--r--ext/standard/tests/file/realpath_bug77484.phpt4
-rw-r--r--ext/standard/tests/general_functions/http_response_code.phpt9
-rw-r--r--ext/standard/tests/general_functions/proc_nice_basic.phpt2
-rwxr-xr-xrun-tests.php85
-rw-r--r--sapi/cli/php_cli.c38
18 files changed, 144 insertions, 29 deletions
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index dabf35b3d2..1932b1cdee 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -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'
diff --git a/ext/opcache/tests/bug65915.phpt b/ext/opcache/tests/bug65915.phpt
index c616c4fb5f..b62dbd8ea7 100644
--- a/ext/opcache/tests/bug65915.phpt
+++ b/ext/opcache/tests/bug65915.phpt
@@ -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";
diff --git a/ext/opcache/tests/zzz_basic_logging.phpt b/ext/opcache/tests/zzz_basic_logging.phpt
index bf04b50861..0fbf308a51 100644
--- a/ext/opcache/tests/zzz_basic_logging.phpt
+++ b/ext/opcache/tests/zzz_basic_logging.phpt
@@ -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";
diff --git a/ext/pcntl/tests/002.phpt b/ext/pcntl/tests/002.phpt
index 68b2643092..254da86136 100644
--- a/ext/pcntl/tests/002.phpt
+++ b/ext/pcntl/tests/002.phpt
@@ -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
diff --git a/ext/pcntl/tests/pcntl_unshare_01.phpt b/ext/pcntl/tests/pcntl_unshare_01.phpt
index fcbf112e4d..6eb5a20bdd 100644
--- a/ext/pcntl/tests/pcntl_unshare_01.phpt
+++ b/ext/pcntl/tests/pcntl_unshare_01.phpt
@@ -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
diff --git a/ext/pcntl/tests/pcntl_unshare_02.phpt b/ext/pcntl/tests/pcntl_unshare_02.phpt
index fdf07bc81c..3e90a3920c 100644
--- a/ext/pcntl/tests/pcntl_unshare_02.phpt
+++ b/ext/pcntl/tests/pcntl_unshare_02.phpt
@@ -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
diff --git a/ext/pcntl/tests/pcntl_unshare_03.phpt b/ext/pcntl/tests/pcntl_unshare_03.phpt
index f82f27b719..8e9bd793e6 100644
--- a/ext/pcntl/tests/pcntl_unshare_03.phpt
+++ b/ext/pcntl/tests/pcntl_unshare_03.phpt
@@ -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
diff --git a/ext/pgsql/tests/skipif.inc b/ext/pgsql/tests/skipif.inc
index 38c2888afa..92712f3e05 100644
--- a/ext/pgsql/tests/skipif.inc
+++ b/ext/pgsql/tests/skipif.inc
@@ -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");
diff --git a/ext/readline/tests/libedit_info_001.phpt b/ext/readline/tests/libedit_info_001.phpt
index 46618120fb..8b6fb80671 100644
--- a/ext/readline/tests/libedit_info_001.phpt
+++ b/ext/readline/tests/libedit_info_001.phpt
@@ -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
diff --git a/ext/readline/tests/libedit_write_history_001.phpt b/ext/readline/tests/libedit_write_history_001.phpt
index 96424e232f..69739c488e 100644
--- a/ext/readline/tests/libedit_write_history_001.phpt
+++ b/ext/readline/tests/libedit_write_history_001.phpt
@@ -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
diff --git a/ext/readline/tests/readline_info_001.phpt b/ext/readline/tests/readline_info_001.phpt
index 8f5d641d94..f0d4e67ca5 100644
--- a/ext/readline/tests/readline_info_001.phpt
+++ b/ext/readline/tests/readline_info_001.phpt
@@ -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
diff --git a/ext/readline/tests/readline_read_history_001.phpt b/ext/readline/tests/readline_read_history_001.phpt
index 6cabaf369b..8fbcaf1a27 100644
--- a/ext/readline/tests/readline_read_history_001.phpt
+++ b/ext/readline/tests/readline_read_history_001.phpt
@@ -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
diff --git a/ext/readline/tests/readline_write_history_001.phpt b/ext/readline/tests/readline_write_history_001.phpt
index 95c34e3e89..bcbeb5efa4 100644
--- a/ext/readline/tests/readline_write_history_001.phpt
+++ b/ext/readline/tests/readline_write_history_001.phpt
@@ -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
diff --git a/ext/standard/tests/file/realpath_bug77484.phpt b/ext/standard/tests/file/realpath_bug77484.phpt
index 85595d09e3..9aa3a335c2 100644
--- a/ext/standard/tests/file/realpath_bug77484.phpt
+++ b/ext/standard/tests/file/realpath_bug77484.phpt
@@ -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
diff --git a/ext/standard/tests/general_functions/http_response_code.phpt b/ext/standard/tests/general_functions/http_response_code.phpt
index bc2775f3d3..ab290c3cef 100644
--- a/ext/standard/tests/general_functions/http_response_code.phpt
+++ b/ext/standard/tests/general_functions/http_response_code.phpt
@@ -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(
diff --git a/ext/standard/tests/general_functions/proc_nice_basic.phpt b/ext/standard/tests/general_functions/proc_nice_basic.phpt
index b8cc3cd128..17bca0f7b7 100644
--- a/ext/standard/tests/general_functions/proc_nice_basic.phpt
+++ b/ext/standard/tests/general_functions/proc_nice_basic.phpt
@@ -25,7 +25,7 @@ if ($exit_code !== 0) {
else
return -1;
}
- $delta = 10;
+ $delta = 5;
$pid = getmypid();
$niceBefore = getNice($pid);
proc_nice($delta);
diff --git a/run-tests.php b/run-tests.php
index 00881cf779..4beafa90b2 100755
--- a/run-tests.php
+++ b/run-tests.php
@@ -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));
diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c
index d28f5a5378..5092fb0ffd 100644
--- a/sapi/cli/php_cli.c
+++ b/sapi/cli/php_cli.c
@@ -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();