Index: includes/cache.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/cache.inc,v
retrieving revision 1.48
diff -u -r1.48 cache.inc
--- includes/cache.inc	18 May 2010 18:26:30 -0000	1.48
+++ includes/cache.inc	27 Aug 2010 14:12:42 -0000
@@ -389,7 +389,7 @@
     // timer. The cache variable is loaded into the $user object by _drupal_session_read()
     // in session.inc. If the data is permanent or we're not enforcing a minimum
     // cache lifetime always return the cached data.
-    if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && $user->cache > $cache->created) {
+    if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && isset($user->cache) && $user->cache > $cache->created) {
       // This cache data is too old and thus not valid for us, ignore it.
       return FALSE;
     }
Index: modules/simpletest/tests/cache.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/cache.test,v
retrieving revision 1.12
diff -u -r1.12 cache.test
--- modules/simpletest/tests/cache.test	5 Aug 2010 23:53:38 -0000	1.12
+++ modules/simpletest/tests/cache.test	27 Aug 2010 14:12:42 -0000
@@ -29,7 +29,7 @@
   }
 
   /**
-   * Assert or a cache entry exists.
+   * Assert that a cache entry exists.
    *
    * @param $message
    *   Message to display.
@@ -55,7 +55,7 @@
   }
 
   /**
-   * Assert or a cache entry has been removed.
+   * Assert that a cache entry has been removed.
    *
    * @param $message
    *   Message to display.
@@ -349,3 +349,231 @@
     $this->assertTrue(cache_is_empty($this->default_bin), t('The cache bin is empty'));
   }
 }
+
+/**
+ * Since cache expiry uses REQUEST_TIME, which makes testing in a single request
+ * impossible, this test has to use a test module and page callbacks for
+ * testing.
+ */
+class CacheExpiryCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('Cache expire test'),
+      'description' => t('Check or the expiration of the cache is working properly'),
+      'group' => t('Cache')
+    );
+  }
+
+  /**
+   * Implementation of setUp().
+   */
+  function setUp() {
+    $this->default_bin = 'cache';
+    $this->default_cid = 'test_expiry';
+    $this->default_value = $this->randomName(10);
+
+    parent::setUp('cache_test');
+  }
+
+  /**
+   * Store data in the persistent cache.
+   *
+   * @see cache_set
+   */
+  protected function cacheSet($expire = CACHE_PERMANENT) {
+    $bin = $this->default_bin;
+    $cid = $this->default_cid;
+    $data = $this->default_value;
+
+    $this->drupalGet("cache_test/set/$cid/$data/$bin/$expire");
+  }
+
+  /**
+   * Assert that a cache entry exists.
+   *
+   * @param $message
+   *   Message to display.
+   * @param $user_expire
+   *   If set, increment $user->cache before retrieving the cache value.
+   */
+  protected function assertCacheExists($message, $user_expire = FALSE) {
+    $bin = $this->default_bin;
+    $cid = $this->default_cid;
+    $data = $this->default_value;
+
+    $this->drupalGet("cache_test/get/$cid/$bin" . ($user_expire ? '/1' : ''));
+    $this->assertText('Value exists in cache: ' . $data, $message);
+  }
+
+  /**
+   * Assert that a cache entry has been removed.
+   *
+   * @param $message
+   *   Message to display.
+   * @param $cid
+   *   The cache id.
+   * @param $bin
+   *   The bin the cache item was stored in.
+   */
+  function assertCacheRemoved($message) {
+    $bin = $this->default_bin;
+    $cid = $this->default_cid;
+
+    $this->drupalGet("cache_test/get/$cid/$bin");
+    $this->assertText('Value does not exist in cache.', $message);
+  }
+
+  /**
+   * Perform the general wipe.
+   * @param $bin
+   *   The bin to perform the wipe on.
+   */
+  protected function generalWipe() {
+    $bin = $this->default_bin;
+
+    $this->drupalGet("cache_test/wipe/$bin");
+  }
+
+  /**
+   * Setup the lifetime settings for caching.
+   *
+   * @param $time
+   *   The time in seconds the cache should minimal live.
+   */
+  protected function setupLifetime($time) {
+    $bin = $this->default_bin;
+
+    $this->drupalGet("cache_test/lifetime/$bin/$time");
+  }
+
+  /**
+   * testTemporaryNoLifetime()
+   * Tests the expiry of data when the $expire is set to CACHE_TEMPORARY and lifetime is off.
+   *   - CACHE_TEMPORARY: Indicates that the item should be removed at the next
+   *     general cache wipe.
+   */
+  function testTemporaryNoLifetime() {
+    global $user;
+
+    $this->setupLifetime(0);
+    $this->cacheSet(CACHE_TEMPORARY);
+
+    $this->assertCacheExists(t('Temporary cache without lifetime exists before wipe.'));
+
+    $this->assertCacheExists(t('Temporary cache without lifetime valid after user cache expiration.'), TRUE);
+
+    $this->generalWipe();
+    $this->assertCacheRemoved(t('Temporary cache without lifetime does not exist after wipe.'));
+  }
+
+  /**
+   * testTemporaryLifetime()
+   * Tests the expiry of data when the $expire is set to CACHE_TEMPORARY and lifetime is on.
+   *   - CACHE_TEMPORARY: Indicates that the item should be removed at the next
+   *     general cache wipe.
+   */
+  function testTemporaryLifetime() {
+    global $user;
+
+    $this->setupLifetime(5);
+    $this->cacheSet(CACHE_TEMPORARY);
+
+    $this->assertCacheExists(t('Temporary cache with lifetime exists before wipe.'));
+
+    $this->assertCacheExists(t('Temporary cache without lifetime valid after user cache expiration.'));
+
+    $this->generalWipe();
+    $this->assertCacheRemoved(t('Temporary cache with lifetime does not exist after wipe.'));
+  }
+
+  /**
+   * testPermanentNoLifetime()
+   * Tests the expiry of data when the $expire is set to CACHE_PERMANENT and lifetime is off.
+   *   - CACHE_PERMANENT: Indicates that the item should never be removed unless
+   *     explicitly told to using cache_clear_all() with a cache ID.
+   */
+  function testPermanentNoLifetime() {
+    global $user;
+
+    $this->setupLifetime(0);
+    $this->cacheSet(CACHE_PERMANENT);
+
+    $this->assertCacheExists(t('Permanent cache without lifetime exists before wipe.'));
+
+    $this->assertCacheExists(t('Permanent cache without lifetime valid after user cache expiration.'), TRUE);
+
+    $this->generalWipe();
+    $this->assertCacheExists(t('Permanent cache without lifetime exists after wipe.'));
+  }
+
+  /**
+   * testPermanentLifetime()
+   * Tests the expiry of data when the $expire is set to CACHE_PERMANENT and lifetime is on.
+   *   - CACHE_PERMANENT: Indicates that the item should never be removed unless
+   *     explicitly told to using cache_clear_all() with a cache ID.
+   */
+  function testPermanentLifetime() {
+    global $user;
+
+    $this->setupLifetime(5);
+    $this->cacheSet(CACHE_PERMANENT);
+
+    $this->assertCacheExists(t('Permanent cache with lifetime exists before wipe.'));
+
+    $this->assertCacheExists(t('Permanent cache with lifetime valid after user cache expiration.'), TRUE);
+
+    $this->generalWipe();
+    $this->assertCacheExists(t('Permanent cache with lifetime exists after wipe.'));
+  }
+
+  /**
+   * testUnixTimestampNoLifetime()
+   * Tests the expiry of data when the $expire is set to unix timestamp and lifetime is off.
+   *   - A Unix timestamp: Indicates that the item should be kept at least until
+   *     the given time, after which it behaves like CACHE_TEMPORARY.
+   */
+  function testUnixTimestampNoLifetime() {
+    global $user;
+
+    $this->setupLifetime(0);
+    $this->cacheSet(time() + 1);
+
+    $this->assertCacheExists(t('Unix timestamp cache without lifetime exists before wipe.'));
+    $this->generalWipe();
+    $this->assertCacheExists(t('Unix timestamp cache without lifetime exists after wipe.'));
+    sleep(2); // expire
+    $this->assertCacheExists(t('Unix timestamp cache without lifetime exists after expiration.'));
+
+    $this->assertCacheExists(t('Unix timestamp cache without lifetime valid after user cache expiration.'), TRUE);
+
+    $this->generalWipe();
+    $this->assertCacheRemoved(t('Unix timestamp cache without lifetime deleted after expiration and wipe.'));
+  }
+
+  /**
+   * testUnixTimestampLifetime()
+   * Tests the expiry of data when the $expire is set to unix timestamp and lifetime is on.
+   *   - A Unix timestamp: Indicates that the item should be kept at least until
+   *     the given time, after which it behaves like CACHE_TEMPORARY.
+   */
+  function testUnixTimestampLifetime() {
+    global $user;
+
+    $this->setupLifetime(15);
+    $this->cacheSet(time() + 5);
+
+    $this->assertCacheExists(t('Unix timestamp cache with lifetime exists before wipe.'));
+    $this->generalWipe();
+    $this->assertCacheExists(t('Unix timestamp cache with lifetime exists after wipe.'));
+    sleep(6); // expire
+    $this->assertCacheExists(t('Unix timestamp cache with lifetime exists after expiration.'));
+
+    $this->assertCacheRemoved(t('Unix timestamp cache with lifetime not valid after user cache expiration.'), TRUE);
+
+    $this->generalWipe();
+    $this->assertCacheRemoved(t('Unix timestamp cache with lifetime deleted after expiration and wipe.'));
+  }
+}
Index: modules/simpletest/tests/cache_test.info
===================================================================
RCS file: modules/simpletest/tests/cache_test.info
diff -N modules/simpletest/tests/cache_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/cache_test.info	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,8 @@
+; $Id$
+name = "Cache expiry test module"
+description = "Module for testing cache expiry features."
+core = 7.x
+package = Testing
+files[] = cache_test.module
+version = VERSION
+hidden = TRUE
Index: modules/simpletest/tests/cache_test.module
===================================================================
RCS file: modules/simpletest/tests/cache_test.module
diff -N modules/simpletest/tests/cache_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/cache_test.module	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,68 @@
+<?php
+// $Id$
+
+/**
+ * Implements hook_menu().
+ */
+function cache_test_menu() {
+  $items['cache_test/get'] = array(
+    'title' => 'cache_test_get',
+    'page callback' => 'cache_test_get',
+    'access callback' => TRUE,
+  );
+  $items['cache_test/set'] = array(
+    'title' => 'cache_test_set',
+    'page callback' => 'cache_test_set',
+    'access callback' => TRUE,
+  );
+  $items['cache_test/wipe'] = array(
+    'title' => 'cache_test_wipe',
+    'page callback' => 'cache_test_wipe',
+    'access callback' => TRUE,
+  );
+  $items['cache_test/lifetime'] = array(
+    'title' => 'cache_test_lifetime',
+    'page callback' => 'cache_test_lifetime',
+    'access callback' => TRUE,
+  );
+  return $items;
+}
+
+/**
+ * Page callback for getting a cache's value.
+ */
+function cache_test_get($cid, $bin, $user_expire = FALSE) {
+  if ($user_expire) {
+    $user->cache = isset($user->cache) ? $user->cache + 10 : REQUEST_TIME + 10;
+  }
+  $cache = cache_get($cid, $bin);
+  if ($user_expire) {
+    $user->cache -= 10;
+  }
+  return isset($cache->data) ? 'Value exists in cache: ' . $cache->data : 'Value does not exist in cache.';
+}
+
+/**
+ * Page callback for setting a cache's value.
+ */
+function cache_test_set($cid, $data, $bin, $expire) {
+  cache_set($cid, $data, $bin, $expire);
+  return 'Done.';
+}
+
+/**
+ * Page callback for wiping a cache.
+ */
+function cache_test_wipe($bin) {
+  cache_clear_all(NULL, $bin);
+  return 'Done.';
+}
+
+/**
+ * Page callback for setting the cache lifetime.
+ */
+function cache_test_lifetime($bin, $time) {
+  variable_set('cache_lifetime', $time);
+  variable_set("cache_flush_$bin", 0);
+  return 'Done.';
+}
