Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Support library for the cache PHPUnit tests.
19
 *
20
 * This file is part of Moodle's cache API, affectionately called MUC.
21
 * It contains the components that are requried in order to use caching.
22
 *
1441 ariadna 23
 * @package    core_cache
1 efrain 24
 * @category   cache
25
 * @copyright  2012 Sam Hemelryk
26
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27
 */
28
 
29
defined('MOODLE_INTERNAL') || die();
30
 
1441 ariadna 31
use core_cache\store;
1 efrain 32
 
33
/**
34
 * Override the default cache configuration for our own maniacal purposes.
35
 *
36
 * This class was originally named cache_config_phpunittest but was renamed in 2.9
37
 * because it is used for both unit tests and acceptance tests.
38
 *
39
 * @since 2.9
40
 * @copyright 2012 Sam Hemelryk
41
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42
 */
43
class cache_config_testing extends cache_config_writer {
44
    /**
45
     * Creates the default configuration and saves it.
46
     *
47
     * This function calls config_save, however it is safe to continue using it afterwards as this function should only ever
48
     * be called when there is no configuration file already.
49
     *
50
     * @param bool $forcesave If set to true then we will forcefully save the default configuration file.
51
     * @return true|array Returns true if the default configuration was successfully created.
52
     *     Returns a configuration array if it could not be saved. This is a bad situation. Check your error logs.
53
     */
54
    public static function create_default_configuration($forcesave = false) {
55
        global $CFG;
56
        // HACK ALERT.
57
        // We probably need to come up with a better way to create the default stores, or at least ensure 100% that the
58
        // default store plugins are protected from deletion.
1441 ariadna 59
        $writer = new self();
1 efrain 60
        $writer->configstores = self::get_default_stores();
61
        $writer->configdefinitions = self::locate_definitions();
62
        $defaultapplication = 'default_application';
63
 
64
        $appdefine = defined('TEST_CACHE_USING_APPLICATION_STORE') ? TEST_CACHE_USING_APPLICATION_STORE : false;
65
        if ($appdefine !== false && preg_match('/^[a-zA-Z][a-zA-Z0-9_]+$/', $appdefine)) {
66
            $expectedstore = $appdefine;
1441 ariadna 67
            $file = $CFG->dirroot . '/cache/stores/' . $appdefine . '/lib.php';
68
            $class = 'cachestore_' . $appdefine;
1 efrain 69
            if (file_exists($file)) {
70
                require_once($file);
71
            }
72
            if (class_exists($class) && $class::ready_to_be_used_for_testing()) {
1441 ariadna 73
                /* @var store $class */
74
                $writer->configstores['test_application'] = [
1 efrain 75
                    'name' => 'test_application',
76
                    'plugin' => $expectedstore,
77
                    'modes' => $class::get_supported_modes(),
78
                    'features' => $class::get_supported_features(),
1441 ariadna 79
                    'configuration' => $class::unit_test_configuration(),
80
                ];
1 efrain 81
 
82
                $defaultapplication = 'test_application';
83
            }
84
        }
85
 
1441 ariadna 86
        $writer->configmodemappings = [
87
            [
88
                'mode' => store::MODE_APPLICATION,
1 efrain 89
                'store' => $defaultapplication,
1441 ariadna 90
                'sort' => -1,
91
            ],
92
            [
93
                'mode' => store::MODE_SESSION,
1 efrain 94
                'store' => 'default_session',
1441 ariadna 95
                'sort' => -1,
96
            ],
97
            [
98
                'mode' => store::MODE_REQUEST,
1 efrain 99
                'store' => 'default_request',
1441 ariadna 100
                'sort' => -1,
101
            ],
102
        ];
103
        $writer->configlocks = [
104
            'default_file_lock' => [
1 efrain 105
                'name' => 'cachelock_file_default',
106
                'type' => 'cachelock_file',
107
                'dir' => 'filelocks',
1441 ariadna 108
                'default' => true,
109
            ],
110
        ];
1 efrain 111
 
112
        $factory = cache_factory::instance();
113
        // We expect the cache to be initialising presently. If its not then something has gone wrong and likely
114
        // we are now in a loop.
115
        if (!$forcesave && $factory->get_state() !== cache_factory::STATE_INITIALISING) {
116
            return $writer->generate_configuration_array();
117
        }
118
        $factory->set_state(cache_factory::STATE_SAVING);
119
        $writer->config_save();
120
        return true;
121
    }
122
 
123
    /**
124
     * Returns the expected path to the configuration file.
125
     *
126
     * We override this function to add handling for $CFG->altcacheconfigpath.
127
     * We want to support it so that people can run unit tests against alternative cache setups.
128
     * However we don't want to ever make changes to the file at $CFG->altcacheconfigpath so we
129
     * always use dataroot and copy the alt file there as required.
130
     *
131
     * @throws cache_exception
132
     * @return string The absolute path
133
     */
134
    protected static function get_config_file_path() {
135
        global $CFG;
136
        // We always use this path.
1441 ariadna 137
        $configpath = $CFG->dataroot . '/muc/config.php';
1 efrain 138
 
139
        if (!empty($CFG->altcacheconfigpath)) {
140
            // No need to check we are within a test here, this is the cache config class that gets used
141
            // only when one of those is true.
1441 ariadna 142
            if (!defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') || !TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH) {
1 efrain 143
                // TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH has not being defined or is false, we want to use the default.
144
                return $configpath;
145
            }
146
 
147
            $path = $CFG->altcacheconfigpath;
148
            if (is_dir($path) && is_writable($path)) {
149
                // Its a writable directory, thats fine. Convert it to a file.
1441 ariadna 150
                $path = $CFG->altcacheconfigpath . '/cacheconfig.php';
1 efrain 151
            }
152
            if (is_readable($path)) {
153
                $directory = dirname($configpath);
154
                if ($directory !== $CFG->dataroot && !file_exists($directory)) {
155
                    $result = make_writable_directory($directory, false);
156
                    if (!$result) {
157
                        throw new cache_exception('ex_configcannotsave', 'cache', '', null, 'Cannot create config directory. Check the permissions on your moodledata directory.');
158
                    }
159
                }
160
                // We don't care that this fails but we should let the developer know.
161
                if (!is_readable($configpath) && !@copy($path, $configpath)) {
162
                    debugging('Failed to copy alt cache config file to required location');
163
                }
164
            }
165
        }
166
 
167
        // We always use the dataroot location.
168
        return $configpath;
169
    }
170
 
171
    /**
172
     * Adds a definition to the stack
173
     * @param string $area
174
     * @param array $properties
175
     * @param bool $addmapping By default this method adds a definition and a mapping for that definition. You can
176
     *    however set this to false if you only want it to add the definition and not the mapping.
177
     */
178
    public function phpunit_add_definition($area, array $properties, $addmapping = true) {
179
        if (!array_key_exists('overrideclass', $properties)) {
180
            switch ($properties['mode']) {
1441 ariadna 181
                case store::MODE_APPLICATION:
1 efrain 182
                    $properties['overrideclass'] = 'cache_phpunit_application';
183
                    break;
1441 ariadna 184
                case store::MODE_SESSION:
1 efrain 185
                    $properties['overrideclass'] = 'cache_phpunit_session';
186
                    break;
1441 ariadna 187
                case store::MODE_REQUEST:
1 efrain 188
                    $properties['overrideclass'] = 'cache_phpunit_request';
189
                    break;
190
            }
191
        }
192
        $this->configdefinitions[$area] = $properties;
193
        if ($addmapping) {
194
            switch ($properties['mode']) {
1441 ariadna 195
                case store::MODE_APPLICATION:
1 efrain 196
                    $this->phpunit_add_definition_mapping($area, 'default_application', 0);
197
                    break;
1441 ariadna 198
                case store::MODE_SESSION:
1 efrain 199
                    $this->phpunit_add_definition_mapping($area, 'default_session', 0);
200
                    break;
1441 ariadna 201
                case store::MODE_REQUEST:
1 efrain 202
                    $this->phpunit_add_definition_mapping($area, 'default_request', 0);
203
                    break;
204
            }
205
        }
206
    }
207
 
208
    /**
209
     * Removes a definition.
210
     * @param string $name
211
     */
212
    public function phpunit_remove_definition($name) {
213
        unset($this->configdefinitions[$name]);
214
    }
215
 
216
    /**
217
     * Removes the configured stores so that there are none available.
218
     */
219
    public function phpunit_remove_stores() {
1441 ariadna 220
        $this->configstores = [];
1 efrain 221
    }
222
 
223
    /**
224
     * Forcefully adds a file store.
225
     *
226
     * You can turn off native TTL support if you want a way to test TTL wrapper objects.
227
     *
228
     * @param string $name
229
     * @param bool $nativettl If false, uses fixture that turns off native TTL support
230
     */
231
    public function phpunit_add_file_store(string $name, bool $nativettl = true): void {
232
        if (!$nativettl) {
233
            require_once(__DIR__ . '/cachestore_file_with_ttl_wrappers.php');
234
        }
1441 ariadna 235
        $this->configstores[$name] = [
1 efrain 236
            'name' => $name,
237
            'plugin' => 'file',
1441 ariadna 238
            'configuration' => [
239
                'path' => '',
240
            ],
1 efrain 241
            'features' => 6,
242
            'modes' => 3,
243
            'mappingsonly' => false,
244
            'class' => $nativettl ? 'cachestore_file' : 'cachestore_file_with_ttl_wrappers',
245
            'default' => false,
1441 ariadna 246
            'lock' => 'cachelock_file_default',
247
        ];
1 efrain 248
    }
249
 
250
    /**
251
     * Hacks the in-memory configuration for a store.
252
     *
253
     * @param string $store Name of store to edit e.g. 'default_application'
254
     * @param array $configchanges List of config changes
255
     */
256
    public function phpunit_edit_store_config(string $store, array $configchanges): void {
257
        foreach ($configchanges as $name => $value) {
258
            $this->configstores[$store]['configuration'][$name] = $value;
259
        }
260
    }
261
 
262
    /**
263
     * Forcefully adds a session store.
264
     *
265
     * @param string $name
266
     */
267
    public function phpunit_add_session_store($name) {
1441 ariadna 268
        $this->configstores[$name] = [
1 efrain 269
            'name' => $name,
270
            'plugin' => 'session',
1441 ariadna 271
            'configuration' => [],
1 efrain 272
            'features' => 14,
273
            'modes' => 2,
274
            'default' => true,
275
            'class' => 'cachestore_session',
276
            'lock' => 'cachelock_file_default',
1441 ariadna 277
        ];
1 efrain 278
    }
279
 
280
    /**
281
     * Forcefully injects a definition => store mapping.
282
     *
283
     * This function does no validation, you should only be calling if it you know
284
     * exactly what to expect.
285
     *
286
     * @param string $definition
287
     * @param string $store
288
     * @param int $sort
289
     */
290
    public function phpunit_add_definition_mapping($definition, $store, $sort) {
1441 ariadna 291
        $this->configdefinitionmappings[] = [
1 efrain 292
            'store' => $store,
293
            'definition' => $definition,
1441 ariadna 294
            'sort' => (int)$sort,
295
        ];
1 efrain 296
    }
297
 
298
    /**
299
     * Overrides the default site identifier used by the Cache API so that we can be sure of what it is.
300
     *
301
     * @return string
302
     */
303
    public function get_site_identifier() {
304
        global $CFG;
1441 ariadna 305
        return $CFG->wwwroot . 'phpunit';
1 efrain 306
    }
307
 
308
    /**
309
     * Checks if the configuration file exists.
310
     *
311
     * @return bool True if it exists
312
     */
313
    public static function config_file_exists() {
314
        // Allow for late static binding by using static.
315
        $configfilepath = static::get_config_file_path();
316
 
317
        // Invalidate opcode php cache, so we get correct status of file.
318
        core_component::invalidate_opcode_php_cache($configfilepath);
319
        return file_exists($configfilepath);
320
    }
321
}
322
 
323
 
324
/**
325
 * Dummy object for testing cacheable object interface and interaction
326
 *
327
 * Wake from cache needs specific testing at times to ensure that during multiple
328
 * cache get() requests it's possible to verify that it's getting woken each time.
329
 *
330
 * @copyright  2012 Sam Hemelryk
331
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
332
 */
333
class cache_phpunit_dummy_object extends stdClass implements cacheable_object {
334
    /**
335
     * Test property 1
336
     * @var string
337
     */
338
    public $property1;
339
    /**
340
     * Test property 1
341
     * @var string
342
     */
343
    public $property2;
344
    /**
345
     * Test property time for verifying wake is run at each get() call.
346
     * @var float
347
     */
348
    public $propertytime;
349
    /**
350
     * Constructor
351
     * @param string $property1
352
     * @param string $property2
353
     */
354
    public function __construct($property1, $property2, $propertytime = null) {
355
        $this->property1 = $property1;
356
        $this->property2 = $property2;
357
        $this->propertytime = $propertytime === null ? microtime(true) : $propertytime;
358
    }
359
    /**
360
     * Prepares this object for caching
361
     * @return array
362
     */
363
    public function prepare_to_cache() {
1441 ariadna 364
        return [$this->property1 . '_ptc', $this->property2 . '_ptc', $this->propertytime];
1 efrain 365
    }
366
    /**
367
     * Returns this object from the cache
368
     * @param array $data
369
     * @return cache_phpunit_dummy_object
370
     */
371
    public static function wake_from_cache($data) {
372
        $time = null;
373
        if (!is_null($data[2])) {
374
            // Windows 32bit microtime() resolution is 15ms, we ensure the time has moved forward.
375
            do {
376
                $time = microtime(true);
377
            } while ($time == $data[2]);
378
        }
1441 ariadna 379
        return new cache_phpunit_dummy_object(array_shift($data) . '_wfc', array_shift($data) . '_wfc', $time);
1 efrain 380
    }
381
}
382
 
383
/**
384
 * Dummy data source object for testing data source interface and implementation
385
 *
386
 * @copyright  2012 Sam Hemelryk
387
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
388
 */
389
class cache_phpunit_dummy_datasource implements cache_data_source {
390
    /**
391
     * Returns an instance of this object for use with the cache.
392
     *
393
     * @param cache_definition $definition
394
     * @return cache_phpunit_dummy_datasource
395
     */
396
    public static function get_instance_for_cache(cache_definition $definition) {
397
        return new cache_phpunit_dummy_datasource();
398
    }
399
 
400
    /**
401
     * Loads a key for the cache.
402
     *
403
     * @param string $key
404
     * @return string
405
     */
406
    public function load_for_cache($key) {
1441 ariadna 407
        return $key . ' has no value really.';
1 efrain 408
    }
409
 
410
    /**
411
     * Loads many keys for the cache
412
     *
413
     * @param array $keys
414
     * @return array
415
     */
416
    public function load_many_for_cache(array $keys) {
1441 ariadna 417
        $return = [];
1 efrain 418
        foreach ($keys as $key) {
1441 ariadna 419
            $return[$key] = $key . ' has no value really.';
1 efrain 420
        }
421
        return $return;
422
    }
423
}
424
 
425
/**
426
 * PHPUnit application cache loader.
427
 *
428
 * Used to expose things we could not otherwise see within an application cache.
429
 *
430
 * @copyright  2012 Sam Hemelryk
431
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
432
 */
433
class cache_phpunit_application extends cache_application {
1441 ariadna 434
    #[\Override]
435
    public function get_store() {
436
        return parent::get_store();
437
    }
1 efrain 438
 
439
    /**
440
     * Returns the class of the store immediately associated with this cache.
441
     * @return string
442
     */
443
    public function phpunit_get_store_class() {
444
        return get_class($this->get_store());
445
    }
446
 
447
    /**
448
     * Returns all the interfaces the cache store implements.
449
     * @return array
450
     */
451
    public function phpunit_get_store_implements() {
452
        return class_implements($this->get_store());
453
    }
454
 
455
    /**
456
     * Returns the given key directly from the static acceleration array.
457
     *
458
     * @param string $key
459
     * @return false|mixed
460
     */
461
    public function phpunit_static_acceleration_get($key) {
462
        return $this->static_acceleration_get($key);
463
    }
464
 
465
    /**
466
     * Purges only the static acceleration while leaving the rest of the store in tack.
467
     *
468
     * Used for behaving like you have loaded 2 pages, and reset static while the backing store
469
     * still contains all the same data.
470
     *
471
     */
472
    public function phpunit_static_acceleration_purge() {
473
        $this->static_acceleration_purge();
474
    }
475
}
476
 
477
/**
478
 * PHPUnit session cache loader.
479
 *
480
 * Used to expose things we could not otherwise see within an session cache.
481
 *
482
 * @copyright  2012 Sam Hemelryk
483
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
484
 */
485
class cache_phpunit_session extends cache_session {
486
    /** @var Static member used for emulating the behaviour of session_id() during the tests. */
487
    protected static $sessionidmockup = 'phpunitmockupsessionid';
488
 
1441 ariadna 489
    #[\Override]
490
    public function get_store() {
491
        return parent::get_store();
492
    }
493
 
1 efrain 494
    /**
495
     * Returns the class of the store immediately associated with this cache.
496
     * @return string
497
     */
498
    public function phpunit_get_store_class() {
499
        return get_class($this->get_store());
500
    }
501
 
502
    /**
503
     * Returns all the interfaces the cache store implements.
504
     * @return array
505
     */
506
    public function phpunit_get_store_implements() {
507
        return class_implements($this->get_store());
508
    }
509
 
510
    /**
511
     * Provide access to the {@link cache_session::get_key_prefix()} method.
512
     *
513
     * @return string
514
     */
515
    public function phpunit_get_key_prefix() {
516
        return $this->get_key_prefix();
517
    }
518
 
519
    /**
520
     * Allows to inject the session identifier.
521
     *
522
     * @param string $sessionid
523
     */
524
    public static function phpunit_mockup_session_id($sessionid) {
525
        static::$sessionidmockup = $sessionid;
526
    }
527
 
528
    /**
529
     * Override the parent behaviour so that it does not need the actual session_id() call.
530
     */
531
    protected function set_session_id() {
532
        $this->sessionid = static::$sessionidmockup;
533
    }
534
}
535
 
536
/**
537
 * PHPUnit request cache loader.
538
 *
539
 * Used to expose things we could not otherwise see within an request cache.
540
 *
541
 * @copyright  2012 Sam Hemelryk
542
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
543
 */
544
class cache_phpunit_request extends cache_request {
1441 ariadna 545
    #[\Override]
546
    public function get_store() {
547
        return parent::get_store();
548
    }
1 efrain 549
 
550
    /**
551
     * Returns the class of the store immediately associated with this cache.
552
     * @return string
553
     */
554
    public function phpunit_get_store_class() {
555
        return get_class($this->get_store());
556
    }
557
 
558
    /**
559
     * Returns all the interfaces the cache store implements.
560
     * @return array
561
     */
562
    public function phpunit_get_store_implements() {
563
        return class_implements($this->get_store());
564
    }
565
}
566
 
567
/**
568
 * Dummy overridden cache loader class that we can use to test overriding loader functionality.
569
 *
570
 * @copyright  2012 Sam Hemelryk
571
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
572
 */
573
class cache_phpunit_dummy_overrideclass extends cache_application {
574
    // Satisfying the code pre-checker is just part of my day job.
575
}
576
 
577
/**
578
 * Cache PHPUnit specific factory.
579
 *
580
 * @copyright  2012 Sam Hemelryk
581
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
582
 */
583
class cache_phpunit_factory extends cache_factory {
584
    /**
585
     * Exposes the cache_factory's disable method.
586
     *
587
     * Perhaps one day that method will be made public, for the time being it is protected.
588
     */
589
    public static function phpunit_disable() {
590
        parent::disable();
591
    }
592
}
593
 
594
/**
595
 * Cache PHPUnit specific Cache helper.
596
 *
597
 * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
598
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
599
 */
600
class cache_phpunit_cache extends cache {
601
    /**
602
     * Make the changes which simulate a new request within the cache.
603
     * This essentially resets currently held static values in the class, and increments the current timestamp.
604
     */
605
    public static function simulate_new_request() {
606
        self::$now += 0.1;
607
        self::$purgetoken = null;
608
    }
609
}