Subversion Repositories Applications.gtt

Rev

Rev 94 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
94 jpm 1
<?php
2
/**
3
 * PEAR_RunTest
4
 *
5
 * PHP versions 4 and 5
6
 *
7
 * @category   pear
8
 * @package    PEAR
9
 * @author     Tomas V.V.Cox <cox@idecnet.com>
10
 * @author     Greg Beaver <cellog@php.net>
187 mathias 11
 * @copyright  1997-2009 The Authors
12
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
94 jpm 13
 * @link       http://pear.php.net/package/PEAR
14
 * @since      File available since Release 1.3.3
15
 */
16
 
17
/**
18
 * for error handling
19
 */
20
require_once 'PEAR.php';
21
require_once 'PEAR/Config.php';
22
 
23
define('DETAILED', 1);
24
putenv("PHP_PEAR_RUNTESTS=1");
25
 
26
/**
27
 * Simplified version of PHP's test suite
28
 *
29
 * Try it with:
30
 *
31
 * $ php -r 'include "../PEAR/RunTest.php"; $t=new PEAR_RunTest; $o=$t->run("./pear_system.phpt");print_r($o);'
32
 *
33
 *
34
 * @category   pear
35
 * @package    PEAR
36
 * @author     Tomas V.V.Cox <cox@idecnet.com>
37
 * @author     Greg Beaver <cellog@php.net>
187 mathias 38
 * @copyright  1997-2009 The Authors
39
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
40
 * @version    Release: 1.10.1
94 jpm 41
 * @link       http://pear.php.net/package/PEAR
42
 * @since      Class available since Release 1.3.3
43
 */
44
class PEAR_RunTest
45
{
187 mathias 46
    var $_headers = array();
94 jpm 47
    var $_logger;
48
    var $_options;
187 mathias 49
    var $_php;
50
    var $tests_count;
51
    var $xdebug_loaded;
52
    /**
53
     * Saved value of php executable, used to reset $_php when we
54
     * have a test that uses cgi
55
     *
56
     * @var unknown_type
57
     */
58
    var $_savephp;
94 jpm 59
    var $ini_overwrites = array(
60
        'output_handler=',
61
        'open_basedir=',
62
        'disable_functions=',
63
        'output_buffering=Off',
64
        'display_errors=1',
65
        'log_errors=0',
66
        'html_errors=0',
67
        'track_errors=1',
187 mathias 68
        'report_memleaks=0',
94 jpm 69
        'report_zend_debug=0',
70
        'docref_root=',
71
        'docref_ext=.html',
72
        'error_prepend_string=',
73
        'error_append_string=',
74
        'auto_prepend_file=',
75
        'auto_append_file=',
187 mathias 76
        'xdebug.default_enable=0',
77
        'allow_url_fopen=1',
94 jpm 78
    );
79
 
80
    /**
81
     * An object that supports the PEAR_Common->log() signature, or null
82
     * @param PEAR_Common|null
83
     */
187 mathias 84
    function __construct($logger = null, $options = array())
94 jpm 85
    {
187 mathias 86
        if (!defined('E_DEPRECATED')) {
87
            define('E_DEPRECATED', 0);
88
        }
89
        if (!defined('E_STRICT')) {
90
            define('E_STRICT', 0);
91
        }
92
        $this->ini_overwrites[] = 'error_reporting=' . (E_ALL & ~(E_DEPRECATED | E_STRICT));
94 jpm 93
        if (is_null($logger)) {
94
            require_once 'PEAR/Common.php';
95
            $logger = new PEAR_Common;
96
        }
187 mathias 97
        $this->_logger  = $logger;
94 jpm 98
        $this->_options = $options;
187 mathias 99
 
100
        $conf = &PEAR_Config::singleton();
101
        $this->_php = $conf->get('php_bin');
94 jpm 102
    }
103
 
104
    /**
105
     * Taken from php-src/run-tests.php
106
     *
107
     * @param string $commandline command name
108
     * @param array $env
109
     * @param string $stdin standard input to pass to the command
110
     * @return unknown
111
     */
112
    function system_with_timeout($commandline, $env = null, $stdin = null)
113
    {
114
        $data = '';
187 mathias 115
        $proc = proc_open($commandline, array(
116
 
117
            1 => array('pipe', 'w'),
118
            2 => array('pipe', 'w')
119
        ), $pipes, null, $env, array('suppress_errors' => true));
94 jpm 120
 
121
        if (!$proc) {
122
            return false;
123
        }
187 mathias 124
 
94 jpm 125
        if (is_string($stdin)) {
126
            fwrite($pipes[0], $stdin);
127
        }
128
        fclose($pipes[0]);
187 mathias 129
 
94 jpm 130
        while (true) {
131
            /* hide errors from interrupted syscalls */
132
            $r = $pipes;
187 mathias 133
            $e = $w = null;
94 jpm 134
            $n = @stream_select($r, $w, $e, 60);
187 mathias 135
 
94 jpm 136
            if ($n === 0) {
137
                /* timed out */
138
                $data .= "\n ** ERROR: process timed out **\n";
139
                proc_terminate($proc);
140
                return array(1234567890, $data);
141
            } else if ($n > 0) {
142
                $line = fread($pipes[1], 8192);
143
                if (strlen($line) == 0) {
144
                    /* EOF */
145
                    break;
146
                }
147
                $data .= $line;
148
            }
149
        }
150
        if (function_exists('proc_get_status')) {
151
            $stat = proc_get_status($proc);
152
            if ($stat['signaled']) {
153
                $data .= "\nTermsig=".$stat['stopsig'];
154
            }
155
        }
156
        $code = proc_close($proc);
157
        if (function_exists('proc_get_status')) {
158
            $code = $stat['exitcode'];
159
        }
160
        return array($code, $data);
161
    }
162
 
187 mathias 163
    /**
164
     * Turns a PHP INI string into an array
165
     *
166
     * Turns -d "include_path=/foo/bar" into this:
167
     * array(
168
     *   'include_path' => array(
169
     *          'operator' => '-d',
170
     *          'value'    => '/foo/bar',
171
     *   )
172
     * )
173
     * Works both with quotes and without
174
     *
175
     * @param string an PHP INI string, -d "include_path=/foo/bar"
176
     * @return array
177
     */
178
    function iniString2array($ini_string)
179
    {
180
        if (!$ini_string) {
181
            return array();
182
        }
183
        $split = preg_split('/[\s]|=/', $ini_string, -1, PREG_SPLIT_NO_EMPTY);
184
        $key   = $split[1][0] == '"'                     ? substr($split[1], 1)     : $split[1];
185
        $value = $split[2][strlen($split[2]) - 1] == '"' ? substr($split[2], 0, -1) : $split[2];
186
        // FIXME review if this is really the struct to go with
187
        $array = array($key => array('operator' => $split[0], 'value' => $value));
188
        return $array;
189
    }
190
 
94 jpm 191
    function settings2array($settings, $ini_settings)
192
    {
193
        foreach ($settings as $setting) {
187 mathias 194
            if (strpos($setting, '=') !== false) {
195
                $setting = explode('=', $setting, 2);
196
                $name  = trim(strtolower($setting[0]));
94 jpm 197
                $value = trim($setting[1]);
198
                $ini_settings[$name] = $value;
199
            }
200
        }
201
        return $ini_settings;
202
    }
187 mathias 203
 
94 jpm 204
    function settings2params($ini_settings)
205
    {
206
        $settings = '';
207
        foreach ($ini_settings as $name => $value) {
187 mathias 208
            if (is_array($value)) {
209
                $operator = $value['operator'];
210
                $value    = $value['value'];
211
            } else {
212
                $operator = '-d';
213
            }
94 jpm 214
            $value = addslashes($value);
187 mathias 215
            $settings .= " $operator \"$name=$value\"";
94 jpm 216
        }
217
        return $settings;
218
    }
219
 
187 mathias 220
    function _preparePhpBin($php, $file, $ini_settings)
221
    {
222
        $file = escapeshellarg($file);
223
        $cmd = $php . $ini_settings . ' -f ' . $file;
94 jpm 224
 
187 mathias 225
        return $cmd;
226
    }
227
 
228
    function runPHPUnit($file, $ini_settings = '')
94 jpm 229
    {
187 mathias 230
        if (!file_exists($file) && file_exists(getcwd() . DIRECTORY_SEPARATOR . $file)) {
231
            $file = realpath(getcwd() . DIRECTORY_SEPARATOR . $file);
232
        } elseif (file_exists($file)) {
233
            $file = realpath($file);
94 jpm 234
        }
187 mathias 235
 
236
        $cmd = $this->_preparePhpBin($this->_php, $file, $ini_settings);
237
        if (isset($this->_logger)) {
238
            $this->_logger->log(2, 'Running command "' . $cmd . '"');
94 jpm 239
        }
240
 
187 mathias 241
        $savedir = getcwd(); // in case the test moves us around
242
        chdir(dirname($file));
243
        echo `$cmd`;
244
        chdir($savedir);
245
        return 'PASSED'; // we have no way of knowing this information so assume passing
246
    }
94 jpm 247
 
187 mathias 248
    /**
249
     * Runs an individual test case.
250
     *
251
     * @param string       The filename of the test
252
     * @param array|string INI settings to be applied to the test run
253
     * @param integer      Number what the current running test is of the
254
     *                     whole test suite being runned.
255
     *
256
     * @return string|object Returns PASSED, WARNED, FAILED depending on how the
257
     *                       test came out.
258
     *                       PEAR Error when the tester it self fails
259
     */
260
    function run($file, $ini_settings = array(), $test_number = 1)
261
    {
262
        $this->_restorePHPBinary();
94 jpm 263
 
187 mathias 264
        if (empty($this->_options['cgi'])) {
265
            // try to see if php-cgi is in the path
266
            $res = $this->system_with_timeout('php-cgi -v');
267
            if (false !== $res && !(is_array($res) && in_array($res[0], array(-1, 127)))) {
268
                $this->_options['cgi'] = 'php-cgi';
269
            }
270
        }
271
        if (1 < $len = strlen($this->tests_count)) {
272
            $test_number = str_pad($test_number, $len, ' ', STR_PAD_LEFT);
273
            $test_nr = "[$test_number/$this->tests_count] ";
274
        } else {
275
            $test_nr = '';
276
        }
277
 
94 jpm 278
        $file = realpath($file);
187 mathias 279
        $section_text = $this->_readFile($file);
280
        if (PEAR::isError($section_text)) {
281
            return $section_text;
94 jpm 282
        }
283
 
187 mathias 284
        if (isset($section_text['POST_RAW']) && isset($section_text['UPLOAD'])) {
285
            return PEAR::raiseError("Cannot contain both POST_RAW and UPLOAD in test file: $file");
286
        }
94 jpm 287
 
187 mathias 288
        $cwd = getcwd();
94 jpm 289
 
187 mathias 290
        $pass_options = '';
291
        if (!empty($this->_options['ini'])) {
292
            $pass_options = $this->_options['ini'];
94 jpm 293
        }
294
 
187 mathias 295
        if (is_string($ini_settings)) {
296
            $ini_settings = $this->iniString2array($ini_settings);
94 jpm 297
        }
187 mathias 298
 
94 jpm 299
        $ini_settings = $this->settings2array($this->ini_overwrites, $ini_settings);
300
        if ($section_text['INI']) {
301
            if (strpos($section_text['INI'], '{PWD}') !== false) {
302
                $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']);
303
            }
187 mathias 304
            $ini = preg_split( "/[\n\r]+/", $section_text['INI']);
305
            $ini_settings = $this->settings2array($ini, $ini_settings);
94 jpm 306
        }
307
        $ini_settings = $this->settings2params($ini_settings);
308
        $shortname = str_replace($cwd . DIRECTORY_SEPARATOR, '', $file);
187 mathias 309
 
310
        $tested = trim($section_text['TEST']);
311
        $tested.= !isset($this->_options['simple']) ? "[$shortname]" : ' ';
312
 
313
        if (!empty($section_text['POST']) || !empty($section_text['POST_RAW']) ||
314
              !empty($section_text['UPLOAD']) || !empty($section_text['GET']) ||
315
              !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) {
94 jpm 316
            if (empty($this->_options['cgi'])) {
317
                if (!isset($this->_options['quiet'])) {
187 mathias 318
                    $this->_logger->log(0, "SKIP $test_nr$tested (reason: --cgi option needed for this test, type 'pear help run-tests')");
94 jpm 319
                }
320
                if (isset($this->_options['tapoutput'])) {
321
                    return array('ok', ' # skip --cgi option needed for this test, "pear help run-tests" for info');
322
                }
323
                return 'SKIPPED';
324
            }
187 mathias 325
            $this->_savePHPBinary();
326
            $this->_php = $this->_options['cgi'];
94 jpm 327
        }
328
 
187 mathias 329
        $temp_dir = realpath(dirname($file));
330
        $main_file_name = basename($file, 'phpt');
331
        $diff_filename     = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'diff';
332
        $log_filename      = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'log';
333
        $exp_filename      = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'exp';
334
        $output_filename   = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'out';
335
        $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'mem';
336
        $temp_file         = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'php';
337
        $temp_skipif       = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'skip.php';
338
        $temp_clean        = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name.'clean.php';
339
        $tmp_post          = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.');
94 jpm 340
 
187 mathias 341
        // unlink old test results
342
        $this->_cleanupOldFiles($file);
94 jpm 343
 
344
        // Check if test should be skipped.
187 mathias 345
        $res  = $this->_runSkipIf($section_text, $temp_skipif, $tested, $ini_settings);
346
        if (count($res) != 2) {
347
            return $res;
94 jpm 348
        }
187 mathias 349
        $info = $res['info'];
350
        $warn = $res['warn'];
94 jpm 351
 
352
        // We've satisfied the preconditions - run the test!
187 mathias 353
        if (isset($this->_options['coverage']) && $this->xdebug_loaded) {
354
            $xdebug_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'xdebug';
355
            $text = "\n" . 'function coverage_shutdown() {' .
356
                    "\n" . '    $xdebug = var_export(xdebug_get_code_coverage(), true);';
357
            if (!function_exists('file_put_contents')) {
358
                $text .= "\n" . '    $fh = fopen(\'' . $xdebug_file . '\', "wb");' .
359
                        "\n" . '    if ($fh !== false) {' .
360
                        "\n" . '        fwrite($fh, $xdebug);' .
361
                        "\n" . '        fclose($fh);' .
362
                        "\n" . '    }';
363
            } else {
364
                $text .= "\n" . '    file_put_contents(\'' . $xdebug_file . '\', $xdebug);';
365
            }
94 jpm 366
 
187 mathias 367
            // Workaround for http://pear.php.net/bugs/bug.php?id=17292
368
            $lines             = explode("\n", $section_text['FILE']);
369
            $numLines          = count($lines);
370
            $namespace         = '';
371
            $coverage_shutdown = 'coverage_shutdown';
94 jpm 372
 
187 mathias 373
            if (
374
                substr($lines[0], 0, 2) == '<?' ||
375
                substr($lines[0], 0, 5) == '<?php'
376
            ) {
377
                unset($lines[0]);
378
            }
94 jpm 379
 
187 mathias 380
 
381
            for ($i = 0; $i < $numLines; $i++) {
382
                if (isset($lines[$i]) && substr($lines[$i], 0, 9) == 'namespace') {
383
                    $namespace         = substr($lines[$i], 10, -1);
384
                    $coverage_shutdown = $namespace . '\\coverage_shutdown';
385
                    $namespace         = "namespace " . $namespace . ";\n";
386
 
387
                    unset($lines[$i]);
388
                    break;
94 jpm 389
                }
390
            }
187 mathias 391
 
392
            $text .= "\n    xdebug_stop_code_coverage();" .
393
                "\n" . '} // end coverage_shutdown()' .
394
                "\n\n" . 'register_shutdown_function("' . $coverage_shutdown . '");';
395
            $text .= "\n" . 'xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);' . "\n";
396
 
397
            $this->save_text($temp_file, "<?php\n" . $namespace . $text  . "\n" . implode("\n", $lines));
94 jpm 398
        } else {
187 mathias 399
            $this->save_text($temp_file, $section_text['FILE']);
94 jpm 400
        }
187 mathias 401
 
402
        $args = $section_text['ARGS'] ? ' -- '.$section_text['ARGS'] : '';
403
        $cmd = $this->_preparePhpBin($this->_php, $temp_file, $ini_settings);
404
        $cmd.= "$args 2>&1";
405
        if (isset($this->_logger)) {
406
            $this->_logger->log(2, 'Running command "' . $cmd . '"');
94 jpm 407
        }
408
 
187 mathias 409
        // Reset environment from any previous test.
410
        $env = $this->_resetEnv($section_text, $temp_file);
411
 
412
        $section_text = $this->_processUpload($section_text, $file);
413
        if (PEAR::isError($section_text)) {
414
            return $section_text;
94 jpm 415
        }
187 mathias 416
 
94 jpm 417
        if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) {
418
            $post = trim($section_text['POST_RAW']);
419
            $raw_lines = explode("\n", $post);
187 mathias 420
 
94 jpm 421
            $request = '';
422
            $started = false;
423
            foreach ($raw_lines as $i => $line) {
424
                if (empty($env['CONTENT_TYPE']) &&
187 mathias 425
                    preg_match('/^Content-Type:(.*)/i', $line, $res)) {
94 jpm 426
                    $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1]));
427
                    continue;
428
                }
429
                if ($started) {
430
                    $request .= "\n";
431
                }
432
                $started = true;
433
                $request .= $line;
434
            }
187 mathias 435
 
94 jpm 436
            $env['CONTENT_LENGTH'] = strlen($request);
437
            $env['REQUEST_METHOD'] = 'POST';
438
 
439
            $this->save_text($tmp_post, $request);
187 mathias 440
            $cmd = "$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post";
94 jpm 441
        } elseif (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) {
442
            $post = trim($section_text['POST']);
443
            $this->save_text($tmp_post, $post);
444
            $content_length = strlen($post);
187 mathias 445
 
94 jpm 446
            $env['REQUEST_METHOD'] = 'POST';
447
            $env['CONTENT_TYPE']   = 'application/x-www-form-urlencoded';
448
            $env['CONTENT_LENGTH'] = $content_length;
187 mathias 449
 
450
            $cmd = "$this->_php$pass_options$ini_settings \"$temp_file\" 2>&1 < $tmp_post";
94 jpm 451
        } else {
452
            $env['REQUEST_METHOD'] = 'GET';
453
            $env['CONTENT_TYPE']   = '';
454
            $env['CONTENT_LENGTH'] = '';
455
        }
187 mathias 456
 
94 jpm 457
        if (OS_WINDOWS && isset($section_text['RETURNS'])) {
458
            ob_start();
459
            system($cmd, $return_value);
460
            $out = ob_get_contents();
461
            ob_end_clean();
462
            $section_text['RETURNS'] = (int) trim($section_text['RETURNS']);
463
            $returnfail = ($return_value != $section_text['RETURNS']);
464
        } else {
465
            $returnfail = false;
187 mathias 466
            $stdin = isset($section_text['STDIN']) ? $section_text['STDIN'] : null;
467
            $out = $this->system_with_timeout($cmd, $env, $stdin);
94 jpm 468
            $return_value = $out[0];
469
            $out = $out[1];
470
        }
471
 
187 mathias 472
        $output = preg_replace('/\r\n/', "\n", trim($out));
473
 
474
        if (isset($tmp_post) && realpath($tmp_post) && file_exists($tmp_post)) {
475
            @unlink(realpath($tmp_post));
94 jpm 476
        }
187 mathias 477
        chdir($cwd); // in case the test moves us around
94 jpm 478
 
479
        /* when using CGI, strip the headers from the output */
187 mathias 480
        $output = $this->_stripHeadersCGI($output);
481
 
482
        if (isset($section_text['EXPECTHEADERS'])) {
483
            $testheaders = $this->_processHeaders($section_text['EXPECTHEADERS']);
484
            $missing = array_diff_assoc($testheaders, $this->_headers);
485
            $changed = '';
486
            foreach ($missing as $header => $value) {
487
                if (isset($this->_headers[$header])) {
488
                    $changed .= "-$header: $value\n+$header: ";
489
                    $changed .= $this->_headers[$header];
490
                } else {
491
                    $changed .= "-$header: $value\n";
94 jpm 492
                }
493
            }
187 mathias 494
            if ($missing) {
495
                // tack on failed headers to output:
496
                $output .= "\n====EXPECTHEADERS FAILURE====:\n$changed";
497
            }
94 jpm 498
        }
499
 
187 mathias 500
        $this->_testCleanup($section_text, $temp_clean);
501
 
502
        // Does the output match what is expected?
94 jpm 503
        do {
504
            if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) {
505
                if (isset($section_text['EXPECTF'])) {
506
                    $wanted = trim($section_text['EXPECTF']);
507
                } else {
508
                    $wanted = trim($section_text['EXPECTREGEX']);
509
                }
187 mathias 510
                $wanted_re = preg_replace('/\r\n/', "\n", $wanted);
94 jpm 511
                if (isset($section_text['EXPECTF'])) {
512
                    $wanted_re = preg_quote($wanted_re, '/');
513
                    // Stick to basics
514
                    $wanted_re = str_replace("%s", ".+?", $wanted_re); //not greedy
515
                    $wanted_re = str_replace("%i", "[+\-]?[0-9]+", $wanted_re);
516
                    $wanted_re = str_replace("%d", "[0-9]+", $wanted_re);
517
                    $wanted_re = str_replace("%x", "[0-9a-fA-F]+", $wanted_re);
518
                    $wanted_re = str_replace("%f", "[+\-]?\.?[0-9]+\.?[0-9]*(E-?[0-9]+)?", $wanted_re);
519
                    $wanted_re = str_replace("%c", ".", $wanted_re);
520
                    // %f allows two points "-.0.0" but that is the best *simple* expression
521
                }
187 mathias 522
 
94 jpm 523
    /* DEBUG YOUR REGEX HERE
524
            var_dump($wanted_re);
525
            print(str_repeat('=', 80) . "\n");
526
            var_dump($output);
527
    */
528
                if (!$returnfail && preg_match("/^$wanted_re\$/s", $output)) {
529
                    if (file_exists($temp_file)) {
530
                        unlink($temp_file);
531
                    }
532
                    if (array_key_exists('FAIL', $section_text)) {
533
                        break;
534
                    }
535
                    if (!isset($this->_options['quiet'])) {
187 mathias 536
                        $this->_logger->log(0, "PASS $test_nr$tested$info");
94 jpm 537
                    }
538
                    if (isset($this->_options['tapoutput'])) {
539
                        return array('ok', ' - ' . $tested);
540
                    }
541
                    return 'PASSED';
542
                }
187 mathias 543
            } else {
544
                if (isset($section_text['EXPECTFILE'])) {
545
                    $f = $temp_dir . '/' . trim($section_text['EXPECTFILE']);
546
                    if (!($fp = @fopen($f, 'rb'))) {
547
                        return PEAR::raiseError('--EXPECTFILE-- section file ' .
548
                            $f . ' not found');
549
                    }
550
                    fclose($fp);
551
                    $section_text['EXPECT'] = file_get_contents($f);
552
                }
94 jpm 553
 
187 mathias 554
                if (isset($section_text['EXPECT'])) {
555
                    $wanted = preg_replace('/\r\n/', "\n", trim($section_text['EXPECT']));
556
                } else {
557
                    $wanted = '';
558
                }
559
 
560
                // compare and leave on success
561
                if (!$returnfail && 0 == strcmp($output, $wanted)) {
94 jpm 562
                    if (file_exists($temp_file)) {
563
                        unlink($temp_file);
564
                    }
565
                    if (array_key_exists('FAIL', $section_text)) {
566
                        break;
567
                    }
568
                    if (!isset($this->_options['quiet'])) {
187 mathias 569
                        $this->_logger->log(0, "PASS $test_nr$tested$info");
94 jpm 570
                    }
571
                    if (isset($this->_options['tapoutput'])) {
572
                        return array('ok', ' - ' . $tested);
573
                    }
574
                    return 'PASSED';
575
                }
576
            }
577
        } while (false);
578
 
579
        if (array_key_exists('FAIL', $section_text)) {
580
            // we expect a particular failure
581
            // this is only used for testing PEAR_RunTest
187 mathias 582
            $expectf  = isset($section_text['EXPECTF']) ? $wanted_re : null;
583
            $faildiff = $this->generate_diff($wanted, $output, null, $expectf);
94 jpm 584
            $faildiff = preg_replace('/\r/', '', $faildiff);
187 mathias 585
            $wanted   = preg_replace('/\r/', '', trim($section_text['FAIL']));
94 jpm 586
            if ($faildiff == $wanted) {
587
                if (!isset($this->_options['quiet'])) {
187 mathias 588
                    $this->_logger->log(0, "PASS $test_nr$tested$info");
94 jpm 589
                }
590
                if (isset($this->_options['tapoutput'])) {
591
                    return array('ok', ' - ' . $tested);
592
                }
593
                return 'PASSED';
594
            }
595
            unset($section_text['EXPECTF']);
596
            $output = $faildiff;
597
            if (isset($section_text['RETURNS'])) {
598
                return PEAR::raiseError('Cannot have both RETURNS and FAIL in the same test: ' .
599
                    $file);
600
            }
601
        }
602
 
603
        // Test failed so we need to report details.
187 mathias 604
        $txt = $warn ? 'WARN ' : 'FAIL ';
605
        $this->_logger->log(0, $txt . $test_nr . $tested . $info);
94 jpm 606
 
607
        // write .exp
187 mathias 608
        $res = $this->_writeLog($exp_filename, $wanted);
609
        if (PEAR::isError($res)) {
610
            return $res;
94 jpm 611
        }
612
 
613
        // write .out
187 mathias 614
        $res = $this->_writeLog($output_filename, $output);
615
        if (PEAR::isError($res)) {
616
            return $res;
94 jpm 617
        }
618
 
619
        // write .diff
187 mathias 620
        $returns = isset($section_text['RETURNS']) ?
621
                        array(trim($section_text['RETURNS']), $return_value) : null;
622
        $expectf = isset($section_text['EXPECTF']) ? $wanted_re : null;
623
        $data = $this->generate_diff($wanted, $output, $returns, $expectf);
624
        $res  = $this->_writeLog($diff_filename, $data);
625
        if (isset($this->_options['showdiff'])) {
626
            $this->_logger->log(0, "========DIFF========");
627
            $this->_logger->log(0, $data);
628
            $this->_logger->log(0, "========DONE========");
94 jpm 629
        }
187 mathias 630
        if (PEAR::isError($res)) {
631
            return $res;
632
        }
94 jpm 633
 
634
        // write .log
187 mathias 635
        $data = "
94 jpm 636
---- EXPECTED OUTPUT
637
$wanted
638
---- ACTUAL OUTPUT
639
$output
640
---- FAILED
187 mathias 641
";
642
 
643
        if ($returnfail) {
644
            $data .= "
94 jpm 645
---- EXPECTED RETURN
646
$section_text[RETURNS]
647
---- ACTUAL RETURN
648
$return_value
187 mathias 649
";
94 jpm 650
        }
651
 
187 mathias 652
        $res = $this->_writeLog($log_filename, $data);
653
        if (PEAR::isError($res)) {
654
            return $res;
94 jpm 655
        }
656
 
657
        if (isset($this->_options['tapoutput'])) {
658
            $wanted = explode("\n", $wanted);
659
            $wanted = "# Expected output:\n#\n#" . implode("\n#", $wanted);
660
            $output = explode("\n", $output);
661
            $output = "#\n#\n# Actual output:\n#\n#" . implode("\n#", $output);
662
            return array($wanted . $output . 'not ok', ' - ' . $tested);
663
        }
664
        return $warn ? 'WARNED' : 'FAILED';
665
    }
666
 
187 mathias 667
    function generate_diff($wanted, $output, $rvalue, $wanted_re)
94 jpm 668
    {
187 mathias 669
        $w  = explode("\n", $wanted);
670
        $o  = explode("\n", $output);
94 jpm 671
        $wr = explode("\n", $wanted_re);
187 mathias 672
        $w1 = array_diff_assoc($w, $o);
673
        $o1 = array_diff_assoc($o, $w);
674
        $o2 = $w2 = array();
675
        foreach ($w1 as $idx => $val) {
94 jpm 676
            if (!$wanted_re || !isset($wr[$idx]) || !isset($o1[$idx]) ||
187 mathias 677
                  !preg_match('/^' . $wr[$idx] . '\\z/', $o1[$idx])) {
94 jpm 678
                $w2[sprintf("%03d<", $idx)] = sprintf("%03d- ", $idx + 1) . $val;
679
            }
680
        }
187 mathias 681
        foreach ($o1 as $idx => $val) {
94 jpm 682
            if (!$wanted_re || !isset($wr[$idx]) ||
187 mathias 683
                  !preg_match('/^' . $wr[$idx] . '\\z/', $val)) {
94 jpm 684
                $o2[sprintf("%03d>", $idx)] = sprintf("%03d+ ", $idx + 1) . $val;
685
            }
686
        }
687
        $diff = array_merge($w2, $o2);
688
        ksort($diff);
187 mathias 689
        $extra = $rvalue ? "##EXPECTED: $rvalue[0]\r\n##RETURNED: $rvalue[1]" : '';
94 jpm 690
        return implode("\r\n", $diff) . $extra;
691
    }
692
 
693
    //  Write the given text to a temporary file, and return the filename.
694
    function save_text($filename, $text)
695
    {
696
        if (!$fp = fopen($filename, 'w')) {
697
            return PEAR::raiseError("Cannot open file '" . $filename . "' (save_text)");
698
        }
187 mathias 699
        fwrite($fp, $text);
94 jpm 700
        fclose($fp);
701
    if (1 < DETAILED) echo "
702
FILE $filename {{{
703
$text
704
}}}
705
";
706
    }
707
 
187 mathias 708
    function _cleanupOldFiles($file)
709
    {
710
        $temp_dir = realpath(dirname($file));
711
        $mainFileName = basename($file, 'phpt');
712
        $diff_filename     = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'diff';
713
        $log_filename      = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'log';
714
        $exp_filename      = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'exp';
715
        $output_filename   = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'out';
716
        $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'mem';
717
        $temp_file         = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'php';
718
        $temp_skipif       = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'skip.php';
719
        $temp_clean        = $temp_dir . DIRECTORY_SEPARATOR . $mainFileName.'clean.php';
720
        $tmp_post          = $temp_dir . DIRECTORY_SEPARATOR . uniqid('phpt.');
721
 
722
        // unlink old test results
723
        @unlink($diff_filename);
724
        @unlink($log_filename);
725
        @unlink($exp_filename);
726
        @unlink($output_filename);
727
        @unlink($memcheck_filename);
728
        @unlink($temp_file);
729
        @unlink($temp_skipif);
730
        @unlink($tmp_post);
731
        @unlink($temp_clean);
732
    }
733
 
734
    function _runSkipIf($section_text, $temp_skipif, $tested, $ini_settings)
735
    {
736
        $info = '';
737
        $warn = false;
738
        if (array_key_exists('SKIPIF', $section_text) && trim($section_text['SKIPIF'])) {
739
            $this->save_text($temp_skipif, $section_text['SKIPIF']);
740
            $output = $this->system_with_timeout("$this->_php$ini_settings -f \"$temp_skipif\"");
741
            $output = $output[1];
742
            $loutput = ltrim($output);
743
            unlink($temp_skipif);
744
            if (!strncasecmp('skip', $loutput, 4)) {
745
                $skipreason = "SKIP $tested";
746
                if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) {
747
                    $skipreason .= '(reason: ' . $m[1] . ')';
748
                }
749
                if (!isset($this->_options['quiet'])) {
750
                    $this->_logger->log(0, $skipreason);
751
                }
752
                if (isset($this->_options['tapoutput'])) {
753
                    return array('ok', ' # skip ' . $reason);
754
                }
755
                return 'SKIPPED';
756
            }
757
 
758
            if (!strncasecmp('info', $loutput, 4)
759
                && preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) {
760
                $info = " (info: $m[1])";
761
            }
762
 
763
            if (!strncasecmp('warn', $loutput, 4)
764
                && preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) {
765
                $warn = true; /* only if there is a reason */
766
                $info = " (warn: $m[1])";
767
            }
768
        }
769
 
770
        return array('warn' => $warn, 'info' => $info);
771
    }
772
 
773
    function _stripHeadersCGI($output)
774
    {
775
        $this->headers = array();
776
        if (!empty($this->_options['cgi']) &&
777
              $this->_php == $this->_options['cgi'] &&
778
              preg_match("/^(.*?)(?:\n\n(.*)|\\z)/s", $output, $match)) {
779
            $output = isset($match[2]) ? trim($match[2]) : '';
780
            $this->_headers = $this->_processHeaders($match[1]);
781
        }
782
 
783
        return $output;
784
    }
785
 
786
    /**
787
     * Return an array that can be used with array_diff() to compare headers
788
     *
789
     * @param string $text
790
     */
791
    function _processHeaders($text)
792
    {
793
        $headers = array();
794
        $rh = preg_split("/[\n\r]+/", $text);
795
        foreach ($rh as $line) {
796
            if (strpos($line, ':')!== false) {
797
                $line = explode(':', $line, 2);
798
                $headers[trim($line[0])] = trim($line[1]);
799
            }
800
        }
801
        return $headers;
802
    }
803
 
804
    function _readFile($file)
805
    {
806
        // Load the sections of the test file.
807
        $section_text = array(
808
            'TEST'   => '(unnamed test)',
809
            'SKIPIF' => '',
810
            'GET'    => '',
811
            'COOKIE' => '',
812
            'POST'   => '',
813
            'ARGS'   => '',
814
            'INI'    => '',
815
            'CLEAN'  => '',
816
        );
817
 
818
        if (!is_file($file) || !$fp = fopen($file, "r")) {
819
            return PEAR::raiseError("Cannot open test file: $file");
820
        }
821
 
822
        $section = '';
823
        while (!feof($fp)) {
824
            $line = fgets($fp);
825
 
826
            // Match the beginning of a section.
827
            if (preg_match('/^--([_A-Z]+)--/', $line, $r)) {
828
                $section = $r[1];
829
                $section_text[$section] = '';
830
                continue;
831
            } elseif (empty($section)) {
832
                fclose($fp);
833
                return PEAR::raiseError("Invalid sections formats in test file: $file");
834
            }
835
 
836
            // Add to the section text.
837
            $section_text[$section] .= $line;
838
        }
839
        fclose($fp);
840
 
841
        return $section_text;
842
    }
843
 
844
    function _writeLog($logname, $data)
845
    {
846
        if (!$log = fopen($logname, 'w')) {
847
            return PEAR::raiseError("Cannot create test log - $logname");
848
        }
849
        fwrite($log, $data);
850
        fclose($log);
851
    }
852
 
853
    function _resetEnv($section_text, $temp_file)
854
    {
855
        $env = $_ENV;
856
        $env['REDIRECT_STATUS'] = '';
857
        $env['QUERY_STRING']    = '';
858
        $env['PATH_TRANSLATED'] = '';
859
        $env['SCRIPT_FILENAME'] = '';
860
        $env['REQUEST_METHOD']  = '';
861
        $env['CONTENT_TYPE']    = '';
862
        $env['CONTENT_LENGTH']  = '';
863
        if (!empty($section_text['ENV'])) {
864
            if (strpos($section_text['ENV'], '{PWD}') !== false) {
865
                $section_text['ENV'] = str_replace('{PWD}', dirname($temp_file), $section_text['ENV']);
866
            }
867
            foreach (explode("\n", trim($section_text['ENV'])) as $e) {
868
                $e = explode('=', trim($e), 2);
869
                if (!empty($e[0]) && isset($e[1])) {
870
                    $env[$e[0]] = $e[1];
871
                }
872
            }
873
        }
874
        if (array_key_exists('GET', $section_text)) {
875
            $env['QUERY_STRING'] = trim($section_text['GET']);
876
        } else {
877
            $env['QUERY_STRING'] = '';
878
        }
879
        if (array_key_exists('COOKIE', $section_text)) {
880
            $env['HTTP_COOKIE'] = trim($section_text['COOKIE']);
881
        } else {
882
            $env['HTTP_COOKIE'] = '';
883
        }
884
        $env['REDIRECT_STATUS'] = '1';
885
        $env['PATH_TRANSLATED'] = $temp_file;
886
        $env['SCRIPT_FILENAME'] = $temp_file;
887
 
888
        return $env;
889
    }
890
 
891
    function _processUpload($section_text, $file)
892
    {
893
        if (array_key_exists('UPLOAD', $section_text) && !empty($section_text['UPLOAD'])) {
894
            $upload_files = trim($section_text['UPLOAD']);
895
            $upload_files = explode("\n", $upload_files);
896
 
897
            $request = "Content-Type: multipart/form-data; boundary=---------------------------20896060251896012921717172737\n" .
898
                       "-----------------------------20896060251896012921717172737\n";
899
            foreach ($upload_files as $fileinfo) {
900
                $fileinfo = explode('=', $fileinfo);
901
                if (count($fileinfo) != 2) {
902
                    return PEAR::raiseError("Invalid UPLOAD section in test file: $file");
903
                }
904
                if (!realpath(dirname($file) . '/' . $fileinfo[1])) {
905
                    return PEAR::raiseError("File for upload does not exist: $fileinfo[1] " .
906
                        "in test file: $file");
907
                }
908
                $file_contents = file_get_contents(dirname($file) . '/' . $fileinfo[1]);
909
                $fileinfo[1] = basename($fileinfo[1]);
910
                $request .= "Content-Disposition: form-data; name=\"$fileinfo[0]\"; filename=\"$fileinfo[1]\"\n";
911
                $request .= "Content-Type: text/plain\n\n";
912
                $request .= $file_contents . "\n" .
913
                    "-----------------------------20896060251896012921717172737\n";
914
            }
915
 
916
            if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) {
917
                // encode POST raw
918
                $post = trim($section_text['POST']);
919
                $post = explode('&', $post);
920
                foreach ($post as $i => $post_info) {
921
                    $post_info = explode('=', $post_info);
922
                    if (count($post_info) != 2) {
923
                        return PEAR::raiseError("Invalid POST data in test file: $file");
924
                    }
925
                    $post_info[0] = rawurldecode($post_info[0]);
926
                    $post_info[1] = rawurldecode($post_info[1]);
927
                    $post[$i] = $post_info;
928
                }
929
                foreach ($post as $post_info) {
930
                    $request .= "Content-Disposition: form-data; name=\"$post_info[0]\"\n\n";
931
                    $request .= $post_info[1] . "\n" .
932
                        "-----------------------------20896060251896012921717172737\n";
933
                }
934
                unset($section_text['POST']);
935
            }
936
            $section_text['POST_RAW'] = $request;
937
        }
938
 
939
        return $section_text;
940
    }
941
 
942
    function _testCleanup($section_text, $temp_clean)
943
    {
944
        if ($section_text['CLEAN']) {
945
            $this->_restorePHPBinary();
946
 
947
            // perform test cleanup
948
            $this->save_text($temp_clean, $section_text['CLEAN']);
949
            $output = $this->system_with_timeout("$this->_php $temp_clean  2>&1");
950
            if (strlen($output[1])) {
951
                echo "BORKED --CLEAN-- section! output:\n", $output[1];
952
            }
953
            if (file_exists($temp_clean)) {
954
                unlink($temp_clean);
955
            }
956
        }
957
    }
958
 
959
    function _savePHPBinary()
960
    {
961
        $this->_savephp = $this->_php;
962
    }
963
 
964
    function _restorePHPBinary()
965
    {
966
        if (isset($this->_savephp))
967
        {
968
            $this->_php = $this->_savephp;
969
            unset($this->_savephp);
970
        }
971
    }
94 jpm 972
}