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