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