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