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