@@ -148,7 +148,7 @@ function main(): void
148148 $ exts_skipped , $ exts_tested , $ exts_to_test , $ failed_tests_file ,
149149 $ ignored_by_ext , $ ini_overwrites , $ colorize ,
150150 $ log_format , $ no_clean , $ no_file_cache ,
151- $ pass_options , $ php , $ php_cgi , $ preload ,
151+ $ pass_options , $ pass_options_args , $ php , $ php_cgi , $ preload ,
152152 $ result_tests_file , $ slow_min_ms , $ start_time ,
153153 $ temp_source , $ temp_target , $ test_cnt ,
154154 $ test_files , $ test_idx , $ test_results , $ testfile ,
@@ -328,6 +328,7 @@ function main(): void
328328 $ failed_tests_file = false ;
329329 $ pass_option_n = false ;
330330 $ pass_options = '' ;
331+ $ pass_options_args = [];
331332
332333 $ output_file = INIT_DIR . '/php_test_results_ ' . date ('Ymd_Hi ' ) . '.txt ' ;
333334
@@ -472,11 +473,13 @@ function main(): void
472473 case 'n ' :
473474 if (!$ pass_option_n ) {
474475 $ pass_options .= ' -n ' ;
476+ $ pass_options_args [] = '-n ' ;
475477 }
476478 $ pass_option_n = true ;
477479 break ;
478480 case 'e ' :
479481 $ pass_options .= ' -e ' ;
482+ $ pass_options_args [] = '-e ' ;
480483 break ;
481484 case '--preload ' :
482485 $ preload = true ;
@@ -682,8 +685,13 @@ function main(): void
682685 if ($ conf_passed !== null ) {
683686 if (IS_WINDOWS ) {
684687 $ pass_options .= " -c " . escapeshellarg ($ conf_passed );
688+ $ pass_options_args [] = '-c ' ;
689+ $ pass_options_args [] = $ conf_passed ;
685690 } else {
686- $ pass_options .= " -c ' " . realpath ($ conf_passed ) . "' " ;
691+ $ configurationFile = realpath ($ conf_passed );
692+ $ pass_options .= " -c ' " . $ configurationFile . "' " ;
693+ $ pass_options_args [] = '-c ' ;
694+ $ pass_options_args [] = (string ) $ configurationFile ;
687695 }
688696 }
689697
@@ -1173,19 +1181,20 @@ function error_report(string $testname, string $logname, string $tested): void
11731181 * @return false|string
11741182 */
11751183function system_with_timeout (
1176- string $ commandline ,
1184+ string | array $ commandline ,
11771185 ?array $ env = null ,
11781186 ?string $ stdin = null ,
11791187 bool $ captureStdIn = true ,
11801188 bool $ captureStdOut = true ,
1181- bool $ captureStdErr = true
1189+ bool $ captureStdErr = true ,
1190+ bool $ mergeStdErr = false
11821191) {
11831192 global $ valgrind ;
11841193
11851194 // when proc_open cmd is passed as a string (without bypass_shell=true option) the cmd goes thru shell
11861195 // and on Windows quotes are discarded, this is a fix to honor the quotes and allow values containing
11871196 // spaces like '"C:\Program Files\PHP\php.exe"' to be passed as 1 argument correctly
1188- if (IS_WINDOWS ) {
1197+ if (IS_WINDOWS && is_string ( $ commandline ) ) {
11891198 $ commandline = 'start "" /b /wait ' . $ commandline . ' & exit ' ;
11901199 }
11911200
@@ -1204,7 +1213,9 @@ function system_with_timeout(
12041213 $ descriptorspec [1 ] = ['pipe ' , 'w ' ];
12051214 }
12061215 if ($ captureStdErr ) {
1207- $ descriptorspec [2 ] = ['pipe ' , 'w ' ];
1216+ $ descriptorspec [2 ] = $ mergeStdErr
1217+ ? ['redirect ' , 1 ]
1218+ : ['pipe ' , 'w ' ];
12081219 }
12091220 $ proc = proc_open ($ commandline , $ descriptorspec , $ pipes , TEST_PHP_SRCDIR , $ bin_env , ['suppress_errors ' => true ]);
12101221
@@ -1277,6 +1288,49 @@ function system_with_timeout(
12771288 return $ data ;
12781289}
12791290
1291+ function can_run_with_structured_test_command (TestFile $ test ): bool
1292+ {
1293+ global $ preload , $ valgrind ;
1294+
1295+ return !$ valgrind
1296+ && !$ preload
1297+ && !$ test ->hasAnySections (
1298+ 'ARGS ' ,
1299+ 'CAPTURE_STDIO ' ,
1300+ 'DEFLATE_POST ' ,
1301+ 'GZIP_POST ' ,
1302+ 'POST ' ,
1303+ 'POST_RAW ' ,
1304+ 'PUT ' ,
1305+ );
1306+ }
1307+
1308+ function create_structured_test_command (
1309+ string $ php ,
1310+ array $ sapiOptionArgs ,
1311+ array $ passOptionArgs ,
1312+ array $ iniSettings ,
1313+ string $ testFile ,
1314+ int $ numRepeats
1315+ ): array {
1316+ $ command = [
1317+ $ php ,
1318+ ...$ sapiOptionArgs ,
1319+ ...$ passOptionArgs ,
1320+ ];
1321+ if ($ numRepeats > 1 ) {
1322+ $ command [] = '--repeat ' ;
1323+ $ command [] = (string ) $ numRepeats ;
1324+ }
1325+
1326+ return [
1327+ ...$ command ,
1328+ ...settings2arguments ($ iniSettings ),
1329+ '-f ' ,
1330+ $ testFile ,
1331+ ];
1332+ }
1333+
12801334function run_all_tests (array $ test_files , array $ env , ?string $ redir_tested = null ): void
12811335{
12821336 global $ test_results , $ failed_tests_file , $ result_tests_file , $ php , $ test_idx , $ file_cache , $ shuffle ;
@@ -1837,7 +1891,7 @@ function skip_test(string $tested, string $tested_file, string $shortname, strin
18371891function run_test (string $ php , $ file , array $ env ): string
18381892{
18391893 global $ log_format , $ ini_overwrites , $ PHP_FAILED_TESTS ;
1840- global $ pass_options , $ DETAILED , $ IN_REDIRECT , $ test_cnt , $ test_idx ;
1894+ global $ pass_options , $ pass_options_args , $ DETAILED , $ IN_REDIRECT , $ test_cnt , $ test_idx ;
18411895 global $ valgrind , $ temp_source , $ temp_target , $ cfg , $ environment ;
18421896 global $ no_clean ;
18431897 global $ SHOW_ONLY_GROUPS ;
@@ -1859,6 +1913,9 @@ function run_test(string $php, $file, array $env): string
18591913 $ skipCache = new SkipCache ($ enableSkipCache , $ cfg ['keep ' ]['skip ' ]);
18601914 }
18611915
1916+ $ originalPhpExecutable = $ php ;
1917+ $ phpExecutable = $ php ;
1918+ $ sapiOptionArgs = [];
18621919 $ php = escapeshellarg ($ php );
18631920 $ orig_php = $ php ;
18641921
@@ -1930,6 +1987,8 @@ function run_test(string $php, $file, array $env): string
19301987 if (!$ php_cgi ) {
19311988 return skip_test ($ tested , $ tested_file , $ shortname , 'CGI not available ' );
19321989 }
1990+ $ phpExecutable = $ php_cgi ;
1991+ $ sapiOptionArgs [] = '-C ' ;
19331992 $ php = escapeshellarg ($ php_cgi ) . ' -C ' ;
19341993 $ uses_cgi = true ;
19351994 if ($ num_repeats > 1 ) {
@@ -1939,13 +1998,17 @@ function run_test(string $php, $file, array $env): string
19391998
19401999 /* For phpdbg tests, check if phpdbg sapi is available and if it is, use it. */
19412000 $ extra_options = '' ;
2001+ $ extraOptionArgs = [];
19422002 if ($ test ->hasSection ('PHPDBG ' )) {
19432003 if (isset ($ phpdbg )) {
2004+ $ phpExecutable = $ phpdbg ;
2005+ $ sapiOptionArgs [] = '-qIb ' ;
19442006 $ php = escapeshellarg ($ phpdbg ) . ' -qIb ' ;
19452007
19462008 // Additional phpdbg command line options for sections that need to
19472009 // be run straight away. For example, EXTENSIONS, SKIPIF, CLEAN.
19482010 $ extra_options = '-rr ' ;
2011+ $ extraOptionArgs [] = '-rr ' ;
19492012 } else {
19502013 return skip_test ($ tested , $ tested_file , $ shortname , 'phpdbg not available ' );
19512014 }
@@ -2099,6 +2162,7 @@ function run_test(string $php, $file, array $env): string
20992162 //$ini_overwrites[] = 'setting=value';
21002163 settings2array ($ ini_overwrites , $ ini_settings );
21012164
2165+ $ orig_ini_settings_args = settings2arguments ($ ini_settings );
21022166 $ orig_ini_settings = settings2params ($ ini_settings );
21032167
21042168 if ($ file_cache !== null ) {
@@ -2146,6 +2210,7 @@ function run_test(string $php, $file, array $env): string
21462210 }
21472211 }
21482212
2213+ $ testIniSettings = $ ini_settings ;
21492214 $ ini_settings = settings2params ($ ini_settings );
21502215
21512216 $ env ['TEST_PHP_EXTRA_ARGS ' ] = $ pass_options . ' ' . $ ini_settings ;
@@ -2156,8 +2221,6 @@ function run_test(string $php, $file, array $env): string
21562221
21572222 if ($ test ->sectionNotEmpty ('SKIPIF ' )) {
21582223 show_file_block ('skip ' , $ test ->getSection ('SKIPIF ' ));
2159- $ extra = !IS_WINDOWS ?
2160- "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD; " : "" ;
21612224
21622225 if ($ valgrind ) {
21632226 $ env ['USE_ZEND_ALLOC ' ] = '0 ' ;
@@ -2167,7 +2230,22 @@ function run_test(string $php, $file, array $env): string
21672230 $ junit ->startTimer ($ shortname );
21682231
21692232 $ startTime = microtime (true );
2170- $ commandLine = "$ extra $ php $ pass_options $ extra_options -q $ orig_ini_settings $ no_file_cache -d display_errors=1 -d display_startup_errors=0 " ;
2233+ $ commandLine = [
2234+ $ phpExecutable ,
2235+ ...$ sapiOptionArgs ,
2236+ ...$ pass_options_args ,
2237+ ...$ extraOptionArgs ,
2238+ '-q ' ,
2239+ ...$ orig_ini_settings_args ,
2240+ '-d ' ,
2241+ 'opcache.file_cache= ' ,
2242+ '-d ' ,
2243+ 'opcache.file_cache_only=0 ' ,
2244+ '-d ' ,
2245+ 'display_errors=1 ' ,
2246+ '-d ' ,
2247+ 'display_startup_errors=0 ' ,
2248+ ];
21712249 $ output = $ skipCache ->checkSkip ($ commandLine , $ test ->getSection ('SKIPIF ' ), $ test_skipif , $ temp_skipif , $ env );
21722250
21732251 $ time = microtime (true ) - $ startTime ;
@@ -2498,7 +2576,26 @@ function run_test(string $php, $file, array $env): string
24982576 $ startTime = $ hrtime [0 ] * 1000000000 + $ hrtime [1 ];
24992577
25002578 $ stdin = $ test ->hasSection ('STDIN ' ) ? $ test ->getSection ('STDIN ' ) : null ;
2501- $ out = system_with_timeout ($ cmd , $ env , $ stdin , $ captureStdIn , $ captureStdOut , $ captureStdErr );
2579+ $ useStructuredCommand = can_run_with_structured_test_command ($ test );
2580+ $ testCommand = $ useStructuredCommand
2581+ ? create_structured_test_command (
2582+ $ phpExecutable ,
2583+ $ sapiOptionArgs ,
2584+ $ pass_options_args ,
2585+ $ testIniSettings ,
2586+ $ test_file ,
2587+ $ num_repeats ,
2588+ )
2589+ : $ cmd ;
2590+ $ out = system_with_timeout (
2591+ $ testCommand ,
2592+ $ env ,
2593+ $ stdin ,
2594+ $ captureStdIn ,
2595+ $ captureStdOut ,
2596+ $ captureStdErr ,
2597+ $ useStructuredCommand && $ captureStdOut && $ captureStdErr ,
2598+ );
25022599
25032600 $ junit ->stopTimer ($ shortname );
25042601 $ hrtime = hrtime ();
@@ -2520,9 +2617,27 @@ function run_test(string $php, $file, array $env): string
25202617 save_text ($ test_clean , trim ($ test ->getSection ('CLEAN ' )), $ temp_clean );
25212618
25222619 if (!$ no_clean ) {
2523- $ extra = !IS_WINDOWS ?
2524- "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD; " : "" ;
2525- $ clean_output = system_with_timeout ("$ extra $ orig_php $ pass_options -q $ orig_ini_settings $ no_file_cache \"$ test_clean \"" , $ env );
2620+ $ cleanCommand = [
2621+ $ originalPhpExecutable ,
2622+ ...$ pass_options_args ,
2623+ '-q ' ,
2624+ ...$ orig_ini_settings_args ,
2625+ '-d ' ,
2626+ 'opcache.file_cache= ' ,
2627+ '-d ' ,
2628+ 'opcache.file_cache_only=0 ' ,
2629+ $ test_clean ,
2630+ ];
2631+ $ cleanEnv = $ env ;
2632+ if (!IS_WINDOWS ) {
2633+ unset(
2634+ $ cleanEnv ['REQUEST_METHOD ' ],
2635+ $ cleanEnv ['QUERY_STRING ' ],
2636+ $ cleanEnv ['PATH_TRANSLATED ' ],
2637+ $ cleanEnv ['SCRIPT_FILENAME ' ],
2638+ );
2639+ }
2640+ $ clean_output = system_with_timeout ($ cleanCommand , $ cleanEnv );
25262641 }
25272642
25282643 if (!$ cfg ['keep ' ]['clean ' ]) {
@@ -3033,6 +3148,20 @@ function settings2params(array $ini_settings): string
30333148 return $ settings ;
30343149}
30353150
3151+ function settings2arguments (array $ ini_settings ): array
3152+ {
3153+ $ arguments = [];
3154+
3155+ foreach ($ ini_settings as $ name => $ value ) {
3156+ foreach ((array ) $ value as $ item ) {
3157+ $ arguments [] = '-d ' ;
3158+ $ arguments [] = "$ name= $ item " ;
3159+ }
3160+ }
3161+
3162+ return $ arguments ;
3163+ }
3164+
30363165function compute_summary (): void
30373166{
30383167 global $ n_total , $ test_results , $ ignored_by_ext , $ sum_results , $ percent_results ;
@@ -3612,12 +3741,12 @@ public function __construct(bool $enable, bool $keepFile)
36123741 $ this ->keepFile = $ keepFile ;
36133742 }
36143743
3615- public function checkSkip (string $ php , string $ code , string $ checkFile , string $ tempFile , array $ env ): string
3744+ public function checkSkip (string | array $ command , string $ code , string $ checkFile , string $ tempFile , array $ env ): string
36163745 {
36173746 // Extension tests frequently use something like <?php require 'skipif.inc';
36183747 // for skip checks. This forces us to cache per directory to avoid pollution.
36193748 $ dir = dirname ($ checkFile );
3620- $ key = " $ php => $ dir " ;
3749+ $ key = ( is_array ( $ command ) ? implode ( "\0" , $ command ) : $ command ) . " => $ dir " ;
36213750
36223751 if (isset ($ this ->skips [$ key ][$ code ])) {
36233752 $ this ->hits ++;
@@ -3628,7 +3757,12 @@ public function checkSkip(string $php, string $code, string $checkFile, string $
36283757 }
36293758
36303759 save_text ($ checkFile , $ code , $ tempFile );
3631- $ result = trim (system_with_timeout ("$ php \"$ checkFile \"" , $ env ));
3760+ if (is_array ($ command )) {
3761+ $ command [] = $ checkFile ;
3762+ } else {
3763+ $ command .= " \"$ checkFile \"" ;
3764+ }
3765+ $ result = trim (system_with_timeout ($ command , $ env ));
36323766 if (strpos ($ result , 'nocache ' ) === 0 ) {
36333767 $ result = '' ;
36343768 } else if ($ this ->enable ) {
0 commit comments