Passed
Pull Request — 4 (#10028)
by Steve
08:50
created

SapphireTest::getAnnotations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 10
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;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Framework\Constraint\LogicalNot 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...
11
// use PHPUnit_Framework_TestCase;
12
use PHPUnit\Framework\TestCase;
13
// use PHPUnit_Util_InvalidArgumentHelper;
14
use PHPUnit\Framework\Exception as PHPUnitFrameworkException;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Framework\Exception 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...
15
use PHPUnit\Util\Test as TestUtil;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Util\Test 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...
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
    return;
50
}
51
52
/**
53
 * Test case class for the Sapphire framework.
54
 * Sapphire unit testing is based on PHPUnit, but provides a number of hooks into our data model that make it easier
55
 * to work with.
56
 *
57
 * This class should not be used anywhere outside of unit tests, as phpunit may not be installed
58
 * in production sites.
59
 */
60
class SapphireTest extends TestCase implements TestOnly
61
{
62
    /**
63
     * Path to fixture data for this test run.
64
     * If passed as an array, multiple fixture files will be loaded.
65
     * Please note that you won't be able to refer with "=>" notation
66
     * between the fixtures, they act independent of each other.
67
     *
68
     * @var string|array
69
     */
70
    protected static $fixture_file = null;
71
72
    /**
73
     * @deprecated 4.0..5.0 Use FixtureTestState instead
74
     * @var FixtureFactory
75
     */
76
    protected $fixtureFactory;
77
78
    /**
79
     * @var Boolean If set to TRUE, this will force a test database to be generated
80
     * in {@link setUp()}. Note that this flag is overruled by the presence of a
81
     * {@link $fixture_file}, which always forces a database build.
82
     *
83
     * @var bool
84
     */
85
    protected $usesDatabase = null;
86
87
    /**
88
     * This test will cleanup its state via transactions.
89
     * If set to false a full schema is forced between tests, but at a performance cost.
90
     *
91
     * @var bool
92
     */
93
    protected $usesTransactions = true;
94
95
    /**
96
     * @var bool
97
     */
98
    protected static $is_running_test = false;
99
100
    /**
101
     * By default, setUp() does not require default records. Pass
102
     * class names in here, and the require/augment default records
103
     * function will be called on them.
104
     *
105
     * @var array
106
     */
107
    protected $requireDefaultRecordsFrom = [];
108
109
    /**
110
     * A list of extensions that can't be applied during the execution of this run.  If they are
111
     * applied, they will be temporarily removed and a database migration called.
112
     *
113
     * The keys of the are the classes that the extensions can't be applied the extensions to, and
114
     * the values are an array of illegal extensions on that class.
115
     *
116
     * Set a class to `*` to remove all extensions (unadvised)
117
     *
118
     * @var array
119
     */
120
    protected static $illegal_extensions = [];
121
122
    /**
123
     * A list of extensions that must be applied during the execution of this run.  If they are
124
     * not applied, they will be temporarily added and a database migration called.
125
     *
126
     * The keys of the are the classes to apply the extensions to, and the values are an array
127
     * of required extensions on that class.
128
     *
129
     * Example:
130
     * <code>
131
     * array("MyTreeDataObject" => array("Versioned", "Hierarchy"))
132
     * </code>
133
     *
134
     * @var array
135
     */
136
    protected static $required_extensions = [];
137
138
    /**
139
     * By default, the test database won't contain any DataObjects that have the interface TestOnly.
140
     * This variable lets you define additional TestOnly DataObjects to set up for this test.
141
     * Set it to an array of DataObject subclass names.
142
     *
143
     * @var array
144
     */
145
    protected static $extra_dataobjects = [];
146
147
    /**
148
     * List of class names of {@see Controller} objects to register routes for
149
     * Controllers must implement Link() method
150
     *
151
     * @var array
152
     */
153
    protected static $extra_controllers = [];
154
155
    /**
156
     * We need to disabling backing up of globals to avoid overriding
157
     * the few globals SilverStripe relies on, like $lang for the i18n subsystem.
158
     *
159
     * @see http://sebastian-bergmann.de/archives/797-Global-Variables-and-PHPUnit.html
160
     */
161
    protected $backupGlobals = false;
162
163
    /**
164
     * State management container for SapphireTest
165
     *
166
     * @var SapphireTestState
167
     */
168
    protected static $state = null;
169
170
    /**
171
     * Temp database helper
172
     *
173
     * @var TempDatabase
174
     */
175
    protected static $tempDB = null;
176
177
    /**
178
     * @return TempDatabase
179
     */
180
    public static function tempDB()
181
    {
182
        if (!class_exists(TempDatabase::class)) {
183
            return null;
184
        }
185
186
        if (!static::$tempDB) {
187
            static::$tempDB = TempDatabase::create();
188
        }
189
        return static::$tempDB;
190
    }
191
192
    /**
193
     * Gets illegal extensions for this class
194
     *
195
     * @return array
196
     */
197
    public static function getIllegalExtensions()
198
    {
199
        return static::$illegal_extensions;
200
    }
201
202
    /**
203
     * Gets required extensions for this class
204
     *
205
     * @return array
206
     */
207
    public static function getRequiredExtensions()
208
    {
209
        return static::$required_extensions;
210
    }
211
212
    /**
213
     * Check if test bootstrapping has been performed. Must not be relied on
214
     * outside of unit tests.
215
     *
216
     * @return bool
217
     */
218
    protected static function is_running_test()
219
    {
220
        return self::$is_running_test;
221
    }
222
223
    /**
224
     * Set test running state
225
     *
226
     * @param bool $bool
227
     */
228
    protected static function set_is_running_test($bool)
229
    {
230
        self::$is_running_test = $bool;
231
    }
232
233
    /**
234
     * @return String
235
     */
236
    public static function get_fixture_file()
237
    {
238
        return static::$fixture_file;
239
    }
240
241
    /**
242
     * @return bool
243
     */
244
    public function getUsesDatabase()
245
    {
246
        return $this->usesDatabase;
247
    }
248
249
    /**
250
     * @return bool
251
     */
252
    public function getUsesTransactions()
253
    {
254
        return $this->usesTransactions;
255
    }
256
257
    /**
258
     * @return array
259
     */
260
    public function getRequireDefaultRecordsFrom()
261
    {
262
        return $this->requireDefaultRecordsFrom;
263
    }
264
265
    /**
266
     * Setup  the test.
267
     * Always sets up in order:
268
     *  - Reset php state
269
     *  - Nest
270
     *  - Custom state helpers
271
     *
272
     * User code should call parent::setUp() before custom setup code
273
     */
274
    protected function setUp(): void
275
    {
276
        if (!defined('FRAMEWORK_PATH')) {
277
            trigger_error(
278
                'Missing constants, did you remember to include the test bootstrap in your phpunit.xml file?',
279
                E_USER_WARNING
280
            );
281
        }
282
283
        // Call state helpers
284
        static::$state->setUp($this);
285
286
        // We cannot run the tests on this abstract class.
287
        if (static::class == __CLASS__) {
0 ignored issues
show
introduced by
The condition static::class == __CLASS__ is always true.
Loading history...
288
            $this->markTestSkipped(sprintf('Skipping %s ', static::class));
289
            return;
290
        }
291
292
        // i18n needs to be set to the defaults or tests fail
293
        if (class_exists(i18n::class)) {
294
            i18n::set_locale(i18n::config()->uninherited('default_locale'));
295
        }
296
297
        // Set default timezone consistently to avoid NZ-specific dependencies
298
        date_default_timezone_set('UTC');
299
300
        if (class_exists(Member::class)) {
301
            Member::set_password_validator(null);
302
        }
303
304
        if (class_exists(Cookie::class)) {
305
            Cookie::config()->update('report_errors', false);
306
        }
307
308
        if (class_exists(RootURLController::class)) {
309
            RootURLController::reset();
310
        }
311
312
        if (class_exists(Security::class)) {
313
            Security::clear_database_is_ready();
314
        }
315
316
        // Set up test routes
317
        $this->setUpRoutes();
318
319
        $fixtureFiles = $this->getFixturePaths();
320
321
        if ($this->shouldSetupDatabaseForCurrentTest($fixtureFiles)) {
322
            // Assign fixture factory to deprecated prop in case old tests use it over the getter
323
            /** @var FixtureTestState $fixtureState */
324
            $fixtureState = static::$state->getStateByName('fixtures');
325
            $this->fixtureFactory = $fixtureState->getFixtureFactory(static::class);
326
327
            $this->logInWithPermission('ADMIN');
328
        }
329
330
        // turn off template debugging
331
        if (class_exists(SSViewer::class)) {
332
            SSViewer::config()->update('source_file_comments', false);
333
        }
334
335
        // Set up the test mailer
336
        if (class_exists(TestMailer::class)) {
337
            Injector::inst()->registerService(new TestMailer(), Mailer::class);
338
        }
339
340
        if (class_exists(Email::class)) {
341
            Email::config()->remove('send_all_emails_to');
342
            Email::config()->remove('send_all_emails_from');
343
            Email::config()->remove('cc_all_emails_to');
344
            Email::config()->remove('bcc_all_emails_to');
345
        }
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()
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()
439
    {
440
        // Call state helpers
441
        static::$state->tearDownOnce(static::class);
442
443
        // Reset DB schema
444
        static::resetDBSchema();
445
    }
446
447
    /**
448
     * @deprecated 4.0.0:5.0.0
449
     * @return FixtureFactory|false
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
     * @deprecated 4.0.0:5.0.0
462
     * @param FixtureFactory $factory
463
     * @return $this
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
     * @deprecated 4.0.0:5.0.0
543
     *
544
     * @param string $fixtureFile The location of the .yml fixture file, relative to the site base dir
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()
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
    ) {
631
        if ($haystack instanceof DBField) {
632
            $haystack = (string)$haystack;
633
        }
634
        parent::assertContains($needle, $haystack, $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity);
635
    }
636
637
    public static function assertNotContains(
638
        $needle,
639
        $haystack,
640
        $message = '',
641
        $ignoreCase = false,
642
        $checkForObjectIdentity = true,
643
        $checkForNonObjectIdentity = false
644
    ) {
645
        if ($haystack instanceof DBField) {
646
            $haystack = (string)$haystack;
647
        }
648
        parent::assertNotContains($needle, $haystack, $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity);
649
    }
650
651
    /**
652
     * Clear the log of emails sent
653
     *
654
     * @return bool True if emails cleared
655
     */
656
    public function clearEmails()
657
    {
658
        /** @var Mailer $mailer */
659
        $mailer = Injector::inst()->get(Mailer::class);
660
        if ($mailer instanceof TestMailer) {
661
            $mailer->clearEmails();
662
            return true;
663
        }
664
        return false;
665
    }
666
667
    /**
668
     * Search for an email that was sent.
669
     * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
670
     * @param string $to
671
     * @param string $from
672
     * @param string $subject
673
     * @param string $content
674
     * @return array|null Contains keys: 'Type', 'To', 'From', 'Subject', 'Content', 'PlainContent', 'AttachedFiles',
675
     *               'HtmlContent'
676
     */
677
    public static function findEmail($to, $from = null, $subject = null, $content = null)
678
    {
679
        /** @var Mailer $mailer */
680
        $mailer = Injector::inst()->get(Mailer::class);
681
        if ($mailer instanceof TestMailer) {
682
            return $mailer->findEmail($to, $from, $subject, $content);
683
        }
684
        return null;
685
    }
686
687
    /**
688
     * Assert that the matching email was sent since the last call to clearEmails()
689
     * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
690
     *
691
     * @param string $to
692
     * @param string $from
693
     * @param string $subject
694
     * @param string $content
695
     */
696
    public static function assertEmailSent($to, $from = null, $subject = null, $content = null)
697
    {
698
        $found = (bool)static::findEmail($to, $from, $subject, $content);
699
700
        $infoParts = '';
701
        $withParts = [];
702
        if ($to) {
703
            $infoParts .= " to '$to'";
704
        }
705
        if ($from) {
706
            $infoParts .= " from '$from'";
707
        }
708
        if ($subject) {
709
            $withParts[] = "subject '$subject'";
710
        }
711
        if ($content) {
712
            $withParts[] = "content '$content'";
713
        }
714
        if ($withParts) {
715
            $infoParts .= ' with ' . implode(' and ', $withParts);
716
        }
717
718
        static::assertTrue(
719
            $found,
720
            "Failed asserting that an email was sent$infoParts."
721
        );
722
    }
723
724
725
    /**
726
     * Assert that the given {@link SS_List} includes DataObjects matching the given key-value
727
     * pairs.  Each match must correspond to 1 distinct record.
728
     *
729
     * @param SS_List|array $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
730
     * either pass a single pattern or an array of patterns.
731
     * @param SS_List $list The {@link SS_List} to test.
732
     * @param string $message
733
     *
734
     * Examples
735
     * --------
736
     * Check that $members includes an entry with Email = [email protected]:
737
     *      $this->assertListContains(['Email' => '[email protected]'], $members);
738
     *
739
     * Check that $members includes entries with Email = [email protected] and with
740
     * Email = [email protected]:
741
     *      $this->assertListContains([
742
     *         ['Email' => '[email protected]'],
743
     *         ['Email' => '[email protected]'],
744
     *      ], $members);
745
     */
746
    public static function assertListContains($matches, SS_List $list, $message = '')
747
    {
748
        if (!is_array($matches)) {
749
            throw self::createPHPUnitFrameworkException(
750
                1,
751
                'array'
752
            );
753
        }
754
755
        static::assertThat(
756
            $list,
757
            new SSListContains(
758
                $matches
759
            ),
760
            $message
761
        );
762
    }
763
764
    /**
765
     * @deprecated 4.0.0:5.0.0 Use assertListContains() instead
766
     *
767
     * @param $matches
768
     * @param $dataObjectSet
769
     */
770
    public function assertDOSContains($matches, $dataObjectSet)
771
    {
772
        Deprecation::notice('5.0', 'Use assertListContains() instead');
773
        return static::assertListContains($matches, $dataObjectSet);
0 ignored issues
show
Bug introduced by
Are you sure the usage of static::assertListContai...atches, $dataObjectSet) targeting SilverStripe\Dev\Sapphir...t::assertListContains() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
774
    }
775
776
    /**
777
     * Asserts that no items in a given list appear in the given dataobject list
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 doesn't have an entry with Email = [email protected]:
787
     *      $this->assertListNotContains(['Email' => '[email protected]'], $members);
788
     *
789
     * Check that $members doesn't have entries with Email = [email protected] and with
790
     * Email = [email protected]:
791
     *      $this->assertListNotContains([
792
     *          ['Email' => '[email protected]'],
793
     *          ['Email' => '[email protected]'],
794
     *      ], $members);
795
     */
796
    public static function assertListNotContains($matches, SS_List $list, $message = '')
797
    {
798
        if (!is_array($matches)) {
799
            throw self::createPHPUnitFrameworkException(
800
                1,
801
                'array'
802
            );
803
        }
804
805
        $constraint =  new LogicalNot(
806
            new SSListContains(
807
                $matches
808
            )
809
        );
810
811
        static::assertThat(
812
            $list,
813
            $constraint,
814
            $message
815
        );
816
    }
817
818
    /**
819
     * @deprecated 4.0.0:5.0.0 Use assertListNotContains() instead
820
     *
821
     * @param $matches
822
     * @param $dataObjectSet
823
     */
824
    public static function assertNotDOSContains($matches, $dataObjectSet)
825
    {
826
        Deprecation::notice('5.0', 'Use assertListNotContains() instead');
827
        return static::assertListNotContains($matches, $dataObjectSet);
0 ignored issues
show
Bug introduced by
Are you sure the usage of static::assertListNotCon...atches, $dataObjectSet) targeting SilverStripe\Dev\Sapphir...assertListNotContains() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
828
    }
829
830
    /**
831
     * Assert that the given {@link SS_List} includes only DataObjects matching the given
832
     * key-value pairs.  Each match must correspond to 1 distinct record.
833
     *
834
     * Example
835
     * --------
836
     * Check that *only* the entries Sam Minnee and Ingo Schommer exist in $members.  Order doesn't
837
     * matter:
838
     *     $this->assertListEquals([
839
     *        ['FirstName' =>'Sam', 'Surname' => 'Minnee'],
840
     *        ['FirstName' => 'Ingo', 'Surname' => 'Schommer'],
841
     *      ], $members);
842
     *
843
     * @param mixed $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
844
     * either pass a single pattern or an array of patterns.
845
     * @param mixed $list The {@link SS_List} to test.
846
     * @param string $message
847
     */
848
    public static function assertListEquals($matches, SS_List $list, $message = '')
849
    {
850
        if (!is_array($matches)) {
851
            throw self::createPHPUnitFrameworkException(
852
                1,
853
                'array'
854
            );
855
        }
856
857
        static::assertThat(
858
            $list,
859
            new SSListContainsOnly(
860
                $matches
861
            ),
862
            $message
863
        );
864
    }
865
866
    /**
867
     * @deprecated 4.0.0:5.0.0 Use assertListEquals() instead
868
     *
869
     * @param $matches
870
     * @param SS_List $dataObjectSet
871
     */
872
    public function assertDOSEquals($matches, $dataObjectSet)
873
    {
874
        Deprecation::notice('5.0', 'Use assertListEquals() instead');
875
        return static::assertListEquals($matches, $dataObjectSet);
0 ignored issues
show
Bug introduced by
Are you sure the usage of static::assertListEquals($matches, $dataObjectSet) targeting SilverStripe\Dev\SapphireTest::assertListEquals() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
876
    }
877
878
879
    /**
880
     * Assert that the every record in the given {@link SS_List} matches the given key-value
881
     * pairs.
882
     *
883
     * Example
884
     * --------
885
     * Check that every entry in $members has a Status of 'Active':
886
     *     $this->assertListAllMatch(['Status' => 'Active'], $members);
887
     *
888
     * @param mixed $match The pattern to match.  The pattern is a map of key-value pairs.
889
     * @param mixed $list The {@link SS_List} to test.
890
     * @param string $message
891
     */
892
    public static function assertListAllMatch($match, SS_List $list, $message = '')
893
    {
894
        if (!is_array($match)) {
895
            throw self::createPHPUnitFrameworkException(
896
                1,
897
                'array'
898
            );
899
        }
900
901
        static::assertThat(
902
            $list,
903
            new SSListContainsOnlyMatchingItems(
904
                $match
905
            ),
906
            $message
907
        );
908
    }
909
910
    /**
911
     * @deprecated 4.0.0:5.0.0 Use assertListAllMatch() instead
912
     *
913
     * @param $match
914
     * @param SS_List $dataObjectSet
915
     */
916
    public function assertDOSAllMatch($match, SS_List $dataObjectSet)
917
    {
918
        Deprecation::notice('5.0', 'Use assertListAllMatch() instead');
919
        return static::assertListAllMatch($match, $dataObjectSet);
0 ignored issues
show
Bug introduced by
Are you sure the usage of static::assertListAllMatch($match, $dataObjectSet) targeting SilverStripe\Dev\Sapphir...t::assertListAllMatch() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
920
    }
921
922
    /**
923
     * Removes sequences of repeated whitespace characters from SQL queries
924
     * making them suitable for string comparison
925
     *
926
     * @param string $sql
927
     * @return string The cleaned and normalised SQL string
928
     */
929
    protected static function normaliseSQL($sql)
930
    {
931
        return trim(preg_replace('/\s+/m', ' ', $sql));
932
    }
933
934
    /**
935
     * Asserts that two SQL queries are equivalent
936
     *
937
     * @param string $expectedSQL
938
     * @param string $actualSQL
939
     * @param string $message
940
     * @param float|int $delta
941
     * @param integer $maxDepth
942
     * @param boolean $canonicalize
943
     * @param boolean $ignoreCase
944
     */
945
    public static function assertSQLEquals(
946
        $expectedSQL,
947
        $actualSQL,
948
        $message = '',
949
        $delta = 0,
950
        $maxDepth = 10,
951
        $canonicalize = false,
952
        $ignoreCase = false
953
    ) {
954
        // Normalise SQL queries to remove patterns of repeating whitespace
955
        $expectedSQL = static::normaliseSQL($expectedSQL);
956
        $actualSQL = static::normaliseSQL($actualSQL);
957
958
        static::assertEquals($expectedSQL, $actualSQL, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
959
    }
960
961
    /**
962
     * Asserts that a SQL query contains a SQL fragment
963
     *
964
     * @param string $needleSQL
965
     * @param string $haystackSQL
966
     * @param string $message
967
     * @param boolean $ignoreCase
968
     * @param boolean $checkForObjectIdentity
969
     */
970
    public static function assertSQLContains(
971
        $needleSQL,
972
        $haystackSQL,
973
        $message = '',
974
        $ignoreCase = false,
975
        $checkForObjectIdentity = true
976
    ) {
977
        $needleSQL = static::normaliseSQL($needleSQL);
978
        $haystackSQL = static::normaliseSQL($haystackSQL);
979
980
        static::assertContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
981
    }
982
983
    /**
984
     * Asserts that a SQL query contains a SQL fragment
985
     *
986
     * @param string $needleSQL
987
     * @param string $haystackSQL
988
     * @param string $message
989
     * @param boolean $ignoreCase
990
     * @param boolean $checkForObjectIdentity
991
     */
992
    public static function assertSQLNotContains(
993
        $needleSQL,
994
        $haystackSQL,
995
        $message = '',
996
        $ignoreCase = false,
997
        $checkForObjectIdentity = true
998
    ) {
999
        $needleSQL = static::normaliseSQL($needleSQL);
1000
        $haystackSQL = static::normaliseSQL($haystackSQL);
1001
1002
        static::assertNotContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
1003
    }
1004
1005
    /**
1006
     * Start test environment
1007
     */
1008
    public static function start()
1009
    {
1010
        if (static::is_running_test()) {
1011
            return;
1012
        }
1013
1014
        // Health check
1015
        if (InjectorLoader::inst()->countManifests()) {
1016
            throw new LogicException('SapphireTest::start() cannot be called within another application');
1017
        }
1018
        static::set_is_running_test(true);
1019
1020
        // Test application
1021
        $kernel = new TestKernel(BASE_PATH);
1022
1023
        if (class_exists(HTTPApplication::class)) {
1024
            // Mock request
1025
            $_SERVER['argv'] = ['vendor/bin/phpunit', '/'];
1026
            $request = CLIRequestBuilder::createFromEnvironment();
1027
1028
            $app = new HTTPApplication($kernel);
1029
            $flush = array_key_exists('flush', $request->getVars());
1030
1031
            // Custom application
1032
            $res = $app->execute($request, function (HTTPRequest $request) {
1033
                // Start session and execute
1034
                $request->getSession()->init($request);
1035
1036
                // Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class
1037
                // (e.g. Member will now have various subclasses of DataObjects that implement TestOnly)
1038
                DataObject::reset();
1039
1040
                // Set dummy controller;
1041
                $controller = Controller::create();
1042
                $controller->setRequest($request);
1043
                $controller->pushCurrent();
1044
                $controller->doInit();
1045
            }, $flush);
1046
1047
            if ($res && $res->isError()) {
1048
                throw new LogicException($res->getBody());
1049
            }
1050
        } else {
1051
            // Allow flush from the command line in the absence of HTTPApplication's special sauce
1052
            $flush = false;
1053
            foreach ($_SERVER['argv'] as $arg) {
1054
                if (preg_match('/^(--)?flush(=1)?$/', $arg)) {
1055
                    $flush = true;
1056
                }
1057
            }
1058
            $kernel->boot($flush);
1059
        }
1060
1061
        // Register state
1062
        static::$state = SapphireTestState::singleton();
1063
        // Register temp DB holder
1064
        static::tempDB();
1065
    }
1066
1067
    /**
1068
     * Reset the testing database's schema, but only if it is active
1069
     * @param bool $includeExtraDataObjects If true, the extraDataObjects tables will also be included
1070
     * @param bool $forceCreate Force DB to be created if it doesn't exist
1071
     */
1072
    public static function resetDBSchema($includeExtraDataObjects = false, $forceCreate = false)
1073
    {
1074
        if (!static::$tempDB) {
1075
            return;
1076
        }
1077
1078
        // Check if DB is active before reset
1079
        if (!static::$tempDB->isUsed()) {
1080
            if (!$forceCreate) {
1081
                return;
1082
            }
1083
            static::$tempDB->build();
1084
        }
1085
        $extraDataObjects = $includeExtraDataObjects ? static::getExtraDataObjects() : [];
1086
        static::$tempDB->resetDBSchema((array)$extraDataObjects);
1087
    }
1088
1089
    /**
1090
     * A wrapper for automatically performing callbacks as a user with a specific permission
1091
     *
1092
     * @param string|array $permCode
1093
     * @param callable $callback
1094
     * @return mixed
1095
     */
1096
    public function actWithPermission($permCode, $callback)
1097
    {
1098
        return Member::actAs($this->createMemberWithPermission($permCode), $callback);
1099
    }
1100
1101
    /**
1102
     * Create Member and Group objects on demand with specific permission code
1103
     *
1104
     * @param string|array $permCode
1105
     * @return Member
1106
     */
1107
    protected function createMemberWithPermission($permCode)
1108
    {
1109
        if (is_array($permCode)) {
1110
            $permArray = $permCode;
1111
            $permCode = implode('.', $permCode);
1112
        } else {
1113
            $permArray = [$permCode];
1114
        }
1115
1116
        // Check cached member
1117
        if (isset($this->cache_generatedMembers[$permCode])) {
1118
            $member = $this->cache_generatedMembers[$permCode];
1119
        } else {
1120
            // Generate group with these permissions
1121
            $group = Group::create();
1122
            $group->Title = "$permCode group";
1123
            $group->write();
1124
1125
            // Create each individual permission
1126
            foreach ($permArray as $permArrayItem) {
1127
                $permission = Permission::create();
1128
                $permission->Code = $permArrayItem;
1129
                $permission->write();
1130
                $group->Permissions()->add($permission);
1131
            }
1132
1133
            $member = Member::get()->filter([
1134
                'Email' => "[email protected]",
1135
            ])->first();
1136
            if (!$member) {
1137
                $member = Member::create();
1138
            }
1139
1140
            $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...
1141
            $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...
1142
            $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...
1143
            $member->write();
1144
            $group->Members()->add($member);
1145
1146
            $this->cache_generatedMembers[$permCode] = $member;
1147
        }
1148
        return $member;
1149
    }
1150
1151
    /**
1152
     * Create a member and group with the given permission code, and log in with it.
1153
     * Returns the member ID.
1154
     *
1155
     * @param string|array $permCode Either a permission, or list of permissions
1156
     * @return int Member ID
1157
     */
1158
    public function logInWithPermission($permCode = 'ADMIN')
1159
    {
1160
        $member = $this->createMemberWithPermission($permCode);
1161
        $this->logInAs($member);
1162
        return $member->ID;
1163
    }
1164
1165
    /**
1166
     * Log in as the given member
1167
     *
1168
     * @param Member|int|string $member The ID, fixture codename, or Member object of the member that you want to log in
1169
     */
1170
    public function logInAs($member)
1171
    {
1172
        if (is_numeric($member)) {
1173
            $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

1173
            $member = DataObject::get_by_id(Member::class, /** @scrutinizer ignore-type */ $member);
Loading history...
1174
        } elseif (!is_object($member)) {
1175
            $member = $this->objFromFixture(Member::class, $member);
1176
        }
1177
        Injector::inst()->get(IdentityStore::class)->logIn($member);
1178
    }
1179
1180
    /**
1181
     * Log out the current user
1182
     */
1183
    public function logOut()
1184
    {
1185
        /** @var IdentityStore $store */
1186
        $store = Injector::inst()->get(IdentityStore::class);
1187
        $store->logOut();
1188
    }
1189
1190
    /**
1191
     * Cache for logInWithPermission()
1192
     */
1193
    protected $cache_generatedMembers = [];
1194
1195
    /**
1196
     * Test against a theme.
1197
     *
1198
     * @param string $themeBaseDir themes directory
1199
     * @param string $theme Theme name
1200
     * @param callable $callback
1201
     * @throws Exception
1202
     */
1203
    protected function useTestTheme($themeBaseDir, $theme, $callback)
1204
    {
1205
        Config::nest();
1206
        if (strpos($themeBaseDir, BASE_PATH) === 0) {
1207
            $themeBaseDir = substr($themeBaseDir, strlen(BASE_PATH));
1208
        }
1209
        SSViewer::config()->update('theme_enabled', true);
1210
        SSViewer::set_themes([$themeBaseDir . '/themes/' . $theme, '$default']);
1211
1212
        try {
1213
            $callback();
1214
        } finally {
1215
            Config::unnest();
1216
        }
1217
    }
1218
1219
    /**
1220
     * Get fixture paths for this test
1221
     *
1222
     * @return array List of paths
1223
     */
1224
    protected function getFixturePaths()
1225
    {
1226
        $fixtureFile = static::get_fixture_file();
1227
        if (empty($fixtureFile)) {
1228
            return [];
1229
        }
1230
1231
        $fixtureFiles = is_array($fixtureFile) ? $fixtureFile : [$fixtureFile];
0 ignored issues
show
introduced by
The condition is_array($fixtureFile) is always false.
Loading history...
1232
1233
        return array_map(function ($fixtureFilePath) {
1234
            return $this->resolveFixturePath($fixtureFilePath);
1235
        }, $fixtureFiles);
1236
    }
1237
1238
    /**
1239
     * Return all extra objects to scaffold for this test
1240
     * @return array
1241
     */
1242
    public static function getExtraDataObjects()
1243
    {
1244
        return static::$extra_dataobjects;
1245
    }
1246
1247
    /**
1248
     * Get additional controller classes to register routes for
1249
     *
1250
     * @return array
1251
     */
1252
    public static function getExtraControllers()
1253
    {
1254
        return static::$extra_controllers;
1255
    }
1256
1257
    /**
1258
     * Map a fixture path to a physical file
1259
     *
1260
     * @param string $fixtureFilePath
1261
     * @return string
1262
     */
1263
    protected function resolveFixturePath($fixtureFilePath)
1264
    {
1265
        // support loading via composer name path.
1266
        if (strpos($fixtureFilePath, ':') !== false) {
1267
            return ModuleResourceLoader::singleton()->resolvePath($fixtureFilePath);
1268
        }
1269
1270
        // Support fixture paths relative to the test class, rather than relative to webroot
1271
        // String checking is faster than file_exists() calls.
1272
        $resolvedPath = realpath($this->getCurrentAbsolutePath() . '/' . $fixtureFilePath);
1273
        if ($resolvedPath) {
1274
            return $resolvedPath;
1275
        }
1276
1277
        // Check if file exists relative to base dir
1278
        $resolvedPath = realpath(Director::baseFolder() . '/' . $fixtureFilePath);
1279
        if ($resolvedPath) {
1280
            return $resolvedPath;
1281
        }
1282
1283
        return $fixtureFilePath;
1284
    }
1285
1286
    protected function setUpRoutes()
1287
    {
1288
        if (!class_exists(Director::class)) {
1289
            return;
1290
        }
1291
1292
        // Get overridden routes
1293
        $rules = $this->getExtraRoutes();
1294
1295
        // Add all other routes
1296
        foreach (Director::config()->uninherited('rules') as $route => $rule) {
1297
            if (!isset($rules[$route])) {
1298
                $rules[$route] = $rule;
1299
            }
1300
        }
1301
1302
        // Add default catch-all rule
1303
        $rules['$Controller//$Action/$ID/$OtherID'] = '*';
1304
1305
        // Add controller-name auto-routing
1306
        Director::config()->set('rules', $rules);
1307
    }
1308
1309
    /**
1310
     * Get extra routes to merge into Director.rules
1311
     *
1312
     * @return array
1313
     */
1314
    protected function getExtraRoutes()
1315
    {
1316
        $rules = [];
1317
        foreach ($this->getExtraControllers() as $class) {
1318
            $controllerInst = Controller::singleton($class);
1319
            $link = Director::makeRelative($controllerInst->Link());
1320
            $route = rtrim($link, '/') . '//$Action/$ID/$OtherID';
1321
            $rules[$route] = $class;
1322
        }
1323
        return $rules;
1324
    }
1325
1326
    // === REIMPLEMENTATION METHODS THAT EXISTED IN SAPPHIRETEST 5 ===
1327
1328
    /**
1329
     * Reimplementation of phpunit5 PHPUnit_Util_InvalidArgumentHelper::factory()
1330
     *
1331
     * @param $argument
1332
     * @param $type
1333
     * @param $value
1334
     */
1335
    protected static function createPHPUnitFrameworkException($argument, $type, $value = null)
1336
    {
1337
        $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

1337
        $stack = debug_backtrace(/** @scrutinizer ignore-type */ false);
Loading history...
1338
1339
        return new PHPUnitFrameworkException(
1340
            sprintf(
1341
                'Argument #%d%sof %s::%s() must be a %s',
1342
                $argument,
1343
                $value !== null ? ' (' . gettype($value) . '#' . $value . ')' : ' (No Value) ',
1344
                $stack[1]['class'],
1345
                $stack[1]['function'],
1346
                $type
1347
            )
1348
        );
1349
    }
1350
1351
    /**
1352
     * Returns the annotations for this test.
1353
     *
1354
     * @return array
1355
     */
1356
    public function getAnnotations()
1357
    {
1358
        return TestUtil::parseTestMethodAnnotations(
1359
            get_class($this),
1360
            $this->name
0 ignored issues
show
Bug introduced by
The property name is declared private in PHPUnit_Framework_TestCase and cannot be accessed from this context.
Loading history...
1361
        );
1362
    }
1363
}
1364