Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php declare(strict_types=1);
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
namespace PHPUnit\Util\PHP;
11
 
12
use const DIRECTORY_SEPARATOR;
13
use const PHP_SAPI;
14
use function array_keys;
15
use function array_merge;
16
use function assert;
17
use function escapeshellarg;
18
use function ini_get_all;
19
use function restore_error_handler;
20
use function set_error_handler;
21
use function sprintf;
22
use function str_replace;
23
use function strpos;
24
use function strrpos;
25
use function substr;
26
use function trim;
27
use function unserialize;
28
use __PHP_Incomplete_Class;
29
use ErrorException;
30
use PHPUnit\Framework\AssertionFailedError;
31
use PHPUnit\Framework\Exception;
32
use PHPUnit\Framework\SyntheticError;
33
use PHPUnit\Framework\Test;
34
use PHPUnit\Framework\TestCase;
35
use PHPUnit\Framework\TestFailure;
36
use PHPUnit\Framework\TestResult;
37
use SebastianBergmann\Environment\Runtime;
38
 
39
/**
40
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
41
 */
42
abstract class AbstractPhpProcess
43
{
44
    /**
45
     * @var Runtime
46
     */
47
    protected $runtime;
48
 
49
    /**
50
     * @var bool
51
     */
52
    protected $stderrRedirection = false;
53
 
54
    /**
55
     * @var string
56
     */
57
    protected $stdin = '';
58
 
59
    /**
60
     * @var string
61
     */
62
    protected $args = '';
63
 
64
    /**
65
     * @var array<string, string>
66
     */
67
    protected $env = [];
68
 
69
    /**
70
     * @var int
71
     */
72
    protected $timeout = 0;
73
 
74
    public static function factory(): self
75
    {
76
        if (DIRECTORY_SEPARATOR === '\\') {
77
            return new WindowsPhpProcess;
78
        }
79
 
80
        return new DefaultPhpProcess;
81
    }
82
 
83
    public function __construct()
84
    {
85
        $this->runtime = new Runtime;
86
    }
87
 
88
    /**
89
     * Defines if should use STDERR redirection or not.
90
     *
91
     * Then $stderrRedirection is TRUE, STDERR is redirected to STDOUT.
92
     */
93
    public function setUseStderrRedirection(bool $stderrRedirection): void
94
    {
95
        $this->stderrRedirection = $stderrRedirection;
96
    }
97
 
98
    /**
99
     * Returns TRUE if uses STDERR redirection or FALSE if not.
100
     */
101
    public function useStderrRedirection(): bool
102
    {
103
        return $this->stderrRedirection;
104
    }
105
 
106
    /**
107
     * Sets the input string to be sent via STDIN.
108
     */
109
    public function setStdin(string $stdin): void
110
    {
111
        $this->stdin = $stdin;
112
    }
113
 
114
    /**
115
     * Returns the input string to be sent via STDIN.
116
     */
117
    public function getStdin(): string
118
    {
119
        return $this->stdin;
120
    }
121
 
122
    /**
123
     * Sets the string of arguments to pass to the php job.
124
     */
125
    public function setArgs(string $args): void
126
    {
127
        $this->args = $args;
128
    }
129
 
130
    /**
131
     * Returns the string of arguments to pass to the php job.
132
     */
133
    public function getArgs(): string
134
    {
135
        return $this->args;
136
    }
137
 
138
    /**
139
     * Sets the array of environment variables to start the child process with.
140
     *
141
     * @param array<string, string> $env
142
     */
143
    public function setEnv(array $env): void
144
    {
145
        $this->env = $env;
146
    }
147
 
148
    /**
149
     * Returns the array of environment variables to start the child process with.
150
     */
151
    public function getEnv(): array
152
    {
153
        return $this->env;
154
    }
155
 
156
    /**
157
     * Sets the amount of seconds to wait before timing out.
158
     */
159
    public function setTimeout(int $timeout): void
160
    {
161
        $this->timeout = $timeout;
162
    }
163
 
164
    /**
165
     * Returns the amount of seconds to wait before timing out.
166
     */
167
    public function getTimeout(): int
168
    {
169
        return $this->timeout;
170
    }
171
 
172
    /**
173
     * Runs a single test in a separate PHP process.
174
     *
175
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
176
     */
177
    public function runTestJob(string $job, Test $test, TestResult $result): void
178
    {
179
        $result->startTest($test);
180
 
181
        $_result = $this->runJob($job);
182
 
183
        $this->processChildResult(
184
            $test,
185
            $result,
186
            $_result['stdout'],
187
            $_result['stderr']
188
        );
189
    }
190
 
191
    /**
192
     * Returns the command based into the configurations.
193
     */
194
    public function getCommand(array $settings, string $file = null): string
195
    {
196
        $command = $this->runtime->getBinary();
197
 
198
        if ($this->runtime->hasPCOV()) {
199
            $settings = array_merge(
200
                $settings,
201
                $this->runtime->getCurrentSettings(
202
                    array_keys(ini_get_all('pcov'))
203
                )
204
            );
205
        } elseif ($this->runtime->hasXdebug()) {
206
            $settings = array_merge(
207
                $settings,
208
                $this->runtime->getCurrentSettings(
209
                    array_keys(ini_get_all('xdebug'))
210
                )
211
            );
212
        }
213
 
214
        $command .= $this->settingsToParameters($settings);
215
 
216
        if (PHP_SAPI === 'phpdbg') {
217
            $command .= ' -qrr';
218
 
219
            if (!$file) {
220
                $command .= 's=';
221
            }
222
        }
223
 
224
        if ($file) {
225
            $command .= ' ' . escapeshellarg($file);
226
        }
227
 
228
        if ($this->args) {
229
            if (!$file) {
230
                $command .= ' --';
231
            }
232
            $command .= ' ' . $this->args;
233
        }
234
 
235
        if ($this->stderrRedirection) {
236
            $command .= ' 2>&1';
237
        }
238
 
239
        return $command;
240
    }
241
 
242
    /**
243
     * Runs a single job (PHP code) using a separate PHP process.
244
     */
245
    abstract public function runJob(string $job, array $settings = []): array;
246
 
247
    protected function settingsToParameters(array $settings): string
248
    {
249
        $buffer = '';
250
 
251
        foreach ($settings as $setting) {
252
            $buffer .= ' -d ' . escapeshellarg($setting);
253
        }
254
 
255
        return $buffer;
256
    }
257
 
258
    /**
259
     * Processes the TestResult object from an isolated process.
260
     *
261
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
262
     */
263
    private function processChildResult(Test $test, TestResult $result, string $stdout, string $stderr): void
264
    {
265
        $time = 0;
266
 
267
        if (!empty($stderr)) {
268
            $result->addError(
269
                $test,
270
                new Exception(trim($stderr)),
271
                $time
272
            );
273
        } else {
274
            set_error_handler(
275
                /**
276
                 * @throws ErrorException
277
                 */
278
                static function ($errno, $errstr, $errfile, $errline): void
279
                {
280
                    throw new ErrorException($errstr, $errno, $errno, $errfile, $errline);
281
                }
282
            );
283
 
284
            try {
285
                if (strpos($stdout, "#!/usr/bin/env php\n") === 0) {
286
                    $stdout = substr($stdout, 19);
287
                }
288
 
289
                $childResult = unserialize(str_replace("#!/usr/bin/env php\n", '', $stdout));
290
                restore_error_handler();
291
 
292
                if ($childResult === false) {
293
                    $result->addFailure(
294
                        $test,
295
                        new AssertionFailedError('Test was run in child process and ended unexpectedly'),
296
                        $time
297
                    );
298
                }
299
            } catch (ErrorException $e) {
300
                restore_error_handler();
301
                $childResult = false;
302
 
303
                $result->addError(
304
                    $test,
305
                    new Exception(trim($stdout), 0, $e),
306
                    $time
307
                );
308
            }
309
 
310
            if ($childResult !== false) {
311
                if (!empty($childResult['output'])) {
312
                    $output = $childResult['output'];
313
                }
314
 
315
                /* @var TestCase $test */
316
 
317
                $test->setResult($childResult['testResult']);
318
                $test->addToAssertionCount($childResult['numAssertions']);
319
 
320
                $childResult = $childResult['result'];
321
                assert($childResult instanceof TestResult);
322
 
323
                if ($result->getCollectCodeCoverageInformation()) {
324
                    $result->getCodeCoverage()->merge(
325
                        $childResult->getCodeCoverage()
326
                    );
327
                }
328
 
329
                $time           = $childResult->time();
330
                $notImplemented = $childResult->notImplemented();
331
                $risky          = $childResult->risky();
332
                $skipped        = $childResult->skipped();
333
                $errors         = $childResult->errors();
334
                $warnings       = $childResult->warnings();
335
                $failures       = $childResult->failures();
336
 
337
                if (!empty($notImplemented)) {
338
                    $result->addError(
339
                        $test,
340
                        $this->getException($notImplemented[0]),
341
                        $time
342
                    );
343
                } elseif (!empty($risky)) {
344
                    $result->addError(
345
                        $test,
346
                        $this->getException($risky[0]),
347
                        $time
348
                    );
349
                } elseif (!empty($skipped)) {
350
                    $result->addError(
351
                        $test,
352
                        $this->getException($skipped[0]),
353
                        $time
354
                    );
355
                } elseif (!empty($errors)) {
356
                    $result->addError(
357
                        $test,
358
                        $this->getException($errors[0]),
359
                        $time
360
                    );
361
                } elseif (!empty($warnings)) {
362
                    $result->addWarning(
363
                        $test,
364
                        $this->getException($warnings[0]),
365
                        $time
366
                    );
367
                } elseif (!empty($failures)) {
368
                    $result->addFailure(
369
                        $test,
370
                        $this->getException($failures[0]),
371
                        $time
372
                    );
373
                }
374
            }
375
        }
376
 
377
        $result->endTest($test, $time);
378
 
379
        if (!empty($output)) {
380
            print $output;
381
        }
382
    }
383
 
384
    /**
385
     * Gets the thrown exception from a PHPUnit\Framework\TestFailure.
386
     *
387
     * @see https://github.com/sebastianbergmann/phpunit/issues/74
388
     */
389
    private function getException(TestFailure $error): Exception
390
    {
391
        $exception = $error->thrownException();
392
 
393
        if ($exception instanceof __PHP_Incomplete_Class) {
394
            $exceptionArray = [];
395
 
396
            foreach ((array) $exception as $key => $value) {
397
                $key                  = substr($key, strrpos($key, "\0") + 1);
398
                $exceptionArray[$key] = $value;
399
            }
400
 
401
            $exception = new SyntheticError(
402
                sprintf(
403
                    '%s: %s',
404
                    $exceptionArray['_PHP_Incomplete_Class_Name'],
405
                    $exceptionArray['message']
406
                ),
407
                $exceptionArray['code'],
408
                $exceptionArray['file'],
409
                $exceptionArray['line'],
410
                $exceptionArray['trace']
411
            );
412
        }
413
 
414
        return $exception;
415
    }
416
}