Completed
Push — master ( 01e071...007090 )
by Ehsan
01:46 queued 13s
created

PhptTestCase::run()   F

Complexity

Conditions 18
Paths 8320

Size

Total Lines 111
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 66
nc 8320
nop 1
dl 0
loc 111
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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