diff --git a/cache/locallib.php b/cache/locallib.php index 1f5a7edac30..7063c46511e 100644 --- a/cache/locallib.php +++ b/cache/locallib.php @@ -148,7 +148,9 @@ class cache_config_writer extends cache_config { 'configuration' => $configuration, 'features' => $class::get_supported_features($configuration), 'modes' => $class::get_supported_modes($configuration), - 'mappingsonly' => !empty($configuration['mappingsonly']) + 'mappingsonly' => !empty($configuration['mappingsonly']), + 'class' => $class, + 'default' => false ); if (array_key_exists('lock', $configuration)) { $this->configstores[$name]['lock'] = $configuration['lock']; @@ -207,7 +209,7 @@ class cache_config_writer extends cache_config { /** * Edits a give plugin instance. * - * The plugin instance if determined by its name, hence you cannot rename plugins. + * The plugin instance is determined by its name, hence you cannot rename plugins. * This function also calls save so you should redirect immediately, or at least very shortly after * calling this method. * @@ -225,14 +227,14 @@ class cache_config_writer extends cache_config { if (!array_key_exists($plugin, $plugins)) { throw new cache_exception('Invalid plugin name specified. The plugin either does not exist or is not valid.'); } - $class = 'cachestore_.'.$plugin; + $class = 'cachestore_'.$plugin; $file = $plugins[$plugin]; if (!class_exists($class)) { if (file_exists($file)) { require_once($file); } if (!class_exists($class)) { - throw new cache_exception('Invalid cache plugin specified. The plugin does not contain the require class.'); + throw new cache_exception('Invalid cache plugin specified. The plugin does not contain the required class.'.$class); } } $this->configstores[$name] = array( @@ -241,7 +243,9 @@ class cache_config_writer extends cache_config { 'configuration' => $configuration, 'features' => $class::get_supported_features($configuration), 'modes' => $class::get_supported_modes($configuration), - 'mappingsonly' => !empty($configuration['mappingsonly']) + 'mappingsonly' => !empty($configuration['mappingsonly']), + 'class' => $class, + 'default' => $this->configstores[$name]['default'] // Can't change the default. ); if (array_key_exists('lock', $configuration)) { $this->configstores[$name]['lock'] = $configuration['lock']; @@ -718,7 +722,11 @@ abstract class cache_administration_helper extends cache_helper { */ public static function get_add_store_form($plugin) { global $CFG; // Needed for includes. - $plugindir = get_plugin_directory('cachestore', $plugin); + $plugins = get_plugin_list('cachestore'); + if (!array_key_exists($plugin, $plugins)) { + throw new coding_exception('Invalid cache plugin used when trying to create an edit form.'); + } + $plugindir = $plugins[$plugin]; $class = 'cachestore_addinstance_form'; if (file_exists($plugindir.'/addinstanceform.php')) { require_once($plugindir.'/addinstanceform.php'); @@ -730,6 +738,59 @@ abstract class cache_administration_helper extends cache_helper { } } + $locks = self::get_possible_locks_for_plugin($plugindir, $plugin); + + $url = new moodle_url('/cache/admin.php', array('action' => 'addstore')); + return new $class($url, array('plugin' => $plugin, 'store' => null, 'locks' => $locks)); + } + + /** + * Returns a form that can be used to edit a store instance. + * + * @param string $plugin + * @param string $store + * @return cachestore_addinstance_form + * @throws coding_exception + */ + public static function get_edit_store_form($plugin, $store) { + global $CFG; // Needed for includes. + $plugins = get_plugin_list('cachestore'); + if (!array_key_exists($plugin, $plugins)) { + throw new coding_exception('Invalid cache plugin used when trying to create an edit form.'); + } + $factory = cache_factory::instance(); + $config = $factory->create_config_instance(); + $stores = $config->get_all_stores(); + if (!array_key_exists($store, $stores)) { + throw new coding_exception('Invalid store name given when trying to create an edit form.'); + } + $plugindir = $plugins[$plugin]; + $class = 'cachestore_addinstance_form'; + if (file_exists($plugindir.'/addinstanceform.php')) { + require_once($plugindir.'/addinstanceform.php'); + if (class_exists('cachestore_'.$plugin.'_addinstance_form')) { + $class = 'cachestore_'.$plugin.'_addinstance_form'; + if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) { + throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form'); + } + } + } + + $locks = self::get_possible_locks_for_plugin($plugindir, $plugin); + + $url = new moodle_url('/cache/admin.php', array('action' => 'editstore')); + return new $class($url, array('plugin' => $plugin, 'store' => $store, 'locks' => $locks)); + } + + /** + * Returns an array of suitable lock instances for use with this plugin, or false if the plugin handles locking itself. + * + * @param string $plugindir + * @param string $plugin + * @return array|false + */ + protected static function get_possible_locks_for_plugin($plugindir, $plugin) { + global $CFG; // Needed for includes. $supportsnativelocking = false; if (file_exists($plugindir.'/lib.php')) { require_once($plugindir.'/lib.php'); @@ -754,34 +815,7 @@ abstract class cache_administration_helper extends cache_helper { $locks = false; } - $url = new moodle_url('/cache/admin.php', array('action' => 'addstore')); - return new $class($url, array('plugin' => $plugin, 'store' => null, 'locks' => $locks)); - } - - /** - * Returns a form that can be used to edit a store instance. - * - * @param string $plugin - * @param string $store - * @return cachestore_addinstance_form - * @throws coding_exception - */ - public static function get_edit_store_form($plugin, $store) { - global $CFG; // Needed for includes. - $plugindir = get_plugin_directory('cachestore', $plugin); - $class = 'cachestore_addinstance_form'; - if (file_exists($plugindir.'/addinstanceform.php')) { - require_once($plugindir.'/addinstanceform.php'); - if (class_exists('cachestore_'.$plugin.'_addinstance_form')) { - $class = 'cachestore_'.$plugin.'_addinstance_form'; - if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) { - throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form'); - } - } - } - - $url = new moodle_url('/cache/admin.php', array('action' => 'editstore')); - return new $class($url, array('plugin' => $plugin, 'store' => $store)); + return $locks; } /** diff --git a/cache/tests/cache_test.php b/cache/tests/cache_test.php index cc40a4e89fa..7439d731703 100644 --- a/cache/tests/cache_test.php +++ b/cache/tests/cache_test.php @@ -345,6 +345,29 @@ class cache_phpunit_tests extends advanced_testcase { $this->assertEquals('Test has no value really.', $cache->get('Test')); } + public function test_definition_ttl() { + $instance = cache_config_phpunittest::instance(true); + $instance->phpunit_add_definition('phpunit/ttltest', array( + 'mode' => cache_store::MODE_APPLICATION, + 'component' => 'phpunit', + 'area' => 'ttltest', + 'ttl' => -10 + )); + $cache = cache::make('phpunit', 'ttltest'); + $this->assertInstanceOf('cache_application', $cache); + + // Purge it to be sure. + $this->assertTrue($cache->purge()); + // It won't be there yet. + $this->assertFalse($cache->has('Test')); + // Set it now. + $this->assertTrue($cache->set('Test', 'Test')); + // Check its not there. + $this->assertFalse($cache->has('Test')); + // Double check by trying to get it. + $this->assertFalse($cache->get('Test')); + } + /** * Tests manual locking operations on an application cache */ @@ -564,4 +587,16 @@ class cache_phpunit_tests extends advanced_testcase { $this->assertFalse($cache->get('testkey1')); $this->assertFalse($cache->get('testkey2')); } + + /** + * Test the use of an alt path. + * If we can generate a config instance we are done :) + */ + public function test_alt_cache_path() { + global $CFG; + $this->resetAfterTest(); + $CFG->altcacheconfigpath = $CFG->dataroot.'/cache/altcacheconfigpath'; + $instance = cache_config_phpunittest::instance(); + $this->assertInstanceOf('cache_config', $instance); + } } \ No newline at end of file diff --git a/cache/tests/locallib_test.php b/cache/tests/locallib_test.php new file mode 100644 index 00000000000..754ebacde6f --- /dev/null +++ b/cache/tests/locallib_test.php @@ -0,0 +1,425 @@ +. + +/** + * PHPunit tests for the cache API and in particular things in locallib.php + * + * This file is part of Moodle's cache API, affectionately called MUC. + * It contains the components that are requried in order to use caching. + * + * @package core + * @category cache + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +// Include the necessary evils. +global $CFG; +require_once($CFG->dirroot.'/cache/locallib.php'); +require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php'); + +/** + * PHPunit tests for the cache API and in particular the cache config writer. + * + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cache_config_writer_phpunit_tests extends advanced_testcase { + + /** + * Set things back to the default before each test. + */ + public function setUp() { + parent::setUp(); + cache_factory::reset(); + cache_config_phpunittest::create_default_configuration(); + } + + /** + * Test getting an instance. Pretty basic. + */ + public function test_instance() { + $config = cache_config_writer::instance(); + $this->assertInstanceOf('cache_config_writer', $config); + } + + /** + * Test the default configuration. + */ + public function test_default_configuration() { + $config = cache_config_writer::instance(); + + // First check stores. + $stores = $config->get_all_stores(); + $hasapplication = false; + $hassession = false; + $hasrequest = false; + foreach ($stores as $store) { + // Check the required keys. + $this->assertArrayHasKey('name', $store); + $this->assertArrayHasKey('plugin', $store); + $this->assertArrayHasKey('modes', $store); + $this->assertArrayHasKey('default', $store); + // Check the mode, we need at least one default store of each mode. + if (!empty($store['default'])) { + if ($store['modes'] & cache_store::MODE_APPLICATION) { + $hasapplication = true; + } + if ($store['modes'] & cache_store::MODE_SESSION) { + $hassession = true; + } + if ($store['modes'] & cache_store::MODE_REQUEST) { + $hasrequest = true; + } + } + } + $this->assertTrue($hasapplication, 'There is no default application cache store.'); + $this->assertTrue($hassession, 'There is no default session cache store.'); + $this->assertTrue($hasrequest, 'There is no default request cache store.'); + + // Next check the definitions. + $definitions = $config->get_definitions(); + $eventinvalidation = false; + foreach ($definitions as $definition) { + // Check the required keys. + $this->assertArrayHasKey('mode', $definition); + $this->assertArrayHasKey('component', $definition); + $this->assertArrayHasKey('area', $definition); + if ($definition['component'] === 'core' && $definition['area'] === 'eventinvalidation') { + $eventinvalidation = true; + } + } + $this->assertTrue($eventinvalidation, 'Missing the event invalidation definition.'); + + // Next mode mappings + $mappings = $config->get_mode_mappings(); + $hasapplication = false; + $hassession = false; + $hasrequest = false; + foreach ($mappings as $mode) { + // Check the required keys. + $this->assertArrayHasKey('mode', $mode); + $this->assertArrayHasKey('store', $mode); + + if ($mode['mode'] === cache_store::MODE_APPLICATION) { + $hasapplication = true; + } + if ($mode['mode'] === cache_store::MODE_SESSION) { + $hassession = true; + } + if ($mode['mode'] === cache_store::MODE_REQUEST) { + $hasrequest = true; + } + } + $this->assertTrue($hasapplication, 'There is no mapping for the application mode.'); + $this->assertTrue($hassession, 'There is no mapping for the session mode.'); + $this->assertTrue($hasrequest, 'There is no mapping for the request mode.'); + + // Finally check config locks + $locks = $config->get_locks(); + foreach ($locks as $lock) { + $this->assertArrayHasKey('name', $lock); + $this->assertArrayHasKey('type', $lock); + $this->assertArrayHasKey('default', $lock); + } + // There has to be at least the default lock. + $this->assertTrue(count($locks) > 0); + } + + /** + * Test updating the definitions. + */ + public function test_update_definitions() { + $config = cache_config_writer::instance(); + $earlydefinitions = $config->get_definitions(); + unset($config); + cache_factory::reset(); + cache_config_writer::update_definitions(); + + $config = cache_config_writer::instance(); + $latedefinitions = $config->get_definitions(); + + $this->assertSame($latedefinitions, $earlydefinitions); + } + + /** + * Test adding/editing/deleting store instances. + */ + public function test_add_edit_delete_plugin_instance() { + $config = cache_config_writer::instance(); + $this->assertArrayNotHasKey('addplugintest', $config->get_all_stores()); + $this->assertArrayNotHasKey('addplugintestwlock', $config->get_all_stores()); + // Add a default file instance. + $config->add_plugin_instance('addplugintest', 'file'); + + cache_factory::reset(); + $config = cache_config_writer::instance(); + $this->assertArrayHasKey('addplugintest', $config->get_all_stores()); + + // Add a store with a lock described. + $config->add_plugin_instance('addplugintestwlock', 'file', array('lock' => 'default_file_lock')); + $this->assertArrayHasKey('addplugintestwlock', $config->get_all_stores()); + + $config->delete_store('addplugintest'); + $this->assertArrayNotHasKey('addplugintest', $config->get_all_stores()); + $this->assertArrayHasKey('addplugintestwlock', $config->get_all_stores()); + + $config->delete_store('addplugintestwlock'); + $this->assertArrayNotHasKey('addplugintest', $config->get_all_stores()); + $this->assertArrayNotHasKey('addplugintestwlock', $config->get_all_stores()); + + // Add a default file instance. + $config->add_plugin_instance('storeconfigtest', 'file', array('test' => 'a', 'one' => 'two')); + $stores = $config->get_all_stores(); + $this->assertArrayHasKey('storeconfigtest', $stores); + $this->assertArrayHasKey('configuration', $stores['storeconfigtest']); + $this->assertArrayHasKey('test', $stores['storeconfigtest']['configuration']); + $this->assertArrayHasKey('one', $stores['storeconfigtest']['configuration']); + $this->assertEquals('a', $stores['storeconfigtest']['configuration']['test']); + $this->assertEquals('two', $stores['storeconfigtest']['configuration']['one']); + + $config->edit_plugin_instance('storeconfigtest', 'file', array('test' => 'b', 'one' => 'three')); + $stores = $config->get_all_stores(); + $this->assertArrayHasKey('storeconfigtest', $stores); + $this->assertArrayHasKey('configuration', $stores['storeconfigtest']); + $this->assertArrayHasKey('test', $stores['storeconfigtest']['configuration']); + $this->assertArrayHasKey('one', $stores['storeconfigtest']['configuration']); + $this->assertEquals('b', $stores['storeconfigtest']['configuration']['test']); + $this->assertEquals('three', $stores['storeconfigtest']['configuration']['one']); + + $config->delete_store('storeconfigtest'); + + try { + $config->delete_store('default_application'); + $this->fail('Default store deleted. This should not be possible!'); + } catch (Exception $e) { + $this->assertInstanceOf('cache_exception', $e); + } + + try { + $config->delete_store('some_crazy_store'); + $this->fail('You should not be able to delete a store that does not exist.'); + } catch (Exception $e) { + $this->assertInstanceOf('cache_exception', $e); + } + + try { + // Try with a plugin that does not exist. + $config->add_plugin_instance('storeconfigtest', 'shallowfail', array('test' => 'a', 'one' => 'two')); + $this->fail('You should not be able to add an instance of a store that does not exist.'); + } catch (Exception $e) { + $this->assertInstanceOf('cache_exception', $e); + } + } + + /** + * Test setting some mode mappings. + */ + public function test_set_mode_mappings() { + $config = cache_config_writer::instance(); + $this->assertTrue($config->add_plugin_instance('setmodetest', 'file')); + $this->assertTrue($config->set_mode_mappings(array( + cache_store::MODE_APPLICATION => array('setmodetest', 'default_application'), + cache_store::MODE_SESSION => array('default_session'), + cache_store::MODE_REQUEST => array('default_request'), + ))); + $mappings = $config->get_mode_mappings(); + $setmodetestfound = false; + foreach ($mappings as $mapping) { + if ($mapping['store'] == 'setmodetest' && $mapping['mode'] == cache_store::MODE_APPLICATION) { + $setmodetestfound = true; + } + } + $this->assertTrue($setmodetestfound, 'Set mapping did not work as expected.'); + } + + /** + * Test setting some definition mappings. + */ + public function test_set_definition_mappings() { + $config = cache_config_phpunittest::instance(true); + $config->phpunit_add_definition('phpunit/testdefinition', array( + 'mode' => cache_store::MODE_APPLICATION, + 'component' => 'phpunit', + 'area' => 'testdefinition' + )); + + $config = cache_config_writer::instance(); + $this->assertTrue($config->add_plugin_instance('setdefinitiontest', 'file')); + $this->assertInternalType('array', $config->get_definition_by_id('phpunit/testdefinition')); + $config->set_definition_mappings('phpunit/testdefinition', array('setdefinitiontest', 'default_application')); + + try { + $config->set_definition_mappings('phpunit/testdefinition', array('something that does not exist')); + $this->fail('You should not be able to set a mapping for a store that does not exist.'); + } catch (Exception $e) { + $this->assertInstanceOf('coding_exception', $e); + } + + try { + $config->set_definition_mappings('something/crazy', array('setdefinitiontest')); + $this->fail('You should not be able to set a mapping for a definition that does not exist.'); + } catch (Exception $e) { + $this->assertInstanceOf('coding_exception', $e); + } + } +} + +/** + * PHPunit tests for the cache API and in particular the cache_administration_helper + * + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cache_administration_helper_phpunit_tests extends advanced_testcase { + + /** + * Set things back to the default before each test. + */ + public function setUp() { + parent::setUp(); + cache_factory::reset(); + cache_config_phpunittest::create_default_configuration(); + } + + /** + * Test the numerous summaries the helper can produce. + */ + public function test_get_summaries() { + // First the preparation. + $config = cache_config_writer::instance(); + $this->assertTrue($config->add_plugin_instance('summariesstore', 'file')); + $config->set_definition_mappings('core/eventinvalidation', array('summariesstore')); + $this->assertTrue($config->set_mode_mappings(array( + cache_store::MODE_APPLICATION => array('summariesstore'), + cache_store::MODE_SESSION => array('default_session'), + cache_store::MODE_REQUEST => array('default_request'), + ))); + + $storesummaries = cache_administration_helper::get_store_summaries(); + $this->assertInternalType('array', $storesummaries); + $this->assertArrayHasKey('summariesstore', $storesummaries); + $summary = $storesummaries['summariesstore']; + // Check the keys + $this->assertArrayHasKey('name', $summary); + $this->assertArrayHasKey('plugin', $summary); + $this->assertArrayHasKey('default', $summary); + $this->assertArrayHasKey('isready', $summary); + $this->assertArrayHasKey('requirementsmet', $summary); + $this->assertArrayHasKey('mappings', $summary); + $this->assertArrayHasKey('modes', $summary); + $this->assertArrayHasKey('supports', $summary); + // Check the important/known values + $this->assertEquals('summariesstore', $summary['name']); + $this->assertEquals('file', $summary['plugin']); + $this->assertEquals(0, $summary['default']); + $this->assertEquals(1, $summary['isready']); + $this->assertEquals(1, $summary['requirementsmet']); + $this->assertEquals(1, $summary['mappings']); + + $definitionsummaries = cache_administration_helper::get_definition_summaries(); + $this->assertInternalType('array', $definitionsummaries); + $this->assertArrayHasKey('core/eventinvalidation', $definitionsummaries); + $summary = $definitionsummaries['core/eventinvalidation']; + // Check the keys + $this->assertArrayHasKey('id', $summary); + $this->assertArrayHasKey('name', $summary); + $this->assertArrayHasKey('mode', $summary); + $this->assertArrayHasKey('component', $summary); + $this->assertArrayHasKey('area', $summary); + $this->assertArrayHasKey('mappings', $summary); + // Check the important/known values + $this->assertEquals('core/eventinvalidation', $summary['id']); + $this->assertInstanceOf('lang_string', $summary['name']); + $this->assertEquals(cache_store::MODE_APPLICATION, $summary['mode']); + $this->assertEquals('core', $summary['component']); + $this->assertEquals('eventinvalidation', $summary['area']); + $this->assertInternalType('array', $summary['mappings']); + $this->assertContains('summariesstore', $summary['mappings']); + + $pluginsummaries = cache_administration_helper::get_plugin_summaries(); + $this->assertInternalType('array', $pluginsummaries); + $this->assertArrayHasKey('file', $pluginsummaries); + $summary = $pluginsummaries['file']; + // Check the keys + $this->assertArrayHasKey('name', $summary); + $this->assertArrayHasKey('requirementsmet', $summary); + $this->assertArrayHasKey('instances', $summary); + $this->assertArrayHasKey('modes', $summary); + $this->assertArrayHasKey('supports', $summary); + $this->assertArrayHasKey('canaddinstance', $summary); + + $locksummaries = cache_administration_helper::get_lock_summaries(); + $this->assertInternalType('array', $locksummaries); + $this->assertTrue(count($locksummaries) > 0); + + $mappings = cache_administration_helper::get_default_mode_stores(); + $this->assertInternalType('array', $mappings); + $this->assertCount(3, $mappings); + $this->assertArrayHasKey(cache_store::MODE_APPLICATION, $mappings); + $this->assertInternalType('array', $mappings[cache_store::MODE_APPLICATION]); + $this->assertContains('summariesstore', $mappings[cache_store::MODE_APPLICATION]); + + $potentials = cache_administration_helper::get_definition_store_options('core', 'eventinvalidation'); + $this->assertInternalType('array', $potentials); // Currently used, suitable, default + $this->assertCount(3, $potentials); + $this->assertArrayHasKey('summariesstore', $potentials[0]); + $this->assertArrayHasKey('summariesstore', $potentials[1]); + $this->assertArrayHasKey('default_application', $potentials[1]); + } + + /** + * Test instantiating an add store form. + */ + public function test_get_add_store_form() { + $form = cache_administration_helper::get_add_store_form('file'); + $this->assertInstanceOf('moodleform', $form); + + try { + $form = cache_administration_helper::get_add_store_form('somethingstupid'); + $this->fail('You should not be able to create an add form for a store plugin that does not exist.'); + } catch (moodle_exception $e) { + $this->assertInstanceOf('coding_exception', $e, 'Needs to be: ' .get_class($e)." ::: ".$e->getMessage()); + } + } + + /** + * Test instantiating a form to edit a store instance. + */ + public function test_get_edit_store_form() { + $config = cache_config_writer::instance(); + $this->assertTrue($config->add_plugin_instance('summariesstore', 'file')); + + $form = cache_administration_helper::get_edit_store_form('file', 'summariesstore'); + $this->assertInstanceOf('moodleform', $form); + + try { + $form = cache_administration_helper::get_edit_store_form('somethingstupid', 'moron'); + $this->fail('You should not be able to create an edit form for a store plugin that does not exist.'); + } catch (moodle_exception $e) { + $this->assertInstanceOf('coding_exception', $e); + } + + try { + $form = cache_administration_helper::get_edit_store_form('file', 'blisters'); + $this->fail('You should not be able to create an edit form for a store plugin that does not exist.'); + } catch (moodle_exception $e) { + $this->assertInstanceOf('coding_exception', $e); + } + } +} \ No newline at end of file