2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * PHPunit tests for the cache API
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.
25 * @copyright 2012 Sam Hemelryk
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 defined('MOODLE_INTERNAL') || die();
31 // Include the necessary evils.
33 require_once($CFG->dirroot.'/cache/locallib.php');
34 require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
37 * PHPunit tests for the cache API
39 * @copyright 2012 Sam Hemelryk
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42 class cache_phpunit_tests extends advanced_testcase {
45 * Set things back to the default before each test.
47 public function setUp() {
49 cache_factory::reset();
50 cache_config_phpunittest::create_default_configuration();
54 * Tests cache configuration
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(4, $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);
79 $modemappings = $instance->get_mode_mappings();
80 $this->assertCount(3, $modemappings);
82 cache_store::MODE_APPLICATION => false,
83 cache_store::MODE_SESSION => false,
84 cache_store::MODE_REQUEST => false,
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);
93 $modes[$mapping['mode']] = true;
96 // Must have the default 3 modes and no more.
97 $this->assertCount(3, $mapping);
98 foreach ($modes as $mode) {
99 $this->assertTrue($mode);
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);
115 * Tests the default application cache
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);
124 * Tests the default session cache
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);
133 * Tests the default request cache
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);
142 * Tests using a cache system when there are no stores available (who knows what the admin did to achieve this).
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',
151 $instance->phpunit_add_definition('phpunit/nostoretest2', array(
152 'mode' => cache_store::MODE_APPLICATION,
153 'component' => 'phpunit',
154 'area' => 'nostoretest2',
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);
167 * Runs a standard series of access and use tests on a cache instance.
169 * This function is great because we can use it to ensure all of the loaders perform exactly the same way.
171 * @param cache_loader $cache
173 protected function run_on_cache(cache_loader $cache) {
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)));
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)));
205 $this->assertTrue($cache->delete($key));
208 $this->assertFalse($cache->get($key));
209 $this->assertFalse($cache->has($key));
212 $this->assertTrue($cache->set($key, $dataarray));
213 $this->assertEquals($dataarray, $cache->get($key));
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);
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'));
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'));
247 * Tests a definition using a data loader
249 public function test_definition_data_loader() {
250 $instance = cache_config_phpunittest::instance(true);
251 $instance->phpunit_add_definition('phpunit/datasourcetest', array(
252 'mode' => cache_store::MODE_APPLICATION,
253 'component' => 'phpunit',
254 'area' => 'datasourcetest',
255 'datasource' => 'cache_phpunit_dummy_datasource'
258 $cache = cache::make('phpunit', 'datasourcetest');
259 $this->assertInstanceOf('cache_application', $cache);
261 // Purge it to be sure.
262 $this->assertTrue($cache->purge());
263 // It won't be there yet.
264 $this->assertFalse($cache->has('Test'));
266 // It should load it ;).
267 $this->assertTrue($cache->has('Test', true));
269 // Purge it to be sure.
270 $this->assertTrue($cache->purge());
271 $this->assertEquals('Test has no value really.', $cache->get('Test'));
275 * Tests a definition using an overridden loader
277 public function test_definition_overridden_loader() {
278 $instance = cache_config_phpunittest::instance(true);
279 $instance->phpunit_add_definition('phpunit/overridetest', array(
280 'mode' => cache_store::MODE_APPLICATION,
281 'component' => 'phpunit',
282 'area' => 'overridetest',
283 'overrideclass' => 'cache_phpunit_dummy_overrideclass'
285 $cache = cache::make('phpunit', 'overridetest');
286 $this->assertInstanceOf('cache_phpunit_dummy_overrideclass', $cache);
287 $this->assertInstanceOf('cache_application', $cache);
288 // Purge it to be sure.
289 $this->assertTrue($cache->purge());
290 // It won't be there yet.
291 $this->assertFalse($cache->has('Test'));
293 $this->assertTrue($cache->set('Test', 'Test has no value really.'));
295 $this->assertEquals('Test has no value really.', $cache->get('Test'));
299 * Tests manual locking operations on an application cache
301 public function test_application_manual_locking() {
302 $instance = cache_config_phpunittest::instance();
303 $instance->phpunit_add_definition('phpunit/lockingtest', array(
304 'mode' => cache_store::MODE_APPLICATION,
305 'component' => 'phpunit',
306 'area' => 'lockingtest'
308 $cache1 = cache::make('phpunit', 'lockingtest');
309 $cache2 = clone($cache1);
311 $this->assertTrue($cache1->set('testkey', 'test data'));
312 $this->assertTrue($cache2->set('testkey', 'test data'));
314 $this->assertTrue($cache1->acquire_lock('testkey'));
315 $this->assertFalse($cache2->acquire_lock('testkey'));
317 $this->assertTrue($cache1->check_lock_state('testkey'));
318 $this->assertFalse($cache2->check_lock_state('testkey'));
320 $this->assertTrue($cache1->release_lock('testkey'));
321 $this->assertFalse($cache2->release_lock('testkey'));
323 $this->assertTrue($cache1->set('testkey', 'test data'));
324 $this->assertTrue($cache2->set('testkey', 'test data'));
328 * Tests application cache event invalidation
330 public function test_application_event_invalidation() {
331 $instance = cache_config_phpunittest::instance();
332 $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
333 'mode' => cache_store::MODE_APPLICATION,
334 'component' => 'phpunit',
335 'area' => 'eventinvalidationtest',
336 'invalidationevents' => array(
340 $cache = cache::make('phpunit', 'eventinvalidationtest');
342 $this->assertTrue($cache->set('testkey1', 'test data 1'));
343 $this->assertEquals('test data 1', $cache->get('testkey1'));
344 $this->assertTrue($cache->set('testkey2', 'test data 2'));
345 $this->assertEquals('test data 2', $cache->get('testkey2'));
347 // Test invalidating a single entry.
348 cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
350 $this->assertFalse($cache->get('testkey1'));
351 $this->assertEquals('test data 2', $cache->get('testkey2'));
353 $this->assertTrue($cache->set('testkey1', 'test data 1'));
355 // Test invalidating both entries.
356 cache_helper::invalidate_by_event('crazyevent', array('testkey1', 'testkey2'));
358 $this->assertFalse($cache->get('testkey1'));
359 $this->assertFalse($cache->get('testkey2'));
363 * Tests application cache definition invalidation
365 public function test_application_definition_invalidation() {
366 $instance = cache_config_phpunittest::instance();
367 $instance->phpunit_add_definition('phpunit/definitioninvalidation', array(
368 'mode' => cache_store::MODE_APPLICATION,
369 'component' => 'phpunit',
370 'area' => 'definitioninvalidation'
372 $cache = cache::make('phpunit', 'definitioninvalidation');
373 $this->assertTrue($cache->set('testkey1', 'test data 1'));
374 $this->assertEquals('test data 1', $cache->get('testkey1'));
375 $this->assertTrue($cache->set('testkey2', 'test data 2'));
376 $this->assertEquals('test data 2', $cache->get('testkey2'));
378 cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), 'testkey1');
380 $this->assertFalse($cache->get('testkey1'));
381 $this->assertEquals('test data 2', $cache->get('testkey2'));
383 $this->assertTrue($cache->set('testkey1', 'test data 1'));
385 cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), array('testkey1'));
387 $this->assertFalse($cache->get('testkey1'));
388 $this->assertEquals('test data 2', $cache->get('testkey2'));
390 $this->assertTrue($cache->set('testkey1', 'test data 1'));
392 cache_helper::invalidate_by_definition('phpunit', 'definitioninvalidation', array(), array('testkey1', 'testkey2'));
394 $this->assertFalse($cache->get('testkey1'));
395 $this->assertFalse($cache->get('testkey2'));
399 * Tests application cache event invalidation over a distributed setup.
401 public function test_distributed_application_event_invalidation() {
403 // This is going to be an intense wee test.
404 // We need to add data the to cache, invalidate it by event, manually force it back without MUC knowing to simulate a
405 // disconnected/distributed setup (think load balanced server using local cache), instantiate the cache again and finally
406 // check that it is not picked up.
407 $instance = cache_config_phpunittest::instance();
408 $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
409 'mode' => cache_store::MODE_APPLICATION,
410 'component' => 'phpunit',
411 'area' => 'eventinvalidationtest',
412 'invalidationevents' => array(
416 $cache = cache::make('phpunit', 'eventinvalidationtest');
417 $this->assertTrue($cache->set('testkey1', 'test data 1'));
418 $this->assertEquals('test data 1', $cache->get('testkey1'));
420 cache_helper::invalidate_by_event('crazyevent', array('testkey1'));
422 $this->assertFalse($cache->get('testkey1'));
424 // OK data added, data invalidated, and invalidation time has been set.
425 // Now we need to manually add back the data and adjust the invalidation time.
426 $timefile = $CFG->dataroot.'/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/494515064.cache';
427 $timecont = serialize(cache::now() - 60); // Back 60sec in the past to force it to re-invalidate.
428 file_put_contents($timefile, $timecont);
429 $this->assertTrue(file_exists($timefile));
431 $datafile = $CFG->dataroot.'/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/3140056538.cache';
432 $datacont = serialize("test data 1");
433 file_put_contents($datafile, $datacont);
434 $this->assertTrue(file_exists($datafile));
436 // Test 1: Rebuild without the event and test its there.
437 cache_factory::reset();
438 $instance = cache_config_phpunittest::instance();
439 $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
440 'mode' => cache_store::MODE_APPLICATION,
441 'component' => 'phpunit',
442 'area' => 'eventinvalidationtest',
444 $cache = cache::make('phpunit', 'eventinvalidationtest');
445 $this->assertEquals('test data 1', $cache->get('testkey1'));
447 // Test 2: Rebuild and test the invalidation of the event via the invalidation cache.
448 cache_factory::reset();
449 $instance = cache_config_phpunittest::instance();
450 $instance->phpunit_add_definition('phpunit/eventinvalidationtest', array(
451 'mode' => cache_store::MODE_APPLICATION,
452 'component' => 'phpunit',
453 'area' => 'eventinvalidationtest',
454 'invalidationevents' => array(
458 $cache = cache::make('phpunit', 'eventinvalidationtest');
459 $this->assertFalse($cache->get('testkey1'));
463 * Tests application cache event purge
465 public function test_application_event_purge() {
466 $instance = cache_config_phpunittest::instance();
467 $instance->phpunit_add_definition('phpunit/eventpurgetest', array(
468 'mode' => cache_store::MODE_APPLICATION,
469 'component' => 'phpunit',
470 'area' => 'eventpurgetest',
471 'invalidationevents' => array(
475 $cache = cache::make('phpunit', 'eventpurgetest');
477 $this->assertTrue($cache->set('testkey1', 'test data 1'));
478 $this->assertEquals('test data 1', $cache->get('testkey1'));
479 $this->assertTrue($cache->set('testkey2', 'test data 2'));
480 $this->assertEquals('test data 2', $cache->get('testkey2'));
483 cache_helper::purge_by_event('crazyevent');
485 // Check things have been removed.
486 $this->assertFalse($cache->get('testkey1'));
487 $this->assertFalse($cache->get('testkey2'));
491 * Tests application cache definition purge
493 public function test_application_definition_purge() {
494 $instance = cache_config_phpunittest::instance();
495 $instance->phpunit_add_definition('phpunit/definitionpurgetest', array(
496 'mode' => cache_store::MODE_APPLICATION,
497 'component' => 'phpunit',
498 'area' => 'definitionpurgetest',
499 'invalidationevents' => array(
503 $cache = cache::make('phpunit', 'definitionpurgetest');
505 $this->assertTrue($cache->set('testkey1', 'test data 1'));
506 $this->assertEquals('test data 1', $cache->get('testkey1'));
507 $this->assertTrue($cache->set('testkey2', 'test data 2'));
508 $this->assertEquals('test data 2', $cache->get('testkey2'));
511 cache_helper::purge_by_definition('phpunit', 'definitionpurgetest');
513 // Check things have been removed.
514 $this->assertFalse($cache->get('testkey1'));
515 $this->assertFalse($cache->get('testkey2'));