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.

1620 lines
52 KiB

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