xref: /PHP-7.0/server-tests.php (revision 478f119a)
1<?php
2/*
3   +----------------------------------------------------------------------+
4   | PHP Version 7                                                        |
5   +----------------------------------------------------------------------+
6   | Copyright (c) 1997-2017 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		);
476	public $env = array();
477	public $info_params = array();
478
479	function testHarness() {
480		$this->iswin32 = substr(PHP_OS, 0, 3) == "WIN";
481		$this->checkRequirements();
482		$this->env = $_ENV;
483		$this->removeSensitiveEnvVars();
484
485		$this->initializeConfiguration();
486		$this->parseArgs();
487		$this->setTestPaths();
488		# change to working directory
489		if ($this->conf['TEST_PHP_SRCDIR']) {
490			@chdir($this->conf['TEST_PHP_SRCDIR']);
491		}
492		$this->cwd = getcwd();
493
494		if (!$this->conf['TEST_PHP_SRCDIR'])
495			$this->conf['TEST_PHP_SRCDIR'] = $this->cwd;
496		if (!$this->conf['TEST_BASE_PATH'] && $this->conf['TEST_PHP_SRCDIR'])
497			$this->conf['TEST_BASE_PATH'] = $this->conf['TEST_PHP_SRCDIR'];
498		if ($this->iswin32) {
499			$this->conf['TEST_PHP_SRCDIR'] = str_replace('/','\\',$this->conf['TEST_PHP_SRCDIR']);
500			$this->conf['TEST_BASE_PATH'] = str_replace('/','\\',$this->conf['TEST_BASE_PATH']);
501		}
502
503		if (!$this->conf['TEST_WEB'] && !is_executable($this->conf['TEST_PHP_EXECUTABLE'])) {
504			$this->error("invalid PHP executable specified by TEST_PHP_EXECUTABLE  = " .
505					$this->conf['TEST_PHP_EXECUTABLE']);
506			return false;
507		}
508
509		$this->getInstalledExtensions();
510		$this->getExecutableInfo();
511		$this->getExecutableIniSettings();
512		$this->test_executable_iscgi = strncmp($this->exec_info['PHP_SAPI'],'cgi',3)==0;
513		$this->calculateDocumentRoot();
514
515		// add TEST_PHP_SRCDIR to the include path, this facilitates
516		// tests including files from src/tests
517		//$this->ini_overwrites['include_path'] = $this->cwd.($this->iswin32?';.;':':.:').$this->exec_info['INCLUDE_PATH'];
518
519		$params = array();
520		settings2array($this->ini_overwrites,$params);
521		$this->info_params = settings2params($params);
522
523		$this->contextHeader();
524		if ($this->conf['TEST_CONTEXT_INFO']) return;
525		$this->loadFileList();
526		$this->moveTestFiles();
527		$this->run();
528		$this->summarizeResults();
529	}
530
531	function getExecutableIniSettings()
532	{
533		$out = $this->runscript(PHP_INI_SETTINGS_SCRIPT,true);
534		$this->inisettings = unserialize($out);
535	}
536
537	function getExecutableInfo()
538	{
539		$out = $this->runscript(PHP_INFO_SCRIPT,true);
540		$out = preg_split("/[\n\r]+/",$out);
541		$info = array();
542		foreach ($out as $line) {
543			if (strpos($line, '=')!==false) {
544				$line = explode("=", $line, 2);
545				$name = trim($line[0]);
546				$value = trim($line[1]);
547				$info[$name] = $value;
548			}
549		}
550		$this->exec_info = $info;
551	}
552
553	function getInstalledExtensions()
554	{
555		// get the list of installed extensions
556		$out = $this->runscript(PHP_EXTENSIONS_SCRIPT,true);
557		$this->exts_to_test = explode(":",$out);
558		sort($this->exts_to_test);
559		$this->exts_tested = count($this->exts_to_test);
560	}
561
562	// if running local, calls executeCode,
563	// otherwise does an http request
564	function runscript($script,$removeheaders=false,$cwd=NULL)
565	{
566		if ($this->conf['TEST_WEB']) {
567			$pi = '/testscript.' . $this->conf['TEST_WEB_EXT'];
568			if (!$cwd) $cwd = $this->conf['TEST_BASE_PATH'];
569			$tmp_file = "$cwd$pi";
570			$pi = substr($cwd,strlen($this->conf['TEST_BASE_PATH'])) . $pi;
571			$url = $this->conf['TEST_WEB_BASE_URL'] . $pi;
572			file_put_contents($tmp_file,$script);
573			$fd = fopen($url, "rb");
574			$out = '';
575			if ($fd) {
576				while (!feof($fd))
577					$out .= fread($fd, 8192);
578				fclose($fd);
579			}
580			unlink($tmp_file);
581			if (0 && $removeheaders &&
582				preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) {
583					return $match[2];
584			}
585			return $out;
586		} else {
587			return executeCode($this->conf['TEST_PHP_EXECUTABLE'],$this->ini_overwrites, $script,$removeheaders,$cwd,$this->env);
588		}
589	}
590
591
592	// Use this function to do any displaying of text, so that
593	// things can be over-written as necessary.
594
595	function writemsg($msg) {
596
597	    echo $msg;
598
599	}
600
601	// Another wrapper function, this one should be used any time
602	// a particular test passes or fails
603
604	function showstatus($item, $status, $reason = '') {
605
606	    switch($status) {
607		case 'PASSED':
608		    $this->writemsg("PASSED: $item ($reason)\n");
609		    break;
610		case 'FAILED':
611		    $this->writemsg("FAILED: $item ($reason)\n");
612		    break;
613		case 'SKIPPED':
614		    $this->writemsg("SKIPPED: $item ($reason)\n");
615		    break;
616	    }
617	}
618
619
620	function help()
621	{
622		$usage = "usage: php run-tests.php [options]\n";
623		foreach ($this->xargs as $arg=>$arg_info) {
624			$usage .= sprintf(" -%s  %-12s %s\n",$arg,$arg_info[1],$arg_info[3]);
625		}
626		return $usage;
627	}
628
629	function parseArgs() {
630		global $argc;
631		global $argv;
632		global $_SERVER;
633
634		if (!isset($argv)) {
635			$argv = $_SERVER['argv'];
636			$argc = $_SERVER['argc'];
637		}
638
639		$conf = NULL;
640		for ($i=1; $i<$argc;) {
641			if ($argv[$i][0] != '-') continue;
642			$opt = $argv[$i++][1];
643			if (isset($value)) unset($value);
644			if (@$argv[$i][0] != '-') {
645				@$value = $argv[$i++];
646			}
647			switch($opt) {
648			case 'c':
649				/* TODO: Implement configuraiton file */
650				include($value);
651				if (!isset($conf)) {
652					$this->writemsg("Invalid configuration file\n");
653					exit(1);
654				}
655				$this->conf = array_merge($this->conf,$conf);
656				break;
657			case 'e':
658				$this->conf['TEST_PHP_ERROR_STYLE'] = strtoupper($value);
659				break;
660			case 'h':
661				print $this->help();
662				exit(0);
663			default:
664				if ($this->xargs[$opt][1] && isset($value))
665					$this->conf[$this->xargs[$opt][0]] = $value;
666				else if (!$this->xargs[$opt][1])
667					$this->conf[$this->xargs[$opt][0]] = isset($value)?$value:1;
668				else
669					$this->error("Invalid argument setting for argument $opt, should be [{$this->xargs[$opt][1]}]\n");
670				break;
671			}
672		}
673
674		// set config into environment, this allows
675		// executed tests to find out about the test
676		// configurations.  config file or args overwrite
677		// env var config settings
678		$this->env = array_merge($this->env,$this->conf);
679		if (!$this->conf['TEST_WEB'] && !$this->conf['TEST_PHP_EXECUTABLE']) {
680			$this->writemsg($this->help());
681			exit(0);
682		}
683	}
684
685	function removeSensitiveEnvVars()
686	{
687		# delete sensitive env vars
688		$this->env['SSH_CLIENT']='deleted';
689		$this->env['SSH_AUTH_SOCK']='deleted';
690		$this->env['SSH_TTY']='deleted';
691	}
692
693	function setEnvConfigVar($name)
694	{
695		if (isset($this->env[$name])) {
696			$this->conf[$name] = $this->env[$name];
697		}
698	}
699
700	function initializeConfiguration()
701	{
702		foreach ($this->xargs as $arg=>$arg_info) {
703			if ($arg_info[0]) {
704				# initialize the default setting
705				$this->conf[$arg_info[0]]=$arg_info[2];
706				# get config from environment
707				$this->setEnvConfigVar($arg_info[0]);
708			}
709		}
710	}
711
712	function setTestPaths()
713	{
714		// configure test paths from config file or command line
715		if (@$this->conf['TEST_PATHS']) {
716			$this->test_dirs = array();
717			if ($this->iswin32) {
718				$paths = explode(';',$this->conf['TEST_PATHS']);
719			} else {
720				$paths = explode(':|;',$this->conf['TEST_PATHS']);
721			}
722			foreach($paths as $path) {
723				$this->test_dirs[] = realpath($path);
724			}
725		}
726	}
727
728	function test_sort($a, $b) {
729		$ta = strpos($a, "{$this->cwd}/tests")===0 ? 1 + (strpos($a, "{$this->cwd}/tests/run-test")===0 ? 1 : 0) : 0;
730		$tb = strpos($b, "{$this->cwd}/tests")===0 ? 1 + (strpos($b, "{$this->cwd}/tests/run-test")===0 ? 1 : 0) : 0;
731		if ($ta == $tb) {
732			return strcmp($a, $b);
733		} else {
734			return $tb - $ta;
735		}
736	}
737
738	function checkRequirements() {
739		if (version_compare(phpversion(), "5.0") < 0) {
740			$this->writemsg(REQ_PHP_VERSION);
741			exit;
742		}
743// We might want to check another server so we won't see that server's /tmp
744//		if (!file_exists("/tmp")) {
745//			$this->writemsg(TMP_MISSING);
746//			exit;
747//		}
748		if (!function_exists("proc_open")) {
749			$this->writemsg(PROC_OPEN_MISSING);
750			exit;
751		}
752		if (!extension_loaded("pcre")) {
753			$this->writemsg(PCRE_MISSING_ERROR);
754			exit;
755		}
756		if (ini_get('safe_mode')) {
757			$this->writemsg(SAFE_MODE_WARNING);
758		}
759	}
760
761	//
762	// Write test context information.
763	//
764	function contextHeader()
765	{
766		$info = '';
767		foreach ($this->exec_info as $k=>$v) {
768			$info .= sprintf("%-20.s: %s\n",$k,$v);
769		}
770		$exts = '';
771		foreach ($this->exts_to_test as $ext) {
772			$exts .="$ext\n              ";
773		}
774		$dirs = '';
775		foreach ($this->test_dirs as $test_dir) {
776			$dirs .= "$test_dir\n              ";
777		}
778		$conf = '';
779		foreach ($this->conf as $k=>$v) {
780			$conf .= sprintf("%-20.s: %s\n",$k,$v);
781		}
782
783		$exeinfo = '';
784		if (!$this->conf['TEST_WEB'])
785			$exeinfo = "CWD                 : {$this->cwd}\n".
786					"PHP                 : {$this->conf['TEST_PHP_EXECUTABLE']}\n";
787
788		$this->writemsg("\n$this->ddash\n".
789			"$exeinfo$info\n".
790			"Test Harness Configuration:\n$conf\n".
791			"Extensions  : $exts\n".
792			"Test Dirs   : $dirs\n".
793			"$this->ddash\n");
794	}
795
796	function loadFileList()
797	{
798		foreach ($this->test_dirs as $dir) {
799			if (is_dir($dir)) {
800				$this->findFilesInDir($dir, ($dir == 'ext'));
801			} else {
802				$this->test_files[] = $dir;
803			}
804		}
805		usort($this->test_files,array($this,"test_sort"));
806		$this->writemsg("found ".count($this->test_files)." files\n");
807	}
808
809	function moveTestFiles()
810	{
811		if (!$this->conf['TEST_BASE_PATH'] ||
812			$this->conf['TEST_BASE_PATH'] == $this->conf['TEST_PHP_SRCDIR']) return;
813		$this->writemsg("moving files from {$this->conf['TEST_PHP_SRCDIR']} to {$this->conf['TEST_BASE_PATH']}\n");
814		$l = strlen($this->conf['TEST_PHP_SRCDIR']);
815		$files = array();
816		$dirs = array();
817		foreach ($this->test_files as $file) {
818			if (strpos($file,$this->conf['TEST_PHP_SRCDIR'])==0) {
819				$newlocation = $this->conf['TEST_BASE_PATH'].substr($file,$l);
820				$files[] = $newlocation;
821				$dirs[dirname($file)] = dirname($newlocation);
822			} else {
823				// XXX what to do with test files outside the
824				// php source directory?  Need to map them into
825				// the new directory somehow.
826			}
827		}
828		foreach ($dirs as $src=>$new) {
829			mkpath($new);
830			copyfiles($src,$new);
831		}
832		$this->test_files = $files;
833	}
834
835	function findFilesInDir($dir,$is_ext_dir=FALSE,$ignore=FALSE)
836	{
837		$skip = array('.', '..', 'CVS');
838		$o = opendir($dir) or $this->error("cannot open directory: $dir");
839		while (($name = readdir($o)) !== FALSE) {
840			if (in_array($name, $skip)) continue;
841			if (is_dir("$dir/$name")) {
842				$skip_ext = ($is_ext_dir && !in_array($name, $this->exts_to_test));
843				if ($skip_ext) {
844					$this->exts_skipped++;
845				}
846				$this->findFilesInDir("$dir/$name", FALSE, $ignore || $skip_ext);
847			}
848
849			// Cleanup any left-over tmp files from last run.
850			if (substr($name, -4) == '.tmp') {
851				@unlink("$dir/$name");
852				continue;
853			}
854
855			// Otherwise we're only interested in *.phpt files.
856			if (substr($name, -5) == '.phpt') {
857				if ($ignore) {
858					$this->ignored_by_ext++;
859				} else {
860					$testfile = realpath("$dir/$name");
861					$this->test_files[] = $testfile;
862				}
863			}
864		}
865		closedir($o);
866	}
867
868	function runHeader()
869	{
870		$this->writemsg("TIME START " . date('Y-m-d H:i:s', $this->start_time) . "\n".$this->ddash."\n");
871		if (count($this->test_to_run)) {
872			$this->writemsg("Running selected tests.\n");
873		} else {
874			$this->writemsg("Running all test files.\n");
875		}
876	}
877
878	function run()
879	{
880		$this->start_time = time();
881		$this->runHeader();
882		// Run selected tests.
883		if (count($this->test_to_run)) {
884
885			foreach($this->test_to_run as $name=>$runnable) {
886				if(!preg_match("/\.phpt$/", $name))
887					continue;
888				if ($runnable) {
889					$this->test_results[$name] = $this->run_test($name);
890				}
891			}
892		} else {
893			foreach ($this->test_files as $name) {
894				$this->test_results[$name] = $this->run_test($name);
895			}
896		}
897		$this->end_time = time();
898	}
899
900	function summarizeResults()
901	{
902		if (count($this->test_results) == 0) {
903			$this->writemsg("No tests were run.\n");
904			return;
905		}
906
907		$n_total = count($this->test_results);
908		$n_total += $this->ignored_by_ext;
909
910		$sum_results = array('PASSED'=>0, 'SKIPPED'=>0, 'FAILED'=>0);
911		foreach ($this->test_results as $v) {
912			$sum_results[$v]++;
913		}
914		$sum_results['SKIPPED'] += $this->ignored_by_ext;
915		$percent_results = array();
916		while (list($v,$n) = each($sum_results)) {
917			$percent_results[$v] = (100.0 * $n) / $n_total;
918		}
919
920		$this->writemsg("\n".$this->ddash."\n".
921			"TIME END " . date('Y-m-d H:i:s', $this->end_time) . "\n".
922			$this->ddash."\n".
923			"TEST RESULT SUMMARY\n".
924			$this->sdash."\n".
925			"Exts skipped    : " . sprintf("%4d",$this->exts_skipped) . "\n".
926			"Exts tested     : " . sprintf("%4d",$this->exts_tested) . "\n".
927			$this->sdash."\n".
928			"Number of tests : " . sprintf("%4d",$n_total) . "\n".
929			"Tests skipped   : " . sprintf("%4d (%2.1f%%)",$sum_results['SKIPPED'],$percent_results['SKIPPED']) . "\n".
930			"Tests failed    : " . sprintf("%4d (%2.1f%%)",$sum_results['FAILED'],$percent_results['FAILED']) . "\n".
931			"Tests passed    : " . sprintf("%4d (%2.1f%%)",$sum_results['PASSED'],$percent_results['PASSED']) . "\n".
932			$this->sdash."\n".
933			"Time taken      : " . sprintf("%4d seconds", $this->end_time - $this->start_time) . "\n".
934			$this->ddash."\n");
935
936		$failed_test_summary = '';
937		if ($this->failed_tests) {
938			$failed_test_summary .= "\n".$this->ddash."\n".
939				"FAILED TEST SUMMARY\n".$this->sdash."\n";
940			foreach ($this->failed_tests as $failed_test_data) {
941				$failed_test_summary .=  $failed_test_data['test_name'] . "\n";
942			}
943			$failed_test_summary .=  $this->ddash."\n";
944		}
945
946		if ($failed_test_summary && !$this->conf['NO_PHPTEST_SUMMARY']) {
947			$this->writemsg($failed_test_summary);
948		}
949
950		/* We got failed Tests, offer the user to send and e-mail to QA team, unless NO_INTERACTION is set */
951		if ($sum_results['FAILED'] && !$this->conf['NO_INTERACTION']) {
952			$fp = fopen("php://stdin", "r+");
953			$this->writemsg("\nPlease allow this report to be send to the PHP QA\nteam. This will give us a better understanding in how\n");
954			$this->writemsg("PHP's test cases are doing.\n");
955			$this->writemsg("(choose \"s\" to just save the results to a file)? [Yns]: ");
956			flush();
957			$user_input = fgets($fp, 10);
958			$just_save_results = (strtolower($user_input[0]) == 's');
959
960			if ($just_save_results || strlen(trim($user_input)) == 0 || strtolower($user_input[0]) == 'y') {
961				/*
962				 * Collect information about the host system for our report
963				 * Fetch phpinfo() output so that we can see the PHP environment
964				 * Make an archive of all the failed tests
965				 * Send an email
966				 */
967
968				/* Ask the user to provide an email address, so that QA team can contact the user */
969				if (!strncasecmp($user_input, 'y', 1) || strlen(trim($user_input)) == 0) {
970					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): ";
971					flush();
972					$fp = fopen("php://stdin", "r+");
973					$user_email = trim(fgets($fp, 1024));
974					$user_email = str_replace("@", " at ", str_replace(".", " dot ", $user_email));
975				}
976
977				$failed_tests_data = '';
978				$sep = "\n" . str_repeat('=', 80) . "\n";
979
980				$failed_tests_data .= $failed_test_summary . "\n";
981
982				if (array_sum($this->failed_tests)) {
983					foreach ($this->failed_tests as $test_info) {
984						$failed_tests_data .= $sep . $test_info['name'];
985						$failed_tests_data .= $sep . file_get_contents(realpath($test_info['output']));
986						$failed_tests_data .= $sep . file_get_contents(realpath($test_info['diff']));
987						$failed_tests_data .= $sep . "\n\n";
988					}
989					$status = "failed";
990				} else {
991					$status = "success";
992				}
993
994				$failed_tests_data .= "\n" . $sep . 'BUILD ENVIRONMENT' . $sep;
995				$failed_tests_data .= "OS:\n". PHP_OS. "\n\n";
996				$automake = $autoconf = $libtool = $compiler = 'N/A';
997
998				if (!$this->iswin32) {
999					$automake = shell_exec('automake --version');
1000					$autoconf = shell_exec('autoconf --version');
1001					/* Always use the generated libtool - Mac OSX uses 'glibtool' */
1002					$libtool = shell_exec('./libtool --version');
1003					/* Try the most common flags for 'version' */
1004					$flags = array('-v', '-V', '--version');
1005					$cc_status=0;
1006					foreach($flags AS $flag) {
1007						system($this->env['CC']." $flag >/dev/null 2>&1", $cc_status);
1008						if($cc_status == 0) {
1009							$compiler = shell_exec($this->env['CC']." $flag 2>&1");
1010							break;
1011						}
1012					}
1013				}
1014
1015				$failed_tests_data .= "Automake:\n$automake\n";
1016				$failed_tests_data .= "Autoconf:\n$autoconf\n";
1017				$failed_tests_data .= "Libtool:\n$libtool\n";
1018				$failed_tests_data .= "Compiler:\n$compiler\n";
1019				$failed_tests_data .= "Bison:\n". @shell_exec('bison --version'). "\n";
1020				$failed_tests_data .= "\n\n";
1021
1022				if (isset($user_email)) {
1023					$failed_tests_data .= "User's E-mail: ".$user_email."\n\n";
1024				}
1025
1026				$failed_tests_data .= $sep . "PHPINFO" . $sep;
1027				$failed_tests_data .= shell_exec($this->conf['TEST_PHP_EXECUTABLE'].' -dhtml_errors=0 -i');
1028
1029				$compression = 0;
1030
1031				if ($just_save_results ||
1032					!post_result_data("status=$status&version=".urlencode(TESTED_PHP_VERSION),$failed_tests_data)) {
1033					$output_file = 'php_test_results_' . date('Ymd_Hi') . ( $compression ? '.txt.gz' : '.txt' );
1034					$fp = fopen($output_file, "w");
1035					fwrite($fp, $failed_tests_data);
1036					fclose($fp);
1037
1038					if (!$just_save_results)
1039						echo "\nThe test script was unable to automatically send the report to PHP's QA Team\n";
1040					echo "Please send ".$output_file." to ".PHP_QA_EMAIL." manually, thank you.\n";
1041				} else {
1042					fwrite($fp, "\nThank you for helping to make PHP better.\n");
1043					fclose($fp);
1044				}
1045			}
1046		}
1047
1048		if($this->conf['REPORT_EXIT_STATUS'] and $sum_results['FAILED']) {
1049			exit(1);
1050		}
1051	}
1052
1053	function getINISettings(&$section_text)
1054	{
1055		$ini_settings = $this->ini_overwrites;
1056		// Any special ini settings
1057		// these may overwrite the test defaults...
1058		if (array_key_exists('INI', $section_text)) {
1059			settings2array(preg_split( "/[\n\r]+/", $section_text['INI']), $ini_settings);
1060		}
1061		return $ini_settings;
1062	}
1063
1064	function getINIParams(&$section_text)
1065	{
1066		if (!$section_text) return '';
1067		// XXX php5 current has a problem doing this in one line
1068		// it fails with Only variables can be passed by reference
1069		// on test ext\calendar\tests\jdtojewish.phpt
1070		// return settings2params($this->getINISettings($section_text));
1071		$ini = $this->getINISettings($section_text);
1072		return settings2params($ini);
1073	}
1074
1075	function calculateDocumentRoot()
1076	{
1077		if ($this->conf['TEST_WEB'] || $this->test_executable_iscgi) {
1078			// configure DOCUMENT_ROOT for web tests
1079			// this assumes that directories from the base url
1080			// matches directory depth from the base path
1081			$parts = parse_url($this->conf['TEST_WEB_BASE_URL']);
1082			$depth = substr_count($parts['path'],'/');
1083			$docroot = $this->conf['TEST_BASE_PATH'];
1084			for ($i=0 ; $i < $depth; $i++) $docroot = dirname($docroot);
1085			$this->conf['TEST_DOCUMENT_ROOT']=$docroot;
1086			$this->conf['TEST_BASE_SCRIPT_NAME']=$parts['path'];
1087			$this->conf['TEST_SERVER_URL']=substr($this->conf['TEST_WEB_BASE_URL'],0,strlen($this->conf['TEST_WEB_BASE_URL'])-strlen($parts['path']));
1088		} else {
1089			$this->conf['TEST_DOCUMENT_ROOT']='';
1090			$this->conf['TEST_BASE_SCRIPT_NAME']='';
1091			$this->conf['TEST_SERVER_URL']='';
1092		}
1093	}
1094
1095	function evalSettings($filename,$data) {
1096		// we eval the section so we can allow dynamic env vars
1097		// for cgi testing
1098		$filename = str_replace('\\','/',$filename);
1099		$cwd = str_replace('\\','/',$this->cwd);
1100		$filepath = dirname($filename);
1101		$scriptname = substr($filename,strlen($this->conf['TEST_DOCUMENT_ROOT']));
1102		// eval fails if no newline
1103		return eval("$data\n");
1104	}
1105
1106	function getENVSettings(&$section_text,$testfile)
1107	{
1108		$env = $this->env;
1109		// Any special environment settings
1110		// these may overwrite the test defaults...
1111		if (array_key_exists('ENV', $section_text)) {
1112			$sect = $this->evalSettings($testfile,$section_text['ENV']);
1113			//print "data evaled:\n$sect\n";
1114			settings2array(preg_split( "/[\n\r]+/", $sect), $env);
1115		}
1116		return $env;
1117	}
1118
1119	function getEvalTestSettings($section_text,$testfile)
1120	{
1121		$rq = array();
1122		// Any special environment settings
1123		// these may overwrite the test defaults...
1124		if ($section_text) {
1125			$sect = $this->evalSettings($testfile,$section_text);
1126			//print "data evaled:\n$sect\n";
1127			settings2array(preg_split( "/[\n\r]+/", $sect), $rq);
1128		}
1129		return $rq;
1130	}
1131
1132	//
1133	// Load the sections of the test file.
1134	//
1135	function getSectionText($file)
1136	{
1137		// Load the sections of the test file.
1138		$section_text = array(
1139			'TEST'   => '(unnamed test)',
1140			'SKIPIF' => '',
1141			'GET'    => '',
1142			'ARGS'   => '',
1143			'_FILE'   => $file,
1144			'_DIR'    => realpath(dirname($file)),
1145		);
1146
1147		$fp = @fopen($file, "r")
1148				or $this->error("Cannot open test file: $file");
1149
1150		$section = '';
1151		while (!feof($fp)) {
1152			$line = fgets($fp);
1153			// Match the beginning of a section.
1154			if (preg_match('/^--([A-Z]+)--/',$line,$r)) {
1155				$section = $r[1];
1156				$section_text[$section] = '';
1157				continue;
1158			}
1159
1160			// Add to the section text.
1161			$section_text[$section] .= $line;
1162		}
1163		fclose($fp);
1164		foreach ($section_text as $k=>$v) {
1165			// for POST data ,we only want to trim the last new line!
1166			if ($k == 'POST' && preg_match('/^(.*?)\r?\n$/Ds',$v,$matches)) {
1167				$section_text[$k]=$matches[1];
1168			} else {
1169				$section_text[$k]=trim($v);
1170			}
1171		}
1172		return $section_text;
1173	}
1174
1175	//
1176	// Check if test should be skipped.
1177	//
1178	function getSkipReason($file,&$section_text,$docgi=false)
1179	{
1180		// if the test uses POST or GET, and it's not the cgi
1181		// executable, skip
1182		if ($docgi && !$this->conf['TEST_WEB'] && !$this->test_executable_iscgi) {
1183			$this->showstatus($section_text['TEST'], 'SKIPPED', 'CGI Test needs CGI Binary');
1184			return "SKIPPED";
1185		}
1186		// if we're doing web testing, then we wont be able to set
1187		// ini setting on the command line.  be sure the executables
1188		// ini settings are compatible with the test, or skip
1189		if (($docgi || $this->conf['TEST_WEB']) &&
1190			isset($section_text['INI']) && $section_text['INI']) {
1191			$settings = $this->getINISettings($section_text);
1192			foreach ($settings as $k=>$v) {
1193				if (strcasecmp($v,'off')==0 || !$v) $v='';
1194				$haveval = isset($this->inisettings[$k]['local_value']);
1195				if ($k == 'include_path') {
1196					// we only want to know that src directory
1197					// is in the include path
1198					if (strpos($this->inisettings[$k]['local_value'],$this->cwd))
1199						continue;
1200				}
1201				if (($haveval && $this->inisettings[$k]['local_value'] != $v) || (!$haveval && $v)) {
1202					$this->showstatus($section_text['TEST'], 'SKIPPED', "Test requires ini setting $k=[$v], not [".($haveval?$this->inisettings[$k]['local_value']:'')."]");
1203					return "SKIPPED";
1204				}
1205			}
1206		}
1207		// now handle a SKIPIF section
1208		if ($section_text['SKIPIF']) {
1209			$output = trim($this->runscript($section_text['SKIPIF'],$this->test_executable_iscgi,realpath(dirname($file))),true);
1210			if (!$output) return NULL;
1211			if ($this->conf['TEST_PHP_DETAILED'] > 2)
1212				print "SKIPIF: [$output]\n";
1213			if (preg_match("/^skip/i", $output)){
1214
1215				$reason = (preg_match("/^skip\s*(.+)\$/", $output)) ? preg_replace("/^skip\s*(.+)\$/", "\\1", $output) : FALSE;
1216				$this->showstatus($section_text['TEST'], 'SKIPPED', $reason);
1217				return 'SKIPPED';
1218			}
1219			if (preg_match("/^info/i", $output)) {
1220				$reason = (preg_match("/^info\s*(.+)\$/", $output)) ? preg_replace("/^info\s*(.+)\$/", "\\1", $output) : FALSE;
1221				if ($reason) {
1222					$tested .= " (info: $reason)";
1223				}
1224			}
1225		}
1226		return NULL;
1227	}
1228
1229	//
1230	//  Run an individual test case.
1231	//
1232	function run_test($file)
1233	{
1234		if ($this->conf['TEST_PHP_DETAILED'])
1235			$this->writemsg("\n=================\nTEST $file\n");
1236
1237		$section_text = $this->getSectionText($file);
1238
1239		if ($this->iswin32)
1240			$shortname = str_replace($this->conf['TEST_BASE_PATH'].'\\', '', $file);
1241		else
1242			$shortname = str_replace($this->conf['TEST_BASE_PATH'].'/', '', $file);
1243		$tested = $section_text['TEST']." [$shortname]";
1244
1245		if ($this->conf['TEST_WEB']) {
1246			$tmp_file   = preg_replace('/\.phpt$/','.'.$this->conf['TEST_WEB_EXT'],$file);
1247			$uri = $this->conf['TEST_BASE_SCRIPT_NAME'].str_replace($this->conf['TEST_BASE_PATH'], '', $tmp_file);
1248			$uri = str_replace('\\', '/', $uri);
1249		} else {
1250			$tmp_file   = preg_replace('/\.phpt$/','.php',$file);
1251		}
1252		@unlink($tmp_file);
1253
1254		// unlink old test results
1255		@unlink(preg_replace('/\.phpt$/','.diff',$file));
1256		@unlink(preg_replace('/\.phpt$/','.log',$file));
1257		@unlink(preg_replace('/\.phpt$/','.exp',$file));
1258		@unlink(preg_replace('/\.phpt$/','.out',$file));
1259
1260		if (!$this->conf['TEST_WEB']) {
1261			// Reset environment from any previous test.
1262			$env = $this->getENVSettings($section_text,$tmp_file);
1263			$ini_overwrites = $this->getINIParams($section_text);
1264		}
1265
1266		// if this is a cgi test, prepare for it
1267		$query_string = '';
1268		$havepost = array_key_exists('POST', $section_text) && !empty($section_text['POST']);
1269		// allow empty query_string requests
1270		$haveget = array_key_exists('GET', $section_text) && !empty($section_text['GET']);
1271		$do_cgi = array_key_exists('CGI', $section_text) || $haveget || $havepost;
1272
1273		$skipreason = $this->getSkipReason($file,$section_text,$do_cgi);
1274		if ($skipreason == 'SKIPPED') {
1275			return $skipreason;
1276		}
1277
1278		// We've satisfied the preconditions - run the test!
1279		file_put_contents($tmp_file,$section_text['FILE']);
1280
1281		$post = NULL;
1282		$args = "";
1283
1284		$headers = array();
1285		if ($this->conf['TEST_WEB']) {
1286			$request = $this->getEvalTestSettings(@$section_text['REQUEST'],$tmp_file);
1287			$headers = $this->getEvalTestSettings(@$section_text['HEADERS'],$tmp_file);
1288
1289			$method = isset($request['method'])?$request['method']:$havepost?'POST':'GET';
1290			$query_string = $haveget?$section_text['GET']:'';
1291
1292			$options = array();
1293			$options['method']=$method;
1294			if (isset($this->conf['timeout']))    $options['timeout']    = $this->conf['timeout'];
1295			if (isset($this->conf['proxy_host'])) $options['proxy_host'] = $this->conf['proxy_host'];
1296			if (isset($this->conf['proxy_port'])) $options['proxy_port'] = $this->conf['proxy_port'];
1297			if (isset($this->conf['proxy_user'])) $options['proxy_user'] = $this->conf['proxy_user'];
1298			if (isset($this->conf['proxy_pass'])) $options['proxy_pass'] = $this->conf['proxy_pass'];
1299
1300			$post = $havepost?$section_text['POST']:NULL;
1301			$url = $this->conf['TEST_SERVER_URL'];
1302			if (isset($request['SCRIPT_NAME']))
1303				$url .= $request['SCRIPT_NAME'];
1304			else
1305				$url .= $uri;
1306			if (isset($request['PATH_INFO']))
1307				$url .= $request['PATH_INFO'];
1308			if (isset($request['FRAGMENT']))
1309				$url .= '#'.$request['FRAGMENT'];
1310			if (isset($request['QUERY_STRING']))
1311				$query_string = $request['QUERY_STRING'];
1312			if ($query_string)
1313				$url .= '?'.$query_string;
1314			if ($this->conf['TEST_PHP_DETAILED'])
1315				$this->writemsg("\nURL  = $url\n");
1316		} else if ($do_cgi) {
1317			$query_string = $haveget?$section_text['GET']:'';
1318
1319			if (!array_key_exists('GATEWAY_INTERFACE', $env))
1320				$env['GATEWAY_INTERFACE']='CGI/1.1';
1321			if (!array_key_exists('SERVER_SOFTWARE', $env))
1322				$env['SERVER_SOFTWARE']='PHP Test Harness';
1323			if (!array_key_exists('SERVER_SOFTWARE', $env))
1324				$env['SERVER_NAME']='127.0.0.1';
1325			if (!array_key_exists('REDIRECT_STATUS', $env))
1326				$env['REDIRECT_STATUS']='200';
1327			if (!array_key_exists('SERVER_NAME', $env))
1328				$env['QUERY_STRING']=$query_string;
1329			if (!array_key_exists('PATH_TRANSLATED', $env) &&
1330				!array_key_exists('SCRIPT_FILENAME', $env)) {
1331				$env['PATH_TRANSLATED']=$tmp_file;
1332				$env['SCRIPT_FILENAME']=$tmp_file;
1333			}
1334			if (!array_key_exists('PATH_TRANSLATED', $env))
1335				$env['PATH_TRANSLATED']='';
1336			if (!array_key_exists('PATH_INFO', $env))
1337				$env['PATH_INFO']='';
1338			if (!array_key_exists('SCRIPT_NAME', $env))
1339				$env['SCRIPT_NAME']='';
1340			if (!array_key_exists('SCRIPT_FILENAME', $env))
1341				$env['SCRIPT_FILENAME']='';
1342
1343			if (array_key_exists('POST', $section_text) && (!$haveget || !empty($section_text['POST']))) {
1344				$post = $section_text['POST'];
1345				$content_length = strlen($post);
1346				if (!array_key_exists('REQUEST_METHOD', $env))
1347					$env['REQUEST_METHOD']='POST';
1348				if (!array_key_exists('CONTENT_TYPE', $env))
1349					$env['CONTENT_TYPE']='application/x-www-form-urlencoded';
1350				if (!array_key_exists('CONTENT_LENGTH', $env))
1351					$env['CONTENT_LENGTH']=$content_length;
1352			} else {
1353				if (!array_key_exists('REQUEST_METHOD', $env))
1354					$env['REQUEST_METHOD']='GET';
1355				if (!array_key_exists('CONTENT_TYPE', $env))
1356					$env['CONTENT_TYPE']='';
1357				if (!array_key_exists('CONTENT_LENGTH', $env))
1358					$env['CONTENT_LENGTH']='';
1359			}
1360			if ($this->conf['TEST_PHP_DETAILED'] > 1)
1361				$this->writemsg("\nCONTENT_LENGTH  = " . $env['CONTENT_LENGTH'] .
1362						"\nCONTENT_TYPE    = " . $env['CONTENT_TYPE'] .
1363						"\nPATH_TRANSLATED = " . $env['PATH_TRANSLATED'] .
1364						"\nPATH_INFO       = " . $env['PATH_INFO'] .
1365						"\nQUERY_STRING    = " . $env['QUERY_STRING'] .
1366						"\nREDIRECT_STATUS = " . $env['REDIRECT_STATUS'] .
1367						"\nREQUEST_METHOD  = " . $env['REQUEST_METHOD'] .
1368						"\nSCRIPT_NAME     = " . $env['SCRIPT_NAME'] .
1369						"\nSCRIPT_FILENAME = " . $env['SCRIPT_FILENAME'] . "\n");
1370			/* not cgi spec to put query string on command line,
1371			   but used by a couple tests to catch a security hole
1372			   in older php versions.  At least IIS can be configured
1373			   to do this. */
1374			$args = $env['QUERY_STRING'];
1375			$args = "$ini_overwrites $tmp_file \"$args\" 2>&1";
1376		} else {
1377			$args = $section_text['ARGS'] ? $section_text['ARGS'] : '';
1378			$args = "$ini_overwrites $tmp_file $args 2>&1";
1379		}
1380
1381		if ($this->conf['TEST_WEB']) {
1382			// we want headers also, so fopen
1383			$r = new HTTPRequest($url,$headers,$options,$post);
1384			//$out = preg_replace("/\r\n/","\n",$r->response);
1385			$out = $r->response;
1386			$headers = $r->response_headers;
1387			//print $r->outgoing_payload."\n";
1388			//print $r->incoming_payload."\n";
1389		} else {
1390			$out = execute($this->conf['TEST_PHP_EXECUTABLE'],$args,$post,$this->cwd,$env);
1391			// if this is a cgi, remove the headers first
1392			if ($this->test_executable_iscgi
1393				 && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) {
1394				$out = $match[2];
1395				$rh = preg_split("/[\n\r]+/",$match[1]);
1396				$headers = array();
1397				foreach ($rh as $line) {
1398					if (strpos($line, ':')!==false) {
1399						$line = explode(":", $line, 2);
1400						$headers[trim($line[0])] = trim($line[1]);
1401					}
1402				}
1403			}
1404		}
1405
1406		if ($this->conf['TEST_PHP_DETAILED'] > 2) {
1407			echo "HEADERS: ";
1408			print_r($headers);
1409			echo "OUTPUT: \n$out\n";
1410
1411		}
1412
1413		// Does the output match what is expected?
1414		$output = trim($out);
1415		$output = preg_replace('/\r\n/',"\n",$output);
1416
1417		$failed = FALSE;
1418
1419		if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) {
1420			if (isset($section_text['EXPECTF'])) {
1421				$wanted = $section_text['EXPECTF'];
1422			} else {
1423				$wanted = $section_text['EXPECTREGEX'];
1424			}
1425			$wanted_re = preg_replace('/\r\n/',"\n",$wanted);
1426			if (isset($section_text['EXPECTF'])) {
1427				// do preg_quote, but miss out any %r delimited sections
1428				$temp = "";
1429				$r = "%r";
1430				$startOffset = 0;
1431				$length = strlen($wanted_re);
1432				while($startOffset < $length) {
1433					$start = strpos($wanted_re, $r, $startOffset);
1434					if ($start !== false) {
1435						// we have found a start tag
1436						$end = strpos($wanted_re, $r, $start+2);
1437						if ($end === false) {
1438							// unbalanced tag, ignore it.
1439							$end = $start = $length;
1440						}
1441					} else {
1442						// no more %r sections
1443						$start = $end = $length;
1444					}
1445					// quote a non re portion of the string
1446					$temp = $temp . preg_quote(substr($wanted_re, $startOffset, ($start - $startOffset)),  '/');
1447					// add the re unquoted.
1448					if ($end > $start) {
1449						$temp = $temp . '(' . substr($wanted_re, $start+2, ($end - $start-2)). ')';
1450					}
1451					$startOffset = $end + 2;
1452				}
1453				$wanted_re = $temp;
1454
1455				$wanted_re = str_replace(
1456					array('%binary_string_optional%'),
1457					'string',
1458					$wanted_re
1459				);
1460				$wanted_re = str_replace(
1461					array('%unicode_string_optional%'),
1462					'string',
1463					$wanted_re
1464				);
1465				$wanted_re = str_replace(
1466					array('%unicode\|string%', '%string\|unicode%'),
1467					'string',
1468					$wanted_re
1469				);
1470				$wanted_re = str_replace(
1471					array('%u\|b%', '%b\|u%'),
1472					'',
1473					$wanted_re
1474				);
1475				// Stick to basics
1476				$wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re);
1477				$wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re);
1478				$wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re);
1479				$wanted_re = str_replace('%a', '.+', $wanted_re);
1480				$wanted_re = str_replace('%A', '.*', $wanted_re);
1481				$wanted_re = str_replace('%w', '\s*', $wanted_re);
1482				$wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re);
1483				$wanted_re = str_replace('%d', '\d+', $wanted_re);
1484				$wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re);
1485				$wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re);
1486				$wanted_re = str_replace('%c', '.', $wanted_re);
1487				// %f allows two points "-.0.0" but that is the best *simple* expression
1488
1489			}
1490	/* DEBUG YOUR REGEX HERE
1491			var_dump($wanted_re);
1492			print(str_repeat('=', 80) . "\n");
1493			var_dump($output);
1494	*/
1495			$failed = !preg_match("/^$wanted_re\$/s", $output);
1496		}
1497
1498		$skipexpect = false;
1499		if (!$failed && $this->conf['TEST_WEB'] && isset($section_text['EXPECTHEADERS'])) {
1500			$want = array();
1501			$lines = preg_split("/[\n\r]+/",$section_text['EXPECTHEADERS']);
1502			$wanted='';
1503            foreach ($lines as $line) {
1504                if (strpos($line, ':')!==false) {
1505                    $line = explode(":", $line, 2);
1506                    $want[trim($line[0])] = trim($line[1]);
1507					$wanted .= trim($line[0]).': '.trim($line[1])."\n";
1508                }
1509            }
1510			$output='';
1511			foreach ($want as $k=>$v) {
1512				$output .= "$k: {$headers[$k]}\n";
1513				if (!isset($headers[$k]) || $headers[$k] != $v) {
1514					$failed = TRUE;
1515				}
1516			}
1517
1518			// different servers may do different things on non-200 results
1519			// for instance, IIS will deliver it's own error pages, so we
1520			// cannot expect to match up the EXPECT section.  We may however,
1521			// want to match EXPECT on more than 200 results, so this may
1522			// need to change later.
1523			$skipexpect = isset($headers['Status']) && $headers['Status'] != 200;
1524		}
1525
1526		if (!$failed && !$skipexpect && isset($section_text['EXPECT'])) {
1527			$wanted = $section_text['EXPECT'];
1528			$wanted = preg_replace('/\r\n/',"\n",$wanted);
1529			$failed = (0 != strcmp($output,$wanted));
1530		}
1531
1532		if (!$failed) {
1533			@unlink($tmp_file);
1534			$this->showstatus($tested, 'PASSED');
1535			return 'PASSED';
1536		}
1537
1538		// Test failed so we need to report details.
1539		$this->showstatus($tested, 'FAILED');
1540
1541		$this->failed_tests[] = array(
1542							'name' => $file,
1543							'test_name' => $tested,
1544							'output' => preg_replace('/\.phpt$/','.log', $file),
1545							'diff'   => preg_replace('/\.phpt$/','.diff', $file)
1546							);
1547
1548		if ($this->conf['TEST_PHP_DETAILED'])
1549			$this->writemsg(generate_diff($wanted,$output)."\n");
1550
1551		// write .exp
1552		if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'E') !== FALSE) {
1553			$logname = preg_replace('/\.phpt$/','.exp',$file);
1554			file_put_contents($logname,$wanted);
1555		}
1556
1557		// write .out
1558		if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'O') !== FALSE) {
1559			$logname = preg_replace('/\.phpt$/','.out',$file);
1560			file_put_contents($logname,$output);
1561		}
1562
1563		// write .diff
1564		if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'D') !== FALSE) {
1565			$logname = preg_replace('/\.phpt$/','.diff',$file);
1566			file_put_contents($logname,generate_diff($wanted,$output));
1567		}
1568
1569		// write .log
1570		if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'L') !== FALSE) {
1571			$logname = preg_replace('/\.phpt$/','.log',$file);
1572			file_put_contents($logname,
1573						"\n---- EXPECTED OUTPUT\n$wanted\n".
1574						"---- ACTUAL OUTPUT\n$output\n".
1575						"---- FAILED\n");
1576			// display emacs/msvc error output
1577			if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'C') !== FALSE) {
1578				$this->error_report($file,$logname,$tested);
1579			}
1580		}
1581		return 'FAILED';
1582	}
1583
1584	//
1585	//  Write an error in a format recognizable to Emacs or MSVC.
1586	//
1587	function error_report($testname,$logname,$tested)
1588	{
1589		$testname = realpath($testname);
1590		$logname  = realpath($logname);
1591		switch ($this->conf['TEST_PHP_ERROR_STYLE']) {
1592		default:
1593		case 'MSVC':
1594			$this->writemsg($testname . "(1) : $tested\n");
1595			$this->writemsg($logname . "(1) :  $tested\n");
1596			break;
1597		case 'EMACS':
1598			$this->writemsg($testname . ":1: $tested\n");
1599			$this->writemsg($logname . ":1:  $tested\n");
1600			break;
1601		}
1602	}
1603
1604	function error($message)
1605	{
1606		$this->writemsg("ERROR: {$message}\n");
1607		exit(1);
1608	}
1609}
1610
1611$test = new testHarness();
1612/*
1613 * Local variables:
1614 * tab-width: 4
1615 * c-basic-offset: 4
1616 * End:
1617 * vim600: fdm=marker
1618 * vim: noet sw=4 ts=4
1619 */
1620?>
1621