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