server-tests.php 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621
  1. <?php
  2. /*
  3. +----------------------------------------------------------------------+
  4. | PHP Version 5 |
  5. +----------------------------------------------------------------------+
  6. | Copyright (c) 1997-2016 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. 'magic_quotes_runtime'=>'0',
  430. );
  431. public $env = array();
  432. public $info_params = array();
  433. function testHarness() {
  434. $this->iswin32 = substr(PHP_OS, 0, 3) == "WIN";
  435. $this->checkRequirements();
  436. $this->env = $_ENV;
  437. $this->removeSensitiveEnvVars();
  438. $this->initializeConfiguration();
  439. $this->parseArgs();
  440. $this->setTestPaths();
  441. # change to working directory
  442. if ($this->conf['TEST_PHP_SRCDIR']) {
  443. @chdir($this->conf['TEST_PHP_SRCDIR']);
  444. }
  445. $this->cwd = getcwd();
  446. if (!$this->conf['TEST_PHP_SRCDIR'])
  447. $this->conf['TEST_PHP_SRCDIR'] = $this->cwd;
  448. if (!$this->conf['TEST_BASE_PATH'] && $this->conf['TEST_PHP_SRCDIR'])
  449. $this->conf['TEST_BASE_PATH'] = $this->conf['TEST_PHP_SRCDIR'];
  450. if ($this->iswin32) {
  451. $this->conf['TEST_PHP_SRCDIR'] = str_replace('/','\\',$this->conf['TEST_PHP_SRCDIR']);
  452. $this->conf['TEST_BASE_PATH'] = str_replace('/','\\',$this->conf['TEST_BASE_PATH']);
  453. }
  454. if (!$this->conf['TEST_WEB'] && !is_executable($this->conf['TEST_PHP_EXECUTABLE'])) {
  455. $this->error("invalid PHP executable specified by TEST_PHP_EXECUTABLE = " .
  456. $this->conf['TEST_PHP_EXECUTABLE']);
  457. return false;
  458. }
  459. $this->getInstalledExtensions();
  460. $this->getExecutableInfo();
  461. $this->getExecutableIniSettings();
  462. $this->test_executable_iscgi = strncmp($this->exec_info['PHP_SAPI'],'cgi',3)==0;
  463. $this->calculateDocumentRoot();
  464. // add TEST_PHP_SRCDIR to the include path, this facilitates
  465. // tests including files from src/tests
  466. //$this->ini_overwrites['include_path'] = $this->cwd.($this->iswin32?';.;':':.:').$this->exec_info['INCLUDE_PATH'];
  467. $params = array();
  468. settings2array($this->ini_overwrites,$params);
  469. $this->info_params = settings2params($params);
  470. $this->contextHeader();
  471. if ($this->conf['TEST_CONTEXT_INFO']) return;
  472. $this->loadFileList();
  473. $this->moveTestFiles();
  474. $this->run();
  475. $this->summarizeResults();
  476. }
  477. function getExecutableIniSettings()
  478. {
  479. $out = $this->runscript(PHP_INI_SETTINGS_SCRIPT,true);
  480. $this->inisettings = unserialize($out);
  481. }
  482. function getExecutableInfo()
  483. {
  484. $out = $this->runscript(PHP_INFO_SCRIPT,true);
  485. $out = preg_split("/[\n\r]+/",$out);
  486. $info = array();
  487. foreach ($out as $line) {
  488. if (strpos($line, '=')!==false) {
  489. $line = explode("=", $line, 2);
  490. $name = trim($line[0]);
  491. $value = trim($line[1]);
  492. $info[$name] = $value;
  493. }
  494. }
  495. $this->exec_info = $info;
  496. }
  497. function getInstalledExtensions()
  498. {
  499. // get the list of installed extensions
  500. $out = $this->runscript(PHP_EXTENSIONS_SCRIPT,true);
  501. $this->exts_to_test = explode(":",$out);
  502. sort($this->exts_to_test);
  503. $this->exts_tested = count($this->exts_to_test);
  504. }
  505. // if running local, calls executeCode,
  506. // otherwise does an http request
  507. function runscript($script,$removeheaders=false,$cwd=NULL)
  508. {
  509. if ($this->conf['TEST_WEB']) {
  510. $pi = '/testscript.' . $this->conf['TEST_WEB_EXT'];
  511. if (!$cwd) $cwd = $this->conf['TEST_BASE_PATH'];
  512. $tmp_file = "$cwd$pi";
  513. $pi = substr($cwd,strlen($this->conf['TEST_BASE_PATH'])) . $pi;
  514. $url = $this->conf['TEST_WEB_BASE_URL'] . $pi;
  515. file_put_contents($tmp_file,$script);
  516. $fd = fopen($url, "rb");
  517. $out = '';
  518. if ($fd) {
  519. while (!feof($fd))
  520. $out .= fread($fd, 8192);
  521. fclose($fd);
  522. }
  523. unlink($tmp_file);
  524. if (0 && $removeheaders &&
  525. preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) {
  526. return $match[2];
  527. }
  528. return $out;
  529. } else {
  530. return executeCode($this->conf['TEST_PHP_EXECUTABLE'],$this->ini_overwrites, $script,$removeheaders,$cwd,$this->env);
  531. }
  532. }
  533. // Use this function to do any displaying of text, so that
  534. // things can be over-written as necessary.
  535. function writemsg($msg) {
  536. echo $msg;
  537. }
  538. // Another wrapper function, this one should be used any time
  539. // a particular test passes or fails
  540. function showstatus($item, $status, $reason = '') {
  541. switch($status) {
  542. case 'PASSED':
  543. $this->writemsg("PASSED: $item ($reason)\n");
  544. break;
  545. case 'FAILED':
  546. $this->writemsg("FAILED: $item ($reason)\n");
  547. break;
  548. case 'SKIPPED':
  549. $this->writemsg("SKIPPED: $item ($reason)\n");
  550. break;
  551. }
  552. }
  553. function help()
  554. {
  555. $usage = "usage: php run-tests.php [options]\n";
  556. foreach ($this->xargs as $arg=>$arg_info) {
  557. $usage .= sprintf(" -%s %-12s %s\n",$arg,$arg_info[1],$arg_info[3]);
  558. }
  559. return $usage;
  560. }
  561. function parseArgs() {
  562. global $argc;
  563. global $argv;
  564. global $_SERVER;
  565. if (!isset($argv)) {
  566. $argv = $_SERVER['argv'];
  567. $argc = $_SERVER['argc'];
  568. }
  569. $conf = NULL;
  570. for ($i=1; $i<$argc;) {
  571. if ($argv[$i][0] != '-') continue;
  572. $opt = $argv[$i++][1];
  573. if (isset($value)) unset($value);
  574. if (@$argv[$i][0] != '-') {
  575. @$value = $argv[$i++];
  576. }
  577. switch($opt) {
  578. case 'c':
  579. /* TODO: Implement configuraiton file */
  580. include($value);
  581. if (!isset($conf)) {
  582. $this->writemsg("Invalid configuration file\n");
  583. exit(1);
  584. }
  585. $this->conf = array_merge($this->conf,$conf);
  586. break;
  587. case 'e':
  588. $this->conf['TEST_PHP_ERROR_STYLE'] = strtoupper($value);
  589. break;
  590. case 'h':
  591. print $this->help();
  592. exit(0);
  593. default:
  594. if ($this->xargs[$opt][1] && isset($value))
  595. $this->conf[$this->xargs[$opt][0]] = $value;
  596. else if (!$this->xargs[$opt][1])
  597. $this->conf[$this->xargs[$opt][0]] = isset($value)?$value:1;
  598. else
  599. $this->error("Invalid argument setting for argument $opt, should be [{$this->xargs[$opt][1]}]\n");
  600. break;
  601. }
  602. }
  603. // set config into environment, this allows
  604. // executed tests to find out about the test
  605. // configurations. config file or args overwrite
  606. // env var config settings
  607. $this->env = array_merge($this->env,$this->conf);
  608. if (!$this->conf['TEST_WEB'] && !$this->conf['TEST_PHP_EXECUTABLE']) {
  609. $this->writemsg($this->help());
  610. exit(0);
  611. }
  612. }
  613. function removeSensitiveEnvVars()
  614. {
  615. # delete sensitive env vars
  616. $this->env['SSH_CLIENT']='deleted';
  617. $this->env['SSH_AUTH_SOCK']='deleted';
  618. $this->env['SSH_TTY']='deleted';
  619. }
  620. function setEnvConfigVar($name)
  621. {
  622. if (isset($this->env[$name])) {
  623. $this->conf[$name] = $this->env[$name];
  624. }
  625. }
  626. function initializeConfiguration()
  627. {
  628. foreach ($this->xargs as $arg=>$arg_info) {
  629. if ($arg_info[0]) {
  630. # initialize the default setting
  631. $this->conf[$arg_info[0]]=$arg_info[2];
  632. # get config from environment
  633. $this->setEnvConfigVar($arg_info[0]);
  634. }
  635. }
  636. }
  637. function setTestPaths()
  638. {
  639. // configure test paths from config file or command line
  640. if (@$this->conf['TEST_PATHS']) {
  641. $this->test_dirs = array();
  642. if ($this->iswin32) {
  643. $paths = explode(';',$this->conf['TEST_PATHS']);
  644. } else {
  645. $paths = explode(':|;',$this->conf['TEST_PATHS']);
  646. }
  647. foreach($paths as $path) {
  648. $this->test_dirs[] = realpath($path);
  649. }
  650. }
  651. }
  652. function test_sort($a, $b) {
  653. $ta = strpos($a, "{$this->cwd}/tests")===0 ? 1 + (strpos($a, "{$this->cwd}/tests/run-test")===0 ? 1 : 0) : 0;
  654. $tb = strpos($b, "{$this->cwd}/tests")===0 ? 1 + (strpos($b, "{$this->cwd}/tests/run-test")===0 ? 1 : 0) : 0;
  655. if ($ta == $tb) {
  656. return strcmp($a, $b);
  657. } else {
  658. return $tb - $ta;
  659. }
  660. }
  661. function checkRequirements() {
  662. if (version_compare(phpversion(), "5.0") < 0) {
  663. $this->writemsg(REQ_PHP_VERSION);
  664. exit;
  665. }
  666. // We might want to check another server so we won't see that server's /tmp
  667. // if (!file_exists("/tmp")) {
  668. // $this->writemsg(TMP_MISSING);
  669. // exit;
  670. // }
  671. if (!function_exists("proc_open")) {
  672. $this->writemsg(PROC_OPEN_MISSING);
  673. exit;
  674. }
  675. if (!extension_loaded("pcre")) {
  676. $this->writemsg(PCRE_MISSING_ERROR);
  677. exit;
  678. }
  679. if (ini_get('safe_mode')) {
  680. $this->writemsg(SAFE_MODE_WARNING);
  681. }
  682. }
  683. //
  684. // Write test context information.
  685. //
  686. function contextHeader()
  687. {
  688. $info = '';
  689. foreach ($this->exec_info as $k=>$v) {
  690. $info .= sprintf("%-20.s: %s\n",$k,$v);
  691. }
  692. $exts = '';
  693. foreach ($this->exts_to_test as $ext) {
  694. $exts .="$ext\n ";
  695. }
  696. $dirs = '';
  697. foreach ($this->test_dirs as $test_dir) {
  698. $dirs .= "$test_dir\n ";
  699. }
  700. $conf = '';
  701. foreach ($this->conf as $k=>$v) {
  702. $conf .= sprintf("%-20.s: %s\n",$k,$v);
  703. }
  704. $exeinfo = '';
  705. if (!$this->conf['TEST_WEB'])
  706. $exeinfo = "CWD : {$this->cwd}\n".
  707. "PHP : {$this->conf['TEST_PHP_EXECUTABLE']}\n";
  708. $this->writemsg("\n$this->ddash\n".
  709. "$exeinfo$info\n".
  710. "Test Harness Configuration:\n$conf\n".
  711. "Extensions : $exts\n".
  712. "Test Dirs : $dirs\n".
  713. "$this->ddash\n");
  714. }
  715. function loadFileList()
  716. {
  717. foreach ($this->test_dirs as $dir) {
  718. if (is_dir($dir)) {
  719. $this->findFilesInDir($dir, ($dir == 'ext'));
  720. } else {
  721. $this->test_files[] = $dir;
  722. }
  723. }
  724. usort($this->test_files,array($this,"test_sort"));
  725. $this->writemsg("found ".count($this->test_files)." files\n");
  726. }
  727. function moveTestFiles()
  728. {
  729. if (!$this->conf['TEST_BASE_PATH'] ||
  730. $this->conf['TEST_BASE_PATH'] == $this->conf['TEST_PHP_SRCDIR']) return;
  731. $this->writemsg("moving files from {$this->conf['TEST_PHP_SRCDIR']} to {$this->conf['TEST_BASE_PATH']}\n");
  732. $l = strlen($this->conf['TEST_PHP_SRCDIR']);
  733. $files = array();
  734. $dirs = array();
  735. foreach ($this->test_files as $file) {
  736. if (strpos($file,$this->conf['TEST_PHP_SRCDIR'])==0) {
  737. $newlocation = $this->conf['TEST_BASE_PATH'].substr($file,$l);
  738. $files[] = $newlocation;
  739. $dirs[dirname($file)] = dirname($newlocation);
  740. } else {
  741. // XXX what to do with test files outside the
  742. // php source directory? Need to map them into
  743. // the new directory somehow.
  744. }
  745. }
  746. foreach ($dirs as $src=>$new) {
  747. mkpath($new);
  748. copyfiles($src,$new);
  749. }
  750. $this->test_files = $files;
  751. }
  752. function findFilesInDir($dir,$is_ext_dir=FALSE,$ignore=FALSE)
  753. {
  754. $skip = array('.', '..', 'CVS');
  755. $o = opendir($dir) or $this->error("cannot open directory: $dir");
  756. while (($name = readdir($o)) !== FALSE) {
  757. if (in_array($name, $skip)) continue;
  758. if (is_dir("$dir/$name")) {
  759. $skip_ext = ($is_ext_dir && !in_array($name, $this->exts_to_test));
  760. if ($skip_ext) {
  761. $this->exts_skipped++;
  762. }
  763. $this->findFilesInDir("$dir/$name", FALSE, $ignore || $skip_ext);
  764. }
  765. // Cleanup any left-over tmp files from last run.
  766. if (substr($name, -4) == '.tmp') {
  767. @unlink("$dir/$name");
  768. continue;
  769. }
  770. // Otherwise we're only interested in *.phpt files.
  771. if (substr($name, -5) == '.phpt') {
  772. if ($ignore) {
  773. $this->ignored_by_ext++;
  774. } else {
  775. $testfile = realpath("$dir/$name");
  776. $this->test_files[] = $testfile;
  777. }
  778. }
  779. }
  780. closedir($o);
  781. }
  782. function runHeader()
  783. {
  784. $this->writemsg("TIME START " . date('Y-m-d H:i:s', $this->start_time) . "\n".$this->ddash."\n");
  785. if (count($this->test_to_run)) {
  786. $this->writemsg("Running selected tests.\n");
  787. } else {
  788. $this->writemsg("Running all test files.\n");
  789. }
  790. }
  791. function run()
  792. {
  793. $this->start_time = time();
  794. $this->runHeader();
  795. // Run selected tests.
  796. if (count($this->test_to_run)) {
  797. foreach($this->test_to_run as $name=>$runnable) {
  798. if(!preg_match("/\.phpt$/", $name))
  799. continue;
  800. if ($runnable) {
  801. $this->test_results[$name] = $this->run_test($name);
  802. }
  803. }
  804. } else {
  805. foreach ($this->test_files as $name) {
  806. $this->test_results[$name] = $this->run_test($name);
  807. }
  808. }
  809. $this->end_time = time();
  810. }
  811. function summarizeResults()
  812. {
  813. if (count($this->test_results) == 0) {
  814. $this->writemsg("No tests were run.\n");
  815. return;
  816. }
  817. $n_total = count($this->test_results);
  818. $n_total += $this->ignored_by_ext;
  819. $sum_results = array('PASSED'=>0, 'SKIPPED'=>0, 'FAILED'=>0);
  820. foreach ($this->test_results as $v) {
  821. $sum_results[$v]++;
  822. }
  823. $sum_results['SKIPPED'] += $this->ignored_by_ext;
  824. $percent_results = array();
  825. while (list($v,$n) = each($sum_results)) {
  826. $percent_results[$v] = (100.0 * $n) / $n_total;
  827. }
  828. $this->writemsg("\n".$this->ddash."\n".
  829. "TIME END " . date('Y-m-d H:i:s', $this->end_time) . "\n".
  830. $this->ddash."\n".
  831. "TEST RESULT SUMMARY\n".
  832. $this->sdash."\n".
  833. "Exts skipped : " . sprintf("%4d",$this->exts_skipped) . "\n".
  834. "Exts tested : " . sprintf("%4d",$this->exts_tested) . "\n".
  835. $this->sdash."\n".
  836. "Number of tests : " . sprintf("%4d",$n_total) . "\n".
  837. "Tests skipped : " . sprintf("%4d (%2.1f%%)",$sum_results['SKIPPED'],$percent_results['SKIPPED']) . "\n".
  838. "Tests failed : " . sprintf("%4d (%2.1f%%)",$sum_results['FAILED'],$percent_results['FAILED']) . "\n".
  839. "Tests passed : " . sprintf("%4d (%2.1f%%)",$sum_results['PASSED'],$percent_results['PASSED']) . "\n".
  840. $this->sdash."\n".
  841. "Time taken : " . sprintf("%4d seconds", $this->end_time - $this->start_time) . "\n".
  842. $this->ddash."\n");
  843. $failed_test_summary = '';
  844. if ($this->failed_tests) {
  845. $failed_test_summary .= "\n".$this->ddash."\n".
  846. "FAILED TEST SUMMARY\n".$this->sdash."\n";
  847. foreach ($this->failed_tests as $failed_test_data) {
  848. $failed_test_summary .= $failed_test_data['test_name'] . "\n";
  849. }
  850. $failed_test_summary .= $this->ddash."\n";
  851. }
  852. if ($failed_test_summary && !$this->conf['NO_PHPTEST_SUMMARY']) {
  853. $this->writemsg($failed_test_summary);
  854. }
  855. /* We got failed Tests, offer the user to send and e-mail to QA team, unless NO_INTERACTION is set */
  856. if ($sum_results['FAILED'] && !$this->conf['NO_INTERACTION']) {
  857. $fp = fopen("php://stdin", "r+");
  858. $this->writemsg("\nPlease allow this report to be send to the PHP QA\nteam. This will give us a better understanding in how\n");
  859. $this->writemsg("PHP's test cases are doing.\n");
  860. $this->writemsg("(choose \"s\" to just save the results to a file)? [Yns]: ");
  861. flush();
  862. $user_input = fgets($fp, 10);
  863. $just_save_results = (strtolower($user_input[0]) == 's');
  864. if ($just_save_results || strlen(trim($user_input)) == 0 || strtolower($user_input[0]) == 'y') {
  865. /*
  866. * Collect information about the host system for our report
  867. * Fetch phpinfo() output so that we can see the PHP environment
  868. * Make an archive of all the failed tests
  869. * Send an email
  870. */
  871. /* Ask the user to provide an email address, so that QA team can contact the user */
  872. if (!strncasecmp($user_input, 'y', 1) || strlen(trim($user_input)) == 0) {
  873. 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): ";
  874. flush();
  875. $fp = fopen("php://stdin", "r+");
  876. $user_email = trim(fgets($fp, 1024));
  877. $user_email = str_replace("@", " at ", str_replace(".", " dot ", $user_email));
  878. }
  879. $failed_tests_data = '';
  880. $sep = "\n" . str_repeat('=', 80) . "\n";
  881. $failed_tests_data .= $failed_test_summary . "\n";
  882. if (array_sum($this->failed_tests)) {
  883. foreach ($this->failed_tests as $test_info) {
  884. $failed_tests_data .= $sep . $test_info['name'];
  885. $failed_tests_data .= $sep . file_get_contents(realpath($test_info['output']));
  886. $failed_tests_data .= $sep . file_get_contents(realpath($test_info['diff']));
  887. $failed_tests_data .= $sep . "\n\n";
  888. }
  889. $status = "failed";
  890. } else {
  891. $status = "success";
  892. }
  893. $failed_tests_data .= "\n" . $sep . 'BUILD ENVIRONMENT' . $sep;
  894. $failed_tests_data .= "OS:\n". PHP_OS. "\n\n";
  895. $automake = $autoconf = $libtool = $compiler = 'N/A';
  896. if (!$this->iswin32) {
  897. $automake = shell_exec('automake --version');
  898. $autoconf = shell_exec('autoconf --version');
  899. /* Always use the generated libtool - Mac OSX uses 'glibtool' */
  900. $libtool = shell_exec('./libtool --version');
  901. /* Try the most common flags for 'version' */
  902. $flags = array('-v', '-V', '--version');
  903. $cc_status=0;
  904. foreach($flags AS $flag) {
  905. system($this->env['CC']." $flag >/dev/null 2>&1", $cc_status);
  906. if($cc_status == 0) {
  907. $compiler = shell_exec($this->env['CC']." $flag 2>&1");
  908. break;
  909. }
  910. }
  911. }
  912. $failed_tests_data .= "Automake:\n$automake\n";
  913. $failed_tests_data .= "Autoconf:\n$autoconf\n";
  914. $failed_tests_data .= "Libtool:\n$libtool\n";
  915. $failed_tests_data .= "Compiler:\n$compiler\n";
  916. $failed_tests_data .= "Bison:\n". @shell_exec('bison --version'). "\n";
  917. $failed_tests_data .= "\n\n";
  918. if (isset($user_email)) {
  919. $failed_tests_data .= "User's E-mail: ".$user_email."\n\n";
  920. }
  921. $failed_tests_data .= $sep . "PHPINFO" . $sep;
  922. $failed_tests_data .= shell_exec($this->conf['TEST_PHP_EXECUTABLE'].' -dhtml_errors=0 -i');
  923. $compression = 0;
  924. if ($just_save_results ||
  925. !post_result_data("status=$status&version=".urlencode(TESTED_PHP_VERSION),$failed_tests_data)) {
  926. $output_file = 'php_test_results_' . date('Ymd_Hi') . ( $compression ? '.txt.gz' : '.txt' );
  927. $fp = fopen($output_file, "w");
  928. fwrite($fp, $failed_tests_data);
  929. fclose($fp);
  930. if (!$just_save_results)
  931. echo "\nThe test script was unable to automatically send the report to PHP's QA Team\n";
  932. echo "Please send ".$output_file." to ".PHP_QA_EMAIL." manually, thank you.\n";
  933. } else {
  934. fwrite($fp, "\nThank you for helping to make PHP better.\n");
  935. fclose($fp);
  936. }
  937. }
  938. }
  939. if($this->conf['REPORT_EXIT_STATUS'] and $sum_results['FAILED']) {
  940. exit(1);
  941. }
  942. }
  943. function getINISettings(&$section_text)
  944. {
  945. $ini_settings = $this->ini_overwrites;
  946. // Any special ini settings
  947. // these may overwrite the test defaults...
  948. if (array_key_exists('INI', $section_text)) {
  949. settings2array(preg_split( "/[\n\r]+/", $section_text['INI']), $ini_settings);
  950. }
  951. return $ini_settings;
  952. }
  953. function getINIParams(&$section_text)
  954. {
  955. if (!$section_text) return '';
  956. // XXX php5 current has a problem doing this in one line
  957. // it fails with Only variables can be passed by reference
  958. // on test ext\calendar\tests\jdtojewish.phpt
  959. // return settings2params($this->getINISettings($section_text));
  960. $ini = $this->getINISettings($section_text);
  961. return settings2params($ini);
  962. }
  963. function calculateDocumentRoot()
  964. {
  965. if ($this->conf['TEST_WEB'] || $this->test_executable_iscgi) {
  966. // configure DOCUMENT_ROOT for web tests
  967. // this assumes that directories from the base url
  968. // matches directory depth from the base path
  969. $parts = parse_url($this->conf['TEST_WEB_BASE_URL']);
  970. $depth = substr_count($parts['path'],'/');
  971. $docroot = $this->conf['TEST_BASE_PATH'];
  972. for ($i=0 ; $i < $depth; $i++) $docroot = dirname($docroot);
  973. $this->conf['TEST_DOCUMENT_ROOT']=$docroot;
  974. $this->conf['TEST_BASE_SCRIPT_NAME']=$parts['path'];
  975. $this->conf['TEST_SERVER_URL']=substr($this->conf['TEST_WEB_BASE_URL'],0,strlen($this->conf['TEST_WEB_BASE_URL'])-strlen($parts['path']));
  976. } else {
  977. $this->conf['TEST_DOCUMENT_ROOT']='';
  978. $this->conf['TEST_BASE_SCRIPT_NAME']='';
  979. $this->conf['TEST_SERVER_URL']='';
  980. }
  981. }
  982. function evalSettings($filename,$data) {
  983. // we eval the section so we can allow dynamic env vars
  984. // for cgi testing
  985. $filename = str_replace('\\','/',$filename);
  986. $cwd = str_replace('\\','/',$this->cwd);
  987. $filepath = dirname($filename);
  988. $scriptname = substr($filename,strlen($this->conf['TEST_DOCUMENT_ROOT']));
  989. // eval fails if no newline
  990. return eval("$data\n");
  991. }
  992. function getENVSettings(&$section_text,$testfile)
  993. {
  994. $env = $this->env;
  995. // Any special environment settings
  996. // these may overwrite the test defaults...
  997. if (array_key_exists('ENV', $section_text)) {
  998. $sect = $this->evalSettings($testfile,$section_text['ENV']);
  999. //print "data evaled:\n$sect\n";
  1000. settings2array(preg_split( "/[\n\r]+/", $sect), $env);
  1001. }
  1002. return $env;
  1003. }
  1004. function getEvalTestSettings($section_text,$testfile)
  1005. {
  1006. $rq = array();
  1007. // Any special environment settings
  1008. // these may overwrite the test defaults...
  1009. if ($section_text) {
  1010. $sect = $this->evalSettings($testfile,$section_text);
  1011. //print "data evaled:\n$sect\n";
  1012. settings2array(preg_split( "/[\n\r]+/", $sect), $rq);
  1013. }
  1014. return $rq;
  1015. }
  1016. //
  1017. // Load the sections of the test file.
  1018. //
  1019. function getSectionText($file)
  1020. {
  1021. // Load the sections of the test file.
  1022. $section_text = array(
  1023. 'TEST' => '(unnamed test)',
  1024. 'SKIPIF' => '',
  1025. 'GET' => '',
  1026. 'ARGS' => '',
  1027. '_FILE' => $file,
  1028. '_DIR' => realpath(dirname($file)),
  1029. );
  1030. $fp = @fopen($file, "r")
  1031. or $this->error("Cannot open test file: $file");
  1032. $section = '';
  1033. while (!feof($fp)) {
  1034. $line = fgets($fp);
  1035. // Match the beginning of a section.
  1036. if (preg_match('/^--([A-Z]+)--/',$line,$r)) {
  1037. $section = $r[1];
  1038. $section_text[$section] = '';
  1039. continue;
  1040. }
  1041. // Add to the section text.
  1042. $section_text[$section] .= $line;
  1043. }
  1044. fclose($fp);
  1045. foreach ($section_text as $k=>$v) {
  1046. // for POST data ,we only want to trim the last new line!
  1047. if ($k == 'POST' && preg_match('/^(.*?)\r?\n$/Ds',$v,$matches)) {
  1048. $section_text[$k]=$matches[1];
  1049. } else {
  1050. $section_text[$k]=trim($v);
  1051. }
  1052. }
  1053. return $section_text;
  1054. }
  1055. //
  1056. // Check if test should be skipped.
  1057. //
  1058. function getSkipReason($file,&$section_text,$docgi=false)
  1059. {
  1060. // if the test uses POST or GET, and it's not the cgi
  1061. // executable, skip
  1062. if ($docgi && !$this->conf['TEST_WEB'] && !$this->test_executable_iscgi) {
  1063. $this->showstatus($section_text['TEST'], 'SKIPPED', 'CGI Test needs CGI Binary');
  1064. return "SKIPPED";
  1065. }
  1066. // if we're doing web testing, then we wont be able to set
  1067. // ini setting on the command line. be sure the executables
  1068. // ini settings are compatible with the test, or skip
  1069. if (($docgi || $this->conf['TEST_WEB']) &&
  1070. isset($section_text['INI']) && $section_text['INI']) {
  1071. $settings = $this->getINISettings($section_text);
  1072. foreach ($settings as $k=>$v) {
  1073. if (strcasecmp($v,'off')==0 || !$v) $v='';
  1074. $haveval = isset($this->inisettings[$k]['local_value']);
  1075. if ($k == 'include_path') {
  1076. // we only want to know that src directory
  1077. // is in the include path
  1078. if (strpos($this->inisettings[$k]['local_value'],$this->cwd))
  1079. continue;
  1080. }
  1081. if (($haveval && $this->inisettings[$k]['local_value'] != $v) || (!$haveval && $v)) {
  1082. $this->showstatus($section_text['TEST'], 'SKIPPED', "Test requires ini setting $k=[$v], not [".($haveval?$this->inisettings[$k]['local_value']:'')."]");
  1083. return "SKIPPED";
  1084. }
  1085. }
  1086. }
  1087. // now handle a SKIPIF section
  1088. if ($section_text['SKIPIF']) {
  1089. $output = trim($this->runscript($section_text['SKIPIF'],$this->test_executable_iscgi,realpath(dirname($file))),true);
  1090. if (!$output) return NULL;
  1091. if ($this->conf['TEST_PHP_DETAILED'] > 2)
  1092. print "SKIPIF: [$output]\n";
  1093. if (preg_match("/^skip/i", $output)){
  1094. $reason = (preg_match("/^skip\s*(.+)\$/", $output)) ? preg_replace("/^skip\s*(.+)\$/", "\\1", $output) : FALSE;
  1095. $this->showstatus($section_text['TEST'], 'SKIPPED', $reason);
  1096. return 'SKIPPED';
  1097. }
  1098. if (preg_match("/^info/i", $output)) {
  1099. $reason = (preg_match("/^info\s*(.+)\$/", $output)) ? preg_replace("/^info\s*(.+)\$/", "\\1", $output) : FALSE;
  1100. if ($reason) {
  1101. $tested .= " (info: $reason)";
  1102. }
  1103. }
  1104. }
  1105. return NULL;
  1106. }
  1107. //
  1108. // Run an individual test case.
  1109. //
  1110. function run_test($file)
  1111. {
  1112. if ($this->conf['TEST_PHP_DETAILED'])
  1113. $this->writemsg("\n=================\nTEST $file\n");
  1114. $section_text = $this->getSectionText($file);
  1115. if ($this->iswin32)
  1116. $shortname = str_replace($this->conf['TEST_BASE_PATH'].'\\', '', $file);
  1117. else
  1118. $shortname = str_replace($this->conf['TEST_BASE_PATH'].'/', '', $file);
  1119. $tested = $section_text['TEST']." [$shortname]";
  1120. if ($this->conf['TEST_WEB']) {
  1121. $tmp_file = preg_replace('/\.phpt$/','.'.$this->conf['TEST_WEB_EXT'],$file);
  1122. $uri = $this->conf['TEST_BASE_SCRIPT_NAME'].str_replace($this->conf['TEST_BASE_PATH'], '', $tmp_file);
  1123. $uri = str_replace('\\', '/', $uri);
  1124. } else {
  1125. $tmp_file = preg_replace('/\.phpt$/','.php',$file);
  1126. }
  1127. @unlink($tmp_file);
  1128. // unlink old test results
  1129. @unlink(preg_replace('/\.phpt$/','.diff',$file));
  1130. @unlink(preg_replace('/\.phpt$/','.log',$file));
  1131. @unlink(preg_replace('/\.phpt$/','.exp',$file));
  1132. @unlink(preg_replace('/\.phpt$/','.out',$file));
  1133. if (!$this->conf['TEST_WEB']) {
  1134. // Reset environment from any previous test.
  1135. $env = $this->getENVSettings($section_text,$tmp_file);
  1136. $ini_overwrites = $this->getINIParams($section_text);
  1137. }
  1138. // if this is a cgi test, prepare for it
  1139. $query_string = '';
  1140. $havepost = array_key_exists('POST', $section_text) && !empty($section_text['POST']);
  1141. // allow empty query_string requests
  1142. $haveget = array_key_exists('GET', $section_text) && !empty($section_text['GET']);
  1143. $do_cgi = array_key_exists('CGI', $section_text) || $haveget || $havepost;
  1144. $skipreason = $this->getSkipReason($file,$section_text,$do_cgi);
  1145. if ($skipreason == 'SKIPPED') {
  1146. return $skipreason;
  1147. }
  1148. // We've satisfied the preconditions - run the test!
  1149. file_put_contents($tmp_file,$section_text['FILE']);
  1150. $post = NULL;
  1151. $args = "";
  1152. $headers = array();
  1153. if ($this->conf['TEST_WEB']) {
  1154. $request = $this->getEvalTestSettings(@$section_text['REQUEST'],$tmp_file);
  1155. $headers = $this->getEvalTestSettings(@$section_text['HEADERS'],$tmp_file);
  1156. $method = isset($request['method'])?$request['method']:$havepost?'POST':'GET';
  1157. $query_string = $haveget?$section_text['GET']:'';
  1158. $options = array();
  1159. $options['method']=$method;
  1160. if (isset($this->conf['timeout'])) $options['timeout'] = $this->conf['timeout'];
  1161. if (isset($this->conf['proxy_host'])) $options['proxy_host'] = $this->conf['proxy_host'];
  1162. if (isset($this->conf['proxy_port'])) $options['proxy_port'] = $this->conf['proxy_port'];
  1163. if (isset($this->conf['proxy_user'])) $options['proxy_user'] = $this->conf['proxy_user'];
  1164. if (isset($this->conf['proxy_pass'])) $options['proxy_pass'] = $this->conf['proxy_pass'];
  1165. $post = $havepost?$section_text['POST']:NULL;
  1166. $url = $this->conf['TEST_SERVER_URL'];
  1167. if (isset($request['SCRIPT_NAME']))
  1168. $url .= $request['SCRIPT_NAME'];
  1169. else
  1170. $url .= $uri;
  1171. if (isset($request['PATH_INFO']))
  1172. $url .= $request['PATH_INFO'];
  1173. if (isset($request['FRAGMENT']))
  1174. $url .= '#'.$request['FRAGMENT'];
  1175. if (isset($request['QUERY_STRING']))
  1176. $query_string = $request['QUERY_STRING'];
  1177. if ($query_string)
  1178. $url .= '?'.$query_string;
  1179. if ($this->conf['TEST_PHP_DETAILED'])
  1180. $this->writemsg("\nURL = $url\n");
  1181. } else if ($do_cgi) {
  1182. $query_string = $haveget?$section_text['GET']:'';
  1183. if (!array_key_exists('GATEWAY_INTERFACE', $env))
  1184. $env['GATEWAY_INTERFACE']='CGI/1.1';
  1185. if (!array_key_exists('SERVER_SOFTWARE', $env))
  1186. $env['SERVER_SOFTWARE']='PHP Test Harness';
  1187. if (!array_key_exists('SERVER_SOFTWARE', $env))
  1188. $env['SERVER_NAME']='127.0.0.1';
  1189. if (!array_key_exists('REDIRECT_STATUS', $env))
  1190. $env['REDIRECT_STATUS']='200';
  1191. if (!array_key_exists('SERVER_NAME', $env))
  1192. $env['QUERY_STRING']=$query_string;
  1193. if (!array_key_exists('PATH_TRANSLATED', $env) &&
  1194. !array_key_exists('SCRIPT_FILENAME', $env)) {
  1195. $env['PATH_TRANSLATED']=$tmp_file;
  1196. $env['SCRIPT_FILENAME']=$tmp_file;
  1197. }
  1198. if (!array_key_exists('PATH_TRANSLATED', $env))
  1199. $env['PATH_TRANSLATED']='';
  1200. if (!array_key_exists('PATH_INFO', $env))
  1201. $env['PATH_INFO']='';
  1202. if (!array_key_exists('SCRIPT_NAME', $env))
  1203. $env['SCRIPT_NAME']='';
  1204. if (!array_key_exists('SCRIPT_FILENAME', $env))
  1205. $env['SCRIPT_FILENAME']='';
  1206. if (array_key_exists('POST', $section_text) && (!$haveget || !empty($section_text['POST']))) {
  1207. $post = $section_text['POST'];
  1208. $content_length = strlen($post);
  1209. if (!array_key_exists('REQUEST_METHOD', $env))
  1210. $env['REQUEST_METHOD']='POST';
  1211. if (!array_key_exists('CONTENT_TYPE', $env))
  1212. $env['CONTENT_TYPE']='application/x-www-form-urlencoded';
  1213. if (!array_key_exists('CONTENT_LENGTH', $env))
  1214. $env['CONTENT_LENGTH']=$content_length;
  1215. } else {
  1216. if (!array_key_exists('REQUEST_METHOD', $env))
  1217. $env['REQUEST_METHOD']='GET';
  1218. if (!array_key_exists('CONTENT_TYPE', $env))
  1219. $env['CONTENT_TYPE']='';
  1220. if (!array_key_exists('CONTENT_LENGTH', $env))
  1221. $env['CONTENT_LENGTH']='';
  1222. }
  1223. if ($this->conf['TEST_PHP_DETAILED'] > 1)
  1224. $this->writemsg("\nCONTENT_LENGTH = " . $env['CONTENT_LENGTH'] .
  1225. "\nCONTENT_TYPE = " . $env['CONTENT_TYPE'] .
  1226. "\nPATH_TRANSLATED = " . $env['PATH_TRANSLATED'] .
  1227. "\nPATH_INFO = " . $env['PATH_INFO'] .
  1228. "\nQUERY_STRING = " . $env['QUERY_STRING'] .
  1229. "\nREDIRECT_STATUS = " . $env['REDIRECT_STATUS'] .
  1230. "\nREQUEST_METHOD = " . $env['REQUEST_METHOD'] .
  1231. "\nSCRIPT_NAME = " . $env['SCRIPT_NAME'] .
  1232. "\nSCRIPT_FILENAME = " . $env['SCRIPT_FILENAME'] . "\n");
  1233. /* not cgi spec to put query string on command line,
  1234. but used by a couple tests to catch a security hole
  1235. in older php versions. At least IIS can be configured
  1236. to do this. */
  1237. $args = $env['QUERY_STRING'];
  1238. $args = "$ini_overwrites $tmp_file \"$args\" 2>&1";
  1239. } else {
  1240. $args = $section_text['ARGS'] ? $section_text['ARGS'] : '';
  1241. $args = "$ini_overwrites $tmp_file $args 2>&1";
  1242. }
  1243. if ($this->conf['TEST_WEB']) {
  1244. // we want headers also, so fopen
  1245. $r = new HTTPRequest($url,$headers,$options,$post);
  1246. //$out = preg_replace("/\r\n/","\n",$r->response);
  1247. $out = $r->response;
  1248. $headers = $r->response_headers;
  1249. //print $r->outgoing_payload."\n";
  1250. //print $r->incoming_payload."\n";
  1251. } else {
  1252. $out = execute($this->conf['TEST_PHP_EXECUTABLE'],$args,$post,$this->cwd,$env);
  1253. // if this is a cgi, remove the headers first
  1254. if ($this->test_executable_iscgi
  1255. && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) {
  1256. $out = $match[2];
  1257. $rh = preg_split("/[\n\r]+/",$match[1]);
  1258. $headers = array();
  1259. foreach ($rh as $line) {
  1260. if (strpos($line, ':')!==false) {
  1261. $line = explode(":", $line, 2);
  1262. $headers[trim($line[0])] = trim($line[1]);
  1263. }
  1264. }
  1265. }
  1266. }
  1267. if ($this->conf['TEST_PHP_DETAILED'] > 2) {
  1268. echo "HEADERS: ";
  1269. print_r($headers);
  1270. echo "OUTPUT: \n$out\n";
  1271. }
  1272. // Does the output match what is expected?
  1273. $output = trim($out);
  1274. $output = preg_replace('/\r\n/',"\n",$output);
  1275. $failed = FALSE;
  1276. if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) {
  1277. if (isset($section_text['EXPECTF'])) {
  1278. $wanted = $section_text['EXPECTF'];
  1279. } else {
  1280. $wanted = $section_text['EXPECTREGEX'];
  1281. }
  1282. $wanted_re = preg_replace('/\r\n/',"\n",$wanted);
  1283. if (isset($section_text['EXPECTF'])) {
  1284. // do preg_quote, but miss out any %r delimited sections
  1285. $temp = "";
  1286. $r = "%r";
  1287. $startOffset = 0;
  1288. $length = strlen($wanted_re);
  1289. while($startOffset < $length) {
  1290. $start = strpos($wanted_re, $r, $startOffset);
  1291. if ($start !== false) {
  1292. // we have found a start tag
  1293. $end = strpos($wanted_re, $r, $start+2);
  1294. if ($end === false) {
  1295. // unbalanced tag, ignore it.
  1296. $end = $start = $length;
  1297. }
  1298. } else {
  1299. // no more %r sections
  1300. $start = $end = $length;
  1301. }
  1302. // quote a non re portion of the string
  1303. $temp = $temp . preg_quote(substr($wanted_re, $startOffset, ($start - $startOffset)), '/');
  1304. // add the re unquoted.
  1305. if ($end > $start) {
  1306. $temp = $temp . '(' . substr($wanted_re, $start+2, ($end - $start-2)). ')';
  1307. }
  1308. $startOffset = $end + 2;
  1309. }
  1310. $wanted_re = $temp;
  1311. $wanted_re = str_replace(
  1312. array('%binary_string_optional%'),
  1313. 'string',
  1314. $wanted_re
  1315. );
  1316. $wanted_re = str_replace(
  1317. array('%unicode_string_optional%'),
  1318. 'string',
  1319. $wanted_re
  1320. );
  1321. $wanted_re = str_replace(
  1322. array('%unicode\|string%', '%string\|unicode%'),
  1323. 'string',
  1324. $wanted_re
  1325. );
  1326. $wanted_re = str_replace(
  1327. array('%u\|b%', '%b\|u%'),
  1328. '',
  1329. $wanted_re
  1330. );
  1331. // Stick to basics
  1332. $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re);
  1333. $wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re);
  1334. $wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re);
  1335. $wanted_re = str_replace('%a', '.+', $wanted_re);
  1336. $wanted_re = str_replace('%A', '.*', $wanted_re);
  1337. $wanted_re = str_replace('%w', '\s*', $wanted_re);
  1338. $wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re);
  1339. $wanted_re = str_replace('%d', '\d+', $wanted_re);
  1340. $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re);
  1341. $wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re);
  1342. $wanted_re = str_replace('%c', '.', $wanted_re);
  1343. // %f allows two points "-.0.0" but that is the best *simple* expression
  1344. }
  1345. /* DEBUG YOUR REGEX HERE
  1346. var_dump($wanted_re);
  1347. print(str_repeat('=', 80) . "\n");
  1348. var_dump($output);
  1349. */
  1350. $failed = !preg_match("/^$wanted_re\$/s", $output);
  1351. }
  1352. $skipexpect = false;
  1353. if (!$failed && $this->conf['TEST_WEB'] && isset($section_text['EXPECTHEADERS'])) {
  1354. $want = array();
  1355. $lines = preg_split("/[\n\r]+/",$section_text['EXPECTHEADERS']);
  1356. $wanted='';
  1357. foreach ($lines as $line) {
  1358. if (strpos($line, ':')!==false) {
  1359. $line = explode(":", $line, 2);
  1360. $want[trim($line[0])] = trim($line[1]);
  1361. $wanted .= trim($line[0]).': '.trim($line[1])."\n";
  1362. }
  1363. }
  1364. $output='';
  1365. foreach ($want as $k=>$v) {
  1366. $output .= "$k: {$headers[$k]}\n";
  1367. if (!isset($headers[$k]) || $headers[$k] != $v) {
  1368. $failed = TRUE;
  1369. }
  1370. }
  1371. // different servers may do different things on non-200 results
  1372. // for instance, IIS will deliver it's own error pages, so we
  1373. // cannot expect to match up the EXPECT section. We may however,
  1374. // want to match EXPECT on more than 200 results, so this may
  1375. // need to change later.
  1376. $skipexpect = isset($headers['Status']) && $headers['Status'] != 200;
  1377. }
  1378. if (!$failed && !$skipexpect && isset($section_text['EXPECT'])) {
  1379. $wanted = $section_text['EXPECT'];
  1380. $wanted = preg_replace('/\r\n/',"\n",$wanted);
  1381. $failed = (0 != strcmp($output,$wanted));
  1382. }
  1383. if (!$failed) {
  1384. @unlink($tmp_file);
  1385. $this->showstatus($tested, 'PASSED');
  1386. return 'PASSED';
  1387. }
  1388. // Test failed so we need to report details.
  1389. $this->showstatus($tested, 'FAILED');
  1390. $this->failed_tests[] = array(
  1391. 'name' => $file,
  1392. 'test_name' => $tested,
  1393. 'output' => preg_replace('/\.phpt$/','.log', $file),
  1394. 'diff' => preg_replace('/\.phpt$/','.diff', $file)
  1395. );
  1396. if ($this->conf['TEST_PHP_DETAILED'])
  1397. $this->writemsg(generate_diff($wanted,$output)."\n");
  1398. // write .exp
  1399. if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'E') !== FALSE) {
  1400. $logname = preg_replace('/\.phpt$/','.exp',$file);
  1401. file_put_contents($logname,$wanted);
  1402. }
  1403. // write .out
  1404. if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'O') !== FALSE) {
  1405. $logname = preg_replace('/\.phpt$/','.out',$file);
  1406. file_put_contents($logname,$output);
  1407. }
  1408. // write .diff
  1409. if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'D') !== FALSE) {
  1410. $logname = preg_replace('/\.phpt$/','.diff',$file);
  1411. file_put_contents($logname,generate_diff($wanted,$output));
  1412. }
  1413. // write .log
  1414. if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'L') !== FALSE) {
  1415. $logname = preg_replace('/\.phpt$/','.log',$file);
  1416. file_put_contents($logname,
  1417. "\n---- EXPECTED OUTPUT\n$wanted\n".
  1418. "---- ACTUAL OUTPUT\n$output\n".
  1419. "---- FAILED\n");
  1420. // display emacs/msvc error output
  1421. if (strpos($this->conf['TEST_PHP_LOG_FORMAT'],'C') !== FALSE) {
  1422. $this->error_report($file,$logname,$tested);
  1423. }
  1424. }
  1425. return 'FAILED';
  1426. }
  1427. //
  1428. // Write an error in a format recognizable to Emacs or MSVC.
  1429. //
  1430. function error_report($testname,$logname,$tested)
  1431. {
  1432. $testname = realpath($testname);
  1433. $logname = realpath($logname);
  1434. switch ($this->conf['TEST_PHP_ERROR_STYLE']) {
  1435. default:
  1436. case 'MSVC':
  1437. $this->writemsg($testname . "(1) : $tested\n");
  1438. $this->writemsg($logname . "(1) : $tested\n");
  1439. break;
  1440. case 'EMACS':
  1441. $this->writemsg($testname . ":1: $tested\n");
  1442. $this->writemsg($logname . ":1: $tested\n");
  1443. break;
  1444. }
  1445. }
  1446. function error($message)
  1447. {
  1448. $this->writemsg("ERROR: {$message}\n");
  1449. exit(1);
  1450. }
  1451. }
  1452. $test = new testHarness();
  1453. /*
  1454. * Local variables:
  1455. * tab-width: 4
  1456. * c-basic-offset: 4
  1457. * End:
  1458. * vim600: fdm=marker
  1459. * vim: noet sw=4 ts=4
  1460. */
  1461. ?>