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.

1573 lines
50 KiB

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