You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1576 lines
51 KiB

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