Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
   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/>.
  16  
  17  namespace cachestore_memcached;
  18  
  19  use cache_definition;
  20  use cache_store;
  21  use cachestore_memcached;
  22  
  23  defined('MOODLE_INTERNAL') || die();
  24  
  25  // Include the necessary evils.
  26  global $CFG;
  27  require_once($CFG->dirroot.'/cache/tests/fixtures/stores.php');
  28  require_once($CFG->dirroot.'/cache/stores/memcached/lib.php');
  29  
  30  /**
  31   * Memcached unit test class.
  32   *
  33   * If you wish to use these unit tests all you need to do is add the following definition to
  34   * your config.php file.
  35   *
  36   * define('TEST_CACHESTORE_MEMCACHED_TESTSERVERS', '127.0.0.1:11211');
  37   *
  38   * @package    cachestore_memcached
  39   * @copyright  2013 Sam Hemelryk
  40   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41   */
  42  class store_test extends \cachestore_tests {
  43      /**
  44       * Returns the memcached class name
  45       * @return string
  46       */
  47      protected function get_class_name() {
  48          return 'cachestore_memcached';
  49      }
  50  
  51      /**
  52       * Tests the valid keys to ensure they work.
  53       */
  54      public function test_valid_keys() {
  55          if (!cachestore_memcached::are_requirements_met() || !defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
  56              $this->markTestSkipped('Could not test cachestore_memcached. Requirements are not met.');
  57          }
  58  
  59          $this->resetAfterTest(true);
  60  
  61          $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcached', 'phpunit_test');
  62          $instance = new cachestore_memcached('Memcached Test', cachestore_memcached::unit_test_configuration());
  63  
  64          if (!$instance->is_ready()) {
  65              // Something prevented memcached store to be inited (extension, TEST_CACHESTORE_MEMCACHED_TESTSERVERS...).
  66              $this->markTestSkipped();
  67          }
  68          $instance->initialise($definition);
  69  
  70          $keys = array(
  71              // Alphanumeric.
  72              'abc', 'ABC', '123', 'aB1', '1aB',
  73              // Hyphens.
  74              'a-1', '1-a', '-a1', 'a1-',
  75              // Underscores.
  76              'a_1', '1_a', '_a1', 'a1_'
  77          );
  78  
  79          // Set some keys.
  80          foreach ($keys as $key) {
  81              $this->assertTrue($instance->set($key, $key), "Failed to set key `$key`");
  82          }
  83  
  84          // Get some keys.
  85          foreach ($keys as $key) {
  86              $this->assertEquals($key, $instance->get($key), "Failed to get key `$key`");
  87          }
  88  
  89          // Try get many.
  90          $values = $instance->get_many($keys);
  91          foreach ($values as $key => $value) {
  92              $this->assertEquals($key, $value);
  93          }
  94  
  95          // Reset a key.
  96          $this->assertTrue($instance->set($keys[0], 'New'), "Failed to reset key `$key`");
  97          $this->assertEquals('New', $instance->get($keys[0]), "Failed to get reset key `$key`");
  98  
  99          // Delete and check that we can't retrieve.
 100          foreach ($keys as $key) {
 101              $this->assertTrue($instance->delete($key), "Failed to delete key `$key`");
 102              $this->assertFalse($instance->get($key), "Retrieved deleted key `$key`");
 103          }
 104  
 105          // Try set many, and check that count is correct.
 106          $many = array();
 107          foreach ($keys as $key) {
 108              $many[] = array('key' => $key, 'value' => $key);
 109          }
 110          $returncount = $instance->set_many($many);
 111          $this->assertEquals(count($many), $returncount, 'Set many count didn\'t match');
 112  
 113          // Check keys retrieved with get_many.
 114          $values = $instance->get_many($keys);
 115          foreach ($keys as $key) {
 116              $this->assertTrue(isset($values[$key]), "Failed to get_many key `$key`");
 117              $this->assertEquals($key, $values[$key], "Failed to match get_many key `$key`");
 118          }
 119  
 120          // Delete many, make sure count matches.
 121          $returncount = $instance->delete_many($keys);
 122          $this->assertEquals(count($many), $returncount, 'Delete many count didn\'t match');
 123  
 124          // Check that each key was deleted.
 125          foreach ($keys as $key) {
 126              $this->assertFalse($instance->get($key), "Retrieved many deleted key `$key`");
 127          }
 128  
 129          // Set the keys again.
 130          $returncount = $instance->set_many($many);
 131          $this->assertEquals(count($many), $returncount, 'Set many count didn\'t match');
 132  
 133          // Purge.
 134          $this->assertTrue($instance->purge(), 'Failure to purge');
 135  
 136          // Delete and check that we can't retrieve.
 137          foreach ($keys as $key) {
 138              $this->assertFalse($instance->get($key), "Retrieved purged key `$key`");
 139          }
 140      }
 141  
 142      /**
 143       * Tests the clustering feature.
 144       */
 145      public function test_clustered() {
 146          if (!cachestore_memcached::are_requirements_met() || !defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
 147              $this->markTestSkipped('Could not test cachestore_memcached. Requirements are not met.');
 148          }
 149  
 150          $this->resetAfterTest(true);
 151  
 152          $testservers = explode("\n", trim(TEST_CACHESTORE_MEMCACHED_TESTSERVERS));
 153  
 154          if (count($testservers) < 2) {
 155              $this->markTestSkipped('Could not test clustered memcached, there are not enough test servers defined.');
 156          }
 157  
 158          // Use the first server as our primary.
 159          // We need to set a prefix for all, otherwise it uses the name, which will not match between connections.
 160          set_config('testprefix', 'pre', 'cachestore_memcached');
 161          // We need to set a name, otherwise we get a reused connection.
 162          set_config('testname', 'cluster', 'cachestore_memcached');
 163          set_config('testservers', $testservers[0], 'cachestore_memcached');
 164          set_config('testsetservers', TEST_CACHESTORE_MEMCACHED_TESTSERVERS, 'cachestore_memcached');
 165          set_config('testclustered', true, 'cachestore_memcached');
 166  
 167          // First and instance that we can use to test the second server.
 168          $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcached', 'phpunit_test');
 169          $instance = cachestore_memcached::initialise_test_instance($definition);
 170  
 171          if (!$instance->is_ready()) {
 172              $this->markTestSkipped();
 173          }
 174  
 175          // Now we are going to setup a connection to each independent server.
 176          set_config('testclustered', false, 'cachestore_memcached');
 177          set_config('testsetservers', '', 'cachestore_memcached');
 178          $checkinstances = array();
 179          foreach ($testservers as $testserver) {
 180              // We need to set a name, otherwise we get a reused connection.
 181              set_config('testname', $testserver, 'cachestore_memcached');
 182              set_config('testservers', $testserver, 'cachestore_memcached');
 183              $checkinstance = cachestore_memcached::initialise_test_instance($definition);
 184              if (!$checkinstance->is_ready()) {
 185                  $this->markTestSkipped();
 186              }
 187              $checkinstances[] = $checkinstance;
 188          }
 189  
 190          $keys = array(
 191              // Alphanumeric.
 192              'abc', 'ABC', '123', 'aB1', '1aB',
 193              // Hyphens.
 194              'a-1', '1-a', '-a1', 'a1-',
 195              // Underscores.
 196              'a_1', '1_a', '_a1', 'a1_'
 197          );
 198  
 199          // Set each key.
 200          foreach ($keys as $key) {
 201              $this->assertTrue($instance->set($key, $key), "Failed to set key `$key`");
 202          }
 203  
 204          // Check each key.
 205          foreach ($keys as $key) {
 206              $this->assertEquals($key, $instance->get($key), "Failed to get key `$key`");
 207              foreach ($checkinstances as $id => $checkinstance) {
 208                  $this->assertEquals($key, $checkinstance->get($key), "Failed to get key `$key` from server $id");
 209              }
 210          }
 211  
 212          // Reset a key.
 213          $this->assertTrue($instance->set($keys[0], 'New'), "Failed to reset key `$key`");
 214          $this->assertEquals('New', $instance->get($keys[0]), "Failed to get reset key `$key`");
 215          foreach ($checkinstances as $id => $checkinstance) {
 216              $this->assertEquals('New', $checkinstance->get($keys[0]), "Failed to get reset key `$key` from server $id");
 217          }
 218  
 219          // Delete and check that we can't retrieve.
 220          foreach ($keys as $key) {
 221              $this->assertTrue($instance->delete($key), "Failed to delete key `$key`");
 222              $this->assertFalse($instance->get($key), "Retrieved deleted key `$key`");
 223              foreach ($checkinstances as $id => $checkinstance) {
 224                  $this->assertFalse($checkinstance->get($key), "Retrieved deleted key `$key` from server $id");
 225              }
 226          }
 227  
 228          // Try set many, and check that count is correct.
 229          $many = array();
 230          foreach ($keys as $key) {
 231              $many[] = array('key' => $key, 'value' => $key);
 232          }
 233          $returncount = $instance->set_many($many);
 234          $this->assertEquals(count($many), $returncount, 'Set many count didn\'t match');
 235  
 236          // Check keys retrieved with get_many.
 237          $values = $instance->get_many($keys);
 238          foreach ($keys as $key) {
 239              $this->assertTrue(isset($values[$key]), "Failed to get_many key `$key`");
 240              $this->assertEquals($key, $values[$key], "Failed to match get_many key `$key`");
 241          }
 242          foreach ($checkinstances as $id => $checkinstance) {
 243              $values = $checkinstance->get_many($keys);
 244              foreach ($keys as $key) {
 245                  $this->assertTrue(isset($values[$key]), "Failed to get_many key `$key` from server $id");
 246                  $this->assertEquals($key, $values[$key], "Failed to get_many key `$key` from server $id");
 247              }
 248          }
 249  
 250          // Delete many, make sure count matches.
 251          $returncount = $instance->delete_many($keys);
 252          $this->assertEquals(count($many), $returncount, 'Delete many count didn\'t match');
 253  
 254          // Check that each key was deleted.
 255          foreach ($keys as $key) {
 256              $this->assertFalse($instance->get($key), "Retrieved many deleted key `$key`");
 257              foreach ($checkinstances as $id => $checkinstance) {
 258                  $this->assertFalse($checkinstance->get($key), "Retrieved many deleted key `$key` from server $id");
 259              }
 260          }
 261  
 262          // Set the keys again.
 263          $returncount = $instance->set_many($many);
 264          $this->assertEquals(count($many), $returncount, 'Set many count didn\'t match');
 265  
 266          // Purge.
 267          $this->assertTrue($instance->purge(), 'Failure to purge');
 268  
 269          // Delete and check that we can't retrieve.
 270          foreach ($keys as $key) {
 271              $this->assertFalse($instance->get($key), "Retrieved purged key `$key`");
 272              foreach ($checkinstances as $id => $checkinstance) {
 273                  $this->assertFalse($checkinstance->get($key), "Retrieved purged key `$key` from server 2");
 274              }
 275          }
 276      }
 277  
 278      /**
 279       * Tests that memcached cache store doesn't just flush everything and instead deletes only what belongs to it
 280       * when it is marked as a shared cache.
 281       */
 282      public function test_multi_use_compatibility() {
 283          if (!cachestore_memcached::are_requirements_met() || !defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
 284              $this->markTestSkipped('Could not test cachestore_memcached. Requirements are not met.');
 285          }
 286  
 287          $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcached', 'phpunit_test');
 288          $cachestore = $this->create_test_cache_with_config($definition, array('isshared' => true));
 289          if (!$cachestore->is_connection_ready()) {
 290              $this->markTestSkipped('Could not test cachestore_memcached. Connection is not ready.');
 291          }
 292  
 293          $connection = new \Memcached(crc32(__METHOD__));
 294          $connection->addServers($this->get_servers(TEST_CACHESTORE_MEMCACHED_TESTSERVERS));
 295          $connection->setOptions(array(
 296              \Memcached::OPT_COMPRESSION => true,
 297              \Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP,
 298              \Memcached::OPT_PREFIX_KEY => 'phpunit_',
 299              \Memcached::OPT_BUFFER_WRITES => false
 300          ));
 301  
 302          // We must flush first to make sure nothing is there.
 303          $connection->flush();
 304  
 305          // Test the cachestore.
 306          $this->assertFalse($cachestore->get('test'));
 307          $this->assertTrue($cachestore->set('test', 'cachestore'));
 308          $this->assertSame('cachestore', $cachestore->get('test'));
 309  
 310          // Test the connection.
 311          $this->assertFalse($connection->get('test'));
 312          $this->assertEquals(\Memcached::RES_NOTFOUND, $connection->getResultCode());
 313          $this->assertTrue($connection->set('test', 'connection'));
 314          $this->assertSame('connection', $connection->get('test'));
 315  
 316          // Test both again and make sure the values are correct.
 317          $this->assertSame('cachestore', $cachestore->get('test'));
 318          $this->assertSame('connection', $connection->get('test'));
 319  
 320          // Purge the cachestore and check the connection was not purged.
 321          $this->assertTrue($cachestore->purge());
 322          $this->assertFalse($cachestore->get('test'));
 323          $this->assertSame('connection', $connection->get('test'));
 324      }
 325  
 326      /**
 327       * Tests that memcached cache store flushes entire cache when it is using a dedicated cache.
 328       */
 329      public function test_dedicated_cache() {
 330          if (!cachestore_memcached::are_requirements_met() || !defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
 331              $this->markTestSkipped('Could not test cachestore_memcached. Requirements are not met.');
 332          }
 333  
 334          $definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_memcached', 'phpunit_test');
 335          $cachestore = $this->create_test_cache_with_config($definition, array('isshared' => false));
 336          $connection = new \Memcached(crc32(__METHOD__));
 337          $connection->addServers($this->get_servers(TEST_CACHESTORE_MEMCACHED_TESTSERVERS));
 338          $connection->setOptions(array(
 339              \Memcached::OPT_COMPRESSION => true,
 340              \Memcached::OPT_SERIALIZER => \Memcached::SERIALIZER_PHP,
 341              \Memcached::OPT_PREFIX_KEY => 'phpunit_',
 342              \Memcached::OPT_BUFFER_WRITES => false
 343          ));
 344  
 345          // We must flush first to make sure nothing is there.
 346          $connection->flush();
 347  
 348          // Test the cachestore.
 349          $this->assertFalse($cachestore->get('test'));
 350          $this->assertTrue($cachestore->set('test', 'cachestore'));
 351          $this->assertSame('cachestore', $cachestore->get('test'));
 352  
 353          // Test the connection.
 354          $this->assertFalse($connection->get('test'));
 355          $this->assertEquals(\Memcached::RES_NOTFOUND, $connection->getResultCode());
 356          $this->assertTrue($connection->set('test', 'connection'));
 357          $this->assertSame('connection', $connection->get('test'));
 358  
 359          // Test both again and make sure the values are correct.
 360          $this->assertSame('cachestore', $cachestore->get('test'));
 361          $this->assertSame('connection', $connection->get('test'));
 362  
 363          // Purge the cachestore and check the connection was also purged.
 364          $this->assertTrue($cachestore->purge());
 365          $this->assertFalse($cachestore->get('test'));
 366          $this->assertFalse($connection->get('test'));
 367      }
 368  
 369      /**
 370       * Given a server string this returns an array of servers.
 371       *
 372       * @param string $serverstring
 373       * @return array
 374       */
 375      public function get_servers($serverstring) {
 376          $servers = array();
 377          foreach (explode("\n", $serverstring) as $server) {
 378              if (!is_array($server)) {
 379                  $server = explode(':', $server, 3);
 380              }
 381              if (!array_key_exists(1, $server)) {
 382                  $server[1] = 11211;
 383                  $server[2] = 100;
 384              } else if (!array_key_exists(2, $server)) {
 385                  $server[2] = 100;
 386              }
 387              $servers[] = $server;
 388          }
 389          return $servers;
 390      }
 391  
 392      /**
 393       * Creates a test instance for unit tests.
 394       * @param cache_definition $definition
 395       * @param array $configuration
 396       * @return null|cachestore_memcached
 397       */
 398      private function create_test_cache_with_config(cache_definition $definition, $configuration = array()) {
 399          $class = $this->get_class_name();
 400  
 401          if (!$class::are_requirements_met()) {
 402              return null;
 403          }
 404          if (!defined('TEST_CACHESTORE_MEMCACHED_TESTSERVERS')) {
 405              return null;
 406          }
 407  
 408          $configuration['servers'] = explode("\n", TEST_CACHESTORE_MEMCACHED_TESTSERVERS);
 409  
 410          $store = new $class('Test memcached', $configuration);
 411          $store->initialise($definition);
 412  
 413          return $store;
 414      }
 415  }