Passed
Pull Request — 4 (#10028)
by Steve
09:11 queued 18s
created

SapphireTest::assertNotContainsNonIterable()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 4
nop 4
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Dev;
4
5
use Exception;
6
use InvalidArgumentException;
7
use LogicException;
8
9
// use PHPUnit_Framework_Constraint_Not;
10
use PHPUnit\Framework\Constraint\LogicalNot;
11
// use PHPUnit_Framework_TestCase;
12
use PHPUnit\Framework\TestCase;
13
// use PHPUnit_Util_InvalidArgumentHelper;
14
use PHPUnit\Framework\Exception as PHPUnitFrameworkException;
15
use PHPUnit\Util\Test as TestUtil;
16
use SilverStripe\CMS\Controllers\RootURLController;
0 ignored issues
show
Bug introduced by
The type SilverStripe\CMS\Controllers\RootURLController was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
use SilverStripe\Control\CLIRequestBuilder;
18
use SilverStripe\Control\Controller;
19
use SilverStripe\Control\Cookie;
20
use SilverStripe\Control\Director;
21
use SilverStripe\Control\Email\Email;
22
use SilverStripe\Control\Email\Mailer;
23
use SilverStripe\Control\HTTPApplication;
24
use SilverStripe\Control\HTTPRequest;
25
use SilverStripe\Core\Config\Config;
26
use SilverStripe\Core\Injector\Injector;
27
use SilverStripe\Core\Injector\InjectorLoader;
28
use SilverStripe\Core\Manifest\ClassLoader;
29
use SilverStripe\Core\Manifest\ModuleResourceLoader;
30
use SilverStripe\Dev\Constraint\SSListContains;
31
use SilverStripe\Dev\Constraint\SSListContainsOnly;
32
use SilverStripe\Dev\Constraint\SSListContainsOnlyMatchingItems;
33
use SilverStripe\Dev\State\FixtureTestState;
34
use SilverStripe\Dev\State\SapphireTestState;
35
use SilverStripe\i18n\i18n;
36
use SilverStripe\ORM\Connect\TempDatabase;
37
use SilverStripe\ORM\DataObject;
38
use SilverStripe\ORM\FieldType\DBDatetime;
39
use SilverStripe\ORM\FieldType\DBField;
40
use SilverStripe\ORM\SS_List;
41
use SilverStripe\Security\Group;
42
use SilverStripe\Security\IdentityStore;
43
use SilverStripe\Security\Member;
44
use SilverStripe\Security\Permission;
45
use SilverStripe\Security\Security;
46
use SilverStripe\View\SSViewer;
47
48
if (class_exists(TestCase::class)) {
49
50
    /**
51
     * This is for phpunit 9
52
     *
53
     * If using phpunit 5, see legacy/SapphireTest.php
54
     *
55
     * Test case class for the Sapphire framework.
56
     * Sapphire unit testing is based on PHPUnit, but provides a number of hooks into our data model that make it easier
57
     * to work with.
58
     *
59
     * This class should not be used anywhere outside of unit tests, as phpunit may not be installed
60
     * in production sites.
61
     */
62
    class SapphireTest extends TestCase implements TestOnly
63
    {
64
        /**
65
         * Path to fixture data for this test run.
66
         * If passed as an array, multiple fixture files will be loaded.
67
         * Please note that you won't be able to refer with "=>" notation
68
         * between the fixtures, they act independent of each other.
69
         *
70
         * @var string|array
71
         */
72
        protected static $fixture_file = null;
73
74
        /**
75
         * @deprecated 4.0..5.0 Use FixtureTestState instead
76
         * @var FixtureFactory
77
         */
78
        protected $fixtureFactory;
79
80
        /**
81
         * @var Boolean If set to TRUE, this will force a test database to be generated
82
         * in {@link setUp()}. Note that this flag is overruled by the presence of a
83
         * {@link $fixture_file}, which always forces a database build.
84
         *
85
         * @var bool
86
         */
87
        protected $usesDatabase = null;
88
89
        /**
90
         * This test will cleanup its state via transactions.
91
         * If set to false a full schema is forced between tests, but at a performance cost.
92
         *
93
         * @var bool
94
         */
95
        protected $usesTransactions = true;
96
97
        /**
98
         * @var bool
99
         */
100
        protected static $is_running_test = false;
101
102
        /**
103
         * By default, setUp() does not require default records. Pass
104
         * class names in here, and the require/augment default records
105
         * function will be called on them.
106
         *
107
         * @var array
108
         */
109
        protected $requireDefaultRecordsFrom = [];
110
111
        /**
112
         * A list of extensions that can't be applied during the execution of this run.  If they are
113
         * applied, they will be temporarily removed and a database migration called.
114
         *
115
         * The keys of the are the classes that the extensions can't be applied the extensions to, and
116
         * the values are an array of illegal extensions on that class.
117
         *
118
         * Set a class to `*` to remove all extensions (unadvised)
119
         *
120
         * @var array
121
         */
122
        protected static $illegal_extensions = [];
123
124
        /**
125
         * A list of extensions that must be applied during the execution of this run.  If they are
126
         * not applied, they will be temporarily added and a database migration called.
127
         *
128
         * The keys of the are the classes to apply the extensions to, and the values are an array
129
         * of required extensions on that class.
130
         *
131
         * Example:
132
         * <code>
133
         * array("MyTreeDataObject" => array("Versioned", "Hierarchy"))
134
         * </code>
135
         *
136
         * @var array
137
         */
138
        protected static $required_extensions = [];
139
140
        /**
141
         * By default, the test database won't contain any DataObjects that have the interface TestOnly.
142
         * This variable lets you define additional TestOnly DataObjects to set up for this test.
143
         * Set it to an array of DataObject subclass names.
144
         *
145
         * @var array
146
         */
147
        protected static $extra_dataobjects = [];
148
149
        /**
150
         * List of class names of {@see Controller} objects to register routes for
151
         * Controllers must implement Link() method
152
         *
153
         * @var array
154
         */
155
        protected static $extra_controllers = [];
156
157
        /**
158
         * We need to disabling backing up of globals to avoid overriding
159
         * the few globals SilverStripe relies on, like $lang for the i18n subsystem.
160
         *
161
         * @see http://sebastian-bergmann.de/archives/797-Global-Variables-and-PHPUnit.html
162
         */
163
        protected $backupGlobals = false;
164
165
        /**
166
         * State management container for SapphireTest
167
         *
168
         * @var SapphireTestState
169
         */
170
        protected static $state = null;
171
172
        /**
173
         * Temp database helper
174
         *
175
         * @var TempDatabase
176
         */
177
        protected static $tempDB = null;
178
179
        /**
180
         * @return TempDatabase
181
         */
182
        public static function tempDB()
183
        {
184
            if (!class_exists(TempDatabase::class)) {
185
                return null;
186
            }
187
188
            if (!static::$tempDB) {
189
                static::$tempDB = TempDatabase::create();
190
            }
191
            return static::$tempDB;
192
        }
193
194
        /**
195
         * Gets illegal extensions for this class
196
         *
197
         * @return array
198
         */
199
        public static function getIllegalExtensions()
200
        {
201
            return static::$illegal_extensions;
202
        }
203
204
        /**
205
         * Gets required extensions for this class
206
         *
207
         * @return array
208
         */
209
        public static function getRequiredExtensions()
210
        {
211
            return static::$required_extensions;
212
        }
213
214
        /**
215
         * Check if test bootstrapping has been performed. Must not be relied on
216
         * outside of unit tests.
217
         *
218
         * @return bool
219
         */
220
        protected static function is_running_test()
221
        {
222
            return self::$is_running_test;
223
        }
224
225
        /**
226
         * Set test running state
227
         *
228
         * @param bool $bool
229
         */
230
        protected static function set_is_running_test($bool)
231
        {
232
            self::$is_running_test = $bool;
233
        }
234
235
        /**
236
         * @return String
237
         */
238
        public static function get_fixture_file()
239
        {
240
            return static::$fixture_file;
241
        }
242
243
        /**
244
         * @return bool
245
         */
246
        public function getUsesDatabase()
247
        {
248
            return $this->usesDatabase;
249
        }
250
251
        /**
252
         * @return bool
253
         */
254
        public function getUsesTransactions()
255
        {
256
            return $this->usesTransactions;
257
        }
258
259
        /**
260
         * @return array
261
         */
262
        public function getRequireDefaultRecordsFrom()
263
        {
264
            return $this->requireDefaultRecordsFrom;
265
        }
266
267
        /**
268
         * Setup  the test.
269
         * Always sets up in order:
270
         *  - Reset php state
271
         *  - Nest
272
         *  - Custom state helpers
273
         *
274
         * User code should call parent::setUp() before custom setup code
275
         */
276
        protected function setUp(): void
277
        {
278
            if (!defined('FRAMEWORK_PATH')) {
279
                trigger_error(
280
                    'Missing constants, did you remember to include the test bootstrap in your phpunit.xml file?',
281
                    E_USER_WARNING
282
                );
283
            }
284
285
            // Call state helpers
286
            static::$state->setUp($this);
287
288
            // We cannot run the tests on this abstract class.
289
            if (static::class == __CLASS__) {
0 ignored issues
show
introduced by
The condition static::class == __CLASS__ is always true.
Loading history...
290
                $this->markTestSkipped(sprintf('Skipping %s ', static::class));
291
            }
292
293
            // i18n needs to be set to the defaults or tests fail
294
            if (class_exists(i18n::class)) {
295
                i18n::set_locale(i18n::config()->uninherited('default_locale'));
296
            }
297
298
            // Set default timezone consistently to avoid NZ-specific dependencies
299
            date_default_timezone_set('UTC');
300
301
            if (class_exists(Member::class)) {
302
                Member::set_password_validator(null);
303
            }
304
305
            if (class_exists(Cookie::class)) {
306
                Cookie::config()->update('report_errors', false);
307
            }
308
309
            if (class_exists(RootURLController::class)) {
310
                RootURLController::reset();
311
            }
312
313
            if (class_exists(Security::class)) {
314
                Security::clear_database_is_ready();
315
            }
316
317
            // Set up test routes
318
            $this->setUpRoutes();
319
320
            $fixtureFiles = $this->getFixturePaths();
321
322
            if ($this->shouldSetupDatabaseForCurrentTest($fixtureFiles)) {
323
                // Assign fixture factory to deprecated prop in case old tests use it over the getter
324
                /** @var FixtureTestState $fixtureState */
325
                $fixtureState = static::$state->getStateByName('fixtures');
326
                $this->fixtureFactory = $fixtureState->getFixtureFactory(static::class);
327
328
                $this->logInWithPermission('ADMIN');
329
            }
330
331
            // turn off template debugging
332
            if (class_exists(SSViewer::class)) {
333
                SSViewer::config()->update('source_file_comments', false);
334
            }
335
336
            // Set up the test mailer
337
            if (class_exists(TestMailer::class)) {
338
                Injector::inst()->registerService(new TestMailer(), Mailer::class);
339
            }
340
341
            if (class_exists(Email::class)) {
342
                Email::config()->remove('send_all_emails_to');
343
                Email::config()->remove('send_all_emails_from');
344
                Email::config()->remove('cc_all_emails_to');
345
                Email::config()->remove('bcc_all_emails_to');
346
            }
347
        }
348
349
350
        /**
351
         * Helper method to determine if the current test should enable a test database
352
         *
353
         * @param $fixtureFiles
354
         * @return bool
355
         */
356
        protected function shouldSetupDatabaseForCurrentTest($fixtureFiles)
357
        {
358
            $databaseEnabledByDefault = $fixtureFiles || $this->usesDatabase;
359
360
            return ($databaseEnabledByDefault && !$this->currentTestDisablesDatabase())
361
                || $this->currentTestEnablesDatabase();
362
        }
363
364
        /**
365
         * Helper method to check, if the current test uses the database.
366
         * This can be switched on with the annotation "@useDatabase"
367
         *
368
         * @return bool
369
         */
370
        protected function currentTestEnablesDatabase()
371
        {
372
            $annotations = $this->getAnnotations();
373
374
            return array_key_exists('useDatabase', $annotations['method'])
375
                && $annotations['method']['useDatabase'][0] !== 'false';
376
        }
377
378
        /**
379
         * Helper method to check, if the current test uses the database.
380
         * This can be switched on with the annotation "@useDatabase false"
381
         *
382
         * @return bool
383
         */
384
        protected function currentTestDisablesDatabase()
385
        {
386
            $annotations = $this->getAnnotations();
387
388
            return array_key_exists('useDatabase', $annotations['method'])
389
                && $annotations['method']['useDatabase'][0] === 'false';
390
        }
391
392
        /**
393
         * Called once per test case ({@link SapphireTest} subclass).
394
         * This is different to {@link setUp()}, which gets called once
395
         * per method. Useful to initialize expensive operations which
396
         * don't change state for any called method inside the test,
397
         * e.g. dynamically adding an extension. See {@link teardownAfterClass()}
398
         * for tearing down the state again.
399
         *
400
         * Always sets up in order:
401
         *  - Reset php state
402
         *  - Nest
403
         *  - Custom state helpers
404
         *
405
         * User code should call parent::setUpBeforeClass() before custom setup code
406
         *
407
         * @throws Exception
408
         */
409
        public static function setUpBeforeClass(): void
410
        {
411
            // Start tests
412
            static::start();
413
414
            if (!static::$state) {
415
                throw new Exception('SapphireTest failed to bootstrap!');
416
            }
417
418
            // Call state helpers
419
            static::$state->setUpOnce(static::class);
420
421
            // Build DB if we have objects
422
            if (class_exists(DataObject::class) && static::getExtraDataObjects()) {
423
                DataObject::reset();
424
                static::resetDBSchema(true, true);
425
            }
426
        }
427
428
        /**
429
         * tearDown method that's called once per test class rather once per test method.
430
         *
431
         * Always sets up in order:
432
         *  - Custom state helpers
433
         *  - Unnest
434
         *  - Reset php state
435
         *
436
         * User code should call parent::tearDownAfterClass() after custom tear down code
437
         */
438
        public static function tearDownAfterClass(): void
439
        {
440
            // Call state helpers
441
            static::$state->tearDownOnce(static::class);
442
443
            // Reset DB schema
444
            static::resetDBSchema();
445
        }
446
447
        /**
448
         * @return FixtureFactory|false
449
         * @deprecated 4.0.0:5.0.0
450
         */
451
        public function getFixtureFactory()
452
        {
453
            Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead');
454
            /** @var FixtureTestState $state */
455
            $state = static::$state->getStateByName('fixtures');
456
            return $state->getFixtureFactory(static::class);
457
        }
458
459
        /**
460
         * Sets a new fixture factory
461
         * @param FixtureFactory $factory
462
         * @return $this
463
         * @deprecated 4.0.0:5.0.0
464
         */
465
        public function setFixtureFactory(FixtureFactory $factory)
466
        {
467
            Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead');
468
            /** @var FixtureTestState $state */
469
            $state = static::$state->getStateByName('fixtures');
470
            $state->setFixtureFactory($factory, static::class);
471
            $this->fixtureFactory = $factory;
0 ignored issues
show
Deprecated Code introduced by
The property SilverStripe\Dev\SapphireTest::$fixtureFactory has been deprecated: 4.0..5.0 Use FixtureTestState instead ( Ignorable by Annotation )

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

471
            /** @scrutinizer ignore-deprecated */ $this->fixtureFactory = $factory;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
472
            return $this;
473
        }
474
475
        /**
476
         * Get the ID of an object from the fixture.
477
         *
478
         * @param string $className The data class or table name, as specified in your fixture file.  Parent classes won't work
479
         * @param string $identifier The identifier string, as provided in your fixture file
480
         * @return int
481
         */
482
        protected function idFromFixture($className, $identifier)
483
        {
484
            /** @var FixtureTestState $state */
485
            $state = static::$state->getStateByName('fixtures');
486
            $id = $state->getFixtureFactory(static::class)->getId($className, $identifier);
487
488
            if (!$id) {
489
                throw new InvalidArgumentException(sprintf(
490
                    "Couldn't find object '%s' (class: %s)",
491
                    $identifier,
492
                    $className
493
                ));
494
            }
495
496
            return $id;
497
        }
498
499
        /**
500
         * Return all of the IDs in the fixture of a particular class name.
501
         * Will collate all IDs form all fixtures if multiple fixtures are provided.
502
         *
503
         * @param string $className The data class or table name, as specified in your fixture file
504
         * @return array A map of fixture-identifier => object-id
505
         */
506
        protected function allFixtureIDs($className)
507
        {
508
            /** @var FixtureTestState $state */
509
            $state = static::$state->getStateByName('fixtures');
510
            return $state->getFixtureFactory(static::class)->getIds($className);
511
        }
512
513
        /**
514
         * Get an object from the fixture.
515
         *
516
         * @param string $className The data class or table name, as specified in your fixture file. Parent classes won't work
517
         * @param string $identifier The identifier string, as provided in your fixture file
518
         *
519
         * @return DataObject
520
         */
521
        protected function objFromFixture($className, $identifier)
522
        {
523
            /** @var FixtureTestState $state */
524
            $state = static::$state->getStateByName('fixtures');
525
            $obj = $state->getFixtureFactory(static::class)->get($className, $identifier);
526
527
            if (!$obj) {
0 ignored issues
show
introduced by
$obj is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
528
                throw new InvalidArgumentException(sprintf(
529
                    "Couldn't find object '%s' (class: %s)",
530
                    $identifier,
531
                    $className
532
                ));
533
            }
534
535
            return $obj;
536
        }
537
538
        /**
539
         * Load a YAML fixture file into the database.
540
         * Once loaded, you can use idFromFixture() and objFromFixture() to get items from the fixture.
541
         * Doesn't clear existing fixtures.
542
         * @param string $fixtureFile The location of the .yml fixture file, relative to the site base dir
543
         * @deprecated 4.0.0:5.0.0
544
         *
545
         */
546
        public function loadFixture($fixtureFile)
547
        {
548
            Deprecation::notice('5.0', __FUNCTION__ . ' is deprecated, use ' . FixtureTestState::class . ' instead');
549
            $fixture = Injector::inst()->create(YamlFixture::class, $fixtureFile);
550
            $fixture->writeInto($this->getFixtureFactory());
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Dev\SapphireTest::getFixtureFactory() has been deprecated: 4.0.0:5.0.0 ( Ignorable by Annotation )

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

550
            $fixture->writeInto(/** @scrutinizer ignore-deprecated */ $this->getFixtureFactory());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
551
        }
552
553
        /**
554
         * Clear all fixtures which were previously loaded through
555
         * {@link loadFixture()}
556
         */
557
        public function clearFixtures()
558
        {
559
            /** @var FixtureTestState $state */
560
            $state = static::$state->getStateByName('fixtures');
561
            $state->getFixtureFactory(static::class)->clear();
562
        }
563
564
        /**
565
         * Useful for writing unit tests without hardcoding folder structures.
566
         *
567
         * @return string Absolute path to current class.
568
         */
569
        protected function getCurrentAbsolutePath()
570
        {
571
            $filename = ClassLoader::inst()->getItemPath(static::class);
572
            if (!$filename) {
573
                throw new LogicException('getItemPath returned null for ' . static::class
574
                    . '. Try adding flush=1 to the test run.');
575
            }
576
            return dirname($filename);
577
        }
578
579
        /**
580
         * @return string File path relative to webroot
581
         */
582
        protected function getCurrentRelativePath()
583
        {
584
            $base = Director::baseFolder();
585
            $path = $this->getCurrentAbsolutePath();
586
            if (substr($path, 0, strlen($base)) == $base) {
587
                $path = preg_replace('/^\/*/', '', substr($path, strlen($base)));
588
            }
589
            return $path;
590
        }
591
592
        /**
593
         * Setup  the test.
594
         * Always sets up in order:
595
         *  - Custom state helpers
596
         *  - Unnest
597
         *  - Reset php state
598
         *
599
         * User code should call parent::tearDown() after custom tear down code
600
         */
601
        protected function tearDown(): void
602
        {
603
            // Reset mocked datetime
604
            if (class_exists(DBDatetime::class)) {
605
                DBDatetime::clear_mock_now();
606
            }
607
608
            // Stop the redirection that might have been requested in the test.
609
            // Note: Ideally a clean Controller should be created for each test.
610
            // Now all tests executed in a batch share the same controller.
611
            if (class_exists(Controller::class)) {
612
                $controller = Controller::has_curr() ? Controller::curr() : null;
613
                if ($controller && ($response = $controller->getResponse()) && $response->getHeader('Location')) {
614
                    $response->setStatusCode(200);
615
                    $response->removeHeader('Location');
616
                }
617
            }
618
619
            // Call state helpers
620
            static::$state->tearDown($this);
621
        }
622
623
        public static function assertContains(
624
            $needle,
625
            $haystack,
626
            $message = '',
627
            $ignoreCase = false,
628
            $checkForObjectIdentity = true,
629
            $checkForNonObjectIdentity = false
630
        ): void {
631
            if ($haystack instanceof DBField) {
632
                $haystack = (string)$haystack;
633
            }
634
            if (is_iterable($haystack)) {
635
                $strict = is_object($needle) ? $checkForObjectIdentity : $checkForObjectIdentity;
636
                if ($strict) {
637
                    parent::assertContains($needle, $haystack, $message);
638
                } else {
639
                    parent::assertContainsEquals($needle, $haystack, $message);
640
                }
641
            } else {
642
                static::assertContainsNonIterable($needle, $haystack, $message, $ignoreCase);
643
            }
644
        }
645
646
        public static function assertContainsNonIterable(
647
            $needle,
648
            $haystack,
649
            $message = '',
650
            $ignoreCase = false
651
        ): void {
652
            if ($haystack instanceof DBField) {
653
                $haystack = (string)$haystack;
654
            }
655
            if ($ignoreCase) {
656
                parent::assertStringContainsStringIgnoringCase($needle, $haystack, $message);
657
            } else {
658
                parent::assertStringContainsString($needle, $haystack, $message);
659
            }
660
        }
661
662
        public static function assertNotContains(
663
            $needle,
664
            $haystack,
665
            $message = '',
666
            $ignoreCase = false,
667
            $checkForObjectIdentity = true,
668
            $checkForNonObjectIdentity = false
669
        ): void {
670
            if ($haystack instanceof DBField) {
671
                $haystack = (string)$haystack;
672
            }
673
            if (is_iterable($haystack)) {
674
                $strict = is_object($needle) ? $checkForObjectIdentity : $checkForObjectIdentity;
675
                if ($strict) {
676
                    parent::assertNotContains($needle, $haystack, $message);
677
                } else {
678
                    parent::assertNotContainsEquals($needle, $haystack, $message);
679
                }
680
            } else {
681
                static::assertNotContainsNonIterable($needle, $haystack, $message, $ignoreCase);
682
            }
683
        }
684
685
        protected static function assertNotContainsNonIterable(
686
            $needle,
687
            $haystack,
688
            $message = '',
689
            $ignoreCase = false
690
        ): void {
691
            if ($haystack instanceof DBField) {
692
                $haystack = (string)$haystack;
693
            }
694
            if ($ignoreCase) {
695
                parent::assertStringNotContainsStringIgnoringCase($needle, $haystack, $message);
696
            } else {
697
                parent::assertStringNotContainsString($needle, $haystack, $message);
698
            }
699
        }
700
701
        /**
702
         * Clear the log of emails sent
703
         *
704
         * @return bool True if emails cleared
705
         */
706
        public function clearEmails()
707
        {
708
            /** @var Mailer $mailer */
709
            $mailer = Injector::inst()->get(Mailer::class);
710
            if ($mailer instanceof TestMailer) {
711
                $mailer->clearEmails();
712
                return true;
713
            }
714
            return false;
715
        }
716
717
        /**
718
         * Search for an email that was sent.
719
         * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
720
         * @param string $to
721
         * @param string $from
722
         * @param string $subject
723
         * @param string $content
724
         * @return array|null Contains keys: 'Type', 'To', 'From', 'Subject', 'Content', 'PlainContent', 'AttachedFiles',
725
         *               'HtmlContent'
726
         */
727
        public static function findEmail($to, $from = null, $subject = null, $content = null)
728
        {
729
            /** @var Mailer $mailer */
730
            $mailer = Injector::inst()->get(Mailer::class);
731
            if ($mailer instanceof TestMailer) {
732
                return $mailer->findEmail($to, $from, $subject, $content);
733
            }
734
            return null;
735
        }
736
737
        /**
738
         * Assert that the matching email was sent since the last call to clearEmails()
739
         * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
740
         *
741
         * @param string $to
742
         * @param string $from
743
         * @param string $subject
744
         * @param string $content
745
         */
746
        public static function assertEmailSent($to, $from = null, $subject = null, $content = null)
747
        {
748
            $found = (bool)static::findEmail($to, $from, $subject, $content);
749
750
            $infoParts = '';
751
            $withParts = [];
752
            if ($to) {
753
                $infoParts .= " to '$to'";
754
            }
755
            if ($from) {
756
                $infoParts .= " from '$from'";
757
            }
758
            if ($subject) {
759
                $withParts[] = "subject '$subject'";
760
            }
761
            if ($content) {
762
                $withParts[] = "content '$content'";
763
            }
764
            if ($withParts) {
765
                $infoParts .= ' with ' . implode(' and ', $withParts);
766
            }
767
768
            static::assertTrue(
769
                $found,
770
                "Failed asserting that an email was sent$infoParts."
771
            );
772
        }
773
774
775
        /**
776
         * Assert that the given {@link SS_List} includes DataObjects matching the given key-value
777
         * pairs.  Each match must correspond to 1 distinct record.
778
         *
779
         * @param SS_List|array $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
780
         * either pass a single pattern or an array of patterns.
781
         * @param SS_List $list The {@link SS_List} to test.
782
         * @param string $message
783
         *
784
         * Examples
785
         * --------
786
         * Check that $members includes an entry with Email = [email protected]:
787
         *      $this->assertListContains(['Email' => '[email protected]'], $members);
788
         *
789
         * Check that $members includes entries with Email = [email protected] and with
790
         * Email = [email protected]:
791
         *      $this->assertListContains([
792
         *         ['Email' => '[email protected]'],
793
         *         ['Email' => '[email protected]'],
794
         *      ], $members);
795
         */
796
        public static function assertListContains($matches, SS_List $list, $message = '')
797
        {
798
            if (!is_array($matches)) {
799
                throw self::createPHPUnitFrameworkException(
800
                    1,
801
                    'array'
802
                );
803
            }
804
805
            static::assertThat(
806
                $list,
807
                new SSListContains(
808
                    $matches
809
                ),
810
                $message
811
            );
812
        }
813
814
        /**
815
         * @param $matches
816
         * @param $dataObjectSet
817
         * @deprecated 4.0.0:5.0.0 Use assertListContains() instead
818
         *
819
         */
820
        public function assertDOSContains($matches, $dataObjectSet)
821
        {
822
            Deprecation::notice('5.0', 'Use assertListContains() instead');
823
            static::assertListContains($matches, $dataObjectSet);
824
        }
825
826
        /**
827
         * Asserts that no items in a given list appear in the given dataobject list
828
         *
829
         * @param SS_List|array $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
830
         * either pass a single pattern or an array of patterns.
831
         * @param SS_List $list The {@link SS_List} to test.
832
         * @param string $message
833
         *
834
         * Examples
835
         * --------
836
         * Check that $members doesn't have an entry with Email = [email protected]:
837
         *      $this->assertListNotContains(['Email' => '[email protected]'], $members);
838
         *
839
         * Check that $members doesn't have entries with Email = [email protected] and with
840
         * Email = [email protected]:
841
         *      $this->assertListNotContains([
842
         *          ['Email' => '[email protected]'],
843
         *          ['Email' => '[email protected]'],
844
         *      ], $members);
845
         */
846
        public static function assertListNotContains($matches, SS_List $list, $message = '')
847
        {
848
            if (!is_array($matches)) {
849
                throw self::createPHPUnitFrameworkException(
850
                    1,
851
                    'array'
852
                );
853
            }
854
855
            $constraint = new LogicalNot(
856
                new SSListContains(
857
                    $matches
858
                )
859
            );
860
861
            static::assertThat(
862
                $list,
863
                $constraint,
864
                $message
865
            );
866
        }
867
868
        /**
869
         * @param $matches
870
         * @param $dataObjectSet
871
         * @deprecated 4.0.0:5.0.0 Use assertListNotContains() instead
872
         *
873
         */
874
        public static function assertNotDOSContains($matches, $dataObjectSet)
875
        {
876
            Deprecation::notice('5.0', 'Use assertListNotContains() instead');
877
            static::assertListNotContains($matches, $dataObjectSet);
878
        }
879
880
        /**
881
         * Assert that the given {@link SS_List} includes only DataObjects matching the given
882
         * key-value pairs.  Each match must correspond to 1 distinct record.
883
         *
884
         * Example
885
         * --------
886
         * Check that *only* the entries Sam Minnee and Ingo Schommer exist in $members.  Order doesn't
887
         * matter:
888
         *     $this->assertListEquals([
889
         *        ['FirstName' =>'Sam', 'Surname' => 'Minnee'],
890
         *        ['FirstName' => 'Ingo', 'Surname' => 'Schommer'],
891
         *      ], $members);
892
         *
893
         * @param mixed $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
894
         * either pass a single pattern or an array of patterns.
895
         * @param mixed $list The {@link SS_List} to test.
896
         * @param string $message
897
         */
898
        public static function assertListEquals($matches, SS_List $list, $message = '')
899
        {
900
            if (!is_array($matches)) {
901
                throw self::createPHPUnitFrameworkException(
902
                    1,
903
                    'array'
904
                );
905
            }
906
907
            static::assertThat(
908
                $list,
909
                new SSListContainsOnly(
910
                    $matches
911
                ),
912
                $message
913
            );
914
        }
915
916
        /**
917
         * @param $matches
918
         * @param SS_List $dataObjectSet
919
         * @deprecated 4.0.0:5.0.0 Use assertListEquals() instead
920
         *
921
         */
922
        public function assertDOSEquals($matches, $dataObjectSet)
923
        {
924
            Deprecation::notice('5.0', 'Use assertListEquals() instead');
925
            static::assertListEquals($matches, $dataObjectSet);
926
        }
927
928
929
        /**
930
         * Assert that the every record in the given {@link SS_List} matches the given key-value
931
         * pairs.
932
         *
933
         * Example
934
         * --------
935
         * Check that every entry in $members has a Status of 'Active':
936
         *     $this->assertListAllMatch(['Status' => 'Active'], $members);
937
         *
938
         * @param mixed $match The pattern to match.  The pattern is a map of key-value pairs.
939
         * @param mixed $list The {@link SS_List} to test.
940
         * @param string $message
941
         */
942
        public static function assertListAllMatch($match, SS_List $list, $message = '')
943
        {
944
            if (!is_array($match)) {
945
                throw self::createPHPUnitFrameworkException(
946
                    1,
947
                    'array'
948
                );
949
            }
950
951
            static::assertThat(
952
                $list,
953
                new SSListContainsOnlyMatchingItems(
954
                    $match
955
                ),
956
                $message
957
            );
958
        }
959
960
        /**
961
         * @param $match
962
         * @param SS_List $dataObjectSet
963
         * @deprecated 4.0.0:5.0.0 Use assertListAllMatch() instead
964
         *
965
         */
966
        public function assertDOSAllMatch($match, SS_List $dataObjectSet)
967
        {
968
            Deprecation::notice('5.0', 'Use assertListAllMatch() instead');
969
            static::assertListAllMatch($match, $dataObjectSet);
970
        }
971
972
        /**
973
         * Removes sequences of repeated whitespace characters from SQL queries
974
         * making them suitable for string comparison
975
         *
976
         * @param string $sql
977
         * @return string The cleaned and normalised SQL string
978
         */
979
        protected static function normaliseSQL($sql)
980
        {
981
            return trim(preg_replace('/\s+/m', ' ', $sql));
982
        }
983
984
        /**
985
         * Asserts that two SQL queries are equivalent
986
         *
987
         * @param string $expectedSQL
988
         * @param string $actualSQL
989
         * @param string $message
990
         * @param float|int $delta
991
         * @param integer $maxDepth
992
         * @param boolean $canonicalize
993
         * @param boolean $ignoreCase
994
         */
995
        public static function assertSQLEquals(
996
            $expectedSQL,
997
            $actualSQL,
998
            $message = ''
999
        ) {
1000
            // Normalise SQL queries to remove patterns of repeating whitespace
1001
            $expectedSQL = static::normaliseSQL($expectedSQL);
1002
            $actualSQL = static::normaliseSQL($actualSQL);
1003
1004
            static::assertEquals($expectedSQL, $actualSQL, $message);
1005
        }
1006
1007
        /**
1008
         * Asserts that a SQL query contains a SQL fragment
1009
         *
1010
         * @param string $needleSQL
1011
         * @param string $haystackSQL
1012
         * @param string $message
1013
         * @param boolean $ignoreCase
1014
         * @param boolean $checkForObjectIdentity
1015
         */
1016
        public static function assertSQLContains(
1017
            $needleSQL,
1018
            $haystackSQL,
1019
            $message = '',
1020
            $ignoreCase = false,
1021
            $checkForObjectIdentity = true
1022
        ) {
1023
            $needleSQL = static::normaliseSQL($needleSQL);
1024
            $haystackSQL = static::normaliseSQL($haystackSQL);
1025
            if (is_iterable($haystackSQL)) {
1026
                /** @var iterable $iterableHaystackSQL */
1027
                $iterableHaystackSQL = $haystackSQL;
1028
                static::assertContains($needleSQL, $iterableHaystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
1029
            } else {
1030
                static::assertContainsNonIterable($needleSQL, $haystackSQL, $message, $ignoreCase);
1031
            }
1032
        }
1033
1034
        /**
1035
         * Asserts that a SQL query contains a SQL fragment
1036
         *
1037
         * @param string $needleSQL
1038
         * @param string $haystackSQL
1039
         * @param string $message
1040
         * @param boolean $ignoreCase
1041
         * @param boolean $checkForObjectIdentity
1042
         */
1043
        public static function assertSQLNotContains(
1044
            $needleSQL,
1045
            $haystackSQL,
1046
            $message = '',
1047
            $ignoreCase = false,
1048
            $checkForObjectIdentity = true
1049
        ) {
1050
            $needleSQL = static::normaliseSQL($needleSQL);
1051
            $haystackSQL = static::normaliseSQL($haystackSQL);
1052
            if (is_iterable($haystackSQL)) {
1053
                /** @var iterable $iterableHaystackSQL */
1054
                $iterableHaystackSQL = $haystackSQL;
1055
                static::assertNotContains($needleSQL, $iterableHaystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
1056
            } else {
1057
                static::assertNotContainsNonIterable($needleSQL, $haystackSQL, $message, $ignoreCase);
1058
            }
1059
        }
1060
1061
        /**
1062
         * Start test environment
1063
         */
1064
        public static function start()
1065
        {
1066
            if (static::is_running_test()) {
1067
                return;
1068
            }
1069
1070
            // Health check
1071
            if (InjectorLoader::inst()->countManifests()) {
1072
                throw new LogicException('SapphireTest::start() cannot be called within another application');
1073
            }
1074
            static::set_is_running_test(true);
1075
1076
            // Test application
1077
            $kernel = new TestKernel(BASE_PATH);
1078
1079
            if (class_exists(HTTPApplication::class)) {
1080
                // Mock request
1081
                $_SERVER['argv'] = ['vendor/bin/phpunit', '/'];
1082
                $request = CLIRequestBuilder::createFromEnvironment();
1083
1084
                $app = new HTTPApplication($kernel);
1085
                $flush = array_key_exists('flush', $request->getVars());
1086
1087
                // Custom application
1088
                $res = $app->execute($request, function (HTTPRequest $request) {
1089
                    // Start session and execute
1090
                    $request->getSession()->init($request);
1091
1092
                    // Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class
1093
                    // (e.g. Member will now have various subclasses of DataObjects that implement TestOnly)
1094
                    DataObject::reset();
1095
1096
                    // Set dummy controller;
1097
                    $controller = Controller::create();
1098
                    $controller->setRequest($request);
1099
                    $controller->pushCurrent();
1100
                    $controller->doInit();
1101
                }, $flush);
1102
1103
                if ($res && $res->isError()) {
1104
                    throw new LogicException($res->getBody());
1105
                }
1106
            } else {
1107
                // Allow flush from the command line in the absence of HTTPApplication's special sauce
1108
                $flush = false;
1109
                foreach ($_SERVER['argv'] as $arg) {
1110
                    if (preg_match('/^(--)?flush(=1)?$/', $arg)) {
1111
                        $flush = true;
1112
                    }
1113
                }
1114
                $kernel->boot($flush);
1115
            }
1116
1117
            // Register state
1118
            static::$state = SapphireTestState::singleton();
1119
            // Register temp DB holder
1120
            static::tempDB();
1121
        }
1122
1123
        /**
1124
         * Reset the testing database's schema, but only if it is active
1125
         * @param bool $includeExtraDataObjects If true, the extraDataObjects tables will also be included
1126
         * @param bool $forceCreate Force DB to be created if it doesn't exist
1127
         */
1128
        public static function resetDBSchema($includeExtraDataObjects = false, $forceCreate = false)
1129
        {
1130
            if (!static::$tempDB) {
1131
                return;
1132
            }
1133
1134
            // Check if DB is active before reset
1135
            if (!static::$tempDB->isUsed()) {
1136
                if (!$forceCreate) {
1137
                    return;
1138
                }
1139
                static::$tempDB->build();
1140
            }
1141
            $extraDataObjects = $includeExtraDataObjects ? static::getExtraDataObjects() : [];
1142
            static::$tempDB->resetDBSchema((array)$extraDataObjects);
1143
        }
1144
1145
        /**
1146
         * A wrapper for automatically performing callbacks as a user with a specific permission
1147
         *
1148
         * @param string|array $permCode
1149
         * @param callable $callback
1150
         * @return mixed
1151
         */
1152
        public function actWithPermission($permCode, $callback)
1153
        {
1154
            return Member::actAs($this->createMemberWithPermission($permCode), $callback);
1155
        }
1156
1157
        /**
1158
         * Create Member and Group objects on demand with specific permission code
1159
         *
1160
         * @param string|array $permCode
1161
         * @return Member
1162
         */
1163
        protected function createMemberWithPermission($permCode)
1164
        {
1165
            if (is_array($permCode)) {
1166
                $permArray = $permCode;
1167
                $permCode = implode('.', $permCode);
1168
            } else {
1169
                $permArray = [$permCode];
1170
            }
1171
1172
            // Check cached member
1173
            if (isset($this->cache_generatedMembers[$permCode])) {
1174
                $member = $this->cache_generatedMembers[$permCode];
1175
            } else {
1176
                // Generate group with these permissions
1177
                $group = Group::create();
1178
                $group->Title = "$permCode group";
1179
                $group->write();
1180
1181
                // Create each individual permission
1182
                foreach ($permArray as $permArrayItem) {
1183
                    $permission = Permission::create();
1184
                    $permission->Code = $permArrayItem;
1185
                    $permission->write();
1186
                    $group->Permissions()->add($permission);
1187
                }
1188
1189
                $member = Member::get()->filter([
1190
                    'Email' => "[email protected]",
1191
                ])->first();
1192
                if (!$member) {
1193
                    $member = Member::create();
1194
                }
1195
1196
                $member->FirstName = $permCode;
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1197
                $member->Surname = 'User';
0 ignored issues
show
Bug Best Practice introduced by
The property Surname does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1198
                $member->Email = "[email protected]";
0 ignored issues
show
Bug Best Practice introduced by
The property Email does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1199
                $member->write();
1200
                $group->Members()->add($member);
1201
1202
                $this->cache_generatedMembers[$permCode] = $member;
1203
            }
1204
            return $member;
1205
        }
1206
1207
        /**
1208
         * Create a member and group with the given permission code, and log in with it.
1209
         * Returns the member ID.
1210
         *
1211
         * @param string|array $permCode Either a permission, or list of permissions
1212
         * @return int Member ID
1213
         */
1214
        public function logInWithPermission($permCode = 'ADMIN')
1215
        {
1216
            $member = $this->createMemberWithPermission($permCode);
1217
            $this->logInAs($member);
1218
            return $member->ID;
1219
        }
1220
1221
        /**
1222
         * Log in as the given member
1223
         *
1224
         * @param Member|int|string $member The ID, fixture codename, or Member object of the member that you want to log in
1225
         */
1226
        public function logInAs($member)
1227
        {
1228
            if (is_numeric($member)) {
1229
                $member = DataObject::get_by_id(Member::class, $member);
0 ignored issues
show
Bug introduced by
It seems like $member can also be of type string; however, parameter $idOrCache of SilverStripe\ORM\DataObject::get_by_id() does only seem to accept boolean|integer, maybe add an additional type check? ( Ignorable by Annotation )

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

1229
                $member = DataObject::get_by_id(Member::class, /** @scrutinizer ignore-type */ $member);
Loading history...
1230
            } elseif (!is_object($member)) {
1231
                $member = $this->objFromFixture(Member::class, $member);
1232
            }
1233
            Injector::inst()->get(IdentityStore::class)->logIn($member);
1234
        }
1235
1236
        /**
1237
         * Log out the current user
1238
         */
1239
        public function logOut()
1240
        {
1241
            /** @var IdentityStore $store */
1242
            $store = Injector::inst()->get(IdentityStore::class);
1243
            $store->logOut();
1244
        }
1245
1246
        /**
1247
         * Cache for logInWithPermission()
1248
         */
1249
        protected $cache_generatedMembers = [];
1250
1251
        /**
1252
         * Test against a theme.
1253
         *
1254
         * @param string $themeBaseDir themes directory
1255
         * @param string $theme Theme name
1256
         * @param callable $callback
1257
         * @throws Exception
1258
         */
1259
        protected function useTestTheme($themeBaseDir, $theme, $callback)
1260
        {
1261
            Config::nest();
1262
            if (strpos($themeBaseDir, BASE_PATH) === 0) {
1263
                $themeBaseDir = substr($themeBaseDir, strlen(BASE_PATH));
1264
            }
1265
            SSViewer::config()->update('theme_enabled', true);
1266
            SSViewer::set_themes([$themeBaseDir . '/themes/' . $theme, '$default']);
1267
1268
            try {
1269
                $callback();
1270
            } finally {
1271
                Config::unnest();
1272
            }
1273
        }
1274
1275
        /**
1276
         * Get fixture paths for this test
1277
         *
1278
         * @return array List of paths
1279
         */
1280
        protected function getFixturePaths()
1281
        {
1282
            $fixtureFile = static::get_fixture_file();
1283
            if (empty($fixtureFile)) {
1284
                return [];
1285
            }
1286
1287
            $fixtureFiles = is_array($fixtureFile) ? $fixtureFile : [$fixtureFile];
0 ignored issues
show
introduced by
The condition is_array($fixtureFile) is always false.
Loading history...
1288
1289
            return array_map(function ($fixtureFilePath) {
1290
                return $this->resolveFixturePath($fixtureFilePath);
1291
            }, $fixtureFiles);
1292
        }
1293
1294
        /**
1295
         * Return all extra objects to scaffold for this test
1296
         * @return array
1297
         */
1298
        public static function getExtraDataObjects()
1299
        {
1300
            return static::$extra_dataobjects;
1301
        }
1302
1303
        /**
1304
         * Get additional controller classes to register routes for
1305
         *
1306
         * @return array
1307
         */
1308
        public static function getExtraControllers()
1309
        {
1310
            return static::$extra_controllers;
1311
        }
1312
1313
        /**
1314
         * Map a fixture path to a physical file
1315
         *
1316
         * @param string $fixtureFilePath
1317
         * @return string
1318
         */
1319
        protected function resolveFixturePath($fixtureFilePath)
1320
        {
1321
            // support loading via composer name path.
1322
            if (strpos($fixtureFilePath, ':') !== false) {
1323
                return ModuleResourceLoader::singleton()->resolvePath($fixtureFilePath);
1324
            }
1325
1326
            // Support fixture paths relative to the test class, rather than relative to webroot
1327
            // String checking is faster than file_exists() calls.
1328
            $resolvedPath = realpath($this->getCurrentAbsolutePath() . '/' . $fixtureFilePath);
1329
            if ($resolvedPath) {
1330
                return $resolvedPath;
1331
            }
1332
1333
            // Check if file exists relative to base dir
1334
            $resolvedPath = realpath(Director::baseFolder() . '/' . $fixtureFilePath);
1335
            if ($resolvedPath) {
1336
                return $resolvedPath;
1337
            }
1338
1339
            return $fixtureFilePath;
1340
        }
1341
1342
        protected function setUpRoutes()
1343
        {
1344
            if (!class_exists(Director::class)) {
1345
                return;
1346
            }
1347
1348
            // Get overridden routes
1349
            $rules = $this->getExtraRoutes();
1350
1351
            // Add all other routes
1352
            foreach (Director::config()->uninherited('rules') as $route => $rule) {
1353
                if (!isset($rules[$route])) {
1354
                    $rules[$route] = $rule;
1355
                }
1356
            }
1357
1358
            // Add default catch-all rule
1359
            $rules['$Controller//$Action/$ID/$OtherID'] = '*';
1360
1361
            // Add controller-name auto-routing
1362
            Director::config()->set('rules', $rules);
1363
        }
1364
1365
        /**
1366
         * Get extra routes to merge into Director.rules
1367
         *
1368
         * @return array
1369
         */
1370
        protected function getExtraRoutes()
1371
        {
1372
            $rules = [];
1373
            foreach ($this->getExtraControllers() as $class) {
1374
                $controllerInst = Controller::singleton($class);
1375
                $link = Director::makeRelative($controllerInst->Link());
1376
                $route = rtrim($link, '/') . '//$Action/$ID/$OtherID';
1377
                $rules[$route] = $class;
1378
            }
1379
            return $rules;
1380
        }
1381
1382
        // === REIMPLEMENTATION METHODS THAT EXISTED IN SAPPHIRE_TEST 5 ===
1383
1384
        /**
1385
         * Reimplementation of phpunit5 PHPUnit_Util_InvalidArgumentHelper::factory()
1386
         *
1387
         * @param $argument
1388
         * @param $type
1389
         * @param $value
1390
         */
1391
        public static function createPHPUnitFrameworkException($argument, $type, $value = null)
1392
        {
1393
            $stack = debug_backtrace(false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $options of debug_backtrace(). ( Ignorable by Annotation )

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

1393
            $stack = debug_backtrace(/** @scrutinizer ignore-type */ false);
Loading history...
1394
1395
            return new PHPUnitFrameworkException(
1396
                sprintf(
1397
                    'Argument #%d%sof %s::%s() must be a %s',
1398
                    $argument,
1399
                    $value !== null ? ' (' . gettype($value) . '#' . $value . ')' : ' (No Value) ',
1400
                    $stack[1]['class'],
1401
                    $stack[1]['function'],
1402
                    $type
1403
                )
1404
            );
1405
        }
1406
1407
        /**
1408
         * Returns the annotations for this test.
1409
         *
1410
         * @return array
1411
         */
1412
        public function getAnnotations()
1413
        {
1414
            return TestUtil::parseTestMethodAnnotations(
1415
                get_class($this),
1416
                $this->getName(false)
1417
            );
1418
        }
1419
    }
1420
}
1421