xref: /PHP-5.5/run-tests.php (revision 43c36bdb)
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: 60288e2d791bcf8486e334d4ea43c876431c9b3a $ */
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: 60288e2d791bcf8486e334d4ea43c876431c9b3a $' . "\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');
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			if (@count($section_text['FILE_EXTERNAL']) == 1) {
1296				// don't allow tests to retrieve files from anywhere but this subdirectory
1297				$section_text['FILE_EXTERNAL'] = dirname($file) . '/' . trim(str_replace('..', '', $section_text['FILE_EXTERNAL']));
1298
1299				if (file_exists($section_text['FILE_EXTERNAL'])) {
1300					$section_text['FILE'] = file_get_contents($section_text['FILE_EXTERNAL'], FILE_BINARY);
1301					unset($section_text['FILE_EXTERNAL']);
1302				} else {
1303					$bork_info = "could not load --FILE_EXTERNAL-- " . dirname($file) . '/' . trim($section_text['FILE_EXTERNAL']);
1304					$borked = true;
1305				}
1306			}
1307
1308			if ((@count($section_text['EXPECT']) + @count($section_text['EXPECTF']) + @count($section_text['EXPECTREGEX'])) != 1) {
1309				$bork_info = "missing section --EXPECT--, --EXPECTF-- or --EXPECTREGEX--";
1310				$borked = true;
1311			}
1312		}
1313	}
1314	fclose($fp);
1315
1316	$shortname = str_replace($cwd . '/', '', $file);
1317	$tested_file = $shortname;
1318
1319	if ($borked) {
1320		show_result("BORK", $bork_info, $tested_file);
1321		$PHP_FAILED_TESTS['BORKED'][] = array (
1322								'name'      => $file,
1323								'test_name' => '',
1324								'output'    => '',
1325								'diff'      => '',
1326								'info'      => "$bork_info [$file]",
1327		);
1328
1329		junit_mark_test_as('BORK', $shortname, $tested_file, 0, $bork_info);
1330		return 'BORKED';
1331	}
1332
1333	$tested = trim($section_text['TEST']);
1334
1335	/* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */
1336	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'])) {
1337		if (isset($php_cgi)) {
1338			$old_php = $php;
1339			$php = $php_cgi . ' -C ';
1340		} else if (!strncasecmp(PHP_OS, "win", 3) && file_exists(dirname($php) . "/php-cgi.exe")) {
1341			$old_php = $php;
1342			$php = realpath(dirname($php) . "/php-cgi.exe") . ' -C ';
1343		} else {
1344			if (file_exists(dirname($php) . "/../../sapi/cgi/php-cgi")) {
1345				$old_php = $php;
1346				$php = realpath(dirname($php) . "/../../sapi/cgi/php-cgi") . ' -C ';
1347			} else if (file_exists("./sapi/cgi/php-cgi")) {
1348				$old_php = $php;
1349				$php = realpath("./sapi/cgi/php-cgi") . ' -C ';
1350			} else if (file_exists(dirname($php) . "/php-cgi")) {
1351				$old_php = $php;
1352				$php = realpath(dirname($php) . "/php-cgi") . ' -C ';
1353			} else {
1354				show_result('SKIP', $tested, $tested_file, "reason: CGI not available");
1355
1356				junit_init_suite(junit_get_suitename_for($shortname));
1357				junit_mark_test_as('SKIP', $shortname, $tested, 0, 'CGI not available');
1358				return 'SKIPPED';
1359			}
1360		}
1361	}
1362
1363	show_test($test_idx, $shortname);
1364
1365	if (is_array($IN_REDIRECT)) {
1366		$temp_dir = $test_dir = $IN_REDIRECT['dir'];
1367	} else {
1368		$temp_dir = $test_dir = realpath(dirname($file));
1369	}
1370
1371	if ($temp_source && $temp_target) {
1372		$temp_dir = str_replace($temp_source, $temp_target, $temp_dir);
1373	}
1374
1375	$main_file_name = basename($file,'phpt');
1376
1377	$diff_filename     = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'diff';
1378	$log_filename      = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'log';
1379	$exp_filename      = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'exp';
1380	$output_filename   = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'out';
1381	$memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'mem';
1382	$sh_filename       = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'sh';
1383	$temp_file         = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php';
1384	$test_file         = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php';
1385	$temp_skipif       = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php';
1386	$test_skipif       = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php';
1387	$temp_clean        = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php';
1388	$test_clean        = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php';
1389	$tmp_post          = $temp_dir . DIRECTORY_SEPARATOR . uniqid('/phpt.');
1390	$tmp_relative_file = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $test_file) . 't';
1391
1392	if ($temp_source && $temp_target) {
1393		$temp_skipif  .= 's';
1394		$temp_file    .= 's';
1395		$temp_clean   .= 's';
1396		$copy_file     = $temp_dir . DIRECTORY_SEPARATOR . basename(is_array($file) ? $file[1] : $file) . '.phps';
1397
1398		if (!is_dir(dirname($copy_file))) {
1399			mkdir(dirname($copy_file), 0777, true) or error("Cannot create output directory - " . dirname($copy_file));
1400		}
1401
1402		if (isset($section_text['FILE'])) {
1403			save_text($copy_file, $section_text['FILE']);
1404		}
1405
1406		$temp_filenames = array(
1407			'file' => $copy_file,
1408			'diff' => $diff_filename,
1409			'log'  => $log_filename,
1410			'exp'  => $exp_filename,
1411			'out'  => $output_filename,
1412			'mem'  => $memcheck_filename,
1413			'sh'   => $sh_filename,
1414			'php'  => $temp_file,
1415			'skip' => $temp_skipif,
1416			'clean'=> $temp_clean);
1417	}
1418
1419	if (is_array($IN_REDIRECT)) {
1420		$tested = $IN_REDIRECT['prefix'] . ' ' . trim($section_text['TEST']);
1421		$tested_file = $tmp_relative_file;
1422	}
1423
1424	// unlink old test results
1425	@unlink($diff_filename);
1426	@unlink($log_filename);
1427	@unlink($exp_filename);
1428	@unlink($output_filename);
1429	@unlink($memcheck_filename);
1430	@unlink($sh_filename);
1431	@unlink($temp_file);
1432	@unlink($test_file);
1433	@unlink($temp_skipif);
1434	@unlink($test_skipif);
1435	@unlink($tmp_post);
1436	@unlink($temp_clean);
1437	@unlink($test_clean);
1438
1439	// Reset environment from any previous test.
1440	$env['REDIRECT_STATUS'] = '';
1441	$env['QUERY_STRING']    = '';
1442	$env['PATH_TRANSLATED'] = '';
1443	$env['SCRIPT_FILENAME'] = '';
1444	$env['REQUEST_METHOD']  = '';
1445	$env['CONTENT_TYPE']    = '';
1446	$env['CONTENT_LENGTH']  = '';
1447	$env['TZ']              = '';
1448
1449	if (!empty($section_text['ENV'])) {
1450
1451		foreach(explode("\n", trim($section_text['ENV'])) as $e) {
1452			$e = explode('=', trim($e), 2);
1453
1454			if (!empty($e[0]) && isset($e[1])) {
1455				$env[$e[0]] = $e[1];
1456			}
1457		}
1458	}
1459
1460	// Default ini settings
1461	$ini_settings = array();
1462	// additional ini overwrites
1463	//$ini_overwrites[] = 'setting=value';
1464	settings2array($ini_overwrites, $ini_settings);
1465
1466	// Any special ini settings
1467	// these may overwrite the test defaults...
1468	if (array_key_exists('INI', $section_text)) {
1469		if (strpos($section_text['INI'], '{PWD}') !== false) {
1470			$section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']);
1471		}
1472		settings2array(preg_split( "/[\n\r]+/", $section_text['INI']), $ini_settings);
1473	}
1474
1475	// Additional required extensions
1476	if (array_key_exists('EXTENSIONS', $section_text)) {
1477		$ext_dir=`$php -r 'echo ini_get("extension_dir");'`;
1478		$extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS']));
1479		$loaded = explode(",", `$php -n -r 'echo join(",", get_loaded_extensions());'`);
1480		foreach ($extensions as $req_ext) {
1481			if (!in_array($req_ext, $loaded)) {
1482				$ini_settings['extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $req_ext . '.' . PHP_SHLIB_SUFFIX;
1483			}
1484		}
1485	}
1486
1487	settings2params($ini_settings);
1488
1489	// Check if test should be skipped.
1490	$info = '';
1491	$warn = false;
1492
1493	if (array_key_exists('SKIPIF', $section_text)) {
1494
1495		if (trim($section_text['SKIPIF'])) {
1496			show_file_block('skip', $section_text['SKIPIF']);
1497			save_text($test_skipif, $section_text['SKIPIF'], $temp_skipif);
1498			$extra = substr(PHP_OS, 0, 3) !== "WIN" ?
1499				"unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;": "";
1500
1501			if ($leak_check) {
1502				$env['USE_ZEND_ALLOC'] = '0';
1503				$env['ZEND_DONT_UNLOAD_MODULES'] = 1;
1504			} else {
1505				$env['USE_ZEND_ALLOC'] = '1';
1506				$env['ZEND_DONT_UNLOAD_MODULES'] = 0;
1507			}
1508
1509			junit_start_timer($shortname);
1510
1511			$output = system_with_timeout("$extra $php $pass_options -q $ini_settings -d display_errors=0 \"$test_skipif\"", $env);
1512
1513			junit_finish_timer($shortname);
1514
1515			if (!$cfg['keep']['skip']) {
1516				@unlink($test_skipif);
1517			}
1518
1519			if (!strncasecmp('skip', ltrim($output), 4)) {
1520
1521				if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) {
1522					show_result('SKIP', $tested, $tested_file, "reason: $m[1]", $temp_filenames);
1523				} else {
1524					show_result('SKIP', $tested, $tested_file, '', $temp_filenames);
1525				}
1526
1527				if (isset($old_php)) {
1528					$php = $old_php;
1529				}
1530
1531				if (!$cfg['keep']['skip']) {
1532					@unlink($test_skipif);
1533				}
1534
1535				$message = !empty($m[1]) ? $m[1] : '';
1536				junit_mark_test_as('SKIP', $shortname, $tested, null, $message);
1537				return 'SKIPPED';
1538			}
1539
1540			if (!strncasecmp('info', ltrim($output), 4)) {
1541				if (preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) {
1542					$info = " (info: $m[1])";
1543				}
1544			}
1545
1546			if (!strncasecmp('warn', ltrim($output), 4)) {
1547				if (preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) {
1548					$warn = true; /* only if there is a reason */
1549					$info = " (warn: $m[1])";
1550				}
1551			}
1552		}
1553	}
1554
1555	if (!extension_loaded("zlib")
1556	&& (	array_key_exists("GZIP_POST", $section_text)
1557		||	array_key_exists("DEFLATE_POST", $section_text))
1558	) {
1559		$message = "ext/zlib required";
1560		show_result('SKIP', $tested, $tested_file, "reason: $message", $temp_filenames);
1561		junit_mark_test_as('SKIP', $shortname, $tested, null, $message);
1562		return 'SKIPPED';
1563	}
1564
1565	if (@count($section_text['REDIRECTTEST']) == 1) {
1566		$test_files = array();
1567
1568		$IN_REDIRECT = eval($section_text['REDIRECTTEST']);
1569		$IN_REDIRECT['via'] = "via [$shortname]\n\t";
1570		$IN_REDIRECT['dir'] = realpath(dirname($file));
1571		$IN_REDIRECT['prefix'] = trim($section_text['TEST']);
1572
1573		if (count($IN_REDIRECT['TESTS']) == 1) {
1574
1575			if (is_array($org_file)) {
1576				$test_files[] = $org_file[1];
1577			} else {
1578				$GLOBALS['test_files'] = $test_files;
1579				find_files($IN_REDIRECT['TESTS']);
1580
1581				foreach($GLOBALS['test_files'] as $f) {
1582					$test_files[] = array($f, $file);
1583				}
1584			}
1585			$test_cnt += @count($test_files) - 1;
1586			$test_idx--;
1587
1588			show_redirect_start($IN_REDIRECT['TESTS'], $tested, $tested_file);
1589
1590			// set up environment
1591			$redirenv = array_merge($environment, $IN_REDIRECT['ENV']);
1592			$redirenv['REDIR_TEST_DIR'] = realpath($IN_REDIRECT['TESTS']) . DIRECTORY_SEPARATOR;
1593
1594			usort($test_files, "test_sort");
1595			run_all_tests($test_files, $redirenv, $tested);
1596
1597			show_redirect_ends($IN_REDIRECT['TESTS'], $tested, $tested_file);
1598
1599			// a redirected test never fails
1600			$IN_REDIRECT = false;
1601
1602			junit_mark_test_as('PASS', $shortname, $tested);
1603			return 'REDIR';
1604
1605		} else {
1606
1607			$bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory.";
1608			show_result("BORK", $bork_info, '', $temp_filenames);
1609			$PHP_FAILED_TESTS['BORKED'][] = array (
1610									'name' => $file,
1611									'test_name' => '',
1612									'output' => '',
1613									'diff'   => '',
1614									'info'   => "$bork_info [$file]",
1615			);
1616		}
1617	}
1618
1619	if (is_array($org_file) || @count($section_text['REDIRECTTEST']) == 1) {
1620
1621		if (is_array($org_file)) {
1622			$file = $org_file[0];
1623		}
1624
1625		$bork_info = "Redirected test did not contain redirection info";
1626		show_result("BORK", $bork_info, '', $temp_filenames);
1627		$PHP_FAILED_TESTS['BORKED'][] = array (
1628								'name' => $file,
1629								'test_name' => '',
1630								'output' => '',
1631								'diff'   => '',
1632								'info'   => "$bork_info [$file]",
1633		);
1634
1635		junit_mark_test_as('BORK', $shortname, $tested, null, $bork_info);
1636
1637		return 'BORKED';
1638	}
1639
1640	// We've satisfied the preconditions - run the test!
1641	show_file_block('php', $section_text['FILE'], 'TEST');
1642	save_text($test_file, $section_text['FILE'], $temp_file);
1643
1644	if (array_key_exists('GET', $section_text)) {
1645		$query_string = trim($section_text['GET']);
1646	} else {
1647		$query_string = '';
1648	}
1649
1650	$env['REDIRECT_STATUS'] = '1';
1651	$env['QUERY_STRING']    = $query_string;
1652	$env['PATH_TRANSLATED'] = $test_file;
1653	$env['SCRIPT_FILENAME'] = $test_file;
1654
1655	if (array_key_exists('COOKIE', $section_text)) {
1656		$env['HTTP_COOKIE'] = trim($section_text['COOKIE']);
1657	} else {
1658		$env['HTTP_COOKIE'] = '';
1659	}
1660
1661	$args = isset($section_text['ARGS']) ? ' -- ' . $section_text['ARGS'] : '';
1662
1663	if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) {
1664
1665		$post = trim($section_text['POST_RAW']);
1666		$raw_lines = explode("\n", $post);
1667
1668		$request = '';
1669		$started = false;
1670
1671		foreach ($raw_lines as $line) {
1672
1673			if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) {
1674				$env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1]));
1675				continue;
1676			}
1677
1678			if ($started) {
1679				$request .= "\n";
1680			}
1681
1682			$started = true;
1683			$request .= $line;
1684		}
1685
1686		$env['CONTENT_LENGTH'] = strlen($request);
1687		$env['REQUEST_METHOD'] = 'POST';
1688
1689		if (empty($request)) {
1690			junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request');
1691			return 'BORKED';
1692		}
1693
1694		save_text($tmp_post, $request);
1695		$cmd = "$php $pass_options $ini_settings -f \"$test_file\" 2>&1 < \"$tmp_post\"";
1696
1697	} elseif (array_key_exists('PUT', $section_text) && !empty($section_text['PUT'])) {
1698
1699		$post = trim($section_text['PUT']);
1700		$raw_lines = explode("\n", $post);
1701
1702		$request = '';
1703		$started = false;
1704
1705		foreach ($raw_lines as $line) {
1706
1707			if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) {
1708				$env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1]));
1709				continue;
1710			}
1711
1712			if ($started) {
1713				$request .= "\n";
1714			}
1715
1716			$started = true;
1717			$request .= $line;
1718		}
1719
1720		$env['CONTENT_LENGTH'] = strlen($request);
1721		$env['REQUEST_METHOD'] = 'PUT';
1722
1723		if (empty($request)) {
1724            junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request');
1725			return 'BORKED';
1726		}
1727
1728		save_text($tmp_post, $request);
1729		$cmd = "$php $pass_options $ini_settings -f \"$test_file\" 2>&1 < \"$tmp_post\"";
1730
1731	} else if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) {
1732
1733		$post = trim($section_text['POST']);
1734		save_text($tmp_post, $post);
1735		$content_length = strlen($post);
1736
1737		$env['REQUEST_METHOD'] = 'POST';
1738		$env['CONTENT_TYPE']   = 'application/x-www-form-urlencoded';
1739		$env['CONTENT_LENGTH'] = $content_length;
1740
1741		$cmd = "$php $pass_options $ini_settings -f \"$test_file\" 2>&1 < \"$tmp_post\"";
1742
1743    } else if (array_key_exists('GZIP_POST', $section_text) && !empty($section_text['GZIP_POST'])) {
1744
1745        $post = trim($section_text['GZIP_POST']);
1746        $post = gzencode($post, 9, FORCE_GZIP);
1747        $env['HTTP_CONTENT_ENCODING'] = 'gzip';
1748
1749        save_text($tmp_post, $post);
1750        $content_length = strlen($post);
1751
1752        $env['REQUEST_METHOD'] = 'POST';
1753        $env['CONTENT_TYPE']   = 'application/x-www-form-urlencoded';
1754        $env['CONTENT_LENGTH'] = $content_length;
1755
1756        $cmd = "$php $pass_options $ini_settings -f \"$test_file\" 2>&1 < \"$tmp_post\"";
1757
1758    } else if (array_key_exists('DEFLATE_POST', $section_text) && !empty($section_text['DEFLATE_POST'])) {
1759        $post = trim($section_text['DEFLATE_POST']);
1760        $post = gzcompress($post, 9);
1761        $env['HTTP_CONTENT_ENCODING'] = 'deflate';
1762        save_text($tmp_post, $post);
1763        $content_length = strlen($post);
1764
1765        $env['REQUEST_METHOD'] = 'POST';
1766        $env['CONTENT_TYPE']   = 'application/x-www-form-urlencoded';
1767        $env['CONTENT_LENGTH'] = $content_length;
1768
1769        $cmd = "$php $pass_options $ini_settings -f \"$test_file\" 2>&1 < \"$tmp_post\"";
1770
1771
1772	} else {
1773
1774		$env['REQUEST_METHOD'] = 'GET';
1775		$env['CONTENT_TYPE']   = '';
1776		$env['CONTENT_LENGTH'] = '';
1777
1778		$cmd = "$php $pass_options $ini_settings -f \"$test_file\" $args 2>&1";
1779	}
1780
1781	if ($leak_check) {
1782		$env['USE_ZEND_ALLOC'] = '0';
1783		$env['ZEND_DONT_UNLOAD_MODULES'] = 1;
1784
1785		if (version_compare($valgrind_version, '3.3.0', '>=')) {
1786			/* valgrind 3.3.0+ doesn't have --log-file-exactly option */
1787			$cmd = "valgrind -q --tool=memcheck --trace-children=yes --log-file=$memcheck_filename $cmd";
1788		} else {
1789			$cmd = "valgrind -q --tool=memcheck --trace-children=yes --log-file-exactly=$memcheck_filename $cmd";
1790		}
1791
1792	} else {
1793		$env['USE_ZEND_ALLOC'] = '1';
1794		$env['ZEND_DONT_UNLOAD_MODULES'] = 0;
1795	}
1796
1797	if ($DETAILED) echo "
1798CONTENT_LENGTH  = " . $env['CONTENT_LENGTH'] . "
1799CONTENT_TYPE    = " . $env['CONTENT_TYPE'] . "
1800PATH_TRANSLATED = " . $env['PATH_TRANSLATED'] . "
1801QUERY_STRING    = " . $env['QUERY_STRING'] . "
1802REDIRECT_STATUS = " . $env['REDIRECT_STATUS'] . "
1803REQUEST_METHOD  = " . $env['REQUEST_METHOD'] . "
1804SCRIPT_FILENAME = " . $env['SCRIPT_FILENAME'] . "
1805HTTP_COOKIE     = " . $env['HTTP_COOKIE'] . "
1806COMMAND $cmd
1807";
1808
1809	junit_start_timer($shortname);
1810
1811	$out = system_with_timeout($cmd, $env, isset($section_text['STDIN']) ? $section_text['STDIN'] : null);
1812
1813	junit_finish_timer($shortname);
1814
1815	if (array_key_exists('CLEAN', $section_text) && (!$no_clean || $cfg['keep']['clean'])) {
1816
1817		if (trim($section_text['CLEAN'])) {
1818			show_file_block('clean', $section_text['CLEAN']);
1819			save_text($test_clean, trim($section_text['CLEAN']), $temp_clean);
1820
1821			if (!$no_clean) {
1822				$clean_params = array();
1823				settings2array($ini_overwrites, $clean_params);
1824				settings2params($clean_params);
1825				$extra = substr(PHP_OS, 0, 3) !== "WIN" ?
1826					"unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;": "";
1827				system_with_timeout("$extra $php $pass_options -q $clean_params \"$test_clean\"", $env);
1828			}
1829
1830			if (!$cfg['keep']['clean']) {
1831				@unlink($test_clean);
1832			}
1833		}
1834	}
1835
1836	@unlink($tmp_post);
1837
1838	$leaked = false;
1839	$passed = false;
1840
1841	if ($leak_check) { // leak check
1842		$leaked = filesize($memcheck_filename) > 0;
1843
1844		if (!$leaked) {
1845			@unlink($memcheck_filename);
1846		}
1847	}
1848
1849	// Does the output match what is expected?
1850	$output = preg_replace("/\r\n/", "\n", trim($out));
1851
1852	/* when using CGI, strip the headers from the output */
1853	$headers = "";
1854
1855	if (isset($old_php) && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) {
1856		$output = trim($match[2]);
1857		$rh = preg_split("/[\n\r]+/", $match[1]);
1858		$headers = array();
1859
1860		foreach ($rh as $line) {
1861			if (strpos($line, ':') !== false) {
1862				$line = explode(':', $line, 2);
1863				$headers[trim($line[0])] = trim($line[1]);
1864			}
1865		}
1866	}
1867
1868	$failed_headers = false;
1869
1870	if (isset($section_text['EXPECTHEADERS'])) {
1871		$want = array();
1872		$wanted_headers = array();
1873		$lines = preg_split("/[\n\r]+/", $section_text['EXPECTHEADERS']);
1874
1875		foreach($lines as $line) {
1876			if (strpos($line, ':') !== false) {
1877				$line = explode(':', $line, 2);
1878				$want[trim($line[0])] = trim($line[1]);
1879				$wanted_headers[] = trim($line[0]) . ': ' . trim($line[1]);
1880			}
1881		}
1882
1883		$org_headers = $headers;
1884		$headers = array();
1885		$output_headers = array();
1886
1887		foreach($want as $k => $v) {
1888
1889			if (isset($org_headers[$k])) {
1890				$headers = $org_headers[$k];
1891				$output_headers[] = $k . ': ' . $org_headers[$k];
1892			}
1893
1894			if (!isset($org_headers[$k]) || $org_headers[$k] != $v) {
1895				$failed_headers = true;
1896			}
1897		}
1898
1899		ksort($wanted_headers);
1900		$wanted_headers = join("\n", $wanted_headers);
1901		ksort($output_headers);
1902		$output_headers = join("\n", $output_headers);
1903	}
1904
1905	show_file_block('out', $output);
1906
1907	if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) {
1908
1909		if (isset($section_text['EXPECTF'])) {
1910			$wanted = trim($section_text['EXPECTF']);
1911		} else {
1912			$wanted = trim($section_text['EXPECTREGEX']);
1913		}
1914
1915		show_file_block('exp', $wanted);
1916		$wanted_re = preg_replace('/\r\n/', "\n", $wanted);
1917
1918		if (isset($section_text['EXPECTF'])) {
1919
1920			// do preg_quote, but miss out any %r delimited sections
1921			$temp = "";
1922			$r = "%r";
1923			$startOffset = 0;
1924			$length = strlen($wanted_re);
1925			while($startOffset < $length) {
1926				$start = strpos($wanted_re, $r, $startOffset);
1927				if ($start !== false) {
1928					// we have found a start tag
1929					$end = strpos($wanted_re, $r, $start+2);
1930					if ($end === false) {
1931						// unbalanced tag, ignore it.
1932						$end = $start = $length;
1933					}
1934				} else {
1935					// no more %r sections
1936					$start = $end = $length;
1937				}
1938				// quote a non re portion of the string
1939				$temp = $temp . preg_quote(substr($wanted_re, $startOffset, ($start - $startOffset)),  '/');
1940				// add the re unquoted.
1941				if ($end > $start) {
1942					$temp = $temp . '(' . substr($wanted_re, $start+2, ($end - $start-2)). ')';
1943				}
1944				$startOffset = $end + 2;
1945			}
1946			$wanted_re = $temp;
1947
1948			$wanted_re = str_replace(
1949				array('%binary_string_optional%'),
1950				'string',
1951				$wanted_re
1952			);
1953			$wanted_re = str_replace(
1954				array('%unicode_string_optional%'),
1955				'string',
1956				$wanted_re
1957			);
1958			$wanted_re = str_replace(
1959				array('%unicode\|string%', '%string\|unicode%'),
1960				'string',
1961				$wanted_re
1962			);
1963			$wanted_re = str_replace(
1964				array('%u\|b%', '%b\|u%'),
1965				'',
1966				$wanted_re
1967			);
1968			// Stick to basics
1969			$wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re);
1970			$wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re);
1971			$wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re);
1972			$wanted_re = str_replace('%a', '.+', $wanted_re);
1973			$wanted_re = str_replace('%A', '.*', $wanted_re);
1974			$wanted_re = str_replace('%w', '\s*', $wanted_re);
1975			$wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re);
1976			$wanted_re = str_replace('%d', '\d+', $wanted_re);
1977			$wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re);
1978			$wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re);
1979			$wanted_re = str_replace('%c', '.', $wanted_re);
1980			// %f allows two points "-.0.0" but that is the best *simple* expression
1981		}
1982/* DEBUG YOUR REGEX HERE
1983		var_dump($wanted_re);
1984		print(str_repeat('=', 80) . "\n");
1985		var_dump($output);
1986*/
1987		if (preg_match("/^$wanted_re\$/s", $output)) {
1988			$passed = true;
1989			if (!$cfg['keep']['php']) {
1990				@unlink($test_file);
1991			}
1992			if (isset($old_php)) {
1993				$php = $old_php;
1994			}
1995
1996			if (!$leaked && !$failed_headers) {
1997				if (isset($section_text['XFAIL'] )) {
1998					$warn = true;
1999					$info = " (warn: XFAIL section but test passes)";
2000				}else {
2001					show_result("PASS", $tested, $tested_file, '', $temp_filenames);
2002					junit_mark_test_as('PASS', $shortname, $tested);
2003					return 'PASSED';
2004				}
2005			}
2006		}
2007
2008	} else {
2009
2010		$wanted = trim($section_text['EXPECT']);
2011		$wanted = preg_replace('/\r\n/',"\n", $wanted);
2012		show_file_block('exp', $wanted);
2013
2014		// compare and leave on success
2015		if (!strcmp($output, $wanted)) {
2016			$passed = true;
2017
2018			if (!$cfg['keep']['php']) {
2019				@unlink($test_file);
2020			}
2021
2022			if (isset($old_php)) {
2023				$php = $old_php;
2024			}
2025
2026			if (!$leaked && !$failed_headers) {
2027				if (isset($section_text['XFAIL'] )) {
2028					$warn = true;
2029					$info = " (warn: XFAIL section but test passes)";
2030				}else {
2031					show_result("PASS", $tested, $tested_file, '', $temp_filenames);
2032					junit_mark_test_as('PASS', $shortname, $tested);
2033					return 'PASSED';
2034				}
2035			}
2036		}
2037
2038		$wanted_re = null;
2039	}
2040
2041	// Test failed so we need to report details.
2042	if ($failed_headers) {
2043		$passed = false;
2044		$wanted = $wanted_headers . "\n--HEADERS--\n" . $wanted;
2045		$output = $output_headers . "\n--HEADERS--\n" . $output;
2046
2047		if (isset($wanted_re)) {
2048			$wanted_re = preg_quote($wanted_headers . "\n--HEADERS--\n", '/') . $wanted_re;
2049		}
2050	}
2051
2052	if ($leaked) {
2053		$restype[] = 'LEAK';
2054	}
2055
2056	if ($warn) {
2057		$restype[] = 'WARN';
2058	}
2059
2060	if (!$passed) {
2061		if (isset($section_text['XFAIL'])) {
2062			$restype[] = 'XFAIL';
2063			$info = '  XFAIL REASON: ' . rtrim($section_text['XFAIL']);
2064		} else {
2065			$restype[] = 'FAIL';
2066		}
2067	}
2068
2069	if (!$passed) {
2070
2071		// write .exp
2072		if (strpos($log_format, 'E') !== false && file_put_contents($exp_filename, $wanted, FILE_BINARY) === false) {
2073			error("Cannot create expected test output - $exp_filename");
2074		}
2075
2076		// write .out
2077		if (strpos($log_format, 'O') !== false && file_put_contents($output_filename, $output, FILE_BINARY) === false) {
2078			error("Cannot create test output - $output_filename");
2079		}
2080
2081		// write .diff
2082		$diff = generate_diff($wanted, $wanted_re, $output);
2083		if (is_array($IN_REDIRECT)) {
2084			$diff = "# original source file: $shortname\n" . $diff;
2085		}
2086		show_file_block('diff', $diff);
2087		if (strpos($log_format, 'D') !== false && file_put_contents($diff_filename, $diff, FILE_BINARY) === false) {
2088			error("Cannot create test diff - $diff_filename");
2089		}
2090
2091		// write .sh
2092		if (strpos($log_format, 'S') !== false && file_put_contents($sh_filename, "#!/bin/sh
2093
2094{$cmd}
2095", FILE_BINARY) === false) {
2096			error("Cannot create test shell script - $sh_filename");
2097		}
2098		chmod($sh_filename, 0755);
2099
2100		// write .log
2101		if (strpos($log_format, 'L') !== false && file_put_contents($log_filename, "
2102---- EXPECTED OUTPUT
2103$wanted
2104---- ACTUAL OUTPUT
2105$output
2106---- FAILED
2107", FILE_BINARY) === false) {
2108			error("Cannot create test log - $log_filename");
2109			error_report($file, $log_filename, $tested);
2110		}
2111	}
2112
2113	show_result(implode('&', $restype), $tested, $tested_file, $info, $temp_filenames);
2114
2115	foreach ($restype as $type) {
2116		$PHP_FAILED_TESTS[$type.'ED'][] = array (
2117			'name'      => $file,
2118			'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]",
2119			'output'    => $output_filename,
2120			'diff'      => $diff_filename,
2121			'info'      => $info,
2122		);
2123	}
2124
2125	if (isset($old_php)) {
2126		$php = $old_php;
2127	}
2128
2129	$diff = empty($diff) ? '' : preg_replace('/\e/', '<esc>', $diff);
2130
2131	junit_mark_test_as($restype, str_replace($cwd . '/', '', $tested_file), $tested, null, $info, $diff);
2132
2133	return $restype[0] . 'ED';
2134}
2135
2136function comp_line($l1, $l2, $is_reg)
2137{
2138	if ($is_reg) {
2139		return preg_match('/^'. $l1 . '$/s', $l2);
2140	} else {
2141		return !strcmp($l1, $l2);
2142	}
2143}
2144
2145function count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2, $cnt1, $cnt2, $steps)
2146{
2147	$equal = 0;
2148
2149	while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) {
2150		$idx1++;
2151		$idx2++;
2152		$equal++;
2153		$steps--;
2154	}
2155	if (--$steps > 0) {
2156		$eq1 = 0;
2157		$st = $steps / 2;
2158
2159		for ($ofs1 = $idx1 + 1; $ofs1 < $cnt1 && $st-- > 0; $ofs1++) {
2160			$eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $ofs1, $idx2, $cnt1, $cnt2, $st);
2161
2162			if ($eq > $eq1) {
2163				$eq1 = $eq;
2164			}
2165		}
2166
2167		$eq2 = 0;
2168		$st = $steps;
2169
2170		for ($ofs2 = $idx2 + 1; $ofs2 < $cnt2 && $st-- > 0; $ofs2++) {
2171			$eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $ofs2, $cnt1, $cnt2, $st);
2172			if ($eq > $eq2) {
2173				$eq2 = $eq;
2174			}
2175		}
2176
2177		if ($eq1 > $eq2) {
2178			$equal += $eq1;
2179		} else if ($eq2 > 0) {
2180			$equal += $eq2;
2181		}
2182	}
2183
2184	return $equal;
2185}
2186
2187function generate_array_diff($ar1, $ar2, $is_reg, $w)
2188{
2189	$idx1 = 0; $ofs1 = 0; $cnt1 = @count($ar1);
2190	$idx2 = 0; $ofs2 = 0; $cnt2 = @count($ar2);
2191	$diff = array();
2192	$old1 = array();
2193	$old2 = array();
2194
2195	while ($idx1 < $cnt1 && $idx2 < $cnt2) {
2196
2197		if (comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) {
2198			$idx1++;
2199			$idx2++;
2200			continue;
2201		} else {
2202
2203			$c1 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1+1, $idx2, $cnt1, $cnt2, 10);
2204			$c2 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2+1, $cnt1,  $cnt2, 10);
2205
2206			if ($c1 > $c2) {
2207				$old1[$idx1] = sprintf("%03d- ", $idx1+1) . $w[$idx1++];
2208				$last = 1;
2209			} else if ($c2 > 0) {
2210				$old2[$idx2] = sprintf("%03d+ ", $idx2+1) . $ar2[$idx2++];
2211				$last = 2;
2212			} else {
2213				$old1[$idx1] = sprintf("%03d- ", $idx1+1) . $w[$idx1++];
2214				$old2[$idx2] = sprintf("%03d+ ", $idx2+1) . $ar2[$idx2++];
2215			}
2216		}
2217	}
2218
2219	reset($old1); $k1 = key($old1); $l1 = -2;
2220	reset($old2); $k2 = key($old2); $l2 = -2;
2221
2222	while ($k1 !== null || $k2 !== null) {
2223
2224		if ($k1 == $l1 + 1 || $k2 === null) {
2225			$l1 = $k1;
2226			$diff[] = current($old1);
2227			$k1 = next($old1) ? key($old1) : null;
2228		} else if ($k2 == $l2 + 1 || $k1 === null) {
2229			$l2 = $k2;
2230			$diff[] = current($old2);
2231			$k2 = next($old2) ? key($old2) : null;
2232		} else if ($k1 < $k2) {
2233			$l1 = $k1;
2234			$diff[] = current($old1);
2235			$k1 = next($old1) ? key($old1) : null;
2236		} else {
2237			$l2 = $k2;
2238			$diff[] = current($old2);
2239			$k2 = next($old2) ? key($old2) : null;
2240		}
2241	}
2242
2243	while ($idx1 < $cnt1) {
2244		$diff[] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++];
2245	}
2246
2247	while ($idx2 < $cnt2) {
2248		$diff[] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++];
2249	}
2250
2251	return $diff;
2252}
2253
2254function generate_diff($wanted, $wanted_re, $output)
2255{
2256	$w = explode("\n", $wanted);
2257	$o = explode("\n", $output);
2258	$r = is_null($wanted_re) ? $w : explode("\n", $wanted_re);
2259	$diff = generate_array_diff($r, $o, !is_null($wanted_re), $w);
2260
2261	return implode("\r\n", $diff);
2262}
2263
2264function error($message)
2265{
2266	echo "ERROR: {$message}\n";
2267	exit(1);
2268}
2269
2270function settings2array($settings, &$ini_settings)
2271{
2272	foreach($settings as $setting) {
2273
2274		if (strpos($setting, '=') !== false) {
2275			$setting = explode("=", $setting, 2);
2276			$name = trim($setting[0]);
2277			$value = trim($setting[1]);
2278
2279			if ($name == 'extension') {
2280
2281				if (!isset($ini_settings[$name])) {
2282					$ini_settings[$name] = array();
2283				}
2284
2285				$ini_settings[$name][] = $value;
2286
2287			} else {
2288				$ini_settings[$name] = $value;
2289			}
2290		}
2291	}
2292}
2293
2294function settings2params(&$ini_settings)
2295{
2296	$settings = '';
2297
2298	foreach($ini_settings as $name => $value) {
2299
2300		if (is_array($value)) {
2301			foreach($value as $val) {
2302				$val = addslashes($val);
2303				$settings .= " -d \"$name=$val\"";
2304			}
2305		} else {
2306			if (substr(PHP_OS, 0, 3) == "WIN" && !empty($value) && $value{0} == '"') {
2307				$len = strlen($value);
2308
2309				if ($value{$len - 1} == '"') {
2310					$value{0} = "'";
2311					$value{$len - 1} = "'";
2312				}
2313			} else {
2314				$value = addslashes($value);
2315			}
2316
2317			$settings .= " -d \"$name=$value\"";
2318		}
2319	}
2320
2321	$ini_settings = $settings;
2322}
2323
2324function compute_summary()
2325{
2326	global $n_total, $test_results, $ignored_by_ext, $sum_results, $percent_results;
2327
2328	$n_total = count($test_results);
2329	$n_total += $ignored_by_ext;
2330	$sum_results = array(
2331		'PASSED'  => 0,
2332		'WARNED'  => 0,
2333		'SKIPPED' => 0,
2334		'FAILED'  => 0,
2335		'BORKED'  => 0,
2336		'LEAKED'  => 0,
2337		'XFAILED' => 0
2338	);
2339
2340	foreach ($test_results as $v) {
2341		$sum_results[$v]++;
2342	}
2343
2344	$sum_results['SKIPPED'] += $ignored_by_ext;
2345	$percent_results = array();
2346
2347	while (list($v, $n) = each($sum_results)) {
2348		$percent_results[$v] = (100.0 * $n) / $n_total;
2349	}
2350}
2351
2352function get_summary($show_ext_summary, $show_html)
2353{
2354	global $exts_skipped, $exts_tested, $n_total, $sum_results, $percent_results, $end_time, $start_time, $failed_test_summary, $PHP_FAILED_TESTS, $leak_check;
2355
2356	$x_total = $n_total - $sum_results['SKIPPED'] - $sum_results['BORKED'];
2357
2358	if ($x_total) {
2359		$x_warned = (100.0 * $sum_results['WARNED']) / $x_total;
2360		$x_failed = (100.0 * $sum_results['FAILED']) / $x_total;
2361		$x_xfailed = (100.0 * $sum_results['XFAILED']) / $x_total;
2362		$x_leaked = (100.0 * $sum_results['LEAKED']) / $x_total;
2363		$x_passed = (100.0 * $sum_results['PASSED']) / $x_total;
2364	} else {
2365		$x_warned = $x_failed = $x_passed = $x_leaked = $x_xfailed = 0;
2366	}
2367
2368	$summary = '';
2369
2370	if ($show_html) {
2371		$summary .= "<pre>\n";
2372	}
2373
2374	if ($show_ext_summary) {
2375		$summary .= '
2376=====================================================================
2377TEST RESULT SUMMARY
2378---------------------------------------------------------------------
2379Exts skipped    : ' . sprintf('%4d', $exts_skipped) . '
2380Exts tested     : ' . sprintf('%4d', $exts_tested) . '
2381---------------------------------------------------------------------
2382';
2383	}
2384
2385	$summary .= '
2386Number of tests : ' . sprintf('%4d', $n_total) . '          ' . sprintf('%8d', $x_total);
2387
2388	if ($sum_results['BORKED']) {
2389		$summary .= '
2390Tests borked    : ' . sprintf('%4d (%5.1f%%)', $sum_results['BORKED'], $percent_results['BORKED']) . ' --------';
2391	}
2392
2393	$summary .= '
2394Tests skipped   : ' . sprintf('%4d (%5.1f%%)', $sum_results['SKIPPED'], $percent_results['SKIPPED']) . ' --------
2395Tests warned    : ' . sprintf('%4d (%5.1f%%)', $sum_results['WARNED'], $percent_results['WARNED']) . ' ' . sprintf('(%5.1f%%)', $x_warned) . '
2396Tests failed    : ' . sprintf('%4d (%5.1f%%)', $sum_results['FAILED'], $percent_results['FAILED']) . ' ' . sprintf('(%5.1f%%)', $x_failed) . '
2397Expected fail   : ' . sprintf('%4d (%5.1f%%)', $sum_results['XFAILED'], $percent_results['XFAILED']) . ' ' . sprintf('(%5.1f%%)', $x_xfailed);
2398
2399	if ($leak_check) {
2400		$summary .= '
2401Tests leaked    : ' . sprintf('%4d (%5.1f%%)', $sum_results['LEAKED'], $percent_results['LEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_leaked);
2402	}
2403
2404	$summary .= '
2405Tests passed    : ' . sprintf('%4d (%5.1f%%)', $sum_results['PASSED'], $percent_results['PASSED']) . ' ' . sprintf('(%5.1f%%)', $x_passed) . '
2406---------------------------------------------------------------------
2407Time taken      : ' . sprintf('%4d seconds', $end_time - $start_time) . '
2408=====================================================================
2409';
2410	$failed_test_summary = '';
2411
2412	if (count($PHP_FAILED_TESTS['XFAILED'])) {
2413		$failed_test_summary .= '
2414=====================================================================
2415EXPECTED FAILED TEST SUMMARY
2416---------------------------------------------------------------------
2417';
2418		foreach ($PHP_FAILED_TESTS['XFAILED'] as $failed_test_data) {
2419			$failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2420		}
2421		$failed_test_summary .=  "=====================================================================\n";
2422	}
2423
2424	if (count($PHP_FAILED_TESTS['BORKED'])) {
2425		$failed_test_summary .= '
2426=====================================================================
2427BORKED TEST SUMMARY
2428---------------------------------------------------------------------
2429';
2430		foreach ($PHP_FAILED_TESTS['BORKED'] as $failed_test_data) {
2431			$failed_test_summary .= $failed_test_data['info'] . "\n";
2432		}
2433
2434		$failed_test_summary .=  "=====================================================================\n";
2435	}
2436
2437	if (count($PHP_FAILED_TESTS['FAILED'])) {
2438		$failed_test_summary .= '
2439=====================================================================
2440FAILED TEST SUMMARY
2441---------------------------------------------------------------------
2442';
2443		foreach ($PHP_FAILED_TESTS['FAILED'] as $failed_test_data) {
2444			$failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2445		}
2446		$failed_test_summary .=  "=====================================================================\n";
2447	}
2448	if (count($PHP_FAILED_TESTS['WARNED'])) {
2449		$failed_test_summary .= '
2450=====================================================================
2451WARNED TEST SUMMARY
2452---------------------------------------------------------------------
2453';
2454		foreach ($PHP_FAILED_TESTS['WARNED'] as $failed_test_data) {
2455			$failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2456		}
2457
2458		$failed_test_summary .=  "=====================================================================\n";
2459	}
2460
2461	if (count($PHP_FAILED_TESTS['LEAKED'])) {
2462		$failed_test_summary .= '
2463=====================================================================
2464LEAKED TEST SUMMARY
2465---------------------------------------------------------------------
2466';
2467		foreach ($PHP_FAILED_TESTS['LEAKED'] as $failed_test_data) {
2468			$failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2469		}
2470
2471		$failed_test_summary .=  "=====================================================================\n";
2472	}
2473
2474	if ($failed_test_summary && !getenv('NO_PHPTEST_SUMMARY')) {
2475		$summary .= $failed_test_summary;
2476	}
2477
2478	if ($show_html) {
2479		$summary .= "</pre>";
2480	}
2481
2482	return $summary;
2483}
2484
2485function show_start($start_time)
2486{
2487	global $html_output, $html_file;
2488
2489	if ($html_output) {
2490		fwrite($html_file, "<h2>Time Start: " . date('Y-m-d H:i:s', $start_time) . "</h2>\n");
2491		fwrite($html_file, "<table>\n");
2492	}
2493
2494	echo "TIME START " . date('Y-m-d H:i:s', $start_time) . "\n=====================================================================\n";
2495}
2496
2497function show_end($end_time)
2498{
2499	global $html_output, $html_file;
2500
2501	if ($html_output) {
2502		fwrite($html_file, "</table>\n");
2503		fwrite($html_file, "<h2>Time End: " . date('Y-m-d H:i:s', $end_time) . "</h2>\n");
2504	}
2505
2506	echo "=====================================================================\nTIME END " . date('Y-m-d H:i:s', $end_time) . "\n";
2507}
2508
2509function show_summary()
2510{
2511	global $html_output, $html_file;
2512
2513	if ($html_output) {
2514		fwrite($html_file, "<hr/>\n" . get_summary(true, true));
2515	}
2516
2517	echo get_summary(true, false);
2518}
2519
2520function show_redirect_start($tests, $tested, $tested_file)
2521{
2522	global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS;
2523
2524	if ($html_output) {
2525		fwrite($html_file, "<tr><td colspan='3'>---&gt; $tests ($tested [$tested_file]) begin</td></tr>\n");
2526	}
2527
2528	if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) {
2529		   echo "REDIRECT $tests ($tested [$tested_file]) begin\n";
2530	} else {
2531		   // Write over the last line to avoid random trailing chars on next echo
2532		   echo str_repeat(" ", $line_length), "\r";
2533	}
2534}
2535
2536function show_redirect_ends($tests, $tested, $tested_file)
2537{
2538	global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS;
2539
2540	if ($html_output) {
2541		fwrite($html_file, "<tr><td colspan='3'>---&gt; $tests ($tested [$tested_file]) done</td></tr>\n");
2542	}
2543
2544	if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) {
2545		   echo "REDIRECT $tests ($tested [$tested_file]) done\n";
2546	} else {
2547		   // Write over the last line to avoid random trailing chars on next echo
2548		   echo str_repeat(" ", $line_length), "\r";
2549	}
2550}
2551
2552function show_test($test_idx, $shortname)
2553{
2554	global $test_cnt;
2555	global $line_length;
2556
2557	$str = "TEST $test_idx/$test_cnt [$shortname]\r";
2558	$line_length = strlen($str);
2559	echo $str;
2560	flush();
2561}
2562
2563function show_result($result, $tested, $tested_file, $extra = '', $temp_filenames = null)
2564{
2565	global $html_output, $html_file, $temp_target, $temp_urlbase, $line_length, $SHOW_ONLY_GROUPS;
2566
2567	if (!$SHOW_ONLY_GROUPS || in_array($result, $SHOW_ONLY_GROUPS)) {
2568		echo "$result $tested [$tested_file] $extra\n";
2569	} else {
2570		// Write over the last line to avoid random trailing chars on next echo
2571		echo str_repeat(" ", $line_length), "\r";
2572	}
2573
2574	if ($html_output) {
2575
2576		if (isset($temp_filenames['file']) && file_exists($temp_filenames['file'])) {
2577			$url = str_replace($temp_target, $temp_urlbase, $temp_filenames['file']);
2578			$tested = "<a href='$url'>$tested</a>";
2579		}
2580
2581		if (isset($temp_filenames['skip']) && file_exists($temp_filenames['skip'])) {
2582
2583			if (empty($extra)) {
2584				$extra = "skipif";
2585			}
2586
2587			$url = str_replace($temp_target, $temp_urlbase, $temp_filenames['skip']);
2588			$extra = "<a href='$url'>$extra</a>";
2589
2590		} else if (empty($extra)) {
2591			$extra = "&nbsp;";
2592		}
2593
2594		if (isset($temp_filenames['diff']) && file_exists($temp_filenames['diff'])) {
2595			$url = str_replace($temp_target, $temp_urlbase, $temp_filenames['diff']);
2596			$diff = "<a href='$url'>diff</a>";
2597		} else {
2598			$diff = "&nbsp;";
2599		}
2600
2601		if (isset($temp_filenames['mem']) && file_exists($temp_filenames['mem'])) {
2602			$url = str_replace($temp_target, $temp_urlbase, $temp_filenames['mem']);
2603			$mem = "<a href='$url'>leaks</a>";
2604		} else {
2605			$mem = "&nbsp;";
2606		}
2607
2608		fwrite($html_file,
2609			"<tr>" .
2610			"<td>$result</td>" .
2611			"<td>$tested</td>" .
2612			"<td>$extra</td>" .
2613			"<td>$diff</td>" .
2614			"<td>$mem</td>" .
2615			"</tr>\n");
2616	}
2617}
2618
2619function junit_init() {
2620	// Check whether a junit log is wanted.
2621	$JUNIT = getenv('TEST_PHP_JUNIT');
2622	if (empty($JUNIT)) {
2623		$JUNIT = FALSE;
2624	} elseif (!$fp = fopen($JUNIT, 'w')) {
2625		error("Failed to open $JUNIT for writing.");
2626	} else {
2627		$JUNIT = array(
2628			'fp'            => $fp,
2629			'name'          => 'php-src',
2630			'test_total'    => 0,
2631			'test_pass'     => 0,
2632			'test_fail'     => 0,
2633			'test_error'    => 0,
2634			'test_skip'     => 0,
2635			'execution_time'=> 0,
2636			'suites'        => array(),
2637			'files'         => array()
2638		);
2639	}
2640
2641	$GLOBALS['JUNIT'] = $JUNIT;
2642}
2643
2644function junit_save_xml() {
2645	global $JUNIT;
2646	if (!junit_enabled()) return;
2647
2648	$xml = '<?xml version="1.0" encoding="UTF-8"?>'. PHP_EOL .
2649		   '<testsuites>' . PHP_EOL;
2650	$xml .= junit_get_suite_xml();
2651	$xml .= '</testsuites>';
2652	fwrite($JUNIT['fp'], $xml);
2653}
2654
2655function junit_get_suite_xml($suite_name = '') {
2656	global $JUNIT;
2657
2658	$suite = $suite_name ? $JUNIT['suites'][$suite_name] : $JUNIT;
2659
2660    $result = sprintf(
2661		'<testsuite name="%s" tests="%s" failures="%d" errors="%d" skip="%d" time="%s">' . PHP_EOL,
2662        $suite['name'], $suite['test_total'], $suite['test_fail'], $suite['test_error'], $suite['test_skip'],
2663		$suite['execution_time']
2664	);
2665
2666	foreach($suite['suites'] as $sub_suite) {
2667		$result .= junit_get_suite_xml($sub_suite['name']);
2668	}
2669
2670	// Output files only in subsuites
2671	if (!empty($suite_name)) {
2672		foreach($suite['files'] as $file) {
2673			$result .= $JUNIT['files'][$file]['xml'];
2674		}
2675	}
2676
2677	$result .= '</testsuite>' . PHP_EOL;
2678
2679	return $result;
2680}
2681
2682function junit_enabled() {
2683	global $JUNIT;
2684	return !empty($JUNIT);
2685}
2686
2687/**
2688 * @param array|string $type
2689 * @param string $file_name
2690 * @param string $test_name
2691 * @param int|string $time
2692 * @param string $message
2693 * @param string $details
2694 * @return void
2695 */
2696function junit_mark_test_as($type, $file_name, $test_name, $time = null, $message = '', $details = '') {
2697	global $JUNIT;
2698	if (!junit_enabled()) return;
2699
2700	$suite = junit_get_suitename_for($file_name);
2701
2702	junit_suite_record($suite, 'test_total');
2703
2704	$time = null !== $time ? $time : junit_get_timer($file_name);
2705	junit_suite_record($suite, 'execution_time', $time);
2706
2707	$escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8');
2708	$escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function ($c) {
2709		return sprintf('[[0x%02x]]', ord($c[0]));
2710	}, $escaped_details);
2711	$escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
2712
2713    $escaped_test_name = basename($file_name) . ' - ' . htmlspecialchars($test_name, ENT_QUOTES);
2714    $JUNIT['files'][$file_name]['xml'] = "<testcase classname='$suite' name='$escaped_test_name' time='$time'>\n";
2715
2716	if (is_array($type)) {
2717		$output_type = $type[0] . 'ED';
2718		$temp = array_intersect(array('XFAIL', 'FAIL', 'WARN'), $type);
2719		$type = reset($temp);
2720	} else {
2721		$output_type = $type . 'ED';
2722	}
2723
2724	if ('PASS' == $type || 'XFAIL' == $type) {
2725		junit_suite_record($suite, 'test_pass');
2726	} elseif ('BORK' == $type) {
2727		junit_suite_record($suite, 'test_error');
2728		$JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'/>\n";
2729	} elseif ('SKIP' == $type) {
2730		junit_suite_record($suite, 'test_skip');
2731		$JUNIT['files'][$file_name]['xml'] .= "<skipped>$escaped_message</skipped>\n";
2732	} elseif ('WARN' == $type) {
2733		junit_suite_record($suite, 'test_warn');
2734		$JUNIT['files'][$file_name]['xml'] .= "<warning>$escaped_message</warning>\n";
2735	} elseif('FAIL' == $type) {
2736		junit_suite_record($suite, 'test_fail');
2737		$JUNIT['files'][$file_name]['xml'] .= "<failure type='$output_type' message='$escaped_message'>$escaped_details</failure>\n";
2738	} else {
2739		junit_suite_record($suite, 'test_error');
2740		$JUNIT['files'][$file_name]['xml'] .= "<error type='$output_type' message='$escaped_message'>$escaped_details</error>\n";
2741	}
2742
2743	$JUNIT['files'][$file_name]['xml'] .= "</testcase>\n";
2744
2745}
2746
2747function junit_suite_record($suite, $param, $value = 1) {
2748	global $JUNIT;
2749
2750	$JUNIT[$param] += $value;
2751	$JUNIT['suites'][$suite][$param] += $value;
2752}
2753
2754function junit_get_timer($file_name) {
2755	global $JUNIT;
2756	if (!junit_enabled()) return 0;
2757
2758	if (isset($JUNIT['files'][$file_name]['total'])) {
2759		return number_format($JUNIT['files'][$file_name]['total'], 4);
2760	}
2761
2762	return 0;
2763}
2764
2765function junit_start_timer($file_name) {
2766	global $JUNIT;
2767	if (!junit_enabled()) return;
2768
2769	if (!isset($JUNIT['files'][$file_name]['start'])) {
2770		$JUNIT['files'][$file_name]['start'] = microtime(true);
2771
2772		$suite = junit_get_suitename_for($file_name);
2773		junit_init_suite($suite);
2774		$JUNIT['suites'][$suite]['files'][$file_name] = $file_name;
2775	}
2776}
2777
2778function junit_get_suitename_for($file_name) {
2779	return junit_path_to_classname(dirname($file_name));
2780}
2781
2782function junit_path_to_classname($file_name) {
2783    global $JUNIT;
2784    return $JUNIT['name'] . '.' . str_replace(DIRECTORY_SEPARATOR, '.', $file_name);
2785}
2786
2787function junit_init_suite($suite_name) {
2788	global $JUNIT;
2789	if (!junit_enabled()) return;
2790
2791	if (!empty($JUNIT['suites'][$suite_name])) {
2792		return;
2793	}
2794
2795	$JUNIT['suites'][$suite_name] = array(
2796		'name'          => $suite_name,
2797		'test_total'    => 0,
2798		'test_pass'     => 0,
2799		'test_fail'     => 0,
2800		'test_error'    => 0,
2801		'test_skip'     => 0,
2802		'suites'        => array(),
2803		'files'         => array(),
2804		'execution_time'=> 0,
2805	);
2806}
2807
2808function junit_finish_timer($file_name) {
2809	global $JUNIT;
2810	if (!junit_enabled()) return;
2811
2812	if (!isset($JUNIT['files'][$file_name]['start'])) {
2813		error("Timer for $file_name was not started!");
2814	}
2815
2816	if (!isset($JUNIT['files'][$file_name]['total'])) {
2817        $JUNIT['files'][$file_name]['total'] = 0;
2818    }
2819
2820	$start = $JUNIT['files'][$file_name]['start'];
2821	$JUNIT['files'][$file_name]['total'] += microtime(true) - $start;
2822	unset($JUNIT['files'][$file_name]['start']);
2823}
2824
2825/*
2826 * Local variables:
2827 * tab-width: 4
2828 * c-basic-offset: 4
2829 * End:
2830 * vim: noet sw=4 ts=4
2831 */
2832?>
2833