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.

1564 lines
50 KiB

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