63 'output_buffering=Off',
67 'report_zend_debug=0',
70 'error_prepend_string=',
71 'error_append_string=',
74 'xdebug.default_enable=0',
84 if (!defined(
'E_DEPRECATED')) {
85 define(
'E_DEPRECATED', 0);
87 if (!defined(
'E_STRICT')) {
88 define(
'E_STRICT', 0);
90 $excluded_error_reporting = E_DEPRECATED;
91 if (!defined(
'PHP_VERSION_ID') || PHP_VERSION_ID < 80400) {
92 $excluded_error_reporting |= E_STRICT;
94 $this->ini_overwrites[] =
'error_reporting=' . (E_ALL & ~$excluded_error_reporting);
95 if (!defined(
'PHP_VERSION_ID') || PHP_VERSION_ID < 80500) {
96 $this->ini_overwrites[] =
'report_memleaks=0';
98 if (is_null($logger)) {
99 require_once
'PEAR/Common.php';
102 $this->_logger = $logger;
105 $conf = &PEAR_Config::singleton();
106 $this->_php = $conf->get(
'php_bin');
120 $proc = proc_open($commandline, array(
121 0 => array(
'pipe',
'r'),
122 1 => array(
'pipe',
'w'),
123 2 => array(
'pipe',
'w')
124 ), $pipes,
null, $env, array(
'suppress_errors' =>
true));
130 if (is_string($stdin)) {
131 fwrite($pipes[0], $stdin);
140 $n = @stream_select($r, $w, $e, 60);
144 $data .=
"\n ** ERROR: process timed out **\n";
145 proc_terminate(
$proc);
146 return array(1234567890, $data);
148 $line = fread($pipes[1], 8192);
149 if (strlen($line) == 0) {
156 if (function_exists(
'proc_get_status')) {
157 $stat = proc_get_status(
$proc);
158 if ($stat[
'signaled']) {
159 $data .=
"\nTermsig=".$stat[
'stopsig'];
162 $code = proc_close(
$proc);
163 if (function_exists(
'proc_get_status')) {
164 $code = $stat[
'exitcode'];
166 return array($code, $data);
189 $split = preg_split(
'/[\s]|=/', $ini_string, -1, PREG_SPLIT_NO_EMPTY);
190 $key = $split[1][0] ==
'"' ? substr($split[1], 1) : $split[1];
191 $value = $split[2][strlen($split[2]) - 1] ==
'"' ? substr($split[2], 0, -1) : $split[2];
193 $array = array($key => array(
'operator' => $split[0],
'value' => $value));
199 foreach ($settings as $setting) {
200 if (strpos($setting,
'=') !==
false) {
201 $setting = explode(
'=', $setting, 2);
202 $name = trim(strtolower($setting[0]));
203 $value = trim($setting[1]);
204 $ini_settings[$name] = $value;
207 return $ini_settings;
213 foreach ($ini_settings as $name => $value) {
214 if (is_array($value)) {
215 $operator = $value[
'operator'];
216 $value = $value[
'value'];
220 $value = addslashes($value);
221 $settings .=
" $operator \"$name=$value\"";
228 $file = escapeshellarg($file);
229 $cmd = $php . $ini_settings .
' -f ' . $file;
236 if (!file_exists($file) && file_exists(getcwd() . DIRECTORY_SEPARATOR . $file)) {
237 $file = realpath(getcwd() . DIRECTORY_SEPARATOR . $file);
238 } elseif (file_exists($file)) {
239 $file = realpath($file);
243 if (isset($this->_logger)) {
244 $this->_logger->log(2,
'Running command "' . $cmd .
'"');
248 chdir(dirname($file));
266 function run($file, $ini_settings = array(), $test_number = 1)
270 if (empty($this->_options[
'cgi'])) {
273 if (
false !== $res && !(is_array($res) && in_array($res[0], array(-1, 127)))) {
274 $this->_options[
'cgi'] =
'php-cgi';
277 if (1 < $len = strlen($this->tests_count)) {
278 $test_number = str_pad($test_number, $len,
' ', STR_PAD_LEFT);
279 $test_nr =
"[$test_number/$this->tests_count] ";
284 $file = realpath($file);
286 if (PEAR::isError($section_text)) {
287 return $section_text;
290 if (isset($section_text[
'POST_RAW']) && isset($section_text[
'UPLOAD'])) {
291 return PEAR::raiseError(
"Cannot contain both POST_RAW and UPLOAD in test file: $file");
297 if (!empty($this->_options[
'ini'])) {
298 $pass_options = $this->_options[
'ini'];
301 if (is_string($ini_settings)) {
305 $ini_settings = $this->
settings2array($this->ini_overwrites, $ini_settings);
306 if ($section_text[
'INI']) {
307 if (strpos($section_text[
'INI'],
'{PWD}') !==
false) {
308 $section_text[
'INI'] = str_replace(
'{PWD}', dirname($file), $section_text[
'INI']);
310 $ini = preg_split(
"/[\n\r]+/", $section_text[
'INI']);
314 $shortname = str_replace($cwd . DIRECTORY_SEPARATOR,
'', $file);
316 $tested = trim($section_text[
'TEST']);
317 $tested.= !isset($this->_options[
'simple']) ?
"[$shortname]" :
' ';
319 if (!empty($section_text[
'POST']) || !empty($section_text[
'POST_RAW']) ||
320 !empty($section_text[
'UPLOAD']) || !empty($section_text[
'GET']) ||
321 !empty($section_text[
'COOKIE']) || !empty($section_text[
'EXPECTHEADERS'])) {
322 if (empty($this->_options[
'cgi'])) {
323 if (!isset($this->_options[
'quiet'])) {
324 $this->_logger->log(0,
"SKIP $test_nr$tested (reason: --cgi option needed for this test, type 'pear help run-tests')");
326 if (isset($this->_options[
'tapoutput'])) {
327 return array(
'ok',
' # skip --cgi option needed for this test, "pear help run-tests" for info');
332 $this->_php = $this->_options[
'cgi'];
335 $temp_dir = realpath(dirname($file));
336 $main_file_name = basename($file,
'phpt');
337 $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.
'diff';
338 $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.
'log';
339 $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.
'exp';
340 $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.
'out';
341 $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.
'mem';
342 $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.
'php';
343 $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.
'skip.php';
344 $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.
'clean.php';
345 $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid(
'phpt.');
351 $res = $this->
_runSkipIf($section_text, $temp_skipif, $tested, $ini_settings);
352 if ($res ==
'SKIPPED' || count($res) != 2) {
355 $info = $res[
'info'];
356 $warn = $res[
'warn'];
359 if (isset($this->_options[
'coverage']) && $this->xdebug_loaded) {
360 $xdebug_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name .
'xdebug';
361 $text =
"\n" .
'function coverage_shutdown() {' .
362 "\n" .
' $xdebug = var_export(xdebug_get_code_coverage(), true);';
363 if (!function_exists(
'file_put_contents')) {
364 $text .=
"\n" .
' $fh = fopen(\'' . $xdebug_file .
'\',
"wb");
' .
365 "\n" . ' if ($fh !==
false) {
' .
366 "\n" . ' fwrite($fh, $xdebug);
' .
367 "\n" . ' fclose($fh);
' .
370 $text .= "\n" . ' file_put_contents(\
'' . $xdebug_file .
'\', $xdebug);
';
373 // Workaround for http://pear.php.net/bugs/bug.php?id=17292
374 $lines = explode("\n", $section_text['FILE
']);
375 $numLines = count($lines);
377 $coverage_shutdown = 'coverage_shutdown
';
380 substr($lines[0], 0, 2) == '<?
' ||
381 substr($lines[0], 0, 5) == '<?php
'
387 for ($i = 0; $i < $numLines; $i++) {
388 if (isset($lines[$i]) && substr($lines[$i], 0, 9) == 'namespace') {
389 $namespace = substr($lines[$i], 10, -1);
390 $coverage_shutdown = $namespace . '\\coverage_shutdown
';
391 $namespace = "namespace " . $namespace . ";\n";
398 $text .= "\n xdebug_stop_code_coverage();" .
400 "\n\n" .
'register_shutdown_function("' . $coverage_shutdown .
'");';
401 $text .=
"\n" .
'xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);' .
"\n";
403 $this->
save_text($temp_file,
"<?php\n" . $namespace . $text .
"\n" . implode(
"\n", $lines));
405 $this->
save_text($temp_file, $section_text[
'FILE']);
408 $args = $section_text[
'ARGS'] ?
' -- '.$section_text[
'ARGS'] :
'';
409 $cmd = $this->
_preparePhpBin($this->_php, $temp_file, $ini_settings);
411 if (isset($this->_logger)) {
412 $this->_logger->log(2,
'Running command "' . $cmd .
'"');
416 $env = $this->
_resetEnv($section_text, $temp_file);
419 if (PEAR::isError($section_text)) {
420 return $section_text;
423 if (array_key_exists(
'POST_RAW', $section_text) && !empty($section_text[
'POST_RAW'])) {
424 $post = trim($section_text[
'POST_RAW']);
425 $raw_lines = explode(
"\n", $post);
429 foreach ($raw_lines as $i => $line) {
430 if (empty($env[
'CONTENT_TYPE']) &&
431 preg_match(
'/^Content-Type:(.*)/i', $line, $res)) {
432 $env[
'CONTENT_TYPE'] = trim(str_replace(
"\r",
'', $res[1]));
442 $env[
'CONTENT_LENGTH'] = strlen($request);
443 $env[
'REQUEST_METHOD'] =
'POST';
446 $cmd =
"$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post";
447 } elseif (array_key_exists(
'POST', $section_text) && !empty($section_text[
'POST'])) {
448 $post = trim($section_text[
'POST']);
450 $content_length = strlen($post);
452 $env[
'REQUEST_METHOD'] =
'POST';
453 $env[
'CONTENT_TYPE'] =
'application/x-www-form-urlencoded';
454 $env[
'CONTENT_LENGTH'] = $content_length;
456 $cmd =
"$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post";
458 $env[
'REQUEST_METHOD'] =
'GET';
459 $env[
'CONTENT_TYPE'] =
'';
460 $env[
'CONTENT_LENGTH'] =
'';
463 if (OS_WINDOWS && isset($section_text[
'RETURNS'])) {
465 system($cmd, $return_value);
466 $out = ob_get_contents();
468 $section_text[
'RETURNS'] = (int) trim($section_text[
'RETURNS']);
469 $returnfail = ($return_value != $section_text[
'RETURNS']);
472 $stdin = isset($section_text[
'STDIN']) ? $section_text[
'STDIN'] :
null;
474 $return_value = $out[0];
478 $output = preg_replace(
'/\r\n/',
"\n", trim($out));
480 if (isset($tmp_post) && realpath($tmp_post) && file_exists($tmp_post)) {
481 @unlink(realpath($tmp_post));
488 if (isset($section_text[
'EXPECTHEADERS'])) {
490 $missing = array_diff_assoc($testheaders, $this->_headers);
492 foreach ($missing as $header => $value) {
493 if (isset($this->_headers[$header])) {
494 $changed .=
"-$header: $value\n+$header: ";
495 $changed .= $this->_headers[$header];
497 $changed .=
"-$header: $value\n";
502 $output .=
"\n====EXPECTHEADERS FAILURE====:\n$changed";
510 if (isset($section_text[
'EXPECTF']) || isset($section_text[
'EXPECTREGEX'])) {
511 if (isset($section_text[
'EXPECTF'])) {
512 $wanted = trim($section_text[
'EXPECTF']);
514 $wanted = trim($section_text[
'EXPECTREGEX']);
516 $wanted_re = preg_replace(
'/\r\n/',
"\n", $wanted);
517 if (isset($section_text[
'EXPECTF'])) {
518 $wanted_re = preg_quote($wanted_re,
'/');
520 $wanted_re = str_replace(
"%s",
".+?", $wanted_re);
521 $wanted_re = str_replace(
"%S",
".*?", $wanted_re);
522 $wanted_re = str_replace(
"%i",
"[+\-]?[0-9]+", $wanted_re);
523 $wanted_re = str_replace(
"%d",
"[0-9]+", $wanted_re);
524 $wanted_re = str_replace(
"%x",
"[0-9a-fA-F]+", $wanted_re);
525 $wanted_re = str_replace(
"%f",
"[+\-]?\.?[0-9]+\.?[0-9]*(E-?[0-9]+)?", $wanted_re);
526 $wanted_re = str_replace(
"%c",
".", $wanted_re);
535 if (!$returnfail && preg_match(
"/^$wanted_re\$/s", $output)) {
536 if (file_exists($temp_file)) {
539 if (array_key_exists(
'FAIL', $section_text)) {
542 if (!isset($this->_options[
'quiet'])) {
543 $this->_logger->log(0,
"PASS $test_nr$tested$info");
545 if (isset($this->_options[
'tapoutput'])) {
546 return array(
'ok',
' - ' . $tested);
551 if (isset($section_text[
'EXPECTFILE'])) {
552 $f = $temp_dir .
'/' . trim($section_text[
'EXPECTFILE']);
553 if (!($fp = @fopen($f,
'rb'))) {
554 return PEAR::raiseError(
'--EXPECTFILE-- section file ' .
558 $section_text[
'EXPECT'] = file_get_contents($f);
561 if (isset($section_text[
'EXPECT'])) {
562 $wanted = preg_replace(
'/\r\n/',
"\n", trim($section_text[
'EXPECT']));
568 if (!$returnfail && 0 == strcmp($output, $wanted)) {
569 if (file_exists($temp_file)) {
572 if (array_key_exists(
'FAIL', $section_text)) {
575 if (!isset($this->_options[
'quiet'])) {
576 $this->_logger->log(0,
"PASS $test_nr$tested$info");
578 if (isset($this->_options[
'tapoutput'])) {
579 return array(
'ok',
' - ' . $tested);
586 if (array_key_exists(
'FAIL', $section_text)) {
589 $expectf = isset($section_text[
'EXPECTF']) ? $wanted_re :
null;
590 $faildiff = $this->
generate_diff($wanted, $output,
null, $expectf);
591 $faildiff = preg_replace(
'/\r/',
'', $faildiff);
592 $wanted = preg_replace(
'/\r/',
'', trim($section_text[
'FAIL']));
593 if ($faildiff == $wanted) {
594 if (!isset($this->_options[
'quiet'])) {
595 $this->_logger->log(0,
"PASS $test_nr$tested$info");
597 if (isset($this->_options[
'tapoutput'])) {
598 return array(
'ok',
' - ' . $tested);
602 unset($section_text[
'EXPECTF']);
604 if (isset($section_text[
'RETURNS'])) {
605 return PEAR::raiseError(
'Cannot have both RETURNS and FAIL in the same test: ' .
611 $txt = $warn ?
'WARN ' :
'FAIL ';
612 $this->_logger->log(0, $txt . $test_nr . $tested . $info);
615 $res = $this->
_writeLog($exp_filename, $wanted);
616 if (PEAR::isError($res)) {
621 $res = $this->
_writeLog($output_filename, $output);
622 if (PEAR::isError($res)) {
627 $returns = isset($section_text[
'RETURNS']) ?
628 array(trim($section_text[
'RETURNS']), $return_value) :
null;
629 $expectf = isset($section_text[
'EXPECTF']) ? $wanted_re :
null;
630 $data = $this->
generate_diff($wanted, $output, $returns, $expectf);
631 $res = $this->
_writeLog($diff_filename, $data);
632 if (isset($this->_options[
'showdiff'])) {
633 $this->_logger->log(0,
"========DIFF========");
634 $this->_logger->log(0, $data);
635 $this->_logger->log(0,
"========DONE========");
637 if (PEAR::isError($res)) {
653$section_text[RETURNS]
659 $res = $this->
_writeLog($log_filename, $data);
660 if (PEAR::isError($res)) {
664 if (isset($this->_options[
'tapoutput'])) {
665 $wanted = explode(
"\n", $wanted);
666 $wanted =
"# Expected output:\n#\n#" . implode(
"\n#", $wanted);
667 $output = explode(
"\n", $output);
668 $output =
"#\n#\n# Actual output:\n#\n#" . implode(
"\n#", $output);
669 return array($wanted . $output .
'not ok',
' - ' . $tested);
671 return $warn ?
'WARNED' :
'FAILED';
900 if (array_key_exists(
'UPLOAD', $section_text) && !empty($section_text[
'UPLOAD'])) {
901 $upload_files = trim($section_text[
'UPLOAD']);
902 $upload_files = explode(
"\n", $upload_files);
904 $request =
"Content-Type: multipart/form-data; boundary=---------------------------20896060251896012921717172737\n" .
905 "-----------------------------20896060251896012921717172737\n";
906 foreach ($upload_files as $fileinfo) {
907 $fileinfo = explode(
'=', $fileinfo);
908 if (count($fileinfo) != 2) {
909 return PEAR::raiseError(
"Invalid UPLOAD section in test file: $file");
911 if (!realpath(dirname($file) .
'/' . $fileinfo[1])) {
912 return PEAR::raiseError(
"File for upload does not exist: $fileinfo[1] " .
913 "in test file: $file");
915 $file_contents = file_get_contents(dirname($file) .
'/' . $fileinfo[1]);
916 $fileinfo[1] = basename($fileinfo[1]);
917 $request .=
"Content-Disposition: form-data; name=\"$fileinfo[0]\"; filename=\"$fileinfo[1]\"\n";
918 $request .=
"Content-Type: text/plain\n\n";
919 $request .= $file_contents .
"\n" .
920 "-----------------------------20896060251896012921717172737\n";
923 if (array_key_exists(
'POST', $section_text) && !empty($section_text[
'POST'])) {
925 $post = trim($section_text[
'POST']);
926 $post = explode(
'&', $post);
927 foreach ($post as $i => $post_info) {
928 $post_info = explode(
'=', $post_info);
929 if (count($post_info) != 2) {
930 return PEAR::raiseError(
"Invalid POST data in test file: $file");
932 $post_info[0] = rawurldecode($post_info[0]);
933 $post_info[1] = rawurldecode($post_info[1]);
934 $post[$i] = $post_info;
936 foreach ($post as $post_info) {
937 $request .=
"Content-Disposition: form-data; name=\"$post_info[0]\"\n\n";
938 $request .= $post_info[1] .
"\n" .
939 "-----------------------------20896060251896012921717172737\n";
941 unset($section_text[
'POST']);
943 $section_text[
'POST_RAW'] = $request;
946 return $section_text;