=== 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-29 14:14:19 +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,20 @@ 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.
+   * @param $issue_nid
+   *   The nid of the issue that covers this bug.
+   */
+  protected function expectedFail($issue_nid) {
+    $this->expectedFail = $issue_nid;
+    return $this;
+  }
+
+
+  /**
    * This function stores the assert. Do not call directly.
    *
    * @param $status
@@ -52,6 +69,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 !issue.', array('!issue' => l(t('a known bug'), "http://drupal.org/node/". intval($issue_nid)))) . $message;
+      if (is_bool($status)) {
+        $status = $status ? 'unexpected pass' : 'expected fail';
+      }
+    }
     if (is_bool($status)) {
       $status = $status ? 'pass' : 'fail';
     }
@@ -72,13 +96,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 +429,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 +669,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 +683,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-29 14:12:41 +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-29 14:12:41 +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-29 14:12:41 +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 expected 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/simpletest.test'
--- modules/simpletest/simpletest.test	2008-08-22 12:35:55 +0000
+++ modules/simpletest/simpletest.test	2008-08-29 14:12:41 +0000
@@ -1,7 +1,10 @@
 <?php
 // $Id: simpletest.test,v 1.6 2008/08/22 12:35:55 dries Exp $
 
-class SimpleTestTestCase extends DrupalWebTestCase {
+/**
+ *  Utility functions for the parent/child tests in this class.
+ */
+class SimpleTestTestCaseUtil extends DrupalWebTestCase {
   /**
    * The results array that has been parsed by getTestResults().
    */
@@ -14,20 +17,6 @@ class SimpleTestTestCase extends DrupalW
   protected $test_ids = array();
 
   /**
-   * Implementation of getInfo().
-   */
-  function getInfo() {
-    return array(
-      'name' => t('SimpleTest functionality'),
-      'description' => t('Test SimpleTest\'s web interface: check that the intended tests were
-                          run and ensure that test reports display the intended results. Also
-                          test SimpleTest\'s internal browser and API\'s both explicitly and
-                          implicitly.'),
-      'group' => t('SimpleTest')
-    );
-  }
-
-  /**
    * Implementation of setUp().
    */
   function setUp() {
@@ -44,6 +33,130 @@ class SimpleTestTestCase extends DrupalW
   }
 
   /**
+   * Assert that an assertion with the specified values is displayed
+   * in the test results.
+   *
+   * @param string $message Assertion message.
+   * @param string $type Assertion type.
+   * @param string $status Assertion status.
+   * @return Assertion result.
+   */
+  function assertAssertion($message, $type, $status) {
+    $message = trim(strip_tags($message));
+    $found = FALSE;
+    foreach ($this->results['assertions'] as $assertion) {
+      if ($assertion['message'] == $message &&
+          $assertion['type'] == $type &&
+          $assertion['status'] == $status) {
+        $found = TRUE;
+        break;
+      }
+    }
+    return $this->assertTrue($found, t('Found assertion {"@message", "@type", "@status"}.', array('@message' => $message, '@type' => $type, '@status' => $status)));
+  }
+
+  /**
+   * Get the results from a test and store them in the class array $results.
+   */
+  function getTestResults() {
+    $results = array();
+
+    if ($this->parse()) {
+      $urls = array('pass_url' => 'ok', 'fail_url' => 'error', 'unexpected_url' => 'warning');
+      foreach ($urls as $key => $val) {
+        $path = 'misc/watchdog-' . $val . '.png';
+        $$key = ((url($path) == $path) ? $path : (base_path() . $path));
+      }
+
+      if ($fieldset = $this->getResultFieldSet()) {
+        // Code assumes this is the only test in group.
+        $results['summary'] = $this->asText($fieldset->div);
+        $results['name'] = $this->asText($fieldset->fieldset->legend);
+
+        $results['assertions'] = array();
+        $tbody = $fieldset->fieldset->table->tbody;
+        foreach ($tbody->tr as $row) {
+          $assertion = array();
+          $assertion['message'] = $this->asText($row->td[0]);
+          $assertion['type'] = $this->asText($row->td[1]);
+          switch ($row->td[5]->img['src']) {
+            case $pass_url:
+              $assertion['status'] = 'Pass';
+              break;
+            case $fail_url:
+              $assertion['status'] = 'Fail';
+              break;
+            case $unexpected_url:
+              $assertion['status'] = 'Pass';
+              break;
+            default:
+              $assertion['status'] = 'UNKNOWN';
+              break;
+          }
+          $results['assertions'][] = $assertion;
+        }
+      }
+    }
+    $this->results = $results;
+  }
+
+  /**
+   * Get the fieldset containing the results for group this test is in.
+   *
+   * @return fieldset containing the results for group this test is in.
+   */
+  function getResultFieldSet() {
+    $fieldsets = $this->xpath('//fieldset');
+    $info = $this->getInfo();
+    foreach ($fieldsets as $fieldset) {
+      if ($fieldset->legend == $info['group']) {
+        return $fieldset;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Extract the text contained by the element.
+   *
+   * @param $element
+   *   Element to extract text from.
+   * @return
+   *   Extracted text.
+   */
+  function asText(SimpleXMLElement $element) {
+    if (!is_object($element)) {
+      return $this->fail('The element is not an element.');
+    }
+    return trim(strip_tags($element->asXML()));
+  }
+
+  /**
+   * Check if the test is being run from inside a CURL request.
+   *
+   * @return The test is being run from inside a CURL request.
+   */
+  function inCURL() {
+    return preg_match("/^simpletest\d+/", $_SERVER['HTTP_USER_AGENT']);
+  }
+}  
+
+class SimpleTestTestCase extends SimpleTestTestCaseUtil {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('SimpleTest functionality'),
+      'description' => t('Test SimpleTest\'s web interface: check that the intended tests were
+                          run and ensure that test reports display the intended results. Also
+                          test SimpleTest\'s internal browser and API\'s both explicitly and
+                          implicitly.'),
+      'group' => t('SimpleTest')
+    );
+  }
+
+  /**
    * Test the internal browsers functionality.
    */
   function testInternalBrowser() {
@@ -133,94 +246,52 @@ class SimpleTestTestCase extends DrupalW
     }
     return NULL;
   }
+}
 
+/**
+ * Test Simpletest's "expected failures" functionality.
+ */
+class SimpleTestExpectedFailures extends SimpleTestTestCaseUtil {
   /**
-   * Assert that an assertion with the specified values is displayed
-   * in the test results.
-   *
-   * @param string $message Assertion message.
-   * @param string $type Assertion type.
-   * @param string $status Assertion status.
-   * @return Assertion result.
-   */
-  function assertAssertion($message, $type, $status) {
-    $message = trim(strip_tags($message));
-    $found = FALSE;
-    foreach ($this->results['assertions'] as $assertion) {
-      if ($assertion['message'] == $message &&
-          $assertion['type'] == $type &&
-          $assertion['status'] == $status) {
-        $found = TRUE;
-        break;
-      }
-    }
-    return $this->assertTrue($found, t('Found assertion {"@message", "@type", "@status"}.', array('@message' => $message, '@type' => $type, '@status' => $status)));
-  }
-
-  /**
-   * Get the results from a test and store them in the class array $results.
+   * Implementation of getInfo().
    */
-  function getTestResults() {
-    $results = array();
-
-    if ($this->parse()) {
-      if ($fieldset = $this->getResultFieldSet()) {
-        // Code assumes this is the only test in group.
-        $results['summary'] = $this->asText($fieldset->div);
-        $results['name'] = $this->asText($fieldset->fieldset->legend);
-
-        $results['assertions'] = array();
-        $tbody = $fieldset->fieldset->table->tbody;
-        foreach ($tbody->tr as $row) {
-          $assertion = array();
-          $assertion['message'] = $this->asText($row->td[0]);
-          $assertion['type'] = $this->asText($row->td[1]);
-          $ok_url = (url('misc/watchdog-ok.png') == 'misc/watchdog-ok.png') ? 'misc/watchdog-ok.png' : (base_path() . 'misc/watchdog-ok.png');
-          $assertion['status'] = ($row->td[5]->img['src'] == $ok_url) ? 'Pass' : 'Fail';
-          $results['assertions'][] = $assertion;
-        }
-      }
-    }
-    $this->results = $results;
+  function getInfo() {
+    return array(
+      'name' => t('Expected failures'),
+      'description' => t('Test SimpleTest\'s expect failures support.'),
+      'group' => t('SimpleTest')
+    );
   }
 
   /**
-   * Get the fieldset containing the results for group this test is in.
-   *
-   * @return fieldset containing the results for group this test is in.
-   */
-  function getResultFieldSet() {
-    $fieldsets = $this->xpath('//fieldset');
-    $info = $this->getInfo();
-    foreach ($fieldsets as $fieldset) {
-      if ($fieldset->legend == $info['group']) {
-        return $fieldset;
-      }
+   * In the child process, cause a normal pass and fail, an expected
+   * failure, and an unexpected pass.  In the parent process, invoke
+   * this test class via the web interface and verify that each of the
+   * tests produce the correct result (note: unexpected pass is a
+   * FAIL).
+   */
+  function  testExpectedFailures() { if ($this->inCURL()) {
+      // Child.  Perform the operations to be tested.
+      $this->assertTrue(TRUE, 'Expected pass');
+      $this->assertTrue(FALSE, 'Expected failure');
+      $this->expectedFail()->assertTrue(FALSE, 'Expected failure');
+      $this->expectedFail()->assertTrue(TRUE, 'Unexpected pass');
     }
-    return FALSE;
-  }
+    else {
+      // Parent.  Run this test via the web interface.
+      $this->drupalGet('admin/build/testing');
 
-  /**
-   * Extract the text contained by the element.
-   *
-   * @param $element
-   *   Element to extract text from.
-   * @return
-   *   Extracted text.
-   */
-  function asText(SimpleXMLElement $element) {
-    if (!is_object($element)) {
-      return $this->fail('The element is not an element.');
+      $edit = array();
+      $edit['SimpleTestExpectedFailures'] = TRUE;
+      $this->drupalPost(NULL, $edit, t('Run tests'));
+
+      // Parse results and confirm that they are correct.
+      $expected_prefix = 'Expected failure: ';
+      $this->getTestResults();
+      $this->assertAssertion('Expected pass', 'Other', 'Pass');
+      $this->assertAssertion('Expected failure', 'Other', 'Fail');
+      $this->assertAssertion($expected_prefix . 'Expected failure', 'Other', 'Pass');
+      $this->assertAssertion($expected_prefix . 'Unexpected pass', 'Other', 'Fail');
     }
-    return trim(strip_tags($element->asXML()));
-  }
-
-  /**
-   * Check if the test is being run from inside a CURL request.
-   *
-   * @return The test is being run from inside a CURL request.
-   */
-  function inCURL() {
-    return preg_match("/^simpletest\d+/", $_SERVER['HTTP_USER_AGENT']);
   }
 }

