define('TEST_CACHESTORE_MEMCACHE_TESTSERVERS', '127.0.0.1:11211');
define('TEST_CACHESTORE_MEMCACHED_TESTSERVERS', '127.0.0.1:11211');
define('TEST_CACHESTORE_MONGODB_TESTSERVER', 'mongodb://localhost:27017');
+
+As of Moodle 2.8 it is also possible to set the default cache stores used when running unit tests.
+You can do this by adding the following define to your config.php file:
+
+ // xxx is one of Memcache, Memecached, mongodb or other cachestore with a test define.
+ define('TEST_CACHE_USING_APPLICATION_STORE', 'xxx');
+
+This allows you to run tests against a defined test store. It uses the defined value to identify a store to test against with a matching TEST_CACHESTORE define.
+Alternatively you can also run unit tests against an actual cache config.
+To do this you must add the following to your config.php file:
+
+ define('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH', true');
+ $CFG->altcacheconfigpath = '/a/temp/directory/yoursite.php'
+
+This tells Moodle to use the config at $CFG->altcacheconfigpath when running unit tests.
+There are a couple of considerations to using this method:
+* By setting $CFG->altcacheconfigpath your site will store the cache config in the specified path, not just the unit test cache config but your site config as well.
+* If you have configured your cache before setting $CFG->altcacheconfigpath you will need to copy it from moodledata/muc/config.php to the destination you specified.
+* This allows you to share a cache config between sites.
+* It also allows you to use unit tests to test your sites cache config.
// situation. It will use disabled alternatives where available.
require_once($CFG->dirroot.'/cache/disabledlib.php');
self::$instance = new cache_factory_disabled();
+ } else if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
+ // We're using the regular factory.
+ require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
+ self::$instance = new cache_phpunit_factory();
+ if (defined('CACHE_DISABLE_STORES') && CACHE_DISABLE_STORES !== false) {
+ // The cache stores have been disabled.
+ self::$instance->set_state(self::STATE_STORES_DISABLED);
+ }
} else {
// We're using the regular factory.
self::$instance = new cache_factory();
// The class to use.
$class = 'cache_config';
+ $unittest = defined('PHPUNIT_TEST') && PHPUNIT_TEST;
+
// Check if this is a PHPUnit test and redirect to the phpunit config classes if it is.
- if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
+ if ($unittest) {
require_once($CFG->dirroot.'/cache/locallib.php');
require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
// We have just a single class for PHP unit tests. We don't care enough about its
if ($writer || $needtocreate) {
require_once($CFG->dirroot.'/cache/locallib.php');
- $class .= '_writer';
- }
-
- // Check if this is a PHPUnit test and redirect to the phpunit config classes if it is.
- if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
- require_once($CFG->dirroot.'/cache/locallib.php');
- require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php');
- // We have just a single class for PHP unit tests. We don't care enough about its
- // performance to do otherwise and having a single method allows us to inject things into it
- // while testing.
- $class = 'cache_config_phpunittest';
+ if (!$unittest) {
+ $class .= '_writer';
+ }
}
$error = false;
* @return cache_store|false
*/
public static function initialise_test_instance(cache_definition $definition);
+
+ /**
+ * Initialises a test instance for unit tests.
+ *
+ * This differs from initialise_test_instance in that it doesn't rely on interacting with the config table.
+ *
+ * @since 2.8
+ * @param cache_definition $definition
+ * @return cache_store|false
+ */
+ public static function initialise_unit_test_instance(cache_definition $definition);
}
/**
// Any stores that have an issue with this will need to override the create_clone method.
return clone($this);
}
+
+ /**
+ * Initialises a test instance for unit tests.
+ *
+ * This differs from initialise_test_instance in that it doesn't rely on interacting with the config table.
+ * By default however it calls initialise_test_instance to support backwards compatability.
+ *
+ * @since 2.8
+ * @param cache_definition $definition
+ * @return cache_store|false
+ */
+ public static function initialise_unit_test_instance(cache_definition $definition) {
+ return self::initialise_test_instance($definition);
+ }
}
return $store;
}
+ /**
+ * Creates a test instance for unit tests if possible.
+ * @param cache_definition $definition
+ * @return bool|cachestore_memcache
+ */
+ public static function initialise_unit_test_instance(cache_definition $definition) {
+ if (!self::are_requirements_met()) {
+ return false;
+ }
+ if (!defined('TEST_CACHESTORE_MEMCACHE_TESTSERVERS')) {
+ return false;
+ }
+ $configuration = array();
+ $configuration['servers'] = explode("\n", TEST_CACHESTORE_MEMCACHE_TESTSERVERS);
+
+ $store = new cachestore_memcache('Test memcache', $configuration);
+ $store->initialise($definition);
+
+ return $store;
+ }
+
/**
* Returns the name of this instance.
* @return string
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_memcache_test extends cachestore_tests {
- /**
- * Prepare to run tests.
- */
- public function setUp() {
- if (defined('TEST_CACHESTORE_MEMCACHE_TESTSERVERS')) {
- set_config('testservers', TEST_CACHESTORE_MEMCACHE_TESTSERVERS, 'cachestore_memcache');
- $this->resetAfterTest();
- }
- parent::setUp();
- }
/**
* Returns the memcache class name
* @return string
*/
public function test_valid_keys() {
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcache', 'phpunit_test');
- $instance = cachestore_memcache::initialise_test_instance($definition);
+ $instance = cachestore_memcache::initialise_unit_test_instance($definition);
if (!$instance) { // Something prevented memcache store to be inited (extension, TEST_CACHESTORE_MEMCACHE_TESTSERVERS...).
$this->markTestSkipped();
return $store;
}
+ /**
+ * Creates a test instance for unit tests if possible.
+ * @param cache_definition $definition
+ * @return bool|cachestore_memcached
+ */
+ public static function initialise_unit_test_instance(cache_definition $definition) {
+ if (!self::are_requirements_met()) {
+ return false;
+ }
+ if (!defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
+ return false;
+ }
+
+ $configuration = array();
+ $configuration['servers'] = explode("\n", TEST_CACHESTORE_MEMCACHED_TESTSERVERS);
+
+ $store = new cachestore_memcached('Test memcached', $configuration);
+ $store->initialise($definition);
+
+ return $store;
+ }
+
/**
* Returns the name of this instance.
* @return string
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_memcached_test extends cachestore_tests {
- /**
- * Prepare to run tests.
- */
- public function setUp() {
- if (defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
- set_config('testservers', TEST_CACHESTORE_MEMCACHED_TESTSERVERS, 'cachestore_memcached');
- $this->resetAfterTest();
- }
- parent::setUp();
- }
/**
* Returns the memcached class name
* @return string
*/
public function test_valid_keys() {
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcached', 'phpunit_test');
- $instance = cachestore_memcached::initialise_test_instance($definition);
+ $instance = cachestore_memcached::initialise_unit_test_instance($definition);
if (!$instance) { // Something prevented memcached store to be inited (extension, TEST_CACHESTORE_MEMCACHED_TESTSERVERS...).
$this->markTestSkipped();
return $store;
}
+
+ /**
+ * Generates an instance of the cache store that can be used for testing.
+ *
+ * @param cache_definition $definition
+ * @return false
+ */
+ public static function initialise_unit_test_instance(cache_definition $definition) {
+ if (!self::are_requirements_met()) {
+ return false;
+ }
+ if (!defined('TEST_CACHESTORE_MONGODB_TESTSERVER')) {
+ return false;
+ }
+
+ $configuration = array();
+ $configuration['servers'] = explode("\n", TEST_CACHESTORE_MONGODB_TESTSERVER);
+
+ $store = new cachestore_mongodb('Test mongodb', $configuration);
+ $store->initialise($definition);
+
+ return $store;
+ }
+
/**
* Returns the name of this instance.
* @return string
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_mongodb_test extends cachestore_tests {
- /**
- * Prepare to run tests.
- */
- public function setUp() {
- if (defined('TEST_CACHESTORE_MONGODB_TESTSERVER')) {
- set_config('testserver', TEST_CACHESTORE_MONGODB_TESTSERVER, 'cachestore_mongodb');
- $this->resetAfterTest();
- }
- parent::setUp();
- }
/**
* Returns the MongoDB class name
* @return string
cache_factory::reset();
}
+ /**
+ * Returns the expected application cache store.
+ * @return string
+ */
+ protected function get_expected_application_cache_store() {
+ $expected = 'cachestore_file';
+ if (defined('TEST_CACHE_USING_APPLICATION_STORE') && preg_match('#[a-zA-Z][a-zA-Z0-9_]*#', TEST_CACHE_USING_APPLICATION_STORE)) {
+ $expected = 'cachestore_'.(string)TEST_CACHE_USING_APPLICATION_STORE;
+ }
+ return $expected;
+ }
+
/**
* Tests cache configuration
*/
public function test_cache_config() {
global $CFG;
- if (!empty($CFG->altcacheconfigpath)) {
+ if (defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH &&
+ !empty($CFG->altcacheconfigpath)) {
// We need to skip this test - it checks the default config structure, but very likely we arn't using the
// default config structure here so theres no point in running the test.
$this->markTestSkipped('Skipped testing default cache config structure as alt cache path is being used.');
}
+ if (defined('TEST_CACHE_USING_APPLICATION_STORE')) {
+ // We need to skip this test - it checks the default config structure, but very likely we arn't using the
+ // default config structure here because we are testing against an alternative application store.
+ $this->markTestSkipped('Skipped testing default cache config structure as alt application store is being used.');
+ }
+
$instance = cache_config::instance();
$this->assertInstanceOf('cache_config_phpunittest', $instance);
* Test the mappingsonly setting.
*/
public function test_definition_mappings_only() {
+ /** @var cache_config_phpunittest $instance */
$instance = cache_config_phpunittest::instance(true);
$instance->phpunit_add_definition('phpunit/mappingsonly', array(
'mode' => cache_store::MODE_APPLICATION,
'component' => 'phpunit',
'area' => 'mappingsonly',
'mappingsonly' => true
- ));
+ ), false);
$instance->phpunit_add_definition('phpunit/nonmappingsonly', array(
'mode' => cache_store::MODE_APPLICATION,
'component' => 'phpunit',
'area' => 'nonmappingsonly',
'mappingsonly' => false
- ));
+ ), false);
$cacheonly = cache::make('phpunit', 'mappingsonly');
$this->assertInstanceOf('cache_application', $cacheonly);
$this->assertEquals('cachestore_dummy', $cacheonly->phpunit_get_store_class());
+ $expected = $this->get_expected_application_cache_store();
$cachenon = cache::make('phpunit', 'nonmappingsonly');
$this->assertInstanceOf('cache_application', $cachenon);
- $this->assertEquals('cachestore_file', $cachenon->phpunit_get_store_class());
+ $this->assertEquals($expected, $cachenon->phpunit_get_store_class());
}
/**
*/
public function test_alt_cache_path() {
global $CFG;
- if ($CFG->altcacheconfigpath) {
+ if ((defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH) || !empty($CFG->altcacheconfigpath)) {
$this->markTestSkipped('Skipped testing alt cache path as it is already being used.');
}
$this->resetAfterTest();
public function test_disable() {
global $CFG;
- if (!empty($CFG->altcacheconfigpath)) {
+ if ((defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') && TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH) || !empty($CFG->altcacheconfigpath)) {
// We can't run this test as it requires us to delete the cache configuration script which we just
// cant do with a custom path in play.
$this->markTestSkipped('Skipped testing cache disable functionality as alt cache path is being used.');
defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot.'/cache/locallib.php');
+
/**
* Override the default cache configuration for our own maniacle purposes.
*
*/
class cache_config_phpunittest extends cache_config_writer {
+ /**
+ * Creates the default configuration and saves it.
+ *
+ * This function calls config_save, however it is safe to continue using it afterwards as this function should only ever
+ * be called when there is no configuration file already.
+ *
+ * @param bool $forcesave If set to true then we will forcefully save the default configuration file.
+ * @return true|array Returns true if the default configuration was successfully created.
+ * Returns a configuration array if it could not be saved. This is a bad situation. Check your error logs.
+ */
+ public static function create_default_configuration($forcesave = false) {
+ global $CFG;
+ // HACK ALERT.
+ // We probably need to come up with a better way to create the default stores, or at least ensure 100% that the
+ // default store plugins are protected from deletion.
+ $writer = new self;
+ $writer->configstores = self::get_default_stores();
+ $writer->configdefinitions = self::locate_definitions();
+ $defaultapplication = 'default_application';
+
+ $appdefine = defined('TEST_CACHE_USING_APPLICATION_STORE') ? TEST_CACHE_USING_APPLICATION_STORE : false;
+ if ($appdefine !== false && preg_match('/^[a-zA-Z][a-zA-Z0-9_]+$/', $appdefine)) {
+ $expectedstore = $appdefine;
+ $expecteddefine = 'TEST_CACHESTORE_'.strtoupper($expectedstore).'_TESTSERVERS';
+ $file = $CFG->dirroot.'/cache/stores/'.$appdefine.'/lib.php';
+ $class = 'cachestore_'.$appdefine;
+ if (file_exists($file)) {
+ require_once($file);
+ }
+ if (defined($expecteddefine) && class_exists($class)) {
+ /** @var cache_store $class */
+ $writer->configstores['test_application'] = array(
+ 'use_test_store' => true,
+ 'name' => 'test_application',
+ 'plugin' => $expectedstore,
+ 'alt' => $writer->configstores[$defaultapplication],
+ 'modes' => $class::get_supported_modes(),
+ 'features' => $class::get_supported_features()
+ );
+ $defaultapplication = 'test_application';
+ }
+ }
+
+ $writer->configmodemappings = array(
+ array(
+ 'mode' => cache_store::MODE_APPLICATION,
+ 'store' => $defaultapplication,
+ 'sort' => -1
+ ),
+ array(
+ 'mode' => cache_store::MODE_SESSION,
+ 'store' => 'default_session',
+ 'sort' => -1
+ ),
+ array(
+ 'mode' => cache_store::MODE_REQUEST,
+ 'store' => 'default_request',
+ 'sort' => -1
+ )
+ );
+ $writer->configlocks = array(
+ 'default_file_lock' => array(
+ 'name' => 'cachelock_file_default',
+ 'type' => 'cachelock_file',
+ 'dir' => 'filelocks',
+ 'default' => true
+ )
+ );
+
+ $factory = cache_factory::instance();
+ // We expect the cache to be initialising presently. If its not then something has gone wrong and likely
+ // we are now in a loop.
+ if (!$forcesave && $factory->get_state() !== cache_factory::STATE_INITIALISING) {
+ return $writer->generate_configuration_array();
+ }
+ $factory->set_state(cache_factory::STATE_SAVING);
+ $writer->config_save();
+ return true;
+ }
+
/**
* Returns the expected path to the configuration file.
*
$configpath = $CFG->dataroot.'/muc/config.php';
if (!empty($CFG->altcacheconfigpath)) {
+
+ if (defined('PHPUNIT_TEST') && PHPUNIT_TEST &&
+ (!defined('TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH') || !TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH)) {
+ // We're within a unit test, but TEST_CACHE_USING_ALT_CACHE_CONFIG_PATH has not being defined or is
+ // false, we want to use the default.
+ return $configpath;
+ }
+
$path = $CFG->altcacheconfigpath;
if (is_dir($path) && is_writable($path)) {
// Its a writable directory, thats fine. Convert it to a file.
* Adds a definition to the stack
* @param string $area
* @param array $properties
+ * @param bool $addmapping By default this method adds a definition and a mapping for that definition. You can
+ * however set this to false if you only want it to add the definition and not the mapping.
*/
- public function phpunit_add_definition($area, array $properties) {
+ public function phpunit_add_definition($area, array $properties, $addmapping = true) {
if (!array_key_exists('overrideclass', $properties)) {
switch ($properties['mode']) {
case cache_store::MODE_APPLICATION:
}
}
$this->configdefinitions[$area] = $properties;
- switch ($properties['mode']) {
- case cache_store::MODE_APPLICATION:
- $this->phpunit_add_definition_mapping($area, 'default_application', 0);
- break;
- case cache_store::MODE_SESSION:
- $this->phpunit_add_definition_mapping($area, 'default_session', 0);
- break;
- case cache_store::MODE_REQUEST:
- $this->phpunit_add_definition_mapping($area, 'default_request', 0);
- break;
+ if ($addmapping) {
+ switch ($properties['mode']) {
+ case cache_store::MODE_APPLICATION:
+ $this->phpunit_add_definition_mapping($area, 'default_application', 0);
+ break;
+ case cache_store::MODE_SESSION:
+ $this->phpunit_add_definition_mapping($area, 'default_session', 0);
+ break;
+ case cache_store::MODE_REQUEST:
+ $this->phpunit_add_definition_mapping($area, 'default_request', 0);
+ break;
+ }
}
}
public static function phpunit_disable() {
parent::disable();
}
+
+ /**
+ * Creates a store instance given its name and configuration.
+ *
+ * If the store has already been instantiated then the original object will be returned. (reused)
+ *
+ * @param string $name The name of the store (must be unique remember)
+ * @param array $details
+ * @param cache_definition $definition The definition to instantiate it for.
+ * @return boolean|cache_store
+ */
+ public function create_store_from_config($name, array $details, cache_definition $definition) {
+
+ if (isset($details['use_test_store'])) {
+ // name, plugin, alt
+ $class = 'cachestore_'.$details['plugin'];
+ $method = 'initialise_unit_test_instance';
+ if (class_exists($class) && method_exists($class, $method)) {
+ $instance = $class::$method($definition);
+
+ if ($instance) {
+ return $instance;
+ }
+ }
+ $details = $details['alt'];
+ $name = $details['name'];
+ }
+
+ return parent::create_store_from_config($name, $details, $definition);
+ }
}
\ No newline at end of file