Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.407
diff -u -p -r1.407 form.inc
--- includes/form.inc	27 Nov 2009 15:14:58 -0000	1.407
+++ includes/form.inc	27 Nov 2009 19:54:36 -0000
@@ -165,8 +165,6 @@ function drupal_build_form($form_id, &$f
     $form_state['input'] = $form_state['method'] == 'get' ? $_GET : $_POST;
   }
 
-  $cacheable = FALSE;
-
   if (isset($_SESSION['batch_form_state'])) {
     // We've been redirected here after a batch processing : the form has
     // already been processed, so we grab the post-process $form_state value
@@ -182,6 +180,9 @@ function drupal_build_form($form_id, &$f
     // be passed on to the form processing code.
     if (isset($form_state['input']['form_id']) && $form_state['input']['form_id'] == $form_id && !empty($form_state['input']['form_build_id'])) {
       $form = form_get_cache($form_state['input']['form_build_id'], $form_state);
+      // Indicate that the cached $form_state may be updated in case
+      // $form_state['cache'] is set.
+      $form_build_id = $form_state['input']['form_build_id'];
     }
 
     // If the previous bit of code didn't result in a populated $form
@@ -213,7 +214,6 @@ function drupal_build_form($form_id, &$f
       // Store a copy of the unprocessed form for caching and indicate that it
       // is cacheable in case $form_state['cache'] is set.
       $original_form = $form;
-      $cacheable = TRUE;
     }
 
     // Now that we know we have a form, we'll process it (validating,
@@ -227,14 +227,14 @@ function drupal_build_form($form_id, &$f
     // have set $form_state['cache'] to indicate that the original form and the
     // $form_state shall be cached. But the form may only be cached if the
     // special 'no_cache' property is not set to TRUE.
-    if (!empty($form_state['cache']) && empty($form_state['no_cache'])) {
+    if (isset($form_build_id) && !empty($form_state['cache']) && empty($form_state['no_cache'])) {
       // Cache the form upon initial build of the form.
-      if ($cacheable) {
+      if (isset($original_form)) {
         form_set_cache($form_build_id, $original_form, $form_state);
       }
       // After processing a cached form, only update the cached form state.
       else {
-        form_set_cache($form_state['input']['form_build_id'], NULL, $form_state);
+        form_set_cache($form_build_id, NULL, $form_state);
       }
     }
   }
@@ -252,7 +252,9 @@ function drupal_build_form($form_id, &$f
   // passing in the latest $form_state in addition to any other variables passed
   // into drupal_get_form().
   if ($form_state['rebuild'] && $form_state['submitted'] && !form_get_errors()) {
-    $form = drupal_rebuild_form($form_id, $form_state);
+    // If the form was cached or came from cache within this request, re-use the
+    // same $form_build_id. Otherwise, rebuild with a new.
+    $form = drupal_rebuild_form($form_id, $form_state, isset($form_build_id) ? $form_build_id : NULL);
   }
 
   // Don't override #theme if someone already set it.
Index: modules/simpletest/tests/form.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v
retrieving revision 1.25
diff -u -p -r1.25 form.test
--- modules/simpletest/tests/form.test	18 Nov 2009 18:51:11 -0000	1.25
+++ modules/simpletest/tests/form.test	27 Nov 2009 20:00:16 -0000
@@ -413,16 +413,15 @@ class FormsFormStorageTestCase extends D
 
   function setUp() {
     parent::setUp('form_test');
+
+    $this->web_user = $this->drupalCreateUser(array('access content'));
+    $this->drupalLogin($this->web_user);
   }
 
   /**
    * Tests using the form in a usual way.
    */
   function testForm() {
-
-    $user = $this->drupalCreateUser(array('access content'));
-    $this->drupalLogin($user);
-
     $this->drupalPost('form_test/form-storage', array('title' => 'new', 'value' => 'value_is_set'), 'Continue');
     $this->assertText('Form constructions: 2', t('The form has been constructed two times till now.'));
 
@@ -435,9 +434,6 @@ class FormsFormStorageTestCase extends D
    * Tests using the form with an activated $form_state['cache'] property.
    */
   function testFormCached() {
-    $user = $this->drupalCreateUser(array('access content'));
-    $this->drupalLogin($user);
-
     $this->drupalPost('form_test/form-storage', array('title' => 'new', 'value' => 'value_is_set'), 'Continue', array('query' => array('cache' => 1)));
     $this->assertText('Form constructions: 1', t('The form has been constructed one time till now.'));
 
@@ -450,12 +446,38 @@ class FormsFormStorageTestCase extends D
    * Tests validation when form storage is used.
    */
   function testValidation() {
-    $user = $this->drupalCreateUser(array('access content'));
-    $this->drupalLogin($user);
-
     $this->drupalPost('form_test/form-storage', array('title' => '', 'value' => 'value_is_set'), 'Continue');
     $this->assertPattern('/value_is_set/', t("The input values have been kept."));
   }
+
+  /**
+   * Tests updating cached form storage during form validation.
+   */
+  function testCachedFormStorageValidation() {
+    // Request the form with 'cache' query parameter.
+    $this->drupalGet('form_test/form-storage', array('query' => array('cache' => 1)));
+    // Fetch form_build_id.
+    $fields = $this->xpath($this->constructFieldXpath('name', 'form_build_id'));
+    $form_build_id = (string) $fields[0]['value'];
+
+    // Skip step 1.
+    $edit = array('title' => 'foo');
+    $this->drupalPost(NULL, $edit, 'Continue');
+    $this->assertFieldByName('form_build_id', $form_build_id);
+
+    // Trigger a validation error to update title in form storage in step 2.
+    $edit = array('title' => '', 'value' => 'change_title');
+    $this->drupalPost(NULL, $edit, 'Save');
+    $this->assertFieldByName('form_build_id', $form_build_id);
+
+    // Post again with 'title' to ensure form storage persists; the value in
+    // form storage is only updated in step 1.
+    $edit = array('title' => 'foo', 'value' => '');
+    $this->drupalPost(NULL, $edit, 'Save');
+    $this->assertFieldByName('form_build_id', $form_build_id);
+    $this->assertFieldByName('title', 'title_changed', t('The altered form storage value was updated in cache and taken over.'));
+    $this->assertText('Title: title_changed', t('The form storage has stored the values.'));
+  }
 }
 
 /**
Index: modules/simpletest/tests/form_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v
retrieving revision 1.17
diff -u -p -r1.17 form_test.module
--- modules/simpletest/tests/form_test.module	21 Nov 2009 17:01:31 -0000	1.17
+++ modules/simpletest/tests/form_test.module	27 Nov 2009 19:58:44 -0000
@@ -301,25 +301,29 @@ function form_storage_test_form($form, &
   // Count how often the form is constructed
   $_SESSION['constructions']++;
 
+  $form['title'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Title',
+    '#default_value' => $form_state['storage']['thing']['title'],
+    '#required' => TRUE,
+  );
+  $form['value'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Value',
+    '#default_value' => $form_state['storage']['thing']['value'],
+    '#element_validate' => array('form_test_storage_element_validate_value_cached'),
+  );
   if ($form_state['storage']['step'] == 1) {
-    $form['title'] = array(
-      '#type' => 'textfield',
-      '#title' => 'title',
-      '#default_value' => $form_state['storage']['thing']['title'],
-      '#required' => TRUE,
-    );
-    $form['value'] = array(
-      '#type' => 'textfield',
-      '#title' => 'value',
-      '#default_value' => $form_state['storage']['thing']['value'],
-    );
     $form['submit'] = array(
       '#type' => 'submit',
       '#value' => 'Continue',
     );
   }
   else {
-    $form['body'] = array('#value' => 'This is the second step.');
+    $form['body'] = array(
+      '#type' => 'item',
+      '#value' => 'This is the second step.',
+    );
     $form['submit'] = array(
       '#type' => 'submit',
       '#value' => 'Save',
@@ -336,6 +340,20 @@ function form_storage_test_form($form, &
 }
 
 /**
+ * Form element validation handler to test updating of cached form storage during validation.
+ */
+function form_test_storage_element_validate_value_cached($element, &$form_state) {
+  // If caching is enabled, change the value of 'title' and trigger a form
+  // validation error. The cached form storage still has to be updated.
+  if (isset($_REQUEST['cache']) && $form_state['values']['value'] == 'change_title') {
+    $form_state['storage']['thing']['title'] = 'title_changed';
+    // @todo This really shouldn't be necessary. Should Form API cache that a
+    //   form can be cached? See http://drupal.org/node/641356
+    $form_state['cache'] = TRUE;
+  }
+}
+
+/**
  * Multistep form submit callback.
  */
 function form_storage_test_form_submit($form, &$form_state) {
