xref: /PHP-5.5/server-tests.php (revision 02e4d7a2)
1<?php
2/*
3   +----------------------------------------------------------------------+
4   | PHP Version 5                                                        |
5   +----------------------------------------------------------------------+
6   | Copyright (c) 1997-2010 The PHP Group                                |
7   +----------------------------------------------------------------------+
8   | This source file is subject to version 3.01 of the PHP license,      |
9   | that is bundled with this package in the file LICENSE, and is        |
10   | available through the world-wide-web at the following url:           |
11   | http://www.php.net/license/3_01.txt                                  |
12   | If you did not receive a copy of the PHP license and are unable to   |
13   | obtain it through the world-wide-web, please send a note to          |
14   | license@php.net so we can mail you a copy immediately.               |
15   +----------------------------------------------------------------------+
16   | Authors: Ilia Alshanetsky <ilia@php.net>                             |
17   |          Preston L. Bannister <pbannister@php.net>                   |
18   |          Marcus Boerger <helly@php.net>                              |
19   |          Shane Caraveo <shane@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
27set_time_limit(0);
28while(@ob_end_clean());
29if (ob_get_level()) echo "Not all buffers were deleted.\n";
30error_reporting(E_ALL);
31
32/**********************************************************************
33 * QA configuration
34 */
35
36define('PHP_QA_EMAIL', 'qa-reports@lists.php.net');
37define('QA_SUBMISSION_PAGE', 'http://qa.php.net/buildtest-process.php');
38
39/**********************************************************************
40 * error messages
41 */
42
43define('PCRE_MISSING_ERROR',
44'+-----------------------------------------------------------+
45|                       ! ERROR !                           |
46| The test-suite requires that you have pcre extension      |
47| enabled. To enable this extension either compile your PHP |
48| with --with-pcre-regex or if you have compiled pcre as a  |
49| shared module load it via php.ini.                        |
50+-----------------------------------------------------------+');
51define('SAFE_MODE_WARNING',
52'+-----------------------------------------------------------+
53|                       ! WARNING !                         |
54| You are running the test-suite with "safe_mode" ENABLED ! |
55|                                                           |
56| Chances are high that no test will work at all,           |
57| depending on how you configured "safe_mode" !             |
58+-----------------------------------------------------------+');
59define('TMP_MISSING',
60'+-----------------------------------------------------------+
61|                       ! ERROR   !                         |
62| You must create /tmp for session tests to work!           |
63|                                                           |
64+-----------------------------------------------------------+');
65define('PROC_OPEN_MISSING',
66'+-----------------------------------------------------------+
67|                       ! ERROR !                           |
68| The test-suite requires that proc_open() is available.    |
69| Please check if you disabled it in php.ini.               |
70+-----------------------------------------------------------+');
71define('REQ_PHP_VERSION',
72'+-----------------------------------------------------------+
73|                       ! ERROR !                           |
74| The test-suite must be run with PHP 5 or later.           |
75| You can still test older extecutables by setting          |
76| TEST_PHP_EXECUTABLE and running this script with PHP 5.   |
77+-----------------------------------------------------------+');
78/**********************************************************************
79 * information scripts
80 */
81define('PHP_INFO_SCRIPT','<?php echo "
82PHP_SAPI=" . PHP_SAPI . "
83PHP_VERSION=" . phpversion() . "
84ZEND_VERSION=" . zend_version() . "
85PHP_OS=" . PHP_OS . "
86INCLUDE_PATH=" . get_cfg_var("include_path") . "
87INI=" . realpath(get_cfg_var("cfg_file_path")) . "
88SCANNED_INI=" . (function_exists(\'php_ini_scanned_files\') ?
89					str_replace("\n","", php_ini_scanned_files()) :
90					"** not determined **") . "
91SERVER_SOFTWARE=" . (isset($_ENV[\'SERVER_SOFTWARE\']) ? $_ENV[\'SERVER_SOFTWARE\'] : \'UNKNOWN\');
92?>');
93
94define('PHP_EXTENSIONS_SCRIPT','<?php print join(get_loaded_extensions(),":"); ?>');
95define('PHP_INI_SETTINGS_SCRIPT','<?php echo serialize(ini_get_all()); ?>');
96
97/**********************************************************************
98 * various utility functions
99 */
100
101function settings2array($settings, &$ini_settings)
102{
103	foreach($settings as $setting) {
104		if (strpos($setting, '=')!==false) {
105			$setting = explode("=", $setting, 2);
106			$name = trim($setting[0]);
107			$value = trim($setting[1]);
108			$ini_settings[$name] = $value;
109		}
110	}
111}
112
113function settings2params(&$ini_settings)
114{
115	$settings = '';
116	if (count($ini_settings)) {
117		foreach($ini_settings as $name => $value) {
118			$value = addslashes($value);
119			$settings .= " -d \"".strtolower($name)."=$value\"";
120		}
121	}
122	return $settings;
123}
124
125function generate_diff($wanted,$output)
126{
127	$w = explode("\n", $wanted);
128	$o = explode("\n", $output);
129	$w1 = array_diff_assoc($w,$o);
130	$o1 = array_diff_assoc($o,$w);
131	$w2 = array();
132	$o2 = array();
133	foreach($w1 as $idx => $val) $w2[sprintf("%03d<",$idx)] = sprintf("%03d- ", $idx+1).$val;
134	foreach($o1 as $idx => $val) $o2[sprintf("%03d>",$idx)] = sprintf("%03d+ ", $idx+1).$val;
135	$diff = array_merge($w2, $o2);
136	ksort($diff);
137	return implode("\r\n", $diff);
138}
139
140function mkpath($path,$mode = 0777) {
141	$dirs = preg_split('/[\\/]/',$path);
142	$path = $dirs[0];
143	for($i = 1;$i < count($dirs);$i++) {
144		$path .= '/'.$dirs[$i];
145		@mkdir($path,$mode);
146	}
147}
148
149function copyfiles($src,$new) {
150	$d = dir($src);
151	while (($entry = $d->read())) {
152		if (is_file("$src/$entry")) {
153			copy("$src/$entry", "$new/$entry");
154		}
155	}
156	$d->close();
157}
158
159function post_result_data($query,$data)
160{
161	$url = QA_SUBMISSION_PAGE.'?'.$query;
162	$post = "php_test_data=" . urlencode(base64_encode(preg_replace("/[\\x00]/", "[0x0]", $data)));
163	$r = new HTTPRequest($url,NULL,NULL,$post);
164	return $this->response_headers['Status']=='200';
165}
166
167
168function execute($command, $args=NULL, $input=NULL, $cwd=NULL, $env=NULL)
169{
170	$data = "";
171
172	if (gettype($args)=='array') {
173		$args = join($args,' ');
174	}
175	$commandline = "$command $args";
176	$proc = proc_open($commandline, array(
177				0 => array('pipe', 'r'),
178				1 => array('pipe', 'w')),
179				$pipes, $cwd, $env);
180
181	if (!$proc)
182		return false;
183
184	if ($input) {
185		$out = fwrite($pipes[0],$input);
186		if ($out != strlen($input)) {
187			return NULL;
188		}
189	}
190
191	fclose($pipes[0]);
192
193	while (true) {
194		/* hide errors from interrupted syscalls */
195		$r = $pipes;
196		$w = null;
197		$e = null;
198		$n = @stream_select($r, $w, $e, 60);
199
200		if ($n === 0) {
201			/* timed out */
202			$data .= "\n ** ERROR: process timed out **\n";
203			proc_terminate($proc);
204			return $data;
205		} else if ($n) {
206			$line = fread($pipes[1], 8192);
207			if (strlen($line) == 0) {
208				/* EOF */
209				break;
210			}
211			$data .= $line;
212		}
213	}
214	$code = proc_close($proc);
215	return $data;
216}
217
218function executeCode($php, $ini_overwrites, $code, $remove_headers=true, $cwd=NULL, $env=NULL)
219{
220	$params = NULL;
221	if ($ini_overwrites) {
222		$info_params = array();
223		settings2array($ini_overwrites,$info_params);
224		$params = settings2params($info_params);
225	}
226	$out = execute($php, $params, $code, $cwd, $env);
227	// kill the headers
228	if ($remove_headers && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) {
229		$out = $match[2];
230	}
231	return $out;
232}
233
234
235/**********************************************************************
236 * a simple request class that lets us handle http based tests
237 */
238
239class HTTPRequest
240{
241    public $headers = array();
242    public $timeout = 4;
243    public $urlparts = NULL;
244    public $url = '';
245    public $userAgent = 'PHP-Test-Harness';
246    public $options = array();
247    public $postdata = NULL;
248    public $errmsg = '';
249    public $errno = 0;
250    public $response;
251    public $response_headers;
252    public $outgoing_payload;
253    public $incoming_payload = '';
254
255    /*
256    URL is the full url
257    headers is assoc array of outgoing http headers
258
259    options may include
260    timeout
261    proxy_host
262    proxy_port
263    proxy_user
264    proxy_pass
265    method (GET|POST)
266
267    post data is, well, post data.  It is not processed so
268        multipart stuff must be prepared before calling this
269        (or add it to class)
270    */
271    function HTTPRequest($URL, $headers=array(), $options=array(), $postdata=NULL)
272    {
273        $this->urlparts = @parse_url($URL);
274        $this->url = $URL;
275        $this->options = $options;
276        $this->headers = $headers;
277        $this->postdata = &$postdata;
278        $this->doRequest();
279    }
280
281    function doRequest()
282    {
283        if (!$this->_validateUrl()) return;
284
285        if (isset($this->options['timeout']))
286            $this->timeout = (int)$this->options['timeout'];
287
288        $this->_sendHTTP();
289    }
290
291    function _validateUrl()
292    {
293        if ( ! is_array($this->urlparts) ) {
294            return FALSE;
295        }
296        if (!isset($this->urlparts['host'])) {
297            $this->urlparts['host']='127.0.0.1';
298        }
299        if (!isset($this->urlparts['port'])) {
300            $this->urlparts['port'] = 80;
301        }
302        if (!isset($this->urlparts['path']) || !$this->urlparts['path'])
303            $this->urlparts['path'] = '/';
304        return TRUE;
305    }
306
307    function _parseResponse()
308    {
309        if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $this->incoming_payload, $match)) {
310            $this->response = $match[2];
311            if (preg_match("/^HTTP\/1\.. (\d+).*/s",$match[1],$status) && !$status[1]) {
312                    $this->errmsg = "HTTP Response $status[1] Not Found";
313                    return FALSE;
314            }
315            $rh = preg_split("/[\n\r]+/",$match[1]);
316            $this->response_headers = array();
317            foreach ($rh as $line) {
318                if (strpos($line, ':')!==false) {
319                    $line = explode(":", $line, 2);
320                    $this->response_headers[trim($line[0])] = trim($line[1]);
321                }
322            }
323            $this->response_headers['Status']=$status[1];
324            // if no content, return false
325            if(strlen($this->response) > 0) return TRUE;
326        }
327        $this->errmsg = 'Invalid HTTP Response';
328        return FALSE;
329    }
330
331    function &_getRequest()
332    {
333        $fullpath = $this->urlparts['path'].
334                    (isset($this->urlparts['query'])?'?'.$this->urlparts['query']:'').
335                    (isset($this->urlparts['fragment'])?'#'.$this->urlparts['fragment']:'');
336        if (isset($this->options['proxy_host'])) {
337            $fullpath = 'http://'.$this->urlparts['host'].':'.$this->urlparts['port'].$fullpath;
338        }
339        if (isset($this->options['proxy_user'])) {
340            $headers['Proxy-Authorization'] = 'Basic ' . base64_encode($this->options['proxy_user'].":".$this->options['proxy_pass']);
341        }
342        $headers['User-Agent'] = $this->userAgent;
343        $headers['Host'] = $this->urlparts['host'];
344        $headers['Content-Length'] = strlen($this->postdata);
345        $headers['Content-Type'] = 'application/x-www-form-urlencoded';
346        if (isset($this->headers)) {
347            $headers = array_merge($headers, $this->headers);
348        }
349        $headertext = '';
350        foreach ($headers as $k => $v) {
351            $headertext .= "$k: $v\r\n";
352        }
353        $method = trim($this->options['method'])?strtoupper(trim($this->options['method'])):'GET';
354        $this->outgoing_payload =
355                "$method $fullpath HTTP/1.0\r\n".
356                $headertext."\r\n".
357                $this->postdata;
358        return $this->outgoing_payload;
359    }
360
361    function _sendHTTP()
362    {
363        $this->_getRequest();
364        $host = $this->urlparts['host'];
365        $port = $this->urlparts['port'];
366        if (isset($this->options['proxy_host'])) {
367            $host = $this->options['proxy_host'];
368            $port = isset($this->options['proxy_port'])?$this->options['proxy_port']:8080;
369        }
370        // send
371        if ($this->timeout > 0) {
372            $fp = fsockopen($host, $port, $this->errno, $this->errmsg, $this->timeout);
373        } else {
374            $fp = fsockopen($host, $port, $this->errno, $this->errmsg);
375        }
376        if (!$fp) {
377            $this->errmsg = "Connect Error to $host:$port";
378            return NULL;
379        }
380        if ($this->timeout > 0) {
381            // some builds of php do not support this, silence
382            // the warning
383            @socket_set_timeout($fp, $this->timeout);
384        }
385        if (!fputs($fp, $this->outgoing_payload, strlen($this->outgoing_payload))) {
386            $this->errmsg = "Error Sending Request Data to $host";
387            return NULL;
388        }
389
390        while ($data = fread($fp, 32768)) {
391            $this->incoming_payload .= $data;
392        }
393
394        fclose($fp);
395
396        $this->_parseResponse();
397    }
398
399# a simple test case
400#$r = new HTTPRequest('http://localhost:81/info.php/path/info');
401#print_r($r->response_headers);
402#print $r->response;
403
404} // end HTTPRequest
405
406
407/**********************************************************************
408 * main test harness
409 */
410
411class testHarness {
412	public $cwd;
413	public $xargs = array(
414		#arg         env var                value        default   description
415		'c' => array(''                    ,'file'       ,NULL    ,'configuration file, see server-tests-config.php for example'),
416		'd' => array('TEST_PATHS'          ,'paths'      ,NULL    ,'colon separate path list'),
417		'e' => array('TEST_PHP_ERROR_STYLE','EMACS|MSVC' ,'EMACS' ,'editor error style'),
418		'h' => array(''                    ,''           ,NULL    ,'this help'),
419		'i' => array('PHPRC'               ,'path|file'  ,NULL    ,'ini file to use for tests (sets PHPRC)'),
420		'l' => array('TEST_PHP_LOG_FORMAT' ,'string'     ,'LEODC' ,'any combination of CDELO'),
421		'm' => array('TEST_BASE_PATH'      ,'path'       ,NULL    ,'copy tests to this path before testing'),
422		'n' => array('NO_PHPTEST_SUMMARY'  ,''           ,0       ,'do not print test summary'),
423		'p' => array('TEST_PHP_EXECUTABLE' ,'path'       ,NULL    ,'php executable to be tested'),
424		'q' => array('NO_INTERACTION'      ,''           ,0       ,'no console interaction (ie dont contact QA)'),
425		'r' => array('REPORT_EXIT_STATUS'  ,''           ,0       ,'exit with status at end of execution'),
426		's' => array('TEST_PHP_SRCDIR'     ,'path'       ,NULL    ,'path to php source code'),
427		't' => array('TEST_PHP_DETAILED'   ,'number'     ,0       ,'level of detail output to dump'),
428		'u' => array('TEST_WEB_BASE_URL'   ,'url'        ,''      ,'base url for http testing'),
429		'v' => array('TEST_CONTEXT_INFO'   ,''           ,0       ,'view text executable context info'),
430		'w' => array('TEST_WEB'            ,''           ,0       ,'run tests via http'),
431		'x' => array('TEST_WEB_EXT'        ,'file ext'   ,'php'   ,'http file extension to use')
432		);
433
434	public $conf = array();
435	public $test_to_run = array();
436	public $test_files = array();
437	public $test_results = array();
438	public $failed_tests = array();
439	public $exts_to_test;
440	public $exts_tested = 0;
441	public $exts_skipped = 0;
442	public $ignored_by_ext = 0;
443	public $test_dirs = array('tests', 'pear', 'ext', 'sapi');
444	public $start_time;
445	public $end_time;
446	public $exec_info;
447	public $test_executable_iscgi = false;
448	public $inisettings; // the test executables settings, used for web tests
449	public $iswin32 = false;
450
451	public $ddash = "=====================================================================";
452	public $sdash = "---------------------------------------------------------------------";
453
454	// Default ini settings
455	public $ini_overwrites = array(
456			'output_handler'=>'',
457			'zlib.output_compression'=>'Off',
458			'open_basedir'=>'',
459			'safe_mode'=>'0',
460			'disable_functions'=>'',
461			'output_buffering'=>'Off',
462			'error_reporting'=>'4095',
463			'display_errors'=>'1',
464			'log_errors'=>'0',
465			'html_errors'=>'0',
466			'track_errors'=>'1',
467			'report_memleaks'=>'1',
468			'report_zend_debug'=>'0',
469			'docref_root'=>'/phpmanual/',
470			'docref_ext'=>'.html',
471			'error_prepend_string'=>'',
472			'error_append_string'=>'',
473			'auto_prepend_file'=>'',
474			'auto_append_file'=>'',
475			'magic_quotes_runtime'=>'0',
476		);
477	public $env = array();
478	public $info_params = array();
479
480	function testHarness() {
481		$this->iswin32 = substr(PHP_OS, 0, 3) == "WIN";
482		$this->checkRequirements();
483		$this->env = $_ENV;
484		$this->removeSensitiveEnvVars();
485
486		$this->initializeConfiguration();
487		$this->parseArgs();
488		$this->setTestPaths();
489		# change to working directory
490		if ($this->conf['TEST_PHP_SRCDIR']) {
491			@chdir($this->conf['TEST_PHP_SRCDIR']);
492		}
493		$this->cwd = getcwd();
494
495		if (!$this->conf['TEST_PHP_SRCDIR'])
496			$this->conf['TEST_PHP_SRCDIR'] = $this->cwd;
497		if (!$this->conf['TEST_BASE_PATH'] && $this->conf['TEST_PHP_SRCDIR'])
498			$this->conf['TEST_BASE_PATH'] = $this->conf['TEST_PHP_SRCDIR'];
499		if ($this->iswin32) {
500			$this->conf['TEST_PHP_SRCDIR'] = str_replace('/','\\',$this->conf['TEST_PHP_SRCDIR']);
501			$this->conf['TEST_BASE_PATH'] = str_replace('/','\\',$this->conf['TEST_BASE_PATH']);
502		}
503
504		if (!$this->conf['TEST_WEB'] && !is_executable($this->conf['TEST_PHP_EXECUTABLE'])) {
505			$this->error("invalid PHP executable specified by TEST_PHP_EXECUTABLE  = " .
506					$this->conf['TEST_PHP_EXECUTABLE']);
507			return false;
508		}
509
510		$this->getInstalledExtensions();
511		$this->getExecutableInfo();
512		$this->getExecutableIniSettings();
513		$this->test_executable_iscgi = strncmp($this->exec_info['PHP_SAPI'],'cgi',3)==0;
514		$this->calculateDocumentRoot();
515
516		// add TEST_PHP_SRCDIR to the include path, this facilitates
517		// tests including files from src/tests
518		//$this->ini_overwrites['include_path'] = $this->cwd.($this->iswin32?';.;':':.:').$this->exec_info['INCLUDE_PATH'];
519
520		$params = array();
521		settings2array($this->ini_overwrites,$params);
522		$this->info_params = settings2params($params);
523
524		$this->contextHeader();
525		if ($this->conf['TEST_CONTEXT_INFO']) return;
526		$this->loadFileList();
527		$this->moveTestFiles();
528		$this->run();
529		$this->summarizeResults();
530	}
531
532	function getExecutableIniSettings()
533	{
534		$out = $this->runscript(PHP_INI_SETTINGS_SCRIPT,true);
535		$this->inisettings = unserialize($out);
536	}
537
538	function getExecutableInfo()
539	{
540		$out = $this->runscript(PHP_INFO_SCRIPT,true);
541		$out = preg_split("/[\n\r]+/",$out);
542		$info = array();
543		foreach ($out as $line) {
544			if (strpos($line, '=')!==false) {
545				$line = explode("=", $line, 2);
546				$name = trim($line[0]);
547				$value = trim($line[1]);
548				$info[$name] = $value;
549			}
550		}
551		$this->exec_info = $info;
552	}
553
554	function getInstalledExtensions()
555	{
556		// get the list of installed extensions
557		$out = $this->runscript(PHP_EXTENSIONS_SCRIPT,true);
558		$this->exts_to_test = explode(":",$out);
559		sort($this->exts_to_test);
560		$this->exts_tested = count($this->exts_to_test);
561	}
562
563	// if running local, calls executeCode,
564	// otherwise does an http request
565	function runscript($script,$removeheaders=false,$cwd=NULL)
566	{
567		if ($this->conf['TEST_WEB']) {
568			$pi = '/testscript.' . $this->conf['TEST_WEB_EXT'];
569			if (!$cwd) $cwd = $this->conf['TEST_BASE_PATH'];
570			$tmp_file = "$cwd$pi";
571			$pi = substr($cwd,strlen($this->conf['TEST_BASE_PATH'])) . $pi;
572			$url = $this->conf['TEST_WEB_BASE_URL'] . $pi;
573			file_put_contents($tmp_file,$script);
574			$fd = fopen($url, "rb");
575			$out = '';
576			if ($fd) {
577				while (!feof($fd))
578					$out .= fread($fd, 8192);
579				fclose($fd);
580			}
581			unlink($tmp_file);
582			if (0 && $removeheaders &&
583				preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) {
584					return $match[2];
585			}
586			return $out;
587		} else {
588			return executeCode($this->conf['TEST_PHP_EXECUTABLE'],$this->ini_overwrites, $script,$removeheaders,$cwd,$this->env);
589		}
590	}
591
592
593	// Use this function to do any displaying of text, so that
594	// things can be over-written as necessary.
595
596	function writemsg($msg) {
597
598	    echo $msg;
599
600	}
601
602	// Another wrapper function, this one should be used any time
603	// a particular test passes or fails
604
605	function showstatus($item, $status, $reason = '') {
606
607	    switch($status) {
608		case 'PASSED':
609		    $this->writemsg("PASSED: $item ($reason)\n");
610		    break;
611		case 'FAILED':
612		    $this->writemsg("FAILED: $item ($reason)\n");
613		    break;
614		case 'SKIPPED':
615		    $this->writemsg("SKIPPED: $item ($reason)\n");
616		    break;
617	    }
618	}
619
620
621	function help()
622	{
623		$usage = "usage: php run-tests.php [options]\n";
624		foreach ($this->xargs as $arg=>$arg_info) {
625			$usage .= sprintf(" -%s  %-12s %s\n",$arg,$arg_info[1],$arg_info[3]);
626		}
627		return $usage;
628	}
629
630	function parseArgs() {
631		global $argc;
632		global $argv;
633		global $_SERVER;
634
635		if (!isset($argv)) {
636			$argv = $_SERVER['argv'];
637			$argc = $_SERVER['argc'];
638		}
639
640		$conf = NULL;
641		for ($i=1; $i<$argc;) {
642			if ($argv[$i][0] != '-') continue;
643			$opt = $argv[$i++][1];
644			if (isset($value)) unset($value);
645			if (@$argv[$i][0] != '-') {
646				@$value = $argv[$i++];
647			}
648			switch($opt) {
649			case 'c':
650				/* TODO: Implement configuraiton file */
651				include($value);
652				if (!isset($conf)) {
653					$this->writemsg("Invalid configuration file\n");
654					exit(1);
655				}
656				$this->conf = array_merge($this->conf,$conf);
657				break;
658			case 'e':
659				$this->conf['TEST_PHP_ERROR_STYLE'] = strtoupper($value);
660				break;
661			case 'h':
662				print $this->help();
663				exit(0);
664			default:
665				if ($this->xargs[$opt][1] && isset($value))
666					$this->conf[$this->xargs[$opt][0]] = $value;
667				else if (!$this->xargs[$opt][1])
668					$this->conf[$this->xargs[$opt][0]] = isset($value)?$value:1;
669				else
670					$this->error("Invalid argument setting for argument $opt, should be [{$this->xargs[$opt][1]}]\n");
671				break;
672			}
673		}
674
675		// set config into environment, this allows
676		// executed tests to find out about the test
677		// configurations.  config file or args overwrite
678		// env var config settings
679		$this->env = array_merge($this->env,$this->conf);
680		if (!$this->conf['TEST_WEB'] && !$this->conf['TEST_PHP_EXECUTABLE']) {
681			$this->writemsg($this->help());
682			exit(0);
683		}
684	}
685
686	function removeSensitiveEnvVars()
687	{
688		# delete sensitive env vars
689		$this->env['SSH_CLIENT']='deleted';
690		$this->env['SSH_AUTH_SOCK']='deleted';
691		$this->env['SSH_TTY']='deleted';
692	}
693
694	function setEnvConfigVar($name)
695	{
696		if (isset($this->env[$name])) {
697			$this->conf[$name] = $this->env[$name];
698		}
699	}
700
701	function initializeConfiguration()
702	{
703		foreach ($this->xargs as $arg=>$arg_info) {
704			if ($arg_info[0]) {
705				# initialize the default setting
706				$this->conf[$arg_info[0]]=$arg_info[2];
707				# get config from environment
708				$this->setEnvConfigVar($arg_info[0]);
709			}
710		}
711	}
712
713	function setTestPaths()
714	{
715		// configure test paths from config file or command line
716		if (@$this->conf['TEST_PATHS']) {
717			$this->test_dirs = array();
718			if ($this->iswin32) {
719				$paths = explode(';',$this->conf['TEST_PATHS']);
720			} else {
721				$paths = explode(':|;',$this->conf['TEST_PATHS']);
722			}
723			foreach($paths as $path) {
724				$this->test_dirs[] = realpath($path);
725			}
726		}
727	}
728
729	function test_sort($a, $b) {
730		$ta = strpos($a, "{$this->cwd}/tests")===0 ? 1 + (strpos($a, "{$this->cwd}/tests/run-test")===0 ? 1 : 0) : 0;
731		$tb = strpos($b, "{$this->cwd}/tests")===0 ? 1 + (strpos($b, "{$this->cwd}/tests/run-test")===0 ? 1 : 0) : 0;
732		if ($ta == $tb) {
733			return strcmp($a, $b);
734		} else {
735			return $tb - $ta;
736		}
737	}
738
739	function checkRequirements() {
740		if (version_compare(phpversion(), "5.0") < 0) {
741			$this->writemsg(REQ_PHP_VERSION);
742			exit;
743		}
744// We might want to check another server so we won't see that server's /tmp
745//		if (!file_exists("/tmp")) {
746//			$this->writemsg(TMP_MISSING);
747//			exit;
748//		}
749		if (!function_exists("proc_open")) {
750			$this->writemsg(PROC_OPEN_MISSING);
751			exit;
752		}
753		if (!extension_loaded("pcre")) {
754			$this->writemsg(PCRE_MISSING_ERROR);
755			exit;
756		}
757		if (ini_get('safe_mode')) {
758			$this->writemsg(SAFE_MODE_WARNING);
759		}
760	}
761
762	//
763	// Write test context information.
764	//
765	function contextHeader()
766	{
767		$info = '';
768		foreach ($this->exec_info as $k=>$v) {
769			$info .= sprintf("%-20.s: %s\n",$k,$v);
770		}
771		$exts = '';
772		foreach ($this->exts_to_test as $ext) {
773			$exts .="$ext\n              ";
774		}
775		$dirs = '';
776		foreach ($this->test_dirs as $test_dir) {
777			$dirs .= "$test_dir\n              ";
778		}
779		$conf = '';
780		foreach ($this->conf as $k=>$v) {
781			$conf .= sprintf("%-20.s: %s\n",$k,$v);
782		}
783
784		$exeinfo = '';
785		if (!$this->conf['TEST_WEB'])
786			$exeinfo = "CWD                 : {$this->cwd}\n".
787					"PHP                 : {$this->conf['TEST_PHP_EXECUTABLE']}\n";
788
789		$this->writemsg("\n$this->ddash\n".
790			"$exeinfo$info\n".
791			"Test Harness Configuration:\n$conf\n".
792			"Extensions  : $exts\n".
793			"Test Dirs   : $dirs\n".
794			"$this->ddash\n");
795	}
796
797	function loadFileList()
798	{
799		foreach ($this->test_dirs as $dir) {
800			if (is_dir($dir)) {
801				$this->findFilesInDir($dir, ($dir == 'ext'));
802			} else {
803				$this->test_files[] = $dir;
804			}
805		}
806		usort($this->test_files,array($this,"test_sort"));
807		$this->writemsg("found ".count($this->test_files)." files\n");
808	}
809
810	function moveTestFiles()
811	{
812		if (!$this->conf['TEST_BASE_PATH'] ||
813			$this->conf['TEST_BASE_PATH'] == $this->conf['TEST_PHP_SRCDIR']) return;
814		$this->writemsg("moving files from {$this->conf['TEST_PHP_SRCDIR']} to {$this->conf['TEST_BASE_PATH']}\n");
815		$l = strlen($this->conf['TEST_PHP_SRCDIR']);
816		$files = array();
817		$dirs = array();
818		foreach ($this->test_files as $file) {
819			if (strpos($file,$this->conf['TEST_PHP_SRCDIR'])==0) {
820				$newlocation = $this->conf['TEST_BASE_PATH'].substr($file,$l);
821				$files[] = $newlocation;
822				$dirs[dirname($file)] = dirname($newlocation);
823			} else {
824				// XXX what to do with test files outside the
825				// php source directory?  Need to map them into
826				// the new directory somehow.
827			}
828		}
829		foreach ($dirs as $src=>$new) {
830			mkpath($new);
831			copyfiles($src,$new);
832		}
833		$this->test_files = $files;
834	}
835
836	function findFilesInDir($dir,$is_ext_dir=FALSE,$ignore=FALSE)
837	{
838		$skip = array('.', '..', 'CVS');
839		$o = opendir($dir) or $this->error("cannot open directory: $dir");
840		while (($name = readdir($o)) !== FALSE) {
841			if (in_array($name, $skip)) continue;
842			if (is_dir("$dir/$name")) {
843				$skip_ext = ($is_ext_dir && !in_array($name, $this->exts_to_test));
844				if ($skip_ext) {
845					$this->exts_skipped++;
846				}
847				$this->findFilesInDir("$dir/$name", FALSE, $ignore || $skip_ext);
848			}
849
850			// Cleanup any left-over tmp files from last run.
851			if (substr($name, -4) == '.tmp') {
852				@unlink("$dir/$name");
853				continue;
854			}
855
856			// Otherwise we're only interested in *.phpt files.
857			if (substr($name, -5) == '.phpt') {
858				if ($ignore) {
859					$this->ignored_by_ext++;
860				} else {
861					$testfile = realpath("$dir/$name");
862					$this->test_files[] = $testfile;
863				}
864			}
865		}
866		closedir($o);
867	}
868
869	function runHeader()
870	{
871		$this->writemsg("TIME START " . date('Y-m-d H:i:s', $this->start_time) . "\n".$this->ddash."\n");
872		if (count($this->test_to_run)) {
873			$this->writemsg("Running selected tests.\n");
874		} else {
875			$this->writemsg("Running all test files.\n");
876		}
877	}
878
879	function run()
880	{
881		$this->start_time = time();
882		$this->runHeader();
883		// Run selected tests.
884		if (count($this->test_to_run)) {
885
886			foreach($this->test_to_run as $name=>$runnable) {
887				if(!preg_match("/\.phpt$/", $name))
888					continue;
889				if ($runnable) {
890					$this->test_results[$name] = $this->run_test($name);
891				}
892			}
893		} else {
894			foreach ($this->test_files as $name) {
895				$this->test_results[$name] = $this->run_test($name);
896			}
897		}
898		$this->end_time = time();
899	}
900
901	function summarizeResults()
902	{
903		if (count($this->test_results) == 0) {
904			$this->writemsg("No tests were run.\n");
905			return;
906		}
907
908		$n_total = count($this->test_results);
909		$n_total += $this->ignored_by_ext;
910
911		$sum_results = array('PASSED'=>0, 'SKIPPED'=>0, 'FAILED'=>0);
912		foreach ($this->test_results as $v) {
913			$sum_results[$v]++;
914		}
915		$sum_results['SKIPPED'] += $this->ignored_by_ext;
916		$percent_results = array();
917		while (list($v,$n) = each($sum_results)) {
918			$percent_results[$v] = (100.0 * $n) / $n_total;
919		}
920
921		$this->writemsg("\n".$this->ddash."\n".
922			"TIME END " . date('Y-m-d H:i:s', $this->end_time) . "\n".
923			$this->ddash."\n".
924			"TEST RESULT SUMMARY\n".
925			$this->sdash."\n".
926			"Exts skipped    : " . sprintf("%4d",$this->exts_skipped) . "\n".
927			"Exts tested     : " . sprintf("%4d",$this->exts_tested) . "\n".
928			$this->sdash."\n".
929			"Number of tests : " . sprintf("%4d",$n_total) . "\n".
930			"Tests skipped   : " . sprintf("%4d (%2.1f%%)",$sum_results['SKIPPED'],$percent_results['SKIPPED']) . "\n".
931			"Tests failed    : " . sprintf("%4d (%2.1f%%)",$sum_results['FAILED'],$percent_results['FAILED']) . "\n".
932			"Tests passed    : " . sprintf("%4d (%2.1f%%)",$sum_results['PASSED'],$percent_results['PASSED']) . "\n".
933			$this->sdash."\n".
934			"Time taken      : " . sprintf("%4d seconds", $this->end_time - $this->start_time) . "\n".
935			$this->ddash."\n");
936
937		$failed_test_summary = '';
938		if ($this->failed_tests) {
939			$failed_test_summary .= "\n".$this->ddash."\n".
940				"FAILED TEST SUMMARY\n".$this->sdash."\n";
941			foreach ($this->failed_tests as $failed_test_data) {
942				$failed_test_summary .=  $failed_test_data['test_name'] . "\n";
943			}
944			$failed_test_summary .=  $this->ddash."\n";
945		}
946
947		if ($failed_test_summary && !$this->conf['NO_PHPTEST_SUMMARY']) {
948			$this->writemsg($failed_test_summary);
949		}
950
951		/* We got failed Tests, offer the user to send and e-mail to QA team, unless NO_INTERACTION is set */
952		if ($sum_results['FAILED'] && !$this->conf['NO_INTERACTION']) {
953			$fp = fopen("php://stdin", "r+");
954			$this->writemsg("\nPlease allow this report to be send to the PHP QA\nteam. This will give us a better understanding in how\n");
955			$this->writemsg("PHP's test cases are doing.\n");
956			$this->writemsg("(choose \"s\" to just save the results to a file)? [Yns]: ");
957			flush();
958			$user_input = fgets($fp, 10);
959			$just_save_results = (strtolower($user_input[0]) == 's');
960
961			if ($just_save_results || strlen(trim($user_input)) == 0 || strtolower($user_input[0]) == 'y') {
962				/*
963				 * Collect information about the host system for our report
964				 * Fetch phpinfo() output so that we can see the PHP enviroment
965				 * Make an archive of all the failed tests
966				 * Send an email
967				 */
968
969				/* Ask the user to provide an email address, so that QA team can contact the user */
970				if (!strncasecmp($user_input, 'y', 1) || strlen(trim($user_input)) == 0) {
971					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): ";
972					flush();
973					$fp = fopen("php://stdin", "r+");
974					$user_email = trim(fgets($fp, 1024));
975					$user_email = str_replace("@", " at ", str_replace(".", " dot ", $user_email));
976				}
977
978				$failed_tests_data = '';
979				$sep = "\n" . str_repeat('=', 80) . "\n";
980
981				$failed_tests_data .= $failed_test_summary . "\n";
982
983				if (array_sum($this->failed_tests)) {
984					foreach ($this->failed_tests as $test_info) {
985						$failed_tests_data .= $sep . $test_info['name'];
986						$failed_tests_data .= $sep . file_get_contents(realpath($test_info['output']));
987						$failed_tests_data .= $sep . file_get_contents(realpath($test_info['diff']));
988						$failed_tests_data .= $sep . "\n\n";
989					}
990					$status = "failed";
991				} else {
992					$status = "success";
993				}
994
995				$failed_tests_data .= "\n" . $sep . 'BUILD ENVIRONMENT' . $sep;
996				$failed_tests_data .= "OS:\n". PHP_OS. "\n\n";
997				$automake = $autoconf = $libtool = $compiler = 'N/A';
998
999				if (!$this->iswin32) {
1000					$automake = shell_exec('automake --version');
1001					$autoconf = shell_exec('autoconf --version');
1002					/* Always use the generated libtool - Mac OSX uses 'glibtool' */
1003					$libtool = shell_exec('./libtool --version');
1004					/* Try the most common flags for 'version' */
1005					$flags = array('-v', '-V', '--version');
1006					$cc_status=0;
1007					foreach($flags AS $flag) {
1008						system($this->env['CC']." $flag >/dev/null 2>&1", $cc_status);
1009						if($cc_status == 0) {
1010							$compiler = shell_exec($this->env['CC']." $flag 2>&1");
1011							break;
1012						}
1013					}
1014				}
1015
1016				$failed_tests_data .= "Automake:\n$automake\n";
1017				$failed_tests_data .= "Autoconf:\n$autoconf\n";
1018				$failed_tests_data .= "Libtool:\n$libtool\n";
1019				$failed_tests_data .= "Compiler:\n$compiler\n";
1020				$failed_tests_data .= "Bison:\n". @shell_exec('bison --version'). "\n";
1021				$failed_tests_data .= "\n\n";
1022
1023				if (isset($user_email)) {
1024					$failed_tests_data .= "User's E-mail: ".$user_email."\n\n";
1025				}
1026
1027				$failed_tests_data .= $sep . "PHPINFO" . $sep;
1028				$failed_tests_data .= shell_exec($this->conf['TEST_PHP_EXECUTABLE'].' -dhtml_errors=0 -i');
1029
1030				$compression = 0;
1031
1032				if ($just_save_results ||
1033					!post_result_data("status=$status&version=".urlencode(TESTED_PHP_VERSION),$failed_tests_data)) {
1034					$output_file = 'php_test_results_' . date('Ymd_Hi') . ( $compression ? '.txt.gz' : '.txt' );
1035					$fp = fopen($output_file, "w");
1036					fwrite($fp, $failed_tests_data);
1037					fclose($fp);
1038
1039					if (!$just_save_results)
1040						echo "\nThe test script was unable to automatically send the report to PHP's QA Team\n";
1041					echo "Please send ".$output_file." to ".PHP_QA_EMAIL." manually, thank you.\n";
1042				} else {
1043					fwrite($fp, "\nThank you for helping to make PHP better.\n");
1044					fclose($fp);
1045				}
1046			}
1047		}
1048
1049		if($this->conf['REPORT_EXIT_STATUS'] and $sum_results['FAILED']) {
1050			exit(1);
1051		}
1052	}
1053
1054	function getINISettings(&$section_text)
1055	{
1056		$ini_settings = $this->ini_overwrites;
1057		// Any special ini settings
1058		// these may overwrite the test defaults...
1059		if (array_key_exists('INI', $section_text)) {
1060			settings2array(preg_split( "/[\n\r]+/", $section_text['INI']), $ini_settings);
1061		}
1062		return $ini_settings;
1063	}
1064
1065	function getINIParams(&$section_text)
1066	{
1067		if (!$section_text) return '';
1068		// XXX php5 current has a problem doing this in one line
1069		// it fails with Only variables can be passed by reference
1070		// on test ext\calendar\tests\jdtojewish.phpt
1071		// return settings2params($this->getINISettings($section_text));
1072		$ini = $this->getINISettings($section_text);
1073		return settings2params($ini);
1074	}
1075
1076	function calculateDocumentRoot()
1077	{
1078		if ($this->conf['TEST_WEB'] || $this->test_executable_iscgi) {
1079			// configure DOCUMENT_ROOT for web tests
1080			// this assumes that directories from the base url
1081			// matches directory depth from the base path
1082			$parts = parse_url($this->conf['TEST_WEB_BASE_URL']);
1083			$depth = substr_count($parts['path'],'/');
1084			$docroot = $this->conf['TEST_BASE_PATH'];
1085			for ($i=0 ; $i < $depth; $i++) $docroot = dirname($docroot);
1086			$this->conf['TEST_DOCUMENT_ROOT']=$docroot;
1087			$this->conf['TEST_BASE_SCRIPT_NAME']=$parts['path'];
1088			$this->conf['TEST_SERVER_URL']=substr($this->conf['TEST_WEB_BASE_URL'],0,strlen($this->conf['TEST_WEB_BASE_URL'])-strlen($parts['path']));
1089		} else {
1090			$this->conf['TEST_DOCUMENT_ROOT']='';
1091			$this->conf['TEST_BASE_SCRIPT_NAME']='';
1092			$this->conf['TEST_SERVER_URL']='';
1093		}
1094	}
1095
1096	function evalSettings($filename,$data) {
1097		// we eval the section so we can allow dynamic env vars
1098		// for cgi testing
1099		$filename = str_replace('\\','/',$filename);
1100		$cwd = str_replace('\\','/',$this->cwd);
1101		$filepath = dirname($filename);
1102		$scriptname = substr($filename,strlen($this->conf['TEST_DOCUMENT_ROOT']));
1103		// eval fails if no newline
1104		return eval("$data\n");
1105	}
1106
1107	function getENVSettings(&$section_text,$testfile)
1108	{
1109		$env = $this->env;
1110		// Any special environment settings
1111		// these may overwrite the test defaults...
1112		if (array_key_exists('ENV', $section_text)) {
1113			$sect = $this->evalSettings($testfile,$section_text['ENV']);
1114			//print "data evaled:\n$sect\n";
1115			settings2array(preg_split( "/[\n\r]+/", $sect), $env);
1116		}
1117		return $env;
1118	}
1119
1120	function getEvalTestSettings($section_text,$testfile)
1121	{
1122		$rq = array();
1123		// Any special environment settings
1124		// these may overwrite the test defaults...
1125		if ($section_text) {
1126			$sect = $this->evalSettings($testfile,$section_text);
1127			//print "data evaled:\n$sect\n";
1128			settings2array(preg_split( "/[\n\r]+/", $sect), $rq);
1129		}
1130		return $rq;
1131	}
1132
1133	//
1134	// Load the sections of the test file.
1135	//
1136	function getSectionText($file)
1137	{
1138		// Load the sections of the test file.
1139		$section_text = array(
1140			'TEST'   => '(unnamed test)',
1141			'SKIPIF' => '',
1142			'GET'    => '',
1143			'ARGS'   => '',
1144			'_FILE'   => $file,
1145			'_DIR'    => realpath(dirname($file)),
1146		);
1147
1148		$fp = @fopen($file, "r")
1149				or $this->error("Cannot open test file: $file");
1150
1151		$section = '';
1152		while (!feof($fp)) {
1153			$line = fgets($fp);
1154			// Match the beginning of a section.
1155			if (preg_match('/^--([A-Z]+)--/',$line,$r)) {
1156				$section = $r[1];
1157				$section_text[$section] = '';
1158				continue;
1159			}
1160
1161			// Add to the section text.
1162			$section_text[$section] .= $line;
1163		}
1164		fclose($fp);
1165		foreach ($section_text as $k=>$v) {
1166			// for POST data ,we only want to trim the last new line!
1167			if ($k == 'POST' && preg_match('/^(.*?)\r?\n$/Ds',$v,$matches)) {
1168				$section_text[$k]=$matches[1];
1169			} else {
1170				$section_text[$k]=trim($v);
1171			}
1172		}
1173		return $section_text;
1174	}
1175
1176	//
1177	// Check if test should be skipped.
1178	//
1179	function getSkipReason($file,&$section_text,$docgi=false)
1180	{
1181		// if the test uses POST or GET, and it's not the cgi
1182		// executable, skip
1183		if ($docgi && !$this->conf['TEST_WEB'] && !$this->test_executable_iscgi) {
1184			$this->showstatus($section_text['TEST'], 'SKIPPED', 'CGI Test needs CGI Binary');
1185			return "SKIPPED";
1186		}
1187		// if we're doing web testing, then we wont be able to set
1188		// ini setting on the command line.  be sure the executables
1189		// ini settings are compatible with the test, or skip
1190		if (($docgi || $this->conf['TEST_WEB']) &&
1191			isset($section_text['INI']) && $section_text['INI']) {
1192			$settings = $this->getINISettings($section_text);
1193			foreach ($settings as $k=>$v) {
1194				if (strcasecmp($v,'off')==0 || !$v) $v='';
1195				$haveval = isset($this->inisettings[$k]['local_value']);
1196				if ($k == 'include_path') {
1197					// we only want to know that src directory
1198					// is in the include path
1199					if (strpos($this->inisettings[$k]['local_value'],$this->cwd))
1200						continue;
1201				}
1202				if (($haveval && $this->inisettings[$k]['local_value'] != $v) || (!$haveval && $v)) {
1203					$this->showstatus($section_text['TEST'], 'SKIPPED', "Test requires ini setting $k=[$v], not [".($haveval?$this->inisettings[$k]['local_value']:'')."]");
1204					return "SKIPPED";
1205				}
1206			}
1207		}
1208		// now handle a SKIPIF section
1209		if ($section_text['SKIPIF']) {
1210			$output = trim($this->runscript($section_text['SKIPIF'],$this->test_executable_iscgi,realpath(dirname($file))),true);
1211			if (!$output) return NULL;
1212			if ($this->conf['TEST_PHP_DETAILED'] > 2)
1213				print "SKIPIF: [$output]\n";
1214			if (preg_match("/^skip/i", $output)){
1215
1216				$reason = (preg_match("/^skip\s*(.+)\$/", $output)) ? preg_replace("/^skip\s*(.+)\$/", "\\1", $output) : FALSE;
1217				$this->showstatus($section_text['TEST'], 'SKIPPED', $reason);
1218				return 'SKIPPED';
1219			}
1220			if (preg_match("/^info/i", $output)) {
1221				$reason = (preg_match("/^info\s*(.+)\$/", $output)) ? preg_replace("/^info\s*(.+)\$/", "\\1", $output) : FALSE;
1222				if ($reason) {
1223					$tested .= " (info: $reason)";
1224				}
1225			}
1226		}
1227		return NULL;
1228	}
1229
1230	//
1231	//  Run an individual test case.
1232	//
1233	function run_test($file)
1234	{
1235		if ($this->conf['TEST_PHP_DETAILED'])
1236			$this->writemsg("\n=================\nTEST $file\n");
1237
1238		$section_text = $this->getSectionText($file);
1239
1240		if ($this->iswin32)
1241			$shortname = str_replace($this->conf['TEST_BASE_PATH'].'\\', '', $file);
1242		else
1243			$shortname = str_replace($this->conf['TEST_BASE_PATH'].'/', '', $file);
1244		$tested = $section_text['TEST']." [$shortname]";
1245
1246		if ($this->conf['TEST_WEB']) {
1247			$tmp_file   = preg_replace('/\.phpt$/','.'.$this->conf['TEST_WEB_EXT'],$file);
1248			$uri = $this->conf['TEST_BASE_SCRIPT_NAME'].str_replace($this->conf['TEST_BASE_PATH'], '', $tmp_file);
1249			$uri = str_replace('\\', '/', $uri);
1250		} else {
1251			$tmp_file   = preg_replace('/\.phpt$/','.php',$file);
1252		}
1253		@unlink($tmp_file);
1254
1255		// unlink old test results
1256		@unlink(preg_replace('/\.phpt$/','.diff',$file));
1257		@unlink(preg_replace('/\.phpt$/','.log',$file));
1258		@unlink(preg_replace('/\.phpt$/','.exp',$file));
1259		@unlink(preg_replace('/\.phpt$/','.out',$file));
1260
1261		if (!$this->conf['TEST_WEB']) {
1262			// Reset environment from any previous test.
1263			$env = $this->getENVSettings($section_text,$tmp_file);
1264			$ini_overwrites = $this->getINIParams($section_text);
1265		}
1266
1267		// if this is a cgi test, prepare for it
1268		$query_string = '';
1269		$havepost = array_key_exists('POST', $section_text) && !empty($section_text['POST']);
1270		// allow empty query_string requests
1271		$haveget = array_key_exists('GET', $section_text) && !empty($section_text['GET']);
1272		$do_cgi = array_key_exists('CGI', $section_text) || $haveget || $havepost;
1273
1274		$skipreason = $this->getSkipReason($file,$section_text,$do_cgi);
1275		if ($skipreason == 'SKIPPED') {
1276			return $skipreason;
1277		}
1278
1279		// We've satisfied the preconditions - run the test!
1280		file_put_contents($tmp_file,$section_text['FILE']);
1281
1282		$post = NULL;
1283		$args = "";
1284
1285		$headers = array();
1286		if ($this->conf['TEST_WEB']) {
1287			$request = $this->getEvalTestSettings(@$section_text['REQUEST'],$tmp_file);
1288			$headers = $this->getEvalTestSettings(@$section_text['HEADERS'],$tmp_file);
1289
1290			$method = isset($request['method'])?$request['method']:$havepost?'POST':'GET';
1291			$query_string = $haveget?$section_text['GET']:'';
1292
1293			$options = array();
1294			$options['method']=$method;
1295			if (isset($this->conf['timeout']))    $options['timeout']    = $this->conf['timeout'];
1296			if (isset($this->conf['proxy_host'])) $options['proxy_host'] = $this->conf['proxy_host'];
1297			if (isset($this->conf['proxy_port'])) $options['proxy_port'] = $this->conf['proxy_port'];
1298			if (isset($this->conf['proxy_user'])) $options['proxy_user'] = $this->conf['proxy_user'];
1299			if (isset($this->conf['proxy_pass'])) $options['proxy_pass'] = $this->conf['proxy_pass'];
1300
1301			$post = $havepost?$section_text['POST']:NULL;
1302			$url = $this->conf['TEST_SERVER_URL'];
1303			if (isset($request['SCRIPT_NAME']))
1304				$url .= $request['SCRIPT_NAME'];
1305			else
1306				$url .= $uri;
1307			if (isset($request['PATH_INFO']))
1308				$url .= $request['PATH_INFO'];
1309			if (isset($request['FRAGMENT']))
1310				$url .= '#'.$request['FRAGMENT'];
1311			if (isset($request['QUERY_STRING']))
1312				$query_string = $request['QUERY_STRING'];
1313			if ($query_string)
1314				$url .= '?'.$query_string;
1315			if ($this->conf['TEST_PHP_DETAILED'])
1316				$this->writemsg("\nURL  = $url\n");
1317		} else if ($do_cgi) {
1318			$query_string = $haveget?$section_text['GET']:'';
1319
1320			if (!array_key_exists('GATEWAY_INTERFACE', $env))
1321				$env['GATEWAY_INTERFACE']='CGI/1.1';
1322			if (!array_key_exists('SERVER_SOFTWARE', $env))
1323				$env['SERVER_SOFTWARE']='PHP Test Harness';
1324			if (!array_key_exists('SERVER_SOFTWARE', $env))
1325				$env['SERVER_NAME']='127.0.0.1';
1326			if (!array_key_exists('REDIRECT_STATUS', $env))
1327				$env['REDIRECT_STATUS']='200';
1328			if (!array_key_exists('SERVER_NAME', $env))
1329				$env['QUERY_STRING']=$query_string;
1330			if (!array_key_exists('PATH_TRANSLATED', $env) &&
1331				!array_key_exists('SCRIPT_FILENAME', $env)) {
1332				$env['PATH_TRANSLATED']=$tmp_file;
1333				$env['SCRIPT_FILENAME']=$tmp_file;
1334			}
1335			if (!array_key_exists('PATH_TRANSLATED', $env))
1336				$env['PATH_TRANSLATED']='';
1337			if (!array_key_exists('PATH_INFO', $env))
1338				$env['PATH_INFO']='';
1339			if (!array_key_exists('SCRIPT_NAME', $env))
1340				$env['SCRIPT_NAME']='';
1341			if (!array_key_exists('SCRIPT_FILENAME', $env))
1342				$env['SCRIPT_FILENAME']='';
1343
1344			if (array_key_exists('POST', $section_text) && (!$haveget || !empty($section_text['POST']))) {
1345				$post = $section_text['POST'];
1346				$content_length = strlen($post);
1347				if (!array_key_exists('REQUEST_METHOD', $env))
1348					$env['REQUEST_METHOD']='POST';
1349				if (!array_key_exists('CONTENT_TYPE', $env))
1350					$env['CONTENT_TYPE']='application/x-www-form-urlencoded';
1351				if (!array_key_exists('CONTENT_LENGTH', $env))
1352					$env['CONTENT_LENGTH']=$content_length;
1353			} else {
1354				if (!array_key_exists('REQUEST_METHOD', $env))
1355					$env['REQUEST_METHOD']='GET';
1356				if (!array_key_exists('CONTENT_TYPE', $env))
1357					$env['CONTENT_TYPE']='';
1358				if (!array_key_exists('CONTENT_LENGTH', $env))
1359					$env['CONTENT_LENGTH']='';
1360			}
1361			if ($this->conf['TEST_PHP_DETAILED'] > 1)
1362				$this->writemsg("\nCONTENT_LENGTH  = " . $env['CONTENT_LENGTH'] .
1363						"\nCONTENT_TYPE    = " . $env['CONTENT_TYPE'] .
1364						"\nPATH_TRANSLATED = " . $env['PATH_TRANSLATED'] .
1365						"\nPATH_INFO       = " . $env['PATH_INFO'] .
1366						"\nQUERY_STRING    = " . $env['QUERY_STRING'] .
1367						"\nREDIRECT_STATUS = " . $env['REDIRECT_STATUS'] .
1368						"\nREQUEST_METHOD  = " . $env['REQUEST_METHOD'] .
1369						"\nSCRIPT_NAME     = " . $env['SCRIPT_NAME'] .
1370						"\nSCRIPT_FILENAME = " . $env['SCRIPT_FILENAME'] . "\n");
1371			/* not cgi spec to put query string on command line,
1372			   but used by a couple tests to catch a security hole
1373			   in older php versions.  At least IIS can be configured
1374			   to do this. */
1375			$args = $env['QUERY_STRING'];
1376			$args = "$ini_overwrites $tmp_file \"$args\" 2>&1";
1377		} else {
1378			$args = $section_text['ARGS'] ? $section_text['ARGS'] : '';
1379			$args = "$ini_overwrites $tmp_file $args 2>&1";
1380		}
1381
1382		if ($this->conf['TEST_WEB']) {
1383			// we want headers also, so fopen
1384			$r = new HTTPRequest($url,$headers,$options,$post);
1385			//$out = preg_replace("/\r\n/","\n",$r->response);
1386			$out = $r->response;
1387			$headers = $r->response_headers;
1388			//print $r->outgoing_payload."\n";
1389			//print $r->incoming_payload."\n";
1390		} else {
1391			$out = execute($this->conf['TEST_PHP_EXECUTABLE'],$args,$post,$this->cwd,$env);
1392			// if this is a cgi, remove the headers first
1393			if ($this->test_executable_iscgi
1394				 && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) {
1395				$out = $match[2];
1396				$rh = preg_split("/[\n\r]+/",$match[1]);
1397				$headers = array();
1398				foreach ($rh as $line) {
1399					if (strpos($line, ':')!==false) {
1400						$line = explode(":", $line, 2);
1401						$headers[trim($line[0])] = trim($line[1]);
1402					}
1403				}
1404			}
1405		}
1406
1407		if ($this->conf['TEST_PHP_DETAILED'] > 2) {
1408			echo "HEADERS: ";
1409			print_r($headers);
1410			echo "OUTPUT: \n$out\n";
1411
1412		}
1413
1414		// Does the output match what is expected?
1415		$output = trim($out);
1416		$output = preg_replace('/\r\n/',"\n",$output);
1417
1418		$failed = FALSE;
1419
1420		if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) {
1421			if (isset($section_text['EXPECTF'])) {
1422				$wanted = $section_text['EXPECTF'];
1423			} else {
1424				$wanted = $section_text['EXPECTREGEX'];
1425			}
1426			$wanted_re = preg_replace('/\r\n/',"\n",$wanted);
1427			if (isset($section_text['EXPECTF'])) {
1428				// do preg_quote, but miss out any %r delimited sections
1429				$temp = "";
1430				$r = "%r";
1431				$startOffset = 0;
1432				$length = strlen($wanted_re);
1433				while($startOffset < $length) {
1434					$start = strpos($wanted_re, $r, $startOffset);
1435					if ($start !== false) {
1436						// we have found a start tag
1437						$end = strpos($wanted_re, $r, $start+2);
1438						if ($end === false) {
1439							// unbalanced tag, ignore it.
1440							$end = $start = $length;
1441						}
1442					} else {
1443						// no more %r sections
1444						$start = $end = $length;
1445					}
1446					// quote a non re portion of the string
1447					$temp = $temp . preg_quote(substr($wanted_re, $startOffset, ($start - $startOffset)),  '/');
1448					// add the re unquoted.
1449					if ($end > $start) {
1450						$temp = $temp . '(' . substr($wanted_re, $start+2, ($end - $start-2)). ')';
1451					}
1452					$startOffset = $end + 2;
1453				}
1454				$wanted_re = $temp;
1455
1456				$wanted_re = str_replace(
1457					array('%binary_string_optional%'),
1458					'string',
1459					$wanted_re
1460				);
1461				$wanted_re = str_replace(
1462					array('%unicode_string_optional%'),
1463					'string',
1464					$wanted_re
1465				);
1466				$wanted_re = str_replace(
1467					array('%unicode\|string%', '%string\|unicode%'),
1468					'string',
1469					$wanted_re
1470				);
1471				$wanted_re = str_replace(
1472					array('%u\|b%', '%b\|u%'),
1473					'',
1474					$wanted_re
1475				);
1476				// Stick to basics
1477				$wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re);
1478				$wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re);
1479				$wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re);
1480				$wanted_re = str_replace('%a', '.+', $wanted_re);
1481				$wanted_re = str_replace('%A', '.*', $wanted_re);
1482				$wanted_re = str_replace('%w', '\s*', $wanted_re);
1483				$wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re);
1484				$wanted_re = str_replace('%d', '\d+', $wanted_re);
1485				$wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re);
1486				$wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re);
1487				$wanted_re = str_replace('%c', '.', $wanted_re);
1488				// %f allows two points "-.0.0" but that is the best *simple* expression
1489
1490			}
1491	/* DEBUG YOUR REGEX HERE
1492			var_dump($wanted_re);
1493			print(str_repeat('=', 80) . "\n");
1494			var_dump($output);
1495	*/
1496			$failed = !preg_match("/^$wanted_re\$/s", $output);
1497		}
1498
1499		$skipexpect = false;
1500		if (!$failed && $this->conf['TEST_WEB'] && isset($section_text['EXPECTHEADERS'])) {
1501			$want = array();
1502			$lines = preg_split("/[\n\r]+/",$section_text['EXPECTHEADERS']);
1503			$wanted='';
1504            foreach ($lines as $line) {
1505                if (strpos($line, ':')!==false) {
1506                    $line = explode(":", $line, 2);
1507                    $want[trim($line[0])] = trim($line[1]);
1508					$wanted .= trim($line[0]).': '.trim($line[1])."\n";
1509                }
1510            }
1511			$output='';
1512			foreach ($want as $k=>$v) {
1513				$output .= "$k: {$headers[$k]}\n";
1514				if (!isset($headers[$k]) || $headers[$k] != $v) {
1515					$failed = TRUE;
1516				}
1517			}
1518
1519			// different servers may do different things on non-200 results
1520			// for instance, IIS will deliver it's own error pages, so we
1521			// cannot expect to match up the EXPECT section.  We may however,
1522			// want to match EXPECT on more than 200 results, so this may
1523			// need to change later.
1524			$skipexpect = isset($headers['Status']) && $headers['Status'] != 200;
1525		}
1526
1527		if (!$failed && !$skipexpect && isset($section_text['EXPECT'])) {
1528			$wanted = $section_text['EXPECT'];
1529			$wanted = preg_replace('/\r\n/',"\n",$wanted);
1530			$failed = (0 != strcmp($output,$wanted));
1531		}
1532
1533		if (!$failed) {
1534			@unlink($tmp_file);
1535			$this->showstatus($tested, 'PASSED');
1536			return 'PASSED';
1537		}
1538
1539		// Test failed so we need to report details.
1540		$this->showstatus($tested, 'FAILED');
1541
1542		$this->failed_tests[] = array(
1543							'name' => $file,
1544							'test_name' => $tested,
1545							'output' => preg_replace('/\.phpt$/','.log', $file),
1546							'diff'   => preg_replace('/\.phpt$/','.diff', $file)
1547							);
1548
1549		if ($this->conf['TEST_PHP_DETAILED'])
1550			$this->writemsg(generate_diff($wanted,$output)."\n");
1551
1552		// write .exp
1553		if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'E') !== FALSE) {
1554			$logname = preg_replace('/\.phpt$/','.exp',$file);
1555			file_put_contents($logname,$wanted);
1556		}
1557
1558		// write .out
1559		if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'O') !== FALSE) {
1560			$logname = preg_replace('/\.phpt$/','.out',$file);
1561			file_put_contents($logname,$output);
1562		}
1563
1564		// write .diff
1565		if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'D') !== FALSE) {
1566			$logname = preg_replace('/\.phpt$/','.diff',$file);
1567			file_put_contents($logname,generate_diff($wanted,$output));
1568		}
1569
1570		// write .log
1571		if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'L') !== FALSE) {
1572			$logname = preg_replace('/\.phpt$/','.log',$file);
1573			file_put_contents($logname,
1574						"\n---- EXPECTED OUTPUT\n$wanted\n".
1575						"---- ACTUAL OUTPUT\n$output\n".
1576						"---- FAILED\n");
1577			// display emacs/msvc error output
1578			if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'C') !== FALSE) {
1579				$this->error_report($file,$logname,$tested);
1580			}
1581		}
1582		return 'FAILED';
1583	}
1584
1585	//
1586	//  Write an error in a format recognizable to Emacs or MSVC.
1587	//
1588	function error_report($testname,$logname,$tested)
1589	{
1590		$testname = realpath($testname);
1591		$logname  = realpath($logname);
1592		switch ($this->conf['TEST_PHP_ERROR_STYLE']) {
1593		default:
1594		case 'MSVC':
1595			$this->writemsg($testname . "(1) : $tested\n");
1596			$this->writemsg($logname . "(1) :  $tested\n");
1597			break;
1598		case 'EMACS':
1599			$this->writemsg($testname . ":1: $tested\n");
1600			$this->writemsg($logname . ":1:  $tested\n");
1601			break;
1602		}
1603	}
1604
1605	function error($message)
1606	{
1607		$this->writemsg("ERROR: {$message}\n");
1608		exit(1);
1609	}
1610}
1611
1612$test = new testHarness();
1613/*
1614 * Local variables:
1615 * tab-width: 4
1616 * c-basic-offset: 4
1617 * End:
1618 * vim600: fdm=marker
1619 * vim: noet sw=4 ts=4
1620 */
1621?>
1622