xref: /PHP-7.3/run-tests.php (revision 729cd8ba)
1#!/usr/bin/env php
2<?php
3/*
4   +----------------------------------------------------------------------+
5   | PHP Version 7                                                        |
6   +----------------------------------------------------------------------+
7   | Copyright (c) 1997-2018 The PHP Group                                |
8   +----------------------------------------------------------------------+
9   | This source file is subject to version 3.01 of the PHP license,      |
10   | that is bundled with this package in the file LICENSE, and is        |
11   | available through the world-wide-web at the following url:           |
12   | https://php.net/license/3_01.txt                                  |
13   | If you did not receive a copy of the PHP license and are unable to   |
14   | obtain it through the world-wide-web, please send a note to          |
15   | license@php.net so we can mail you a copy immediately.               |
16   +----------------------------------------------------------------------+
17   | Authors: Ilia Alshanetsky <iliaa@php.net>                            |
18   |          Preston L. Bannister <pbannister@php.net>                   |
19   |          Marcus Boerger <helly@php.net>                              |
20   |          Derick Rethans <derick@php.net>                             |
21   |          Sander Roobol <sander@php.net>                              |
22   | (based on version by: Stig Bakken <ssb@php.net>)                     |
23   | (based on the PHP 3 test framework by Rasmus Lerdorf)                |
24   +----------------------------------------------------------------------+
25 */
26
27/* $Id: 6e551f4bb4be63fc488bb5568aa27da773720a97 $ */
28
29define('INIT_DIR', getcwd());
30
31// change into the PHP source directory.
32if (getenv('TEST_PHP_SRCDIR')) {
33	@chdir(getenv('TEST_PHP_SRCDIR'));
34}
35define('TEST_PHP_SRCDIR', getcwd());
36
37
38/* Sanity check to ensure that pcre extension needed by this script is available.
39 * In the event it is not, print a nice error message indicating that this script will
40 * not run without it.
41 */
42
43if (!extension_loaded('pcre')) {
44	echo <<<NO_PCRE_ERROR
45
46+-----------------------------------------------------------+
47|                       ! ERROR !                           |
48| The test-suite requires that you have pcre extension      |
49| enabled. To enable this extension either compile your PHP |
50| with --with-pcre-regex or if you've compiled pcre as a    |
51| shared module load it via php.ini.                        |
52+-----------------------------------------------------------+
53
54NO_PCRE_ERROR;
55exit(1);
56}
57
58if (!function_exists('proc_open')) {
59	echo <<<NO_PROC_OPEN_ERROR
60
61+-----------------------------------------------------------+
62|                       ! ERROR !                           |
63| The test-suite requires that proc_open() is available.    |
64| Please check if you disabled it in php.ini.               |
65+-----------------------------------------------------------+
66
67NO_PROC_OPEN_ERROR;
68exit(1);
69}
70
71// If timezone is not set, use UTC.
72if (ini_get('date.timezone') == '') {
73	date_default_timezone_set('UTC');
74}
75
76// Delete some security related environment variables
77putenv('SSH_CLIENT=deleted');
78putenv('SSH_AUTH_SOCK=deleted');
79putenv('SSH_TTY=deleted');
80putenv('SSH_CONNECTION=deleted');
81
82set_time_limit(0);
83
84ini_set('pcre.backtrack_limit', PHP_INT_MAX);
85
86// delete as much output buffers as possible
87while(@ob_end_clean());
88if (ob_get_level()) echo "Not all buffers were deleted.\n";
89
90error_reporting(E_ALL);
91
92$environment = $_ENV ?? array();
93// Note: php.ini-development sets variables_order="GPCS" not "EGPCS", in which case $_ENV is NOT populated.
94//       detect and handle this case, or die or warn
95if (empty($environment)) {
96	// not documented, but returns array of all environment variables
97	$environment = getenv();
98}
99if (empty($environment['TEMP'])) {
100	$environment['TEMP'] = sys_get_temp_dir();
101
102	if (empty($environment['TEMP'])) {
103		// for example, OpCache on Windows will fail in this case because child processes (for tests) will not get
104		// a TEMP variable, so GetTempPath() will fallback to c:\windows, while GetTempPath() will return %TEMP% for parent
105		// (likely a different path). The parent will initialize the OpCache in that path, and child will fail to reattach to
106		// the OpCache because it will be using the wrong path.
107		die("TEMP environment is NOT set");
108	} else if (count($environment)==1) {
109		// not having other environment variables, only having TEMP, is probably ok, but strange and may make a
110		// difference in the test pass rate, so warn the user.
111		echo "WARNING: Only 1 environment variable will be available to tests(TEMP environment variable)".PHP_EOL;
112	}
113}
114//
115if ((substr(PHP_OS, 0, 3) == "WIN") && empty($environment["SystemRoot"])) {
116  $environment["SystemRoot"] = getenv("SystemRoot");
117}
118
119// Don't ever guess at the PHP executable location.
120// Require the explicit specification.
121// Otherwise we could end up testing the wrong file!
122
123$php = null;
124$php_cgi = null;
125$phpdbg = null;
126
127if (getenv('TEST_PHP_EXECUTABLE')) {
128	$php = getenv('TEST_PHP_EXECUTABLE');
129
130	if ($php=='auto') {
131		$php = TEST_PHP_SRCDIR . '/sapi/cli/php';
132		putenv("TEST_PHP_EXECUTABLE=$php");
133
134		if (!getenv('TEST_PHP_CGI_EXECUTABLE')) {
135			$php_cgi = TEST_PHP_SRCDIR . '/sapi/cgi/php-cgi';
136
137			if (file_exists($php_cgi)) {
138				putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi");
139			} else {
140				$php_cgi = null;
141			}
142		}
143	}
144	$environment['TEST_PHP_EXECUTABLE'] = $php;
145}
146
147if (getenv('TEST_PHP_CGI_EXECUTABLE')) {
148	$php_cgi = getenv('TEST_PHP_CGI_EXECUTABLE');
149
150	if ($php_cgi=='auto') {
151		$php_cgi = TEST_PHP_SRCDIR . '/sapi/cgi/php-cgi';
152		putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi");
153	}
154
155	$environment['TEST_PHP_CGI_EXECUTABLE'] = $php_cgi;
156}
157
158if (!getenv('TEST_PHPDBG_EXECUTABLE')) {
159	if (!strncasecmp(PHP_OS, "win", 3) && file_exists(dirname($php) . "/phpdbg.exe")) {
160		$phpdbg = realpath(dirname($php) . "/phpdbg.exe");
161	} elseif (file_exists(dirname($php) . "/../../sapi/phpdbg/phpdbg")) {
162		$phpdbg = realpath(dirname($php) . "/../../sapi/phpdbg/phpdbg");
163	} elseif (file_exists("./sapi/phpdbg/phpdbg")) {
164		$phpdbg = realpath("./sapi/phpdbg/phpdbg");
165	} elseif (file_exists(dirname($php) . "/phpdbg")) {
166		$phpdbg = realpath(dirname($php) . "/phpdbg");
167	} else {
168		$phpdbg = null;
169	}
170	if ($phpdbg) {
171		putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg");
172	}
173}
174
175if (getenv('TEST_PHPDBG_EXECUTABLE')) {
176	$phpdbg = getenv('TEST_PHPDBG_EXECUTABLE');
177
178	if ($phpdbg=='auto') {
179		$phpdbg = TEST_PHP_SRCDIR . '/sapi/phpdbg/phpdbg';
180		putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg");
181	}
182
183	$environment['TEST_PHPDBG_EXECUTABLE'] = $phpdbg;
184}
185
186if (!function_exists("hrtime")) {
187	function hrtime(bool $as_num = false)
188	{
189		$t = microtime(true);
190
191		if ($as_num) {
192			return $t*1000000000;
193		}
194
195		$s = floor($t);
196		return array(0 => $s, 1 => ($t - $s)*1000000000);
197	}
198}
199
200function verify_config()
201{
202	global $php;
203
204	if (empty($php) || !file_exists($php)) {
205		error('environment variable TEST_PHP_EXECUTABLE must be set to specify PHP executable!');
206	}
207
208	if (function_exists('is_executable') && !is_executable($php)) {
209		error("invalid PHP executable specified by TEST_PHP_EXECUTABLE  = $php");
210	}
211}
212
213if (getenv('TEST_PHP_LOG_FORMAT')) {
214	$log_format = strtoupper(getenv('TEST_PHP_LOG_FORMAT'));
215} else {
216	$log_format = 'LEODS';
217}
218
219// Check whether a detailed log is wanted.
220if (getenv('TEST_PHP_DETAILED')) {
221	$DETAILED = getenv('TEST_PHP_DETAILED');
222} else {
223	$DETAILED = 0;
224}
225
226junit_init();
227
228if (getenv('SHOW_ONLY_GROUPS')) {
229	$SHOW_ONLY_GROUPS = explode(",", getenv('SHOW_ONLY_GROUPS'));
230} else {
231	$SHOW_ONLY_GROUPS = array();
232}
233
234// Check whether user test dirs are requested.
235if (getenv('TEST_PHP_USER')) {
236	$user_tests = explode (',', getenv('TEST_PHP_USER'));
237} else {
238	$user_tests = array();
239}
240
241$exts_to_test = array();
242$ini_overwrites = array(
243		'output_handler=',
244		'open_basedir=',
245		'disable_functions=',
246		'output_buffering=Off',
247		'error_reporting=' . (E_ALL | E_STRICT),
248		'display_errors=1',
249		'display_startup_errors=1',
250		'log_errors=0',
251		'html_errors=0',
252		'track_errors=0',
253		'report_memleaks=1',
254		'report_zend_debug=0',
255		'docref_root=',
256		'docref_ext=.html',
257		'error_prepend_string=',
258		'error_append_string=',
259		'auto_prepend_file=',
260		'auto_append_file=',
261		'ignore_repeated_errors=0',
262		'precision=14',
263		'memory_limit=128M',
264		'log_errors_max_len=0',
265		'opcache.fast_shutdown=0',
266		'opcache.file_update_protection=0',
267		'zend.assertions=1',
268	);
269
270$no_file_cache = '-d opcache.file_cache= -d opcache.file_cache_only=0';
271
272function write_information()
273{
274	global $php, $php_cgi, $phpdbg, $php_info, $user_tests, $ini_overwrites, $pass_options, $exts_to_test, $valgrind, $no_file_cache;
275
276	// Get info from php
277	$info_file = __DIR__ . '/run-test-info.php';
278	@unlink($info_file);
279	$php_info = '<?php echo "
280PHP_SAPI    : " , PHP_SAPI , "
281PHP_VERSION : " , phpversion() , "
282ZEND_VERSION: " , zend_version() , "
283PHP_OS      : " , PHP_OS , " - " , php_uname() , "
284INI actual  : " , realpath(get_cfg_var("cfg_file_path")) , "
285More .INIs  : " , (function_exists(\'php_ini_scanned_files\') ? str_replace("\n","", php_ini_scanned_files()) : "** not determined **"); ?>';
286	save_text($info_file, $php_info);
287	$info_params = array();
288	settings2array($ini_overwrites, $info_params);
289	settings2params($info_params);
290	$php_info = `$php $pass_options $info_params $no_file_cache "$info_file"`;
291	define('TESTED_PHP_VERSION', `$php -n -r "echo PHP_VERSION;"`);
292
293	if ($php_cgi && $php != $php_cgi) {
294		$php_info_cgi = `$php_cgi $pass_options $info_params $no_file_cache -q "$info_file"`;
295		$php_info_sep = "\n---------------------------------------------------------------------";
296		$php_cgi_info = "$php_info_sep\nPHP         : $php_cgi $php_info_cgi$php_info_sep";
297	} else {
298		$php_cgi_info = '';
299	}
300
301	if ($phpdbg) {
302		$phpdbg_info = `$phpdbg $pass_options $info_params $no_file_cache -qrr "$info_file"`;
303		$php_info_sep = "\n---------------------------------------------------------------------";
304		$phpdbg_info = "$php_info_sep\nPHP         : $phpdbg $phpdbg_info$php_info_sep";
305	} else {
306		$phpdbg_info = '';
307	}
308
309	if (function_exists('opcache_invalidate')) {
310		opcache_invalidate($info_file, true);
311	}
312	@unlink($info_file);
313
314	// load list of enabled extensions
315	save_text($info_file, '<?php echo str_replace("Zend OPcache", "opcache", implode(",", get_loaded_extensions())); ?>');
316	$exts_to_test = explode(',',`$php $pass_options $info_params $no_file_cache "$info_file"`);
317	// check for extensions that need special handling and regenerate
318	$info_params_ex = array(
319		'session' => array('session.auto_start=0'),
320		'tidy' => array('tidy.clean_output=0'),
321		'zlib' => array('zlib.output_compression=Off'),
322		'xdebug' => array('xdebug.default_enable=0','xdebug.mode=off'),
323		'mbstring' => array('mbstring.func_overload=0'),
324	);
325
326	foreach($info_params_ex as $ext => $ini_overwrites_ex) {
327		if (in_array($ext, $exts_to_test)) {
328			$ini_overwrites = array_merge($ini_overwrites, $ini_overwrites_ex);
329		}
330	}
331
332	if (function_exists('opcache_invalidate')) {
333		opcache_invalidate($info_file, true);
334	}
335	@unlink($info_file);
336
337	// Write test context information.
338	echo "
339=====================================================================
340PHP         : $php $php_info $php_cgi_info $phpdbg_info
341CWD         : ".TEST_PHP_SRCDIR."
342Extra dirs  : ";
343	foreach ($user_tests as $test_dir) {
344		echo "{$test_dir}\n              ";
345	}
346	echo "
347VALGRIND    : " . ($valgrind ? $valgrind->getHeader() : 'Not used') . "
348=====================================================================
349";
350}
351
352define('PHP_QA_EMAIL', 'qa-reports@lists.php.net');
353define('QA_SUBMISSION_PAGE', 'http://qa.php.net/buildtest-process.php');
354define('QA_REPORTS_PAGE', 'http://qa.php.net/reports');
355define('TRAVIS_CI' , (bool) getenv('TRAVIS'));
356
357function save_or_mail_results()
358{
359	global $sum_results, $just_save_results, $failed_test_summary,
360		   $PHP_FAILED_TESTS, $php, $output_file;
361
362	/* We got failed Tests, offer the user to send an e-mail to QA team, unless NO_INTERACTION is set */
363	if (!getenv('NO_INTERACTION') && !TRAVIS_CI) {
364		$fp = fopen("php://stdin", "r+");
365		if ($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['WARNED'] || $sum_results['LEAKED']) {
366			echo "\nYou may have found a problem in PHP.";
367		}
368		echo "\nThis report can be automatically sent to the PHP QA team at\n";
369		echo QA_REPORTS_PAGE . " and http://news.php.net/php.qa.reports\n";
370		echo "This gives us a better understanding of PHP's behavior.\n";
371		echo "If you don't want to send the report immediately you can choose\n";
372		echo "option \"s\" to save it.	You can then email it to ". PHP_QA_EMAIL . " later.\n";
373		echo "Do you want to send this report now? [Yns]: ";
374		flush();
375
376		$user_input = fgets($fp, 10);
377		$just_save_results = (strtolower($user_input[0]) == 's');
378	}
379
380	if ($just_save_results || !getenv('NO_INTERACTION') || TRAVIS_CI) {
381		if ($just_save_results || TRAVIS_CI || strlen(trim($user_input)) == 0 || strtolower($user_input[0]) == 'y') {
382			/*
383			 * Collect information about the host system for our report
384			 * Fetch phpinfo() output so that we can see the PHP environment
385			 * Make an archive of all the failed tests
386			 * Send an email
387			 */
388			if ($just_save_results) {
389				$user_input = 's';
390			}
391
392			/* Ask the user to provide an email address, so that QA team can contact the user */
393			if (TRAVIS_CI) {
394				$user_email = 'travis at php dot net';
395			} elseif (!strncasecmp($user_input, 'y', 1) || strlen(trim($user_input)) == 0) {
396				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): ";
397				flush();
398				$user_email = trim(fgets($fp, 1024));
399				$user_email = str_replace("@", " at ", str_replace(".", " dot ", $user_email));
400			}
401
402			$failed_tests_data = '';
403			$sep = "\n" . str_repeat('=', 80) . "\n";
404			$failed_tests_data .= $failed_test_summary . "\n";
405			$failed_tests_data .= get_summary(true, false) . "\n";
406
407			if ($sum_results['FAILED']) {
408				foreach ($PHP_FAILED_TESTS['FAILED'] as $test_info) {
409					$failed_tests_data .= $sep . $test_info['name'] . $test_info['info'];
410					$failed_tests_data .= $sep . file_get_contents(realpath($test_info['output']), FILE_BINARY);
411					$failed_tests_data .= $sep . file_get_contents(realpath($test_info['diff']), FILE_BINARY);
412					$failed_tests_data .= $sep . "\n\n";
413				}
414				$status = "failed";
415			} else {
416				$status = "success";
417			}
418
419			$failed_tests_data .= "\n" . $sep . 'BUILD ENVIRONMENT' . $sep;
420			$failed_tests_data .= "OS:\n" . PHP_OS . " - " . php_uname() . "\n\n";
421			$ldd = $autoconf = $sys_libtool = $libtool = $compiler = 'N/A';
422
423			if (substr(PHP_OS, 0, 3) != "WIN") {
424				/* If PHP_AUTOCONF is set, use it; otherwise, use 'autoconf'. */
425				if (getenv('PHP_AUTOCONF')) {
426					$autoconf = shell_exec(getenv('PHP_AUTOCONF') . ' --version');
427				} else {
428					$autoconf = shell_exec('autoconf --version');
429				}
430
431				/* Always use the generated libtool - Mac OSX uses 'glibtool' */
432				$libtool = shell_exec(INIT_DIR . '/libtool --version');
433
434				/* Use shtool to find out if there is glibtool present (MacOSX) */
435				$sys_libtool_path = shell_exec(__DIR__ . '/build/shtool path glibtool libtool');
436
437				if ($sys_libtool_path) {
438					$sys_libtool = shell_exec(str_replace("\n", "", $sys_libtool_path) . ' --version');
439				}
440
441				/* Try the most common flags for 'version' */
442				$flags = array('-v', '-V', '--version');
443				$cc_status = 0;
444
445				foreach($flags AS $flag) {
446					system(getenv('CC') . " $flag >/dev/null 2>&1", $cc_status);
447					if ($cc_status == 0) {
448						$compiler = shell_exec(getenv('CC') . " $flag 2>&1");
449						break;
450					}
451				}
452
453				$ldd = shell_exec("ldd $php 2>/dev/null");
454			}
455
456			$failed_tests_data .= "Autoconf:\n$autoconf\n";
457			$failed_tests_data .= "Bundled Libtool:\n$libtool\n";
458			$failed_tests_data .= "System Libtool:\n$sys_libtool\n";
459			$failed_tests_data .= "Compiler:\n$compiler\n";
460			$failed_tests_data .= "Bison:\n". shell_exec('bison --version 2>/dev/null') . "\n";
461			$failed_tests_data .= "Libraries:\n$ldd\n";
462			$failed_tests_data .= "\n";
463
464			if (isset($user_email)) {
465				$failed_tests_data .= "User's E-mail: " . $user_email . "\n\n";
466			}
467
468			$failed_tests_data .= $sep . "PHPINFO" . $sep;
469			$failed_tests_data .= shell_exec($php . ' -ddisplay_errors=stderr -dhtml_errors=0 -i 2> /dev/null');
470
471			if (($just_save_results || !mail_qa_team($failed_tests_data, $status)) && !TRAVIS_CI) {
472				file_put_contents($output_file, $failed_tests_data);
473
474				if (!$just_save_results) {
475					echo "\nThe test script was unable to automatically send the report to PHP's QA Team\n";
476				}
477
478				echo "Please send " . $output_file . " to " . PHP_QA_EMAIL . " manually, thank you.\n";
479			} elseif (!getenv('NO_INTERACTION') && !TRAVIS_CI) {
480				fwrite($fp, "\nThank you for helping to make PHP better.\n");
481				fclose($fp);
482			}
483		}
484	}
485}
486
487// Determine the tests to be run.
488
489$test_files = array();
490$redir_tests = array();
491$test_results = array();
492$PHP_FAILED_TESTS = array('BORKED' => array(), 'FAILED' => array(), 'WARNED' => array(), 'LEAKED' => array(), 'XFAILED' => array(), 'SLOW' => array());
493
494// If parameters given assume they represent selected tests to run.
495$result_tests_file= false;
496$failed_tests_file= false;
497$pass_option_n = false;
498$pass_options = '';
499
500$output_file = INIT_DIR . '/php_test_results_' . date('Ymd_Hi') . '.txt';
501
502$just_save_results = false;
503$valgrind = null;
504$html_output = false;
505$html_file = null;
506$temp_source = null;
507$temp_target = null;
508$temp_urlbase = null;
509$conf_passed = null;
510$no_clean = false;
511$slow_min_ms = INF;
512
513$cfgtypes = array('show', 'keep');
514$cfgfiles = array('skip', 'php', 'clean', 'out', 'diff', 'exp', 'mem');
515$cfg = array();
516
517foreach($cfgtypes as $type) {
518	$cfg[$type] = array();
519
520	foreach($cfgfiles as $file) {
521		$cfg[$type][$file] = false;
522	}
523}
524
525if (getenv('TEST_PHP_ARGS')) {
526
527	if (!isset($argc, $argv) || !$argc) {
528		$argv = array(__FILE__);
529	}
530
531	$argv = array_merge($argv, explode(' ', getenv('TEST_PHP_ARGS')));
532	$argc = count($argv);
533}
534
535if (isset($argc) && $argc > 1) {
536
537	for ($i=1; $i<$argc; $i++) {
538		$is_switch = false;
539		$switch = substr($argv[$i],1,1);
540		$repeat = substr($argv[$i],0,1) == '-';
541
542		while ($repeat) {
543
544			if (!$is_switch) {
545				$switch = substr($argv[$i],1,1);
546			}
547
548			$is_switch = true;
549
550			if ($repeat) {
551				foreach($cfgtypes as $type) {
552					if (strpos($switch, '--' . $type) === 0) {
553						foreach($cfgfiles as $file) {
554							if ($switch == '--' . $type . '-' . $file) {
555								$cfg[$type][$file] = true;
556								$is_switch = false;
557								break;
558							}
559						}
560					}
561				}
562			}
563
564			if (!$is_switch) {
565				$is_switch = true;
566				break;
567			}
568
569			$repeat = false;
570
571			switch($switch) {
572				case 'r':
573				case 'l':
574					$test_list = file($argv[++$i]);
575					if ($test_list) {
576						foreach($test_list as $test) {
577							$matches = array();
578							if (preg_match('/^#.*\[(.*)\]\:\s+(.*)$/', $test, $matches)) {
579								$redir_tests[] = array($matches[1], $matches[2]);
580							} else if (strlen($test)) {
581								$test_files[] = trim($test);
582							}
583						}
584					}
585					if ($switch != 'l') {
586						break;
587					}
588					$i--;
589					// break left intentionally
590				case 'w':
591					$failed_tests_file = fopen($argv[++$i], 'w+t');
592					break;
593				case 'a':
594					$failed_tests_file = fopen($argv[++$i], 'a+t');
595					break;
596				case 'W':
597					$result_tests_file = fopen($argv[++$i], 'w+t');
598					break;
599				case 'c':
600					$conf_passed = $argv[++$i];
601					break;
602				case 'd':
603					$ini_overwrites[] = $argv[++$i];
604					break;
605				case 'g':
606					$SHOW_ONLY_GROUPS = explode(",", $argv[++$i]);
607					break;
608				//case 'h'
609				case '--keep-all':
610					foreach($cfgfiles as $file) {
611						$cfg['keep'][$file] = true;
612					}
613					break;
614				//case 'l'
615				case 'm':
616					$valgrind = new RuntestsValgrind($environment);
617					break;
618				case 'n':
619					if (!$pass_option_n) {
620						$pass_options .= ' -n';
621					}
622					$pass_option_n = true;
623					break;
624				case 'e':
625					$pass_options .= ' -e';
626					break;
627				case '--no-clean':
628					$no_clean = true;
629					break;
630				case 'p':
631					$php = $argv[++$i];
632					putenv("TEST_PHP_EXECUTABLE=$php");
633					$environment['TEST_PHP_EXECUTABLE'] = $php;
634					break;
635				case 'P':
636					if(constant('PHP_BINARY')) {
637						$php = PHP_BINARY;
638					} else {
639						break;
640					}
641					putenv("TEST_PHP_EXECUTABLE=$php");
642					$environment['TEST_PHP_EXECUTABLE'] = $php;
643					break;
644				case 'q':
645					putenv('NO_INTERACTION=1');
646					break;
647				//case 'r'
648				case 's':
649					$output_file = $argv[++$i];
650					$just_save_results = true;
651					break;
652				case '--set-timeout':
653					$environment['TEST_TIMEOUT'] = $argv[++$i];
654					break;
655				case '--show-all':
656					foreach($cfgfiles as $file) {
657						$cfg['show'][$file] = true;
658					}
659					break;
660				case '--show-slow':
661					$slow_min_ms = $argv[++$i];
662					break;
663				case '--temp-source':
664					$temp_source = $argv[++$i];
665					break;
666				case '--temp-target':
667					$temp_target = $argv[++$i];
668					if ($temp_urlbase) {
669						$temp_urlbase = $temp_target;
670					}
671					break;
672				case '--temp-urlbase':
673					$temp_urlbase = $argv[++$i];
674					break;
675				case 'v':
676				case '--verbose':
677					$DETAILED = true;
678					break;
679				case 'x':
680					$environment['SKIP_SLOW_TESTS'] = 1;
681					break;
682				case '--offline':
683					$environment['SKIP_ONLINE_TESTS'] = 1;
684					break;
685				//case 'w'
686				case '-':
687					// repeat check with full switch
688					$switch = $argv[$i];
689					if ($switch != '-') {
690						$repeat = true;
691					}
692					break;
693				case '--html':
694					$html_file = fopen($argv[++$i], 'wt');
695					$html_output = is_resource($html_file);
696					break;
697				case '--version':
698					echo '$Id: 6e551f4bb4be63fc488bb5568aa27da773720a97 $' . "\n";
699					exit(1);
700
701				default:
702					echo "Illegal switch '$switch' specified!\n";
703				case 'h':
704				case '-help':
705				case '--help':
706					echo <<<HELP
707Synopsis:
708    php run-tests.php [options] [files] [directories]
709
710Options:
711    -l <file>   Read the testfiles to be executed from <file>. After the test
712                has finished all failed tests are written to the same <file>.
713                If the list is empty and no further test is specified then
714                all tests are executed (same as: -r <file> -w <file>).
715
716    -r <file>   Read the testfiles to be executed from <file>.
717
718    -w <file>   Write a list of all failed tests to <file>.
719
720    -a <file>   Same as -w but append rather then truncating <file>.
721
722    -W <file>   Write a list of all tests and their result status to <file>.
723
724    -c <file>   Look for php.ini in directory <file> or use <file> as ini.
725
726    -n          Pass -n option to the php binary (Do not use a php.ini).
727
728    -d foo=bar  Pass -d option to the php binary (Define INI entry foo
729                with value 'bar').
730
731    -g          Comma separated list of groups to show during test run
732                (possible values: PASS, FAIL, XFAIL, SKIP, BORK, WARN, LEAK, REDIRECT).
733
734    -m          Test for memory leaks with Valgrind.
735
736    -p <php>    Specify PHP executable to run.
737
738    -P          Use PHP_BINARY as PHP executable to run.
739
740    -q          Quiet, no user interaction (same as environment NO_INTERACTION).
741
742    -s <file>   Write output to <file>.
743
744    -x          Sets 'SKIP_SLOW_TESTS' environmental variable.
745
746    --offline   Sets 'SKIP_ONLINE_TESTS' environmental variable.
747
748    --verbose
749    -v          Verbose mode.
750
751    --help
752    -h          This Help.
753
754    --html <file> Generate HTML output.
755
756    --temp-source <sdir>  --temp-target <tdir> [--temp-urlbase <url>]
757                Write temporary files to <tdir> by replacing <sdir> from the
758                filenames to generate with <tdir>. If --html is being used and
759                <url> given then the generated links are relative and prefixed
760                with the given url. In general you want to make <sdir> the path
761                to your source files and <tdir> some pach in your web page
762                hierarchy with <url> pointing to <tdir>.
763
764    --keep-[all|php|skip|clean]
765                Do not delete 'all' files, 'php' test file, 'skip' or 'clean'
766                file.
767
768    --set-timeout [n]
769                Set timeout for individual tests, where [n] is the number of
770                seconds. The default value is 60 seconds, or 300 seconds when
771                testing for memory leaks.
772
773    --show-[all|php|skip|clean|exp|diff|out]
774                Show 'all' files, 'php' test file, 'skip' or 'clean' file. You
775                can also use this to show the output 'out', the expected result
776                'exp' or the difference between them 'diff'. The result types
777                get written independent of the log format, however 'diff' only
778                exists when a test fails.
779
780    --show-slow [n]
781                Show all tests that took longer than [n] milliseconds to run.
782
783    --no-clean  Do not execute clean section if any.
784
785HELP;
786					exit(1);
787			}
788		}
789
790		if (!$is_switch) {
791			$testfile = realpath($argv[$i]);
792
793			if (!$testfile && strpos($argv[$i], '*') !== false && function_exists('glob')) {
794
795				if (substr($argv[$i], -5) == '.phpt') {
796					$pattern_match = glob($argv[$i]);
797				} else if (preg_match("/\*$/", $argv[$i])) {
798					$pattern_match = glob($argv[$i] . '.phpt');
799				} else {
800					die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL);
801				}
802
803				if (is_array($pattern_match)) {
804					$test_files = array_merge($test_files, $pattern_match);
805				}
806
807			} else if (is_dir($testfile)) {
808				find_files($testfile);
809			} else if (substr($testfile, -5) == '.phpt') {
810				$test_files[] = $testfile;
811			} else {
812				die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL);
813			}
814		}
815	}
816
817	if (strlen($conf_passed)) {
818		if (substr(PHP_OS, 0, 3) == "WIN") {
819			$pass_options .= " -c " . escapeshellarg($conf_passed);
820		} else {
821			$pass_options .= " -c '" . realpath($conf_passed) . "'";
822		}
823	}
824
825	$test_files = array_unique($test_files);
826	$test_files = array_merge($test_files, $redir_tests);
827
828	// Run selected tests.
829	$test_cnt = count($test_files);
830
831	if ($test_cnt) {
832		putenv('NO_INTERACTION=1');
833		verify_config();
834		write_information();
835		usort($test_files, "test_sort");
836		$start_time = time();
837
838		if (!$html_output) {
839			echo "Running selected tests.\n";
840		} else {
841			show_start($start_time);
842		}
843
844		$test_idx = 0;
845		run_all_tests($test_files, $environment);
846		$end_time = time();
847
848		if ($html_output) {
849			show_end($end_time);
850		}
851
852		if ($failed_tests_file) {
853			fclose($failed_tests_file);
854		}
855
856		if ($result_tests_file) {
857			fclose($result_tests_file);
858		}
859
860		compute_summary();
861		if ($html_output) {
862			fwrite($html_file, "<hr/>\n" . get_summary(false, true));
863		}
864		echo "=====================================================================";
865		echo get_summary(false, false);
866
867		if ($html_output) {
868			fclose($html_file);
869		}
870
871		if ($output_file != '' && $just_save_results) {
872			save_or_mail_results();
873		}
874
875		junit_save_xml();
876
877		if (getenv('REPORT_EXIT_STATUS') !== '0' &&
878			getenv('REPORT_EXIT_STATUS') !== 'no' &&
879			($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['LEAKED'])) {
880			exit(1);
881		}
882
883		exit(0);
884	}
885}
886
887verify_config();
888write_information();
889
890// Compile a list of all test files (*.phpt).
891$test_files = array();
892$exts_tested = count($exts_to_test);
893$exts_skipped = 0;
894$ignored_by_ext = 0;
895sort($exts_to_test);
896$test_dirs = array();
897$optionals = array('tests', 'ext', 'Zend', 'sapi');
898
899foreach($optionals as $dir) {
900	if (is_dir($dir)) {
901		$test_dirs[] = $dir;
902	}
903}
904
905// Convert extension names to lowercase
906foreach ($exts_to_test as $key => $val) {
907	$exts_to_test[$key] = strtolower($val);
908}
909
910foreach ($test_dirs as $dir) {
911	find_files(TEST_PHP_SRCDIR."/{$dir}", $dir == 'ext');
912}
913
914foreach ($user_tests as $dir) {
915	find_files($dir, $dir == 'ext');
916}
917
918function find_files($dir, $is_ext_dir = false, $ignore = false)
919{
920	global $test_files, $exts_to_test, $ignored_by_ext, $exts_skipped;
921
922	$o = opendir($dir) or error("cannot open directory: $dir");
923
924	while (($name = readdir($o)) !== false) {
925
926		if (is_dir("{$dir}/{$name}") && !in_array($name, array('.', '..', '.svn'))) {
927			$skip_ext = ($is_ext_dir && !in_array(strtolower($name), $exts_to_test));
928			if ($skip_ext) {
929				$exts_skipped++;
930			}
931			find_files("{$dir}/{$name}", false, $ignore || $skip_ext);
932		}
933
934		// Cleanup any left-over tmp files from last run.
935		if (substr($name, -4) == '.tmp') {
936			@unlink("$dir/$name");
937			continue;
938		}
939
940		// Otherwise we're only interested in *.phpt files.
941		if (substr($name, -5) == '.phpt') {
942			if ($ignore) {
943				$ignored_by_ext++;
944			} else {
945				$testfile = realpath("{$dir}/{$name}");
946				$test_files[] = $testfile;
947			}
948		}
949	}
950
951	closedir($o);
952}
953
954function test_name($name)
955{
956	if (is_array($name)) {
957		return $name[0] . ':' . $name[1];
958	} else {
959		return $name;
960	}
961}
962
963function test_sort($a, $b)
964{
965	$a = test_name($a);
966	$b = test_name($b);
967
968	$ta = strpos($a, TEST_PHP_SRCDIR."/tests") === 0 ? 1 + (strpos($a, TEST_PHP_SRCDIR."/tests/run-test") === 0 ? 1 : 0) : 0;
969	$tb = strpos($b, TEST_PHP_SRCDIR."/tests") === 0 ? 1 + (strpos($b, TEST_PHP_SRCDIR."/tests/run-test") === 0 ? 1 : 0) : 0;
970
971	if ($ta == $tb) {
972		return strcmp($a, $b);
973	} else {
974		return $tb - $ta;
975	}
976}
977
978$test_files = array_unique($test_files);
979usort($test_files, "test_sort");
980
981$start_time = time();
982show_start($start_time);
983
984$test_cnt = count($test_files);
985$test_idx = 0;
986run_all_tests($test_files, $environment);
987$end_time = time();
988
989if ($failed_tests_file) {
990	fclose($failed_tests_file);
991}
992
993if ($result_tests_file) {
994	fclose($result_tests_file);
995}
996
997// Summarize results
998
999if (0 == count($test_results)) {
1000	echo "No tests were run.\n";
1001	return;
1002}
1003
1004compute_summary();
1005
1006show_end($end_time);
1007show_summary();
1008
1009if ($html_output) {
1010	fclose($html_file);
1011}
1012
1013save_or_mail_results();
1014
1015junit_save_xml();
1016if (getenv('REPORT_EXIT_STATUS') !== '0' &&
1017	getenv('REPORT_EXIT_STATUS') !== 'no' &&
1018	($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['LEAKED'])) {
1019	exit(1);
1020}
1021exit(0);
1022
1023//
1024// Send Email to QA Team
1025//
1026
1027function mail_qa_team($data, $status = false)
1028{
1029	$url_bits = parse_url(QA_SUBMISSION_PAGE);
1030
1031	if ($proxy = getenv('http_proxy')) {
1032		$proxy = parse_url($proxy);
1033		$path = $url_bits['host'].$url_bits['path'];
1034		$host = $proxy['host'];
1035		if (empty($proxy['port'])) {
1036			$proxy['port'] = 80;
1037		}
1038		$port = $proxy['port'];
1039	} else {
1040		$path = $url_bits['path'];
1041		$host = $url_bits['host'];
1042		$port = empty($url_bits['port']) ? 80 : $port = $url_bits['port'];
1043	}
1044
1045	$data = "php_test_data=" . urlencode(base64_encode(str_replace("\00", '[0x0]', $data)));
1046	$data_length = strlen($data);
1047
1048	$fs = fsockopen($host, $port, $errno, $errstr, 10);
1049
1050	if (!$fs) {
1051		return false;
1052	}
1053
1054	$php_version = urlencode(TESTED_PHP_VERSION);
1055
1056	echo "\nPosting to ". QA_SUBMISSION_PAGE . "\n";
1057	fwrite($fs, "POST " . $path . "?status=$status&version=$php_version HTTP/1.1\r\n");
1058	fwrite($fs, "Host: " . $host . "\r\n");
1059	fwrite($fs, "User-Agent: QA Browser 0.1\r\n");
1060	fwrite($fs, "Content-Type: application/x-www-form-urlencoded\r\n");
1061	fwrite($fs, "Content-Length: " . $data_length . "\r\n\r\n");
1062	fwrite($fs, $data);
1063	fwrite($fs, "\r\n\r\n");
1064	fclose($fs);
1065
1066	return 1;
1067}
1068
1069
1070//
1071//  Write the given text to a temporary file, and return the filename.
1072//
1073
1074function save_text($filename, $text, $filename_copy = null)
1075{
1076	global $DETAILED;
1077
1078	if ($filename_copy && $filename_copy != $filename) {
1079		if (file_put_contents($filename_copy, $text, FILE_BINARY) === false) {
1080			error("Cannot open file '" . $filename_copy . "' (save_text)");
1081		}
1082	}
1083
1084	if (file_put_contents($filename, $text, FILE_BINARY) === false) {
1085		error("Cannot open file '" . $filename . "' (save_text)");
1086	}
1087
1088	if (1 < $DETAILED) echo "
1089FILE $filename {{{
1090$text
1091}}}
1092";
1093}
1094
1095//
1096//  Write an error in a format recognizable to Emacs or MSVC.
1097//
1098
1099function error_report($testname, $logname, $tested)
1100{
1101	$testname = realpath($testname);
1102	$logname  = realpath($logname);
1103
1104	switch (strtoupper(getenv('TEST_PHP_ERROR_STYLE'))) {
1105		case 'MSVC':
1106			echo $testname . "(1) : $tested\n";
1107			echo $logname . "(1) :  $tested\n";
1108			break;
1109		case 'EMACS':
1110			echo $testname . ":1: $tested\n";
1111			echo $logname . ":1:  $tested\n";
1112			break;
1113	}
1114}
1115
1116function system_with_timeout($commandline, $env = null, $stdin = null, $captureStdIn = true, $captureStdOut = true, $captureStdErr = true)
1117{
1118	global $valgrind;
1119
1120	$data = '';
1121
1122	$bin_env = array();
1123	foreach((array)$env as $key => $value) {
1124		$bin_env[$key] = $value;
1125	}
1126
1127	$descriptorspec = array();
1128	if ($captureStdIn) {
1129		$descriptorspec[0] = array('pipe', 'r');
1130	}
1131	if ($captureStdOut) {
1132		$descriptorspec[1] = array('pipe', 'w');
1133	}
1134	if ($captureStdErr) {
1135		$descriptorspec[2] = array('pipe', 'w');
1136	}
1137	$proc = proc_open($commandline, $descriptorspec, $pipes, TEST_PHP_SRCDIR, $bin_env, array('suppress_errors' => true));
1138
1139	if (!$proc) {
1140		return false;
1141	}
1142
1143	if ($captureStdIn) {
1144		if (!is_null($stdin)) {
1145			fwrite($pipes[0], $stdin);
1146		}
1147		fclose($pipes[0]);
1148		unset($pipes[0]);
1149	}
1150
1151	$timeout = $valgrind ? 300 : ($env['TEST_TIMEOUT'] ?? 60);
1152
1153	while (true) {
1154		/* hide errors from interrupted syscalls */
1155		$r = $pipes;
1156		$w = null;
1157		$e = null;
1158
1159		$n = @stream_select($r, $w, $e, $timeout);
1160
1161		if ($n === false) {
1162			break;
1163		} else if ($n === 0) {
1164			/* timed out */
1165			$data .= "\n ** ERROR: process timed out **\n";
1166			proc_terminate($proc, 9);
1167			return $data;
1168		} else if ($n > 0) {
1169			if ($captureStdOut) {
1170				$line = fread($pipes[1], 8192);
1171			} elseif ($captureStdErr) {
1172				$line = fread($pipes[2], 8192);
1173			} else {
1174				$line = '';
1175			}
1176			if (strlen($line) == 0) {
1177				/* EOF */
1178				break;
1179			}
1180			$data .= $line;
1181		}
1182	}
1183
1184	$stat = proc_get_status($proc);
1185
1186	if ($stat['signaled']) {
1187		$data .= "\nTermsig=" . $stat['stopsig'] . "\n";
1188	}
1189	if ($stat["exitcode"] > 128 && $stat["exitcode"] < 160) {
1190		$data .= "\nTermsig=" . ($stat["exitcode"] - 128) . "\n";
1191	}
1192
1193	proc_close($proc);
1194	return $data;
1195}
1196
1197function run_all_tests($test_files, $env, $redir_tested = null)
1198{
1199	global $test_results, $failed_tests_file, $result_tests_file, $php, $test_idx;
1200
1201	foreach($test_files as $name) {
1202
1203		if (is_array($name)) {
1204			$index = "# $name[1]: $name[0]";
1205
1206			if ($redir_tested) {
1207				$name = $name[0];
1208			}
1209		} else if ($redir_tested) {
1210			$index = "# $redir_tested: $name";
1211		} else {
1212			$index = $name;
1213		}
1214		$test_idx++;
1215		$result = run_test($php, $name, $env);
1216
1217		if (!is_array($name) && $result != 'REDIR') {
1218			$test_results[$index] = $result;
1219			if ($failed_tests_file && ($result == 'XFAILED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) {
1220				fwrite($failed_tests_file, "$index\n");
1221			}
1222			if ($result_tests_file) {
1223				fwrite($result_tests_file, "$result\t$index\n");
1224			}
1225		}
1226	}
1227}
1228
1229//
1230//  Show file or result block
1231//
1232function show_file_block($file, $block, $section = null)
1233{
1234	global $cfg;
1235
1236	if ($cfg['show'][$file]) {
1237
1238		if (is_null($section)) {
1239			$section = strtoupper($file);
1240		}
1241
1242		echo "\n========" . $section . "========\n";
1243		echo rtrim($block);
1244		echo "\n========DONE========\n";
1245	}
1246}
1247
1248//
1249//  Run an individual test case.
1250//
1251function run_test($php, $file, $env)
1252{
1253	global $log_format, $ini_overwrites, $PHP_FAILED_TESTS;
1254	global $pass_options, $DETAILED, $IN_REDIRECT, $test_cnt, $test_idx;
1255	global $valgrind, $temp_source, $temp_target, $cfg, $environment;
1256	global $no_clean;
1257	global $SHOW_ONLY_GROUPS;
1258	global $no_file_cache;
1259	global $slow_min_ms;
1260	$temp_filenames = null;
1261	$org_file = $file;
1262
1263	if (isset($env['TEST_PHP_CGI_EXECUTABLE'])) {
1264		$php_cgi = $env['TEST_PHP_CGI_EXECUTABLE'];
1265	}
1266
1267	if (isset($env['TEST_PHPDBG_EXECUTABLE'])) {
1268		$phpdbg = $env['TEST_PHPDBG_EXECUTABLE'];
1269	}
1270
1271	if (is_array($file)) {
1272		$file = $file[0];
1273	}
1274
1275	if ($DETAILED) echo "
1276=================
1277TEST $file
1278";
1279
1280	// Load the sections of the test file.
1281	$section_text = array('TEST' => '');
1282
1283	$fp = fopen($file, "rb") or error("Cannot open test file: $file");
1284
1285	$bork_info = null;
1286
1287	if (!feof($fp)) {
1288		$line = fgets($fp);
1289
1290		if ($line === false) {
1291			$bork_info = "cannot read test";
1292		}
1293	} else {
1294		$bork_info = "empty test [$file]";
1295	}
1296	if ($bork_info === null && strncmp('--TEST--', $line, 8)) {
1297		$bork_info = "tests must start with --TEST-- [$file]";
1298	}
1299
1300	$section = 'TEST';
1301	$secfile = false;
1302	$secdone = false;
1303
1304	while (!feof($fp)) {
1305		$line = fgets($fp);
1306
1307		if ($line === false) {
1308			break;
1309		}
1310
1311		// Match the beginning of a section.
1312		if (preg_match('/^--([_A-Z]+)--/', $line, $r)) {
1313			$section = (string) $r[1];
1314
1315			if (isset($section_text[$section]) && $section_text[$section]) {
1316				$bork_info = "duplicated $section section";
1317			}
1318
1319			// check for unknown sections
1320			if (!in_array($section, array(
1321				'EXPECT', 'EXPECTF', 'EXPECTREGEX', 'EXPECTREGEX_EXTERNAL', 'EXPECT_EXTERNAL', 'EXPECTF_EXTERNAL', 'EXPECTHEADERS',
1322				'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS', 'REQUEST', 'HEADERS',
1323				'FILE', 'FILEEOF', 'FILE_EXTERNAL', 'REDIRECTTEST',
1324				'CAPTURE_STDIO', 'STDIN', 'CGI', 'PHPDBG',
1325				'INI', 'ENV', 'EXTENSIONS',
1326				'SKIPIF', 'XFAIL', 'CLEAN',
1327				'CREDITS', 'DESCRIPTION', 'CONFLICTS',
1328			))) {
1329				$bork_info = 'Unknown section "' . $section . '"';
1330			}
1331
1332			$section_text[$section] = '';
1333			$secfile = $section == 'FILE' || $section == 'FILEEOF' || $section == 'FILE_EXTERNAL';
1334			$secdone = false;
1335			continue;
1336		}
1337
1338		// Add to the section text.
1339		if (!$secdone) {
1340			$section_text[$section] .= $line;
1341		}
1342
1343		// End of actual test?
1344		if ($secfile && preg_match('/^===DONE===\s*$/', $line)) {
1345			$secdone = true;
1346		}
1347	}
1348
1349	// the redirect section allows a set of tests to be reused outside of
1350	// a given test dir
1351	if ($bork_info === null) {
1352		if (isset($section_text['REDIRECTTEST'])) {
1353
1354			if ($IN_REDIRECT) {
1355				$bork_info = "Can't redirect a test from within a redirected test";
1356			}
1357
1358		} else {
1359
1360			if (!isset($section_text['PHPDBG']) && @count($section_text['FILE']) + @count($section_text['FILEEOF']) + @count($section_text['FILE_EXTERNAL']) != 1) {
1361				$bork_info = "missing section --FILE--";
1362			}
1363
1364			if (isset($section_text['FILEEOF'])) {
1365				$section_text['FILE'] = preg_replace("/[\r\n]+$/", '', $section_text['FILEEOF']);
1366				unset($section_text['FILEEOF']);
1367			}
1368
1369			foreach (array( 'FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX' ) as $prefix) {
1370				$key = $prefix . '_EXTERNAL';
1371
1372				if (isset($section_text[$key])) {
1373					// don't allow tests to retrieve files from anywhere but this subdirectory
1374					$section_text[$key] = dirname($file) . '/' . trim(str_replace('..', '', $section_text[$key]));
1375
1376					if (file_exists($section_text[$key])) {
1377						$section_text[$prefix] = file_get_contents($section_text[$key], FILE_BINARY);
1378						unset($section_text[$key]);
1379					} else {
1380						$bork_info = "could not load --" . $key . "-- " . dirname($file) . '/' . trim($section_text[$key]);
1381					}
1382				}
1383			}
1384
1385			if ((@count($section_text['EXPECT']) + @count($section_text['EXPECTF']) + @count($section_text['EXPECTREGEX'])) != 1) {
1386				$bork_info = "missing section --EXPECT--, --EXPECTF-- or --EXPECTREGEX--";
1387			}
1388		}
1389	}
1390	fclose($fp);
1391
1392	$shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file);
1393	$tested_file = $shortname;
1394
1395	if ($bork_info !== null) {
1396		show_result("BORK", $bork_info, $tested_file);
1397		$PHP_FAILED_TESTS['BORKED'][] = array (
1398								'name'      => $file,
1399								'test_name' => '',
1400								'output'    => '',
1401								'diff'      => '',
1402								'info'      => "$bork_info [$file]",
1403		);
1404
1405		junit_mark_test_as('BORK', $shortname, $tested_file, 0, $bork_info);
1406		return 'BORKED';
1407	}
1408
1409	if (isset($section_text['CAPTURE_STDIO'])) {
1410		$captureStdIn = stripos($section_text['CAPTURE_STDIO'], 'STDIN') !== false;
1411		$captureStdOut = stripos($section_text['CAPTURE_STDIO'], 'STDOUT') !== false;
1412		$captureStdErr = stripos($section_text['CAPTURE_STDIO'], 'STDERR') !== false;
1413	} else {
1414		$captureStdIn = true;
1415		$captureStdOut = true;
1416		$captureStdErr = true;
1417	}
1418	if ($captureStdOut && $captureStdErr) {
1419		$cmdRedirect = ' 2>&1';
1420	} else {
1421		$cmdRedirect = '';
1422	}
1423
1424	$tested = trim($section_text['TEST']);
1425
1426	/* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */
1427	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'])) {
1428		if (isset($php_cgi)) {
1429			$php = $php_cgi . ' -C ';
1430		} else if (!strncasecmp(PHP_OS, "win", 3) && file_exists(dirname($php) . "/php-cgi.exe")) {
1431			$php = realpath(dirname($php) . "/php-cgi.exe") . ' -C ';
1432		} else {
1433			if (file_exists(dirname($php) . "/../../sapi/cgi/php-cgi")) {
1434				$php = realpath(dirname($php) . "/../../sapi/cgi/php-cgi") . ' -C ';
1435			} else if (file_exists("./sapi/cgi/php-cgi")) {
1436				$php = realpath("./sapi/cgi/php-cgi") . ' -C ';
1437			} else if (file_exists(dirname($php) . "/php-cgi")) {
1438				$php = realpath(dirname($php) . "/php-cgi") . ' -C ';
1439			} else {
1440				show_result('SKIP', $tested, $tested_file, "reason: CGI not available");
1441
1442				junit_init_suite(junit_get_suitename_for($shortname));
1443				junit_mark_test_as('SKIP', $shortname, $tested, 0, 'CGI not available');
1444				return 'SKIPPED';
1445			}
1446		}
1447		$uses_cgi = true;
1448	}
1449
1450	/* For phpdbg tests, check if phpdbg sapi is available and if it is, use it. */
1451	if (array_key_exists('PHPDBG', $section_text)) {
1452		if (!isset($section_text['STDIN'])) {
1453			$section_text['STDIN'] = $section_text['PHPDBG']."\n";
1454		}
1455
1456		if (isset($phpdbg)) {
1457			$php = $phpdbg . ' -qIb';
1458		} else {
1459			show_result('SKIP', $tested, $tested_file, "reason: phpdbg not available");
1460
1461			junit_init_suite(junit_get_suitename_for($shortname));
1462			junit_mark_test_as('SKIP', $shortname, $tested, 0, 'phpdbg not available');
1463			return 'SKIPPED';
1464		}
1465	}
1466
1467	if (!$SHOW_ONLY_GROUPS) {
1468		show_test($test_idx, $shortname);
1469	}
1470
1471	if (is_array($IN_REDIRECT)) {
1472		$temp_dir = $test_dir = $IN_REDIRECT['dir'];
1473	} else {
1474		$temp_dir = $test_dir = realpath(dirname($file));
1475	}
1476
1477	if ($temp_source && $temp_target) {
1478		$temp_dir = str_replace($temp_source, $temp_target, $temp_dir);
1479	}
1480
1481	$main_file_name = basename($file,'phpt');
1482
1483	$diff_filename     = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'diff';
1484	$log_filename      = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'log';
1485	$exp_filename      = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'exp';
1486	$output_filename   = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'out';
1487	$memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'mem';
1488	$sh_filename       = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'sh';
1489	$temp_file         = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php';
1490	$test_file         = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php';
1491	$temp_skipif       = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php';
1492	$test_skipif       = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php';
1493	$temp_clean        = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php';
1494	$test_clean        = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php';
1495	$tmp_post          = $temp_dir . DIRECTORY_SEPARATOR . uniqid('/phpt.');
1496	$tmp_relative_file = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $test_file) . 't';
1497
1498	if ($temp_source && $temp_target) {
1499		$temp_skipif  .= 's';
1500		$temp_file    .= 's';
1501		$temp_clean   .= 's';
1502		$copy_file     = $temp_dir . DIRECTORY_SEPARATOR . basename(is_array($file) ? $file[1] : $file) . '.phps';
1503
1504		if (!is_dir(dirname($copy_file))) {
1505			mkdir(dirname($copy_file), 0777, true) or error("Cannot create output directory - " . dirname($copy_file));
1506		}
1507
1508		if (isset($section_text['FILE'])) {
1509			save_text($copy_file, $section_text['FILE']);
1510		}
1511
1512		$temp_filenames = array(
1513			'file' => $copy_file,
1514			'diff' => $diff_filename,
1515			'log'  => $log_filename,
1516			'exp'  => $exp_filename,
1517			'out'  => $output_filename,
1518			'mem'  => $memcheck_filename,
1519			'sh'   => $sh_filename,
1520			'php'  => $temp_file,
1521			'skip' => $temp_skipif,
1522			'clean'=> $temp_clean);
1523	}
1524
1525	if (is_array($IN_REDIRECT)) {
1526		$tested = $IN_REDIRECT['prefix'] . ' ' . trim($section_text['TEST']);
1527		$tested_file = $tmp_relative_file;
1528	}
1529
1530	// unlink old test results
1531	@unlink($diff_filename);
1532	@unlink($log_filename);
1533	@unlink($exp_filename);
1534	@unlink($output_filename);
1535	@unlink($memcheck_filename);
1536	@unlink($sh_filename);
1537	@unlink($temp_file);
1538	@unlink($test_file);
1539	@unlink($temp_skipif);
1540	@unlink($test_skipif);
1541	@unlink($tmp_post);
1542	@unlink($temp_clean);
1543	@unlink($test_clean);
1544
1545	// Reset environment from any previous test.
1546	$env['REDIRECT_STATUS'] = '';
1547	$env['QUERY_STRING']    = '';
1548	$env['PATH_TRANSLATED'] = '';
1549	$env['SCRIPT_FILENAME'] = '';
1550	$env['REQUEST_METHOD']  = '';
1551	$env['CONTENT_TYPE']    = '';
1552	$env['CONTENT_LENGTH']  = '';
1553	$env['TZ']              = '';
1554
1555	if (!empty($section_text['ENV'])) {
1556
1557		foreach(explode("\n", trim($section_text['ENV'])) as $e) {
1558			$e = explode('=', trim($e), 2);
1559
1560			if (!empty($e[0]) && isset($e[1])) {
1561				$env[$e[0]] = $e[1];
1562			}
1563		}
1564	}
1565
1566	// Default ini settings
1567	$ini_settings = array();
1568
1569	// Additional required extensions
1570	if (array_key_exists('EXTENSIONS', $section_text)) {
1571		$ext_params = array();
1572		settings2array($ini_overwrites, $ext_params);
1573		settings2params($ext_params);
1574		$ext_dir=`$php $pass_options $ext_params -d display_errors=0 -r "echo ini_get('extension_dir');"`;
1575		$extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS']));
1576		$loaded = explode(",", `$php $pass_options $ext_params -d display_errors=0 -r "echo implode(',', get_loaded_extensions());"`);
1577		$ext_prefix = substr(PHP_OS, 0, 3) === "WIN" ? "php_" : "";
1578		foreach ($extensions as $req_ext) {
1579			if (!in_array($req_ext, $loaded)) {
1580				if ($req_ext == 'opcache') {
1581					$ini_settings['zend_extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX;
1582				} else {
1583					$ini_settings['extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX;
1584				}
1585			}
1586		}
1587	}
1588
1589	// additional ini overwrites
1590	//$ini_overwrites[] = 'setting=value';
1591	settings2array($ini_overwrites, $ini_settings);
1592
1593	// Any special ini settings
1594	// these may overwrite the test defaults...
1595	if (array_key_exists('INI', $section_text)) {
1596		$section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']);
1597		$section_text['INI'] = str_replace('{TMP}', sys_get_temp_dir(), $section_text['INI']);
1598		settings2array(preg_split( "/[\n\r]+/", $section_text['INI']), $ini_settings);
1599	}
1600
1601	settings2params($ini_settings);
1602
1603	$env['TEST_PHP_EXTRA_ARGS'] = $pass_options . ' ' . $ini_settings;
1604
1605	// Check if test should be skipped.
1606	$info = '';
1607	$warn = false;
1608
1609	if (array_key_exists('SKIPIF', $section_text)) {
1610
1611		if (trim($section_text['SKIPIF'])) {
1612			show_file_block('skip', $section_text['SKIPIF']);
1613			save_text($test_skipif, $section_text['SKIPIF'], $temp_skipif);
1614			$extra = substr(PHP_OS, 0, 3) !== "WIN" ?
1615				"unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;": "";
1616
1617			if ($valgrind) {
1618				$env['USE_ZEND_ALLOC'] = '0';
1619				$env['ZEND_DONT_UNLOAD_MODULES'] = 1;
1620			} else {
1621				$env['USE_ZEND_ALLOC'] = '1';
1622				$env['ZEND_DONT_UNLOAD_MODULES'] = 0;
1623			}
1624
1625			junit_start_timer($shortname);
1626
1627			$output = system_with_timeout("$extra $php $pass_options -q $ini_settings $no_file_cache -d display_errors=0 \"$test_skipif\"", $env);
1628
1629			junit_finish_timer($shortname);
1630
1631			if (!$cfg['keep']['skip']) {
1632				@unlink($test_skipif);
1633			}
1634
1635			if (!strncasecmp('skip', ltrim($output), 4)) {
1636
1637				if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) {
1638					show_result('SKIP', $tested, $tested_file, "reason: $m[1]", $temp_filenames);
1639				} else {
1640					show_result('SKIP', $tested, $tested_file, '', $temp_filenames);
1641				}
1642
1643				if (!$cfg['keep']['skip']) {
1644					@unlink($test_skipif);
1645				}
1646
1647				$message = !empty($m[1]) ? $m[1] : '';
1648				junit_mark_test_as('SKIP', $shortname, $tested, null, $message);
1649				return 'SKIPPED';
1650			}
1651
1652			if (!strncasecmp('info', ltrim($output), 4)) {
1653				if (preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) {
1654					$info = " (info: $m[1])";
1655				}
1656			}
1657
1658			if (!strncasecmp('warn', ltrim($output), 4)) {
1659				if (preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) {
1660					$warn = true; /* only if there is a reason */
1661					$info = " (warn: $m[1])";
1662				}
1663			}
1664
1665			if (!strncasecmp('xfail', ltrim($output), 5)) {
1666				// Pretend we have an XFAIL section
1667				$section_text['XFAIL'] = trim(substr(ltrim($output), 5));
1668			}
1669		}
1670	}
1671
1672	if (!extension_loaded("zlib")
1673	&& (	array_key_exists("GZIP_POST", $section_text)
1674		||	array_key_exists("DEFLATE_POST", $section_text))
1675	) {
1676		$message = "ext/zlib required";
1677		show_result('SKIP', $tested, $tested_file, "reason: $message", $temp_filenames);
1678		junit_mark_test_as('SKIP', $shortname, $tested, null, $message);
1679		return 'SKIPPED';
1680	}
1681
1682	if (isset($section_text['REDIRECTTEST'])) {
1683		$test_files = array();
1684
1685		$IN_REDIRECT = eval($section_text['REDIRECTTEST']);
1686		$IN_REDIRECT['via'] = "via [$shortname]\n\t";
1687		$IN_REDIRECT['dir'] = realpath(dirname($file));
1688		$IN_REDIRECT['prefix'] = trim($section_text['TEST']);
1689
1690		if (!empty($IN_REDIRECT['TESTS'])) {
1691
1692			if (is_array($org_file)) {
1693				$test_files[] = $org_file[1];
1694			} else {
1695				$GLOBALS['test_files'] = $test_files;
1696				find_files($IN_REDIRECT['TESTS']);
1697
1698				foreach($GLOBALS['test_files'] as $f) {
1699					$test_files[] = array($f, $file);
1700				}
1701			}
1702			$test_cnt += @count($test_files) - 1;
1703			$test_idx--;
1704
1705			show_redirect_start($IN_REDIRECT['TESTS'], $tested, $tested_file);
1706
1707			// set up environment
1708			$redirenv = array_merge($environment, $IN_REDIRECT['ENV']);
1709			$redirenv['REDIR_TEST_DIR'] = realpath($IN_REDIRECT['TESTS']) . DIRECTORY_SEPARATOR;
1710
1711			usort($test_files, "test_sort");
1712			run_all_tests($test_files, $redirenv, $tested);
1713
1714			show_redirect_ends($IN_REDIRECT['TESTS'], $tested, $tested_file);
1715
1716			// a redirected test never fails
1717			$IN_REDIRECT = false;
1718
1719			junit_mark_test_as('PASS', $shortname, $tested);
1720			return 'REDIR';
1721
1722		} else {
1723
1724			$bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory.";
1725			show_result("BORK", $bork_info, '', $temp_filenames);
1726			$PHP_FAILED_TESTS['BORKED'][] = array (
1727									'name' => $file,
1728									'test_name' => '',
1729									'output' => '',
1730									'diff'   => '',
1731									'info'   => "$bork_info [$file]",
1732			);
1733		}
1734	}
1735
1736	if (is_array($org_file) || @count($section_text['REDIRECTTEST']) == 1) {
1737
1738		if (is_array($org_file)) {
1739			$file = $org_file[0];
1740		}
1741
1742		$bork_info = "Redirected test did not contain redirection info";
1743		show_result("BORK", $bork_info, '', $temp_filenames);
1744		$PHP_FAILED_TESTS['BORKED'][] = array (
1745								'name' => $file,
1746								'test_name' => '',
1747								'output' => '',
1748								'diff'   => '',
1749								'info'   => "$bork_info [$file]",
1750		);
1751
1752		junit_mark_test_as('BORK', $shortname, $tested, null, $bork_info);
1753
1754		return 'BORKED';
1755	}
1756
1757	// We've satisfied the preconditions - run the test!
1758	if (isset($section_text['FILE'])) {
1759		show_file_block('php', $section_text['FILE'], 'TEST');
1760		save_text($test_file, $section_text['FILE'], $temp_file);
1761	} else {
1762		$test_file = $temp_file = "";
1763	}
1764
1765	if (array_key_exists('GET', $section_text)) {
1766		$query_string = trim($section_text['GET']);
1767	} else {
1768		$query_string = '';
1769	}
1770
1771	$env['REDIRECT_STATUS'] = '1';
1772	if (empty($env['QUERY_STRING'])) {
1773		$env['QUERY_STRING']    = $query_string;
1774	}
1775	if (empty($env['PATH_TRANSLATED'])) {
1776		$env['PATH_TRANSLATED'] = $test_file;
1777	}
1778	if (empty($env['SCRIPT_FILENAME'])) {
1779		$env['SCRIPT_FILENAME'] = $test_file;
1780	}
1781
1782	if (array_key_exists('COOKIE', $section_text)) {
1783		$env['HTTP_COOKIE'] = trim($section_text['COOKIE']);
1784	} else {
1785		$env['HTTP_COOKIE'] = '';
1786	}
1787
1788	$args = isset($section_text['ARGS']) ? ' -- ' . $section_text['ARGS'] : '';
1789
1790	if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) {
1791
1792		$post = trim($section_text['POST_RAW']);
1793		$raw_lines = explode("\n", $post);
1794
1795		$request = '';
1796		$started = false;
1797
1798		foreach ($raw_lines as $line) {
1799
1800			if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) {
1801				$env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1]));
1802				continue;
1803			}
1804
1805			if ($started) {
1806				$request .= "\n";
1807			}
1808
1809			$started = true;
1810			$request .= $line;
1811		}
1812
1813		$env['CONTENT_LENGTH'] = strlen($request);
1814		$env['REQUEST_METHOD'] = 'POST';
1815
1816		if (empty($request)) {
1817			junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request');
1818			return 'BORKED';
1819		}
1820
1821		save_text($tmp_post, $request);
1822		$cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";
1823
1824	} elseif (array_key_exists('PUT', $section_text) && !empty($section_text['PUT'])) {
1825
1826		$post = trim($section_text['PUT']);
1827		$raw_lines = explode("\n", $post);
1828
1829		$request = '';
1830		$started = false;
1831
1832		foreach ($raw_lines as $line) {
1833
1834			if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) {
1835				$env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1]));
1836				continue;
1837			}
1838
1839			if ($started) {
1840				$request .= "\n";
1841			}
1842
1843			$started = true;
1844			$request .= $line;
1845		}
1846
1847		$env['CONTENT_LENGTH'] = strlen($request);
1848		$env['REQUEST_METHOD'] = 'PUT';
1849
1850		if (empty($request)) {
1851			junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request');
1852			return 'BORKED';
1853		}
1854
1855		save_text($tmp_post, $request);
1856		$cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";
1857
1858	} else if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) {
1859
1860		$post = trim($section_text['POST']);
1861		$content_length = strlen($post);
1862		save_text($tmp_post, $post);
1863
1864		$env['REQUEST_METHOD'] = 'POST';
1865		if (empty($env['CONTENT_TYPE'])) {
1866			$env['CONTENT_TYPE']   = 'application/x-www-form-urlencoded';
1867		}
1868
1869		if (empty($env['CONTENT_LENGTH'])) {
1870			$env['CONTENT_LENGTH'] = $content_length;
1871		}
1872
1873		$cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";
1874
1875	} else if (array_key_exists('GZIP_POST', $section_text) && !empty($section_text['GZIP_POST'])) {
1876
1877		$post = trim($section_text['GZIP_POST']);
1878		$post = gzencode($post, 9, FORCE_GZIP);
1879		$env['HTTP_CONTENT_ENCODING'] = 'gzip';
1880
1881		save_text($tmp_post, $post);
1882		$content_length = strlen($post);
1883
1884		$env['REQUEST_METHOD'] = 'POST';
1885		$env['CONTENT_TYPE']   = 'application/x-www-form-urlencoded';
1886		$env['CONTENT_LENGTH'] = $content_length;
1887
1888		$cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";
1889
1890	} else if (array_key_exists('DEFLATE_POST', $section_text) && !empty($section_text['DEFLATE_POST'])) {
1891		$post = trim($section_text['DEFLATE_POST']);
1892		$post = gzcompress($post, 9);
1893		$env['HTTP_CONTENT_ENCODING'] = 'deflate';
1894		save_text($tmp_post, $post);
1895		$content_length = strlen($post);
1896
1897		$env['REQUEST_METHOD'] = 'POST';
1898		$env['CONTENT_TYPE']   = 'application/x-www-form-urlencoded';
1899		$env['CONTENT_LENGTH'] = $content_length;
1900
1901		$cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\"";
1902
1903	} else {
1904
1905		$env['REQUEST_METHOD'] = 'GET';
1906		$env['CONTENT_TYPE']   = '';
1907		$env['CONTENT_LENGTH'] = '';
1908
1909		$cmd = "$php $pass_options $ini_settings -f \"$test_file\" $args$cmdRedirect";
1910	}
1911
1912	if ($valgrind) {
1913		$env['USE_ZEND_ALLOC'] = '0';
1914		$env['ZEND_DONT_UNLOAD_MODULES'] = 1;
1915
1916		$cmd = $valgrind->wrapCommand($cmd, $memcheck_filename, strpos($test_file, "pcre") !== false);
1917	} else {
1918		$env['USE_ZEND_ALLOC'] = '1';
1919		$env['ZEND_DONT_UNLOAD_MODULES'] = 0;
1920	}
1921
1922	if ($DETAILED) echo "
1923CONTENT_LENGTH  = " . $env['CONTENT_LENGTH'] . "
1924CONTENT_TYPE    = " . $env['CONTENT_TYPE'] . "
1925PATH_TRANSLATED = " . $env['PATH_TRANSLATED'] . "
1926QUERY_STRING    = " . $env['QUERY_STRING'] . "
1927REDIRECT_STATUS = " . $env['REDIRECT_STATUS'] . "
1928REQUEST_METHOD  = " . $env['REQUEST_METHOD'] . "
1929SCRIPT_FILENAME = " . $env['SCRIPT_FILENAME'] . "
1930HTTP_COOKIE     = " . $env['HTTP_COOKIE'] . "
1931COMMAND $cmd
1932";
1933
1934	junit_start_timer($shortname);
1935	$hrtime = hrtime();
1936	$startTime = $hrtime[0]*1000000000 + $hrtime[1];
1937
1938	$out = system_with_timeout($cmd, $env, $section_text['STDIN'] ?? null, $captureStdIn, $captureStdOut, $captureStdErr);
1939
1940	junit_finish_timer($shortname);
1941	$hrtime = hrtime();
1942	$time = $hrtime[0]*1000000000 + $hrtime[1] - $startTime;
1943	if ($time >= $slow_min_ms * 1000000) {
1944		$PHP_FAILED_TESTS['SLOW'][] = array(
1945			'name'      => $file,
1946			'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]",
1947			'output'    => '',
1948			'diff'      => '',
1949			'info'      => $time / 1000000000,
1950		);
1951	}
1952
1953	if (array_key_exists('CLEAN', $section_text) && (!$no_clean || $cfg['keep']['clean'])) {
1954
1955		if (trim($section_text['CLEAN'])) {
1956			show_file_block('clean', $section_text['CLEAN']);
1957			save_text($test_clean, trim($section_text['CLEAN']), $temp_clean);
1958
1959			if (!$no_clean) {
1960				$clean_params = array();
1961				settings2array($ini_overwrites, $clean_params);
1962				settings2params($clean_params);
1963				$extra = substr(PHP_OS, 0, 3) !== "WIN" ?
1964					"unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;": "";
1965				system_with_timeout("$extra $php $pass_options -q $clean_params $no_file_cache \"$test_clean\"", $env);
1966			}
1967
1968			if (!$cfg['keep']['clean']) {
1969				@unlink($test_clean);
1970			}
1971		}
1972	}
1973
1974	$leaked = false;
1975	$passed = false;
1976
1977	if ($valgrind) { // leak check
1978		$leaked = filesize($memcheck_filename) > 0;
1979
1980		if (!$leaked) {
1981			@unlink($memcheck_filename);
1982		}
1983	}
1984
1985	// Does the output match what is expected?
1986	$output = preg_replace("/\r\n/", "\n", trim($out));
1987
1988	/* when using CGI, strip the headers from the output */
1989	$headers = array();
1990
1991	if (!empty($uses_cgi) && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) {
1992		$output = trim($match[2]);
1993		$rh = preg_split("/[\n\r]+/", $match[1]);
1994
1995		foreach ($rh as $line) {
1996			if (strpos($line, ':') !== false) {
1997				$line = explode(':', $line, 2);
1998				$headers[trim($line[0])] = trim($line[1]);
1999			}
2000		}
2001	}
2002
2003	$failed_headers = false;
2004
2005	if (isset($section_text['EXPECTHEADERS'])) {
2006		$want = array();
2007		$wanted_headers = array();
2008		$lines = preg_split("/[\n\r]+/", $section_text['EXPECTHEADERS']);
2009
2010		foreach($lines as $line) {
2011			if (strpos($line, ':') !== false) {
2012				$line = explode(':', $line, 2);
2013				$want[trim($line[0])] = trim($line[1]);
2014				$wanted_headers[] = trim($line[0]) . ': ' . trim($line[1]);
2015			}
2016		}
2017
2018		$output_headers = array();
2019
2020		foreach($want as $k => $v) {
2021
2022			if (isset($headers[$k])) {
2023				$output_headers[] = $k . ': ' . $headers[$k];
2024			}
2025
2026			if (!isset($headers[$k]) || $headers[$k] != $v) {
2027				$failed_headers = true;
2028			}
2029		}
2030
2031		ksort($wanted_headers);
2032		$wanted_headers = implode("\n", $wanted_headers);
2033		ksort($output_headers);
2034		$output_headers = implode("\n", $output_headers);
2035	}
2036
2037	show_file_block('out', $output);
2038
2039	if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) {
2040
2041		if (isset($section_text['EXPECTF'])) {
2042			$wanted = trim($section_text['EXPECTF']);
2043		} else {
2044			$wanted = trim($section_text['EXPECTREGEX']);
2045		}
2046
2047		show_file_block('exp', $wanted);
2048		$wanted_re = preg_replace('/\r\n/', "\n", $wanted);
2049
2050		if (isset($section_text['EXPECTF'])) {
2051
2052			// do preg_quote, but miss out any %r delimited sections
2053			$temp = "";
2054			$r = "%r";
2055			$startOffset = 0;
2056			$length = strlen($wanted_re);
2057			while($startOffset < $length) {
2058				$start = strpos($wanted_re, $r, $startOffset);
2059				if ($start !== false) {
2060					// we have found a start tag
2061					$end = strpos($wanted_re, $r, $start+2);
2062					if ($end === false) {
2063						// unbalanced tag, ignore it.
2064						$end = $start = $length;
2065					}
2066				} else {
2067					// no more %r sections
2068					$start = $end = $length;
2069				}
2070				// quote a non re portion of the string
2071				$temp .= preg_quote(substr($wanted_re, $startOffset, $start - $startOffset),  '/');
2072				// add the re unquoted.
2073				if ($end > $start) {
2074					$temp .= '(' . substr($wanted_re, $start+2, $end - $start-2). ')';
2075				}
2076				$startOffset = $end + 2;
2077			}
2078			$wanted_re = $temp;
2079
2080			// Stick to basics
2081			$wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re);
2082			$wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re);
2083			$wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re);
2084			$wanted_re = str_replace('%a', '.+', $wanted_re);
2085			$wanted_re = str_replace('%A', '.*', $wanted_re);
2086			$wanted_re = str_replace('%w', '\s*', $wanted_re);
2087			$wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re);
2088			$wanted_re = str_replace('%d', '\d+', $wanted_re);
2089			$wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re);
2090			$wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re);
2091			$wanted_re = str_replace('%c', '.', $wanted_re);
2092			// %f allows two points "-.0.0" but that is the best *simple* expression
2093		}
2094/* DEBUG YOUR REGEX HERE
2095		var_dump($wanted_re);
2096		print(str_repeat('=', 80) . "\n");
2097		var_dump($output);
2098*/
2099		if (preg_match("/^$wanted_re\$/s", $output)) {
2100			$passed = true;
2101			if (!$cfg['keep']['php']) {
2102				@unlink($test_file);
2103			}
2104
2105			if (!$leaked && !$failed_headers) {
2106				if (isset($section_text['XFAIL'] )) {
2107					$warn = true;
2108					$info = " (warn: XFAIL section but test passes)";
2109				}else {
2110					show_result("PASS", $tested, $tested_file, '', $temp_filenames);
2111					junit_mark_test_as('PASS', $shortname, $tested);
2112					return 'PASSED';
2113				}
2114			}
2115		}
2116
2117	} else {
2118
2119		$wanted = trim($section_text['EXPECT']);
2120		$wanted = preg_replace('/\r\n/',"\n", $wanted);
2121		show_file_block('exp', $wanted);
2122
2123		// compare and leave on success
2124		if (!strcmp($output, $wanted)) {
2125			$passed = true;
2126
2127			if (!$cfg['keep']['php']) {
2128				@unlink($test_file);
2129			}
2130
2131			if (!$leaked && !$failed_headers) {
2132				if (isset($section_text['XFAIL'] )) {
2133					$warn = true;
2134					$info = " (warn: XFAIL section but test passes)";
2135				}else {
2136					show_result("PASS", $tested, $tested_file, '', $temp_filenames);
2137					junit_mark_test_as('PASS', $shortname, $tested);
2138					return 'PASSED';
2139				}
2140			}
2141		}
2142
2143		$wanted_re = null;
2144	}
2145
2146	// Test failed so we need to report details.
2147	if ($failed_headers) {
2148		$passed = false;
2149		$wanted = $wanted_headers . "\n--HEADERS--\n" . $wanted;
2150		$output = $output_headers . "\n--HEADERS--\n" . $output;
2151
2152		if (isset($wanted_re)) {
2153			$wanted_re = preg_quote($wanted_headers . "\n--HEADERS--\n", '/') . $wanted_re;
2154		}
2155	}
2156
2157	if ($leaked) {
2158		$restype[] = 'LEAK';
2159	}
2160
2161	if ($warn) {
2162		$restype[] = 'WARN';
2163	}
2164
2165	if ($passed) {
2166		@unlink($tmp_post);
2167	}
2168
2169	if (!$passed) {
2170		if (isset($section_text['XFAIL'])) {
2171			$restype[] = 'XFAIL';
2172			$info = '  XFAIL REASON: ' . rtrim($section_text['XFAIL']);
2173		} else {
2174			$restype[] = 'FAIL';
2175		}
2176	}
2177
2178	if (!$passed) {
2179
2180		// write .exp
2181		if (strpos($log_format, 'E') !== false && file_put_contents($exp_filename, $wanted, FILE_BINARY) === false) {
2182			error("Cannot create expected test output - $exp_filename");
2183		}
2184
2185		// write .out
2186		if (strpos($log_format, 'O') !== false && file_put_contents($output_filename, $output, FILE_BINARY) === false) {
2187			error("Cannot create test output - $output_filename");
2188		}
2189
2190		// write .diff
2191		$diff = generate_diff($wanted, $wanted_re, $output);
2192		if (is_array($IN_REDIRECT)) {
2193			$diff = "# original source file: $shortname\n" . $diff;
2194		}
2195		show_file_block('diff', $diff);
2196		if (strpos($log_format, 'D') !== false && file_put_contents($diff_filename, $diff, FILE_BINARY) === false) {
2197			error("Cannot create test diff - $diff_filename");
2198		}
2199
2200		// write .sh
2201		if (strpos($log_format, 'S') !== false && file_put_contents($sh_filename, "#!/bin/sh
2202
2203{$cmd}
2204", FILE_BINARY) === false) {
2205			error("Cannot create test shell script - $sh_filename");
2206		}
2207		chmod($sh_filename, 0755);
2208
2209		// write .log
2210		if (strpos($log_format, 'L') !== false && file_put_contents($log_filename, "
2211---- EXPECTED OUTPUT
2212$wanted
2213---- ACTUAL OUTPUT
2214$output
2215---- FAILED
2216", FILE_BINARY) === false) {
2217			error("Cannot create test log - $log_filename");
2218			error_report($file, $log_filename, $tested);
2219		}
2220	}
2221
2222	if ($valgrind && $leaked && $cfg["show"]["mem"]) {
2223		show_file_block('mem', file_get_contents($memcheck_filename));
2224	}
2225
2226	show_result(implode('&', $restype), $tested, $tested_file, $info, $temp_filenames);
2227
2228	foreach ($restype as $type) {
2229		$PHP_FAILED_TESTS[$type.'ED'][] = array (
2230			'name'      => $file,
2231			'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]",
2232			'output'    => $output_filename,
2233			'diff'      => $diff_filename,
2234			'info'      => $info,
2235		);
2236	}
2237
2238	$diff = empty($diff) ? '' : preg_replace('/\e/', '<esc>', $diff);
2239
2240	junit_mark_test_as($restype, str_replace(TEST_PHP_SRCDIR . '/', '', $tested_file), $tested, null, $info, $diff);
2241
2242	return $restype[0] . 'ED';
2243}
2244
2245function comp_line($l1, $l2, $is_reg)
2246{
2247	if ($is_reg) {
2248		return preg_match('/^'. $l1 . '$/s', $l2);
2249	} else {
2250		return !strcmp($l1, $l2);
2251	}
2252}
2253
2254function count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2, $cnt1, $cnt2, $steps)
2255{
2256	$equal = 0;
2257
2258	while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) {
2259		$idx1++;
2260		$idx2++;
2261		$equal++;
2262		$steps--;
2263	}
2264	if (--$steps > 0) {
2265		$eq1 = 0;
2266		$st = $steps / 2;
2267
2268		for ($ofs1 = $idx1 + 1; $ofs1 < $cnt1 && $st-- > 0; $ofs1++) {
2269			$eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $ofs1, $idx2, $cnt1, $cnt2, $st);
2270
2271			if ($eq > $eq1) {
2272				$eq1 = $eq;
2273			}
2274		}
2275
2276		$eq2 = 0;
2277		$st = $steps;
2278
2279		for ($ofs2 = $idx2 + 1; $ofs2 < $cnt2 && $st-- > 0; $ofs2++) {
2280			$eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $ofs2, $cnt1, $cnt2, $st);
2281			if ($eq > $eq2) {
2282				$eq2 = $eq;
2283			}
2284		}
2285
2286		if ($eq1 > $eq2) {
2287			$equal += $eq1;
2288		} else if ($eq2 > 0) {
2289			$equal += $eq2;
2290		}
2291	}
2292
2293	return $equal;
2294}
2295
2296function generate_array_diff($ar1, $ar2, $is_reg, $w)
2297{
2298	$idx1 = 0; $cnt1 = @count($ar1);
2299	$idx2 = 0; $cnt2 = @count($ar2);
2300	$diff = array();
2301	$old1 = array();
2302	$old2 = array();
2303
2304	while ($idx1 < $cnt1 && $idx2 < $cnt2) {
2305
2306		if (comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) {
2307			$idx1++;
2308			$idx2++;
2309			continue;
2310		} else {
2311
2312			$c1 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1+1, $idx2, $cnt1, $cnt2, 10);
2313			$c2 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2+1, $cnt1,  $cnt2, 10);
2314
2315			if ($c1 > $c2) {
2316				$old1[$idx1] = sprintf("%03d- ", $idx1+1) . $w[$idx1++];
2317			} else if ($c2 > 0) {
2318				$old2[$idx2] = sprintf("%03d+ ", $idx2+1) . $ar2[$idx2++];
2319			} else {
2320				$old1[$idx1] = sprintf("%03d- ", $idx1+1) . $w[$idx1++];
2321				$old2[$idx2] = sprintf("%03d+ ", $idx2+1) . $ar2[$idx2++];
2322			}
2323		}
2324	}
2325
2326	reset($old1); $k1 = key($old1); $l1 = -2;
2327	reset($old2); $k2 = key($old2); $l2 = -2;
2328
2329	while ($k1 !== null || $k2 !== null) {
2330
2331		if ($k1 == $l1 + 1 || $k2 === null) {
2332			$l1 = $k1;
2333			$diff[] = current($old1);
2334			$k1 = next($old1) ? key($old1) : null;
2335		} else if ($k2 == $l2 + 1 || $k1 === null) {
2336			$l2 = $k2;
2337			$diff[] = current($old2);
2338			$k2 = next($old2) ? key($old2) : null;
2339		} else if ($k1 < $k2) {
2340			$l1 = $k1;
2341			$diff[] = current($old1);
2342			$k1 = next($old1) ? key($old1) : null;
2343		} else {
2344			$l2 = $k2;
2345			$diff[] = current($old2);
2346			$k2 = next($old2) ? key($old2) : null;
2347		}
2348	}
2349
2350	while ($idx1 < $cnt1) {
2351		$diff[] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++];
2352	}
2353
2354	while ($idx2 < $cnt2) {
2355		$diff[] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++];
2356	}
2357
2358	return $diff;
2359}
2360
2361function generate_diff($wanted, $wanted_re, $output)
2362{
2363	$w = explode("\n", $wanted);
2364	$o = explode("\n", $output);
2365	$r = is_null($wanted_re) ? $w : explode("\n", $wanted_re);
2366	$diff = generate_array_diff($r, $o, !is_null($wanted_re), $w);
2367
2368	return implode("\r\n", $diff);
2369}
2370
2371function error($message)
2372{
2373	echo "ERROR: {$message}\n";
2374	exit(1);
2375}
2376
2377function settings2array($settings, &$ini_settings)
2378{
2379	foreach($settings as $setting) {
2380
2381		if (strpos($setting, '=') !== false) {
2382			$setting = explode("=", $setting, 2);
2383			$name = trim($setting[0]);
2384			$value = trim($setting[1]);
2385
2386			if ($name == 'extension' || $name == 'zend_extension') {
2387
2388				if (!isset($ini_settings[$name])) {
2389					$ini_settings[$name] = array();
2390				}
2391
2392				$ini_settings[$name][] = $value;
2393
2394			} else {
2395				$ini_settings[$name] = $value;
2396			}
2397		}
2398	}
2399}
2400
2401function settings2params(&$ini_settings)
2402{
2403	$settings = '';
2404
2405	foreach($ini_settings as $name => $value) {
2406
2407		if (is_array($value)) {
2408			foreach($value as $val) {
2409				$val = addslashes($val);
2410				$settings .= " -d \"$name=$val\"";
2411			}
2412		} else {
2413			if (substr(PHP_OS, 0, 3) == "WIN" && !empty($value) && $value{0} == '"') {
2414				$len = strlen($value);
2415
2416				if ($value{$len - 1} == '"') {
2417					$value{0} = "'";
2418					$value{$len - 1} = "'";
2419				}
2420			} else {
2421				$value = addslashes($value);
2422			}
2423
2424			$settings .= " -d \"$name=$value\"";
2425		}
2426	}
2427
2428	$ini_settings = $settings;
2429}
2430
2431function compute_summary()
2432{
2433	global $n_total, $test_results, $ignored_by_ext, $sum_results, $percent_results;
2434
2435	$n_total = count($test_results);
2436	$n_total += $ignored_by_ext;
2437	$sum_results = array(
2438		'PASSED'  => 0,
2439		'WARNED'  => 0,
2440		'SKIPPED' => 0,
2441		'FAILED'  => 0,
2442		'BORKED'  => 0,
2443		'LEAKED'  => 0,
2444		'XFAILED' => 0
2445	);
2446
2447	foreach ($test_results as $v) {
2448		$sum_results[$v]++;
2449	}
2450
2451	$sum_results['SKIPPED'] += $ignored_by_ext;
2452	$percent_results = array();
2453
2454	foreach ($sum_results as $v => $n) {
2455		$percent_results[$v] = (100.0 * $n) / $n_total;
2456	}
2457}
2458
2459function get_summary($show_ext_summary, $show_html)
2460{
2461	global $exts_skipped, $exts_tested, $n_total, $sum_results, $percent_results, $end_time, $start_time, $failed_test_summary, $PHP_FAILED_TESTS, $valgrind;
2462
2463	$x_total = $n_total - $sum_results['SKIPPED'] - $sum_results['BORKED'];
2464
2465	if ($x_total) {
2466		$x_warned = (100.0 * $sum_results['WARNED']) / $x_total;
2467		$x_failed = (100.0 * $sum_results['FAILED']) / $x_total;
2468		$x_xfailed = (100.0 * $sum_results['XFAILED']) / $x_total;
2469		$x_leaked = (100.0 * $sum_results['LEAKED']) / $x_total;
2470		$x_passed = (100.0 * $sum_results['PASSED']) / $x_total;
2471	} else {
2472		$x_warned = $x_failed = $x_passed = $x_leaked = $x_xfailed = 0;
2473	}
2474
2475	$summary = '';
2476
2477	if ($show_html) {
2478		$summary .= "<pre>\n";
2479	}
2480
2481	if ($show_ext_summary) {
2482		$summary .= '
2483=====================================================================
2484TEST RESULT SUMMARY
2485---------------------------------------------------------------------
2486Exts skipped    : ' . sprintf('%4d', $exts_skipped) . '
2487Exts tested     : ' . sprintf('%4d', $exts_tested) . '
2488---------------------------------------------------------------------
2489';
2490	}
2491
2492	$summary .= '
2493Number of tests : ' . sprintf('%4d', $n_total) . '          ' . sprintf('%8d', $x_total);
2494
2495	if ($sum_results['BORKED']) {
2496		$summary .= '
2497Tests borked    : ' . sprintf('%4d (%5.1f%%)', $sum_results['BORKED'], $percent_results['BORKED']) . ' --------';
2498	}
2499
2500	$summary .= '
2501Tests skipped   : ' . sprintf('%4d (%5.1f%%)', $sum_results['SKIPPED'], $percent_results['SKIPPED']) . ' --------
2502Tests warned    : ' . sprintf('%4d (%5.1f%%)', $sum_results['WARNED'], $percent_results['WARNED']) . ' ' . sprintf('(%5.1f%%)', $x_warned) . '
2503Tests failed    : ' . sprintf('%4d (%5.1f%%)', $sum_results['FAILED'], $percent_results['FAILED']) . ' ' . sprintf('(%5.1f%%)', $x_failed) . '
2504Expected fail   : ' . sprintf('%4d (%5.1f%%)', $sum_results['XFAILED'], $percent_results['XFAILED']) . ' ' . sprintf('(%5.1f%%)', $x_xfailed);
2505
2506	if ($valgrind) {
2507		$summary .= '
2508Tests leaked    : ' . sprintf('%4d (%5.1f%%)', $sum_results['LEAKED'], $percent_results['LEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_leaked);
2509	}
2510
2511	$summary .= '
2512Tests passed    : ' . sprintf('%4d (%5.1f%%)', $sum_results['PASSED'], $percent_results['PASSED']) . ' ' . sprintf('(%5.1f%%)', $x_passed) . '
2513---------------------------------------------------------------------
2514Time taken      : ' . sprintf('%4d seconds', $end_time - $start_time) . '
2515=====================================================================
2516';
2517	$failed_test_summary = '';
2518
2519	if (count($PHP_FAILED_TESTS['SLOW'])) {
2520		usort($PHP_FAILED_TESTS['SLOW'], function($a, $b) {
2521			return $a['info'] < $b['info'] ? 1 : -1;
2522		});
2523
2524		$failed_test_summary .= '
2525=====================================================================
2526SLOW TEST SUMMARY
2527---------------------------------------------------------------------
2528';
2529		foreach ($PHP_FAILED_TESTS['SLOW'] as $failed_test_data) {
2530			$failed_test_summary .= sprintf('(%.3f s) ', $failed_test_data['info']) . $failed_test_data['test_name'] . "\n";
2531		}
2532		$failed_test_summary .=  "=====================================================================\n";
2533	}
2534
2535	if (count($PHP_FAILED_TESTS['XFAILED'])) {
2536		$failed_test_summary .= '
2537=====================================================================
2538EXPECTED FAILED TEST SUMMARY
2539---------------------------------------------------------------------
2540';
2541		foreach ($PHP_FAILED_TESTS['XFAILED'] as $failed_test_data) {
2542			$failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2543		}
2544		$failed_test_summary .=  "=====================================================================\n";
2545	}
2546
2547	if (count($PHP_FAILED_TESTS['BORKED'])) {
2548		$failed_test_summary .= '
2549=====================================================================
2550BORKED TEST SUMMARY
2551---------------------------------------------------------------------
2552';
2553		foreach ($PHP_FAILED_TESTS['BORKED'] as $failed_test_data) {
2554			$failed_test_summary .= $failed_test_data['info'] . "\n";
2555		}
2556
2557		$failed_test_summary .=  "=====================================================================\n";
2558	}
2559
2560	if (count($PHP_FAILED_TESTS['FAILED'])) {
2561		$failed_test_summary .= '
2562=====================================================================
2563FAILED TEST SUMMARY
2564---------------------------------------------------------------------
2565';
2566		foreach ($PHP_FAILED_TESTS['FAILED'] as $failed_test_data) {
2567			$failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2568		}
2569		$failed_test_summary .=  "=====================================================================\n";
2570	}
2571	if (count($PHP_FAILED_TESTS['WARNED'])) {
2572		$failed_test_summary .= '
2573=====================================================================
2574WARNED TEST SUMMARY
2575---------------------------------------------------------------------
2576';
2577		foreach ($PHP_FAILED_TESTS['WARNED'] as $failed_test_data) {
2578			$failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2579		}
2580
2581		$failed_test_summary .=  "=====================================================================\n";
2582	}
2583
2584	if (count($PHP_FAILED_TESTS['LEAKED'])) {
2585		$failed_test_summary .= '
2586=====================================================================
2587LEAKED TEST SUMMARY
2588---------------------------------------------------------------------
2589';
2590		foreach ($PHP_FAILED_TESTS['LEAKED'] as $failed_test_data) {
2591			$failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2592		}
2593
2594		$failed_test_summary .=  "=====================================================================\n";
2595	}
2596
2597	if ($failed_test_summary && !getenv('NO_PHPTEST_SUMMARY')) {
2598		$summary .= $failed_test_summary;
2599	}
2600
2601	if ($show_html) {
2602		$summary .= "</pre>";
2603	}
2604
2605	return $summary;
2606}
2607
2608function show_start($start_time)
2609{
2610	global $html_output, $html_file;
2611
2612	if ($html_output) {
2613		fwrite($html_file, "<h2>Time Start: " . date('Y-m-d H:i:s', $start_time) . "</h2>\n");
2614		fwrite($html_file, "<table>\n");
2615	}
2616
2617	echo "TIME START " . date('Y-m-d H:i:s', $start_time) . "\n=====================================================================\n";
2618}
2619
2620function show_end($end_time)
2621{
2622	global $html_output, $html_file;
2623
2624	if ($html_output) {
2625		fwrite($html_file, "</table>\n");
2626		fwrite($html_file, "<h2>Time End: " . date('Y-m-d H:i:s', $end_time) . "</h2>\n");
2627	}
2628
2629	echo "=====================================================================\nTIME END " . date('Y-m-d H:i:s', $end_time) . "\n";
2630}
2631
2632function show_summary()
2633{
2634	global $html_output, $html_file;
2635
2636	if ($html_output) {
2637		fwrite($html_file, "<hr/>\n" . get_summary(true, true));
2638	}
2639
2640	echo get_summary(true, false);
2641}
2642
2643function show_redirect_start($tests, $tested, $tested_file)
2644{
2645	global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS;
2646
2647	if ($html_output) {
2648		fwrite($html_file, "<tr><td colspan='3'>---&gt; $tests ($tested [$tested_file]) begin</td></tr>\n");
2649	}
2650
2651	if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) {
2652		   echo "REDIRECT $tests ($tested [$tested_file]) begin\n";
2653	} else {
2654		   // Write over the last line to avoid random trailing chars on next echo
2655		   echo str_repeat(" ", $line_length), "\r";
2656	}
2657}
2658
2659function show_redirect_ends($tests, $tested, $tested_file)
2660{
2661	global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS;
2662
2663	if ($html_output) {
2664		fwrite($html_file, "<tr><td colspan='3'>---&gt; $tests ($tested [$tested_file]) done</td></tr>\n");
2665	}
2666
2667	if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) {
2668		   echo "REDIRECT $tests ($tested [$tested_file]) done\n";
2669	} else {
2670		   // Write over the last line to avoid random trailing chars on next echo
2671		   echo str_repeat(" ", $line_length), "\r";
2672	}
2673}
2674
2675function show_test($test_idx, $shortname)
2676{
2677	global $test_cnt;
2678	global $line_length;
2679
2680	$str = "TEST $test_idx/$test_cnt [$shortname]\r";
2681	$line_length = strlen($str);
2682	echo $str;
2683	flush();
2684}
2685
2686function show_result($result, $tested, $tested_file, $extra = '', $temp_filenames = null)
2687{
2688	global $html_output, $html_file, $temp_target, $temp_urlbase, $line_length, $SHOW_ONLY_GROUPS;
2689
2690	if (!$SHOW_ONLY_GROUPS || in_array($result, $SHOW_ONLY_GROUPS)) {
2691		echo "$result $tested [$tested_file] $extra\n";
2692	} else if (!$SHOW_ONLY_GROUPS) {
2693		// Write over the last line to avoid random trailing chars on next echo
2694		echo str_repeat(" ", $line_length), "\r";
2695	}
2696
2697	if ($html_output) {
2698
2699		if (isset($temp_filenames['file']) && file_exists($temp_filenames['file'])) {
2700			$url = str_replace($temp_target, $temp_urlbase, $temp_filenames['file']);
2701			$tested = "<a href='$url'>$tested</a>";
2702		}
2703
2704		if (isset($temp_filenames['skip']) && file_exists($temp_filenames['skip'])) {
2705
2706			if (empty($extra)) {
2707				$extra = "skipif";
2708			}
2709
2710			$url = str_replace($temp_target, $temp_urlbase, $temp_filenames['skip']);
2711			$extra = "<a href='$url'>$extra</a>";
2712
2713		} else if (empty($extra)) {
2714			$extra = "&nbsp;";
2715		}
2716
2717		if (isset($temp_filenames['diff']) && file_exists($temp_filenames['diff'])) {
2718			$url = str_replace($temp_target, $temp_urlbase, $temp_filenames['diff']);
2719			$diff = "<a href='$url'>diff</a>";
2720		} else {
2721			$diff = "&nbsp;";
2722		}
2723
2724		if (isset($temp_filenames['mem']) && file_exists($temp_filenames['mem'])) {
2725			$url = str_replace($temp_target, $temp_urlbase, $temp_filenames['mem']);
2726			$mem = "<a href='$url'>leaks</a>";
2727		} else {
2728			$mem = "&nbsp;";
2729		}
2730
2731		fwrite($html_file,
2732			"<tr>" .
2733			"<td>$result</td>" .
2734			"<td>$tested</td>" .
2735			"<td>$extra</td>" .
2736			"<td>$diff</td>" .
2737			"<td>$mem</td>" .
2738			"</tr>\n");
2739	}
2740}
2741
2742function junit_init() {
2743	// Check whether a junit log is wanted.
2744	$JUNIT = getenv('TEST_PHP_JUNIT');
2745	if (empty($JUNIT)) {
2746		$JUNIT = FALSE;
2747	} elseif (!$fp = fopen($JUNIT, 'w')) {
2748		error("Failed to open $JUNIT for writing.");
2749	} else {
2750		$JUNIT = array(
2751			'fp'            => $fp,
2752			'name'          => 'PHP',
2753			'test_total'    => 0,
2754			'test_pass'     => 0,
2755			'test_fail'     => 0,
2756			'test_error'    => 0,
2757			'test_skip'     => 0,
2758			'test_warn'     => 0,
2759			'execution_time'=> 0,
2760			'suites'        => array(),
2761			'files'         => array()
2762		);
2763	}
2764
2765	$GLOBALS['JUNIT'] = $JUNIT;
2766}
2767
2768function junit_save_xml() {
2769	global $JUNIT;
2770	if (!junit_enabled()) return;
2771
2772	$xml = '<' . '?' . 'xml version="1.0" encoding="UTF-8"' . '?' . '>'. PHP_EOL;
2773    $xml .= sprintf(
2774		'<testsuites name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL,
2775        $JUNIT['name'], $JUNIT['test_total'], $JUNIT['test_fail'], $JUNIT['test_error'], $JUNIT['test_skip'],
2776		$JUNIT['execution_time']
2777	);
2778	$xml .= junit_get_suite_xml();
2779	$xml .= '</testsuites>';
2780	fwrite($JUNIT['fp'], $xml);
2781}
2782
2783function junit_get_suite_xml($suite_name = '') {
2784	global $JUNIT;
2785
2786	$result = "";
2787
2788	foreach ($JUNIT['suites'] as $suite_name => $suite) {
2789		$result .= sprintf(
2790			'<testsuite name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL,
2791			$suite['name'], $suite['test_total'], $suite['test_fail'], $suite['test_error'], $suite['test_skip'],
2792			$suite['execution_time']
2793		);
2794
2795		if (!empty($suite_name)) {
2796			foreach($suite['files'] as $file) {
2797				$result .= $JUNIT['files'][$file]['xml'];
2798			}
2799		}
2800
2801		$result .= '</testsuite>' . PHP_EOL;
2802	}
2803
2804	return $result;
2805}
2806
2807function junit_enabled() {
2808	global $JUNIT;
2809	return !empty($JUNIT);
2810}
2811
2812/**
2813 * @param array|string $type
2814 * @param string $file_name
2815 * @param string $test_name
2816 * @param int|string $time
2817 * @param string $message
2818 * @param string $details
2819 * @return void
2820 */
2821function junit_mark_test_as($type, $file_name, $test_name, $time = null, $message = '', $details = '') {
2822	global $JUNIT;
2823	if (!junit_enabled()) return;
2824
2825	$suite = junit_get_suitename_for($file_name);
2826
2827	junit_suite_record($suite, 'test_total');
2828
2829	$time = $time ?? junit_get_timer($file_name);
2830	junit_suite_record($suite, 'execution_time', $time);
2831
2832	$escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8');
2833	$escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function ($c) {
2834		return sprintf('[[0x%02x]]', ord($c[0]));
2835	}, $escaped_details);
2836	$escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
2837
2838    $escaped_test_name = htmlspecialchars($test_name, ENT_QUOTES);
2839    $JUNIT['files'][$file_name]['xml'] = "<testcase classname='" . $suite . "." . basename($file_name) . "' name='$escaped_test_name' time='$time'>\n";
2840
2841	if (is_array($type)) {
2842		$output_type = $type[0] . 'ED';
2843		$temp = array_intersect(array('XFAIL', 'FAIL', 'WARN'), $type);
2844		$type = reset($temp);
2845	} else {
2846		$output_type = $type . 'ED';
2847	}
2848
2849	if ('PASS' == $type || 'XFAIL' == $type) {
2850		junit_suite_record($suite, 'test_pass');
2851	} elseif ('BORK' == $type) {
2852		junit_suite_record($suite, 'test_error');
2853		$JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'/>\n";
2854	} elseif ('SKIP' == $type) {
2855		junit_suite_record($suite, 'test_skip');
2856		$JUNIT['files'][$file_name]['xml'] .= "<skipped>$escaped_message</skipped>\n";
2857	} elseif ('WARN' == $type) {
2858		junit_suite_record($suite, 'test_warn');
2859		$JUNIT['files'][$file_name]['xml'] .= "<warning>$escaped_message</warning>\n";
2860	} elseif ('FAIL' == $type) {
2861		junit_suite_record($suite, 'test_fail');
2862		$JUNIT['files'][$file_name]['xml'] .= "<failure type='$output_type' message='$escaped_message'>$escaped_details</failure>\n";
2863	} else {
2864		junit_suite_record($suite, 'test_error');
2865		$JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'>$escaped_details</error>\n";
2866	}
2867
2868	$JUNIT['files'][$file_name]['xml'] .= "</testcase>\n";
2869
2870}
2871
2872function junit_suite_record($suite, $param, $value = 1) {
2873	global $JUNIT;
2874
2875	$JUNIT[$param] += $value;
2876	$JUNIT['suites'][$suite][$param] += $value;
2877}
2878
2879function junit_get_timer($file_name) {
2880	global $JUNIT;
2881	if (!junit_enabled()) return 0;
2882
2883	if (isset($JUNIT['files'][$file_name]['total'])) {
2884		return number_format($JUNIT['files'][$file_name]['total'], 4);
2885	}
2886
2887	return 0;
2888}
2889
2890function junit_start_timer($file_name) {
2891	global $JUNIT;
2892	if (!junit_enabled()) return;
2893
2894	if (!isset($JUNIT['files'][$file_name]['start'])) {
2895		$JUNIT['files'][$file_name]['start'] = microtime(true);
2896
2897		$suite = junit_get_suitename_for($file_name);
2898		junit_init_suite($suite);
2899		$JUNIT['suites'][$suite]['files'][$file_name] = $file_name;
2900	}
2901}
2902
2903function junit_get_suitename_for($file_name) {
2904	return junit_path_to_classname(dirname($file_name));
2905}
2906
2907function junit_path_to_classname($file_name) {
2908    global $JUNIT;
2909
2910	$ret = $JUNIT['name'];
2911	$_tmp = array();
2912
2913	// lookup whether we're in the PHP source checkout
2914	$max = 5;
2915	if (is_file($file_name)) {
2916		$dir = dirname(realpath($file_name));
2917	} else {
2918		$dir = realpath($file_name);
2919	}
2920	do {
2921		array_unshift($_tmp, basename($dir));
2922		$chk = $dir . DIRECTORY_SEPARATOR . "main" . DIRECTORY_SEPARATOR . "php_version.h";
2923		$dir = dirname($dir);
2924	} while (!file_exists($chk) && --$max > 0);
2925	if (file_exists($chk)) {
2926		if ($max) {
2927			array_shift($_tmp);
2928		}
2929		foreach ($_tmp as $p) {
2930			$ret = $ret . "." . preg_replace(",[^a-z0-9]+,i", ".", $p);
2931		}
2932		return $ret;
2933	}
2934
2935	return $JUNIT['name'] . '.' . str_replace(array(DIRECTORY_SEPARATOR, '-'), '.', $file_name);
2936}
2937
2938function junit_init_suite($suite_name) {
2939	global $JUNIT;
2940	if (!junit_enabled()) return;
2941
2942	if (!empty($JUNIT['suites'][$suite_name])) {
2943		return;
2944	}
2945
2946	$JUNIT['suites'][$suite_name] = array(
2947		'name'          => $suite_name,
2948		'test_total'    => 0,
2949		'test_pass'     => 0,
2950		'test_fail'     => 0,
2951		'test_error'    => 0,
2952		'test_skip'     => 0,
2953		'suites'        => array(),
2954		'files'         => array(),
2955		'execution_time'=> 0,
2956	);
2957}
2958
2959function junit_finish_timer($file_name) {
2960	global $JUNIT;
2961	if (!junit_enabled()) return;
2962
2963	if (!isset($JUNIT['files'][$file_name]['start'])) {
2964		error("Timer for $file_name was not started!");
2965	}
2966
2967	if (!isset($JUNIT['files'][$file_name]['total'])) {
2968        $JUNIT['files'][$file_name]['total'] = 0;
2969    }
2970
2971	$start = $JUNIT['files'][$file_name]['start'];
2972	$JUNIT['files'][$file_name]['total'] += microtime(true) - $start;
2973	unset($JUNIT['files'][$file_name]['start']);
2974}
2975
2976class RuntestsValgrind {
2977	protected $version = '';
2978	protected $header = '';
2979	protected $version_3_3_0 = false;
2980	protected $version_3_8_0 = false;
2981
2982	public function getVersion() {
2983		return $this->version;
2984	}
2985
2986	public function getHeader() {
2987		return $this->header;
2988	}
2989
2990	public function __construct(array $environment) {
2991	    $header = system_with_timeout('valgrind --version', $environment);
2992		if (!$header) {
2993			error("Valgrind returned no version info, cannot proceed.\nPlease check if Valgrind is installed.");
2994		}
2995		$count = 0;
2996		$version = preg_replace("/valgrind-(\d+)\.(\d+)\.(\d+)([.\w_-]+)?(\s+)/", '$1.$2.$3', $header, 1, $count);
2997		if ($count != 1) {
2998			error("Valgrind returned invalid version info (\"{$header}\"), cannot proceed.");
2999		}
3000		$this->version = $version;
3001		$this->header = trim($header);
3002		$this->version_3_3_0 = version_compare($version, '3.3.0', '>=');
3003		$this->version_3_8_0 = version_compare($version, '3.8.0', '>=');
3004	}
3005
3006    public function wrapCommand($cmd, $memcheck_filename, $check_all) {
3007		$vcmd = 'valgrind -q --tool=memcheck --trace-children=yes';
3008		if ($check_all) {
3009			$vcmd .= ' --smc-check=all';
3010		}
3011
3012		/* --vex-iropt-register-updates=allregs-at-mem-access is necessary for phpdbg watchpoint tests */
3013		if ($this->version_3_8_0) {
3014			/* valgrind 3.3.0+ doesn't have --log-file-exactly option */
3015			return "$vcmd --vex-iropt-register-updates=allregs-at-mem-access --log-file=$memcheck_filename $cmd";
3016		} elseif ($this->version_3_3_0) {
3017			return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file=$memcheck_filename $cmd";
3018		} else {
3019			return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file-exactly=$memcheck_filename $cmd";
3020		}
3021	}
3022}
3023
3024/*
3025 * Local variables:
3026 * tab-width: 4
3027 * c-basic-offset: 4
3028 * End:
3029 * vim: noet sw=4 ts=4
3030 */
3031?>
3032