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