=== modified file 'modules/simpletest/drupal_web_test_case.php'
--- modules/simpletest/drupal_web_test_case.php	2008-08-23 07:42:54 +0000
+++ modules/simpletest/drupal_web_test_case.php	2008-08-28 05:14:32 +0000
@@ -20,9 +20,12 @@ class DrupalWebTestCase {
   protected $db_prefix_original;
   protected $original_file_directory;
 
-  var $_results = array('#pass' => 0, '#fail' => 0, '#exception' => 0);
+  var $_results = array('#pass' => 0, '#fail' => 0, '#exception' => 0, '#unexpected pass' => 0, '#expected fail' => 0);
+
   var $_assertions = array();
 
+  var $expectedFail = FALSE;
+
   /**
    * Constructor for DrupalWebTestCase.
    *
@@ -34,6 +37,18 @@ class DrupalWebTestCase {
   }
 
   /**
+   * The next assert is expected to fail.
+   *
+   * Use $this->expectedFail->assert... to mark the assert as an expected fail.
+   * If your test reveals a bug then use this function.
+   */
+  protected function expectedFail() {
+    $this->expectedFail = TRUE;
+    return $this;
+  }
+
+
+  /**
    * This function stores the assert. Do not call directly.
    *
    * @param $status
@@ -52,6 +67,13 @@ class DrupalWebTestCase {
    */
   protected function _assert($status, $message = '', $group = 'Other', $custom_caller = NULL) {
     global $db_prefix;
+    if ($this->expectedFail) {
+      $this->expectedFail = FALSE;
+      $message .= t('This is expected to fail due to a known bug.');
+      if (is_bool($status)) {
+        $status = $status ? 'unexpected pass' : 'expected fail';
+      }
+    }
     if (is_bool($status)) {
       $status = $status ? 'pass' : 'fail';
     }
@@ -72,13 +94,13 @@ class DrupalWebTestCase {
     $db_prefix = $this->db_prefix_original;
     db_insert('simpletest')->fields(array(
       'test_id' => $this->test_id,
-    'test_class' => get_class($this), 
-    'status' => $status, 
-    'message' => substr($message, 0, 255),  // Some messages are too long for the database.
-    'message_group' => $group, 
-    'caller' => $function['function'], 
-    'line' => $function['line'], 
-    'file' => $function['file'],
+      'test_class' => get_class($this),
+      'status' => $status,
+      'message' => substr($message, 0, 255),  // Some messages are too long for the database.
+      'message_group' => $group,
+      'caller' => $function['function'],
+      'line' => $function['line'],
+      'file' => $function['file'],
     ))->execute();
     $this->_assertions[] = array(
       'status' => $status,
@@ -405,7 +427,7 @@ class DrupalWebTestCase {
     node_types_rebuild();
 
     $this->assertEqual($saved_type, SAVED_NEW, t('Created content type %type.', array('%type' => $type->type)));
-    
+
     // Reset permissions so that permissions for this content type are available.
     $this->checkPermissions(array(), TRUE);
 
@@ -645,7 +667,7 @@ class DrupalWebTestCase {
 
     // Generate temporary prefixed database to ensure that tests have a clean starting point.
     $db_prefix = 'simpletest' . mt_rand(1000, 1000000);
-    
+
     include_once './includes/install.inc';
     drupal_install_system();
 
@@ -659,7 +681,7 @@ class DrupalWebTestCase {
     // stale data for the previous run's database prefix and all
     // calls to it will fail.
     drupal_get_schema(NULL, TRUE);
-    
+
     // Run default profile tasks.
     $task = 'profile';
     default_profile_tasks($task, '');

=== modified file 'modules/simpletest/simpletest.css'
--- modules/simpletest/simpletest.css	2008-06-24 21:51:02 +0000
+++ modules/simpletest/simpletest.css	2008-08-27 20:23:51 +0000
@@ -24,11 +24,13 @@ table#simpletest-form-table tr.simpletes
   background-color: #EDF5FA !important;
 }
 
-div.simpletest-pass {
+div.simpletest-pass,
+div.simpletest-expected-fail {
   color: #33a333;
 }
 
-div.simpletest-fail {
+div.simpletest-fail,
+div.simpletest-unexpected-pass {
   color: #a30000;
 }
 

=== modified file 'modules/simpletest/simpletest.install'
--- modules/simpletest/simpletest.install	2008-08-16 20:57:14 +0000
+++ modules/simpletest/simpletest.install	2008-08-27 20:10:03 +0000
@@ -160,7 +160,7 @@ function simpletest_schema() {
       ),
       'status' => array(
         'type' => 'varchar',
-        'length' => 9,
+        'length' => 17
         'not null' => TRUE,
         'default' => '',
         'description' => t('Message status. Core understands pass, fail, exception.'),

=== modified file 'modules/simpletest/simpletest.module'
--- modules/simpletest/simpletest.module	2008-08-21 19:36:35 +0000
+++ modules/simpletest/simpletest.module	2008-08-28 05:13:09 +0000
@@ -70,6 +70,8 @@ function simpletest_test_form() {
       '#pass' => 0,
       '#fail' => 0,
       '#exception' => 0,
+      '#unexpected pass' => 0,
+      '#expected fail' => 0,
       '#weight' => -10,
     );
     $form['summary'] = $summary;
@@ -79,6 +81,8 @@ function simpletest_test_form() {
       'pass' => theme('image', 'misc/watchdog-ok.png'),
       'fail' => theme('image', 'misc/watchdog-error.png'),
       'exception' => theme('image', 'misc/watchdog-warning.png'),
+      'unexpected pass' => theme('image', 'misc/watchdog-error.png'),
+      'expected fail' => theme('image', 'misc/watchdog-warning.png'),
     );
     $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status')));
     while ($result = db_fetch_object($results)) {
@@ -93,7 +97,8 @@ function simpletest_test_form() {
         $element['summary'] = $summary;
       }
       $status = $result->status;
-      // This reporter can only handle pass, fail and exception.
+      // This reporter can only handle pass, fail, exception, unexpected pass
+      // and expected fail.
       if (isset($map[$status])) {
         $element['#title'] = $info['name'];
         $status_index = '#'. $status;
@@ -109,7 +114,7 @@ function simpletest_test_form() {
             $result->caller,
             $map[$status],
           ),
-          'class' => "simpletest-$status",
+          'class' => str_replace(' ', '-', "simpletest-$status"),
         );
       }
       unset($element);
@@ -127,7 +132,7 @@ function simpletest_test_form() {
       $group_ok = TRUE;
       foreach ($elements as $class => &$element) {
         $info = $uncategorized_tests[$class]->getInfo();
-        $ok = $element['summary']['#fail'] + $element['summary']['#exception'] == 0;
+        $ok = $element['summary']['#fail'] + $element['summary']['#exception'] + $element['summary']['#unexpected pass'] == 0;
         $element += array(
           '#type' => 'fieldset',
           '#collapsible' => TRUE,
@@ -270,10 +275,12 @@ function theme_simpletest_result_summary
 }
 
 function _simpletest_format_summary_line($summary) {
-  return t('@pass, @fail, @exception', array(
+  return t('@pass, @fail, @exception, @unexpected_pass, @expected_fail', array(
     '@pass' => format_plural(isset($summary['#pass']) ? $summary['#pass'] : 0, '1 pass', '@count passes'),
     '@fail' => format_plural(isset($summary['#fail']) ? $summary['#fail'] : 0, '1 fail', '@count fails'),
     '@exception' => format_plural(isset($summary['#exception']) ? $summary['#exception'] : 0, '1 exception', '@count exceptions'),
+    '@unexpected_pass' => format_plural(isset($summary['#unexpected pass']) ? $summary['#unexpected pass'] : 0, '1 unexpected pass', '@count unexpected passes'),
+    '@expected_fail' => format_plural(isset($summary['#expected fail']) ? $summary['#expected fail'] : 0, '1 expected fail', '@count unexpected fails'),
   ));
 }
 
@@ -350,7 +357,7 @@ function _simpletest_batch_operation($te
     // First iteration: initialize working values.
     $test_list = $test_list_init;
     $context['sandbox']['max'] = count($test_list);
-    $test_results = array('#pass' => 0, '#fail' => 0, '#exception' => 0);
+    $test_results = array('#pass' => 0, '#fail' => 0, '#exception' => 0, '#unexpected pass', '#expected fail');
   }
   else {
     // Nth iteration: get the current values where we last stored them.
@@ -374,7 +381,8 @@ function _simpletest_batch_operation($te
   $test_results[$test_class]['#name'] = $info['name'];
   $items = array();
   foreach (element_children($test_results) as $class) {
-    $items[] = '<div class="simpletest-' . ($test_results[$class]['#fail'] + $test_results[$class]['#exception'] ? 'fail' : 'pass') . '">' . t('@name: @summary', array('@name' => $test_results[$class]['#name'], '@summary' => _simpletest_format_summary_line($test_results[$class]))) . '</div>';
+    $errors = $test_results[$class]['#fail'] + $test_results[$class]['#exception'] + $test_results[$class]['#unexpected pass'];
+    $items[] = '<div class="simpletest-' . ($errors ? 'fail' : 'pass') . '">' . t('@name: @summary', array('@name' => $test_results[$class]['#name'], '@summary' => _simpletest_format_summary_line($test_results[$class]))) . '</div>';
   }
   $message = t('Processed test @num of @max - %test.', array('%test' => $info['name'], '@num' => $max - $size, '@max' => $max));
   $message .= theme('item_list', $items);

=== modified file 'modules/simpletest/tests/cache.test'
--- modules/simpletest/tests/cache.test	2008-08-14 09:18:28 +0000
+++ modules/simpletest/tests/cache.test	2008-08-27 20:14:00 +0000
@@ -233,4 +233,157 @@ class CacheClearCase extends CacheTestCa
                       || $this->checkCacheExists('test_cid_clear2', $this->default_value),
                       t('Two caches removed after clearing cid substring with wildcard true.'));
   }
-}
\ No newline at end of file
+}
+
+class CacheExpiryCase extends CacheTestCase {
+  /**
+   * 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_table = 'cache';
+    $this->default_cid = 'test_expiry';
+    $this->default_value = $this->randomName(10);
+
+    parent::setUp();
+  }
+
+  /**
+   * 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() {
+    $this->setupLifetime(0);
+    cache_set($this->default_cid, $this->default_value, $this->default_table, CACHE_TEMPORARY);
+
+    $this->assertCacheExists(t('Temporary cache data without lifetime exists before wipe.'));
+
+    $user->cache = isset($user->cache) ? $user->cache +2 : time() + 2;
+    $this->assertCacheExists(t('Temporary cache without lifetime valid after user cache expiration.'));
+    $user->cache = $user->cache - 2;
+
+    $this->generalWipe();
+    $this->assertCacheRemoved(t('Temporary cache without lifetime does not exists 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() {
+    $this->setupLifetime(5);
+    cache_set($this->default_cid, $this->default_value, $this->default_table, CACHE_TEMPORARY);
+
+    $this->assertCacheExists(t('Temporary cache with lifetime data exists before wipe.'));
+
+    $user->cache = isset($user->cache) ? $user->cache + 2 : time() + 2;
+    $this->assertCacheExists(t('Temporary cache without lifetime valid after user cache expiration.'));
+    $user->cache = $user->cache - 2;
+
+    $this->generalWipe();
+    $this->expectedFail()->assertCacheRemoved(t('Temporary cache with lifetime does not exists 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() {
+    $this->setupLifetime(0);
+    cache_set($this->default_cid, $this->default_value, $this->default_table, CACHE_PERMANENT);
+
+    $this->assertCacheExists(t('Permanent cache data without lifetime exists before wipe.'));
+
+    $user->cache = isset($user->cache) ? $user->cache + 2 : time() + 2;
+    $this->assertCacheExists(t('Permanent cache without lifetime valid after user cache expiration.'));
+    $user->cache = $user->cache - 2;
+
+    $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() {
+    $this->setupLifetime(5);
+    cache_set($this->default_cid, $this->default_value, $this->default_table, CACHE_PERMANENT);
+
+    $this->assertCacheExists(t('Permanent cache data with lifetime exists before wipe.'));
+
+    $user->cache = isset($user->cache) ? $user->cache + 2 : time() + 2;
+    $this->assertCacheExists(t('Permanent cache with lifetime valid after user cache expiration.'));
+    $user->cache = $user->cache - 2;
+
+    $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() {
+    $this->setupLifetime(0);
+    cache_set($this->default_cid, $this->default_value, $this->default_table, time() + 1);
+
+    $this->assertCacheExists(t('Unit timestamp cache data without lifetime exists before wipe.'));
+    $this->generalWipe();
+    $this->assertCacheExists(t('Unit timestamp cache without lifetime exists after wipe.'));
+    sleep(2); // expire
+    $this->assertCacheExists(t('Unit timestamp cache without lifetime exists after expiration.'));
+
+    $user->cache = isset($user->cache) ? $user->cache + 2 : time() + 2;
+    $this->assertCacheExists(t('Unit timestamp cache without lifetime valid after user cache expiration.'));
+    $user->cache = $user->cache - 2;
+
+    $this->generalWipe();
+    $this->assertCacheRemoved(t('Unit 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() {
+    $this->setupLifetime(5);
+    cache_set($this->default_cid, $this->default_value, $this->default_table, time() + 1);
+    global $user;
+
+    $this->assertCacheExists(t('Unit timestamp cache data without lifetime exists before wipe.'));
+    $this->generalWipe();
+    $this->assertCacheExists(t('Unit timestamp cache with lifetime exists after wipe.'));
+    sleep(2); // expire
+    $this->assertCacheExists(t('Unit timestamp cache with lifetime exists after expiration.'));
+
+    $user->cache = $user->cache + 2;
+    $this->assertCacheRemoved(t('Unit timestamp cache with lifetime not valid after user cache expiration.'));
+    $user->cache = $user->cache - 2;
+
+    $this->generalWipe();
+    $this->assertCacheRemoved(t('Unit timestamp cache with lifetime deleted after expiration and wipe.'));
+  }
+}

