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\Runner;
11
 
12
use const DEBUG_BACKTRACE_IGNORE_ARGS;
13
use const DIRECTORY_SEPARATOR;
14
use function array_merge;
15
use function basename;
16
use function debug_backtrace;
17
use function defined;
18
use function dirname;
19
use function explode;
20
use function extension_loaded;
21
use function file;
22
use function file_get_contents;
23
use function file_put_contents;
24
use function is_array;
25
use function is_file;
26
use function is_readable;
27
use function is_string;
28
use function ltrim;
29
use function phpversion;
30
use function preg_match;
31
use function preg_replace;
32
use function preg_split;
33
use function realpath;
34
use function rtrim;
35
use function sprintf;
36
use function str_replace;
37
use function strncasecmp;
38
use function strpos;
39
use function substr;
40
use function trim;
41
use function unlink;
42
use function unserialize;
43
use function var_export;
44
use function version_compare;
45
use PHPUnit\Framework\Assert;
46
use PHPUnit\Framework\AssertionFailedError;
47
use PHPUnit\Framework\ExecutionOrderDependency;
48
use PHPUnit\Framework\ExpectationFailedException;
49
use PHPUnit\Framework\IncompleteTestError;
50
use PHPUnit\Framework\PHPTAssertionFailedError;
51
use PHPUnit\Framework\Reorderable;
52
use PHPUnit\Framework\SelfDescribing;
53
use PHPUnit\Framework\SkippedTestError;
54
use PHPUnit\Framework\SyntheticSkippedError;
55
use PHPUnit\Framework\Test;
56
use PHPUnit\Framework\TestResult;
57
use PHPUnit\Util\PHP\AbstractPhpProcess;
58
use SebastianBergmann\CodeCoverage\RawCodeCoverageData;
59
use SebastianBergmann\Template\Template;
60
use SebastianBergmann\Timer\Timer;
61
use Throwable;
62
 
63
/**
64
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
65
 */
66
final class PhptTestCase implements Reorderable, SelfDescribing, Test
67
{
68
    /**
69
     * @var string
70
     */
71
    private $filename;
72
 
73
    /**
74
     * @var AbstractPhpProcess
75
     */
76
    private $phpUtil;
77
 
78
    /**
79
     * @var string
80
     */
81
    private $output = '';
82
 
83
    /**
84
     * Constructs a test case with the given filename.
85
     *
86
     * @throws Exception
87
     */
88
    public function __construct(string $filename, AbstractPhpProcess $phpUtil = null)
89
    {
90
        if (!is_file($filename)) {
91
            throw new Exception(
92
                sprintf(
93
                    'File "%s" does not exist.',
94
                    $filename
95
                )
96
            );
97
        }
98
 
99
        $this->filename = $filename;
100
        $this->phpUtil  = $phpUtil ?: AbstractPhpProcess::factory();
101
    }
102
 
103
    /**
104
     * Counts the number of test cases executed by run(TestResult result).
105
     */
106
    public function count(): int
107
    {
108
        return 1;
109
    }
110
 
111
    /**
112
     * Runs a test and collects its result in a TestResult instance.
113
     *
114
     * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
115
     * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException
116
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
117
     * @throws Exception
118
     */
119
    public function run(TestResult $result = null): TestResult
120
    {
121
        if ($result === null) {
122
            $result = new TestResult;
123
        }
124
 
125
        try {
126
            $sections = $this->parse();
127
        } catch (Exception $e) {
128
            $result->startTest($this);
129
            $result->addFailure($this, new SkippedTestError($e->getMessage()), 0);
130
            $result->endTest($this, 0);
131
 
132
            return $result;
133
        }
134
 
135
        $code     = $this->render($sections['FILE']);
136
        $xfail    = false;
137
        $settings = $this->parseIniSection($this->settings($result->getCollectCodeCoverageInformation()));
138
 
139
        $result->startTest($this);
140
 
141
        if (isset($sections['INI'])) {
142
            $settings = $this->parseIniSection($sections['INI'], $settings);
143
        }
144
 
145
        if (isset($sections['ENV'])) {
146
            $env = $this->parseEnvSection($sections['ENV']);
147
            $this->phpUtil->setEnv($env);
148
        }
149
 
150
        $this->phpUtil->setUseStderrRedirection(true);
151
 
152
        if ($result->enforcesTimeLimit()) {
153
            $this->phpUtil->setTimeout($result->getTimeoutForLargeTests());
154
        }
155
 
156
        $skip = $this->runSkip($sections, $result, $settings);
157
 
158
        if ($skip) {
159
            return $result;
160
        }
161
 
162
        if (isset($sections['XFAIL'])) {
163
            $xfail = trim($sections['XFAIL']);
164
        }
165
 
166
        if (isset($sections['STDIN'])) {
167
            $this->phpUtil->setStdin($sections['STDIN']);
168
        }
169
 
170
        if (isset($sections['ARGS'])) {
171
            $this->phpUtil->setArgs($sections['ARGS']);
172
        }
173
 
174
        if ($result->getCollectCodeCoverageInformation()) {
175
            $codeCoverageCacheDirectory = null;
176
            $pathCoverage               = false;
177
 
178
            $codeCoverage = $result->getCodeCoverage();
179
 
180
            if ($codeCoverage) {
181
                if ($codeCoverage->cachesStaticAnalysis()) {
182
                    $codeCoverageCacheDirectory = $codeCoverage->cacheDirectory();
183
                }
184
 
185
                $pathCoverage = $codeCoverage->collectsBranchAndPathCoverage();
186
            }
187
 
188
            $this->renderForCoverage($code, $pathCoverage, $codeCoverageCacheDirectory);
189
        }
190
 
191
        $timer = new Timer;
192
        $timer->start();
193
 
194
        $jobResult    = $this->phpUtil->runJob($code, $this->stringifyIni($settings));
195
        $time         = $timer->stop()->asSeconds();
196
        $this->output = $jobResult['stdout'] ?? '';
197
 
198
        if (isset($codeCoverage) && ($coverage = $this->cleanupForCoverage())) {
199
            $codeCoverage->append($coverage, $this, true, [], []);
200
        }
201
 
202
        try {
203
            $this->assertPhptExpectation($sections, $this->output);
204
        } catch (AssertionFailedError $e) {
205
            $failure = $e;
206
 
207
            if ($xfail !== false) {
208
                $failure = new IncompleteTestError($xfail, 0, $e);
209
            } elseif ($e instanceof ExpectationFailedException) {
210
                $comparisonFailure = $e->getComparisonFailure();
211
 
212
                if ($comparisonFailure) {
213
                    $diff = $comparisonFailure->getDiff();
214
                } else {
215
                    $diff = $e->getMessage();
216
                }
217
 
218
                $hint    = $this->getLocationHintFromDiff($diff, $sections);
219
                $trace   = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS));
220
                $failure = new PHPTAssertionFailedError(
221
                    $e->getMessage(),
222
                    0,
223
                    $trace[0]['file'],
224
                    $trace[0]['line'],
225
                    $trace,
226
                    $comparisonFailure ? $diff : ''
227
                );
228
            }
229
 
230
            $result->addFailure($this, $failure, $time);
231
        } catch (Throwable $t) {
232
            $result->addError($this, $t, $time);
233
        }
234
 
235
        if ($xfail !== false && $result->allCompletelyImplemented()) {
236
            $result->addFailure($this, new IncompleteTestError('XFAIL section but test passes'), $time);
237
        }
238
 
239
        $this->runClean($sections, $result->getCollectCodeCoverageInformation());
240
 
241
        $result->endTest($this, $time);
242
 
243
        return $result;
244
    }
245
 
246
    /**
247
     * Returns the name of the test case.
248
     */
249
    public function getName(): string
250
    {
251
        return $this->toString();
252
    }
253
 
254
    /**
255
     * Returns a string representation of the test case.
256
     */
257
    public function toString(): string
258
    {
259
        return $this->filename;
260
    }
261
 
262
    public function usesDataProvider(): bool
263
    {
264
        return false;
265
    }
266
 
267
    public function getNumAssertions(): int
268
    {
269
        return 1;
270
    }
271
 
272
    public function getActualOutput(): string
273
    {
274
        return $this->output;
275
    }
276
 
277
    public function hasOutput(): bool
278
    {
279
        return !empty($this->output);
280
    }
281
 
282
    public function sortId(): string
283
    {
284
        return $this->filename;
285
    }
286
 
287
    /**
288
     * @return list<ExecutionOrderDependency>
289
     */
290
    public function provides(): array
291
    {
292
        return [];
293
    }
294
 
295
    /**
296
     * @return list<ExecutionOrderDependency>
297
     */
298
    public function requires(): array
299
    {
300
        return [];
301
    }
302
 
303
    /**
304
     * Parse --INI-- section key value pairs and return as array.
305
     *
306
     * @param array|string $content
307
     */
308
    private function parseIniSection($content, array $ini = []): array
309
    {
310
        if (is_string($content)) {
311
            $content = explode("\n", trim($content));
312
        }
313
 
314
        foreach ($content as $setting) {
315
            if (strpos($setting, '=') === false) {
316
                continue;
317
            }
318
 
319
            $setting = explode('=', $setting, 2);
320
            $name    = trim($setting[0]);
321
            $value   = trim($setting[1]);
322
 
323
            if ($name === 'extension' || $name === 'zend_extension') {
324
                if (!isset($ini[$name])) {
325
                    $ini[$name] = [];
326
                }
327
 
328
                $ini[$name][] = $value;
329
 
330
                continue;
331
            }
332
 
333
            $ini[$name] = $value;
334
        }
335
 
336
        return $ini;
337
    }
338
 
339
    private function parseEnvSection(string $content): array
340
    {
341
        $env = [];
342
 
343
        foreach (explode("\n", trim($content)) as $e) {
344
            $e = explode('=', trim($e), 2);
345
 
346
            if (!empty($e[0]) && isset($e[1])) {
347
                $env[$e[0]] = $e[1];
348
            }
349
        }
350
 
351
        return $env;
352
    }
353
 
354
    /**
355
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
356
     * @throws Exception
357
     * @throws ExpectationFailedException
358
     */
359
    private function assertPhptExpectation(array $sections, string $output): void
360
    {
361
        $assertions = [
362
            'EXPECT'      => 'assertEquals',
363
            'EXPECTF'     => 'assertStringMatchesFormat',
364
            'EXPECTREGEX' => 'assertMatchesRegularExpression',
365
        ];
366
 
367
        $actual = preg_replace('/\r\n/', "\n", trim($output));
368
 
369
        foreach ($assertions as $sectionName => $sectionAssertion) {
370
            if (isset($sections[$sectionName])) {
371
                $sectionContent = preg_replace('/\r\n/', "\n", trim($sections[$sectionName]));
372
                $expected       = $sectionName === 'EXPECTREGEX' ? "/{$sectionContent}/" : $sectionContent;
373
 
374
                if ($expected === '') {
375
                    throw new Exception('No PHPT expectation found');
376
                }
377
 
378
                Assert::$sectionAssertion($expected, $actual);
379
 
380
                return;
381
            }
382
        }
383
 
384
        throw new Exception('No PHPT assertion found');
385
    }
386
 
387
    /**
388
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
389
     */
390
    private function runSkip(array &$sections, TestResult $result, array $settings): bool
391
    {
392
        if (!isset($sections['SKIPIF'])) {
393
            return false;
394
        }
395
 
396
        $skipif    = $this->render($sections['SKIPIF']);
397
        $jobResult = $this->phpUtil->runJob($skipif, $this->stringifyIni($settings));
398
 
399
        if (!strncasecmp('skip', ltrim($jobResult['stdout']), 4)) {
400
            $message = '';
401
 
402
            if (preg_match('/^\s*skip\s*(.+)\s*/i', $jobResult['stdout'], $skipMatch)) {
403
                $message = substr($skipMatch[1], 2);
404
            }
405
 
406
            $hint  = $this->getLocationHint($message, $sections, 'SKIPIF');
407
            $trace = array_merge($hint, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS));
408
            $result->addFailure(
409
                $this,
410
                new SyntheticSkippedError($message, 0, $trace[0]['file'], $trace[0]['line'], $trace),
411
 
412
            );
413
            $result->endTest($this, 0);
414
 
415
            return true;
416
        }
417
 
418
        return false;
419
    }
420
 
421
    private function runClean(array &$sections, bool $collectCoverage): void
422
    {
423
        $this->phpUtil->setStdin('');
424
        $this->phpUtil->setArgs('');
425
 
426
        if (isset($sections['CLEAN'])) {
427
            $cleanCode = $this->render($sections['CLEAN']);
428
 
429
            $this->phpUtil->runJob($cleanCode, $this->settings($collectCoverage));
430
        }
431
    }
432
 
433
    /**
434
     * @throws Exception
435
     */
436
    private function parse(): array
437
    {
438
        $sections = [];
439
        $section  = '';
440
 
441
        $unsupportedSections = [
442
            'CGI',
443
            'COOKIE',
444
            'DEFLATE_POST',
445
            'EXPECTHEADERS',
446
            'EXTENSIONS',
447
            'GET',
448
            'GZIP_POST',
449
            'HEADERS',
450
            'PHPDBG',
451
            'POST',
452
            'POST_RAW',
453
            'PUT',
454
            'REDIRECTTEST',
455
            'REQUEST',
456
        ];
457
 
458
        $lineNr = 0;
459
 
460
        foreach (file($this->filename) as $line) {
461
            $lineNr++;
462
 
463
            if (preg_match('/^--([_A-Z]+)--/', $line, $result)) {
464
                $section                        = $result[1];
465
                $sections[$section]             = '';
466
                $sections[$section . '_offset'] = $lineNr;
467
 
468
                continue;
469
            }
470
 
471
            if (empty($section)) {
472
                throw new Exception('Invalid PHPT file: empty section header');
473
            }
474
 
475
            $sections[$section] .= $line;
476
        }
477
 
478
        if (isset($sections['FILEEOF'])) {
479
            $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n");
480
            unset($sections['FILEEOF']);
481
        }
482
 
483
        $this->parseExternal($sections);
484
 
485
        if (!$this->validate($sections)) {
486
            throw new Exception('Invalid PHPT file');
487
        }
488
 
489
        foreach ($unsupportedSections as $section) {
490
            if (isset($sections[$section])) {
491
                throw new Exception(
492
                    "PHPUnit does not support PHPT {$section} sections"
493
                );
494
            }
495
        }
496
 
497
        return $sections;
498
    }
499
 
500
    /**
501
     * @throws Exception
502
     */
503
    private function parseExternal(array &$sections): void
504
    {
505
        $allowSections = [
506
            'FILE',
507
            'EXPECT',
508
            'EXPECTF',
509
            'EXPECTREGEX',
510
        ];
511
        $testDirectory = dirname($this->filename) . DIRECTORY_SEPARATOR;
512
 
513
        foreach ($allowSections as $section) {
514
            if (isset($sections[$section . '_EXTERNAL'])) {
515
                $externalFilename = trim($sections[$section . '_EXTERNAL']);
516
 
517
                if (!is_file($testDirectory . $externalFilename) ||
518
                    !is_readable($testDirectory . $externalFilename)) {
519
                    throw new Exception(
520
                        sprintf(
521
                            'Could not load --%s-- %s for PHPT file',
522
                            $section . '_EXTERNAL',
523
                            $testDirectory . $externalFilename
524
                        )
525
                    );
526
                }
527
 
528
                $sections[$section] = file_get_contents($testDirectory . $externalFilename);
529
            }
530
        }
531
    }
532
 
533
    private function validate(array &$sections): bool
534
    {
535
        $requiredSections = [
536
            'FILE',
537
            [
538
                'EXPECT',
539
                'EXPECTF',
540
                'EXPECTREGEX',
541
            ],
542
        ];
543
 
544
        foreach ($requiredSections as $section) {
545
            if (is_array($section)) {
546
                $foundSection = false;
547
 
548
                foreach ($section as $anySection) {
549
                    if (isset($sections[$anySection])) {
550
                        $foundSection = true;
551
 
552
                        break;
553
                    }
554
                }
555
 
556
                if (!$foundSection) {
557
                    return false;
558
                }
559
 
560
                continue;
561
            }
562
 
563
            if (!isset($sections[$section])) {
564
                return false;
565
            }
566
        }
567
 
568
        return true;
569
    }
570
 
571
    private function render(string $code): string
572
    {
573
        return str_replace(
574
            [
575
                '__DIR__',
576
                '__FILE__',
577
            ],
578
            [
579
                "'" . dirname($this->filename) . "'",
580
                "'" . $this->filename . "'",
581
            ],
582
            $code
583
        );
584
    }
585
 
586
    private function getCoverageFiles(): array
587
    {
588
        $baseDir  = dirname(realpath($this->filename)) . DIRECTORY_SEPARATOR;
589
        $basename = basename($this->filename, 'phpt');
590
 
591
        return [
592
            'coverage' => $baseDir . $basename . 'coverage',
593
            'job'      => $baseDir . $basename . 'php',
594
        ];
595
    }
596
 
597
    private function renderForCoverage(string &$job, bool $pathCoverage, ?string $codeCoverageCacheDirectory): void
598
    {
599
        $files = $this->getCoverageFiles();
600
 
601
        $template = new Template(
602
            __DIR__ . '/../Util/PHP/Template/PhptTestCase.tpl'
603
        );
604
 
605
        $composerAutoload = '\'\'';
606
 
607
        if (defined('PHPUNIT_COMPOSER_INSTALL')) {
608
            $composerAutoload = var_export(PHPUNIT_COMPOSER_INSTALL, true);
609
        }
610
 
611
        $phar = '\'\'';
612
 
613
        if (defined('__PHPUNIT_PHAR__')) {
614
            $phar = var_export(__PHPUNIT_PHAR__, true);
615
        }
616
 
617
        $globals = '';
618
 
619
        if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) {
620
            $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . var_export(
621
                $GLOBALS['__PHPUNIT_BOOTSTRAP'],
622
                true
623
            ) . ";\n";
624
        }
625
 
626
        if ($codeCoverageCacheDirectory === null) {
627
            $codeCoverageCacheDirectory = 'null';
628
        } else {
629
            $codeCoverageCacheDirectory = "'" . $codeCoverageCacheDirectory . "'";
630
        }
631
 
632
        $template->setVar(
633
            [
634
                'composerAutoload'           => $composerAutoload,
635
                'phar'                       => $phar,
636
                'globals'                    => $globals,
637
                'job'                        => $files['job'],
638
                'coverageFile'               => $files['coverage'],
639
                'driverMethod'               => $pathCoverage ? 'forLineAndPathCoverage' : 'forLineCoverage',
640
                'codeCoverageCacheDirectory' => $codeCoverageCacheDirectory,
641
            ]
642
        );
643
 
644
        file_put_contents($files['job'], $job);
645
 
646
        $job = $template->render();
647
    }
648
 
649
    private function cleanupForCoverage(): RawCodeCoverageData
650
    {
651
        $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]);
652
        $files    = $this->getCoverageFiles();
653
 
654
        if (is_file($files['coverage'])) {
655
            $buffer = @file_get_contents($files['coverage']);
656
 
657
            if ($buffer !== false) {
658
                $coverage = @unserialize($buffer);
659
 
660
                if ($coverage === false) {
661
                    $coverage = RawCodeCoverageData::fromXdebugWithoutPathCoverage([]);
662
                }
663
            }
664
        }
665
 
666
        foreach ($files as $file) {
667
            @unlink($file);
668
        }
669
 
670
        return $coverage;
671
    }
672
 
673
    private function stringifyIni(array $ini): array
674
    {
675
        $settings = [];
676
 
677
        foreach ($ini as $key => $value) {
678
            if (is_array($value)) {
679
                foreach ($value as $val) {
680
                    $settings[] = $key . '=' . $val;
681
                }
682
 
683
                continue;
684
            }
685
 
686
            $settings[] = $key . '=' . $value;
687
        }
688
 
689
        return $settings;
690
    }
691
 
692
    private function getLocationHintFromDiff(string $message, array $sections): array
693
    {
694
        $needle       = '';
695
        $previousLine = '';
696
        $block        = 'message';
697
 
698
        foreach (preg_split('/\r\n|\r|\n/', $message) as $line) {
699
            $line = trim($line);
700
 
701
            if ($block === 'message' && $line === '--- Expected') {
702
                $block = 'expected';
703
            }
704
 
705
            if ($block === 'expected' && $line === '@@ @@') {
706
                $block = 'diff';
707
            }
708
 
709
            if ($block === 'diff') {
710
                if (strpos($line, '+') === 0) {
711
                    $needle = $this->getCleanDiffLine($previousLine);
712
 
713
                    break;
714
                }
715
 
716
                if (strpos($line, '-') === 0) {
717
                    $needle = $this->getCleanDiffLine($line);
718
 
719
                    break;
720
                }
721
            }
722
 
723
            if (!empty($line)) {
724
                $previousLine = $line;
725
            }
726
        }
727
 
728
        return $this->getLocationHint($needle, $sections);
729
    }
730
 
731
    private function getCleanDiffLine(string $line): string
732
    {
733
        if (preg_match('/^[\-+]([\'\"]?)(.*)\1$/', $line, $matches)) {
734
            $line = $matches[2];
735
        }
736
 
737
        return $line;
738
    }
739
 
740
    private function getLocationHint(string $needle, array $sections, ?string $sectionName = null): array
741
    {
742
        $needle = trim($needle);
743
 
744
        if (empty($needle)) {
745
            return [[
746
                'file' => realpath($this->filename),
747
                'line' => 1,
748
            ]];
749
        }
750
 
751
        if ($sectionName) {
752
            $search = [$sectionName];
753
        } else {
754
            $search = [
755
                // 'FILE',
756
                'EXPECT',
757
                'EXPECTF',
758
                'EXPECTREGEX',
759
            ];
760
        }
761
 
762
        $sectionOffset = null;
763
 
764
        foreach ($search as $section) {
765
            if (!isset($sections[$section])) {
766
                continue;
767
            }
768
 
769
            if (isset($sections[$section . '_EXTERNAL'])) {
770
                $externalFile = trim($sections[$section . '_EXTERNAL']);
771
 
772
                return [
773
                    [
774
                        'file' => realpath(dirname($this->filename) . DIRECTORY_SEPARATOR . $externalFile),
775
                        'line' => 1,
776
                    ],
777
                    [
778
                        'file' => realpath($this->filename),
779
                        'line' => ($sections[$section . '_EXTERNAL_offset'] ?? 0) + 1,
780
                    ],
781
                ];
782
            }
783
 
784
            $sectionOffset = $sections[$section . '_offset'] ?? 0;
785
            $offset        = $sectionOffset + 1;
786
 
787
            foreach (preg_split('/\r\n|\r|\n/', $sections[$section]) as $line) {
788
                if (strpos($line, $needle) !== false) {
789
                    return [[
790
                        'file' => realpath($this->filename),
791
                        'line' => $offset,
792
                    ]];
793
                }
794
                $offset++;
795
            }
796
        }
797
 
798
        if ($sectionName) {
799
            // String not found in specified section, show user the start of the named section
800
            return [[
801
                'file' => realpath($this->filename),
802
                'line' => $sectionOffset,
803
            ]];
804
        }
805
 
806
        // No section specified, show user start of code
807
        return [[
808
            'file' => realpath($this->filename),
809
            'line' => 1,
810
        ]];
811
    }
812
 
813
    /**
814
     * @psalm-return list<string>
815
     */
816
    private function settings(bool $collectCoverage): array
817
    {
818
        $settings = [
819
            'allow_url_fopen=1',
820
            'auto_append_file=',
821
            'auto_prepend_file=',
822
            'disable_functions=',
823
            'display_errors=1',
824
            'docref_ext=.html',
825
            'docref_root=',
826
            'error_append_string=',
827
            'error_prepend_string=',
828
            'error_reporting=-1',
829
            'html_errors=0',
830
            'log_errors=0',
831
            'open_basedir=',
832
            'output_buffering=Off',
833
            'output_handler=',
834
            'report_memleaks=0',
835
            'report_zend_debug=0',
836
        ];
837
 
838
        if (extension_loaded('pcov')) {
839
            if ($collectCoverage) {
840
                $settings[] = 'pcov.enabled=1';
841
            } else {
842
                $settings[] = 'pcov.enabled=0';
843
            }
844
        }
845
 
846
        if (extension_loaded('xdebug')) {
847
            if (version_compare(phpversion('xdebug'), '3', '>=')) {
848
                if ($collectCoverage) {
849
                    $settings[] = 'xdebug.mode=coverage';
850
                } else {
851
                    $settings[] = 'xdebug.mode=off';
852
                }
853
            } else {
854
                $settings[] = 'xdebug.default_enable=0';
855
 
856
                if ($collectCoverage) {
857
                    $settings[] = 'xdebug.coverage_enable=1';
858
                }
859
            }
860
        }
861
 
862
        return $settings;
863
    }
864
}