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