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