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