You are here

memcache.test in Memcache API and Integration 7

Same filename and directory in other branches
  1. 6 tests/memcache.test

Test cases for the memcache cache backend.

File

tests/memcache.test
View source
<?php

/**
 * @file
 * Test cases for the memcache cache backend.
 */
class MemcacheTestCase extends DrupalWebTestCase {
  protected $profile = 'testing';
  protected $default_bin = 'cache_memcache';
  protected $default_cid = 'test_temporary';
  protected $default_value = 'MemcacheTest';

  /**
   * Re-implements DrupalWebTestCase::setUp() so that we can override $conf.
   *
   * @see DrupalWebTestCase::setUp()
   */
  public function setUp() {
    global $user, $language, $conf;

    // Create the database prefix for this test.
    $this
      ->prepareDatabasePrefix();

    // Prepare the environment for running tests.
    $this
      ->prepareEnvironment();
    if (!$this->setupEnvironment) {
      return FALSE;
    }

    // Reset all statics and variables to perform tests in a clean environment.
    $conf = array();
    drupal_static_reset();

    // Setup our own memcache variables here. We can't use variable_set() yet.
    if ($this->default_bin) {
      $conf["cache_flush_{$this->default_bin}"] = 0;
      $conf["cache_class_{$this->default_bin}"] = 'MemcacheDrupal';
    }

    // Change the database prefix.
    // All static variables need to be reset before the database prefix is
    // changed, since DrupalCacheArray implementations attempt to
    // write back to persistent caches when they are destructed.
    $this
      ->changeDatabasePrefix();
    if (!$this->setupDatabasePrefix) {
      return FALSE;
    }

    // Preset the 'install_profile' system variable, so the first call into
    // system_rebuild_module_data() (in drupal_install_system()) will register
    // the test's profile as a module. Without this, the installation profile of
    // the parent site (executing the test) is registered, and the test
    // profile's hook_install() and other hook implementations are never
    // invoked.
    $conf['install_profile'] = $this->profile;

    // Perform the actual Drupal installation.
    include_once DRUPAL_ROOT . '/includes/install.inc';
    drupal_install_system();
    $this
      ->preloadRegistry();

    // Set path variables.
    variable_set('file_public_path', $this->public_files_directory);
    variable_set('file_private_path', $this->private_files_directory);
    variable_set('file_temporary_path', $this->temp_files_directory);

    // Set the 'simpletest_parent_profile' variable to add the parent profile's
    // search path to the child site's search paths.
    // @see drupal_system_listing()
    // @todo This may need to be primed like 'install_profile' above.
    variable_set('simpletest_parent_profile', $this->originalProfile);

    // Include the testing profile.
    variable_set('install_profile', $this->profile);
    $profile_details = install_profile_info($this->profile, 'en');

    // Install the modules specified by the testing profile.
    module_enable($profile_details['dependencies'], FALSE);

    // Install modules needed for this test. This could have been passed in as
    // either a single array argument or a variable number of string arguments.
    // @todo Remove this compatibility layer in Drupal 8, and only accept
    // $modules as a single array argument.
    $modules = func_get_args();
    if (isset($modules[0]) && is_array($modules[0])) {
      $modules = $modules[0];
    }
    if ($modules) {
      $success = module_enable($modules, TRUE);
      $this
        ->assertTrue($success, t('Enabled modules: %modules', array(
        '%modules' => implode(', ', $modules),
      )));
    }

    // Run the profile tasks.
    $install_profile_module_exists = db_query("SELECT 1 FROM {system} WHERE type = 'module' AND name = :name", array(
      ':name' => $this->profile,
    ))
      ->fetchField();
    if ($install_profile_module_exists) {
      module_enable(array(
        $this->profile,
      ), FALSE);
    }

    // Reset/rebuild all data structures after enabling the modules.
    $this
      ->resetAll();

    // Run cron once in that environment, as install.php does at the end of
    // the installation process.
    drupal_cron_run();

    // Ensure that the session is not written to the new environment and replace
    // the global $user session with uid 1 from the new test site.
    drupal_save_session(FALSE);

    // Login as uid 1.
    $user = user_load(1);

    // Restore necessary variables.
    variable_set('install_task', 'done');
    variable_set('clean_url', $this->originalCleanUrl);
    variable_set('site_mail', 'simpletest@example.com');
    variable_set('date_default_timezone', date_default_timezone_get());

    // Set up English language.
    unset($conf['language_default']);
    $language = language_default();

    // Use the test mail class instead of the default mail handler class.
    variable_set('mail_system', array(
      'default-system' => 'TestingMailSystem',
    ));
    drupal_set_time_limit($this->timeLimit);
    $this->setup = TRUE;
    if ($this->default_bin) {

      // Save our memcache variables.
      variable_set("cache_flush_{$this->default_bin}", 0);
      variable_set("cache_class_{$this->default_bin}", 'MemcacheDrupal');
    }
    $this
      ->resetVariables();
  }

  /**
   * Test that memcache is configured correctly.
   */
  public function testCacheBin() {
    if ($this->default_bin) {

      // Confirm that the default cache bin is handled by memcache.
      $this
        ->assertEqual(get_class(_cache_get_object($this->default_bin)), 'MemCacheDrupal', t('Memcache caching is configured correctly.'));
    }
  }

  /**
   * Check whether or not a cache entry exists.
   *
   * @param string $cid
   *   The cache id.
   * @param mixed $var
   *   The variable the cache should contain.
   * @param string $bin
   *   Defaults to $this->default_bin. The bin the cache item was stored in.
   *
   * @return bool
   *   TRUE on pass, FALSE on fail.
   */
  protected function checkCacheExists($cid, $var, $bin = NULL) {
    if ($bin == NULL) {
      $bin = $this->default_bin;
    }
    $cache = cache_get($cid, $bin);
    return isset($cache->data) && $cache->data == $var;
  }

  /**
   * Assert or a cache entry exists.
   *
   * @param string $message
   *   Message to display.
   * @param mixed $var
   *   Defaults to $this->default_value. The variable the cache should contain.
   * @param string $cid
   *   Defaults to $this->default_cid. The cache id.
   * @param string $bin
   *   Defaults to $this->default_bin. The bin the cache item was stored in.
   */
  protected function assertCacheExists($message, $var = NULL, $cid = NULL, $bin = NULL) {
    if ($bin == NULL) {
      $bin = $this->default_bin;
    }
    if ($cid == NULL) {
      $cid = $this->default_cid;
    }
    if ($var == NULL) {
      $var = $this->default_value;
    }
    $this
      ->assertTrue($this
      ->checkCacheExists($cid, $var, $bin), $message);
  }

  /**
   * Assert or a cache entry has been removed.
   *
   * @param string $message
   *   Message to display.
   * @param string $cid
   *   Defaults to $this->default_cid. The cache id.
   * @param string $bin
   *   Defaults to $this->default_bin. The bin the cache item was stored in.
   */
  public function assertCacheRemoved($message, $cid = NULL, $bin = NULL) {
    if ($bin == NULL) {
      $bin = $this->default_bin;
    }
    if ($cid == NULL) {
      $cid = $this->default_cid;
    }
    $cache = cache_get($cid, $bin);
    $this
      ->assertFalse($cache, $message);
  }

  /**
   * Perform the general wipe.
   *
   * @param string $bin
   *   Defaults to $this->default_bin. The bin to perform the wipe on.
   */
  protected function generalWipe($bin = NULL) {
    if ($bin == NULL) {
      $bin = $this->default_bin;
    }
    cache_clear_all(NULL, $bin);
  }

  /**
   * Reloads internal MemCacheDrupal variables.
   */
  protected function resetVariables() {
    if ($this->default_bin) {
      $_SESSION['cache_flush'][$this->default_bin] = 0;
      $cache = _cache_get_object($this->default_bin);
      if ($cache instanceof MemCacheDrupal) {
        $cache
          ->reloadVariables();
      }
    }
  }

}
class MemCacheSavingCase extends MemcacheTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Memcache saving test',
      'description' => 'Check our variables are saved and restored the right way.',
      'group' => 'Memcache',
    );
  }

  /**
   * @see MemcacheTestCase::setUp()
   */
  public function setUp() {
    parent::setUp();
  }

  /**
   * Test the saving and restoring of a string.
   */
  public function testString() {
    $this
      ->checkVariable($this
      ->randomName(100));
  }

  /**
   * Test the saving and restoring of an integer.
   */
  public function testInteger() {
    $this
      ->checkVariable(100);
  }

  /**
   * Test the saving and restoring of a double.
   */
  public function testDouble() {
    $this
      ->checkVariable(1.29);
  }

  /**
   * Test the saving and restoring of an array.
   */
  public function testArray() {
    $this
      ->checkVariable(array(
      'drupal1',
      'drupal2' => 'drupal3',
      'drupal4' => array(
        'drupal5',
        'drupal6',
      ),
    ));
  }

  /**
   * Test the saving and restoring of an object.
   */
  public function testObject() {
    $test_object = new stdClass();
    $test_object->test1 = $this
      ->randomName(100);
    $test_object->test2 = 100;
    $test_object->test3 = array(
      'drupal1',
      'drupal2' => 'drupal3',
      'drupal4' => array(
        'drupal5',
        'drupal6',
      ),
    );
    cache_set('test_object', $test_object, $this->default_bin);
    $cache = cache_get('test_object', $this->default_bin);
    $this
      ->assertTrue(isset($cache->data) && $cache->data == $test_object, t('Object is saved and restored properly.'));
  }

  /**
   * Test save and restoring a string with a long key.
   */
  public function testStringLongKey() {
    $this
      ->checkVariable($this
      ->randomName(100), 'ThequickbrownfoxjumpsoverthelazydogThequickbrownfoxjumpsoverthelazydogThequickbrownfoxjumpsoverthelazydogThequickbrownfoxjumpsoverthelazydogThequickbrownfoxjumpsoverthelazydogThequickbrownfoxjumpsoverthelazydogThequickbrownfoxjumpsoverthelazydogThequickbrownfoxjumpsoverthelazydog');
  }

  /**
   * Test save and restoring a string using a key with special characters.
   */
  public function testStringSpecialKey() {
    $this
      ->checkVariable($this
      ->randomName(100), 'Qwerty!@#$%^&*()_+-=[]\\;\',./<>?:"{}|£¢');
  }

  /**
   * Test saving and restoring an integer value directly with dmemcache_set().
   */
  function testIntegerValue() {
    $key = $this
      ->randomName(100);
    $val = rand(1, 1000);
    dmemcache_set($key, $val, 0, 'cache');
    $cache = dmemcache_get($key, 'cache');
    $this
      ->assertTrue($val === $cache, t('Integer is saved and restored properly with key @key', array(
      '@key' => $key,
    )));
  }

  /**
   * Test saving and restoring a very large value (>1MiB).
   */
  function testLargeValue() {
    $this
      ->checkVariable(array_fill(0, 500000, rand()));
  }

  /**
   * Test save and restoring a string with a long key and a very large value.
   */
  function testLongKeyLargeValue() {
    $this
      ->checkVariable(array_fill(0, 500000, rand()), $this
      ->randomName(300));
  }

  /**
   * Check or a variable is stored and restored properly.
   */
  public function checkVariable($var, $key = 'test_var') {
    cache_set($key, $var, $this->default_bin);
    $cache = cache_get($key, $this->default_bin);
    $this
      ->assertTrue(isset($cache->data) && $cache->data === $var, t('@type is saved and restored properly!key.', array(
      '@type' => ucfirst(gettype($var)),
      '!key' => $key != 'test_var' ? t(' with key @key', array(
        '@key' => $key,
      )) : '',
    )));
  }

}

/**
 * Test cache_get_multiple().
 */
class MemCacheGetMultipleUnitTest extends MemcacheTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Fetching multiple cache items',
      'description' => 'Confirm that multiple records are fetched correctly.',
      'group' => 'Memcache',
    );
  }

  /**
   * @see MemcacheTestCase::setUp()
   */
  function setUp() {
    parent::setUp();
  }

  /**
   * Test cache_get_multiple().
   */
  public function testCacheMultiple() {
    $item1 = $this
      ->randomName(10);
    $item2 = $this
      ->randomName(10);
    cache_set('test:item1', $item1, $this->default_bin);
    cache_set('test:item2', $item2, $this->default_bin);
    $this
      ->assertTrue($this
      ->checkCacheExists('test:item1', $item1), t('Item 1 is cached.'));
    $this
      ->assertTrue($this
      ->checkCacheExists('test:item2', $item2), t('Item 2 is cached.'));

    // Fetch both records from the database with cache_get_multiple().
    $item_ids = array(
      'test:item1',
      'test:item2',
    );
    $items = cache_get_multiple($item_ids, $this->default_bin);
    $this
      ->assertEqual($items['test:item1']->data, $item1, t('Item was returned from cache successfully.'));
    $this
      ->assertEqual($items['test:item2']->data, $item2, t('Item was returned from cache successfully.'));
    $this
      ->assertTrue(empty($item_ids), t('Ids of returned items have been removed.'));

    // Remove one item from the cache.
    cache_clear_all('test:item2', $this->default_bin);

    // Confirm that only one item is returned by cache_get_multiple().
    $item_ids = array(
      'test:item1',
      'test:item2',
    );
    $items = cache_get_multiple($item_ids, $this->default_bin);
    $this
      ->assertEqual($items['test:item1']->data, $item1, t('Item was returned from cache successfully.'));
    $this
      ->assertFalse(isset($items['test:item2']), t('Item was not returned from the cache.'));
    $this
      ->assertTrue(count($items) == 1, t('Only valid cache entries returned.'));
    $this
      ->assertTrue(count($item_ids) == 1, t('Invalid cache ids still present.'));
  }

}

/**
 * Test cache clearing methods.
 */
class MemCacheClearCase extends MemcacheTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Cache clear test',
      'description' => 'Check our clearing is done the proper way.',
      'group' => 'Memcache',
    );
  }

  /**
   * @see MemcacheTestCase::setUp()
   */
  public function setUp() {
    parent::setUp('memcache_test');
    $this->default_value = $this
      ->randomName(10);
  }

  /**
   * Test clearing the cache with a cid, no cache lifetime.
   */
  public function testClearCidNoLifetime() {
    $this
      ->clearCidTest();
  }

  /**
   * Test clearing the cache with a cid, with cache lifetime.
   */
  public function testClearCidLifetime() {
    variable_set('cache_lifetime', 6000);
    $this
      ->clearCidTest();
  }

  /**
   * Test clearing using wildcard prefixes, no cache lifetime.
   */
  public function testClearWildcardNoLifetime() {
    $this
      ->clearWildcardPrefixTest();
  }

  /**
   * Test clearing using wildcard prefix, with cache lifetime.
   */
  public function testClearWildcardLifetime() {
    variable_set('cache_lifetime', 6000);
    $this
      ->clearWildcardPrefixTest();
  }

  /**
   * Test full bin flushes with no cache lifetime.
   */
  public function testClearWildcardFull() {
    cache_set('test_cid_clear1', $this->default_value, $this->default_bin);
    cache_set('test_cid_clear2', $this->default_value, $this->default_bin);
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid_clear1', $this->default_value) && $this
      ->checkCacheExists('test_cid_clear2', $this->default_value), t('Two caches were created for checking cid "*" with wildcard true.'));
    cache_clear_all('*', $this->default_bin, TRUE);
    $this
      ->assertFalse($this
      ->checkCacheExists('test_cid_clear1', $this->default_value) || $this
      ->checkCacheExists('test_cid_clear2', $this->default_value), t('Two caches removed after clearing cid "*" with wildcard true.'));
  }

  /**
   * Test full bin flushes with cache lifetime.
   */
  public function testClearCacheLifetime() {
    variable_set('cache_lifetime', 600);
    $this
      ->resetVariables();

    // Set a cache item with an expiry.
    cache_set('test_cid', $this->default_value, $this->default_bin, time() + 3600);
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid', $this->default_value), 'Cache item was created successfully.');

    // Set a permanent cache item.
    cache_set('test_cid_2', $this->default_value, $this->default_bin);

    // Clear the page and block caches.
    cache_clear_all(MEMCACHE_CONTENT_CLEAR, $this->default_bin);

    // Since the cache was cleared within the current session, cache_get()
    // should return false.
    $this
      ->assertFalse($this
      ->checkCacheExists('test_cid', $this->default_value), 'Cache item was cleared successfully.');

    // However permament items should stay in place.
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid_2', $this->default_value), 'Cache item was not cleared');

    // If $_SESSION['cache_flush'] is not set, then the expired item should
    // be returned.
    unset($_SESSION['cache_flush']);
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid', $this->default_value), 'Cache item is still returned due to minimum cache lifetime.');

    // Set a much shorter cache lifetime.
    variable_set('cache_content_flush_' . $this->default_bin, 0);
    variable_set('cache_lifetime', 1);
    cache_set('test_cid', $this->default_value, $this->default_bin, time() + 6000);
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid', $this->default_value), 'Cache item was created successfully.');
    cache_clear_all(MEMCACHE_CONTENT_CLEAR, $this->default_bin);
    $this
      ->assertFalse($this
      ->checkCacheExists('test_cid', $this->default_value), 'Cache item is not returned once minimum cache lifetime has expired.');

    // Reset the cache clear variables.
    variable_set('cache_content_flush_' . $this->default_bin, 0);
    variable_set('cache_lifetime', 6000);
    $this
      ->resetVariables();

    // Confirm that cache_lifetime does not take effect for full bin flushes.
    cache_set('test_cid', $this->default_value, $this->default_bin, time() + 6000);
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid', $this->default_value), 'Cache item was created successfully.');
    cache_set('test_cid_2', $this->default_value, $this->default_bin);
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid_2', $this->default_value), 'Cache item was created successfully.');

    // Now flush the bin.
    cache_clear_all('*', $this->default_bin, TRUE);
    $this
      ->assertFalse($this
      ->checkCacheExists('test_cid', $this->default_value), 'Cache item was cleared successfully.');
    $this
      ->assertFalse($this
      ->checkCacheExists('test_cid_2', $this->default_value), 'Cache item was cleared successfully.');
  }

  /**
   * Test different wildcards to verify the wildcard optimizations.
   */
  public function testWildCardOptimizations() {

    // Set and clear a cache with a short cid/wildcard.
    cache_set('foo:1', $this->default_value, $this->default_bin);
    $this
      ->assertCacheExists(t('Foo cache was set.'), $this->default_value, 'foo:1');
    cache_clear_all('foo', $this->default_bin, TRUE);
    $this
      ->assertCacheRemoved(t('Foo cache was invalidated.'), 'foo:1');

    // Set additional longer caches.
    cache_set('foobar', $this->default_value, $this->default_bin);
    cache_set('foofoo', $this->default_value, $this->default_bin);
    $this
      ->assertCacheExists(t('Foobar cache set.'), $this->default_value, 'foobar');
    $this
      ->assertCacheExists(t('Foofoo cache set.'), $this->default_value, 'foofoo');

    // Clear one of them with a wildcard and make sure the other one is still
    // valid.
    cache_clear_all('foobar', $this->default_bin, TRUE);
    $this
      ->assertCacheRemoved(t('Foobar cache invalidated.'), 'foobar');
    $this
      ->assertCacheExists(t('Foofoo cache still valid.'), $this->default_value, 'foofoo');

    // Set and clear a cache with a different, equally short cid/wildcard.
    cache_set('bar:1', $this->default_value, $this->default_bin);
    $this
      ->assertCacheExists(t('Bar cache was set.'), $this->default_value, 'bar:1');
    cache_clear_all('bar', $this->default_bin, TRUE);
    $this
      ->assertCacheRemoved(t('Bar cache invalidated.'), 'bar:1');
    $this
      ->assertCacheExists(t('Foofoo cache still valid.'), $this->default_value, 'foofoo');

    // Clear cache with an even shorter wildcard. This results in a full bin
    // bin clear, all entries are marked invalid.
    cache_set('bar:2', $this->default_value, $this->default_bin);
    cache_clear_all('ba', $this->default_bin, TRUE);
    $this
      ->assertCacheRemoved(t('Bar:1 cache invalidated.'), 'bar:1');
    $this
      ->assertCacheRemoved(t('Bar:2 cache invalidated.'), 'bar:2');
    $this
      ->assertCacheRemoved(t('Foofoo cache invalidated.'), 'foofoo');
  }

  /**
   * Test CACHE_TEMPORARY and CACHE_PERMANENT behaviour.
   */
  public function testClearTemporaryPermanent() {
    cache_set('test_cid_clear_temporary', $this->default_value, $this->default_bin, CACHE_TEMPORARY);
    cache_set('test_cid_clear_permanent', $this->default_value, $this->default_bin, CACHE_PERMANENT);
    cache_set('test_cid_clear_future', $this->default_value, $this->default_bin, time() + 3600);
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid_clear_temporary', $this->default_value) && $this
      ->checkCacheExists('test_cid_clear_permanent', $this->default_value) && $this
      ->checkCacheExists('test_cid_clear_future', $this->default_value), t('Three cache items were created for checking cache expiry.'));

    // This should clear only expirable items (CACHE_TEMPORARY).
    cache_clear_all(NULL, $this->default_bin, TRUE);
    $this
      ->assertFalse($this
      ->checkCacheExists('test_cid_clear_temporary', $this->default_value), t('Temporary cache item was removed after clearing cid NULL.'));
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid_clear_permanent', $this->default_value), t('Permanent cache item was not removed after clearing cid NULL.'));
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid_clear_future', $this->default_value), t('Future cache item was not removed after clearing cid NULL.'));
  }

  /**
   * Test clearing using a cid.
   */
  public function clearCidTest() {
    cache_set('test_cid_clear', $this->default_value, $this->default_bin);
    $this
      ->assertCacheExists(t('Cache was set for clearing cid.'), $this->default_value, 'test_cid_clear');
    cache_clear_all('test_cid_clear', $this->default_bin);
    $this
      ->assertCacheRemoved(t('Cache was removed after clearing cid.'), 'test_cid_clear');
    cache_set('test_cid_clear1', $this->default_value, $this->default_bin);
    cache_set('test_cid_clear2', $this->default_value, $this->default_bin);
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid_clear1', $this->default_value) && $this
      ->checkCacheExists('test_cid_clear2', $this->default_value), t('Two caches were created for checking cid "*" with wildcard false.'));
    cache_clear_all('*', $this->default_bin);
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid_clear1', $this->default_value) && $this
      ->checkCacheExists('test_cid_clear2', $this->default_value), t('Two caches still exists after clearing cid "*" with wildcard false.'));
  }

  /**
   * Test cache clears using wildcard prefixes.
   */
  public function clearWildcardPrefixTest() {
    $this
      ->resetVariables();
    cache_set('test_cid_clear:1', $this->default_value, $this->default_bin);
    cache_set('test_cid_clear:2', $this->default_value, $this->default_bin);
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid_clear:1', $this->default_value) && $this
      ->checkCacheExists('test_cid_clear:2', $this->default_value), t('Two caches were created for checking cid substring with wildcard true.'));
    cache_clear_all('test_cid_clear:', $this->default_bin, TRUE);
    $this
      ->assertFalse($this
      ->checkCacheExists('test_cid_clear:1', $this->default_value) || $this
      ->checkCacheExists('test_cid_clear:2', $this->default_value), t('Two caches removed after clearing cid substring with wildcard true.'));

    // Test for the case where a wildcard object disappears, for example a
    // partial memcache restart or eviction.
    cache_set('test_cid_clear:1', $this->default_value, $this->default_bin);
    $this
      ->assertTrue($this
      ->checkCacheExists('test_cid_clear:1', $this->default_value), 'The cache was created successfully.');
    cache_clear_all('test_', $this->default_bin, TRUE);
    $this
      ->assertFalse($this
      ->checkCacheExists('test_cid_clear:1', $this->default_value), 'The cache was cleared successfully.');

    // Delete the wildcard manually to simulate an eviction.
    $wildcard = '.wildcard-test_cid_clear:';
    dmemcache_delete($wildcard, $this->default_bin);

    // Reset the memcache_wildcards() static cache.
    // @todo: this is a class object in D7.
    // memcache_wildcards(FALSE, FALSE, FALSE, TRUE);
    $this
      ->assertFalse($this
      ->checkCacheExists('test_cid_clear:1', $this->default_value), 'The cache was cleared successfully.');
  }

  /**
   * Test wildcard flushing on separate pages to ensure no static cache is used.
   */
  public function testClearWildcardOnSeparatePages() {
    $random_wildcard = $this
      ->randomName(2) . ':' . $this
      ->randomName(3);
    $random_key = $random_wildcard . ':' . $this
      ->randomName(4) . ':' . $this
      ->randomName(2);
    $random_value = $this
      ->randomName();
    $this
      ->drupalGetAJAX('memcache-test/clear-cache');
    $data = $this
      ->drupalGetAJAX('memcache-test/set/' . $random_key . '/' . $random_value);
    $this
      ->assertTrue(is_array($data), 'Cache has data.');
    $this
      ->assertEqual($random_key, $data['cid'], 'Cache keys match.');
    $this
      ->assertEqual($random_value, $data['data'], 'Cache values match.');
    $data = $this
      ->drupalGetAJAX('memcache-test/get/' . $random_key);
    $this
      ->assertEqual($random_key, $data['cid'], 'Cache keys match.');
    $this
      ->assertEqual($random_value, $data['data'], 'Cache values match.');
    $this
      ->drupalGet('memcache-test/clear/' . $random_key);
    $data = $this
      ->drupalGetAJAX('memcache-test/get/' . $random_key);
    $this
      ->assertFalse($data, 'Cache value at specific key was properly flushed.');
    $data = $this
      ->drupalGetAJAX('memcache-test/set/' . $random_key . '/' . $random_value);
    $this
      ->assertTrue(is_array($data), 'Cache has data.');
    $this
      ->assertEqual($random_key, $data['cid'], 'Cache keys match.');
    $this
      ->assertEqual($random_value, $data['data'], 'Cache values match.');
    $data = $this
      ->drupalGetAJAX('memcache-test/get/' . $random_key);
    $this
      ->assertEqual($random_key, $data['cid'], 'Cache keys match.');
    $this
      ->assertEqual($random_value, $data['data'], 'Cache values match.');
    $this
      ->drupalGet('memcache-test/wildcard-clear/' . $random_wildcard);
    $data = $this
      ->drupalGetAJAX('memcache-test/get/' . $random_key);
    $this
      ->assertFalse($data, 'Cache was properly flushed.');
  }

}

/**
 * Tests memcache stampede protection.
 */
class MemCacheStampedeProtection extends MemcacheTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Memcache stampede protection',
      'description' => 'Tests memcache stampede protection.',
      'group' => 'Memcache',
    );
  }

  /**
   * Tests the opt out functionality of stampede protection using a unit test.
   */
  public function testStampedeProtectionIgnoringUnit() {
    global $conf;

    // Setup a new test bin, to be able to override the used class.
    $conf['cache_flush_test_bin'] = 0;
    $conf['cache_class_test_bin'] = 'MockMemCacheDrupal';
    $conf['cache_flush_test_bin_2'] = 0;
    $conf['cache_class_test_bin_2'] = 'MockMemCacheDrupal';

    // Setup stampede protection.
    $conf['memcache_stampede_protection'] = TRUE;
    $conf['memcache_stampede_protection_ignore'] = array(
      'test_bin',
      'test_bin_2' => array(
        'cid_no_prefix',
        'cid_with_prefix:*',
      ),
    );

    // Ensure the mock object is used.

    /** @var \MockMemCacheDrupal $cache_object_bin */
    $cache_object_bin = _cache_get_object('test_bin');
    $this
      ->assertEqual(get_class($cache_object_bin), 'MockMemCacheDrupal');

    /** @var \MockMemCacheDrupal $cache_object_bin2 */
    $cache_object_bin2 = _cache_get_object('test_bin_2');
    $this
      ->assertEqual(get_class($cache_object_bin2), 'MockMemCacheDrupal');

    // Test ignoring of an entire bin.
    $this
      ->assertFalse($cache_object_bin
      ->stampedeProtected('test_cid'), t('Disable stampede protection for cid contained in a disabled bin.'));
    $this
      ->assertFalse($cache_object_bin
      ->stampedeProtected('cid_no_prefix'), t('Disable stampede protection for cid without prefix in a disabled bin.'));
    $this
      ->assertFalse($cache_object_bin
      ->stampedeProtected('cid_with_prefix:example'), t('Disable stampede protection for cid with prefix in a disabled bin.'));

    // Test ignoring of specific CIDs.
    $this
      ->assertTrue($cache_object_bin2
      ->stampedeProtected('test_cid'), t('Don\'t disable stampede protection for a specific non-matching cid.'));
    $this
      ->assertFalse($cache_object_bin2
      ->stampedeProtected('cid_no_prefix'), t('Disable stampede protection for a specific cid.'));
    $this
      ->assertFalse($cache_object_bin2
      ->stampedeProtected('cid_with_prefix:example'), t('Disable stampede protection for a specific cid with disabled prefix.'));
    $this
      ->assertTrue($cache_object_bin2
      ->stampedeProtected('cid_with_other_prefix:example'), t('Don\'t disable stampede protection for a specific cid with a different prefix.'));
  }

}
include_once dirname(__DIR__) . '/memcache.inc';

/**
 * Wraps the MemCacheDrupal class in order to be able to call a protected method.
 */
class MockMemCacheDrupal extends MemCacheDrupal {
  public function stampedeProtected($cid) {
    return parent::stampedeProtected($cid);
  }

}

/**
 * Test some real world cache scenarios with default modules.
 *
 * Please make sure you've set the proper memcache settings in the settings.php.
 * Looks like I've not chance to change the cache settings to what's needed by
 * this test.
 */
class MemCacheRealWorldCase extends MemcacheTestCase {
  protected $profile = 'standard';
  protected $default_bin = 'cache_menu';
  public static function getInfo() {
    return array(
      'name' => 'Real world cache tests',
      'description' => 'Test some real world cache scenarios.',
      'group' => 'Memcache',
    );
  }

  /**
   * @see MemcacheTestCase::setUp()
   */
  public function setUp() {
    parent::setUp('menu');
  }

  /**
   * Test if the menu module caching acts as expected.
   *
   * The menu module clears the affected menu if an menu item is changed using
   * wildcards.
   */
  public function testMenu() {

    // Create and login user.
    $account = $this
      ->drupalCreateUser(array(
      'access administration pages',
      'administer blocks',
      'administer menu',
      'create article content',
    ));
    $this
      ->drupalLogin($account);

    // Add Menu Link to test with.
    $item = $this
      ->addMenuLink();
    $original_title = $item['link_title'];

    // Check if menu link is displayed.
    $this
      ->drupalGet('');
    $this
      ->assertText($original_title, 'Menu item displayed in frontend');

    // Change menu item multiple times and check if the change is reflected.
    for ($i = 0; $i < 3; $i++) {

      // Edit menu link.
      $edit = array();
      $edit['link_title'] = $this
        ->randomName(16);
      $this
        ->drupalPost("admin/structure/menu/item/{$item['mlid']}/edit", $edit, t('Save'));
      if (!$this
        ->assertResponse(200)) {

        // One fail is enough.
        break;
      }

      // Verify edited menu link.
      if (!$this
        ->drupalGet('admin/structure/menu/manage/' . $item['menu_name'])) {

        // One fail is enough.
        break;
      }
      $this
        ->assertText($edit['link_title'], 'Menu link was edited');
      $this
        ->drupalGet('');
      if (!$this
        ->assertText($edit['link_title'], 'Change is reflected in frontend')) {

        // One fail is enough.
        break;
      }
    }
  }

  /**
   * Adds a menu link.
   *
   * @see MenuTestCase::addMenuLink()
   */
  public function addMenuLink($plid = 0, $link = '<front>', $menu_name = 'main-menu') {

    // View add menu link page.
    $this
      ->drupalGet("admin/structure/menu/manage/{$menu_name}/add");
    $this
      ->assertResponse(200);
    $title = '!OriginalLink_' . $this
      ->randomName(16);
    $edit = array(
      'link_path' => $link,
      'link_title' => $title,
      'description' => '',
      // Use this to disable the menu and test.
      'enabled' => TRUE,
      // Setting this to true should test whether it works when we do the
      // std_user tests.
      'expanded' => TRUE,
      'parent' => $menu_name . ':' . $plid,
      'weight' => '0',
    );

    // Add menu link.
    $this
      ->drupalPost(NULL, $edit, t('Save'));
    $this
      ->assertResponse(200);

    // Unlike most other modules, there is no confirmation message displayed.
    $this
      ->assertText($title, 'Menu link was added');
    $item = db_query('SELECT * FROM {menu_links} WHERE link_title = :title', array(
      ':title' => $title,
    ))
      ->fetchAssoc();
    return $item;
  }

}

/**
 * Test statistics generation.
 */
class MemCacheStatisticsTestCase extends MemcacheTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Statistics tests',
      'description' => 'Test that statistics are being recorded appropriately.',
      'group' => 'Memcache',
    );
  }

  /**
   * @see MemcacheTestCase::setUp()
   */
  public function setUp() {
    parent::setUp('memcache_admin');
    $conf['cache_default_class'] = 'MemCacheDrupal';
    $conf['cache_class_cache_form'] = 'DrupalDatabaseCache';
  }

  /**
   * Checks for early bootstrap statistics.
   *
   * Tests that bootstrap cache commands are recorded when statistics are
   * enabled and tests that statistics are not recorded when the user doesn't
   * have access or displaying statistics is disabled.
   */
  public function testBootstrapStatistics() {
    global $_dmemcache_stats;

    // Expected statistics for cache_set() and cache_get().
    $test_full_key = dmemcache_key($this->default_cid, $this->default_bin);

    // List of bootstrap cids to check for.
    $cache_bootstrap_cids = array(
      'variables',
      'bootstrap_modules',
      'lookup_cache',
      'system_list',
      'module_implements',
    );

    // Turn on memcache statistics.
    variable_set('show_memcache_statistics', TRUE);
    drupal_static_reset('dmemcache_stats_init');
    $this
      ->drupalGet('<front>');

    // Check that statistics are not displayed for anonymous users.
    $this
      ->assertNoRaw('<div id="memcache-devel">', 'Statistics not present.');

    // Create and login a user without access to view statistics.
    $account = $this
      ->drupalCreateUser();
    $this
      ->drupalLogin($account);

    // Check that statistics are not displayed for authenticated users without
    // permission.
    $this
      ->assertNoRaw('<div id="memcache-devel">', 'Statistics not present.');

    // Create and login a user with access to view statistics.
    $account = $this
      ->drupalCreateUser(array(
      'access memcache statistics',
    ));
    $this
      ->drupalLogin($account);
    $this
      ->drupalGet('<front>');

    // Check that bootstrap statistics are visible.
    foreach ($cache_bootstrap_cids as $stat) {
      $key = $GLOBALS['drupal_test_info']['test_run_id'] . 'cache_bootstrap-' . $stat;
      $this
        ->assertRaw("<td>get</td><td>cache_bootstrap</td><td>{$key}</td>", t('Key @key found.', array(
        '@key' => $key,
      )));
    }

    // Clear boostrap cache items.
    foreach ($cache_bootstrap_cids as $stat) {
      _cache_get_object('cache_bootstrap')
        ->clear($stat);
    }
    $this
      ->drupalGet('<front>');

    // Check that early bootstrap statistics are still visible and are being
    // set too, after they were removed.
    foreach ($cache_bootstrap_cids as $stat) {
      $key = $GLOBALS['drupal_test_info']['test_run_id'] . 'cache_bootstrap-' . $stat;
      $this
        ->assertRaw("<td>get</td><td>cache_bootstrap</td><td>{$key}</td>", t('Key @key found (get).', array(
        '@key' => $key,
      )));
      $this
        ->assertRaw("<td>set</td><td>cache_bootstrap</td><td>{$key}</td>", t('Key @key found (set).', array(
        '@key' => $key,
      )));
    }

    // Clear the internal statistics store.
    $_dmemcache_stats = array(
      'all' => array(),
      'ops' => array(),
    );

    // Check that cache_set() statistics are being recorded.
    cache_set($this->default_cid, $this->default_value, $this->default_bin);
    $this
      ->assertEqual($_dmemcache_stats['all'][0][1], 'set', 'Set action recorded.');
    $this
      ->assertEqual($_dmemcache_stats['all'][0][4], 'hit', 'Set action successful.');
    $this
      ->assertNotNull($_dmemcache_stats['ops']['set']);

    // Clear the internal statistics store.
    $_dmemcache_stats = array(
      'all' => array(),
      'ops' => array(),
    );

    // Check that cache_get() statistics are being recorded.
    cache_get($this->default_cid, $this->default_bin);
    $this
      ->assertEqual($_dmemcache_stats['all'][0][1], 'get', 'Get action recorded.');
    $this
      ->assertEqual($_dmemcache_stats['all'][0][4], 'hit', 'Get action successful.');
    $this
      ->assertNotNull($_dmemcache_stats['ops']['get']);

    // Turn off memcache statistics.
    variable_set('show_memcache_statistics', FALSE);
    drupal_static_reset('dmemcache_stats_init');
    $this
      ->drupalGet('<front>');

    // Check that statistics are not recorded when the user has access, but
    // statistics are disabled.
    $this
      ->assertNoRaw('<div id="memcache-devel">', 'Statistics not present.');

    // Clear the internal statistics store.
    $_dmemcache_stats = array(
      'all' => array(),
      'ops' => array(),
    );

    // Confirm that statistics are not recorded for get()'s when disabled.
    cache_set($this->default_cid, $this->default_value, $this->default_bin);
    $this
      ->assertEqual($_dmemcache_stats, array(
      'all' => array(),
      'ops' => array(),
    ));

    // Clear the internal statistics store.
    $_dmemcache_stats = array(
      'all' => array(),
      'ops' => array(),
    );

    // Confirm that statistics are not recorded for set()'s when disabled.
    cache_get($this->default_cid, $this->default_bin);
    $this
      ->assertEqual($_dmemcache_stats, array(
      'all' => array(),
      'ops' => array(),
    ));
  }

}

Classes

Namesort descending Description
MemCacheClearCase Test cache clearing methods.
MemCacheGetMultipleUnitTest Test cache_get_multiple().
MemCacheRealWorldCase Test some real world cache scenarios with default modules.
MemCacheSavingCase
MemCacheStampedeProtection Tests memcache stampede protection.
MemCacheStatisticsTestCase Test statistics generation.
MemcacheTestCase @file Test cases for the memcache cache backend.
MockMemCacheDrupal Wraps the MemCacheDrupal class in order to be able to call a protected method.