Subversion Repositories web.creative

Rev

Details | Last modification | View Log

Rev Author Line No. Line
1 mjordaan 1
<?php
2
/*
3
 * This file is part of PHPUnit.
4
 *
5
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
 
11
/**
12
 * Runner for PHPT test cases.
13
 */
14
class PHPUnit_Extensions_PhptTestCase implements PHPUnit_Framework_Test, PHPUnit_Framework_SelfDescribing
15
{
16
    /**
17
     * @var string
18
     */
19
    private $filename;
20
 
21
    /**
22
     * @var PHPUnit_Util_PHP
23
     */
24
    private $phpUtil;
25
 
26
    /**
27
     * @var array
28
     */
29
    private $settings = [
30
        'allow_url_fopen=1',
31
        'auto_append_file=',
32
        'auto_prepend_file=',
33
        'disable_functions=',
34
        'display_errors=1',
35
        'docref_root=',
36
        'docref_ext=.html',
37
        'error_append_string=',
38
        'error_prepend_string=',
39
        'error_reporting=-1',
40
        'html_errors=0',
41
        'log_errors=0',
42
        'magic_quotes_runtime=0',
43
        'output_handler=',
44
        'open_basedir=',
45
        'output_buffering=Off',
46
        'report_memleaks=0',
47
        'report_zend_debug=0',
48
        'safe_mode=0',
49
        'xdebug.default_enable=0'
50
    ];
51
 
52
    /**
53
     * Constructs a test case with the given filename.
54
     *
55
     * @param string           $filename
56
     * @param PHPUnit_Util_PHP $phpUtil
57
     *
58
     * @throws PHPUnit_Framework_Exception
59
     */
60
    public function __construct($filename, $phpUtil = null)
61
    {
62
        if (!is_string($filename)) {
63
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
64
        }
65
 
66
        if (!is_file($filename)) {
67
            throw new PHPUnit_Framework_Exception(
68
                sprintf(
69
                    'File "%s" does not exist.',
70
                    $filename
71
                )
72
            );
73
        }
74
 
75
        $this->filename = $filename;
76
        $this->phpUtil  = $phpUtil ?: PHPUnit_Util_PHP::factory();
77
    }
78
 
79
    /**
80
     * Counts the number of test cases executed by run(TestResult result).
81
     *
82
     * @return int
83
     */
84
    public function count()
85
    {
86
        return 1;
87
    }
88
 
89
    /**
90
     * @param array  $sections
91
     * @param string $output
92
     */
93
    private function assertPhptExpectation(array $sections, $output)
94
    {
95
        $assertions = [
96
            'EXPECT'      => 'assertEquals',
97
            'EXPECTF'     => 'assertStringMatchesFormat',
98
            'EXPECTREGEX' => 'assertRegExp',
99
        ];
100
 
101
        $actual = preg_replace('/\r\n/', "\n", trim($output));
102
 
103
        foreach ($assertions as $sectionName => $sectionAssertion) {
104
            if (isset($sections[$sectionName])) {
105
                $sectionContent = preg_replace('/\r\n/', "\n", trim($sections[$sectionName]));
106
                $assertion      = $sectionAssertion;
107
                $expected       = $sectionName == 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent;
108
 
109
                break;
110
            }
111
        }
112
 
113
        PHPUnit_Framework_Assert::$assertion($expected, $actual);
114
    }
115
 
116
    /**
117
     * Runs a test and collects its result in a TestResult instance.
118
     *
119
     * @param PHPUnit_Framework_TestResult $result
120
     *
121
     * @return PHPUnit_Framework_TestResult
122
     */
123
    public function run(PHPUnit_Framework_TestResult $result = null)
124
    {
125
        $sections = $this->parse();
126
        $code     = $this->render($sections['FILE']);
127
 
128
        if ($result === null) {
129
            $result = new PHPUnit_Framework_TestResult;
130
        }
131
 
132
        $skip     = false;
133
        $xfail    = false;
134
        $time     = 0;
135
        $settings = $this->settings;
136
 
137
        $result->startTest($this);
138
 
139
        if (isset($sections['INI'])) {
140
            $settings = array_merge($settings, $this->parseIniSection($sections['INI']));
141
        }
142
 
143
        if (isset($sections['ENV'])) {
144
            $env = $this->parseEnvSection($sections['ENV']);
145
            $this->phpUtil->setEnv($env);
146
        }
147
 
148
        // Redirects STDERR to STDOUT
149
        $this->phpUtil->setUseStderrRedirection(true);
150
 
151
        if ($result->enforcesTimeLimit()) {
152
            $this->phpUtil->setTimeout($result->getTimeoutForLargeTests());
153
        }
154
 
155
        if (isset($sections['SKIPIF'])) {
156
            $jobResult = $this->phpUtil->runJob($sections['SKIPIF'], $settings);
157
 
158
            if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) {
159
                if (preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $message)) {
160
                    $message = substr($message[1], 2);
161
                } else {
162
                    $message = '';
163
                }
164
 
165
                $result->addFailure($this, new PHPUnit_Framework_SkippedTestError($message), 0);
166
 
167
                $skip = true;
168
            }
169
        }
170
 
171
        if (isset($sections['XFAIL'])) {
172
            $xfail = trim($sections['XFAIL']);
173
        }
174
 
175
        if (!$skip) {
176
            if (isset($sections['STDIN'])) {
177
                $this->phpUtil->setStdin($sections['STDIN']);
178
            }
179
 
180
            if (isset($sections['ARGS'])) {
181
                $this->phpUtil->setArgs($sections['ARGS']);
182
            }
183
 
184
            PHP_Timer::start();
185
 
186
            $jobResult = $this->phpUtil->runJob($code, $settings);
187
            $time      = PHP_Timer::stop();
188
 
189
            try {
190
                $this->assertPhptExpectation($sections, $jobResult['stdout']);
191
            } catch (PHPUnit_Framework_AssertionFailedError $e) {
192
                if ($xfail !== false) {
193
                    $result->addFailure(
194
                        $this,
195
                        new PHPUnit_Framework_IncompleteTestError(
196
                            $xfail,
197
                            0,
198
                            $e
199
                        ),
200
                        $time
201
                    );
202
                } else {
203
                    $result->addFailure($this, $e, $time);
204
                }
205
            } catch (Throwable $t) {
206
                $result->addError($this, $t, $time);
207
            } catch (Exception $e) {
208
                $result->addError($this, $e, $time);
209
            }
210
 
211
            if ($result->allCompletelyImplemented() && $xfail !== false) {
212
                $result->addFailure(
213
                    $this,
214
                    new PHPUnit_Framework_IncompleteTestError(
215
                        'XFAIL section but test passes'
216
                    ),
217
                    $time
218
                );
219
            }
220
 
221
            $this->phpUtil->setStdin('');
222
            $this->phpUtil->setArgs('');
223
 
224
            if (isset($sections['CLEAN'])) {
225
                $cleanCode = $this->render($sections['CLEAN']);
226
 
227
                $this->phpUtil->runJob($cleanCode, $this->settings);
228
            }
229
        }
230
 
231
        $result->endTest($this, $time);
232
 
233
        return $result;
234
    }
235
 
236
    /**
237
     * Returns the name of the test case.
238
     *
239
     * @return string
240
     */
241
    public function getName()
242
    {
243
        return $this->toString();
244
    }
245
 
246
    /**
247
     * Returns a string representation of the test case.
248
     *
249
     * @return string
250
     */
251
    public function toString()
252
    {
253
        return $this->filename;
254
    }
255
 
256
    /**
257
     * @return array
258
     *
259
     * @throws PHPUnit_Framework_Exception
260
     */
261
    private function parse()
262
    {
263
        $sections = [];
264
        $section  = '';
265
 
266
        $allowExternalSections = [
267
            'FILE',
268
            'EXPECT',
269
            'EXPECTF',
270
            'EXPECTREGEX'
271
        ];
272
 
273
        $requiredSections = [
274
            'FILE',
275
            [
276
                'EXPECT',
277
                'EXPECTF',
278
                'EXPECTREGEX'
279
            ]
280
        ];
281
 
282
        $unsupportedSections = [
283
            'REDIRECTTEST',
284
            'REQUEST',
285
            'POST',
286
            'PUT',
287
            'POST_RAW',
288
            'GZIP_POST',
289
            'DEFLATE_POST',
290
            'GET',
291
            'COOKIE',
292
            'HEADERS',
293
            'CGI',
294
            'EXPECTHEADERS',
295
            'EXTENSIONS',
296
            'PHPDBG'
297
        ];
298
 
299
        foreach (file($this->filename) as $line) {
300
            if (preg_match('/^--([_A-Z]+)--/', $line, $result)) {
301
                $section            = $result[1];
302
                $sections[$section] = '';
303
 
304
                continue;
305
            } elseif (empty($section)) {
306
                throw new PHPUnit_Framework_Exception('Invalid PHPT file');
307
            }
308
 
309
            $sections[$section] .= $line;
310
        }
311
 
312
        if (isset($sections['FILEEOF'])) {
313
            $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n");
314
            unset($sections['FILEEOF']);
315
        }
316
 
317
        $testDirectory = dirname($this->filename) . DIRECTORY_SEPARATOR;
318
 
319
        foreach ($allowExternalSections as $section) {
320
            if (isset($sections[$section . '_EXTERNAL'])) {
321
                // do not allow directory traversal
322
                $externalFilename = str_replace('..', '', trim($sections[$section . '_EXTERNAL']));
323
 
324
                // only allow files from the test directory
325
                if (!is_file($testDirectory . $externalFilename) || !is_readable($testDirectory . $externalFilename)) {
326
                    throw new PHPUnit_Framework_Exception(
327
                        sprintf(
328
                            'Could not load --%s-- %s for PHPT file',
329
                            $section . '_EXTERNAL',
330
                            $testDirectory . $externalFilename
331
                        )
332
                    );
333
                }
334
 
335
                $sections[$section] = file_get_contents($testDirectory . $externalFilename);
336
 
337
                unset($sections[$section . '_EXTERNAL']);
338
            }
339
        }
340
 
341
        $isValid = true;
342
 
343
        foreach ($requiredSections as $section) {
344
            if (is_array($section)) {
345
                $foundSection = false;
346
 
347
                foreach ($section as $anySection) {
348
                    if (isset($sections[$anySection])) {
349
                        $foundSection = true;
350
 
351
                        break;
352
                    }
353
                }
354
 
355
                if (!$foundSection) {
356
                    $isValid = false;
357
 
358
                    break;
359
                }
360
            } else {
361
                if (!isset($sections[$section])) {
362
                    $isValid = false;
363
 
364
                    break;
365
                }
366
            }
367
        }
368
 
369
        if (!$isValid) {
370
            throw new PHPUnit_Framework_Exception('Invalid PHPT file');
371
        }
372
 
373
        foreach ($unsupportedSections as $section) {
374
            if (isset($sections[$section])) {
375
                throw new PHPUnit_Framework_Exception(
376
                    'PHPUnit does not support this PHPT file'
377
                );
378
            }
379
        }
380
 
381
        return $sections;
382
    }
383
 
384
    /**
385
     * @param string $code
386
     *
387
     * @return string
388
     */
389
    private function render($code)
390
    {
391
        return str_replace(
392
            [
393
                '__DIR__',
394
                '__FILE__'
395
            ],
396
            [
397
                "'" . dirname($this->filename) . "'",
398
                "'" . $this->filename . "'"
399
            ],
400
            $code
401
        );
402
    }
403
 
404
    /**
405
     * Parse --INI-- section key value pairs and return as array.
406
     *
407
     * @param string
408
     *
409
     * @return array
410
     */
411
    protected function parseIniSection($content)
412
    {
413
        return preg_split('/\n|\r/', $content, -1, PREG_SPLIT_NO_EMPTY);
414
    }
415
 
416
    protected function parseEnvSection($content)
417
    {
418
        $env = [];
419
 
420
        foreach (explode("\n", trim($content)) as $e) {
421
            $e = explode('=', trim($e), 2);
422
 
423
            if (!empty($e[0]) && isset($e[1])) {
424
                $env[$e[0]] = $e[1];
425
            }
426
        }
427
 
428
        return $env;
429
    }
430
}