server-tests.php 51 KB

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