MDL-45513 cache: implemented defines for unit testing alternative caches
[moodle.git] / cache / tests / fixtures / lib.php
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/>.
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  *
23  * @package    core
24  * @category   cache
25  * @copyright  2012 Sam Hemelryk
26  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
29 defined('MOODLE_INTERNAL') || die();
31 require_once($CFG->dirroot.'/cache/locallib.php');
33 /**
34  * Override the default cache configuration for our own maniacle purposes.
35  *
36  * @copyright  2012 Sam Hemelryk
37  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 class cache_config_phpunittest extends cache_config_writer {
41     /**
42      * Creates the default configuration and saves it.
43      *
44      * This function calls config_save, however it is safe to continue using it afterwards as this function should only ever
45      * be called when there is no configuration file already.
46      *
47      * @param bool $forcesave If set to true then we will forcefully save the default configuration file.
48      * @return true|array Returns true if the default configuration was successfully created.
49      *     Returns a configuration array if it could not be saved. This is a bad situation. Check your error logs.
50      */
51     public static function create_default_configuration($forcesave = false) {
52         global $CFG;
53         // HACK ALERT.
54         // We probably need to come up with a better way to create the default stores, or at least ensure 100% that the
55         // default store plugins are protected from deletion.
56         $writer = new self;
57         $writer->configstores = self::get_default_stores();
58         $writer->configdefinitions = self::locate_definitions();
59         $defaultapplication = 'default_application';
61         $appdefine = defined('TEST_CACHE_USING_APPLICATION_STORE') ? TEST_CACHE_USING_APPLICATION_STORE : false;
62         if ($appdefine !== false && preg_match('/^[a-zA-Z][a-zA-Z0-9_]+$/', $appdefine)) {
63             $expectedstore = $appdefine;
64             $expecteddefine = 'TEST_CACHESTORE_'.strtoupper($expectedstore).'_TESTSERVERS';
65             $file = $CFG->dirroot.'/cache/stores/'.$appdefine.'/lib.php';
66             $class = 'cachestore_'.$appdefine;
67             if (file_exists($file)) {
68                 require_once($file);
69             }
70             if (defined($expecteddefine) && class_exists($class)) {
71                 /** @var cache_store $class */
72                 $writer->configstores['test_application'] = array(
73                     'use_test_store' => true,
74                     'name' => 'test_application',
75                     'plugin' => $expectedstore,
76                     'alt' => $writer->configstores[$defaultapplication],
77                     'modes' => $class::get_supported_modes(),
78                     'features' => $class::get_supported_features()
79                 );
80                 $defaultapplication = 'test_application';
81             }
82         }
84         $writer->configmodemappings = array(
85             array(
86                 'mode' => cache_store::MODE_APPLICATION,
87                 'store' => $defaultapplication,
88                 'sort' => -1
89             ),
90             array(
91                 'mode' => cache_store::MODE_SESSION,
92                 'store' => 'default_session',
93                 'sort' => -1
94             ),
95             array(
96                 'mode' => cache_store::MODE_REQUEST,
97                 'store' => 'default_request',
98                 'sort' => -1
99             )
100         );
101         $writer->configlocks = array(
102             'default_file_lock' => array(
103                 'name' => 'cachelock_file_default',
104                 'type' => 'cachelock_file',
105                 'dir' => 'filelocks',
106                 'default' => true
107             )
108         );
110         $factory = cache_factory::instance();
111         // We expect the cache to be initialising presently. If its not then something has gone wrong and likely
112         // we are now in a loop.
113         if (!$forcesave && $factory->get_state() !== cache_factory::STATE_INITIALISING) {
114             return $writer->generate_configuration_array();
115         }
116         $factory->set_state(cache_factory::STATE_SAVING);
117         $writer->config_save();
118         return true;
119     }
121     /**
122      * Returns the expected path to the configuration file.
123      *
124      * We override this function to add handling for $CFG->altcacheconfigpath.
125      * We want to support it so that people can run unit tests against alternative cache setups.
126      * However we don't want to ever make changes to the file at $CFG->altcacheconfigpath so we
127      * always use dataroot and copy the alt file there as required.
128      *
129      * @throws cache_exception
130      * @return string The absolute path
131      */
132     protected static function get_config_file_path() {
133         global $CFG;
134         // We always use this path.
135         $configpath = $CFG->dataroot.'/muc/config.php';
137         if (!empty($CFG->altcacheconfigpath)) {
139             if  (defined('PHPUNIT_TEST') && PHPUNIT_TEST &&
140                 (!defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') || !TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH)) {
141                 // We're within a unit test, but TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH has not being defined or is
142                 // false, we want to use the default.
143                 return $configpath;
144             }
146             $path = $CFG->altcacheconfigpath;
147             if (is_dir($path) && is_writable($path)) {
148                 // Its a writable directory, thats fine. Convert it to a file.
149                 $path = $CFG->altcacheconfigpath.'/cacheconfig.php';
150             }
151             if (is_readable($path)) {
152                 $directory = dirname($configpath);
153                 if ($directory !== $CFG->dataroot && !file_exists($directory)) {
154                     $result = make_writable_directory($directory, false);
155                     if (!$result) {
156                         throw new cache_exception('ex_configcannotsave', 'cache', '', null, 'Cannot create config directory. Check the permissions on your moodledata directory.');
157                     }
158                 }
159                 // We don't care that this fails but we should let the developer know.
160                 if (!is_readable($configpath) && !@copy($path, $configpath)) {
161                     debugging('Failed to copy alt cache config file to required location');
162                 }
163             }
164         }
166         // We always use the dataroot location.
167         return $configpath;
168     }
170     /**
171      * Adds a definition to the stack
172      * @param string $area
173      * @param array $properties
174      * @param bool $addmapping By default this method adds a definition and a mapping for that definition. You can
175      *    however set this to false if you only want it to add the definition and not the mapping.
176      */
177     public function phpunit_add_definition($area, array $properties, $addmapping = true) {
178         if (!array_key_exists('overrideclass', $properties)) {
179             switch ($properties['mode']) {
180                 case cache_store::MODE_APPLICATION:
181                     $properties['overrideclass'] = 'cache_phpunit_application';
182                     break;
183                 case cache_store::MODE_SESSION:
184                     $properties['overrideclass'] = 'cache_phpunit_session';
185                     break;
186                 case cache_store::MODE_REQUEST:
187                     $properties['overrideclass'] = 'cache_phpunit_request';
188                     break;
189             }
190         }
191         $this->configdefinitions[$area] = $properties;
192         if ($addmapping) {
193             switch ($properties['mode']) {
194                 case cache_store::MODE_APPLICATION:
195                     $this->phpunit_add_definition_mapping($area, 'default_application', 0);
196                     break;
197                 case cache_store::MODE_SESSION:
198                     $this->phpunit_add_definition_mapping($area, 'default_session', 0);
199                     break;
200                 case cache_store::MODE_REQUEST:
201                     $this->phpunit_add_definition_mapping($area, 'default_request', 0);
202                     break;
203             }
204         }
205     }
207     /**
208      * Removes a definition.
209      * @param string $name
210      */
211     public function phpunit_remove_definition($name) {
212         unset($this->configdefinitions[$name]);
213     }
215     /**
216      * Removes the configured stores so that there are none available.
217      */
218     public function phpunit_remove_stores() {
219         $this->configstores = array();
220     }
222     /**
223      * Forcefully adds a file store.
224      *
225      * @param string $name
226      */
227     public function phpunit_add_file_store($name) {
228         $this->configstores[$name] = array(
229             'name' => $name,
230             'plugin' => 'file',
231             'configuration' => array(
232                 'path' => ''
233             ),
234             'features' => 6,
235             'modes' => 3,
236             'mappingsonly' => false,
237             'class' => 'cachestore_file',
238             'default' => false,
239             'lock' => 'cachelock_file_default'
240         );
241     }
243     /**
244      * Forcefully adds a session store.
245      *
246      * @param string $name
247      */
248     public function phpunit_add_session_store($name) {
249         $this->configstores[$name] = array(
250             'name' => $name,
251             'plugin' => 'session',
252             'configuration' => array(),
253             'features' => 14,
254             'modes' => 2,
255             'default' => true,
256             'class' => 'cachestore_session',
257             'lock' => 'cachelock_file_default',
258         );
259     }
261     /**
262      * Forcefully injects a definition => store mapping.
263      *
264      * This function does no validation, you should only be calling if it you know
265      * exactly what to expect.
266      *
267      * @param string $definition
268      * @param string $store
269      * @param int $sort
270      */
271     public function phpunit_add_definition_mapping($definition, $store, $sort) {
272         $this->configdefinitionmappings[] = array(
273             'store' => $store,
274             'definition' => $definition,
275             'sort' => (int)$sort
276         );
277     }
279     /**
280      * Overrides the default site identifier used by the Cache API so that we can be sure of what it is.
281      *
282      * @return string
283      */
284     public function get_site_identifier() {
285         global $CFG;
286         return $CFG->wwwroot.'phpunit';
287     }
290 /**
291  * Dummy object for testing cacheable object interface and interaction
292  *
293  * @copyright  2012 Sam Hemelryk
294  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
295  */
296 class cache_phpunit_dummy_object extends stdClass implements cacheable_object {
297     /**
298      * Test property 1
299      * @var string
300      */
301     public $property1;
302     /**
303      * Test property 1
304      * @var string
305      */
306     public $property2;
307     /**
308      * Constructor
309      * @param string $property1
310      * @param string $property2
311      */
312     public function __construct($property1, $property2) {
313         $this->property1 = $property1;
314         $this->property2 = $property2;
315     }
316     /**
317      * Prepares this object for caching
318      * @return array
319      */
320     public function prepare_to_cache() {
321         return array($this->property1.'_ptc', $this->property2.'_ptc');
322     }
323     /**
324      * Returns this object from the cache
325      * @param array $data
326      * @return cache_phpunit_dummy_object
327      */
328     public static function wake_from_cache($data) {
329         return new cache_phpunit_dummy_object(array_shift($data).'_wfc', array_shift($data).'_wfc');
330     }
333 /**
334  * Dummy data source object for testing data source interface and implementation
335  *
336  * @copyright  2012 Sam Hemelryk
337  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
338  */
339 class cache_phpunit_dummy_datasource implements cache_data_source {
340     /**
341      * Returns an instance of this object for use with the cache.
342      *
343      * @param cache_definition $definition
344      * @return cache_phpunit_dummy_datasource
345      */
346     public static function get_instance_for_cache(cache_definition $definition) {
347         return new cache_phpunit_dummy_datasource();
348     }
350     /**
351      * Loads a key for the cache.
352      *
353      * @param string $key
354      * @return string
355      */
356     public function load_for_cache($key) {
357         return $key.' has no value really.';
358     }
360     /**
361      * Loads many keys for the cache
362      *
363      * @param array $keys
364      * @return array
365      */
366     public function load_many_for_cache(array $keys) {
367         $return = array();
368         foreach ($keys as $key) {
369             $return[$key] = $key.' has no value really.';
370         }
371         return $return;
372     }
375 /**
376  * PHPUnit application cache loader.
377  *
378  * Used to expose things we could not otherwise see within an application cache.
379  *
380  * @copyright  2012 Sam Hemelryk
381  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
382  */
383 class cache_phpunit_application extends cache_application {
385     /**
386      * Returns the class of the store immediately associated with this cache.
387      * @return string
388      */
389     public function phpunit_get_store_class() {
390         return get_class($this->get_store());
391     }
393     /**
394      * Returns all the interfaces the cache store implements.
395      * @return array
396      */
397     public function phpunit_get_store_implements() {
398         return class_implements($this->get_store());
399     }
401     /**
402      * Returns the given key directly from the static acceleration array.
403      *
404      * @param string $key
405      * @return false|mixed
406      */
407     public function phpunit_static_acceleration_get($key) {
408         $key = $this->parse_key($key);
409         return $this->static_acceleration_get($key);
410     }
413 /**
414  * PHPUnit session cache loader.
415  *
416  * Used to expose things we could not otherwise see within an session cache.
417  *
418  * @copyright  2012 Sam Hemelryk
419  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
420  */
421 class cache_phpunit_session extends cache_session {
423     /**
424      * Returns the class of the store immediately associated with this cache.
425      * @return string
426      */
427     public function phpunit_get_store_class() {
428         return get_class($this->get_store());
429     }
431     /**
432      * Returns all the interfaces the cache store implements.
433      * @return array
434      */
435     public function phpunit_get_store_implements() {
436         return class_implements($this->get_store());
437     }
440 /**
441  * PHPUnit request cache loader.
442  *
443  * Used to expose things we could not otherwise see within an request cache.
444  *
445  * @copyright  2012 Sam Hemelryk
446  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
447  */
448 class cache_phpunit_request extends cache_request {
450     /**
451      * Returns the class of the store immediately associated with this cache.
452      * @return string
453      */
454     public function phpunit_get_store_class() {
455         return get_class($this->get_store());
456     }
458     /**
459      * Returns all the interfaces the cache store implements.
460      * @return array
461      */
462     public function phpunit_get_store_implements() {
463         return class_implements($this->get_store());
464     }
467 /**
468  * Dummy overridden cache loader class that we can use to test overriding loader functionality.
469  *
470  * @copyright  2012 Sam Hemelryk
471  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
472  */
473 class cache_phpunit_dummy_overrideclass extends cache_application {
474     // Satisfying the code pre-checker is just part of my day job.
477 /**
478  * Cache PHPUnit specific factory.
479  *
480  * @copyright  2012 Sam Hemelryk
481  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
482  */
483 class cache_phpunit_factory extends cache_factory {
484     /**
485      * Exposes the cache_factory's disable method.
486      *
487      * Perhaps one day that method will be made public, for the time being it is protected.
488      */
489     public static function phpunit_disable() {
490         parent::disable();
491     }
493     /**
494      * Creates a store instance given its name and configuration.
495      *
496      * If the store has already been instantiated then the original object will be returned. (reused)
497      *
498      * @param string $name The name of the store (must be unique remember)
499      * @param array $details
500      * @param cache_definition $definition The definition to instantiate it for.
501      * @return boolean|cache_store
502      */
503     public function create_store_from_config($name, array $details, cache_definition $definition) {
505         if (isset($details['use_test_store'])) {
506             // name, plugin, alt
507             $class = 'cachestore_'.$details['plugin'];
508             $method = 'initialise_unit_test_instance';
509             if (class_exists($class) && method_exists($class, $method)) {
510                 $instance = $class::$method($definition);
512                 if ($instance) {
513                     return $instance;
514                 }
515             }
516             $details = $details['alt'];
517             $name = $details['name'];
518         }
520         return parent::create_store_from_config($name, $details, $definition);
521     }