cc40a4e89faf2a0c8b6e9499a0450e14a9513709
[moodle.git] / cache / tests / cache_test.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  * PHPunit tests for the cache API
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 // Include the necessary evils.
32 global $CFG;
33 require_once($CFG->dirroot.'/cache/locallib.php');
34 require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
36 /**
37  * PHPunit tests for the cache API
38  *
39  * @copyright  2012 Sam Hemelryk
40  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  */
42 class cache_phpunit_tests extends advanced_testcase {
44     /**
45      * Set things back to the default before each test.
46      */
47     public function setUp() {
48         parent::setUp();
49         cache_factory::reset();
50         cache_config_phpunittest::create_default_configuration();
51     }
53     /**
54      * Tests cache configuration
55      */
56     public function test_cache_config() {
57         $instance = cache_config::instance();
58         $this->assertInstanceOf('cache_config_phpunittest', $instance);
60         $this->assertTrue(cache_config_phpunittest::config_file_exists());
62         $stores = $instance->get_all_stores();
63         $this->assertCount(3, $stores);
64         foreach ($stores as $name => $store) {
65             // Check its an array.
66             $this->assertInternalType('array', $store);
67             // Check the name is the key.
68             $this->assertEquals($name, $store['name']);
69             // Check that it has been declared default.
70             $this->assertTrue($store['default']);
71             // Required attributes = name + plugin + configuration + modes + features.
72             $this->assertArrayHasKey('name', $store);
73             $this->assertArrayHasKey('plugin', $store);
74             $this->assertArrayHasKey('configuration', $store);
75             $this->assertArrayHasKey('modes', $store);
76             $this->assertArrayHasKey('features', $store);
77         }
79         $modemappings = $instance->get_mode_mappings();
80         $this->assertCount(3, $modemappings);
81         $modes = array(
82             cache_store::MODE_APPLICATION => false,
83             cache_store::MODE_SESSION => false,
84             cache_store::MODE_REQUEST => false,
85         );
86         foreach ($modemappings as $mapping) {
87             // We expect 3 properties.
88             $this->assertCount(3, $mapping);
89             // Required attributes = mode + store.
90             $this->assertArrayHasKey('mode', $mapping);
91             $this->assertArrayHasKey('store', $mapping);
92             // Record the mode.
93             $modes[$mapping['mode']] = true;
94         }
96         // Must have the default 3 modes and no more.
97         $this->assertCount(3, $mapping);
98         foreach ($modes as $mode) {
99             $this->assertTrue($mode);
100         }
102         $definitions = $instance->get_definitions();
103         // The event invalidation definition is required for the cache API and must be there.
104         $this->assertArrayHasKey('core/eventinvalidation', $definitions);
106         $definitionmappings = $instance->get_definition_mappings();
107         foreach ($definitionmappings as $mapping) {
108             // Required attributes = definition + store.
109             $this->assertArrayHasKey('definition', $mapping);
110             $this->assertArrayHasKey('store', $mapping);
111         }
112     }
114     /**
115      * Tests the default application cache
116      */
117     public function test_default_application_cache() {
118         $cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'phpunit', 'applicationtest');
119         $this->assertInstanceOf('cache_application', $cache);
120         $this->run_on_cache($cache);
121     }
123     /**
124      * Tests the default session cache
125      */
126     public function test_default_session_cache() {
127         $cache = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'applicationtest');
128         $this->assertInstanceOf('cache_session', $cache);
129         $this->run_on_cache($cache);
130     }
132     /**
133      * Tests the default request cache
134      */
135     public function test_default_request_cache() {
136         $cache = cache::make_from_params(cache_store::MODE_REQUEST, 'phpunit', 'applicationtest');
137         $this->assertInstanceOf('cache_request', $cache);
138         $this->run_on_cache($cache);
139     }
141     /**
142      * Tests using a cache system when there are no stores available (who knows what the admin did to achieve this).
143      */
144     public function test_on_cache_without_store() {
145         $instance = cache_config_phpunittest::instance(true);
146         $instance->phpunit_add_definition('phpunit/nostoretest1', array(
147             'mode' => cache_store::MODE_APPLICATION,
148             'component' => 'phpunit',
149             'area' => 'nostoretest1',
150         ));
151         $instance->phpunit_add_definition('phpunit/nostoretest2', array(
152             'mode' => cache_store::MODE_APPLICATION,
153             'component' => 'phpunit',
154             'area' => 'nostoretest2',
155             'persistent' => true
156         ));
157         $instance->phpunit_remove_stores();
159         $cache = cache::make('phpunit', 'nostoretest1');
160         $this->run_on_cache($cache);
162         $cache = cache::make('phpunit', 'nostoretest2');
163         $this->run_on_cache($cache);
164     }
166     /**
167      * Runs a standard series of access and use tests on a cache instance.
168      *
169      * This function is great because we can use it to ensure all of the loaders perform exactly the same way.
170      *
171      * @param cache_loader $cache
172      */
173     protected function run_on_cache(cache_loader $cache) {
174         $key = 'testkey';
175         $datascalar = 'test data';
176         $dataarray = array('test' => 'data', 'part' => 'two');
177         $dataobject = (object)$dataarray;
179         $this->assertTrue($cache->purge());
181         // Check all read methods.
182         $this->assertFalse($cache->get($key));
183         $this->assertFalse($cache->has($key));
184         $result = $cache->get_many(array($key));
185         $this->assertCount(1, $result);
186         $this->assertFalse(reset($result));
187         $this->assertFalse($cache->has_any(array($key)));
188         $this->assertFalse($cache->has_all(array($key)));
190         // Set the data.
191         $this->assertTrue($cache->set($key, $datascalar));
192         // Setting it more than once should be permitted.
193         $this->assertTrue($cache->set($key, $datascalar));
195         // Recheck the read methods.
196         $this->assertEquals($datascalar, $cache->get($key));
197         $this->assertTrue($cache->has($key));
198         $result = $cache->get_many(array($key));
199         $this->assertCount(1, $result);
200         $this->assertEquals($datascalar, reset($result));
201         $this->assertTrue($cache->has_any(array($key)));
202         $this->assertTrue($cache->has_all(array($key)));
204         // Delete it.
205         $this->assertTrue($cache->delete($key));
207         // Check its gone.
208         $this->assertFalse($cache->get($key));
209         $this->assertFalse($cache->has($key));
211         // Test arrays.
212         $this->assertTrue($cache->set($key, $dataarray));
213         $this->assertEquals($dataarray, $cache->get($key));
215         // Test objects.
216         $this->assertTrue($cache->set($key, $dataobject));
217         $this->assertEquals($dataobject, $cache->get($key));
219         $specobject = new cache_phpunit_dummy_object('red', 'blue');
220         $this->assertTrue($cache->set($key, $specobject));
221         $result = $cache->get($key);
222         $this->assertInstanceOf('cache_phpunit_dummy_object', $result);
223         $this->assertEquals('red_ptc_wfc', $result->property1);
224         $this->assertEquals('blue_ptc_wfc', $result->property2);
226         // Test set many.
227         $cache->set_many(array('key1' => 'data1', 'key2' => 'data2'));
228         $this->assertEquals('data1', $cache->get('key1'));
229         $this->assertEquals('data2', $cache->get('key2'));
230         $this->assertTrue($cache->delete('key1'));
231         $this->assertTrue($cache->delete('key2'));
233         // Test delete many.
234         $this->assertTrue($cache->set('key1', 'data1'));
235         $this->assertTrue($cache->set('key2', 'data2'));
237         $this->assertEquals('data1', $cache->get('key1'));
238         $this->assertEquals('data2', $cache->get('key2'));
240         $this->assertEquals(2, $cache->delete_many(array('key1', 'key2')));
242         $this->assertFalse($cache->get('key1'));
243         $this->assertFalse($cache->get('key2'));
245         // Quick reference test.
246         $obj = new stdClass;
247         $obj->key = 'value';
248         $ref =& $obj;
249         $this->assertTrue($cache->set('obj', $obj));
251         $obj->key = 'eulav';
252         $var = $cache->get('obj');
253         $this->assertInstanceOf('stdClass', $var);
254         $this->assertEquals('value', $var->key);
256         $ref->key = 'eulav';
257         $var = $cache->get('obj');
258         $this->assertInstanceOf('stdClass', $var);
259         $this->assertEquals('value', $var->key);
261         $this->assertTrue($cache->delete('obj'));
263         // Deep reference test.
264         $obj1 = new stdClass;
265         $obj1->key = 'value';
266         $obj2 = new stdClass;
267         $obj2->key = 'test';
268         $obj3 = new stdClass;
269         $obj3->key = 'pork';
270         $obj1->subobj =& $obj2;
271         $obj2->subobj =& $obj3;
272         $this->assertTrue($cache->set('obj', $obj1));
274         $obj1->key = 'eulav';
275         $obj2->key = 'tset';
276         $obj3->key = 'krop';
277         $var = $cache->get('obj');
278         $this->assertInstanceOf('stdClass', $var);
279         $this->assertEquals('value', $var->key);
280         $this->assertInstanceOf('stdClass', $var->subobj);
281         $this->assertEquals('test', $var->subobj->key);
282         $this->assertInstanceOf('stdClass', $var->subobj->subobj);
283         $this->assertEquals('pork', $var->subobj->subobj->key);
284         $this->assertTrue($cache->delete('obj'));
286         // Death reference test... basicaly we don't want this to die.
287         $obj = new stdClass;
288         $obj->key = 'value';
289         $obj->self =& $obj;
290         $this->assertTrue($cache->set('obj', $obj));
291         $var = $cache->get('obj');
292         $this->assertInstanceOf('stdClass', $var);
293         $this->assertEquals('value', $var->key);
294     }
296     /**
297      * Tests a definition using a data loader
298      */
299     public function test_definition_data_loader() {
300         $instance = cache_config_phpunittest::instance(true);
301         $instance->phpunit_add_definition('phpunit/datasourcetest', array(
302             'mode' => cache_store::MODE_APPLICATION,
303             'component' => 'phpunit',
304             'area' => 'datasourcetest',
305             'datasource' => 'cache_phpunit_dummy_datasource'
306         ));
308         $cache = cache::make('phpunit', 'datasourcetest');
309         $this->assertInstanceOf('cache_application', $cache);
311         // Purge it to be sure.
312         $this->assertTrue($cache->purge());
313         // It won't be there yet.
314         $this->assertFalse($cache->has('Test'));
316         // It should load it ;).
317         $this->assertTrue($cache->has('Test', true));
319         // Purge it to be sure.
320         $this->assertTrue($cache->purge());
321         $this->assertEquals('Test has no value really.', $cache->get('Test'));
322     }
324     /**
325      * Tests a definition using an overridden loader
326      */
327     public function test_definition_overridden_loader() {
328         $instance = cache_config_phpunittest::instance(true);
329         $instance->phpunit_add_definition('phpunit/overridetest', array(
330             'mode' => cache_store::MODE_APPLICATION,
331             'component' => 'phpunit',
332             'area' => 'overridetest',
333             'overrideclass' => 'cache_phpunit_dummy_overrideclass'
334         ));
335         $cache = cache::make('phpunit', 'overridetest');
336         $this->assertInstanceOf('cache_phpunit_dummy_overrideclass', $cache);
337         $this->assertInstanceOf('cache_application', $cache);
338         // Purge it to be sure.
339         $this->assertTrue($cache->purge());
340         // It won't be there yet.
341         $this->assertFalse($cache->has('Test'));
342         // Add it.
343         $this->assertTrue($cache->set('Test', 'Test has no value really.'));
344         // Check its there.
345         $this->assertEquals('Test has no value really.', $cache->get('Test'));
346     }
348     /**
349      * Tests manual locking operations on an application cache
350      */
351     public function test_application_manual_locking() {
352         $instance = cache_config_phpunittest::instance();
353         $instance->phpunit_add_definition('phpunit/lockingtest', array(
354             'mode' => cache_store::MODE_APPLICATION,
355             'component' => 'phpunit',
356             'area' => 'lockingtest'
357         ));
358         $cache1 = cache::make('phpunit', 'lockingtest');
359         $cache2 = clone($cache1);
361         $this->assertTrue($cache1->set('testkey', 'test data'));
362         $this->assertTrue($cache2->set('testkey', 'test data'));
364         $this->assertTrue($cache1->acquire_lock('testkey'));
365         $this->assertFalse($cache2->acquire_lock('testkey'));
367         $this->assertTrue($cache1->check_lock_state('testkey'));
368         $this->assertFalse($cache2->check_lock_state('testkey'));
370         $this->assertTrue($cache1->release_lock('testkey'));
371         $this->assertFalse($cache2->release_lock('testkey'));
373         $this->assertTrue($cache1->set('testkey', 'test data'));
374         $this->assertTrue($cache2->set('testkey', 'test data'));
375     }
377     /**
378      * Tests application cache event invalidation
379      */
380     public function test_application_event_invalidation() {
381         $instance = cache_config_phpunittest::instance();
382         $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
383             'mode' => cache_store::MODE_APPLICATION,
384             'component' => 'phpunit',
385             'area' => 'eventinvalidationtest',
386             'invalidationevents' => array(
387                 'crazyevent'
388             )
389         ));
390         $cache = cache::make('phpunit', 'eventinvalidationtest');
392         $this->assertTrue($cache->set('testkey1', 'test data 1'));
393         $this->assertEquals('test data 1', $cache->get('testkey1'));
394         $this->assertTrue($cache->set('testkey2', 'test data 2'));
395         $this->assertEquals('test data 2', $cache->get('testkey2'));
397         // Test invalidating a single entry.
398         cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
400         $this->assertFalse($cache->get('testkey1'));
401         $this->assertEquals('test data 2', $cache->get('testkey2'));
403         $this->assertTrue($cache->set('testkey1', 'test data 1'));
405         // Test invalidating both entries.
406         cache_helper::invalidate_by_event('crazyevent', array('testkey1', 'testkey2'));
408         $this->assertFalse($cache->get('testkey1'));
409         $this->assertFalse($cache->get('testkey2'));
410     }
412     /**
413      * Tests application cache definition invalidation
414      */
415     public function test_application_definition_invalidation() {
416         $instance = cache_config_phpunittest::instance();
417         $instance->phpunit_add_definition('phpunit/definitioninvalidation', array(
418             'mode' => cache_store::MODE_APPLICATION,
419             'component' => 'phpunit',
420             'area' => 'definitioninvalidation'
421         ));
422         $cache = cache::make('phpunit', 'definitioninvalidation');
423         $this->assertTrue($cache->set('testkey1', 'test data 1'));
424         $this->assertEquals('test data 1', $cache->get('testkey1'));
425         $this->assertTrue($cache->set('testkey2', 'test data 2'));
426         $this->assertEquals('test data 2', $cache->get('testkey2'));
428         cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), 'testkey1');
430         $this->assertFalse($cache->get('testkey1'));
431         $this->assertEquals('test data 2', $cache->get('testkey2'));
433         $this->assertTrue($cache->set('testkey1', 'test data 1'));
435         cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), array('testkey1'));
437         $this->assertFalse($cache->get('testkey1'));
438         $this->assertEquals('test data 2', $cache->get('testkey2'));
440         $this->assertTrue($cache->set('testkey1', 'test data 1'));
442         cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), array('testkey1', 'testkey2'));
444         $this->assertFalse($cache->get('testkey1'));
445         $this->assertFalse($cache->get('testkey2'));
446     }
448     /**
449      * Tests application cache event invalidation over a distributed setup.
450      */
451     public function test_distributed_application_event_invalidation() {
452         global $CFG;
453         // This is going to be an intense wee test.
454         // We need to add data the to cache, invalidate it by event, manually force it back without MUC knowing to simulate a
455         // disconnected/distributed setup (think load balanced server using local cache), instantiate the cache again and finally
456         // check that it is not picked up.
457         $instance = cache_config_phpunittest::instance();
458         $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
459             'mode' => cache_store::MODE_APPLICATION,
460             'component' => 'phpunit',
461             'area' => 'eventinvalidationtest',
462             'invalidationevents' => array(
463                 'crazyevent'
464             )
465         ));
466         $cache = cache::make('phpunit', 'eventinvalidationtest');
467         $this->assertTrue($cache->set('testkey1', 'test data 1'));
468         $this->assertEquals('test data 1', $cache->get('testkey1'));
470         cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
472         $this->assertFalse($cache->get('testkey1'));
474         // OK data added, data invalidated, and invalidation time has been set.
475         // Now we need to manually add back the data and adjust the invalidation time.
476         $timefile = $CFG->dataroot.'/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/494515064.cache';
477         $timecont = serialize(cache::now() - 60); // Back 60sec in the past to force it to re-invalidate.
478         file_put_contents($timefile, $timecont);
479         $this->assertTrue(file_exists($timefile));
481         $datafile = $CFG->dataroot.'/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/3140056538.cache';
482         $datacont = serialize("test data 1");
483         file_put_contents($datafile, $datacont);
484         $this->assertTrue(file_exists($datafile));
486         // Test 1: Rebuild without the event and test its there.
487         cache_factory::reset();
488         $instance = cache_config_phpunittest::instance();
489         $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
490             'mode' => cache_store::MODE_APPLICATION,
491             'component' => 'phpunit',
492             'area' => 'eventinvalidationtest',
493         ));
494         $cache = cache::make('phpunit', 'eventinvalidationtest');
495         $this->assertEquals('test data 1', $cache->get('testkey1'));
497         // Test 2: Rebuild and test the invalidation of the event via the invalidation cache.
498         cache_factory::reset();
499         $instance = cache_config_phpunittest::instance();
500         $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
501             'mode' => cache_store::MODE_APPLICATION,
502             'component' => 'phpunit',
503             'area' => 'eventinvalidationtest',
504             'invalidationevents' => array(
505                 'crazyevent'
506             )
507         ));
508         $cache = cache::make('phpunit', 'eventinvalidationtest');
509         $this->assertFalse($cache->get('testkey1'));
510     }
512     /**
513      * Tests application cache event purge
514      */
515     public function test_application_event_purge() {
516         $instance = cache_config_phpunittest::instance();
517         $instance->phpunit_add_definition('phpunit/eventpurgetest', array(
518             'mode' => cache_store::MODE_APPLICATION,
519             'component' => 'phpunit',
520             'area' => 'eventpurgetest',
521             'invalidationevents' => array(
522                 'crazyevent'
523             )
524         ));
525         $cache = cache::make('phpunit', 'eventpurgetest');
527         $this->assertTrue($cache->set('testkey1', 'test data 1'));
528         $this->assertEquals('test data 1', $cache->get('testkey1'));
529         $this->assertTrue($cache->set('testkey2', 'test data 2'));
530         $this->assertEquals('test data 2', $cache->get('testkey2'));
532         // Purge the event.
533         cache_helper::purge_by_event('crazyevent');
535         // Check things have been removed.
536         $this->assertFalse($cache->get('testkey1'));
537         $this->assertFalse($cache->get('testkey2'));
538     }
540     /**
541      * Tests application cache definition purge
542      */
543     public function test_application_definition_purge() {
544         $instance = cache_config_phpunittest::instance();
545         $instance->phpunit_add_definition('phpunit/definitionpurgetest', array(
546             'mode' => cache_store::MODE_APPLICATION,
547             'component' => 'phpunit',
548             'area' => 'definitionpurgetest',
549             'invalidationevents' => array(
550                 'crazyevent'
551             )
552         ));
553         $cache = cache::make('phpunit', 'definitionpurgetest');
555         $this->assertTrue($cache->set('testkey1', 'test data 1'));
556         $this->assertEquals('test data 1', $cache->get('testkey1'));
557         $this->assertTrue($cache->set('testkey2', 'test data 2'));
558         $this->assertEquals('test data 2', $cache->get('testkey2'));
560         // Purge the event.
561         cache_helper::purge_by_definition('phpunit', 'definitionpurgetest');
563         // Check things have been removed.
564         $this->assertFalse($cache->get('testkey1'));
565         $this->assertFalse($cache->get('testkey2'));
566     }