1#!/usr/bin/env php
2<?php
3/*
4   +----------------------------------------------------------------------+
5   | Copyright (c) The PHP Group                                          |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | https://php.net/license/3_01.txt                                     |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Ilia Alshanetsky <iliaa@php.net>                            |
16   |          Preston L. Bannister <pbannister@php.net>                   |
17   |          Marcus Boerger <helly@php.net>                              |
18   |          Derick Rethans <derick@php.net>                             |
19   |          Sander Roobol <sander@php.net>                              |
20   |          Andrea Faulds <ajf@ajf.me>                                  |
21   | (based on version by: Stig Bakken <ssb@php.net>)                     |
22   | (based on the PHP 3 test framework by Rasmus Lerdorf)                |
23   +----------------------------------------------------------------------+
24 */
25
26/* $Id$ */
27
28/* Let there be no top-level code beyond this point:
29 * Only functions and classes, thanks!
30 *
31 * Minimum required PHP version: 5.3.0
32 */
33
34function show_usage()
35{
36    echo <<<HELP
37Synopsis:
38    php run-tests.php [options] [files] [directories]
39
40Options:
41    -j<workers> Run up to <workers> simultaneous testing processes in parallel for
42                quicker testing on systems with multiple logical processors.
43                Note that this is experimental feature.
44
45    -l <file>   Read the testfiles to be executed from <file>. After the test
46                has finished all failed tests are written to the same <file>.
47                If the list is empty and no further test is specified then
48                all tests are executed (same as: -r <file> -w <file>).
49
50    -r <file>   Read the testfiles to be executed from <file>.
51
52    -w <file>   Write a list of all failed tests to <file>.
53
54    -a <file>   Same as -w but append rather then truncating <file>.
55
56    -W <file>   Write a list of all tests and their result status to <file>.
57
58    -c <file>   Look for php.ini in directory <file> or use <file> as ini.
59
60    -n          Pass -n option to the php binary (Do not use a php.ini).
61
62    -d foo=bar  Pass -d option to the php binary (Define INI entry foo
63                with value 'bar').
64
65    -g          Comma separated list of groups to show during test run
66                (possible values: PASS, FAIL, XFAIL, XLEAK, SKIP, BORK, WARN, LEAK, REDIRECT).
67
68    -m          Test for memory leaks with Valgrind (equivalent to -M memcheck).
69
70    -M <tool>   Test for errors with Valgrind tool.
71
72    -p <php>    Specify PHP executable to run.
73
74    -P          Use PHP_BINARY as PHP executable to run (default).
75
76    -q          Quiet, no user interaction (same as environment NO_INTERACTION).
77
78    -s <file>   Write output to <file>.
79
80    -x          Sets 'SKIP_SLOW_TESTS' environmental variable.
81
82    --offline   Sets 'SKIP_ONLINE_TESTS' environmental variable.
83
84    --verbose
85    -v          Verbose mode.
86
87    --help
88    -h          This Help.
89
90    --temp-source <sdir>  --temp-target <tdir> [--temp-urlbase <url>]
91                Write temporary files to <tdir> by replacing <sdir> from the
92                filenames to generate with <tdir>. In general you want to make
93                <sdir> the path to your source files and <tdir> some patch in
94                your web page hierarchy with <url> pointing to <tdir>.
95
96    --keep-[all|php|skip|clean]
97                Do not delete 'all' files, 'php' test file, 'skip' or 'clean'
98                file.
99
100    --set-timeout [n]
101                Set timeout for individual tests, where [n] is the number of
102                seconds. The default value is 60 seconds, or 300 seconds when
103                testing for memory leaks.
104
105    --context [n]
106                Sets the number of lines of surrounding context to print for diffs.
107                The default value is 3.
108
109    --show-[all|php|skip|clean|exp|diff|out|mem]
110                Show 'all' files, 'php' test file, 'skip' or 'clean' file. You
111                can also use this to show the output 'out', the expected result
112                'exp', the difference between them 'diff' or the valgrind log
113                'mem'. The result types get written independent of the log format,
114                however 'diff' only exists when a test fails.
115
116    --show-slow [n]
117                Show all tests that took longer than [n] milliseconds to run.
118
119    --no-clean  Do not execute clean section if any.
120
121    --color
122    --no-color  Do/Don't colorize the result type in the test result.
123
124    --repeat [n]
125                Run the tests multiple times in the same process and check the
126                output of the last execution (CLI SAPI only).
127
128
129HELP;
130}
131
132/**
133 * One function to rule them all, one function to find them, one function to
134 * bring them all and in the darkness bind them.
135 * This is the entry point and exit point überfunction. It contains all the
136 * code that was previously found at the top level. It could and should be
137 * refactored to be smaller and more manageable.
138 */
139function main()
140{
141    /* This list was derived in a naïve mechanical fashion. If a member
142     * looks like it doesn't belong, it probably doesn't; cull at will.
143     */
144    global $DETAILED, $PHP_FAILED_TESTS, $SHOW_ONLY_GROUPS, $argc, $argv, $cfg,
145           $cfgfiles, $cfgtypes, $conf_passed, $end_time, $environment,
146           $exts_skipped, $exts_tested, $exts_to_test, $failed_tests_file,
147           $ignored_by_ext, $ini_overwrites, $is_switch, $colorize,
148           $just_save_results, $log_format, $matches, $no_clean, $no_file_cache,
149           $optionals, $output_file, $pass_option_n, $pass_options,
150           $pattern_match, $php, $php_cgi, $phpdbg, $preload, $redir_tests,
151           $repeat, $result_tests_file, $slow_min_ms, $start_time, $switch,
152           $temp_source, $temp_target, $test_cnt, $test_dirs,
153           $test_files, $test_idx, $test_list, $test_results, $testfile,
154           $user_tests, $valgrind, $sum_results, $shuffle, $file_cache, $num_repeats;
155    // Parallel testing
156    global $workers, $workerID;
157    global $context_line_count;
158
159    define('IS_WINDOWS', substr(PHP_OS, 0, 3) == "WIN");
160
161    $workerID = 0;
162    if (getenv("TEST_PHP_WORKER")) {
163        $workerID = intval(getenv("TEST_PHP_WORKER"));
164        run_worker();
165        return;
166    }
167
168    define('INIT_DIR', getcwd());
169
170    // Change into the PHP source directory.
171    if (getenv('TEST_PHP_SRCDIR')) {
172        @chdir(getenv('TEST_PHP_SRCDIR'));
173    }
174
175    define('TEST_PHP_SRCDIR', getcwd());
176
177    check_proc_open_function_exists();
178
179    // If timezone is not set, use UTC.
180    if (ini_get('date.timezone') == '') {
181        date_default_timezone_set('UTC');
182    }
183
184    // Delete some security related environment variables
185    putenv('SSH_CLIENT=deleted');
186    putenv('SSH_AUTH_SOCK=deleted');
187    putenv('SSH_TTY=deleted');
188    putenv('SSH_CONNECTION=deleted');
189
190    set_time_limit(0);
191
192    ini_set('pcre.backtrack_limit', PHP_INT_MAX);
193
194    init_output_buffers();
195
196    error_reporting(E_ALL);
197
198    $environment = empty($_ENV) ? array() : $_ENV;
199
200    // Some configurations like php.ini-development set variables_order="GPCS"
201    // not "EGPCS", in which case $_ENV is NOT populated. Detect if the $_ENV
202    // was empty and handle it by explicitly populating through getenv().
203    if (empty($environment)) {
204        $environment = getenv();
205    }
206
207    if (empty($environment['TEMP'])) {
208        $environment['TEMP'] = sys_get_temp_dir();
209
210        if (empty($environment['TEMP'])) {
211            // For example, OpCache on Windows will fail in this case because
212            // child processes (for tests) will not get a TEMP variable, so
213            // GetTempPath() will fallback to c:\windows, while GetTempPath()
214            // will return %TEMP% for parent (likely a different path). The
215            // parent will initialize the OpCache in that path, and child will
216            // fail to reattach to the OpCache because it will be using the
217            // wrong path.
218            die("TEMP environment is NOT set");
219        } else {
220            if (count($environment) == 1) {
221                // Not having other environment variables, only having TEMP, is
222                // probably ok, but strange and may make a difference in the
223                // test pass rate, so warn the user.
224                echo "WARNING: Only 1 environment variable will be available to tests(TEMP environment variable)" . PHP_EOL;
225            }
226        }
227    }
228
229    if (IS_WINDOWS && empty($environment["SystemRoot"])) {
230        $environment["SystemRoot"] = getenv("SystemRoot");
231    }
232
233    $php = null;
234    $php_cgi = null;
235    $phpdbg = null;
236
237    if (getenv('TEST_PHP_LOG_FORMAT')) {
238        $log_format = strtoupper(getenv('TEST_PHP_LOG_FORMAT'));
239    } else {
240        $log_format = 'LEODS';
241    }
242
243    // Check whether a detailed log is wanted.
244    if (getenv('TEST_PHP_DETAILED')) {
245        $DETAILED = getenv('TEST_PHP_DETAILED');
246    } else {
247        $DETAILED = 0;
248    }
249
250    junit_init();
251
252    if (getenv('SHOW_ONLY_GROUPS')) {
253        $SHOW_ONLY_GROUPS = explode(",", getenv('SHOW_ONLY_GROUPS'));
254    } else {
255        $SHOW_ONLY_GROUPS = array();
256    }
257
258    // Check whether user test dirs are requested.
259    if (getenv('TEST_PHP_USER')) {
260        $user_tests = explode(',', getenv('TEST_PHP_USER'));
261    } else {
262        $user_tests = array();
263    }
264
265    $exts_to_test = array();
266    $ini_overwrites = array(
267        'output_handler=',
268        'open_basedir=',
269        'disable_functions=',
270        'output_buffering=Off',
271        'error_reporting=' . E_ALL,
272        'display_errors=1',
273        'display_startup_errors=1',
274        'log_errors=0',
275        'html_errors=0',
276        'track_errors=0',
277        'report_memleaks=1',
278        'report_zend_debug=0',
279        'docref_root=',
280        'docref_ext=.html',
281        'error_prepend_string=',
282        'error_append_string=',
283        'auto_prepend_file=',
284        'auto_append_file=',
285        'ignore_repeated_errors=0',
286        'precision=14',
287        'serialize_precision=-1',
288        'memory_limit=128M',
289        'log_errors_max_len=0',
290        'opcache.fast_shutdown=0',
291        'opcache.file_update_protection=0',
292        'opcache.revalidate_freq=0',
293        'opcache.jit_hot_loop=1',
294        'opcache.jit_hot_func=1',
295        'opcache.jit_hot_return=1',
296        'opcache.jit_hot_side_exit=1',
297        'zend.assertions=1',
298        'zend.exception_ignore_args=0',
299        'zend.exception_string_param_max_len=15',
300        'short_open_tag=0',
301	);
302
303    $no_file_cache = '-d opcache.file_cache= -d opcache.file_cache_only=0';
304
305    define('PHP_QA_EMAIL', 'qa-reports@lists.php.net');
306    define('QA_SUBMISSION_PAGE', 'http://qa.php.net/buildtest-process.php');
307    define('QA_REPORTS_PAGE', 'http://qa.php.net/reports');
308    define('TRAVIS_CI', (bool) getenv('TRAVIS'));
309
310    // Determine the tests to be run.
311
312    $test_files = array();
313    $redir_tests = array();
314    $test_results = array();
315    $PHP_FAILED_TESTS = array(
316        'BORKED' => array(),
317        'FAILED' => array(),
318        'WARNED' => array(),
319        'LEAKED' => array(),
320        'XFAILED' => array(),
321        'XLEAKED' => array(),
322        'SLOW' => array()
323    );
324
325    // If parameters given assume they represent selected tests to run.
326    $result_tests_file = false;
327    $failed_tests_file = false;
328    $pass_option_n = false;
329    $pass_options = '';
330
331    $output_file = INIT_DIR . '/php_test_results_' . date('Ymd_Hi') . '.txt';
332
333    $just_save_results = false;
334    $valgrind = null;
335    $temp_source = null;
336    $temp_target = null;
337    $conf_passed = null;
338    $no_clean = false;
339    $colorize = true;
340    if (function_exists('sapi_windows_vt100_support') && !sapi_windows_vt100_support(STDOUT, true)) {
341        $colorize = false;
342    }
343    if (array_key_exists('NO_COLOR', $_ENV)) {
344        $colorize = false;
345    }
346    $selected_tests = false;
347    $slow_min_ms = INF;
348    $preload = false;
349    $file_cache = null;
350    $shuffle = false;
351    $workers = null;
352    $context_line_count = 3;
353    $num_repeats = 1;
354
355    $cfgtypes = array('show', 'keep');
356    $cfgfiles = array('skip', 'php', 'clean', 'out', 'diff', 'exp', 'mem');
357    $cfg = array();
358
359    foreach ($cfgtypes as $type) {
360        $cfg[$type] = array();
361
362        foreach ($cfgfiles as $file) {
363            $cfg[$type][$file] = false;
364        }
365    }
366
367    if (!isset($argc, $argv) || !$argc) {
368        $argv = array(__FILE__);
369        $argc = 1;
370    }
371
372    if (getenv('TEST_PHP_ARGS')) {
373        $argv = array_merge($argv, explode(' ', getenv('TEST_PHP_ARGS')));
374        $argc = count($argv);
375    }
376
377    for ($i = 1; $i < $argc; $i++) {
378        $is_switch = false;
379        $switch = substr($argv[$i], 1, 1);
380        $repeat = substr($argv[$i], 0, 1) == '-';
381
382        while ($repeat) {
383            if (!$is_switch) {
384                $switch = substr($argv[$i], 1, 1);
385            }
386
387            $is_switch = true;
388
389            if ($repeat) {
390                foreach ($cfgtypes as $type) {
391                    if (strpos($switch, '--' . $type) === 0) {
392                        foreach ($cfgfiles as $file) {
393                            if ($switch == '--' . $type . '-' . $file) {
394                                $cfg[$type][$file] = true;
395                                $is_switch = false;
396                                break;
397                            }
398                        }
399                    }
400                }
401            }
402
403            if (!$is_switch) {
404                $is_switch = true;
405                break;
406            }
407
408            $repeat = false;
409
410            switch ($switch) {
411                case 'j':
412                    $workers = substr($argv[$i], 2);
413                    if (!preg_match('/^\d+$/', $workers) || $workers == 0) {
414                        error("'$workers' is not a valid number of workers, try e.g. -j16 for 16 workers");
415                    }
416                    $workers = intval($workers, 10);
417                    // Don't use parallel testing infrastructure if there is only one worker.
418                    if ($workers === 1) {
419                        $workers = null;
420                    }
421                    break;
422                case 'r':
423                case 'l':
424                    $test_list = file($argv[++$i]);
425                    if ($test_list) {
426                        foreach ($test_list as $test) {
427                            $matches = array();
428                            if (preg_match('/^#.*\[(.*)\]\:\s+(.*)$/', $test, $matches)) {
429                                $redir_tests[] = array($matches[1], $matches[2]);
430                            } else {
431                                if (strlen($test)) {
432                                    $test_files[] = trim($test);
433                                }
434                            }
435                        }
436                    }
437                    if ($switch != 'l') {
438                        break;
439                    }
440                    $i--;
441                // no break
442                case 'w':
443                    $failed_tests_file = fopen($argv[++$i], 'w+t');
444                    break;
445                case 'a':
446                    $failed_tests_file = fopen($argv[++$i], 'a+t');
447                    break;
448                case 'W':
449                    $result_tests_file = fopen($argv[++$i], 'w+t');
450                    break;
451                case 'c':
452                    $conf_passed = $argv[++$i];
453                    break;
454                case 'd':
455                    $ini_overwrites[] = $argv[++$i];
456                    break;
457                case 'g':
458                    $SHOW_ONLY_GROUPS = explode(",", $argv[++$i]);
459                    break;
460                //case 'h'
461                case '--keep-all':
462                    foreach ($cfgfiles as $file) {
463                        $cfg['keep'][$file] = true;
464                    }
465                    break;
466                //case 'l'
467                case 'm':
468                    $valgrind = new RuntestsValgrind($environment);
469                    break;
470                case 'M':
471                    $valgrind = new RuntestsValgrind($environment, $argv[++$i]);
472                    break;
473                case 'n':
474                    if (!$pass_option_n) {
475                        $pass_options .= ' -n';
476                    }
477                    $pass_option_n = true;
478                    break;
479                case 'e':
480                    $pass_options .= ' -e';
481                    break;
482                case '--preload':
483                    $preload = true;
484                    break;
485                case '--file-cache-prime':
486                    $file_cache = 'prime';
487                    break;
488                case '--file-cache-use':
489                    $file_cache = 'use';
490                    break;
491                case '--no-clean':
492                    $no_clean = true;
493                    break;
494                case '--color':
495                    $colorize = true;
496                    break;
497                case '--no-color':
498                    $colorize = false;
499                    break;
500                case 'p':
501                    $php = $argv[++$i];
502                    putenv("TEST_PHP_EXECUTABLE=$php");
503                    $environment['TEST_PHP_EXECUTABLE'] = $php;
504                    break;
505                case 'P':
506                    $php = PHP_BINARY;
507                    putenv("TEST_PHP_EXECUTABLE=$php");
508                    $environment['TEST_PHP_EXECUTABLE'] = $php;
509                    break;
510                case 'q':
511                    putenv('NO_INTERACTION=1');
512                    $environment['NO_INTERACTION'] = 1;
513                    break;
514                //case 'r'
515                case 's':
516                    $output_file = $argv[++$i];
517                    $just_save_results = true;
518                    break;
519                case '--set-timeout':
520                    $environment['TEST_TIMEOUT'] = $argv[++$i];
521                    break;
522                case '--context':
523                    $context_line_count = empty($argv[++$i]) ? '' : $argv[$i];
524                    if (!preg_match('/^\d+$/', $context_line_count)) {
525                        error("'$context_line_count' is not a valid number of lines of context, try e.g. --context 3 for 3 lines");
526                    }
527                    $context_line_count = intval($context_line_count, 10);
528                    break;
529                case '--show-all':
530                    foreach ($cfgfiles as $file) {
531                        $cfg['show'][$file] = true;
532                    }
533                    break;
534                case '--show-slow':
535                    $slow_min_ms = $argv[++$i];
536                    break;
537                case '--temp-source':
538                    $temp_source = $argv[++$i];
539                    break;
540                case '--temp-target':
541                    $temp_target = $argv[++$i];
542                    break;
543                case 'v':
544                case '--verbose':
545                    $DETAILED = true;
546                    break;
547                case 'x':
548                    $environment['SKIP_SLOW_TESTS'] = 1;
549                    break;
550                case '--offline':
551                    $environment['SKIP_ONLINE_TESTS'] = 1;
552                    break;
553                case '--shuffle':
554                    $shuffle = true;
555                    break;
556                case '--asan':
557                case '--msan':
558                    $environment['USE_ZEND_ALLOC'] = 0;
559                    $environment['USE_TRACKED_ALLOC'] = 1;
560                    $environment['SKIP_ASAN'] = 1;
561                    $environment['SKIP_PERF_SENSITIVE'] = 1;
562                    if ($switch === '--msan') {
563                        $environment['SKIP_MSAN'] = 1;
564                    }
565
566                    $lsanSuppressions = __DIR__ . '/azure/lsan-suppressions.txt';
567                    if (file_exists($lsanSuppressions)) {
568                        $environment['LSAN_OPTIONS'] = 'suppressions=' . $lsanSuppressions
569                            . ':print_suppressions=0';
570                    }
571                    break;
572                case '--repeat':
573                    $num_repeats = (int) $argv[++$i];
574                    $environment['SKIP_REPEAT'] = 1;
575                    break;
576                //case 'w'
577                case '-':
578                    // repeat check with full switch
579                    $switch = $argv[$i];
580                    if ($switch != '-') {
581                        $repeat = true;
582                    }
583                    break;
584                case '--version':
585                    echo '$Id$' . "\n";
586                    exit(1);
587
588                default:
589                    echo "Illegal switch '$switch' specified!\n";
590                    // no break
591                case 'h':
592                case '-help':
593                case '--help':
594                    show_usage();
595                    exit(1);
596            }
597        }
598
599        if (!$is_switch) {
600            $selected_tests = true;
601            $testfile = realpath($argv[$i]);
602
603            if (!$testfile && strpos($argv[$i], '*') !== false && function_exists('glob')) {
604                if (substr($argv[$i], -5) == '.phpt') {
605                    $pattern_match = glob($argv[$i]);
606                } else {
607                    if (preg_match("/\*$/", $argv[$i])) {
608                        $pattern_match = glob($argv[$i] . '.phpt');
609                    } else {
610                        die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL);
611                    }
612                }
613
614                if (is_array($pattern_match)) {
615                    $test_files = array_merge($test_files, $pattern_match);
616                }
617            } else {
618                if (is_dir($testfile)) {
619                    find_files($testfile);
620                } else {
621                    if (substr($testfile, -5) == '.phpt') {
622                        $test_files[] = $testfile;
623                    } else {
624                        die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL);
625                    }
626                }
627            }
628        }
629    }
630
631    if ($selected_tests && count($test_files) === 0) {
632        echo "No tests found.\n";
633        return;
634    }
635
636    if (!$php) {
637        $php = getenv('TEST_PHP_EXECUTABLE');
638    }
639    if (!$php) {
640        $php = PHP_BINDIR . '/php';
641		if (IS_WINDOWS) {
642			$php .= '.exe';
643		}
644		var_dump($php);
645    }
646
647    if (!$php_cgi) {
648        $php_cgi = getenv('TEST_PHP_CGI_EXECUTABLE');
649    }
650    if (!$php_cgi) {
651        $php_cgi = get_binary($php, 'php-cgi', 'sapi/cgi/php-cgi');
652    }
653
654    if (!$phpdbg) {
655        $phpdbg = getenv('TEST_PHPDBG_EXECUTABLE');
656    }
657    if (!$phpdbg) {
658        $phpdbg = get_binary($php, 'phpdbg', 'sapi/phpdbg/phpdbg');
659    }
660
661    putenv("TEST_PHP_EXECUTABLE=$php");
662    $environment['TEST_PHP_EXECUTABLE'] = $php;
663    putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi");
664    $environment['TEST_PHP_CGI_EXECUTABLE'] = $php_cgi;
665    putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg");
666    $environment['TEST_PHPDBG_EXECUTABLE'] = $phpdbg;
667
668    if ($conf_passed !== null) {
669        if (IS_WINDOWS) {
670            $pass_options .= " -c " . escapeshellarg($conf_passed);
671        } else {
672            $pass_options .= " -c '" . realpath($conf_passed) . "'";
673        }
674    }
675
676    $test_files = array_unique($test_files);
677    $test_files = array_merge($test_files, $redir_tests);
678
679    // Run selected tests.
680    $test_cnt = count($test_files);
681
682    verify_config();
683    write_information();
684
685    if ($test_cnt) {
686        putenv('NO_INTERACTION=1');
687        usort($test_files, "test_sort");
688        $start_time = time();
689
690        echo "Running selected tests.\n";
691
692        $test_idx = 0;
693        run_all_tests($test_files, $environment);
694        $end_time = time();
695
696        if ($failed_tests_file) {
697            fclose($failed_tests_file);
698        }
699
700        if ($result_tests_file) {
701            fclose($result_tests_file);
702        }
703
704        if (0 == count($test_results)) {
705            echo "No tests were run.\n";
706            return;
707        }
708
709        compute_summary();
710        echo "=====================================================================";
711        echo get_summary(false);
712
713        if ($output_file != '' && $just_save_results) {
714            save_or_mail_results();
715        }
716    } else {
717        // Compile a list of all test files (*.phpt).
718        $test_files = array();
719        $exts_tested = count($exts_to_test);
720        $exts_skipped = 0;
721        $ignored_by_ext = 0;
722        sort($exts_to_test);
723        $test_dirs = array();
724        $optionals = array('Zend', 'tests', 'ext', 'sapi');
725
726        foreach ($optionals as $dir) {
727            if (is_dir($dir)) {
728                $test_dirs[] = $dir;
729            }
730        }
731
732        // Convert extension names to lowercase
733        foreach ($exts_to_test as $key => $val) {
734            $exts_to_test[$key] = strtolower($val);
735        }
736
737        foreach ($test_dirs as $dir) {
738            find_files(TEST_PHP_SRCDIR . "/{$dir}", $dir == 'ext');
739        }
740
741        foreach ($user_tests as $dir) {
742            find_files($dir, $dir == 'ext');
743        }
744
745        $test_files = array_unique($test_files);
746        usort($test_files, "test_sort");
747
748        $start_time = time();
749        show_start($start_time);
750
751        $test_cnt = count($test_files);
752        $test_idx = 0;
753        run_all_tests($test_files, $environment);
754        $end_time = time();
755
756        if ($failed_tests_file) {
757            fclose($failed_tests_file);
758        }
759
760        if ($result_tests_file) {
761            fclose($result_tests_file);
762        }
763
764        // Summarize results
765
766        if (0 == count($test_results)) {
767            echo "No tests were run.\n";
768            return;
769        }
770
771        compute_summary();
772
773        show_end($end_time);
774        show_summary();
775
776        save_or_mail_results();
777    }
778
779    junit_save_xml();
780    if (getenv('REPORT_EXIT_STATUS') !== '0' && getenv('REPORT_EXIT_STATUS') !== 'no' &&
781            ($sum_results['FAILED'] || $sum_results['LEAKED'])) {
782        exit(1);
783    }
784}
785
786if (!function_exists("hrtime")) {
787    /**
788     * @return array|float|int
789     */
790    function hrtime($as_num = false)
791    {
792        $t = microtime(true);
793
794        if ($as_num) {
795            return $t * 1000000000;
796        }
797
798        $s = floor($t);
799        return array(0 => $s, 1 => ($t - $s) * 1000000000);
800    }
801}
802
803function verify_config()
804{
805    global $php;
806
807    if (empty($php) || !file_exists($php)) {
808        error('environment variable TEST_PHP_EXECUTABLE must be set to specify PHP executable!');
809    }
810
811    if (!is_executable($php)) {
812        error("invalid PHP executable specified by TEST_PHP_EXECUTABLE  = $php");
813    }
814}
815
816function write_information()
817{
818    global $php, $php_cgi, $phpdbg, $php_info, $user_tests, $ini_overwrites, $pass_options, $exts_to_test, $valgrind, $no_file_cache;
819
820    // Get info from php
821    $info_file = __DIR__ . '/run-test-info.php';
822    @unlink($info_file);
823    $php_info = '<?php echo "
824PHP_SAPI    : " , PHP_SAPI , "
825PHP_VERSION : " , phpversion() , "
826ZEND_VERSION: " , zend_version() , "
827PHP_OS      : " , PHP_OS , " - " , php_uname() , "
828INI actual  : " , realpath(get_cfg_var("cfg_file_path")) , "
829More .INIs  : " , (function_exists(\'php_ini_scanned_files\') ? str_replace("\n","", php_ini_scanned_files()) : "** not determined **"); ?>';
830    save_text($info_file, $php_info);
831    $info_params = array();
832    settings2array($ini_overwrites, $info_params);
833    $info_params = settings2params($info_params);
834    $php_info = `$php $pass_options $info_params $no_file_cache "$info_file"`;
835    define('TESTED_PHP_VERSION', `$php -n -r "echo PHP_VERSION;"`);
836
837    if ($php_cgi && $php != $php_cgi) {
838        $php_info_cgi = `$php_cgi $pass_options $info_params $no_file_cache -q "$info_file"`;
839        $php_info_sep = "\n---------------------------------------------------------------------";
840        $php_cgi_info = "$php_info_sep\nPHP         : $php_cgi $php_info_cgi$php_info_sep";
841    } else {
842        $php_cgi_info = '';
843    }
844
845    if ($phpdbg) {
846        $phpdbg_info = `$phpdbg $pass_options $info_params $no_file_cache -qrr "$info_file"`;
847        $php_info_sep = "\n---------------------------------------------------------------------";
848        $phpdbg_info = "$php_info_sep\nPHP         : $phpdbg $phpdbg_info$php_info_sep";
849    } else {
850        $phpdbg_info = '';
851    }
852
853    if (function_exists('opcache_invalidate')) {
854        opcache_invalidate($info_file, true);
855    }
856    @unlink($info_file);
857
858    // load list of enabled extensions
859    save_text($info_file,
860        '<?php echo str_replace("Zend OPcache", "opcache", implode(",", get_loaded_extensions())); ?>');
861    $exts_to_test = explode(',', `$php $pass_options $info_params $no_file_cache "$info_file"`);
862    // check for extensions that need special handling and regenerate
863    $info_params_ex = array(
864        'session' => array('session.auto_start=0'),
865        'tidy' => array('tidy.clean_output=0'),
866        'zlib' => array('zlib.output_compression=Off'),
867        'xdebug' => array('xdebug.mode=off'),
868        'mbstring' => array('mbstring.func_overload=0'),
869	);
870
871    foreach ($info_params_ex as $ext => $ini_overwrites_ex) {
872        if (in_array($ext, $exts_to_test)) {
873            $ini_overwrites = array_merge($ini_overwrites, $ini_overwrites_ex);
874        }
875    }
876
877    if (function_exists('opcache_invalidate')) {
878        opcache_invalidate($info_file, true);
879    }
880    @unlink($info_file);
881
882    // Write test context information.
883    echo "
884=====================================================================
885PHP         : $php $php_info $php_cgi_info $phpdbg_info
886CWD         : " . TEST_PHP_SRCDIR . "
887Extra dirs  : ";
888    foreach ($user_tests as $test_dir) {
889        echo "{$test_dir}\n			  ";
890    }
891    echo "
892VALGRIND    : " . ($valgrind ? $valgrind->getHeader() : 'Not used') . "
893=====================================================================
894";
895}
896
897function save_or_mail_results()
898{
899    global $sum_results, $just_save_results, $failed_test_summary,
900           $PHP_FAILED_TESTS, $php, $output_file;
901
902    /* We got failed Tests, offer the user to send an e-mail to QA team, unless NO_INTERACTION is set */
903    if (!getenv('NO_INTERACTION') && !TRAVIS_CI) {
904        $fp = fopen("php://stdin", "r+");
905        if ($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['WARNED'] || $sum_results['LEAKED']) {
906            echo "\nYou may have found a problem in PHP.";
907        }
908        echo "\nThis report can be automatically sent to the PHP QA team at\n";
909        echo QA_REPORTS_PAGE . " and http://news.php.net/php.qa.reports\n";
910        echo "This gives us a better understanding of PHP's behavior.\n";
911        echo "If you don't want to send the report immediately you can choose\n";
912        echo "option \"s\" to save it.	You can then email it to " . PHP_QA_EMAIL . " later.\n";
913        echo "Do you want to send this report now? [Yns]: ";
914        flush();
915
916        $user_input = fgets($fp, 10);
917        $just_save_results = (!empty($user_input) && strtolower($user_input[0]) === 's');
918    }
919
920    if ($just_save_results || !getenv('NO_INTERACTION') || TRAVIS_CI) {
921        if ($just_save_results || TRAVIS_CI || strlen(trim($user_input)) == 0 || strtolower($user_input[0]) == 'y') {
922            /*
923             * Collect information about the host system for our report
924             * Fetch phpinfo() output so that we can see the PHP environment
925             * Make an archive of all the failed tests
926             * Send an email
927             */
928            if ($just_save_results) {
929                $user_input = 's';
930            }
931
932            /* Ask the user to provide an email address, so that QA team can contact the user */
933            if (TRAVIS_CI) {
934                $user_email = 'travis at php dot net';
935            } elseif (!strncasecmp($user_input, 'y', 1) || strlen(trim($user_input)) == 0) {
936                echo "\nPlease enter your email address.\n(Your address will be mangled so that it will not go out on any\nmailinglist in plain text): ";
937                flush();
938                $user_email = trim(fgets($fp, 1024));
939                $user_email = str_replace("@", " at ", str_replace(".", " dot ", $user_email));
940            }
941
942            $failed_tests_data = '';
943            $sep = "\n" . str_repeat('=', 80) . "\n";
944            $failed_tests_data .= $failed_test_summary . "\n";
945            $failed_tests_data .= get_summary(true) . "\n";
946
947            if ($sum_results['FAILED']) {
948                foreach ($PHP_FAILED_TESTS['FAILED'] as $test_info) {
949                    $failed_tests_data .= $sep . $test_info['name'] . $test_info['info'];
950                    $failed_tests_data .= $sep . file_get_contents(realpath($test_info['output']));
951                    $failed_tests_data .= $sep . file_get_contents(realpath($test_info['diff']));
952                    $failed_tests_data .= $sep . "\n\n";
953                }
954                $status = "failed";
955            } else {
956                $status = "success";
957            }
958
959            $failed_tests_data .= "\n" . $sep . 'BUILD ENVIRONMENT' . $sep;
960            $failed_tests_data .= "OS:\n" . PHP_OS . " - " . php_uname() . "\n\n";
961            $ldd = $autoconf = $sys_libtool = $libtool = $compiler = 'N/A';
962
963            if (!IS_WINDOWS) {
964                /* If PHP_AUTOCONF is set, use it; otherwise, use 'autoconf'. */
965                if (getenv('PHP_AUTOCONF')) {
966                    $autoconf = shell_exec(getenv('PHP_AUTOCONF') . ' --version');
967                } else {
968                    $autoconf = shell_exec('autoconf --version');
969                }
970
971                /* Always use the generated libtool - Mac OSX uses 'glibtool' */
972                $libtool = shell_exec(INIT_DIR . '/libtool --version');
973
974                /* Use shtool to find out if there is glibtool present (MacOSX) */
975                $sys_libtool_path = shell_exec(__DIR__ . '/build/shtool path glibtool libtool');
976
977                if ($sys_libtool_path) {
978                    $sys_libtool = shell_exec(str_replace("\n", "", $sys_libtool_path) . ' --version');
979                }
980
981                /* Try the most common flags for 'version' */
982                $flags = array('-v', '-V', '--version');
983                $cc_status = 0;
984
985                foreach ($flags as $flag) {
986                    system(getenv('CC') . " $flag >/dev/null 2>&1", $cc_status);
987                    if ($cc_status == 0) {
988                        $compiler = shell_exec(getenv('CC') . " $flag 2>&1");
989                        break;
990                    }
991                }
992
993                $ldd = shell_exec("ldd $php 2>/dev/null");
994            }
995
996            $failed_tests_data .= "Autoconf:\n$autoconf\n";
997            $failed_tests_data .= "Bundled Libtool:\n$libtool\n";
998            $failed_tests_data .= "System Libtool:\n$sys_libtool\n";
999            $failed_tests_data .= "Compiler:\n$compiler\n";
1000            $failed_tests_data .= "Bison:\n" . shell_exec('bison --version 2>/dev/null') . "\n";
1001            $failed_tests_data .= "Libraries:\n$ldd\n";
1002            $failed_tests_data .= "\n";
1003
1004            if (isset($user_email)) {
1005                $failed_tests_data .= "User's E-mail: " . $user_email . "\n\n";
1006            }
1007
1008            $failed_tests_data .= $sep . "PHPINFO" . $sep;
1009            $failed_tests_data .= shell_exec($php . ' -ddisplay_errors=stderr -dhtml_errors=0 -i 2> /dev/null');
1010
1011            if (($just_save_results || !mail_qa_team($failed_tests_data, $status)) && !TRAVIS_CI) {
1012                file_put_contents($output_file, $failed_tests_data);
1013
1014                if (!$just_save_results) {
1015                    echo "\nThe test script was unable to automatically send the report to PHP's QA Team\n";
1016                }
1017
1018                echo "Please send " . $output_file . " to " . PHP_QA_EMAIL . " manually, thank you.\n";
1019            } elseif (!getenv('NO_INTERACTION') && !TRAVIS_CI) {
1020                fwrite($fp, "\nThank you for helping to make PHP better.\n");
1021                fclose($fp);
1022            }
1023        }
1024    }
1025}
1026
1027function get_binary($php, $sapi, $sapi_path)
1028{
1029    $dir = dirname($php);
1030    if (IS_WINDOWS && file_exists("$dir/$sapi.exe")) {
1031        return realpath("$dir/$sapi.exe");
1032    }
1033    if (file_exists("$dir/../../$sapi_path")) {
1034        return realpath("$dir/../../$sapi_path");
1035    }
1036    if (file_exists("$dir/$sapi")) {
1037        return realpath("$dir/$sapi");
1038    }
1039    return null;
1040}
1041
1042function find_files($dir, $is_ext_dir = false, $ignore = false)
1043{
1044    global $test_files, $exts_to_test, $ignored_by_ext, $exts_skipped;
1045
1046    $o = opendir($dir) or error("cannot open directory: $dir");
1047
1048    while (($name = readdir($o)) !== false) {
1049        if (is_dir("{$dir}/{$name}") && !in_array($name, array('.', '..', '.svn'))) {
1050            $skip_ext = ($is_ext_dir && !in_array(strtolower($name), $exts_to_test));
1051            if ($skip_ext) {
1052                $exts_skipped++;
1053            }
1054            find_files("{$dir}/{$name}", false, $ignore || $skip_ext);
1055        }
1056
1057        // Cleanup any left-over tmp files from last run.
1058        if (substr($name, -4) == '.tmp') {
1059            @unlink("$dir/$name");
1060            continue;
1061        }
1062
1063        // Otherwise we're only interested in *.phpt files.
1064        if (substr($name, -5) == '.phpt') {
1065            if ($ignore) {
1066                $ignored_by_ext++;
1067            } else {
1068                $testfile = realpath("{$dir}/{$name}");
1069                $test_files[] = $testfile;
1070            }
1071        }
1072    }
1073
1074    closedir($o);
1075}
1076
1077/**
1078 * @param array|string $name
1079 */
1080function test_name($name)
1081{
1082    if (is_array($name)) {
1083        return $name[0] . ':' . $name[1];
1084    } else {
1085        return $name;
1086    }
1087}
1088/**
1089 * @param array|string $a
1090 * @param array|string $b
1091 */
1092function test_sort($a, $b)
1093{
1094    $a = test_name($a);
1095    $b = test_name($b);
1096
1097    $ta = strpos($a, TEST_PHP_SRCDIR . "/tests") === 0 ? 1 + (strpos($a,
1098            TEST_PHP_SRCDIR . "/tests/run-test") === 0 ? 1 : 0) : 0;
1099    $tb = strpos($b, TEST_PHP_SRCDIR . "/tests") === 0 ? 1 + (strpos($b,
1100            TEST_PHP_SRCDIR . "/tests/run-test") === 0 ? 1 : 0) : 0;
1101
1102    if ($ta == $tb) {
1103        return strcmp($a, $b);
1104    } else {
1105        return $tb - $ta;
1106    }
1107}
1108
1109//
1110// Send Email to QA Team
1111//
1112
1113function mail_qa_team($data, $status = false)
1114{
1115    $url_bits = parse_url(QA_SUBMISSION_PAGE);
1116
1117    if ($proxy = getenv('http_proxy')) {
1118        $proxy = parse_url($proxy);
1119        $path = $url_bits['host'] . $url_bits['path'];
1120        $host = $proxy['host'];
1121        if (empty($proxy['port'])) {
1122            $proxy['port'] = 80;
1123        }
1124        $port = $proxy['port'];
1125    } else {
1126        $path = $url_bits['path'];
1127        $host = $url_bits['host'];
1128        $port = empty($url_bits['port']) ? 80 : $port = $url_bits['port'];
1129    }
1130
1131    $data = "php_test_data=" . urlencode(base64_encode(str_replace("\00", '[0x0]', $data)));
1132    $data_length = strlen($data);
1133
1134    $fs = fsockopen($host, $port, $errno, $errstr, 10);
1135
1136    if (!$fs) {
1137        return false;
1138    }
1139
1140    $php_version = urlencode(TESTED_PHP_VERSION);
1141
1142    echo "\nPosting to " . QA_SUBMISSION_PAGE . "\n";
1143    fwrite($fs, "POST " . $path . "?status=$status&version=$php_version HTTP/1.1\r\n");
1144    fwrite($fs, "Host: " . $host . "\r\n");
1145    fwrite($fs, "User-Agent: QA Browser 0.1\r\n");
1146    fwrite($fs, "Content-Type: application/x-www-form-urlencoded\r\n");
1147    fwrite($fs, "Content-Length: " . $data_length . "\r\n\r\n");
1148    fwrite($fs, $data);
1149    fwrite($fs, "\r\n\r\n");
1150    fclose($fs);
1151
1152    return true;
1153}
1154
1155//
1156//  Write the given text to a temporary file, and return the filename.
1157//
1158
1159function save_text($filename, $text, $filename_copy = null)
1160{
1161    global $DETAILED;
1162
1163    if ($filename_copy && $filename_copy != $filename) {
1164        if (file_put_contents($filename_copy, $text) === false) {
1165            error("Cannot open file '" . $filename_copy . "' (save_text)");
1166        }
1167    }
1168
1169    if (file_put_contents($filename, $text) === false) {
1170        error("Cannot open file '" . $filename . "' (save_text)");
1171    }
1172
1173    if (1 < $DETAILED) {
1174        echo "
1175FILE $filename {{{
1176$text
1177}}}
1178";
1179    }
1180}
1181
1182//
1183//  Write an error in a format recognizable to Emacs or MSVC.
1184//
1185
1186function error_report($testname, $logname, $tested)
1187{
1188    $testname = realpath($testname);
1189    $logname = realpath($logname);
1190
1191    switch (strtoupper(getenv('TEST_PHP_ERROR_STYLE'))) {
1192        case 'MSVC':
1193            echo $testname . "(1) : $tested\n";
1194            echo $logname . "(1) :  $tested\n";
1195            break;
1196        case 'EMACS':
1197            echo $testname . ":1: $tested\n";
1198            echo $logname . ":1:  $tested\n";
1199            break;
1200    }
1201}
1202
1203/**
1204 * @return false|string
1205 */
1206function system_with_timeout(
1207    $commandline,
1208    $env = null,
1209    $stdin = null,
1210    $captureStdIn = true,
1211    $captureStdOut = true,
1212    $captureStdErr = true
1213) {
1214    global $valgrind;
1215
1216    $data = '';
1217
1218    $bin_env = array();
1219    foreach ((array) $env as $key => $value) {
1220        $bin_env[$key] = $value;
1221    }
1222
1223    $descriptorspec = array();
1224    if ($captureStdIn) {
1225        $descriptorspec[0] = array('pipe', 'r');
1226    }
1227    if ($captureStdOut) {
1228        $descriptorspec[1] = array('pipe', 'w');
1229    }
1230    if ($captureStdErr) {
1231        $descriptorspec[2] = array('pipe', 'w');
1232    }
1233    $proc = proc_open($commandline, $descriptorspec, $pipes, TEST_PHP_SRCDIR, $bin_env, array('suppress_errors' => true));
1234
1235    if (!$proc) {
1236        return false;
1237    }
1238
1239    if ($captureStdIn) {
1240        if (!is_null($stdin)) {
1241            fwrite($pipes[0], $stdin);
1242        }
1243        fclose($pipes[0]);
1244        unset($pipes[0]);
1245    }
1246
1247    $timeout = $valgrind ? 300 : (empty($env['TEST_TIMEOUT']) ? 60 : $env['TEST_TIMEOUT']);
1248
1249    while (true) {
1250        /* hide errors from interrupted syscalls */
1251        $r = $pipes;
1252        $w = null;
1253        $e = null;
1254
1255        $n = @stream_select($r, $w, $e, $timeout);
1256
1257        if ($n === false) {
1258            break;
1259        } elseif ($n === 0) {
1260            /* timed out */
1261            $data .= "\n ** ERROR: process timed out **\n";
1262            proc_terminate($proc, 9);
1263            return $data;
1264        } elseif ($n > 0) {
1265            if ($captureStdOut) {
1266                $line = fread($pipes[1], 8192);
1267            } elseif ($captureStdErr) {
1268                $line = fread($pipes[2], 8192);
1269            } else {
1270                $line = '';
1271            }
1272            if (strlen($line) == 0) {
1273                /* EOF */
1274                break;
1275            }
1276            $data .= $line;
1277        }
1278    }
1279
1280    $stat = proc_get_status($proc);
1281
1282    if ($stat['signaled']) {
1283        $data .= "\nTermsig=" . $stat['stopsig'] . "\n";
1284    }
1285    if ($stat["exitcode"] > 128 && $stat["exitcode"] < 160) {
1286        $data .= "\nTermsig=" . ($stat["exitcode"] - 128) . "\n";
1287    }
1288
1289    proc_close($proc);
1290    return $data;
1291}
1292
1293/**
1294 * @param string|array|null $redir_tested
1295 */
1296function run_all_tests(array $test_files, array $env, $redir_tested = null)
1297{
1298    global $test_results, $failed_tests_file, $result_tests_file, $php, $test_idx, $file_cache;
1299    // Parallel testing
1300    global $PHP_FAILED_TESTS, $workers, $workerID, $workerSock;
1301
1302    if ($file_cache !== null) {
1303        /* Automatically skip opcache tests in --file-cache mode,
1304         * because opcache generally doesn't expect those to run under file cache */
1305        $test_files = array_filter($test_files, function($test) {
1306            return !is_string($test) || false === strpos($test, 'ext/opcache');
1307        });
1308    }
1309
1310    /* Ignore -jN if there is only one file to analyze. */
1311    if ($workers !== null && count($test_files) > 1 && !$workerID) {
1312        run_all_tests_parallel($test_files, $env, $redir_tested);
1313        return;
1314    }
1315
1316    foreach ($test_files as $name) {
1317        if (is_array($name)) {
1318            $index = "# $name[1]: $name[0]";
1319
1320            if ($redir_tested) {
1321                $name = $name[0];
1322            }
1323        } elseif ($redir_tested) {
1324            $index = "# $redir_tested: $name";
1325        } else {
1326            $index = $name;
1327        }
1328        $test_idx++;
1329
1330        if ($workerID) {
1331            $PHP_FAILED_TESTS = array('BORKED' => array(), 'FAILED' => array(), 'WARNED' => array(), 'LEAKED' => array(), 'XFAILED' => array(), 'XLEAKED' => array(), 'SLOW' => array());
1332            ob_start();
1333        }
1334
1335        $result = run_test($php, $name, $env);
1336        if ($workerID) {
1337            $resultText = ob_get_clean();
1338        }
1339
1340        if (!is_array($name) && $result != 'REDIR') {
1341            if ($workerID) {
1342                send_message($workerSock, array(
1343                    "type" => "test_result",
1344                    "name" => $name,
1345                    "index" => $index,
1346                    "result" => $result,
1347                    "text" => $resultText,
1348                    "PHP_FAILED_TESTS" => $PHP_FAILED_TESTS
1349				));
1350                continue;
1351            }
1352
1353            $test_results[$index] = $result;
1354            if ($failed_tests_file && ($result == 'XFAILED' || $result == 'XLEAKED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) {
1355                fwrite($failed_tests_file, "$index\n");
1356            }
1357            if ($result_tests_file) {
1358                fwrite($result_tests_file, "$result\t$index\n");
1359            }
1360        }
1361    }
1362}
1363
1364/** The heart of parallel testing.
1365 * @param string|array|null $redir_tested
1366 */
1367function run_all_tests_parallel(array $test_files, array $env, $redir_tested)
1368{
1369    global $workers, $test_idx, $test_cnt, $test_results, $failed_tests_file, $result_tests_file, $PHP_FAILED_TESTS, $shuffle, $SHOW_ONLY_GROUPS, $valgrind;
1370
1371    // The PHP binary running run-tests.php, and run-tests.php itself
1372    // This PHP executable is *not* necessarily the same as the tested version
1373    $thisPHP = PHP_BINARY;
1374    $thisScript = __FILE__;
1375
1376    $workerProcs = array();
1377    $workerSocks = array();
1378
1379    echo "=====================================================================\n";
1380    echo "========= WELCOME TO THE FUTURE: run-tests PARALLEL EDITION =========\n";
1381    echo "=====================================================================\n";
1382
1383    // Each test may specify a list of conflict keys. While a test that conflicts with
1384    // key K is running, no other test that conflicts with K may run. Conflict keys are
1385    // specified either in the --CONFLICTS-- section, or CONFLICTS file inside a directory.
1386    $dirConflictsWith = array();
1387    $fileConflictsWith = array();
1388    $sequentialTests = array();
1389    foreach ($test_files as $i => $file) {
1390        $contents = file_get_contents($file);
1391        if (preg_match('/^--CONFLICTS--(.+?)^--/ms', $contents, $matches)) {
1392            $conflicts = parse_conflicts($matches[1]);
1393        } else {
1394            // Cache per-directory conflicts in a separate map, so we compute these only once.
1395            $dir = dirname($file);
1396            if (!isset($dirConflictsWith[$dir])) {
1397                $dirConflicts = array();
1398                if (file_exists($dir . '/CONFLICTS')) {
1399                    $contents = file_get_contents($dir . '/CONFLICTS');
1400                    $dirConflicts = parse_conflicts($contents);
1401                }
1402                $dirConflictsWith[$dir] = $dirConflicts;
1403            }
1404            $conflicts = $dirConflictsWith[$dir];
1405        }
1406
1407        // For tests conflicting with "all", no other tests may run in parallel. We'll run these
1408        // tests separately at the end, when only one worker is left.
1409        if (in_array('all', $conflicts, true)) {
1410            $sequentialTests[] = $file;
1411            unset($test_files[$i]);
1412        }
1413
1414        $fileConflictsWith[$file] = $conflicts;
1415    }
1416
1417    // Some tests assume that they are executed in a certain order. We will be popping from
1418    // $test_files, so reverse its order here. This makes sure that order is preserved at least
1419    // for tests with a common conflict key.
1420    $test_files = array_reverse($test_files);
1421
1422    // To discover parallelization issues it is useful to randomize the test order.
1423    if ($shuffle) {
1424        shuffle($test_files);
1425    }
1426
1427    // Don't start more workers than test files.
1428    $workers = max(1, min($workers, count($test_files)));
1429
1430    echo "Spawning $workers workers... ";
1431
1432    // We use sockets rather than STDIN/STDOUT for comms because on Windows,
1433    // those can't be non-blocking for some reason.
1434    $listenSock = stream_socket_server("tcp://127.0.0.1:0") or error("Couldn't create socket on localhost.");
1435    $sockName = stream_socket_get_name($listenSock, false);
1436    // PHP is terrible and returns IPv6 addresses not enclosed by []
1437    $portPos = strrpos($sockName, ":");
1438    $sockHost = substr($sockName, 0, $portPos);
1439    if (false !== strpos($sockHost, ":")) {
1440        $sockHost = "[$sockHost]";
1441    }
1442    $sockPort = substr($sockName, $portPos + 1);
1443    $sockUri = "tcp://$sockHost:$sockPort";
1444    $totalFileCount = count($test_files);
1445
1446    $startTime = microtime(true);
1447    for ($i = 1; $i <= $workers; $i++) {
1448        $proc = proc_open(
1449            $thisPHP . ' ' . escapeshellarg($thisScript),
1450            array(), // Inherit our stdin, stdout and stderr
1451            $pipes,
1452            null,
1453            $_ENV + array(
1454                "TEST_PHP_WORKER" => $i,
1455                "TEST_PHP_URI" => $sockUri,
1456			),
1457            array(
1458                "suppress_errors" => true,
1459                'create_new_console' => true,
1460			)
1461        );
1462        if ($proc === false) {
1463            kill_children($workerProcs);
1464            error("Failed to spawn worker $i");
1465        }
1466        $workerProcs[$i] = $proc;
1467    }
1468
1469    for ($i = 1; $i <= $workers; $i++) {
1470        $workerSock = stream_socket_accept($listenSock, 5);
1471        if ($workerSock === false) {
1472            kill_children($workerProcs);
1473            error("Failed to accept connection from worker.");
1474        }
1475
1476        $greeting = base64_encode(serialize(array(
1477            "type" => "hello",
1478            "GLOBALS" => $GLOBALS,
1479            "constants" => array(
1480                "INIT_DIR" => INIT_DIR,
1481                "TEST_PHP_SRCDIR" => TEST_PHP_SRCDIR,
1482                "PHP_QA_EMAIL" => PHP_QA_EMAIL,
1483                "QA_SUBMISSION_PAGE" => QA_SUBMISSION_PAGE,
1484                "QA_REPORTS_PAGE" => QA_REPORTS_PAGE,
1485                "TRAVIS_CI" => TRAVIS_CI
1486			)
1487		))) . "\n";
1488
1489        stream_set_timeout($workerSock, 5);
1490        if (fwrite($workerSock, $greeting) === false) {
1491            kill_children($workerProcs);
1492            error("Failed to send greeting to worker.");
1493        }
1494
1495        $rawReply = fgets($workerSock);
1496        if ($rawReply === false) {
1497            kill_children($workerProcs);
1498            error("Failed to read greeting reply from worker.");
1499        }
1500
1501        $reply = unserialize(base64_decode($rawReply));
1502        if (!$reply || $reply["type"] !== "hello_reply") {
1503            kill_children($workerProcs);
1504            error("Greeting reply from worker unexpected or could not be decoded: '$rawReply'");
1505        }
1506
1507        stream_set_timeout($workerSock, 0);
1508        stream_set_blocking($workerSock, false);
1509
1510        $workerID = $reply["workerID"];
1511        $workerSocks[$workerID] = $workerSock;
1512    }
1513    printf("Done in %.2fs\n", microtime(true) - $startTime);
1514    echo "=====================================================================\n";
1515    echo "\n";
1516
1517    $rawMessageBuffers = array();
1518    $testsInProgress = 0;
1519
1520    // Map from conflict key to worker ID.
1521    $activeConflicts = array();
1522    // Tests waiting due to conflicts. Map from conflict key to array.
1523    $waitingTests = array();
1524
1525escape:
1526    while ($test_files || $sequentialTests || $testsInProgress > 0) {
1527        $toRead = array_values($workerSocks);
1528        $toWrite = null;
1529        $toExcept = null;
1530        if (stream_select($toRead, $toWrite, $toExcept, 10)) {
1531            foreach ($toRead as $workerSock) {
1532                $i = array_search($workerSock, $workerSocks);
1533                if ($i === false) {
1534                    kill_children($workerProcs);
1535                    error("Could not find worker stdout in array of worker stdouts, THIS SHOULD NOT HAPPEN.");
1536                }
1537                while (false !== ($rawMessage = fgets($workerSock))) {
1538                    // work around fgets truncating things
1539                    if ((empty($rawMessageBuffers[$i]) ? '' : $rawMessageBuffers[$i]) !== '') {
1540                        $rawMessage = $rawMessageBuffers[$i] . $rawMessage;
1541                        $rawMessageBuffers[$i] = '';
1542                    }
1543                    if (substr($rawMessage, -1) !== "\n") {
1544                        $rawMessageBuffers[$i] = $rawMessage;
1545                        continue;
1546                    }
1547
1548                    $message = unserialize(base64_decode($rawMessage));
1549                    if (!$message) {
1550                        kill_children($workerProcs);
1551                        $stuff = fread($workerSock, 65536);
1552                        error("Could not decode message from worker $i: '$rawMessage$stuff'");
1553                    }
1554
1555                    switch ($message["type"]) {
1556                        case "tests_finished":
1557                            $testsInProgress--;
1558                            foreach ($activeConflicts as $key => $workerId) {
1559                                if ($workerId === $i) {
1560                                    unset($activeConflicts[$key]);
1561                                    if (isset($waitingTests[$key])) {
1562                                        while ($test = array_pop($waitingTests[$key])) {
1563                                            $test_files[] = $test;
1564                                        }
1565                                        unset($waitingTests[$key]);
1566                                    }
1567                                }
1568                            }
1569                            if (junit_enabled()) {
1570                                junit_merge_results($message["junit"]);
1571                            }
1572                            // no break
1573                        case "ready":
1574                            // Schedule sequential tests only once we are down to one worker.
1575                            if (count($workerProcs) === 1 && $sequentialTests) {
1576                                $test_files = array_merge($test_files, $sequentialTests);
1577                                $sequentialTests = array();
1578                            }
1579                            // Batch multiple tests to reduce communication overhead.
1580                            // - When valgrind is used, communication overhead is relatively small,
1581                            //   so just use a batch size of 1.
1582                            // - If this is running a small enough number of tests,
1583                            //   reduce the batch size to give batches to more workers.
1584                            $files = array();
1585                            $maxBatchSize = $valgrind ? 1 : ($shuffle ? 4 : 32);
1586                            $averageFilesPerWorker = max(1, (int) ceil($totalFileCount / count($workerProcs)));
1587                            $batchSize = min($maxBatchSize, $averageFilesPerWorker);
1588                            while (count($files) <= $batchSize && $file = array_pop($test_files)) {
1589                                foreach ($fileConflictsWith[$file] as $conflictKey) {
1590                                    if (isset($activeConflicts[$conflictKey])) {
1591                                        $waitingTests[$conflictKey][] = $file;
1592                                        continue 2;
1593                                    }
1594                                }
1595                                $files[] = $file;
1596                            }
1597                            if ($files) {
1598                                foreach ($files as $file) {
1599                                    foreach ($fileConflictsWith[$file] as $conflictKey) {
1600                                        $activeConflicts[$conflictKey] = $i;
1601                                    }
1602                                }
1603                                $testsInProgress++;
1604                                send_message($workerSocks[$i], array(
1605                                    "type" => "run_tests",
1606                                    "test_files" => $files,
1607                                    "env" => $env,
1608                                    "redir_tested" => $redir_tested
1609								));
1610                            } else {
1611                                proc_terminate($workerProcs[$i]);
1612                                unset($workerProcs[$i]);
1613                                unset($workerSocks[$i]);
1614                                goto escape;
1615                            }
1616                            break;
1617                        case "test_result":
1618                            list($name, $index, $result, $resultText) = array($message["name"], $message["index"], $message["result"], $message["text"]);
1619                            foreach ($message["PHP_FAILED_TESTS"] as $category => $tests) {
1620                                $PHP_FAILED_TESTS[$category] = array_merge($PHP_FAILED_TESTS[$category], $tests);
1621                            }
1622                            $test_idx++;
1623
1624                            if (!$SHOW_ONLY_GROUPS) {
1625                                clear_show_test();
1626                            }
1627
1628                            echo $resultText;
1629
1630                            if (!$SHOW_ONLY_GROUPS) {
1631                                show_test($test_idx, count($workerProcs) . "/$workers concurrent test workers running");
1632                            }
1633
1634                            if (!is_array($name) && $result != 'REDIR') {
1635                                $test_results[$index] = $result;
1636
1637                                if ($failed_tests_file && ($result == 'XFAILED' || $result == 'XLEAKED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) {
1638                                    fwrite($failed_tests_file, "$index\n");
1639                                }
1640                                if ($result_tests_file) {
1641                                    fwrite($result_tests_file, "$result\t$index\n");
1642                                }
1643                            }
1644                            break;
1645                        case "error":
1646                            kill_children($workerProcs);
1647                            error("Worker $i reported error: $message[msg]");
1648                            break;
1649                        case "php_error":
1650                            kill_children($workerProcs);
1651                            $error_consts = array(
1652                                'E_ERROR',
1653                                'E_WARNING',
1654                                'E_PARSE',
1655                                'E_NOTICE',
1656                                'E_CORE_ERROR',
1657                                'E_CORE_WARNING',
1658                                'E_COMPILE_ERROR',
1659                                'E_COMPILE_WARNING',
1660                                'E_USER_ERROR',
1661                                'E_USER_WARNING',
1662                                'E_USER_NOTICE',
1663                                'E_STRICT', // TODO Cleanup when removed from Zend Engine.
1664                                'E_RECOVERABLE_ERROR',
1665                                'E_USER_DEPRECATED'
1666							);
1667                            $error_consts = array_combine(array_map('constant', $error_consts), $error_consts);
1668                            error("Worker $i reported unexpected {$error_consts[$message['errno']]}: $message[errstr] in $message[errfile] on line $message[errline]");
1669                            // no break
1670                        default:
1671                            kill_children($workerProcs);
1672                            error("Unrecognised message type '$message[type]' from worker $i");
1673                    }
1674                }
1675            }
1676        }
1677    }
1678
1679    if (!$SHOW_ONLY_GROUPS) {
1680        clear_show_test();
1681    }
1682
1683    kill_children($workerProcs);
1684
1685    if ($testsInProgress < 0) {
1686        error("$testsInProgress test batches “in progress”, which is less than zero. THIS SHOULD NOT HAPPEN.");
1687    }
1688}
1689
1690function send_message($stream, array $message)
1691{
1692	$blocking = stream_get_meta_data($stream);
1693	$blocking = $blocking["blocked"];
1694    stream_set_blocking($stream, true);
1695    fwrite($stream, base64_encode(serialize($message)) . "\n");
1696    stream_set_blocking($stream, $blocking);
1697}
1698
1699function kill_children(array $children)
1700{
1701    foreach ($children as $child) {
1702        if ($child) {
1703            proc_terminate($child);
1704        }
1705    }
1706}
1707
1708function run_worker()
1709{
1710    global $workerID, $workerSock;
1711
1712    $sockUri = getenv("TEST_PHP_URI");
1713
1714    $workerSock = stream_socket_client($sockUri, $_, $_, 5) or error("Couldn't connect to $sockUri");
1715
1716    $greeting = fgets($workerSock);
1717    $greeting = unserialize(base64_decode($greeting)) or die("Could not decode greeting\n");
1718    if ($greeting["type"] !== "hello") {
1719        error("Unexpected greeting of type $greeting[type]");
1720    }
1721
1722    set_error_handler(function ($errno, $errstr, $errfile, $errline) use ($workerSock) {
1723        if (error_reporting() & $errno) {
1724            send_message($workerSock, compact('errno', 'errstr', 'errfile', 'errline') + array(
1725                'type' => 'php_error'
1726			));
1727        }
1728
1729        return true;
1730    });
1731
1732    foreach ($greeting["GLOBALS"] as $var => $value) {
1733        if ($var !== "workerID" && $var !== "workerSock" && $var !== "GLOBALS") {
1734            $GLOBALS[$var] = $value;
1735        }
1736    }
1737    foreach ($greeting["constants"] as $const => $value) {
1738        define($const, $value);
1739    }
1740
1741    send_message($workerSock, array(
1742        "type" => "hello_reply",
1743        "workerID" => $workerID
1744	));
1745
1746    send_message($workerSock, array(
1747        "type" => "ready"
1748	));
1749
1750    while (($command = fgets($workerSock))) {
1751        $command = unserialize(base64_decode($command));
1752
1753        switch ($command["type"]) {
1754            case "run_tests":
1755                run_all_tests($command["test_files"], $command["env"], $command["redir_tested"]);
1756                send_message($workerSock, array(
1757                    "type" => "tests_finished",
1758                    "junit" => junit_enabled() ? $GLOBALS['JUNIT'] : null,
1759				));
1760                junit_init();
1761                break;
1762            default:
1763                send_message($workerSock, array(
1764                    "type" => "error",
1765                    "msg" => "Unrecognised message type: $command[type]"
1766				));
1767                break 2;
1768        }
1769    }
1770}
1771
1772//
1773//  Show file or result block
1774//
1775function show_file_block($file, $block, $section = null)
1776{
1777    global $cfg;
1778    global $colorize;
1779
1780    if ($cfg['show'][$file]) {
1781        if (is_null($section)) {
1782            $section = strtoupper($file);
1783        }
1784        if ($section === 'DIFF' && $colorize) {
1785            // '-' is Light Red for removal, '+' is Light Green for addition
1786            $block = preg_replace('/^[0-9]+\-\s.*$/m', "\x1B[1;31m\\0\x1B[0m", $block);
1787            $block = preg_replace('/^[0-9]+\+\s.*$/m', "\x1B[1;32m\\0\x1B[0m", $block);
1788        }
1789
1790        echo "\n========" . $section . "========\n";
1791        echo rtrim($block);
1792        echo "\n========DONE========\n";
1793    }
1794}
1795
1796function skip_test($tested, $tested_file, $shortname, $reason) {
1797    show_result('SKIP', $tested, $tested_file, "reason: $reason");
1798    junit_init_suite(junit_get_suitename_for($shortname));
1799    junit_mark_test_as('SKIP', $shortname, $tested, 0, $reason);
1800    return 'SKIPPED';
1801}
1802
1803//
1804//  Run an individual test case.
1805//
1806/**
1807 * @param string|array $file
1808 */
1809function run_test($php, $file, array $env)
1810{
1811    global $log_format, $ini_overwrites, $PHP_FAILED_TESTS;
1812    global $pass_options, $DETAILED, $IN_REDIRECT, $test_cnt, $test_idx;
1813    global $valgrind, $temp_source, $temp_target, $cfg, $environment;
1814    global $no_clean;
1815    global $SHOW_ONLY_GROUPS;
1816    global $no_file_cache;
1817    global $slow_min_ms;
1818    global $preload, $file_cache;
1819    global $num_repeats;
1820    // Parallel testing
1821    global $workerID;
1822    $temp_filenames = null;
1823    $org_file = $file;
1824
1825    if (isset($env['TEST_PHP_CGI_EXECUTABLE'])) {
1826        $php_cgi = $env['TEST_PHP_CGI_EXECUTABLE'];
1827    }
1828
1829    if (isset($env['TEST_PHPDBG_EXECUTABLE'])) {
1830        $phpdbg = $env['TEST_PHPDBG_EXECUTABLE'];
1831    }
1832
1833    if (is_array($file)) {
1834        $file = $file[0];
1835    }
1836
1837    if ($DETAILED) {
1838        echo "
1839=================
1840TEST $file
1841";
1842    }
1843
1844    // Load the sections of the test file.
1845    $section_text = array('TEST' => '');
1846
1847    $fp = fopen($file, "rb") or error("Cannot open test file: $file");
1848
1849    $bork_info = null;
1850
1851    if (!feof($fp)) {
1852        $line = fgets($fp);
1853
1854        if ($line === false) {
1855            $bork_info = "cannot read test";
1856        }
1857    } else {
1858        $bork_info = "empty test [$file]";
1859    }
1860    if ($bork_info === null && strncmp('--TEST--', $line, 8)) {
1861        $bork_info = "tests must start with --TEST-- [$file]";
1862    }
1863
1864    $section = 'TEST';
1865    $secfile = false;
1866    $secdone = false;
1867
1868    while (!feof($fp)) {
1869        $line = fgets($fp);
1870
1871        if ($line === false) {
1872            break;
1873        }
1874
1875        // Match the beginning of a section.
1876        if (preg_match('/^--([_A-Z]+)--/', $line, $r)) {
1877            $section = (string) $r[1];
1878
1879            if (isset($section_text[$section]) && $section_text[$section]) {
1880                $bork_info = "duplicated $section section";
1881            }
1882
1883            // check for unknown sections
1884            if (!in_array($section, array(
1885                'EXPECT', 'EXPECTF', 'EXPECTREGEX', 'EXPECTREGEX_EXTERNAL', 'EXPECT_EXTERNAL', 'EXPECTF_EXTERNAL', 'EXPECTHEADERS',
1886				'EXPECTREGEX_DYNAMIC', 'EXPECT_DYNAMIC', 'EXPECTF_DYNAMIC',
1887                'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS',
1888                'FILE', 'FILEEOF', 'FILE_EXTERNAL', 'REDIRECTTEST',
1889                'CAPTURE_STDIO', 'STDIN', 'CGI', 'PHPDBG',
1890                'INI', 'ENV', 'EXTENSIONS',
1891                'SKIPIF', 'XFAIL', 'XLEAK', 'CLEAN',
1892                'CREDITS', 'DESCRIPTION', 'CONFLICTS', 'WHITESPACE_SENSITIVE',
1893			))) {
1894                $bork_info = 'Unknown section "' . $section . '"';
1895            }
1896
1897            $section_text[$section] = '';
1898            $secfile = $section == 'FILE' || $section == 'FILEEOF' || $section == 'FILE_EXTERNAL';
1899            $secdone = false;
1900            continue;
1901        }
1902
1903        // Add to the section text.
1904        if (!$secdone) {
1905            $section_text[$section] .= $line;
1906        }
1907
1908        // End of actual test?
1909        if ($secfile && preg_match('/^===DONE===\s*$/', $line)) {
1910            $secdone = true;
1911        }
1912    }
1913
1914    $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file);
1915    $tested_file = $shortname;
1916    $tested = trim($section_text['TEST']);
1917
1918    // the redirect section allows a set of tests to be reused outside of
1919    // a given test dir
1920    if ($bork_info === null) {
1921        if (isset($section_text['REDIRECTTEST'])) {
1922            if ($IN_REDIRECT) {
1923                $bork_info = "Can't redirect a test from within a redirected test";
1924            }
1925        } else {
1926            if (!isset($section_text['PHPDBG']) && isset($section_text['FILE']) + isset($section_text['FILEEOF']) + isset($section_text['FILE_EXTERNAL']) != 1) {
1927                $bork_info = "missing section --FILE--";
1928            }
1929
1930            if (isset($section_text['FILEEOF'])) {
1931                $section_text['FILE'] = preg_replace("/[\r\n]+$/", '', $section_text['FILEEOF']);
1932                unset($section_text['FILEEOF']);
1933            }
1934
1935            if ($num_repeats > 1 && isset($section_text['FILE_EXTERNAL'])) {
1936                return skip_test($tested, $tested_file, $shortname, 'Test with FILE_EXTERNAL might not be repeatable');
1937            }
1938
1939            foreach (array('FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX') as $prefix) {
1940                $key = $prefix . '_EXTERNAL';
1941
1942                if (isset($section_text[$key])) {
1943                    // don't allow tests to retrieve files from anywhere but this subdirectory
1944                    $section_text[$key] = dirname($file) . '/' . trim(str_replace('..', '', $section_text[$key]));
1945
1946                    if (file_exists($section_text[$key])) {
1947                        $section_text[$prefix] = file_get_contents($section_text[$key]);
1948                        unset($section_text[$key]);
1949                    } else {
1950                        $bork_info = "could not load --" . $key . "-- " . dirname($file) . '/' . trim($section_text[$key]);
1951                    }
1952                }
1953            }
1954            foreach (array('EXPECT', 'EXPECTF', 'EXPECTREGEX') as $prefix) {
1955				$key = $prefix . '_DYNAMIC';
1956				if (isset($section_text[$key])) {
1957					global $php;
1958					$temp_file = tmpfile();
1959					fwrite($temp_file, $section_text[$key]);
1960					fflush($temp_file);
1961					$temp_file_name = stream_get_meta_data($temp_file);
1962					$temp_file_name = $temp_file_name['uri'];
1963					$output = system_with_timeout("$php -n -d display_errors=1 -d display_startup_errors=0 \"$temp_file_name\"", array());
1964					$output = trim($output);
1965					$section_text[$prefix] = $output;
1966					unset($section_text[$key]);
1967				}
1968			}
1969
1970            if ((isset($section_text['EXPECT']) + isset($section_text['EXPECTF']) + isset($section_text['EXPECTREGEX'])) != 1) {
1971                $bork_info = "missing section --EXPECT--, --EXPECTF-- or --EXPECTREGEX--";
1972            }
1973        }
1974    }
1975    fclose($fp);
1976
1977    if ($bork_info !== null) {
1978        show_result("BORK", $bork_info, $tested_file);
1979        $PHP_FAILED_TESTS['BORKED'][] = array(
1980            'name' => $file,
1981            'test_name' => '',
1982            'output' => '',
1983            'diff' => '',
1984            'info' => "$bork_info [$file]",
1985		);
1986
1987        junit_mark_test_as('BORK', $shortname, $tested_file, 0, $bork_info);
1988        return 'BORKED';
1989    }
1990
1991    if (isset($section_text['CAPTURE_STDIO'])) {
1992        $captureStdIn = stripos($section_text['CAPTURE_STDIO'], 'STDIN') !== false;
1993        $captureStdOut = stripos($section_text['CAPTURE_STDIO'], 'STDOUT') !== false;
1994        $captureStdErr = stripos($section_text['CAPTURE_STDIO'], 'STDERR') !== false;
1995    } else {
1996        $captureStdIn = true;
1997        $captureStdOut = true;
1998        $captureStdErr = true;
1999    }
2000    if ($captureStdOut && $captureStdErr) {
2001        $cmdRedirect = ' 2>&1';
2002    } else {
2003        $cmdRedirect = '';
2004    }
2005
2006    /* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */
2007    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'])) {
2008        if (!$php_cgi) {
2009            return skip_test($tested, $tested_file, $shortname, 'CGI not available');
2010        }
2011        $php = $php_cgi . ' -C ';
2012        $uses_cgi = true;
2013        if ($num_repeats > 1) {
2014            return skip_test($tested, $tested_file, $shortname, 'CGI does not support --repeat');
2015        }
2016    }
2017
2018    /* For phpdbg tests, check if phpdbg sapi is available and if it is, use it. */
2019    $extra_options = '';
2020    if (array_key_exists('PHPDBG', $section_text)) {
2021        if (!isset($section_text['STDIN'])) {
2022            $section_text['STDIN'] = $section_text['PHPDBG'] . "\n";
2023        }
2024
2025        if (isset($phpdbg)) {
2026            $php = $phpdbg . ' -qIb';
2027
2028            // Additional phpdbg command line options for sections that need to
2029            // be run straight away. For example, EXTENSIONS, SKIPIF, CLEAN.
2030            $extra_options = '-rr';
2031        } else {
2032            return skip_test($tested, $tested_file, $shortname, 'phpdbg not available');
2033        }
2034        if ($num_repeats > 1) {
2035            return skip_test($tested, $tested_file, $shortname, 'phpdbg does not support --repeat');
2036        }
2037    }
2038
2039    if ($num_repeats > 1) {
2040        if (array_key_exists('CLEAN', $section_text)) {
2041            return skip_test($tested, $tested_file, $shortname, 'Test with CLEAN might not be repeatable');
2042        }
2043        if (array_key_exists('STDIN', $section_text)) {
2044            return skip_test($tested, $tested_file, $shortname, 'Test with STDIN might not be repeatable');
2045        }
2046        if (array_key_exists('CAPTURE_STDIO', $section_text)) {
2047            return skip_test($tested, $tested_file, $shortname, 'Test with CAPTURE_STDIO might not be repeatable');
2048        }
2049    }
2050
2051    if (!$SHOW_ONLY_GROUPS && !$workerID) {
2052        show_test($test_idx, $shortname);
2053    }
2054
2055    if (is_array($IN_REDIRECT)) {
2056        $temp_dir = $test_dir = $IN_REDIRECT['dir'];
2057    } else {
2058        $temp_dir = $test_dir = realpath(dirname($file));
2059    }
2060
2061    if ($temp_source && $temp_target) {
2062        $temp_dir = str_replace($temp_source, $temp_target, $temp_dir);
2063    }
2064
2065    $main_file_name = basename($file, 'phpt');
2066
2067    $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'diff';
2068    $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'log';
2069    $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'exp';
2070    $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'out';
2071    $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'mem';
2072    $sh_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'sh';
2073    $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php';
2074    $test_file = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php';
2075    $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php';
2076    $test_skipif = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php';
2077    $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php';
2078    $test_clean = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php';
2079    $preload_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'preload.php';
2080    $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'post';
2081    $tmp_relative_file = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $test_file) . 't';
2082
2083    if ($temp_source && $temp_target) {
2084        $temp_skipif .= 's';
2085        $temp_file .= 's';
2086        $temp_clean .= 's';
2087        $copy_file = $temp_dir . DIRECTORY_SEPARATOR . basename(is_array($file) ? $file[1] : $file) . '.phps';
2088
2089        if (!is_dir(dirname($copy_file))) {
2090            mkdir(dirname($copy_file), 0777, true) or error("Cannot create output directory - " . dirname($copy_file));
2091        }
2092
2093        if (isset($section_text['FILE'])) {
2094            save_text($copy_file, $section_text['FILE']);
2095        }
2096
2097        $temp_filenames = array(
2098            'file' => $copy_file,
2099            'diff' => $diff_filename,
2100            'log' => $log_filename,
2101            'exp' => $exp_filename,
2102            'out' => $output_filename,
2103            'mem' => $memcheck_filename,
2104            'sh' => $sh_filename,
2105            'php' => $temp_file,
2106            'skip' => $temp_skipif,
2107            'clean' => $temp_clean
2108		);
2109    }
2110
2111    if (is_array($IN_REDIRECT)) {
2112        $tested = $IN_REDIRECT['prefix'] . ' ' . trim($section_text['TEST']);
2113        $tested_file = $tmp_relative_file;
2114        $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $tested_file);
2115    }
2116
2117    // unlink old test results
2118    @unlink($diff_filename);
2119    @unlink($log_filename);
2120    @unlink($exp_filename);
2121    @unlink($output_filename);
2122    @unlink($memcheck_filename);
2123    @unlink($sh_filename);
2124    @unlink($temp_file);
2125    @unlink($test_file);
2126    @unlink($temp_skipif);
2127    @unlink($test_skipif);
2128    @unlink($tmp_post);
2129    @unlink($temp_clean);
2130    @unlink($test_clean);
2131    @unlink($preload_filename);
2132
2133    // Reset environment from any previous test.
2134    $env['REDIRECT_STATUS'] = '';
2135    $env['QUERY_STRING'] = '';
2136    $env['PATH_TRANSLATED'] = '';
2137    $env['SCRIPT_FILENAME'] = '';
2138    $env['REQUEST_METHOD'] = '';
2139    $env['CONTENT_TYPE'] = '';
2140    $env['CONTENT_LENGTH'] = '';
2141    $env['TZ'] = '';
2142
2143    if (!empty($section_text['ENV'])) {
2144        foreach (explode("\n", trim($section_text['ENV'])) as $e) {
2145            $e = explode('=', trim($e), 2);
2146
2147            if (!empty($e[0]) && isset($e[1])) {
2148                $env[$e[0]] = $e[1];
2149            }
2150        }
2151    }
2152
2153    // Default ini settings
2154    $ini_settings = $workerID ? array('opcache.cache_id' => "worker$workerID") : array();
2155
2156    // Additional required extensions
2157    if (array_key_exists('EXTENSIONS', $section_text)) {
2158        $ext_params = array();
2159        settings2array($ini_overwrites, $ext_params);
2160        $ext_params = settings2params($ext_params);
2161        $ext_dir = `$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r "echo ini_get('extension_dir');"`;
2162        $extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS']));
2163        $loaded = explode(",", `$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r "echo implode(',', get_loaded_extensions());"`);
2164        $ext_prefix = IS_WINDOWS ? "php_" : "";
2165        foreach ($extensions as $req_ext) {
2166            if (!in_array($req_ext, $loaded)) {
2167                if ($req_ext == 'opcache') {
2168                    $ini_settings['zend_extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX;
2169                } else {
2170                    $ini_settings['extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX;
2171                }
2172            }
2173        }
2174    }
2175
2176    // additional ini overwrites
2177    //$ini_overwrites[] = 'setting=value';
2178    settings2array($ini_overwrites, $ini_settings);
2179
2180    $orig_ini_settings = settings2params($ini_settings);
2181
2182    if ($file_cache !== null) {
2183        $ini_settings['opcache.file_cache'] = '/tmp';
2184        // Make sure warnings still show up on the second run.
2185        $ini_settings['opcache.record_warnings'] = '1';
2186        // File cache is currently incompatible with JIT.
2187        $ini_settings['opcache.jit'] = '0';
2188        if ($file_cache === 'use') {
2189            // Disable timestamp validation in order to fetch from file cache,
2190            // even though all the files are re-created.
2191            $ini_settings['opcache.validate_timestamps'] = '0';
2192        }
2193    } else if ($num_repeats > 1) {
2194        // Make sure warnings still show up on the second run.
2195        $ini_settings['opcache.record_warnings'] = '1';
2196    }
2197
2198    // Any special ini settings
2199    // these may overwrite the test defaults...
2200    if (array_key_exists('INI', $section_text)) {
2201        $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']);
2202        $section_text['INI'] = str_replace('{TMP}', sys_get_temp_dir(), $section_text['INI']);
2203        $replacement = IS_WINDOWS ? '"' . PHP_BINARY . ' -r \"while ($in = fgets(STDIN)) echo $in;\" > $1"' : 'tee $1 >/dev/null';
2204        $section_text['INI'] = preg_replace('/{MAIL:(\S+)}/', $replacement, $section_text['INI']);
2205        settings2array(preg_split("/[\n\r]+/", $section_text['INI']), $ini_settings);
2206
2207        if ($num_repeats > 1 && isset($ini_settings['opcache.opt_debug_level'])) {
2208            return skip_test($tested, $tested_file, $shortname, 'opt_debug_level tests are not repeatable');
2209        }
2210    }
2211
2212    $ini_settings = settings2params($ini_settings);
2213
2214    $env['TEST_PHP_EXTRA_ARGS'] = $pass_options . ' ' . $ini_settings;
2215
2216    // Check if test should be skipped.
2217    $info = '';
2218    $warn = false;
2219
2220    if (array_key_exists('SKIPIF', $section_text)) {
2221        if (trim($section_text['SKIPIF'])) {
2222            show_file_block('skip', $section_text['SKIPIF']);
2223            save_text($test_skipif, $section_text['SKIPIF'], $temp_skipif);
2224            $extra = !IS_WINDOWS ?
2225                "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : "";
2226
2227            if ($valgrind) {
2228                $env['USE_ZEND_ALLOC'] = '0';
2229                $env['ZEND_DONT_UNLOAD_MODULES'] = 1;
2230            }
2231
2232            junit_start_timer($shortname);
2233
2234            $output = system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache -d display_errors=1 -d display_startup_errors=0 \"$test_skipif\"", $env);
2235            $output = trim($output);
2236
2237            junit_finish_timer($shortname);
2238
2239            if (!$cfg['keep']['skip']) {
2240                @unlink($test_skipif);
2241            }
2242
2243            if (!strncasecmp('skip', $output, 4)) {
2244                if (preg_match('/^skip\s*(.+)/i', $output, $m)) {
2245                    show_result('SKIP', $tested, $tested_file, "reason: $m[1]", $temp_filenames);
2246                } else {
2247                    show_result('SKIP', $tested, $tested_file, '', $temp_filenames);
2248                }
2249
2250                if (!$cfg['keep']['skip']) {
2251                    @unlink($test_skipif);
2252                }
2253
2254                $message = !empty($m[1]) ? $m[1] : '';
2255                junit_mark_test_as('SKIP', $shortname, $tested, null, $message);
2256                return 'SKIPPED';
2257            }
2258
2259            if (!strncasecmp('info', $output, 4) && preg_match('/^info\s*(.+)/i', $output, $m)) {
2260                $info = " (info: $m[1])";
2261            } elseif (!strncasecmp('warn', $output, 4) && preg_match('/^warn\s+(.+)/i', $output, $m)) {
2262                $warn = true; /* only if there is a reason */
2263                $info = " (warn: $m[1])";
2264            } elseif (!strncasecmp('xfail', $output, 5)) {
2265                // Pretend we have an XFAIL section
2266                $section_text['XFAIL'] = ltrim(substr($output, 5));
2267            } elseif ($output !== '') {
2268                show_result("BORK", $output, $tested_file, 'reason: invalid output from SKIPIF', $temp_filenames);
2269                $PHP_FAILED_TESTS['BORKED'][] = array(
2270                    'name' => $file,
2271                    'test_name' => '',
2272                    'output' => '',
2273                    'diff' => '',
2274                    'info' => "$output [$file]",
2275				);
2276
2277                junit_mark_test_as('BORK', $shortname, $tested, null, $output);
2278                return 'BORKED';
2279            }
2280        }
2281    }
2282
2283    if (!extension_loaded("zlib")
2284        && (array_key_exists("GZIP_POST", $section_text)
2285        || array_key_exists("DEFLATE_POST", $section_text))) {
2286        $message = "ext/zlib required";
2287        show_result('SKIP', $tested, $tested_file, "reason: $message", $temp_filenames);
2288        junit_mark_test_as('SKIP', $shortname, $tested, null, $message);
2289        return 'SKIPPED';
2290    }
2291
2292    if (isset($section_text['REDIRECTTEST'])) {
2293        $test_files = array();
2294
2295        $IN_REDIRECT = eval($section_text['REDIRECTTEST']);
2296        $IN_REDIRECT['via'] = "via [$shortname]\n\t";
2297        $IN_REDIRECT['dir'] = realpath(dirname($file));
2298        $IN_REDIRECT['prefix'] = trim($section_text['TEST']);
2299
2300        if (!empty($IN_REDIRECT['TESTS'])) {
2301            if (is_array($org_file)) {
2302                $test_files[] = $org_file[1];
2303            } else {
2304                $GLOBALS['test_files'] = $test_files;
2305                find_files($IN_REDIRECT['TESTS']);
2306
2307                foreach ($GLOBALS['test_files'] as $f) {
2308                    $test_files[] = array($f, $file);
2309                }
2310            }
2311            $test_cnt += count($test_files) - 1;
2312            $test_idx--;
2313
2314            show_redirect_start($IN_REDIRECT['TESTS'], $tested, $tested_file);
2315
2316            // set up environment
2317            $redirenv = array_merge($environment, $IN_REDIRECT['ENV']);
2318            $redirenv['REDIR_TEST_DIR'] = realpath($IN_REDIRECT['TESTS']) . DIRECTORY_SEPARATOR;
2319
2320            usort($test_files, "test_sort");
2321            run_all_tests($test_files, $redirenv, $tested);
2322
2323            show_redirect_ends($IN_REDIRECT['TESTS'], $tested, $tested_file);
2324
2325            // a redirected test never fails
2326            $IN_REDIRECT = false;
2327
2328            junit_mark_test_as('PASS', $shortname, $tested);
2329            return 'REDIR';
2330        } else {
2331            $bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory.";
2332            show_result("BORK", $bork_info, '', '', $temp_filenames);
2333            $PHP_FAILED_TESTS['BORKED'][] = array(
2334                'name' => $file,
2335                'test_name' => '',
2336                'output' => '',
2337                'diff' => '',
2338                'info' => "$bork_info [$file]",
2339			);
2340        }
2341    }
2342
2343    if (is_array($org_file) || isset($section_text['REDIRECTTEST'])) {
2344        if (is_array($org_file)) {
2345            $file = $org_file[0];
2346        }
2347
2348        $bork_info = "Redirected test did not contain redirection info";
2349        show_result("BORK", $bork_info, '', '', $temp_filenames);
2350        $PHP_FAILED_TESTS['BORKED'][] = array(
2351            'name' => $file,
2352            'test_name' => '',
2353            'output' => '',
2354            'diff' => '',
2355            'info' => "$bork_info [$file]",
2356		);
2357
2358        junit_mark_test_as('BORK', $shortname, $tested, null, $bork_info);
2359
2360        return 'BORKED';
2361    }
2362
2363    // We've satisfied the preconditions - run the test!
2364    if (isset($section_text['FILE'])) {
2365        show_file_block('php', $section_text['FILE'], 'TEST');
2366        save_text($test_file, $section_text['FILE'], $temp_file);
2367    } else {
2368        $test_file = $temp_file = "";
2369    }
2370
2371    if (array_key_exists('GET', $section_text)) {
2372        $query_string = trim($section_text['GET']);
2373    } else {
2374        $query_string = '';
2375    }
2376
2377    $env['REDIRECT_STATUS'] = '1';
2378    if (empty($env['QUERY_STRING'])) {
2379        $env['QUERY_STRING'] = $query_string;
2380    }
2381    if (empty($env['PATH_TRANSLATED'])) {
2382        $env['PATH_TRANSLATED'] = $test_file;
2383    }
2384    if (empty($env['SCRIPT_FILENAME'])) {
2385        $env['SCRIPT_FILENAME'] = $test_file;
2386    }
2387
2388    if (array_key_exists('COOKIE', $section_text)) {
2389        $env['HTTP_COOKIE'] = trim($section_text['COOKIE']);
2390    } else {
2391        $env['HTTP_COOKIE'] = '';
2392    }
2393
2394    $args = isset($section_text['ARGS']) ? ' -- ' . $section_text['ARGS'] : '';
2395
2396    if ($preload && !empty($test_file)) {
2397        save_text($preload_filename, "<?php opcache_compile_file('$test_file');");
2398        $local_pass_options = $pass_options;
2399        unset($pass_options);
2400        $pass_options = $local_pass_options;
2401        $pass_options .= " -d opcache.preload=" . $preload_filename;
2402    }
2403
2404    if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) {
2405        $post = trim($section_text['POST_RAW']);
2406        $raw_lines = explode("\n", $post);
2407
2408        $request = '';
2409        $started = false;
2410
2411        foreach ($raw_lines as $line) {
2412            if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) {
2413                $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1]));
2414                continue;
2415            }
2416
2417            if ($started) {
2418                $request .= "\n";
2419            }
2420
2421            $started = true;
2422            $request .= $line;
2423        }
2424
2425        $env['CONTENT_LENGTH'] = strlen($request);
2426        $env['REQUEST_METHOD'] = 'POST';
2427
2428        if (empty($request)) {
2429            junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request');
2430            return 'BORKED';
2431        }
2432
2433        save_text($tmp_post, $request);
2434        $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";
2435    } elseif (array_key_exists('PUT', $section_text) && !empty($section_text['PUT'])) {
2436        $post = trim($section_text['PUT']);
2437        $raw_lines = explode("\n", $post);
2438
2439        $request = '';
2440        $started = false;
2441
2442        foreach ($raw_lines as $line) {
2443            if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) {
2444                $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1]));
2445                continue;
2446            }
2447
2448            if ($started) {
2449                $request .= "\n";
2450            }
2451
2452            $started = true;
2453            $request .= $line;
2454        }
2455
2456        $env['CONTENT_LENGTH'] = strlen($request);
2457        $env['REQUEST_METHOD'] = 'PUT';
2458
2459        if (empty($request)) {
2460            junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request');
2461            return 'BORKED';
2462        }
2463
2464        save_text($tmp_post, $request);
2465        $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";
2466    } elseif (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) {
2467        $post = trim($section_text['POST']);
2468        $content_length = strlen($post);
2469        save_text($tmp_post, $post);
2470
2471        $env['REQUEST_METHOD'] = 'POST';
2472        if (empty($env['CONTENT_TYPE'])) {
2473            $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
2474        }
2475
2476        if (empty($env['CONTENT_LENGTH'])) {
2477            $env['CONTENT_LENGTH'] = $content_length;
2478        }
2479
2480        $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";
2481    } elseif (array_key_exists('GZIP_POST', $section_text) && !empty($section_text['GZIP_POST'])) {
2482        $post = trim($section_text['GZIP_POST']);
2483        $post = gzencode($post, 9, FORCE_GZIP);
2484        $env['HTTP_CONTENT_ENCODING'] = 'gzip';
2485
2486        save_text($tmp_post, $post);
2487        $content_length = strlen($post);
2488
2489        $env['REQUEST_METHOD'] = 'POST';
2490        $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
2491        $env['CONTENT_LENGTH'] = $content_length;
2492
2493        $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";
2494    } elseif (array_key_exists('DEFLATE_POST', $section_text) && !empty($section_text['DEFLATE_POST'])) {
2495        $post = trim($section_text['DEFLATE_POST']);
2496        $post = gzcompress($post, 9);
2497        $env['HTTP_CONTENT_ENCODING'] = 'deflate';
2498        save_text($tmp_post, $post);
2499        $content_length = strlen($post);
2500
2501        $env['REQUEST_METHOD'] = 'POST';
2502        $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
2503        $env['CONTENT_LENGTH'] = $content_length;
2504
2505        $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";
2506    } else {
2507        $env['REQUEST_METHOD'] = 'GET';
2508        $env['CONTENT_TYPE'] = '';
2509        $env['CONTENT_LENGTH'] = '';
2510
2511        $repeat_option = $num_repeats > 1 ? "--repeat $num_repeats" : "";
2512        $cmd = "$php $pass_options $repeat_option $ini_settings -f \"$test_file\" $args$cmdRedirect";
2513    }
2514
2515    if ($valgrind) {
2516        $env['USE_ZEND_ALLOC'] = '0';
2517        $env['ZEND_DONT_UNLOAD_MODULES'] = 1;
2518
2519        $cmd = $valgrind->wrapCommand($cmd, $memcheck_filename, strpos($test_file, "pcre") !== false);
2520    }
2521
2522    if ($DETAILED) {
2523        echo "
2524CONTENT_LENGTH  = " . $env['CONTENT_LENGTH'] . "
2525CONTENT_TYPE    = " . $env['CONTENT_TYPE'] . "
2526PATH_TRANSLATED = " . $env['PATH_TRANSLATED'] . "
2527QUERY_STRING    = " . $env['QUERY_STRING'] . "
2528REDIRECT_STATUS = " . $env['REDIRECT_STATUS'] . "
2529REQUEST_METHOD  = " . $env['REQUEST_METHOD'] . "
2530SCRIPT_FILENAME = " . $env['SCRIPT_FILENAME'] . "
2531HTTP_COOKIE     = " . $env['HTTP_COOKIE'] . "
2532COMMAND $cmd
2533";
2534    }
2535
2536    junit_start_timer($shortname);
2537    $hrtime = hrtime();
2538    $startTime = $hrtime[0] * 1000000000 + $hrtime[1];
2539
2540    $out = system_with_timeout($cmd, $env, empty($section_text['STDIN']) ? null : $section_text['STDIN'], $captureStdIn, $captureStdOut, $captureStdErr);
2541
2542    junit_finish_timer($shortname);
2543    $hrtime = hrtime();
2544    $time = $hrtime[0] * 1000000000 + $hrtime[1] - $startTime;
2545    if ($time >= $slow_min_ms * 1000000) {
2546        $PHP_FAILED_TESTS['SLOW'][] = array(
2547            'name' => $file,
2548            'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]",
2549            'output' => '',
2550            'diff' => '',
2551            'info' => $time / 1000000000,
2552		);
2553    }
2554
2555    if (array_key_exists('CLEAN', $section_text) && (!$no_clean || $cfg['keep']['clean'])) {
2556        if (trim($section_text['CLEAN'])) {
2557            show_file_block('clean', $section_text['CLEAN']);
2558            save_text($test_clean, trim($section_text['CLEAN']), $temp_clean);
2559
2560            if (!$no_clean) {
2561                $extra = !IS_WINDOWS ?
2562                    "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : "";
2563                system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache \"$test_clean\"", $env);
2564            }
2565
2566            if (!$cfg['keep']['clean']) {
2567                @unlink($test_clean);
2568            }
2569        }
2570    }
2571
2572    @unlink($preload_filename);
2573
2574    $leaked = false;
2575    $passed = false;
2576
2577    if ($valgrind) { // leak check
2578        $leaked = filesize($memcheck_filename) > 0;
2579
2580        if (!$leaked) {
2581            @unlink($memcheck_filename);
2582        }
2583    }
2584
2585    if ($num_repeats > 1) {
2586        // In repeat mode, retain the output before the first execution,
2587        // and of the last execution. Do this early, because the trimming below
2588        // makes the newline handling complicated.
2589        $separator1 = "Executing for the first time...\n";
2590        $separator1_pos = strpos($out, $separator1);
2591        if ($separator1_pos !== false) {
2592            $separator2 = "Finished execution, repeating...\n";
2593            $separator2_pos = strrpos($out, $separator2);
2594            if ($separator2_pos !== false) {
2595                $out = substr($out, 0, $separator1_pos)
2596                     . substr($out, $separator2_pos + strlen($separator2));
2597            } else {
2598                $out = substr($out, 0, $separator1_pos)
2599                     . substr($out, $separator1_pos + strlen($separator1));
2600            }
2601        }
2602    }
2603
2604    // Does the output match what is expected?
2605    $output = preg_replace("/\r\n/", "\n", trim($out));
2606
2607    /* when using CGI, strip the headers from the output */
2608    $headers = array();
2609
2610    if (!empty($uses_cgi) && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) {
2611        $output = trim($match[2]);
2612        $rh = preg_split("/[\n\r]+/", $match[1]);
2613
2614        foreach ($rh as $line) {
2615            if (strpos($line, ':') !== false) {
2616                $line = explode(':', $line, 2);
2617                $headers[trim($line[0])] = trim($line[1]);
2618            }
2619        }
2620    }
2621
2622    $failed_headers = false;
2623
2624    if (isset($section_text['EXPECTHEADERS'])) {
2625        $want = array();
2626        $wanted_headers = array();
2627        $lines = preg_split("/[\n\r]+/", $section_text['EXPECTHEADERS']);
2628
2629        foreach ($lines as $line) {
2630            if (strpos($line, ':') !== false) {
2631                $line = explode(':', $line, 2);
2632                $want[trim($line[0])] = trim($line[1]);
2633                $wanted_headers[] = trim($line[0]) . ': ' . trim($line[1]);
2634            }
2635        }
2636
2637        $output_headers = array();
2638
2639        foreach ($want as $k => $v) {
2640            if (isset($headers[$k])) {
2641                $output_headers[] = $k . ': ' . $headers[$k];
2642            }
2643
2644            if (!isset($headers[$k]) || $headers[$k] != $v) {
2645                $failed_headers = true;
2646            }
2647        }
2648
2649        ksort($wanted_headers);
2650        $wanted_headers = implode("\n", $wanted_headers);
2651        ksort($output_headers);
2652        $output_headers = implode("\n", $output_headers);
2653    }
2654
2655    show_file_block('out', $output);
2656
2657    if ($preload) {
2658        $output = trim(preg_replace("/\n?Warning: Can't preload [^\n]*\n?/", "", $output));
2659    }
2660
2661    if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) {
2662        if (isset($section_text['EXPECTF'])) {
2663            $wanted = trim($section_text['EXPECTF']);
2664        } else {
2665            $wanted = trim($section_text['EXPECTREGEX']);
2666        }
2667
2668        show_file_block('exp', $wanted);
2669        $wanted_re = preg_replace('/\r\n/', "\n", $wanted);
2670
2671        if (isset($section_text['EXPECTF'])) {
2672            // do preg_quote, but miss out any %r delimited sections
2673            $temp = "";
2674            $r = "%r";
2675            $startOffset = 0;
2676            $length = strlen($wanted_re);
2677            while ($startOffset < $length) {
2678                $start = strpos($wanted_re, $r, $startOffset);
2679                if ($start !== false) {
2680                    // we have found a start tag
2681                    $end = strpos($wanted_re, $r, $start + 2);
2682                    if ($end === false) {
2683                        // unbalanced tag, ignore it.
2684                        $end = $start = $length;
2685                    }
2686                } else {
2687                    // no more %r sections
2688                    $start = $end = $length;
2689                }
2690                // quote a non re portion of the string
2691                $temp .= preg_quote(substr($wanted_re, $startOffset, $start - $startOffset), '/');
2692                // add the re unquoted.
2693                if ($end > $start) {
2694                    $temp .= '(' . substr($wanted_re, $start + 2, $end - $start - 2) . ')';
2695                }
2696                $startOffset = $end + 2;
2697            }
2698            $wanted_re = $temp;
2699
2700            // Stick to basics
2701            $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re);
2702            $wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re);
2703            $wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re);
2704            $wanted_re = str_replace('%a', '.+', $wanted_re);
2705            $wanted_re = str_replace('%A', '.*', $wanted_re);
2706            $wanted_re = str_replace('%w', '\s*', $wanted_re);
2707            $wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re);
2708            $wanted_re = str_replace('%d', '\d+', $wanted_re);
2709            $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re);
2710            $wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re);
2711            $wanted_re = str_replace('%c', '.', $wanted_re);
2712            // %f allows two points "-.0.0" but that is the best *simple* expression
2713        }
2714
2715        if (preg_match("/^$wanted_re\$/s", $output)) {
2716            $passed = true;
2717            if (!$cfg['keep']['php']) {
2718                @unlink($test_file);
2719            }
2720            @unlink($tmp_post);
2721
2722            if (!$leaked && !$failed_headers) {
2723                if (isset($section_text['XFAIL'])) {
2724                    $warn = true;
2725                    $info = " (warn: XFAIL section but test passes)";
2726                } elseif (isset($section_text['XLEAK'])) {
2727                    $warn = true;
2728                    $info = " (warn: XLEAK section but test passes)";
2729                } else {
2730                    show_result("PASS", $tested, $tested_file, '', $temp_filenames);
2731                    junit_mark_test_as('PASS', $shortname, $tested);
2732                    return 'PASSED';
2733                }
2734            }
2735        }
2736    } else {
2737        $wanted = trim($section_text['EXPECT']);
2738        $wanted = preg_replace('/\r\n/', "\n", $wanted);
2739        show_file_block('exp', $wanted);
2740
2741        // compare and leave on success
2742        if (!strcmp($output, $wanted)) {
2743            $passed = true;
2744
2745            if (!$cfg['keep']['php']) {
2746                @unlink($test_file);
2747            }
2748            @unlink($tmp_post);
2749
2750            if (!$leaked && !$failed_headers) {
2751                if (isset($section_text['XFAIL'])) {
2752                    $warn = true;
2753                    $info = " (warn: XFAIL section but test passes)";
2754                } elseif (isset($section_text['XLEAK'])) {
2755                    $warn = true;
2756                    $info = " (warn: XLEAK section but test passes)";
2757                } else {
2758                    show_result("PASS", $tested, $tested_file, '', $temp_filenames);
2759                    junit_mark_test_as('PASS', $shortname, $tested);
2760                    return 'PASSED';
2761                }
2762            }
2763        }
2764
2765        $wanted_re = null;
2766    }
2767
2768    // Test failed so we need to report details.
2769    if ($failed_headers) {
2770        $passed = false;
2771        $wanted = $wanted_headers . "\n--HEADERS--\n" . $wanted;
2772        $output = $output_headers . "\n--HEADERS--\n" . $output;
2773
2774        if (isset($wanted_re)) {
2775            $wanted_re = preg_quote($wanted_headers . "\n--HEADERS--\n", '/') . $wanted_re;
2776        }
2777    }
2778
2779    if ($leaked) {
2780        $restype[] = isset($section_text['XLEAK']) ?
2781                        'XLEAK' : 'LEAK';
2782    }
2783
2784    if ($warn) {
2785        $restype[] = 'WARN';
2786    }
2787
2788    if (!$passed) {
2789        if (isset($section_text['XFAIL'])) {
2790            $restype[] = 'XFAIL';
2791            $info = '  XFAIL REASON: ' . rtrim($section_text['XFAIL']);
2792        } elseif (isset($section_text['XLEAK'])) {
2793            $restype[] = 'XLEAK';
2794            $info = '  XLEAK REASON: ' . rtrim($section_text['XLEAK']);
2795        } else {
2796            $restype[] = 'FAIL';
2797        }
2798    }
2799
2800    if (!$passed) {
2801        // write .exp
2802        if (strpos($log_format, 'E') !== false && file_put_contents($exp_filename, $wanted) === false) {
2803            error("Cannot create expected test output - $exp_filename");
2804        }
2805
2806        // write .out
2807        if (strpos($log_format, 'O') !== false && file_put_contents($output_filename, $output) === false) {
2808            error("Cannot create test output - $output_filename");
2809        }
2810
2811        // write .diff
2812        $diff = generate_diff($wanted, $wanted_re, $output);
2813        if (is_array($IN_REDIRECT)) {
2814            $orig_shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file);
2815            $diff = "# original source file: $orig_shortname\n" . $diff;
2816        }
2817        show_file_block('diff', $diff);
2818        if (strpos($log_format, 'D') !== false && file_put_contents($diff_filename, $diff) === false) {
2819            error("Cannot create test diff - $diff_filename");
2820        }
2821
2822        // write .sh
2823        if (strpos($log_format, 'S') !== false) {
2824            $sh_script = <<<SH
2825#!/bin/sh
2826
2827case "$1" in
2828"gdb")
2829    gdb --args {$cmd}
2830    ;;
2831"valgrind")
2832    USE_ZEND_ALLOC=0 valgrind $2 ${cmd}
2833    ;;
2834"rr")
2835    rr record $2 ${cmd}
2836    ;;
2837*)
2838    {$cmd}
2839    ;;
2840esac
2841SH;
2842            if (file_put_contents($sh_filename, $sh_script) === false) {
2843                error("Cannot create test shell script - $sh_filename");
2844            }
2845            chmod($sh_filename, 0755);
2846        }
2847
2848        // write .log
2849        if (strpos($log_format, 'L') !== false && file_put_contents($log_filename, "
2850---- EXPECTED OUTPUT
2851$wanted
2852---- ACTUAL OUTPUT
2853$output
2854---- FAILED
2855") === false) {
2856            error("Cannot create test log - $log_filename");
2857            error_report($file, $log_filename, $tested);
2858        }
2859    }
2860
2861    if ($valgrind && $leaked && $cfg["show"]["mem"]) {
2862        show_file_block('mem', file_get_contents($memcheck_filename));
2863    }
2864
2865    show_result(implode('&', $restype), $tested, $tested_file, $info, $temp_filenames);
2866
2867    foreach ($restype as $type) {
2868        $PHP_FAILED_TESTS[$type . 'ED'][] = array(
2869            'name' => $file,
2870            'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]",
2871            'output' => $output_filename,
2872            'diff' => $diff_filename,
2873            'info' => $info,
2874		);
2875    }
2876
2877    $diff = empty($diff) ? '' : preg_replace('/\e/', '<esc>', $diff);
2878
2879    junit_mark_test_as($restype, $shortname, $tested, null, $info, $diff);
2880
2881    return $restype[0] . 'ED';
2882}
2883
2884/**
2885 * @return bool|int
2886 */
2887function comp_line($l1, $l2, $is_reg)
2888{
2889    if ($is_reg) {
2890        return preg_match('/^' . $l1 . '$/s', $l2);
2891    } else {
2892        return !strcmp($l1, $l2);
2893    }
2894}
2895
2896function count_array_diff(
2897    array $ar1,
2898    array $ar2,
2899    $is_reg,
2900    array $w,
2901    $idx1,
2902    $idx2,
2903    $cnt1,
2904    $cnt2,
2905    $steps
2906) {
2907    $equal = 0;
2908
2909    while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) {
2910        $idx1++;
2911        $idx2++;
2912        $equal++;
2913        $steps--;
2914    }
2915    if (--$steps > 0) {
2916        $eq1 = 0;
2917        $st = $steps / 2;
2918
2919        for ($ofs1 = $idx1 + 1; $ofs1 < $cnt1 && $st-- > 0; $ofs1++) {
2920            $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $ofs1, $idx2, $cnt1, $cnt2, $st);
2921
2922            if ($eq > $eq1) {
2923                $eq1 = $eq;
2924            }
2925        }
2926
2927        $eq2 = 0;
2928        $st = $steps;
2929
2930        for ($ofs2 = $idx2 + 1; $ofs2 < $cnt2 && $st-- > 0; $ofs2++) {
2931            $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $ofs2, $cnt1, $cnt2, $st);
2932            if ($eq > $eq2) {
2933                $eq2 = $eq;
2934            }
2935        }
2936
2937        if ($eq1 > $eq2) {
2938            $equal += $eq1;
2939        } elseif ($eq2 > 0) {
2940            $equal += $eq2;
2941        }
2942    }
2943
2944    return $equal;
2945}
2946
2947function generate_array_diff($ar1, $ar2, $is_reg, array $w)
2948{
2949    global $context_line_count;
2950    $idx1 = 0;
2951    $cnt1 = @count($ar1);
2952    $idx2 = 0;
2953    $cnt2 = @count($ar2);
2954    $diff = array();
2955    $old1 = array();
2956    $old2 = array();
2957    $number_len = max(3, strlen((string)max($cnt1 + 1, $cnt2 + 1)));
2958    $line_number_spec = '%0' . $number_len . 'd';
2959
2960    /** Mapping from $idx2 to $idx1, including indexes of idx2 that are identical to idx1 as well as entries that don't have matches */
2961    $mapping = array();
2962
2963    while ($idx1 < $cnt1 && $idx2 < $cnt2) {
2964        $mapping[$idx2] = $idx1;
2965        if (comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) {
2966            $idx1++;
2967            $idx2++;
2968            continue;
2969        } else {
2970            $c1 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1 + 1, $idx2, $cnt1, $cnt2, 10);
2971            $c2 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2 + 1, $cnt1, $cnt2, 10);
2972
2973            if ($c1 > $c2) {
2974                $old1[$idx1] = sprintf("{$line_number_spec}- ", $idx1 + 1) . $w[$idx1++];
2975            } elseif ($c2 > 0) {
2976                $old2[$idx2] = sprintf("{$line_number_spec}+ ", $idx2 + 1) . $ar2[$idx2++];
2977            } else {
2978                $old1[$idx1] = sprintf("{$line_number_spec}- ", $idx1 + 1) . $w[$idx1++];
2979                $old2[$idx2] = sprintf("{$line_number_spec}+ ", $idx2 + 1) . $ar2[$idx2++];
2980            }
2981            $last_printed_context_line = $idx1;
2982        }
2983    }
2984    $mapping[$idx2] = $idx1;
2985
2986    reset($old1);
2987    $k1 = key($old1);
2988    $l1 = -2;
2989    reset($old2);
2990    $k2 = key($old2);
2991    $l2 = -2;
2992    $old_k1 = -1;
2993    $add_context_lines = function ($new_k1) use (&$old_k1, &$diff, $w, $context_line_count, $number_len) {
2994        if ($old_k1 >= $new_k1 || !$context_line_count) {
2995            return;
2996        }
2997        $end = $new_k1 - 1;
2998        $range_end = min($end, $old_k1 + $context_line_count);
2999        if ($old_k1 >= 0) {
3000            while ($old_k1 < $range_end) {
3001                $diff[] = str_repeat(' ', $number_len + 2) . $w[$old_k1++];
3002            }
3003        }
3004        if ($end - $context_line_count > $old_k1) {
3005            $old_k1 = $end - $context_line_count;
3006            if ($old_k1 > 0) {
3007                // Add a '--' to mark sections where the common areas were truncated
3008                $diff[] = '--';
3009            }
3010        }
3011        $old_k1 = max($old_k1, 0);
3012        while ($old_k1 < $end) {
3013            $diff[] = str_repeat(' ', $number_len + 2) . $w[$old_k1++];
3014        }
3015        $old_k1 = $new_k1;
3016    };
3017
3018    while ($k1 !== null || $k2 !== null) {
3019        if ($k1 == $l1 + 1 || $k2 === null) {
3020            $add_context_lines($k1);
3021            $l1 = $k1;
3022            $diff[] = current($old1);
3023            $old_k1 = $k1;
3024            $k1 = next($old1) ? key($old1) : null;
3025        } elseif ($k2 == $l2 + 1 || $k1 === null) {
3026            $add_context_lines($mapping[$k2]);
3027            $l2 = $k2;
3028            $diff[] = current($old2);
3029            $k2 = next($old2) ? key($old2) : null;
3030        } elseif ($k1 < $mapping[$k2]) {
3031            $add_context_lines($k1);
3032            $l1 = $k1;
3033            $diff[] = current($old1);
3034            $k1 = next($old1) ? key($old1) : null;
3035        } else {
3036            $add_context_lines($mapping[$k2]);
3037            $l2 = $k2;
3038            $diff[] = current($old2);
3039            $k2 = next($old2) ? key($old2) : null;
3040        }
3041    }
3042
3043    while ($idx1 < $cnt1) {
3044        $add_context_lines($idx1 + 1);
3045        $diff[] = sprintf("{$line_number_spec}- ", $idx1 + 1) . $w[$idx1++];
3046    }
3047
3048    while ($idx2 < $cnt2) {
3049        if (isset($mapping[$idx2])) {
3050            $add_context_lines($mapping[$idx2] + 1);
3051        }
3052        $diff[] = sprintf("{$line_number_spec}+ ", $idx2 + 1) . $ar2[$idx2++];
3053    }
3054    $add_context_lines(min($old_k1 + $context_line_count + 1, $cnt1 + 1));
3055    if ($context_line_count && $old_k1 < $cnt1 + 1) {
3056        // Add a '--' to mark sections where the common areas were truncated
3057        $diff[] = '--';
3058    }
3059
3060    return $diff;
3061}
3062
3063function generate_diff($wanted, $wanted_re, $output)
3064{
3065    $w = explode("\n", $wanted);
3066    $o = explode("\n", $output);
3067    $r = is_null($wanted_re) ? $w : explode("\n", $wanted_re);
3068    $diff = generate_array_diff($r, $o, !is_null($wanted_re), $w);
3069
3070    return implode(PHP_EOL, $diff);
3071}
3072
3073function error($message)
3074{
3075    echo "ERROR: {$message}\n";
3076    exit(1);
3077}
3078
3079function settings2array(array $settings, &$ini_settings)
3080{
3081    foreach ($settings as $setting) {
3082        if (strpos($setting, '=') !== false) {
3083            $setting = explode("=", $setting, 2);
3084            $name = trim($setting[0]);
3085            $value = trim($setting[1]);
3086
3087            if ($name == 'extension' || $name == 'zend_extension') {
3088                if (!isset($ini_settings[$name])) {
3089                    $ini_settings[$name] = array();
3090                }
3091
3092                $ini_settings[$name][] = $value;
3093            } else {
3094                $ini_settings[$name] = $value;
3095            }
3096        }
3097    }
3098}
3099
3100function settings2params(array $ini_settings)
3101{
3102    $settings = '';
3103
3104    foreach ($ini_settings as $name => $value) {
3105        if (is_array($value)) {
3106            foreach ($value as $val) {
3107                $val = addslashes($val);
3108                $settings .= " -d \"$name=$val\"";
3109            }
3110        } else {
3111            if (IS_WINDOWS && !empty($value) && $value[0] == '"') {
3112                $len = strlen($value);
3113
3114                if ($value[$len - 1] == '"') {
3115                    $value[0] = "'";
3116                    $value[$len - 1] = "'";
3117                }
3118            } else {
3119                $value = addslashes($value);
3120            }
3121
3122            $settings .= " -d \"$name=$value\"";
3123        }
3124    }
3125
3126    return $settings;
3127}
3128
3129function compute_summary()
3130{
3131    global $n_total, $test_results, $ignored_by_ext, $sum_results, $percent_results;
3132
3133    $n_total = count($test_results);
3134    $n_total += $ignored_by_ext;
3135    $sum_results = array(
3136        'PASSED' => 0,
3137        'WARNED' => 0,
3138        'SKIPPED' => 0,
3139        'FAILED' => 0,
3140        'BORKED' => 0,
3141        'LEAKED' => 0,
3142        'XFAILED' => 0,
3143        'XLEAKED' => 0
3144	);
3145
3146    foreach ($test_results as $v) {
3147        $sum_results[$v]++;
3148    }
3149
3150    $sum_results['SKIPPED'] += $ignored_by_ext;
3151    $percent_results = array();
3152
3153    foreach ($sum_results as $v => $n) {
3154        $percent_results[$v] = (100.0 * $n) / $n_total;
3155    }
3156}
3157
3158function get_summary($show_ext_summary)
3159{
3160    global $exts_skipped, $exts_tested, $n_total, $sum_results, $percent_results, $end_time, $start_time, $failed_test_summary, $PHP_FAILED_TESTS, $valgrind;
3161
3162    $x_total = $n_total - $sum_results['SKIPPED'] - $sum_results['BORKED'];
3163
3164    if ($x_total) {
3165        $x_warned = (100.0 * $sum_results['WARNED']) / $x_total;
3166        $x_failed = (100.0 * $sum_results['FAILED']) / $x_total;
3167        $x_xfailed = (100.0 * $sum_results['XFAILED']) / $x_total;
3168        $x_xleaked = (100.0 * $sum_results['XLEAKED']) / $x_total;
3169        $x_leaked = (100.0 * $sum_results['LEAKED']) / $x_total;
3170        $x_passed = (100.0 * $sum_results['PASSED']) / $x_total;
3171    } else {
3172        $x_warned = $x_failed = $x_passed = $x_leaked = $x_xfailed = $x_xleaked = 0;
3173    }
3174
3175    $summary = '';
3176
3177    if ($show_ext_summary) {
3178        $summary .= '
3179=====================================================================
3180TEST RESULT SUMMARY
3181---------------------------------------------------------------------
3182Exts skipped    : ' . sprintf('%4d', $exts_skipped) . '
3183Exts tested     : ' . sprintf('%4d', $exts_tested) . '
3184---------------------------------------------------------------------
3185';
3186    }
3187
3188    $summary .= '
3189Number of tests : ' . sprintf('%4d', $n_total) . '          ' . sprintf('%8d', $x_total);
3190
3191    if ($sum_results['BORKED']) {
3192        $summary .= '
3193Tests borked    : ' . sprintf('%4d (%5.1f%%)', $sum_results['BORKED'], $percent_results['BORKED']) . ' --------';
3194    }
3195
3196    $summary .= '
3197Tests skipped   : ' . sprintf('%4d (%5.1f%%)', $sum_results['SKIPPED'], $percent_results['SKIPPED']) . ' --------
3198Tests warned    : ' . sprintf('%4d (%5.1f%%)', $sum_results['WARNED'], $percent_results['WARNED']) . ' ' . sprintf('(%5.1f%%)', $x_warned) . '
3199Tests failed    : ' . sprintf('%4d (%5.1f%%)', $sum_results['FAILED'], $percent_results['FAILED']) . ' ' . sprintf('(%5.1f%%)', $x_failed);
3200
3201    if ($sum_results['XFAILED']) {
3202        $summary .= '
3203Expected fail   : ' . sprintf('%4d (%5.1f%%)', $sum_results['XFAILED'], $percent_results['XFAILED']) . ' ' . sprintf('(%5.1f%%)', $x_xfailed);
3204    }
3205
3206    if ($valgrind) {
3207        $summary .= '
3208Tests leaked    : ' . sprintf('%4d (%5.1f%%)', $sum_results['LEAKED'], $percent_results['LEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_leaked);
3209        if ($sum_results['XLEAKED']) {
3210            $summary .= '
3211Expected leak   : ' . sprintf('%4d (%5.1f%%)', $sum_results['XLEAKED'], $percent_results['XLEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_xleaked);
3212        }
3213    }
3214
3215    $summary .= '
3216Tests passed    : ' . sprintf('%4d (%5.1f%%)', $sum_results['PASSED'], $percent_results['PASSED']) . ' ' . sprintf('(%5.1f%%)', $x_passed) . '
3217---------------------------------------------------------------------
3218Time taken      : ' . sprintf('%4d seconds', $end_time - $start_time) . '
3219=====================================================================
3220';
3221    $failed_test_summary = '';
3222
3223    if (count($PHP_FAILED_TESTS['SLOW'])) {
3224        usort($PHP_FAILED_TESTS['SLOW'], function (array $a, array $b) {
3225            return $a['info'] < $b['info'] ? 1 : -1;
3226        });
3227
3228        $failed_test_summary .= '
3229=====================================================================
3230SLOW TEST SUMMARY
3231---------------------------------------------------------------------
3232';
3233        foreach ($PHP_FAILED_TESTS['SLOW'] as $failed_test_data) {
3234            $failed_test_summary .= sprintf('(%.3f s) ', $failed_test_data['info']) . $failed_test_data['test_name'] . "\n";
3235        }
3236        $failed_test_summary .= "=====================================================================\n";
3237    }
3238
3239    if (count($PHP_FAILED_TESTS['XFAILED'])) {
3240        $failed_test_summary .= '
3241=====================================================================
3242EXPECTED FAILED TEST SUMMARY
3243---------------------------------------------------------------------
3244';
3245        foreach ($PHP_FAILED_TESTS['XFAILED'] as $failed_test_data) {
3246            $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
3247        }
3248        $failed_test_summary .= "=====================================================================\n";
3249    }
3250
3251    if (count($PHP_FAILED_TESTS['BORKED'])) {
3252        $failed_test_summary .= '
3253=====================================================================
3254BORKED TEST SUMMARY
3255---------------------------------------------------------------------
3256';
3257        foreach ($PHP_FAILED_TESTS['BORKED'] as $failed_test_data) {
3258            $failed_test_summary .= $failed_test_data['info'] . "\n";
3259        }
3260
3261        $failed_test_summary .= "=====================================================================\n";
3262    }
3263
3264    if (count($PHP_FAILED_TESTS['FAILED'])) {
3265        $failed_test_summary .= '
3266=====================================================================
3267FAILED TEST SUMMARY
3268---------------------------------------------------------------------
3269';
3270        foreach ($PHP_FAILED_TESTS['FAILED'] as $failed_test_data) {
3271            $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
3272        }
3273        $failed_test_summary .= "=====================================================================\n";
3274    }
3275    if (count($PHP_FAILED_TESTS['WARNED'])) {
3276        $failed_test_summary .= '
3277=====================================================================
3278WARNED TEST SUMMARY
3279---------------------------------------------------------------------
3280';
3281        foreach ($PHP_FAILED_TESTS['WARNED'] as $failed_test_data) {
3282            $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
3283        }
3284
3285        $failed_test_summary .= "=====================================================================\n";
3286    }
3287
3288    if (count($PHP_FAILED_TESTS['LEAKED'])) {
3289        $failed_test_summary .= '
3290=====================================================================
3291LEAKED TEST SUMMARY
3292---------------------------------------------------------------------
3293';
3294        foreach ($PHP_FAILED_TESTS['LEAKED'] as $failed_test_data) {
3295            $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
3296        }
3297
3298        $failed_test_summary .= "=====================================================================\n";
3299    }
3300
3301    if (count($PHP_FAILED_TESTS['XLEAKED'])) {
3302        $failed_test_summary .= '
3303=====================================================================
3304EXPECTED LEAK TEST SUMMARY
3305---------------------------------------------------------------------
3306';
3307        foreach ($PHP_FAILED_TESTS['XLEAKED'] as $failed_test_data) {
3308            $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
3309        }
3310
3311        $failed_test_summary .= "=====================================================================\n";
3312    }
3313
3314    if ($failed_test_summary && !getenv('NO_PHPTEST_SUMMARY')) {
3315        $summary .= $failed_test_summary;
3316    }
3317
3318    return $summary;
3319}
3320
3321function show_start($start_time)
3322{
3323    echo "TIME START " . date('Y-m-d H:i:s', $start_time) . "\n=====================================================================\n";
3324}
3325
3326function show_end($end_time)
3327{
3328    echo "=====================================================================\nTIME END " . date('Y-m-d H:i:s', $end_time) . "\n";
3329}
3330
3331function show_summary()
3332{
3333    echo get_summary(true);
3334}
3335
3336function show_redirect_start($tests, $tested, $tested_file)
3337{
3338    global $SHOW_ONLY_GROUPS;
3339
3340    if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) {
3341        echo "REDIRECT $tests ($tested [$tested_file]) begin\n";
3342    } else {
3343        clear_show_test();
3344    }
3345}
3346
3347function show_redirect_ends($tests, $tested, $tested_file)
3348{
3349    global $SHOW_ONLY_GROUPS;
3350
3351    if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) {
3352        echo "REDIRECT $tests ($tested [$tested_file]) done\n";
3353    } else {
3354        clear_show_test();
3355    }
3356}
3357
3358function show_test($test_idx, $shortname)
3359{
3360    global $test_cnt;
3361    global $line_length;
3362
3363    $str = "TEST $test_idx/$test_cnt [$shortname]\r";
3364    $line_length = strlen($str);
3365    echo $str;
3366    flush();
3367}
3368
3369function clear_show_test()
3370{
3371    global $line_length;
3372    // Parallel testing
3373    global $workerID;
3374
3375    if (!$workerID) {
3376        // Write over the last line to avoid random trailing chars on next echo
3377        echo str_repeat(" ", $line_length), "\r";
3378    }
3379}
3380
3381function parse_conflicts($text)
3382{
3383    // Strip comments
3384    $text = preg_replace('/#.*/', '', $text);
3385    return array_map('trim', explode("\n", trim($text)));
3386}
3387
3388function show_result(
3389    $result,
3390    $tested,
3391    $tested_file,
3392    $extra = '',
3393    array $temp_filenames = null
3394) {
3395    global $SHOW_ONLY_GROUPS, $colorize;
3396
3397    if (!$SHOW_ONLY_GROUPS || in_array($result, $SHOW_ONLY_GROUPS)) {
3398        if ($colorize) {
3399            /* Use ANSI escape codes for coloring test result */
3400            switch ( $result ) {
3401                case 'PASS': // Light Green
3402                    $color = "\x1B[1;32m{$result}\x1B[0m"; break;
3403                case 'FAIL':
3404                case 'BORK':
3405                case 'LEAK':
3406                    // Light Red
3407                    $color = "\x1B[1;31m{$result}\x1B[0m"; break;
3408                default: // Yellow
3409                    $color = "\x1B[1;33m{$result}\x1B[0m"; break;
3410            }
3411
3412            echo "$color $tested [$tested_file] $extra\n";
3413        } else {
3414            echo "$result $tested [$tested_file] $extra\n";
3415        }
3416    } elseif (!$SHOW_ONLY_GROUPS) {
3417        clear_show_test();
3418    }
3419
3420}
3421
3422function junit_init()
3423{
3424    // Check whether a junit log is wanted.
3425    global $workerID;
3426    $JUNIT = getenv('TEST_PHP_JUNIT');
3427    if (empty($JUNIT)) {
3428        $GLOBALS['JUNIT'] = false;
3429        return;
3430    }
3431    if ($workerID) {
3432        $fp = null;
3433    } elseif (!$fp = fopen($JUNIT, 'w')) {
3434        error("Failed to open $JUNIT for writing.");
3435    }
3436    $GLOBALS['JUNIT'] = array(
3437        'fp' => $fp,
3438        'name' => 'PHP',
3439        'test_total' => 0,
3440        'test_pass' => 0,
3441        'test_fail' => 0,
3442        'test_error' => 0,
3443        'test_skip' => 0,
3444        'test_warn' => 0,
3445        'execution_time' => 0,
3446        'suites' => array(),
3447        'files' => array()
3448	);
3449}
3450
3451function junit_save_xml()
3452{
3453    global $JUNIT;
3454    if (!junit_enabled()) {
3455        return;
3456    }
3457
3458    $xml = '<' . '?' . 'xml version="1.0" encoding="UTF-8"' . '?' . '>' . PHP_EOL;
3459    $xml .= sprintf(
3460        '<testsuites name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL,
3461        $JUNIT['name'],
3462        $JUNIT['test_total'],
3463        $JUNIT['test_fail'],
3464        $JUNIT['test_error'],
3465        $JUNIT['test_skip'],
3466        $JUNIT['execution_time']
3467    );
3468    $xml .= junit_get_suite_xml();
3469    $xml .= '</testsuites>';
3470    fwrite($JUNIT['fp'], $xml);
3471}
3472
3473function junit_get_suite_xml($suite_name = '')
3474{
3475    global $JUNIT;
3476
3477    $result = "";
3478
3479    foreach ($JUNIT['suites'] as $suite_name => $suite) {
3480        $result .= sprintf(
3481            '<testsuite name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL,
3482            $suite['name'],
3483            $suite['test_total'],
3484            $suite['test_fail'],
3485            $suite['test_error'],
3486            $suite['test_skip'],
3487            $suite['execution_time']
3488        );
3489
3490        if (!empty($suite_name)) {
3491            foreach ($suite['files'] as $file) {
3492                $result .= $JUNIT['files'][$file]['xml'];
3493            }
3494        }
3495
3496        $result .= '</testsuite>' . PHP_EOL;
3497    }
3498
3499    return $result;
3500}
3501
3502function junit_enabled()
3503{
3504    global $JUNIT;
3505    return !empty($JUNIT);
3506}
3507
3508/**
3509 * @param array|string $type
3510 */
3511function junit_mark_test_as(
3512    $type,
3513    $file_name,
3514    $test_name,
3515    $time = null,
3516    $message = '',
3517    $details = ''
3518) {
3519    global $JUNIT;
3520    if (!junit_enabled()) {
3521        return;
3522    }
3523
3524    $suite = junit_get_suitename_for($file_name);
3525
3526    junit_suite_record($suite, 'test_total');
3527
3528    $time = empty($time) ? junit_get_timer($file_name) : $time;
3529    junit_suite_record($suite, 'execution_time', $time);
3530
3531    $escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8');
3532    $escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function (array $c) {
3533        return sprintf('[[0x%02x]]', ord($c[0]));
3534    }, $escaped_details);
3535    $escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
3536
3537    $escaped_test_name = htmlspecialchars($file_name . ' (' . $test_name . ')', ENT_QUOTES);
3538    $JUNIT['files'][$file_name]['xml'] = "<testcase name='$escaped_test_name' time='$time'>\n";
3539
3540    if (is_array($type)) {
3541        $output_type = $type[0] . 'ED';
3542        $temp = array_intersect(array('XFAIL', 'XLEAK', 'FAIL', 'WARN'), $type);
3543        $type = reset($temp);
3544    } else {
3545        $output_type = $type . 'ED';
3546    }
3547
3548    if ('PASS' == $type || 'XFAIL' == $type || 'XLEAK' == $type) {
3549        junit_suite_record($suite, 'test_pass');
3550    } elseif ('BORK' == $type) {
3551        junit_suite_record($suite, 'test_error');
3552        $JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'/>\n";
3553    } elseif ('SKIP' == $type) {
3554        junit_suite_record($suite, 'test_skip');
3555        $JUNIT['files'][$file_name]['xml'] .= "<skipped>$escaped_message</skipped>\n";
3556    } elseif ('WARN' == $type) {
3557        junit_suite_record($suite, 'test_warn');
3558        $JUNIT['files'][$file_name]['xml'] .= "<warning>$escaped_message</warning>\n";
3559    } elseif ('FAIL' == $type) {
3560        junit_suite_record($suite, 'test_fail');
3561        $JUNIT['files'][$file_name]['xml'] .= "<failure type='$output_type' message='$escaped_message'>$escaped_details</failure>\n";
3562    } else {
3563        junit_suite_record($suite, 'test_error');
3564        $JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'>$escaped_details</error>\n";
3565    }
3566
3567    $JUNIT['files'][$file_name]['xml'] .= "</testcase>\n";
3568}
3569
3570function junit_suite_record($suite, $param, $value = 1)
3571{
3572    global $JUNIT;
3573
3574    $JUNIT[$param] += $value;
3575    $JUNIT['suites'][$suite][$param] += $value;
3576}
3577
3578function junit_get_timer($file_name)
3579{
3580    global $JUNIT;
3581    if (!junit_enabled()) {
3582        return 0;
3583    }
3584
3585    if (isset($JUNIT['files'][$file_name]['total'])) {
3586        return number_format($JUNIT['files'][$file_name]['total'], 4);
3587    }
3588
3589    return 0;
3590}
3591
3592function junit_start_timer($file_name)
3593{
3594    global $JUNIT;
3595    if (!junit_enabled()) {
3596        return;
3597    }
3598
3599    if (!isset($JUNIT['files'][$file_name]['start'])) {
3600        $JUNIT['files'][$file_name]['start'] = microtime(true);
3601
3602        $suite = junit_get_suitename_for($file_name);
3603        junit_init_suite($suite);
3604        $JUNIT['suites'][$suite]['files'][$file_name] = $file_name;
3605    }
3606}
3607
3608function junit_get_suitename_for($file_name)
3609{
3610    return junit_path_to_classname(dirname($file_name));
3611}
3612
3613function junit_path_to_classname($file_name)
3614{
3615    global $JUNIT;
3616
3617    if (!junit_enabled()) {
3618        return '';
3619    }
3620
3621    $ret = $JUNIT['name'];
3622    $_tmp = array();
3623
3624    // lookup whether we're in the PHP source checkout
3625    $max = 5;
3626    if (is_file($file_name)) {
3627        $dir = dirname(realpath($file_name));
3628    } else {
3629        $dir = realpath($file_name);
3630    }
3631    do {
3632        array_unshift($_tmp, basename($dir));
3633        $chk = $dir . DIRECTORY_SEPARATOR . "main" . DIRECTORY_SEPARATOR . "php_version.h";
3634        $dir = dirname($dir);
3635    } while (!file_exists($chk) && --$max > 0);
3636    if (file_exists($chk)) {
3637        if ($max) {
3638            array_shift($_tmp);
3639        }
3640        foreach ($_tmp as $p) {
3641            $ret .= "." . preg_replace(",[^a-z0-9]+,i", ".", $p);
3642        }
3643        return $ret;
3644    }
3645
3646    return $JUNIT['name'] . '.' . str_replace(array(DIRECTORY_SEPARATOR, '-'), '.', $file_name);
3647}
3648
3649function junit_init_suite($suite_name)
3650{
3651    global $JUNIT;
3652    if (!junit_enabled()) {
3653        return;
3654    }
3655
3656    if (!empty($JUNIT['suites'][$suite_name])) {
3657        return;
3658    }
3659
3660    $JUNIT['suites'][$suite_name] = array(
3661        'name' => $suite_name,
3662        'test_total' => 0,
3663        'test_pass' => 0,
3664        'test_fail' => 0,
3665        'test_error' => 0,
3666        'test_skip' => 0,
3667        'test_warn' => 0,
3668        'files' => array(),
3669        'execution_time' => 0,
3670	);
3671}
3672
3673function junit_finish_timer($file_name)
3674{
3675    global $JUNIT;
3676    if (!junit_enabled()) {
3677        return;
3678    }
3679
3680    if (!isset($JUNIT['files'][$file_name]['start'])) {
3681        error("Timer for $file_name was not started!");
3682    }
3683
3684    if (!isset($JUNIT['files'][$file_name]['total'])) {
3685        $JUNIT['files'][$file_name]['total'] = 0;
3686    }
3687
3688    $start = $JUNIT['files'][$file_name]['start'];
3689    $JUNIT['files'][$file_name]['total'] += microtime(true) - $start;
3690    unset($JUNIT['files'][$file_name]['start']);
3691}
3692
3693function junit_merge_results(array $junit)
3694{
3695    global $JUNIT;
3696    $JUNIT['test_total'] += $junit['test_total'];
3697    $JUNIT['test_pass']  += $junit['test_pass'];
3698    $JUNIT['test_fail']  += $junit['test_fail'];
3699    $JUNIT['test_error'] += $junit['test_error'];
3700    $JUNIT['test_skip']  += $junit['test_skip'];
3701    $JUNIT['test_warn']  += $junit['test_warn'];
3702    $JUNIT['execution_time'] += $junit['execution_time'];
3703    $JUNIT['files'] += $junit['files'];
3704    foreach ($junit['suites'] as $name => $suite) {
3705        if (!isset($JUNIT['suites'][$name])) {
3706            $JUNIT['suites'][$name] = $suite;
3707            continue;
3708        }
3709
3710        $SUITE =& $JUNIT['suites'][$name];
3711        $SUITE['test_total'] += $suite['test_total'];
3712        $SUITE['test_pass']  += $suite['test_pass'];
3713        $SUITE['test_fail']  += $suite['test_fail'];
3714        $SUITE['test_error'] += $suite['test_error'];
3715        $SUITE['test_skip']  += $suite['test_skip'];
3716        $SUITE['test_warn']  += $suite['test_warn'];
3717        $SUITE['execution_time'] += $suite['execution_time'];
3718        $SUITE['files'] += $suite['files'];
3719    }
3720}
3721
3722class RuntestsValgrind
3723{
3724    protected $version = '';
3725    protected $header = '';
3726    protected $version_3_3_0 = false;
3727    protected $version_3_8_0 = false;
3728    protected $tool = null;
3729
3730    public function getVersion()
3731    {
3732        return $this->version;
3733    }
3734
3735    public function getHeader()
3736    {
3737        return $this->header;
3738    }
3739
3740    public function __construct(array $environment, $tool = 'memcheck')
3741    {
3742        $this->tool = $tool;
3743        $header = system_with_timeout("valgrind --tool={$this->tool} --version", $environment);
3744        if (!$header) {
3745            error("Valgrind returned no version info for {$this->tool}, cannot proceed.\n".
3746                  "Please check if Valgrind is installed and the tool is named correctly.");
3747        }
3748        $count = 0;
3749        $version = preg_replace("/valgrind-(\d+)\.(\d+)\.(\d+)([.\w_-]+)?(\s+)/", '$1.$2.$3', $header, 1, $count);
3750        if ($count != 1) {
3751            error("Valgrind returned invalid version info (\"{$header}\") for {$this->tool}, cannot proceed.");
3752        }
3753        $this->version = $version;
3754        $this->header = sprintf(
3755            "%s (%s)", trim($header), $this->tool);
3756        $this->version_3_3_0 = version_compare($version, '3.3.0', '>=');
3757        $this->version_3_8_0 = version_compare($version, '3.8.0', '>=');
3758    }
3759
3760    public function wrapCommand($cmd, $memcheck_filename, $check_all)
3761	{
3762		$supp_file = INIT_DIR . "/valgrind.supp";
3763		$vcmd = "valgrind -q --tool={$this->tool} --trace-children=yes --leak-check=full " .
3764			"--gen-suppressions=all  --num-callers=16 --run-libc-freeres=no";
3765		if (file_exists($supp_file)) {
3766			$vcmd .= " --suppressions='$supp_file'";
3767		}
3768        if ($check_all) {
3769            $vcmd .= ' --smc-check=all';
3770        }
3771
3772        /* --vex-iropt-register-updates=allregs-at-mem-access is necessary for phpdbg watchpoint tests */
3773        if ($this->version_3_8_0) {
3774            /* valgrind 3.3.0+ doesn't have --log-file-exactly option */
3775            return "$vcmd --vex-iropt-register-updates=allregs-at-mem-access --log-file=$memcheck_filename $cmd";
3776        } elseif ($this->version_3_3_0) {
3777            return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file=$memcheck_filename $cmd";
3778        } else {
3779            return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file-exactly=$memcheck_filename $cmd";
3780        }
3781    }
3782}
3783
3784function init_output_buffers()
3785{
3786    // Delete as much output buffers as possible.
3787    while (@ob_end_clean()) {
3788    }
3789
3790    if (ob_get_level()) {
3791        echo "Not all buffers were deleted.\n";
3792    }
3793}
3794
3795function check_proc_open_function_exists()
3796{
3797    if (!function_exists('proc_open')) {
3798        echo <<<NO_PROC_OPEN_ERROR
3799
3800+-----------------------------------------------------------+
3801|                       ! ERROR !                           |
3802| The test-suite requires that proc_open() is available.    |
3803| Please check if you disabled it in php.ini.               |
3804+-----------------------------------------------------------+
3805
3806NO_PROC_OPEN_ERROR;
3807        exit(1);
3808    }
3809}
3810
3811main();
3812