TestCase::at()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
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\Framework;
11
12
use DeepCopy\DeepCopy;
13
use PHPUnit\Framework\Constraint\Exception as ExceptionConstraint;
14
use PHPUnit\Framework\Constraint\ExceptionCode;
15
use PHPUnit\Framework\Constraint\ExceptionMessage;
16
use PHPUnit\Framework\Constraint\ExceptionMessageRegularExpression;
17
use PHPUnit\Framework\MockObject\Generator as MockGenerator;
18
use PHPUnit\Framework\MockObject\Matcher\AnyInvokedCount as AnyInvokedCountMatcher;
19
use PHPUnit\Framework\MockObject\Matcher\InvokedAtIndex as InvokedAtIndexMatcher;
20
use PHPUnit\Framework\MockObject\Matcher\InvokedAtLeastCount as InvokedAtLeastCountMatcher;
21
use PHPUnit\Framework\MockObject\Matcher\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher;
22
use PHPUnit\Framework\MockObject\Matcher\InvokedAtMostCount as InvokedAtMostCountMatcher;
23
use PHPUnit\Framework\MockObject\Matcher\InvokedCount as InvokedCountMatcher;
24
use PHPUnit\Framework\MockObject\MockBuilder;
25
use PHPUnit\Framework\MockObject\MockObject;
26
use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub;
27
use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub;
28
use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub;
29
use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub;
30
use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub;
31
use PHPUnit\Framework\MockObject\Stub\ReturnStub;
32
use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub;
33
use PHPUnit\Runner\BaseTestRunner;
34
use PHPUnit\Runner\PhptTestCase;
35
use PHPUnit\Util\GlobalState;
36
use PHPUnit\Util\PHP\AbstractPhpProcess;
37
use Prophecy;
38
use Prophecy\Exception\Prediction\PredictionException;
39
use Prophecy\Prophecy\MethodProphecy;
40
use Prophecy\Prophecy\ObjectProphecy;
41
use Prophecy\Prophet;
42
use ReflectionClass;
43
use ReflectionException;
44
use ReflectionObject;
45
use SebastianBergmann\Comparator\Comparator;
46
use SebastianBergmann\Comparator\Factory as ComparatorFactory;
47
use SebastianBergmann\Diff\Differ;
48
use SebastianBergmann\Exporter\Exporter;
49
use SebastianBergmann\GlobalState\Blacklist;
50
use SebastianBergmann\GlobalState\Restorer;
51
use SebastianBergmann\GlobalState\Snapshot;
52
use SebastianBergmann\ObjectEnumerator\Enumerator;
53
use Text_Template;
54
use Throwable;
55
56
abstract class TestCase extends Assert implements SelfDescribing, Test
57
{
58
    private const LOCALE_CATEGORIES = [\LC_ALL, \LC_COLLATE, \LC_CTYPE, \LC_MONETARY, \LC_NUMERIC, \LC_TIME];
59
60
    /**
61
     * @var bool
62
     */
63
    protected $backupGlobals;
64
65
    /**
66
     * @var array
67
     */
68
    protected $backupGlobalsBlacklist = [];
69
70
    /**
71
     * @var bool
72
     */
73
    protected $backupStaticAttributes;
74
75
    /**
76
     * @var array
77
     */
78
    protected $backupStaticAttributesBlacklist = [];
79
80
    /**
81
     * @var bool
82
     */
83
    protected $runTestInSeparateProcess;
84
85
    /**
86
     * @var bool
87
     */
88
    protected $preserveGlobalState = true;
89
90
    /**
91
     * @var bool
92
     */
93
    private $runClassInSeparateProcess;
94
95
    /**
96
     * @var bool
97
     */
98
    private $inIsolation = false;
99
100
    /**
101
     * @var array
102
     */
103
    private $data;
104
105
    /**
106
     * @var string
107
     */
108
    private $dataName;
109
110
    /**
111
     * @var bool
112
     */
113
    private $useErrorHandler;
114
115
    /**
116
     * @var null|string
117
     */
118
    private $expectedException;
119
120
    /**
121
     * @var null|string
122
     */
123
    private $expectedExceptionMessage;
124
125
    /**
126
     * @var null|string
127
     */
128
    private $expectedExceptionMessageRegExp;
129
130
    /**
131
     * @var null|int|string
132
     */
133
    private $expectedExceptionCode;
134
135
    /**
136
     * @var string
137
     */
138
    private $name;
139
140
    /**
141
     * @var string[]
142
     */
143
    private $dependencies = [];
144
145
    /**
146
     * @var array
147
     */
148
    private $dependencyInput = [];
149
150
    /**
151
     * @var array
152
     */
153
    private $iniSettings = [];
154
155
    /**
156
     * @var array
157
     */
158
    private $locale = [];
159
160
    /**
161
     * @var array
162
     */
163
    private $mockObjects = [];
164
165
    /**
166
     * @var MockGenerator
167
     */
168
    private $mockObjectGenerator;
169
170
    /**
171
     * @var int
172
     */
173
    private $status = BaseTestRunner::STATUS_UNKNOWN;
174
175
    /**
176
     * @var string
177
     */
178
    private $statusMessage = '';
179
180
    /**
181
     * @var int
182
     */
183
    private $numAssertions = 0;
184
185
    /**
186
     * @var TestResult
187
     */
188
    private $result;
189
190
    /**
191
     * @var mixed
192
     */
193
    private $testResult;
194
195
    /**
196
     * @var string
197
     */
198
    private $output = '';
199
200
    /**
201
     * @var string
202
     */
203
    private $outputExpectedRegex;
204
205
    /**
206
     * @var string
207
     */
208
    private $outputExpectedString;
209
210
    /**
211
     * @var mixed
212
     */
213
    private $outputCallback = false;
214
215
    /**
216
     * @var bool
217
     */
218
    private $outputBufferingActive = false;
219
220
    /**
221
     * @var int
222
     */
223
    private $outputBufferingLevel;
224
225
    /**
226
     * @var Snapshot
227
     */
228
    private $snapshot;
229
230
    /**
231
     * @var Prophecy\Prophet
232
     */
233
    private $prophet;
234
235
    /**
236
     * @var bool
237
     */
238
    private $beStrictAboutChangesToGlobalState = false;
239
240
    /**
241
     * @var bool
242
     */
243
    private $registerMockObjectsFromTestArgumentsRecursively = false;
244
245
    /**
246
     * @var string[]
247
     */
248
    private $warnings = [];
249
250
    /**
251
     * @var array
252
     */
253
    private $groups = [];
254
255
    /**
256
     * @var bool
257
     */
258
    private $doesNotPerformAssertions = false;
259
260
    /**
261
     * @var Comparator[]
262
     */
263
    private $customComparators = [];
264
265
    /**
266
     * Returns a matcher that matches when the method is executed
267
     * zero or more times.
268
     */
269
    public static function any(): AnyInvokedCountMatcher
270
    {
271
        return new AnyInvokedCountMatcher;
272
    }
273
274
    /**
275
     * Returns a matcher that matches when the method is never executed.
276
     */
277
    public static function never(): InvokedCountMatcher
278
    {
279
        return new InvokedCountMatcher(0);
280
    }
281
282
    /**
283
     * Returns a matcher that matches when the method is executed
284
     * at least N times.
285
     */
286
    public static function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher
287
    {
288
        return new InvokedAtLeastCountMatcher(
289
            $requiredInvocations
290
        );
291
    }
292
293
    /**
294
     * Returns a matcher that matches when the method is executed at least once.
295
     */
296
    public static function atLeastOnce(): InvokedAtLeastOnceMatcher
297
    {
298
        return new InvokedAtLeastOnceMatcher;
299
    }
300
301
    /**
302
     * Returns a matcher that matches when the method is executed exactly once.
303
     */
304
    public static function once(): InvokedCountMatcher
305
    {
306
        return new InvokedCountMatcher(1);
307
    }
308
309
    /**
310
     * Returns a matcher that matches when the method is executed
311
     * exactly $count times.
312
     */
313
    public static function exactly(int $count): InvokedCountMatcher
314
    {
315
        return new InvokedCountMatcher($count);
316
    }
317
318
    /**
319
     * Returns a matcher that matches when the method is executed
320
     * at most N times.
321
     */
322
    public static function atMost(int $allowedInvocations): InvokedAtMostCountMatcher
323
    {
324
        return new InvokedAtMostCountMatcher($allowedInvocations);
325
    }
326
327
    /**
328
     * Returns a matcher that matches when the method is executed
329
     * at the given index.
330
     */
331
    public static function at(int $index): InvokedAtIndexMatcher
332
    {
333
        return new InvokedAtIndexMatcher($index);
334
    }
335
336
    public static function returnValue($value): ReturnStub
337
    {
338
        return new ReturnStub($value);
339
    }
340
341
    public static function returnValueMap(array $valueMap): ReturnValueMapStub
342
    {
343
        return new ReturnValueMapStub($valueMap);
344
    }
345
346
    public static function returnArgument(int $argumentIndex): ReturnArgumentStub
347
    {
348
        return new ReturnArgumentStub($argumentIndex);
349
    }
350
351
    public static function returnCallback($callback): ReturnCallbackStub
352
    {
353
        return new ReturnCallbackStub($callback);
354
    }
355
356
    /**
357
     * Returns the current object.
358
     *
359
     * This method is useful when mocking a fluent interface.
360
     */
361
    public static function returnSelf(): ReturnSelfStub
362
    {
363
        return new ReturnSelfStub;
364
    }
365
366
    public static function throwException(Throwable $exception): ExceptionStub
367
    {
368
        return new ExceptionStub($exception);
369
    }
370
371
    public static function onConsecutiveCalls(...$args): ConsecutiveCallsStub
372
    {
373
        return new ConsecutiveCallsStub($args);
374
    }
375
376
    /**
377
     * @param string $name
378
     * @param string $dataName
379
     */
380
    public function __construct($name = null, array $data = [], $dataName = '')
381
    {
382
        if ($name !== null) {
383
            $this->setName($name);
384
        }
385
386
        $this->data     = $data;
387
        $this->dataName = $dataName;
388
    }
389
390
    /**
391
     * This method is called before the first test of this test class is run.
392
     */
393
    public static function setUpBeforeClass()/* The :void return type declaration that should be here would cause a BC issue */
394
    {
395
    }
396
397
    /**
398
     * This method is called after the last test of this test class is run.
399
     */
400
    public static function tearDownAfterClass()/* The :void return type declaration that should be here would cause a BC issue */
401
    {
402
    }
403
404
    /**
405
     * This method is called before each test.
406
     */
407
    protected function setUp()/* The :void return type declaration that should be here would cause a BC issue */
408
    {
409
    }
410
411
    /**
412
     * This method is called after each test.
413
     */
414
    protected function tearDown()/* The :void return type declaration that should be here would cause a BC issue */
415
    {
416
    }
417
418
    /**
419
     * Returns a string representation of the test case.
420
     *
421
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
422
     * @throws \ReflectionException
423
     */
424
    public function toString(): string
425
    {
426
        $class = new ReflectionClass($this);
427
428
        $buffer = \sprintf(
429
            '%s::%s',
430
            $class->name,
431
            $this->getName(false)
432
        );
433
434
        return $buffer . $this->getDataSetAsString();
435
    }
436
437
    public function count(): int
438
    {
439
        return 1;
440
    }
441
442
    public function getGroups(): array
443
    {
444
        return $this->groups;
445
    }
446
447
    public function setGroups(array $groups): void
448
    {
449
        $this->groups = $groups;
450
    }
451
452
    public function getAnnotations(): array
453
    {
454
        return \PHPUnit\Util\Test::parseTestMethodAnnotations(
455
            \get_class($this),
456
            $this->name
457
        );
458
    }
459
460
    /**
461
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
462
     */
463
    public function getName(bool $withDataSet = true): ?string
464
    {
465
        if ($withDataSet) {
466
            return $this->name . $this->getDataSetAsString(false);
467
        }
468
469
        return $this->name;
470
    }
471
472
    /**
473
     * Returns the size of the test.
474
     *
475
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
476
     */
477
    public function getSize(): int
478
    {
479
        return \PHPUnit\Util\Test::getSize(
480
            \get_class($this),
481
            $this->getName(false)
482
        );
483
    }
484
485
    /**
486
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
487
     */
488
    public function hasSize(): bool
489
    {
490
        return $this->getSize() !== \PHPUnit\Util\Test::UNKNOWN;
491
    }
492
493
    /**
494
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
495
     */
496
    public function isSmall(): bool
497
    {
498
        return $this->getSize() === \PHPUnit\Util\Test::SMALL;
499
    }
500
501
    /**
502
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
503
     */
504
    public function isMedium(): bool
505
    {
506
        return $this->getSize() === \PHPUnit\Util\Test::MEDIUM;
507
    }
508
509
    /**
510
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
511
     */
512
    public function isLarge(): bool
513
    {
514
        return $this->getSize() === \PHPUnit\Util\Test::LARGE;
515
    }
516
517
    public function getActualOutput(): string
518
    {
519
        if (!$this->outputBufferingActive) {
520
            return $this->output;
521
        }
522
523
        return \ob_get_contents();
524
    }
525
526
    public function hasOutput(): bool
527
    {
528
        if ($this->output === '') {
529
            return false;
530
        }
531
532
        if ($this->hasExpectationOnOutput()) {
533
            return false;
534
        }
535
536
        return true;
537
    }
538
539
    public function doesNotPerformAssertions(): bool
540
    {
541
        return $this->doesNotPerformAssertions;
542
    }
543
544
    public function expectOutputRegex(string $expectedRegex): void
545
    {
546
        $this->outputExpectedRegex = $expectedRegex;
547
    }
548
549
    public function expectOutputString(string $expectedString): void
550
    {
551
        $this->outputExpectedString = $expectedString;
552
    }
553
554
    public function hasExpectationOnOutput(): bool
555
    {
556
        return \is_string($this->outputExpectedString) || \is_string($this->outputExpectedRegex);
557
    }
558
559
    public function getExpectedException(): ?string
560
    {
561
        return $this->expectedException;
562
    }
563
564
    /**
565
     * @return null|int|string
566
     */
567
    public function getExpectedExceptionCode()
568
    {
569
        return $this->expectedExceptionCode;
570
    }
571
572
    public function getExpectedExceptionMessage(): ?string
573
    {
574
        return $this->expectedExceptionMessage;
575
    }
576
577
    public function getExpectedExceptionMessageRegExp(): ?string
578
    {
579
        return $this->expectedExceptionMessageRegExp;
580
    }
581
582
    public function expectException(string $exception): void
583
    {
584
        $this->expectedException = $exception;
585
    }
586
587
    /**
588
     * @param int|string $code
589
     */
590
    public function expectExceptionCode($code): void
591
    {
592
        $this->expectedExceptionCode = $code;
593
    }
594
595
    public function expectExceptionMessage(string $message): void
596
    {
597
        $this->expectedExceptionMessage = $message;
598
    }
599
600
    public function expectExceptionMessageRegExp(string $messageRegExp): void
601
    {
602
        $this->expectedExceptionMessageRegExp = $messageRegExp;
603
    }
604
605
    /**
606
     * Sets up an expectation for an exception to be raised by the code under test.
607
     * Information for expected exception class, expected exception message, and
608
     * expected exception code are retrieved from a given Exception object.
609
     */
610
    public function expectExceptionObject(\Exception $exception): void
611
    {
612
        $this->expectException(\get_class($exception));
613
        $this->expectExceptionMessage($exception->getMessage());
614
        $this->expectExceptionCode($exception->getCode());
615
    }
616
617
    public function expectNotToPerformAssertions()
618
    {
619
        $this->doesNotPerformAssertions = true;
620
    }
621
622
    public function setRegisterMockObjectsFromTestArgumentsRecursively(bool $flag): void
623
    {
624
        $this->registerMockObjectsFromTestArgumentsRecursively = $flag;
625
    }
626
627
    public function setUseErrorHandler(bool $useErrorHandler): void
628
    {
629
        $this->useErrorHandler = $useErrorHandler;
630
    }
631
632
    public function getStatus(): int
633
    {
634
        return $this->status;
635
    }
636
637
    public function markAsRisky(): void
638
    {
639
        $this->status = BaseTestRunner::STATUS_RISKY;
640
    }
641
642
    public function getStatusMessage(): string
643
    {
644
        return $this->statusMessage;
645
    }
646
647
    public function hasFailed(): bool
648
    {
649
        $status = $this->getStatus();
650
651
        return $status === BaseTestRunner::STATUS_FAILURE || $status === BaseTestRunner::STATUS_ERROR;
652
    }
653
654
    /**
655
     * Runs the test case and collects the results in a TestResult object.
656
     * If no TestResult object is passed a new one will be created.
657
     *
658
     * @throws CodeCoverageException
659
     * @throws ReflectionException
660
     * @throws \SebastianBergmann\CodeCoverage\CoveredCodeNotExecutedException
661
     * @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
662
     * @throws \SebastianBergmann\CodeCoverage\MissingCoversAnnotationException
663
     * @throws \SebastianBergmann\CodeCoverage\RuntimeException
664
     * @throws \SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException
665
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
666
     */
667
    public function run(TestResult $result = null): TestResult
668
    {
669
        if ($result === null) {
670
            $result = $this->createResult();
671
        }
672
673
        if (!$this instanceof WarningTestCase) {
674
            $this->setTestResultObject($result);
675
            $this->setUseErrorHandlerFromAnnotation();
676
        }
677
678
        if ($this->useErrorHandler !== null) {
679
            $oldErrorHandlerSetting = $result->getConvertErrorsToExceptions();
680
            $result->convertErrorsToExceptions($this->useErrorHandler);
681
        }
682
683
        if (!$this instanceof WarningTestCase &&
684
            !$this instanceof SkippedTestCase &&
685
            !$this->handleDependencies()) {
686
            return $result;
687
        }
688
689
        if ($this->runInSeparateProcess()) {
690
            $runEntireClass = $this->runClassInSeparateProcess && !$this->runTestInSeparateProcess;
691
692
            $class = new ReflectionClass($this);
693
694
            if ($runEntireClass) {
695
                $template = new Text_Template(
696
                    __DIR__ . '/../Util/PHP/Template/TestCaseClass.tpl'
697
                );
698
            } else {
699
                $template = new Text_Template(
700
                    __DIR__ . '/../Util/PHP/Template/TestCaseMethod.tpl'
701
                );
702
            }
703
704
            if ($this->preserveGlobalState) {
705
                $constants     = GlobalState::getConstantsAsString();
706
                $globals       = GlobalState::getGlobalsAsString();
707
                $includedFiles = GlobalState::getIncludedFilesAsString();
708
                $iniSettings   = GlobalState::getIniSettingsAsString();
709
            } else {
710
                $constants = '';
711
712
                if (!empty($GLOBALS['__PHPUNIT_BOOTSTRAP'])) {
713
                    $globals = '$GLOBALS[\'__PHPUNIT_BOOTSTRAP\'] = ' . \var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], true) . ";\n";
714
                } else {
715
                    $globals = '';
716
                }
717
                $includedFiles = '';
718
                $iniSettings   = '';
719
            }
720
721
            $coverage                                   = $result->getCollectCodeCoverageInformation() ? 'true' : 'false';
722
            $isStrictAboutTestsThatDoNotTestAnything    = $result->isStrictAboutTestsThatDoNotTestAnything() ? 'true' : 'false';
723
            $isStrictAboutOutputDuringTests             = $result->isStrictAboutOutputDuringTests() ? 'true' : 'false';
724
            $enforcesTimeLimit                          = $result->enforcesTimeLimit() ? 'true' : 'false';
725
            $isStrictAboutTodoAnnotatedTests            = $result->isStrictAboutTodoAnnotatedTests() ? 'true' : 'false';
726
            $isStrictAboutResourceUsageDuringSmallTests = $result->isStrictAboutResourceUsageDuringSmallTests() ? 'true' : 'false';
727
728
            if (\defined('PHPUNIT_COMPOSER_INSTALL')) {
729
                $composerAutoload = \var_export(PHPUNIT_COMPOSER_INSTALL, true);
0 ignored issues
show
Bug introduced by
The constant PHPUnit\Framework\PHPUNIT_COMPOSER_INSTALL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
730
            } else {
731
                $composerAutoload = '\'\'';
732
            }
733
734
            if (\defined('__PHPUNIT_PHAR__')) {
735
                $phar = \var_export(__PHPUNIT_PHAR__, true);
0 ignored issues
show
Bug introduced by
The constant __PHPUNIT_PHAR__ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
736
            } else {
737
                $phar = '\'\'';
738
            }
739
740
            if ($result->getCodeCoverage()) {
741
                $codeCoverageFilter = $result->getCodeCoverage()->filter();
742
            } else {
743
                $codeCoverageFilter = null;
744
            }
745
746
            $data               = \var_export(\serialize($this->data), true);
747
            $dataName           = \var_export($this->dataName, true);
748
            $dependencyInput    = \var_export(\serialize($this->dependencyInput), true);
749
            $includePath        = \var_export(\get_include_path(), true);
750
            $codeCoverageFilter = \var_export(\serialize($codeCoverageFilter), true);
751
            // must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC
752
            // the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences
753
            $data               = "'." . $data . ".'";
754
            $dataName           = "'.(" . $dataName . ").'";
755
            $dependencyInput    = "'." . $dependencyInput . ".'";
756
            $includePath        = "'." . $includePath . ".'";
757
            $codeCoverageFilter = "'." . $codeCoverageFilter . ".'";
758
759
            $configurationFilePath = $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] ?? '';
760
761
            $var = [
762
                'composerAutoload'                           => $composerAutoload,
763
                'phar'                                       => $phar,
764
                'filename'                                   => $class->getFileName(),
765
                'className'                                  => $class->getName(),
766
                'collectCodeCoverageInformation'             => $coverage,
767
                'data'                                       => $data,
768
                'dataName'                                   => $dataName,
769
                'dependencyInput'                            => $dependencyInput,
770
                'constants'                                  => $constants,
771
                'globals'                                    => $globals,
772
                'include_path'                               => $includePath,
773
                'included_files'                             => $includedFiles,
774
                'iniSettings'                                => $iniSettings,
775
                'isStrictAboutTestsThatDoNotTestAnything'    => $isStrictAboutTestsThatDoNotTestAnything,
776
                'isStrictAboutOutputDuringTests'             => $isStrictAboutOutputDuringTests,
777
                'enforcesTimeLimit'                          => $enforcesTimeLimit,
778
                'isStrictAboutTodoAnnotatedTests'            => $isStrictAboutTodoAnnotatedTests,
779
                'isStrictAboutResourceUsageDuringSmallTests' => $isStrictAboutResourceUsageDuringSmallTests,
780
                'codeCoverageFilter'                         => $codeCoverageFilter,
781
                'configurationFilePath'                      => $configurationFilePath,
782
                'name'                                       => $this->getName(false),
783
            ];
784
785
            if (!$runEntireClass) {
786
                $var['methodName'] = $this->name;
787
            }
788
789
            $template->setVar(
790
                $var
791
            );
792
793
            $php = AbstractPhpProcess::factory();
794
            $php->runTestJob($template->render(), $this, $result);
795
        } else {
796
            $result->run($this);
797
        }
798
799
        if (isset($oldErrorHandlerSetting)) {
800
            $result->convertErrorsToExceptions($oldErrorHandlerSetting);
801
        }
802
803
        $this->result = null;
804
805
        return $result;
806
    }
807
808
    /**
809
     * @throws \Throwable
810
     */
811
    public function runBare(): void
812
    {
813
        $this->numAssertions = 0;
814
815
        $this->snapshotGlobalState();
816
        $this->startOutputBuffering();
817
        \clearstatcache();
818
        $currentWorkingDirectory = \getcwd();
819
820
        $hookMethods = \PHPUnit\Util\Test::getHookMethods(\get_class($this));
821
822
        $hasMetRequirements = false;
823
824
        try {
825
            $this->checkRequirements();
826
            $hasMetRequirements = true;
827
828
            if ($this->inIsolation) {
829
                foreach ($hookMethods['beforeClass'] as $method) {
830
                    $this->$method();
831
                }
832
            }
833
834
            $this->setExpectedExceptionFromAnnotation();
835
            $this->setDoesNotPerformAssertionsFromAnnotation();
836
837
            foreach ($hookMethods['before'] as $method) {
838
                $this->$method();
839
            }
840
841
            $this->assertPreConditions();
842
            $this->testResult = $this->runTest();
843
            $this->verifyMockObjects();
844
            $this->assertPostConditions();
845
846
            if (!empty($this->warnings)) {
847
                throw new Warning(
848
                    \implode(
849
                        "\n",
850
                        \array_unique($this->warnings)
851
                    )
852
                );
853
            }
854
855
            $this->status = BaseTestRunner::STATUS_PASSED;
856
        } catch (IncompleteTest $e) {
857
            $this->status        = BaseTestRunner::STATUS_INCOMPLETE;
858
            $this->statusMessage = $e->getMessage();
859
        } catch (SkippedTest $e) {
860
            $this->status        = BaseTestRunner::STATUS_SKIPPED;
861
            $this->statusMessage = $e->getMessage();
862
        } catch (Warning $e) {
863
            $this->status        = BaseTestRunner::STATUS_WARNING;
864
            $this->statusMessage = $e->getMessage();
865
        } catch (AssertionFailedError $e) {
866
            $this->status        = BaseTestRunner::STATUS_FAILURE;
867
            $this->statusMessage = $e->getMessage();
868
        } catch (PredictionException $e) {
869
            $this->status        = BaseTestRunner::STATUS_FAILURE;
870
            $this->statusMessage = $e->getMessage();
871
        } catch (Throwable $_e) {
872
            $e                   = $_e;
873
            $this->status        = BaseTestRunner::STATUS_ERROR;
874
            $this->statusMessage = $_e->getMessage();
875
        }
876
877
        $this->mockObjects = [];
878
        $this->prophet     = null;
879
880
        // Tear down the fixture. An exception raised in tearDown() will be
881
        // caught and passed on when no exception was raised before.
882
        try {
883
            if ($hasMetRequirements) {
884
                foreach ($hookMethods['after'] as $method) {
885
                    $this->$method();
886
                }
887
888
                if ($this->inIsolation) {
889
                    foreach ($hookMethods['afterClass'] as $method) {
890
                        $this->$method();
891
                    }
892
                }
893
            }
894
        } catch (Throwable $_e) {
895
            $e = $e ?? $_e;
896
        }
897
898
        try {
899
            $this->stopOutputBuffering();
900
        } catch (RiskyTestError $_e) {
901
            $e = $e ?? $_e;
902
        }
903
904
        if (isset($_e)) {
905
            $this->status        = BaseTestRunner::STATUS_ERROR;
906
            $this->statusMessage = $_e->getMessage();
907
        }
908
909
        \clearstatcache();
910
911
        if ($currentWorkingDirectory != \getcwd()) {
912
            \chdir($currentWorkingDirectory);
913
        }
914
915
        $this->restoreGlobalState();
916
        $this->unregisterCustomComparators();
917
        $this->cleanupIniSettings();
918
        $this->cleanupLocaleSettings();
919
        \libxml_clear_errors();
920
921
        // Perform assertion on output.
922
        if (!isset($e)) {
923
            try {
924
                if ($this->outputExpectedRegex !== null) {
925
                    $this->assertRegExp($this->outputExpectedRegex, $this->output);
926
                } elseif ($this->outputExpectedString !== null) {
927
                    $this->assertEquals($this->outputExpectedString, $this->output);
928
                }
929
            } catch (Throwable $_e) {
930
                $e = $_e;
931
            }
932
        }
933
934
        // Workaround for missing "finally".
935
        if (isset($e)) {
936
            if ($e instanceof PredictionException) {
937
                $e = new AssertionFailedError($e->getMessage());
938
            }
939
940
            $this->onNotSuccessfulTest($e);
941
        }
942
    }
943
944
    public function setName(string $name): void
945
    {
946
        $this->name = $name;
947
    }
948
949
    /**
950
     * @param string[] $dependencies
951
     */
952
    public function setDependencies(array $dependencies): void
953
    {
954
        $this->dependencies = $dependencies;
955
    }
956
957
    public function getDependencies(): array
958
    {
959
        return $this->dependencies;
960
    }
961
962
    public function hasDependencies(): bool
963
    {
964
        return \count($this->dependencies) > 0;
965
    }
966
967
    public function setDependencyInput(array $dependencyInput): void
968
    {
969
        $this->dependencyInput = $dependencyInput;
970
    }
971
972
    public function setBeStrictAboutChangesToGlobalState(?bool $beStrictAboutChangesToGlobalState): void
973
    {
974
        $this->beStrictAboutChangesToGlobalState = $beStrictAboutChangesToGlobalState;
975
    }
976
977
    public function setBackupGlobals(?bool $backupGlobals): void
978
    {
979
        if ($this->backupGlobals === null && $backupGlobals !== null) {
980
            $this->backupGlobals = $backupGlobals;
981
        }
982
    }
983
984
    public function setBackupStaticAttributes(?bool $backupStaticAttributes): void
985
    {
986
        if ($this->backupStaticAttributes === null && $backupStaticAttributes !== null) {
987
            $this->backupStaticAttributes = $backupStaticAttributes;
988
        }
989
    }
990
991
    public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess): void
992
    {
993
        if ($this->runTestInSeparateProcess === null) {
994
            $this->runTestInSeparateProcess = $runTestInSeparateProcess;
995
        }
996
    }
997
998
    public function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void
999
    {
1000
        if ($this->runClassInSeparateProcess === null) {
1001
            $this->runClassInSeparateProcess = $runClassInSeparateProcess;
1002
        }
1003
    }
1004
1005
    public function setPreserveGlobalState(bool $preserveGlobalState): void
1006
    {
1007
        $this->preserveGlobalState = $preserveGlobalState;
1008
    }
1009
1010
    public function setInIsolation(bool $inIsolation): void
1011
    {
1012
        $this->inIsolation = $inIsolation;
1013
    }
1014
1015
    public function isInIsolation(): bool
1016
    {
1017
        return $this->inIsolation;
1018
    }
1019
1020
    public function getResult()
1021
    {
1022
        return $this->testResult;
1023
    }
1024
1025
    public function setResult($result): void
1026
    {
1027
        $this->testResult = $result;
1028
    }
1029
1030
    public function setOutputCallback(callable $callback): void
1031
    {
1032
        $this->outputCallback = $callback;
1033
    }
1034
1035
    public function getTestResultObject(): ?TestResult
1036
    {
1037
        return $this->result;
1038
    }
1039
1040
    public function setTestResultObject(TestResult $result): void
1041
    {
1042
        $this->result = $result;
1043
    }
1044
1045
    public function registerMockObject(MockObject $mockObject): void
1046
    {
1047
        $this->mockObjects[] = $mockObject;
1048
    }
1049
1050
    /**
1051
     * Returns a builder object to create mock objects using a fluent interface.
1052
     *
1053
     * @param string|string[] $className
1054
     */
1055
    public function getMockBuilder($className): MockBuilder
1056
    {
1057
        return new MockBuilder($this, $className);
1058
    }
1059
1060
    public function addToAssertionCount(int $count): void
1061
    {
1062
        $this->numAssertions += $count;
1063
    }
1064
1065
    /**
1066
     * Returns the number of assertions performed by this test.
1067
     */
1068
    public function getNumAssertions(): int
1069
    {
1070
        return $this->numAssertions;
1071
    }
1072
1073
    public function usesDataProvider(): bool
1074
    {
1075
        return !empty($this->data);
1076
    }
1077
1078
    public function dataDescription(): string
1079
    {
1080
        return \is_string($this->dataName) ? $this->dataName : '';
0 ignored issues
show
introduced by
The condition is_string($this->dataName) is always true.
Loading history...
1081
    }
1082
1083
    /**
1084
     * @return int|string
1085
     */
1086
    public function dataName()
1087
    {
1088
        return $this->dataName;
1089
    }
1090
1091
    public function registerComparator(Comparator $comparator): void
1092
    {
1093
        ComparatorFactory::getInstance()->register($comparator);
1094
1095
        $this->customComparators[] = $comparator;
1096
    }
1097
1098
    public function getDataSetAsString(bool $includeData = true): string
1099
    {
1100
        $buffer = '';
1101
1102
        if (!empty($this->data)) {
1103
            if (\is_int($this->dataName)) {
0 ignored issues
show
introduced by
The condition is_int($this->dataName) is always false.
Loading history...
1104
                $buffer .= \sprintf(' with data set #%d', $this->dataName);
1105
            } else {
1106
                $buffer .= \sprintf(' with data set "%s"', $this->dataName);
1107
            }
1108
1109
            $exporter = new Exporter;
1110
1111
            if ($includeData) {
1112
                $buffer .= \sprintf(' (%s)', $exporter->shortenedRecursiveExport($this->data));
1113
            }
1114
        }
1115
1116
        return $buffer;
1117
    }
1118
1119
    /**
1120
     * Gets the data set of a TestCase.
1121
     */
1122
    public function getProvidedData(): array
1123
    {
1124
        return $this->data;
1125
    }
1126
1127
    public function addWarning(string $warning): void
1128
    {
1129
        $this->warnings[] = $warning;
1130
    }
1131
1132
    /**
1133
     * Override to run the test and assert its state.
1134
     *
1135
     * @throws AssertionFailedError
1136
     * @throws Exception
1137
     * @throws ExpectationFailedException
1138
     * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException
1139
     * @throws Throwable
1140
     */
1141
    protected function runTest()
1142
    {
1143
        if ($this->name === null) {
1144
            throw new Exception(
1145
                'PHPUnit\Framework\TestCase::$name must not be null.'
1146
            );
1147
        }
1148
1149
        $testArguments = \array_merge($this->data, $this->dependencyInput);
1150
1151
        $this->registerMockObjectsFromTestArguments($testArguments);
1152
1153
        try {
1154
            $testResult = $this->{$this->name}(...\array_values($testArguments));
1155
        } catch (Throwable $exception) {
1156
            if (!$this->checkExceptionExpectations($exception)) {
1157
                throw $exception;
1158
            }
1159
1160
            if ($this->expectedException !== null) {
1161
                $this->assertThat(
1162
                    $exception,
1163
                    new ExceptionConstraint(
1164
                        $this->expectedException
1165
                    )
1166
                );
1167
            }
1168
1169
            if ($this->expectedExceptionMessage !== null) {
1170
                $this->assertThat(
1171
                    $exception,
1172
                    new ExceptionMessage(
1173
                        $this->expectedExceptionMessage
1174
                    )
1175
                );
1176
            }
1177
1178
            if ($this->expectedExceptionMessageRegExp !== null) {
1179
                $this->assertThat(
1180
                    $exception,
1181
                    new ExceptionMessageRegularExpression(
1182
                        $this->expectedExceptionMessageRegExp
1183
                    )
1184
                );
1185
            }
1186
1187
            if ($this->expectedExceptionCode !== null) {
1188
                $this->assertThat(
1189
                    $exception,
1190
                    new ExceptionCode(
1191
                        $this->expectedExceptionCode
1192
                    )
1193
                );
1194
            }
1195
1196
            return;
1197
        }
1198
1199
        if ($this->expectedException !== null) {
1200
            $this->assertThat(
1201
                null,
1202
                new ExceptionConstraint(
1203
                    $this->expectedException
1204
                )
1205
            );
1206
        } elseif ($this->expectedExceptionMessage !== null) {
1207
            $this->numAssertions++;
1208
1209
            throw new AssertionFailedError(
1210
                \sprintf(
1211
                    'Failed asserting that exception with message "%s" is thrown',
1212
                    $this->expectedExceptionMessage
1213
                )
1214
            );
1215
        } elseif ($this->expectedExceptionMessageRegExp !== null) {
1216
            $this->numAssertions++;
1217
1218
            throw new AssertionFailedError(
1219
                \sprintf(
1220
                    'Failed asserting that exception with message matching "%s" is thrown',
1221
                    $this->expectedExceptionMessageRegExp
1222
                )
1223
            );
1224
        } elseif ($this->expectedExceptionCode !== null) {
1225
            $this->numAssertions++;
1226
1227
            throw new AssertionFailedError(
1228
                \sprintf(
1229
                    'Failed asserting that exception with code "%s" is thrown',
1230
                    $this->expectedExceptionCode
1231
                )
1232
            );
1233
        }
1234
1235
        return $testResult;
1236
    }
1237
1238
    /**
1239
     * This method is a wrapper for the ini_set() function that automatically
1240
     * resets the modified php.ini setting to its original value after the
1241
     * test is run.
1242
     *
1243
     * @throws Exception
1244
     */
1245
    protected function iniSet(string $varName, $newValue): void
1246
    {
1247
        $currentValue = \ini_set($varName, $newValue);
1248
1249
        if ($currentValue !== false) {
1250
            $this->iniSettings[$varName] = $currentValue;
1251
        } else {
1252
            throw new Exception(
1253
                \sprintf(
1254
                    'INI setting "%s" could not be set to "%s".',
1255
                    $varName,
1256
                    $newValue
1257
                )
1258
            );
1259
        }
1260
    }
1261
1262
    /**
1263
     * This method is a wrapper for the setlocale() function that automatically
1264
     * resets the locale to its original value after the test is run.
1265
     *
1266
     * @throws Exception
1267
     */
1268
    protected function setLocale(...$args): void
1269
    {
1270
        if (\count($args) < 2) {
1271
            throw new Exception;
1272
        }
1273
1274
        [$category, $locale] = $args;
1275
1276
        if (\defined('LC_MESSAGES')) {
1277
            $categories[] = \LC_MESSAGES;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$categories was never initialized. Although not strictly required by PHP, it is generally a good practice to add $categories = array(); before regardless.
Loading history...
1278
        }
1279
1280
        if (!\in_array($category, self::LOCALE_CATEGORIES, true)) {
1281
            throw new Exception;
1282
        }
1283
1284
        if (!\is_array($locale) && !\is_string($locale)) {
1285
            throw new Exception;
1286
        }
1287
1288
        $this->locale[$category] = \setlocale($category, 0);
1289
1290
        $result = \setlocale(...$args);
1291
1292
        if ($result === false) {
1293
            throw new Exception(
1294
                'The locale functionality is not implemented on your platform, ' .
1295
                'the specified locale does not exist or the category name is ' .
1296
                'invalid.'
1297
            );
1298
        }
1299
    }
1300
1301
    /**
1302
     * Returns a test double for the specified class.
1303
     *
1304
     * @param string|string[] $originalClassName
1305
     *
1306
     * @throws Exception
1307
     * @throws \InvalidArgumentException
1308
     */
1309
    protected function createMock($originalClassName): MockObject
1310
    {
1311
        return $this->getMockBuilder($originalClassName)
1312
                    ->disableOriginalConstructor()
1313
                    ->disableOriginalClone()
1314
                    ->disableArgumentCloning()
1315
                    ->disallowMockingUnknownTypes()
1316
                    ->getMock();
1317
    }
1318
1319
    /**
1320
     * Returns a configured test double for the specified class.
1321
     *
1322
     * @param string|string[] $originalClassName
1323
     *
1324
     * @throws Exception
1325
     * @throws \InvalidArgumentException
1326
     */
1327
    protected function createConfiguredMock($originalClassName, array $configuration): MockObject
1328
    {
1329
        $o = $this->createMock($originalClassName);
1330
1331
        foreach ($configuration as $method => $return) {
1332
            $o->method($method)->willReturn($return);
1333
        }
1334
1335
        return $o;
1336
    }
1337
1338
    /**
1339
     * Returns a partial test double for the specified class.
1340
     *
1341
     * @param string|string[] $originalClassName
1342
     * @param string[]        $methods
1343
     *
1344
     * @throws Exception
1345
     * @throws \InvalidArgumentException
1346
     */
1347
    protected function createPartialMock($originalClassName, array $methods): MockObject
1348
    {
1349
        return $this->getMockBuilder($originalClassName)
1350
                    ->disableOriginalConstructor()
1351
                    ->disableOriginalClone()
1352
                    ->disableArgumentCloning()
1353
                    ->disallowMockingUnknownTypes()
1354
                    ->setMethods(empty($methods) ? null : $methods)
1355
                    ->getMock();
1356
    }
1357
1358
    /**
1359
     * Returns a test proxy for the specified class.
1360
     *
1361
     * @throws Exception
1362
     * @throws \InvalidArgumentException
1363
     */
1364
    protected function createTestProxy(string $originalClassName, array $constructorArguments = []): MockObject
1365
    {
1366
        return $this->getMockBuilder($originalClassName)
1367
                    ->setConstructorArgs($constructorArguments)
1368
                    ->enableProxyingToOriginalMethods()
1369
                    ->getMock();
1370
    }
1371
1372
    /**
1373
     * Mocks the specified class and returns the name of the mocked class.
1374
     *
1375
     * @param string $originalClassName
1376
     * @param array  $methods
1377
     * @param string $mockClassName
1378
     * @param bool   $callOriginalConstructor
1379
     * @param bool   $callOriginalClone
1380
     * @param bool   $callAutoload
1381
     * @param bool   $cloneArguments
1382
     *
1383
     * @throws Exception
1384
     * @throws ReflectionException
1385
     * @throws \InvalidArgumentException
1386
     */
1387
    protected function getMockClass($originalClassName, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = false, $callOriginalClone = true, $callAutoload = true, $cloneArguments = false): string
1388
    {
1389
        $mock = $this->getMockObjectGenerator()->getMock(
1390
            $originalClassName,
1391
            $methods,
1392
            $arguments,
1393
            $mockClassName,
1394
            $callOriginalConstructor,
1395
            $callOriginalClone,
1396
            $callAutoload,
1397
            $cloneArguments
1398
        );
1399
1400
        return \get_class($mock);
1401
    }
1402
1403
    /**
1404
     * Returns a mock object for the specified abstract class with all abstract
1405
     * methods of the class mocked. Concrete methods are not mocked by default.
1406
     * To mock concrete methods, use the 7th parameter ($mockedMethods).
1407
     *
1408
     * @param string $originalClassName
1409
     * @param string $mockClassName
1410
     * @param bool   $callOriginalConstructor
1411
     * @param bool   $callOriginalClone
1412
     * @param bool   $callAutoload
1413
     * @param array  $mockedMethods
1414
     * @param bool   $cloneArguments
1415
     *
1416
     * @throws Exception
1417
     * @throws ReflectionException
1418
     * @throws \InvalidArgumentException
1419
     */
1420
    protected function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = false): MockObject
1421
    {
1422
        $mockObject = $this->getMockObjectGenerator()->getMockForAbstractClass(
1423
            $originalClassName,
1424
            $arguments,
1425
            $mockClassName,
1426
            $callOriginalConstructor,
1427
            $callOriginalClone,
1428
            $callAutoload,
1429
            $mockedMethods,
1430
            $cloneArguments
1431
        );
1432
1433
        $this->registerMockObject($mockObject);
1434
1435
        return $mockObject;
1436
    }
1437
1438
    /**
1439
     * Returns a mock object based on the given WSDL file.
1440
     *
1441
     * @param string $wsdlFile
1442
     * @param string $originalClassName
1443
     * @param string $mockClassName
1444
     * @param bool   $callOriginalConstructor
1445
     * @param array  $options                 An array of options passed to SOAPClient::_construct
1446
     *
1447
     * @throws Exception
1448
     * @throws ReflectionException
1449
     * @throws \InvalidArgumentException
1450
     */
1451
    protected function getMockFromWsdl($wsdlFile, $originalClassName = '', $mockClassName = '', array $methods = [], $callOriginalConstructor = true, array $options = []): MockObject
1452
    {
1453
        if ($originalClassName === '') {
1454
            $fileName          = \pathinfo(\basename(\parse_url($wsdlFile)['path']), \PATHINFO_FILENAME);
1455
            $originalClassName = \preg_replace('/[^a-zA-Z0-9_]/', '', $fileName);
1456
        }
1457
1458
        if (!\class_exists($originalClassName)) {
1459
            eval(
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1460
                $this->getMockObjectGenerator()->generateClassFromWsdl(
1461
                    $wsdlFile,
1462
                    $originalClassName,
1463
                    $methods,
1464
                    $options
1465
                )
1466
            );
1467
        }
1468
1469
        $mockObject = $this->getMockObjectGenerator()->getMock(
1470
            $originalClassName,
1471
            $methods,
1472
            ['', $options],
1473
            $mockClassName,
1474
            $callOriginalConstructor,
1475
            false,
1476
            false
1477
        );
1478
1479
        $this->registerMockObject($mockObject);
1480
1481
        return $mockObject;
1482
    }
1483
1484
    /**
1485
     * Returns a mock object for the specified trait with all abstract methods
1486
     * of the trait mocked. Concrete methods to mock can be specified with the
1487
     * `$mockedMethods` parameter.
1488
     *
1489
     * @param string $traitName
1490
     * @param string $mockClassName
1491
     * @param bool   $callOriginalConstructor
1492
     * @param bool   $callOriginalClone
1493
     * @param bool   $callAutoload
1494
     * @param array  $mockedMethods
1495
     * @param bool   $cloneArguments
1496
     *
1497
     * @throws Exception
1498
     * @throws ReflectionException
1499
     * @throws \InvalidArgumentException
1500
     */
1501
    protected function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = false): MockObject
1502
    {
1503
        $mockObject = $this->getMockObjectGenerator()->getMockForTrait(
1504
            $traitName,
1505
            $arguments,
1506
            $mockClassName,
1507
            $callOriginalConstructor,
1508
            $callOriginalClone,
1509
            $callAutoload,
1510
            $mockedMethods,
1511
            $cloneArguments
1512
        );
1513
1514
        $this->registerMockObject($mockObject);
1515
1516
        return $mockObject;
1517
    }
1518
1519
    /**
1520
     * Returns an object for the specified trait.
1521
     *
1522
     * @param string $traitName
1523
     * @param string $traitClassName
1524
     * @param bool   $callOriginalConstructor
1525
     * @param bool   $callOriginalClone
1526
     * @param bool   $callAutoload
1527
     *
1528
     * @throws Exception
1529
     * @throws ReflectionException
1530
     * @throws \InvalidArgumentException
1531
     *
1532
     * @return object
1533
     */
1534
    protected function getObjectForTrait($traitName, array $arguments = [], $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true)/*: object*/
1535
    {
1536
        return $this->getMockObjectGenerator()->getObjectForTrait(
1537
            $traitName,
1538
            $arguments,
1539
            $traitClassName,
1540
            $callOriginalConstructor,
1541
            $callOriginalClone,
1542
            $callAutoload
1543
        );
1544
    }
1545
1546
    /**
1547
     * @param null|string $classOrInterface
1548
     *
1549
     * @throws Prophecy\Exception\Doubler\ClassNotFoundException
1550
     * @throws Prophecy\Exception\Doubler\DoubleException
1551
     * @throws Prophecy\Exception\Doubler\InterfaceNotFoundException
1552
     */
1553
    protected function prophesize($classOrInterface = null): ObjectProphecy
1554
    {
1555
        return $this->getProphet()->prophesize($classOrInterface);
1556
    }
1557
1558
    /**
1559
     * Creates a default TestResult object.
1560
     */
1561
    protected function createResult(): TestResult
1562
    {
1563
        return new TestResult;
1564
    }
1565
1566
    /**
1567
     * Performs assertions shared by all tests of a test case.
1568
     *
1569
     * This method is called between setUp() and test.
1570
     */
1571
    protected function assertPreConditions()/* The :void return type declaration that should be here would cause a BC issue */
1572
    {
1573
    }
1574
1575
    /**
1576
     * Performs assertions shared by all tests of a test case.
1577
     *
1578
     * This method is called between test and tearDown().
1579
     */
1580
    protected function assertPostConditions()/* The :void return type declaration that should be here would cause a BC issue */
1581
    {
1582
    }
1583
1584
    /**
1585
     * This method is called when a test method did not execute successfully.
1586
     *
1587
     * @throws Throwable
1588
     */
1589
    protected function onNotSuccessfulTest(Throwable $t)/* The :void return type declaration that should be here would cause a BC issue */
1590
    {
1591
        throw $t;
1592
    }
1593
1594
    private function setExpectedExceptionFromAnnotation(): void
1595
    {
1596
        try {
1597
            $expectedException = \PHPUnit\Util\Test::getExpectedException(
1598
                \get_class($this),
1599
                $this->name
1600
            );
1601
1602
            if ($expectedException !== false) {
1603
                $this->expectException($expectedException['class']);
1604
1605
                if ($expectedException['code'] !== null) {
1606
                    $this->expectExceptionCode($expectedException['code']);
1607
                }
1608
1609
                if ($expectedException['message'] !== '') {
1610
                    $this->expectExceptionMessage($expectedException['message']);
1611
                } elseif ($expectedException['message_regex'] !== '') {
1612
                    $this->expectExceptionMessageRegExp($expectedException['message_regex']);
1613
                }
1614
            }
1615
        } catch (ReflectionException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1616
        }
1617
    }
1618
1619
    private function setUseErrorHandlerFromAnnotation(): void
1620
    {
1621
        try {
1622
            $useErrorHandler = \PHPUnit\Util\Test::getErrorHandlerSettings(
1623
                \get_class($this),
1624
                $this->name
1625
            );
1626
1627
            if ($useErrorHandler !== null) {
1628
                $this->setUseErrorHandler($useErrorHandler);
1629
            }
1630
        } catch (ReflectionException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1631
        }
1632
    }
1633
1634
    private function checkRequirements(): void
1635
    {
1636
        if (!$this->name || !\method_exists($this, $this->name)) {
1637
            return;
1638
        }
1639
1640
        $missingRequirements = \PHPUnit\Util\Test::getMissingRequirements(
1641
            \get_class($this),
1642
            $this->name
1643
        );
1644
1645
        if (!empty($missingRequirements)) {
1646
            $this->markTestSkipped(\implode(\PHP_EOL, $missingRequirements));
1647
        }
1648
    }
1649
1650
    private function verifyMockObjects(): void
1651
    {
1652
        foreach ($this->mockObjects as $mockObject) {
1653
            if ($mockObject->__phpunit_hasMatchers()) {
1654
                $this->numAssertions++;
1655
            }
1656
1657
            $mockObject->__phpunit_verify(
1658
                $this->shouldInvocationMockerBeReset($mockObject)
1659
            );
1660
        }
1661
1662
        if ($this->prophet !== null) {
1663
            try {
1664
                $this->prophet->checkPredictions();
1665
            } finally {
1666
                foreach ($this->prophet->getProphecies() as $objectProphecy) {
1667
                    foreach ($objectProphecy->getMethodProphecies() as $methodProphecies) {
1668
                        /** @var MethodProphecy[] $methodProphecies */
1669
                        foreach ($methodProphecies as $methodProphecy) {
1670
                            $this->numAssertions += \count($methodProphecy->getCheckedPredictions());
1671
                        }
1672
                    }
1673
                }
1674
            }
1675
        }
1676
    }
1677
1678
    private function handleDependencies(): bool
1679
    {
1680
        if (!empty($this->dependencies) && !$this->inIsolation) {
1681
            $className  = \get_class($this);
1682
            $passed     = $this->result->passed();
1683
            $passedKeys = \array_keys($passed);
1684
            $numKeys    = \count($passedKeys);
1685
1686
            for ($i = 0; $i < $numKeys; $i++) {
1687
                $pos = \strpos($passedKeys[$i], ' with data set');
1688
1689
                if ($pos !== false) {
1690
                    $passedKeys[$i] = \substr($passedKeys[$i], 0, $pos);
1691
                }
1692
            }
1693
1694
            $passedKeys = \array_flip(\array_unique($passedKeys));
1695
1696
            foreach ($this->dependencies as $dependency) {
1697
                $deepClone    = false;
1698
                $shallowClone = false;
1699
1700
                if (\strpos($dependency, 'clone ') === 0) {
1701
                    $deepClone  = true;
1702
                    $dependency = \substr($dependency, \strlen('clone '));
1703
                } elseif (\strpos($dependency, '!clone ') === 0) {
1704
                    $deepClone  = false;
1705
                    $dependency = \substr($dependency, \strlen('!clone '));
1706
                }
1707
1708
                if (\strpos($dependency, 'shallowClone ') === 0) {
1709
                    $shallowClone = true;
1710
                    $dependency   = \substr($dependency, \strlen('shallowClone '));
1711
                } elseif (\strpos($dependency, '!shallowClone ') === 0) {
1712
                    $shallowClone = false;
1713
                    $dependency   = \substr($dependency, \strlen('!shallowClone '));
1714
                }
1715
1716
                if (\strpos($dependency, '::') === false) {
1717
                    $dependency = $className . '::' . $dependency;
1718
                }
1719
1720
                if (!isset($passedKeys[$dependency])) {
1721
                    if (!\is_callable($dependency, false, $callableName) || $dependency !== $callableName) {
1722
                        $this->markWarningForUncallableDependency($dependency);
1723
                    } else {
1724
                        $this->markSkippedForMissingDependecy($dependency);
1725
                    }
1726
1727
                    return false;
1728
                }
1729
1730
                if (isset($passed[$dependency])) {
1731
                    if ($passed[$dependency]['size'] != \PHPUnit\Util\Test::UNKNOWN &&
1732
                        $this->getSize() != \PHPUnit\Util\Test::UNKNOWN &&
1733
                        $passed[$dependency]['size'] > $this->getSize()) {
1734
                        $this->result->addError(
1735
                            $this,
1736
                            new SkippedTestError(
1737
                                'This test depends on a test that is larger than itself.'
1738
                            ),
1739
                            0
1740
                        );
1741
1742
                        return false;
1743
                    }
1744
1745
                    if ($deepClone) {
1746
                        $deepCopy = new DeepCopy;
1747
                        $deepCopy->skipUncloneable(false);
1748
1749
                        $this->dependencyInput[$dependency] = $deepCopy->copy($passed[$dependency]['result']);
1750
                    } elseif ($shallowClone) {
1751
                        $this->dependencyInput[$dependency] = clone $passed[$dependency]['result'];
1752
                    } else {
1753
                        $this->dependencyInput[$dependency] = $passed[$dependency]['result'];
1754
                    }
1755
                } else {
1756
                    $this->dependencyInput[$dependency] = null;
1757
                }
1758
            }
1759
        }
1760
1761
        return true;
1762
    }
1763
1764
    private function markSkippedForMissingDependecy(string $dependency): void
1765
    {
1766
        $this->status = BaseTestRunner::STATUS_SKIPPED;
1767
        $this->result->startTest($this);
1768
        $this->result->addError(
1769
            $this,
1770
            new SkippedTestError(
1771
                \sprintf(
1772
                    'This test depends on "%s" to pass.',
1773
                    $dependency
1774
                )
1775
            ),
1776
            0
1777
        );
1778
        $this->result->endTest($this, 0);
1779
    }
1780
1781
    private function markWarningForUncallableDependency(string $dependency): void
1782
    {
1783
        $this->status = BaseTestRunner::STATUS_WARNING;
1784
        $this->result->startTest($this);
1785
        $this->result->addWarning(
1786
            $this,
1787
            new Warning(
1788
                \sprintf(
1789
                    'This test depends on "%s" which does not exist.',
1790
                    $dependency
1791
                )
1792
            ),
1793
            0
1794
        );
1795
        $this->result->endTest($this, 0);
1796
    }
1797
1798
    /**
1799
     * Get the mock object generator, creating it if it doesn't exist.
1800
     */
1801
    private function getMockObjectGenerator(): MockGenerator
1802
    {
1803
        if ($this->mockObjectGenerator === null) {
1804
            $this->mockObjectGenerator = new MockGenerator;
1805
        }
1806
1807
        return $this->mockObjectGenerator;
1808
    }
1809
1810
    private function startOutputBuffering(): void
1811
    {
1812
        \ob_start();
1813
1814
        $this->outputBufferingActive = true;
1815
        $this->outputBufferingLevel  = \ob_get_level();
1816
    }
1817
1818
    /**
1819
     * @throws RiskyTestError
1820
     */
1821
    private function stopOutputBuffering(): void
1822
    {
1823
        if (\ob_get_level() !== $this->outputBufferingLevel) {
1824
            while (\ob_get_level() >= $this->outputBufferingLevel) {
1825
                \ob_end_clean();
1826
            }
1827
1828
            throw new RiskyTestError(
1829
                'Test code or tested code did not (only) close its own output buffers'
1830
            );
1831
        }
1832
1833
        $this->output = \ob_get_contents();
1834
1835
        if ($this->outputCallback !== false) {
1836
            $this->output = (string) \call_user_func($this->outputCallback, $this->output);
1837
        }
1838
1839
        \ob_end_clean();
1840
1841
        $this->outputBufferingActive = false;
1842
        $this->outputBufferingLevel  = \ob_get_level();
1843
    }
1844
1845
    private function snapshotGlobalState(): void
1846
    {
1847
        if ($this->runTestInSeparateProcess || $this->inIsolation ||
1848
            (!$this->backupGlobals === true && !$this->backupStaticAttributes)) {
1849
            return;
1850
        }
1851
1852
        $this->snapshot = $this->createGlobalStateSnapshot($this->backupGlobals === true);
1853
    }
1854
1855
    /**
1856
     * @throws RiskyTestError
1857
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
1858
     * @throws \InvalidArgumentException
1859
     */
1860
    private function restoreGlobalState(): void
1861
    {
1862
        if (!$this->snapshot instanceof Snapshot) {
0 ignored issues
show
introduced by
$this->snapshot is always a sub-type of SebastianBergmann\GlobalState\Snapshot.
Loading history...
1863
            return;
1864
        }
1865
1866
        if ($this->beStrictAboutChangesToGlobalState) {
1867
            try {
1868
                $this->compareGlobalStateSnapshots(
1869
                    $this->snapshot,
1870
                    $this->createGlobalStateSnapshot($this->backupGlobals === true)
1871
                );
1872
            } catch (RiskyTestError $rte) {
1873
                // Intentionally left empty
1874
            }
1875
        }
1876
1877
        $restorer = new Restorer;
1878
1879
        if ($this->backupGlobals === true) {
1880
            $restorer->restoreGlobalVariables($this->snapshot);
1881
        }
1882
1883
        if ($this->backupStaticAttributes) {
1884
            $restorer->restoreStaticAttributes($this->snapshot);
1885
        }
1886
1887
        $this->snapshot = null;
1888
1889
        if (isset($rte)) {
1890
            throw $rte;
1891
        }
1892
    }
1893
1894
    private function createGlobalStateSnapshot(bool $backupGlobals): Snapshot
1895
    {
1896
        $blacklist = new Blacklist;
1897
1898
        foreach ($this->backupGlobalsBlacklist as $globalVariable) {
1899
            $blacklist->addGlobalVariable($globalVariable);
1900
        }
1901
1902
        if (!\defined('PHPUNIT_TESTSUITE')) {
1903
            $blacklist->addClassNamePrefix('PHPUnit');
1904
            $blacklist->addClassNamePrefix('SebastianBergmann\CodeCoverage');
1905
            $blacklist->addClassNamePrefix('SebastianBergmann\FileIterator');
1906
            $blacklist->addClassNamePrefix('SebastianBergmann\Invoker');
1907
            $blacklist->addClassNamePrefix('SebastianBergmann\Timer');
1908
            $blacklist->addClassNamePrefix('PHP_Token');
1909
            $blacklist->addClassNamePrefix('Symfony');
1910
            $blacklist->addClassNamePrefix('Text_Template');
1911
            $blacklist->addClassNamePrefix('Doctrine\Instantiator');
1912
            $blacklist->addClassNamePrefix('Prophecy');
1913
1914
            foreach ($this->backupStaticAttributesBlacklist as $class => $attributes) {
1915
                foreach ($attributes as $attribute) {
1916
                    $blacklist->addStaticAttribute($class, $attribute);
1917
                }
1918
            }
1919
        }
1920
1921
        return new Snapshot(
1922
            $blacklist,
1923
            $backupGlobals,
1924
            (bool) $this->backupStaticAttributes,
1925
            false,
1926
            false,
1927
            false,
1928
            false,
1929
            false,
1930
            false,
1931
            false
1932
        );
1933
    }
1934
1935
    /**
1936
     * @throws RiskyTestError
1937
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
1938
     * @throws \InvalidArgumentException
1939
     */
1940
    private function compareGlobalStateSnapshots(Snapshot $before, Snapshot $after): void
1941
    {
1942
        $backupGlobals = $this->backupGlobals === null || $this->backupGlobals === true;
1943
1944
        if ($backupGlobals) {
1945
            $this->compareGlobalStateSnapshotPart(
1946
                $before->globalVariables(),
1947
                $after->globalVariables(),
1948
                "--- Global variables before the test\n+++ Global variables after the test\n"
1949
            );
1950
1951
            $this->compareGlobalStateSnapshotPart(
1952
                $before->superGlobalVariables(),
1953
                $after->superGlobalVariables(),
1954
                "--- Super-global variables before the test\n+++ Super-global variables after the test\n"
1955
            );
1956
        }
1957
1958
        if ($this->backupStaticAttributes) {
1959
            $this->compareGlobalStateSnapshotPart(
1960
                $before->staticAttributes(),
1961
                $after->staticAttributes(),
1962
                "--- Static attributes before the test\n+++ Static attributes after the test\n"
1963
            );
1964
        }
1965
    }
1966
1967
    /**
1968
     * @throws RiskyTestError
1969
     */
1970
    private function compareGlobalStateSnapshotPart(array $before, array $after, string $header): void
1971
    {
1972
        if ($before != $after) {
1973
            $differ   = new Differ($header);
0 ignored issues
show
Bug introduced by
$header of type string is incompatible with the type SebastianBergmann\Diff\O...fOutputBuilderInterface expected by parameter $outputBuilder of SebastianBergmann\Diff\Differ::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1973
            $differ   = new Differ(/** @scrutinizer ignore-type */ $header);
Loading history...
1974
            $exporter = new Exporter;
1975
1976
            $diff = $differ->diff(
1977
                $exporter->export($before),
1978
                $exporter->export($after)
1979
            );
1980
1981
            throw new RiskyTestError(
1982
                $diff
1983
            );
1984
        }
1985
    }
1986
1987
    private function getProphet(): Prophet
1988
    {
1989
        if ($this->prophet === null) {
1990
            $this->prophet = new Prophet;
1991
        }
1992
1993
        return $this->prophet;
1994
    }
1995
1996
    /**
1997
     * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException
1998
     */
1999
    private function shouldInvocationMockerBeReset(MockObject $mock): bool
2000
    {
2001
        $enumerator = new Enumerator;
2002
2003
        foreach ($enumerator->enumerate($this->dependencyInput) as $object) {
2004
            if ($mock === $object) {
2005
                return false;
2006
            }
2007
        }
2008
2009
        if (!\is_array($this->testResult) && !\is_object($this->testResult)) {
2010
            return true;
2011
        }
2012
2013
        return !\in_array($mock, $enumerator->enumerate($this->testResult), true);
2014
    }
2015
2016
    /**
2017
     * @throws \SebastianBergmann\ObjectEnumerator\InvalidArgumentException
2018
     * @throws \SebastianBergmann\ObjectReflector\InvalidArgumentException
2019
     * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
2020
     */
2021
    private function registerMockObjectsFromTestArguments(array $testArguments, array &$visited = []): void
2022
    {
2023
        if ($this->registerMockObjectsFromTestArgumentsRecursively) {
2024
            $enumerator = new Enumerator;
2025
2026
            foreach ($enumerator->enumerate($testArguments) as $object) {
2027
                if ($object instanceof MockObject) {
2028
                    $this->registerMockObject($object);
2029
                }
2030
            }
2031
        } else {
2032
            foreach ($testArguments as $testArgument) {
2033
                if ($testArgument instanceof MockObject) {
2034
                    if ($this->isCloneable($testArgument)) {
2035
                        $testArgument = clone $testArgument;
2036
                    }
2037
2038
                    $this->registerMockObject($testArgument);
2039
                } elseif (\is_array($testArgument) && !\in_array($testArgument, $visited, true)) {
2040
                    $visited[] = $testArgument;
2041
2042
                    $this->registerMockObjectsFromTestArguments(
2043
                        $testArgument,
2044
                        $visited
2045
                    );
2046
                }
2047
            }
2048
        }
2049
    }
2050
2051
    private function setDoesNotPerformAssertionsFromAnnotation(): void
2052
    {
2053
        $annotations = $this->getAnnotations();
2054
2055
        if (isset($annotations['method']['doesNotPerformAssertions'])) {
2056
            $this->doesNotPerformAssertions = true;
2057
        }
2058
    }
2059
2060
    private function isCloneable(MockObject $testArgument): bool
2061
    {
2062
        $reflector = new ReflectionObject($testArgument);
2063
2064
        if (!$reflector->isCloneable()) {
2065
            return false;
2066
        }
2067
2068
        if ($reflector->hasMethod('__clone') &&
2069
            $reflector->getMethod('__clone')->isPublic()) {
2070
            return true;
2071
        }
2072
2073
        return false;
2074
    }
2075
2076
    private function unregisterCustomComparators(): void
2077
    {
2078
        $factory = ComparatorFactory::getInstance();
2079
2080
        foreach ($this->customComparators as $comparator) {
2081
            $factory->unregister($comparator);
2082
        }
2083
2084
        $this->customComparators = [];
2085
    }
2086
2087
    private function cleanupIniSettings(): void
2088
    {
2089
        foreach ($this->iniSettings as $varName => $oldValue) {
2090
            \ini_set($varName, $oldValue);
2091
        }
2092
2093
        $this->iniSettings = [];
2094
    }
2095
2096
    private function cleanupLocaleSettings(): void
2097
    {
2098
        foreach ($this->locale as $category => $locale) {
2099
            \setlocale($category, $locale);
2100
        }
2101
2102
        $this->locale = [];
2103
    }
2104
2105
    /**
2106
     * @throws ReflectionException
2107
     */
2108
    private function checkExceptionExpectations(Throwable $throwable): bool
2109
    {
2110
        $result = false;
2111
2112
        if ($this->expectedException !== null || $this->expectedExceptionCode !== null || $this->expectedExceptionMessage !== null || $this->expectedExceptionMessageRegExp !== null) {
2113
            $result = true;
2114
        }
2115
2116
        if ($throwable instanceof Exception) {
2117
            $result = false;
2118
        }
2119
2120
        if (\is_string($this->expectedException)) {
2121
            $reflector = new ReflectionClass($this->expectedException);
2122
2123
            if ($this->expectedException === 'PHPUnit\Framework\Exception' ||
2124
                $this->expectedException === '\PHPUnit\Framework\Exception' ||
2125
                $reflector->isSubclassOf(Exception::class)) {
2126
                $result = true;
2127
            }
2128
        }
2129
2130
        return $result;
2131
    }
2132
2133
    private function runInSeparateProcess(): bool
2134
    {
2135
        return ($this->runTestInSeparateProcess === true || $this->runClassInSeparateProcess === true) &&
2136
               $this->inIsolation !== true && !$this instanceof PhptTestCase;
2137
    }
2138
}
2139