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