Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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