summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/simpletest/tests/batch.test285
-rw-r--r--modules/simpletest/tests/batch_test.callbacks.inc118
-rw-r--r--modules/simpletest/tests/batch_test.info9
-rw-r--r--modules/simpletest/tests/batch_test.module475
-rw-r--r--modules/simpletest/tests/form.test39
-rw-r--r--modules/simpletest/tests/form_test.module68
-rw-r--r--modules/simpletest/tests/system_test.module33
-rw-r--r--modules/system/system.install56
-rw-r--r--modules/system/system.module13
-rw-r--r--modules/system/system.queue.inc73
10 files changed, 960 insertions, 209 deletions
diff --git a/modules/simpletest/tests/batch.test b/modules/simpletest/tests/batch.test
index 7c6ade423..54b29c8a9 100644
--- a/modules/simpletest/tests/batch.test
+++ b/modules/simpletest/tests/batch.test
@@ -3,40 +3,286 @@
/**
* @file
- * Unit tests for the Drupal Batch API.
+ * Tests for the Batch API.
*/
/**
- * Tests for the batch API progress page theme.
+ * Tests for the Batch API.
*/
-class BatchAPIThemeTestCase extends DrupalWebTestCase {
+class BatchProcessingTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
- 'name' => 'Batch API progress page theme',
- 'description' => 'Tests that while a progressive batch is running, it correctly uses the theme of the page that started the batch.',
+ 'name' => 'Batch processing',
+ 'description' => 'Test batch processing in form and non-form workflow.',
'group' => 'Batch API',
);
}
function setUp() {
- parent::setUp('system_test');
- // Make sure that the page which starts the batch (an administrative page)
- // is using a different theme than would normally be used by the batch API.
- variable_set('theme_default', 'garland');
- variable_set('admin_theme', 'seven');
+ parent::setUp('batch_test');
+ }
+
+ /**
+ * Test batches triggered outside of form submission.
+ */
+ function testBatchNoForm() {
+ // Displaying the page triggers batch 1.
+ $this->drupalGet('batch_test/no_form');
+ $this->assertBatchMessages($this->_resultMessages(1), t('Batch for step 2 performed successfully.'));
+ $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), t('Execution order was correct.'));
+ $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
+ }
+
+ /**
+ * Test batches defined in a form submit handler.
+ */
+ function testBatchForm() {
+ // Batch 0: no operation.
+ $edit = array('batch' => 'batch_0');
+ $this->drupalPost('batch_test/simple', $edit, 'Submit');
+ $this->assertBatchMessages($this->_resultMessages('batch_0'), t('Batch with no operation performed successfully.'));
+ $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
+
+ // Batch 1: several simple operations.
+ $edit = array('batch' => 'batch_1');
+ $this->drupalPost('batch_test/simple', $edit, 'Submit');
+ $this->assertBatchMessages($this->_resultMessages('batch_1'), t('Batch with simple operations performed successfully.'));
+ $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), t('Execution order was correct.'));
+ $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
+
+ // Batch 2: one multistep operation.
+ $edit = array('batch' => 'batch_2');
+ $this->drupalPost('batch_test/simple', $edit, 'Submit');
+ $this->assertBatchMessages($this->_resultMessages('batch_2'), t('Batch with multistep operation performed successfully.'));
+ $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_2'), t('Execution order was correct.'));
+ $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
+
+ // Batch 3: simple + multistep combined.
+ $edit = array('batch' => 'batch_3');
+ $this->drupalPost('batch_test/simple', $edit, 'Submit');
+ $this->assertBatchMessages($this->_resultMessages('batch_3'), t('Batch with simple and multistep operations performed successfully.'));
+ $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_3'), t('Execution order was correct.'));
+ $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
+
+ // Batch 4: nested batch.
+ $edit = array('batch' => 'batch_4');
+ $this->drupalPost('batch_test/simple', $edit, 'Submit');
+ $this->assertBatchMessages($this->_resultMessages('batch_4'), t('Nested batch performed successfully.'));
+ $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_4'), t('Execution order was correct.'));
+ $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
+ }
+
+ /**
+ * Test batches defined in a multistep form.
+ */
+ function testBatchFormMultistep() {
+ $this->drupalGet('batch_test/multistep');
+ $this->assertText('step 1', t('Form is displayed in step 1.'));
+
+ // First step triggers batch 1.
+ $this->drupalPost(NULL, array(), 'Submit');
+ $this->assertBatchMessages($this->_resultMessages('batch_1'), t('Batch for step 1 performed successfully.'));
+ $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), t('Execution order was correct.'));
+ $this->assertText('step 2', t('Form is displayed in step 2.'));
+
+ // Second step triggers batch 2.
+ $this->drupalPost(NULL, array(), 'Submit');
+ $this->assertBatchMessages($this->_resultMessages('batch_2'), t('Batch for step 2 performed successfully.'));
+ $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_2'), t('Execution order was correct.'));
+ $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
+ }
+
+ /**
+ * Test batches defined in different submit handlers on the same form.
+ */
+ function testBatchFormMultipleBatches() {
+ // Batches 1, 2 and 3 are triggered in sequence by different submit
+ // handlers. Each submit handler modify the submitted 'value'.
+ $value = rand(0, 255);
+ $edit = array('value' => $value);
+ $this->drupalPost('batch_test/chained', $edit, 'Submit');
+ // Check that result messages are present and in the correct order.
+ $this->assertBatchMessages($this->_resultMessages('chained'), t('Batches defined in separate submit handlers performed successfully.'));
+ // The stack contains execution order of batch callbacks and submit
+ // hanlders and logging of corresponding $form_state[{values'].
+ $this->assertEqual(batch_test_stack(), $this->_resultStack('chained', $value), t('Execution order was correct, and $form_state is correctly persisted.'));
+ $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
+ }
+
+ /**
+ * Test batches defined in a programmatically submitted form.
+ *
+ * Same as above, but the form is submitted through drupal_form_execute().
+ */
+ function testBatchFormProgrammatic() {
+ // Batches 1, 2 and 3 are triggered in sequence by different submit
+ // handlers. Each submit handler modify the submitted 'value'.
+ $value = rand(0, 255);
+ $this->drupalGet('batch_test/programmatic/' . $value);
+ // Check that result messages are present and in the correct order.
+ $this->assertBatchMessages($this->_resultMessages('chained'), t('Batches defined in separate submit handlers performed successfully.'));
+ // The stack contains execution order of batch callbacks and submit
+ // hanlders and logging of corresponding $form_state[{values'].
+ $this->assertEqual(batch_test_stack(), $this->_resultStack('chained', $value), t('Execution order was correct, and $form_state is correctly persisted.'));
+ $this->assertText('Got out of a programmatic batched form.', t('Page execution continues normally.'));
+ }
+
+ /**
+ * Test that drupal_form_submit() can run within a batch operation.
+ */
+ function testDrupalFormSubmitInBatch() {
+ // Displaying the page triggers a batch that programmatically submits a
+ // form.
+ $value = rand(0, 255);
+ $this->drupalGet('batch_test/nested_programmatic/' . $value);
+ $this->assertEqual(batch_test_stack(), array('mock form submitted with value = ' . $value), t('drupal_form_submit() ran successfully within a batch operation.'));
+ }
+
+ /**
+ * Will trigger a pass if the texts were found in order in the raw content.
+ *
+ * @param $texts
+ * Array of raw strings to look for .
+ * @param $message
+ * Message to display.
+ * @return
+ * TRUE on pass, FALSE on fail.
+ */
+ function assertBatchMessages($texts, $message) {
+ $pattern = '|' . implode('.*', $texts) .'|s';
+ return $this->assertPattern($pattern, $message);
+ }
+
+ /**
+ * Helper function: return expected execution stacks for the test batches.
+ */
+ function _resultStack($id, $value = 0) {
+ $stack = array();
+ switch ($id) {
+ case 'batch_1':
+ for ($i = 1; $i <= 10; $i++) {
+ $stack[] = "op 1 id $i";
+ }
+ break;
+
+ case 'batch_2':
+ for ($i = 1; $i <= 10; $i++) {
+ $stack[] = "op 2 id $i";
+ }
+ break;
+
+ case 'batch_3':
+ for ($i = 1; $i <= 5; $i++) {
+ $stack[] = "op 1 id $i";
+ }
+ for ($i = 1; $i <= 5; $i++) {
+ $stack[] = "op 2 id $i";
+ }
+ for ($i = 6; $i <= 10; $i++) {
+ $stack[] = "op 1 id $i";
+ }
+ for ($i = 6; $i <= 10; $i++) {
+ $stack[] = "op 2 id $i";
+ }
+ break;
+
+ case 'batch_4':
+ for ($i = 1; $i <= 5; $i++) {
+ $stack[] = "op 1 id $i";
+ }
+ $stack[] = 'setting up batch 2';
+ for ($i = 6; $i <= 10; $i++) {
+ $stack[] = "op 1 id $i";
+ }
+ $stack = array_merge($stack, $this->_resultStack('batch_2'));
+ break;
+
+ case 'chained':
+ $stack[] = 'submit handler 1';
+ $stack[] = 'value = ' . $value;
+ $stack = array_merge($stack, $this->_resultStack('batch_1'));
+ $stack[] = 'submit handler 2';
+ $stack[] = 'value = ' . ($value + 1);
+ $stack = array_merge($stack, $this->_resultStack('batch_2'));
+ $stack[] = 'submit handler 3';
+ $stack[] = 'value = ' . ($value + 2);
+ $stack[] = 'submit handler 4';
+ $stack[] = 'value = ' . ($value + 3);
+ $stack = array_merge($stack, $this->_resultStack('batch_3'));
+ break;
+ }
+ return $stack;
+ }
+
+ /**
+ * Helper function: return expected result messages for the test batches.
+ */
+ function _resultMessages($id) {
+ $messages = array();
+
+ switch ($id) {
+ case 'batch_0':
+ $messages[] = 'results for batch 0<br />none';
+ break;
+
+ case 'batch_1':
+ $messages[] = 'results for batch 1<br />op 1: processed 10 elements';
+ break;
+
+ case 'batch_2':
+ $messages[] = 'results for batch 2<br />op 2: processed 10 elements';
+ break;
+
+ case 'batch_3':
+ $messages[] = 'results for batch 3<br />op 1: processed 10 elements<br />op 2: processed 10 elements';
+ break;
+
+ case 'batch_4':
+ $messages[] = 'results for batch 4<br />op 1: processed 10 elements';
+ $messages = array_merge($messages, $this->_resultMessages('batch_2'));
+ break;
+
+ case 'chained':
+ $messages = array_merge($messages, $this->_resultMessages('batch_1'));
+ $messages = array_merge($messages, $this->_resultMessages('batch_2'));
+ $messages = array_merge($messages, $this->_resultMessages('batch_3'));
+ break;
+ }
+ return $messages;
+ }
+}
+
+/**
+ * Tests for the Batch API Progress page.
+ */
+class BatchPageTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Batch progress page',
+ 'description' => 'Test the content of the progress page.',
+ 'group' => 'Batch API',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('batch_test');
}
/**
* Tests that the batch API progress page uses the correct theme.
*/
- function testBatchAPIProgressPageTheme() {
+ function testBatchProgressPageTheme() {
+ // Make sure that the page which starts the batch (an administrative page)
+ // is using a different theme than would normally be used by the batch API.
+ variable_set('theme_default', 'garland');
+ variable_set('admin_theme', 'seven');
// Visit an administrative page that runs a test batch, and check that the
// theme that was used during batch execution (which the batch callback
// function saved as a variable) matches the theme used on the
// administrative page.
- $this->drupalGet('admin/system-test/batch-theme');
- $batch_theme_used = variable_get('system_test_batch_theme_used', 'garland');
- $this->assertEqual($batch_theme_used, 'seven', t('A progressive batch correctly uses the theme of the page that started the batch.'));
+ $this->drupalGet('admin/batch_test/test_theme');
+ // The stack should contain the name of the the used on the progress page.
+ $this->assertEqual(batch_test_stack(), array('seven'), t('A progressive batch correctly uses the theme of the page that started the batch.'));
}
}
@@ -44,13 +290,13 @@ class BatchAPIThemeTestCase extends DrupalWebTestCase {
* Tests the function _batch_api_percentage() to make sure that the rounding
* works properly in all cases.
*/
-class BatchAPIPercentagesTestCase extends DrupalWebTestCase {
+class BatchPercentagesUnitTestCase extends DrupalUnitTestCase {
protected $testCases = array();
public static function getInfo() {
return array(
- 'name' => 'Batch API percentages',
- 'description' => 'Tests the handling of percentage rounding in the Drupal batch API. This is critical to Drupal user experience.',
+ 'name' => 'Batch percentages',
+ 'description' => 'Unit tests of progress percentage rounding.',
'group' => 'Batch API',
);
}
@@ -99,10 +345,9 @@ class BatchAPIPercentagesTestCase extends DrupalWebTestCase {
}
/**
- * Test the _batch_api_percentage() function with the data stored in the
- * testCases class variable.
+ * Test the _batch_api_percentage() function.
*/
- function testBatchAPIPercentages() {
+ function testBatchPercentages() {
require_once DRUPAL_ROOT . '/includes/batch.inc';
foreach ($this->testCases as $expected_result => $arguments) {
// PHP sometimes casts numeric strings that are array keys to integers,
diff --git a/modules/simpletest/tests/batch_test.callbacks.inc b/modules/simpletest/tests/batch_test.callbacks.inc
new file mode 100644
index 000000000..5f24757ee
--- /dev/null
+++ b/modules/simpletest/tests/batch_test.callbacks.inc
@@ -0,0 +1,118 @@
+<?php
+
+// $Id$
+
+/**
+ * @file
+ * Batch callbacks for the Batch API tests.
+ */
+
+/**
+ * Simple batch operation.
+ */
+function _batch_test_callback_1($id, $sleep, &$context) {
+ // No-op, but ensure the batch take a couple iterations.
+ usleep($sleep);
+ // Track execution, and store some result for post-processing in the
+ // 'finished' callback.
+ batch_test_stack("op 1 id $id");
+ $context['results'][1][] = $id;
+}
+
+/**
+ * Multistep batch operation.
+ */
+function _batch_test_callback_2($start, $total, $sleep, &$context) {
+ // Initialize context with progress information.
+ if (!isset($context['sandbox']['current'])) {
+ $context['sandbox']['current'] = $start;
+ $context['sandbox']['count'] = 0;
+ }
+
+ // Process by groups of 5 (arbitrary value).
+ $limit = 5;
+ for ($i = 0; $i < $limit && $context['sandbox']['count'] < $total; $i++) {
+ // No-op, but ensure the batch take a couple iterations.
+ usleep($sleep);
+ // Track execution, and store some result for post-processing in the
+ // 'finished' callback.
+ $id = $context['sandbox']['current'] + $i;
+ batch_test_stack("op 2 id $id");
+ $context['results'][2][] = $id;
+
+ // Update progress information.
+ $context['sandbox']['count']++;
+ }
+ $context['sandbox']['current'] += $i;
+
+ // Inform batch engine about progress.
+ if ($context['sandbox']['count'] != $total) {
+ $context['finished'] = $context['sandbox']['count'] / $total;
+ }
+}
+
+/**
+ * Batch operation setting up its own batch.
+ */
+function _batch_test_nested_batch_callback() {
+ batch_test_stack('setting up batch 2');
+ batch_set(_batch_test_batch_2());
+}
+
+/**
+ * Common 'finished' callbacks for batches 1 to 4.
+ */
+function _batch_test_finished_helper($batch_id, $success, $results, $operations) {
+ $messages = array("results for batch $batch_id");
+ if ($results) {
+ foreach ($results as $op => $op_results) {
+ $messages[] = 'op '. $op . ': processed ' . count($op_results) . ' elements';
+ }
+ }
+ else {
+ $messages[] = 'none';
+ }
+
+ if (!$success) {
+ // A fatal error occurred during the processing.
+ $error_operation = reset($operations);
+ $messages[] = t('An error occurred while processing @op with arguments:<br/>@args', array('@op' => $error_operation[0], '@args' => print_r($error_operation[1], TRUE)));
+ }
+
+ drupal_set_message(implode('<br />', $messages));
+}
+
+/**
+ * 'finished' callback for batch 0.
+ */
+function _batch_test_finished_0($success, $results, $operations) {
+ _batch_test_finished_helper(0, $success, $results, $operations);
+}
+
+/**
+ * 'finished' callback for batch 1.
+ */
+function _batch_test_finished_1($success, $results, $operations) {
+ _batch_test_finished_helper(1, $success, $results, $operations);
+}
+
+/**
+ * 'finished' callback for batch 2.
+ */
+function _batch_test_finished_2($success, $results, $operations) {
+ _batch_test_finished_helper(2, $success, $results, $operations);
+}
+
+/**
+ * 'finished' callback for batch 3.
+ */
+function _batch_test_finished_3($success, $results, $operations) {
+ _batch_test_finished_helper(3, $success, $results, $operations);
+}
+
+/**
+ * 'finished' callback for batch 4.
+ */
+function _batch_test_finished_4($success, $results, $operations) {
+ _batch_test_finished_helper(4, $success, $results, $operations);
+}
diff --git a/modules/simpletest/tests/batch_test.info b/modules/simpletest/tests/batch_test.info
new file mode 100644
index 000000000..432c17ff7
--- /dev/null
+++ b/modules/simpletest/tests/batch_test.info
@@ -0,0 +1,9 @@
+; $Id$
+name = "Batch API test"
+description = "Support module for Batch API tests."
+package = Testing
+version = VERSION
+core = 7.x
+files[] = batch_test.module
+files[] = batch_test.callbacks.inc
+hidden = TRUE
diff --git a/modules/simpletest/tests/batch_test.module b/modules/simpletest/tests/batch_test.module
new file mode 100644
index 000000000..03938aaa8
--- /dev/null
+++ b/modules/simpletest/tests/batch_test.module
@@ -0,0 +1,475 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Helper module for the Batch API tests.
+ */
+
+/**
+ * Implement hook_menu().
+ */
+function batch_test_menu() {
+ $items = array();
+
+ $items['batch_test'] = array(
+ 'title' => 'Batch test',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('batch_test_simple_form'),
+ 'access callback' => TRUE,
+ );
+ // Simple form: one submit handler, setting a batch.
+ $items['batch_test/simple'] = array(
+ 'title' => 'Simple',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => 0,
+ );
+ // Multistep form: two steps, each setting a batch.
+ $items['batch_test/multistep'] = array(
+ 'title' => 'Multistep',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('batch_test_multistep_form'),
+ 'access callback' => TRUE,
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 1,
+ );
+ // Chained form: four submit handlers, several of which set a batch.
+ $items['batch_test/chained'] = array(
+ 'title' => 'Chained',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('batch_test_chained_form'),
+ 'access callback' => TRUE,
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 2,
+ );
+ // Programmatic form: the page submits the 'Chained' form through
+ // drupal_form_submit().
+ $items['batch_test/programmatic'] = array(
+ 'title' => 'Programmatic',
+ 'page callback' => 'batch_test_programmatic',
+ 'access callback' => TRUE,
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 3,
+ );
+ // No form: fire a batch simply by accessing a page.
+ $items['batch_test/no_form'] = array(
+ 'title' => 'Simple page',
+ 'page callback' => 'batch_test_no_form',
+ 'access callback' => TRUE,
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 4,
+ );
+ // Tests programmatic form submission within a batch operation.
+ $items['batch_test/nested_programmatic'] = array(
+ 'title' => 'Nested programmatic',
+ 'page callback' => 'batch_test_nested_drupal_form_submit',
+ 'access callback' => TRUE,
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 5,
+ );
+ // Landing page to test redirects.
+ $items['batch_test/redirect'] = array(
+ 'title' => 'Redirect',
+ 'page callback' => 'batch_test_redirect_page',
+ 'access callback' => TRUE,
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 6,
+ );
+ //
+ // This item lives under 'admin' so that the page uses the admin theme.
+ $items['admin/batch_test/test_theme'] = array(
+ 'page callback' => 'batch_test_theme_batch',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ );
+
+ return $items;
+}
+
+/**
+ * Simple form.
+ */
+function batch_test_simple_form() {
+ $form['batch'] = array(
+ '#type' => 'select',
+ '#title' => 'Choose batch',
+ '#options' => array(
+ 'batch_0' => 'batch 0',
+ 'batch_1' => 'batch 1',
+ 'batch_2' => 'batch 2',
+ 'batch_3' => 'batch 3',
+ 'batch_4' => 'batch 4',
+ ),
+ );
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => 'Submit',
+ );
+
+ return $form;
+}
+
+/**
+ * Submit handler for the simple form.
+ */
+function batch_test_simple_form_submit($form, &$form_state) {
+ batch_test_stack(NULL, TRUE);
+
+ $function = '_batch_test_' . $form_state['values']['batch'];
+ batch_set($function());
+
+ $form_state['redirect'] = 'batch_test/redirect';
+}
+
+
+/**
+ * Multistep form.
+ */
+function batch_test_multistep_form($form, &$form_state) {
+ if (empty($form_state['storage']['step'])) {
+ $form_state['storage']['step'] = 1;
+ }
+
+ $form['step_display'] = array(
+ '#markup' => 'step ' . $form_state['storage']['step'] . '<br/>',
+ );
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => 'Submit',
+ );
+
+ return $form;
+}
+
+/**
+ * Submit handler for the multistep form.
+ */
+function batch_test_multistep_form_submit($form, &$form_state) {
+ batch_test_stack(NULL, TRUE);
+
+ switch ($form_state['storage']['step']) {
+ case 1:
+ batch_set(_batch_test_batch_1());
+ break;
+ case 2:
+ batch_set(_batch_test_batch_2());
+ break;
+ }
+
+ if ($form_state['storage']['step'] < 2) {
+ $form_state['storage']['step']++;
+ $form_state['rebuild'] = TRUE;
+ }
+
+ // This will only be effective on the last step.
+ $form_state['redirect'] = 'batch_test/redirect';
+}
+
+/**
+ * Form with chained submit callbacks.
+ */
+function batch_test_chained_form() {
+ // This value is used to test that $form_state persists through batched
+ // submit handlers.
+ $form['value'] = array(
+ '#type' => 'textfield',
+ '#title' => 'Value',
+ '#default_value' => 1,
+ );
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => 'Submit',
+ );
+ $form['#submit'] = array(
+ 'batch_test_chained_form_submit_1',
+ 'batch_test_chained_form_submit_2',
+ 'batch_test_chained_form_submit_3',
+ 'batch_test_chained_form_submit_4',
+ );
+
+ return $form;
+}
+
+/**
+ * Submit handler #1 for the chained form.
+ */
+function batch_test_chained_form_submit_1($form, &$form_state) {
+ batch_test_stack(NULL, TRUE);
+
+ batch_test_stack('submit handler 1');
+ batch_test_stack('value = ' . $form_state['values']['value']);
+
+ $form_state['values']['value']++;
+ batch_set(_batch_test_batch_1());
+
+ // This redirect should not be taken into account.
+ $form_state['redirect'] = 'should/be/discarded';
+}
+
+/**
+ * Submit handler #2 for the chained form.
+ */
+function batch_test_chained_form_submit_2($form, &$form_state) {
+ batch_test_stack('submit handler 2');
+ batch_test_stack('value = ' . $form_state['values']['value']);
+
+ $form_state['values']['value']++;
+ batch_set(_batch_test_batch_2());
+
+ // This redirect should not be taken into account.
+ $form_state['redirect'] = 'should/be/discarded';
+}
+
+/**
+ * Submit handler #3 for the chained form.
+ */
+function batch_test_chained_form_submit_3($form, &$form_state) {
+ batch_test_stack('submit handler 3');
+ batch_test_stack('value = ' . $form_state['values']['value']);
+
+ $form_state['values']['value']++;
+
+ // This redirect should not be taken into account.
+ $form_state['redirect'] = 'should/be/discarded';
+}
+
+/**
+ * Submit handler #4 for the chained form.
+ */
+function batch_test_chained_form_submit_4($form, &$form_state) {
+ batch_test_stack('submit handler 4');
+ batch_test_stack('value = ' . $form_state['values']['value']);
+
+ $form_state['values']['value']++;
+ batch_set(_batch_test_batch_3());
+
+ // This is the redirect that should prevail.
+ $form_state['redirect'] = 'batch_test/redirect';
+}
+
+/**
+ * Menu callback: programmatically submits the 'Chained' form.
+ */
+function batch_test_programmatic($value = 1) {
+ $form_state = array(
+ 'values' => array('value' => $value)
+ );
+ drupal_form_submit('batch_test_chained_form', $form_state);
+ return 'Got out of a programmatic batched form.';
+}
+
+/**
+ * Menu callback: programmatically submits a form within a batch.
+ */
+function batch_test_nested_drupal_form_submit($value = 1) {
+ // Set the batch and process it.
+ $batch['operations'] = array(
+ array('_batch_test_nested_drupal_form_submit_callback', array($value)),
+ );
+ batch_set($batch);
+ batch_process('batch_test/redirect');
+}
+
+/**
+ * Batch operation: submits form_test_mock_form using drupal_form_submit().
+ */
+function _batch_test_nested_drupal_form_submit_callback($value) {
+ $state['values']['test_value'] = $value;
+ drupal_form_submit('batch_test_mock_form', $state);
+}
+
+/**
+ * A simple form with a textfield and submit button.
+ */
+function batch_test_mock_form($form, $form_state) {
+ $form['test_value'] = array(
+ '#type' => 'textfield',
+ );
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Submit'),
+ );
+
+ return $form;
+}
+
+/**
+ * Submit handler for the batch_test_mock form.
+ */
+function batch_test_mock_form_submit($form, &$form_state) {
+ batch_test_stack('mock form submitted with value = ' . $form_state['values']['test_value']);
+}
+
+/**
+ * Menu callback: fire a batch process without a form submission.
+ */
+function batch_test_no_form() {
+ batch_test_stack(NULL, TRUE);
+
+ batch_set(_batch_test_batch_1());
+ batch_process('batch_test/redirect');
+}
+
+/**
+ * Menu callback: successful redirection.
+ */
+function batch_test_redirect_page() {
+ return 'Redirection successful.';
+}
+
+/**
+ * Batch 0: no operation.
+ */
+function _batch_test_batch_0() {
+ $batch = array(
+ 'operations' => array(),
+ 'finished' => '_batch_test_finished_0',
+ 'file' => drupal_get_path('module', 'batch_test'). '/batch_test.callbacks.inc',
+ );
+ return $batch;
+}
+
+/**
+ * Batch 1: repeats a simple operation.
+ *
+ * Operations: op 1 from 1 to 10.
+ */
+function _batch_test_batch_1() {
+ // Ensure the batch takes at least two iterations.
+ $total = 10;
+ $sleep = (1000000 / $total) * 2;
+
+ $operations = array();
+ for ($i = 1; $i <= $total; $i++) {
+ $operations[] = array('_batch_test_callback_1', array($i, $sleep));
+ }
+ $batch = array(
+ 'operations' => $operations,
+ 'finished' => '_batch_test_finished_1',
+ 'file' => drupal_get_path('module', 'batch_test'). '/batch_test.callbacks.inc',
+ );
+ return $batch;
+}
+
+/**
+ * Batch 2: single multistep operation.
+ *
+ * Operations: op 2 from 1 to 10.
+ */
+function _batch_test_batch_2() {
+ // Ensure the batch takes at least two iterations.
+ $total = 10;
+ $sleep = (1000000 / $total) * 2;
+
+ $operations = array(
+ array('_batch_test_callback_2', array(1, $total, $sleep)),
+ );
+ $batch = array(
+ 'operations' => $operations,
+ 'finished' => '_batch_test_finished_2',
+ 'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
+ );
+ return $batch;
+}
+
+/**
+ * Batch 3: both single and multistep operations.
+ *
+ * Operations:
+ * - op 1 from 1 to 5,
+ * - op 2 from 1 to 5,
+ * - op 1 from 6 to 10,
+ * - op 2 from 6 to 10.
+ */
+function _batch_test_batch_3() {
+ // Ensure the batch takes at least two iterations.
+ $total = 10;
+ $sleep = (1000000 / $total) * 2;
+
+ $operations = array();
+ for ($i = 1; $i <= round($total / 2); $i++) {
+ $operations[] = array('_batch_test_callback_1', array($i, $sleep));
+ }
+ $operations[] = array('_batch_test_callback_2', array(1, $total / 2, $sleep));
+ for ($i = round($total / 2) + 1; $i <= $total; $i++) {
+ $operations[] = array('_batch_test_callback_1', array($i, $sleep));
+ }
+ $operations[] = array('_batch_test_callback_2', array(6, $total / 2, $sleep));
+ $batch = array(
+ 'operations' => $operations,
+ 'finished' => '_batch_test_finished_3',
+ 'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
+ );
+ return $batch;
+}
+
+/**
+ * Batch 4: batch within a batch.
+ *
+ * Operations:
+ * - op 1 from 1 to 5,
+ * - set batch 2 (op 2 from 1 to 10, should run at the end)
+ * - op 1 from 6 to 10,
+ */
+function _batch_test_batch_4() {
+ // Ensure the batch takes at least two iterations.
+ $total = 10;
+ $sleep = (1000000 / $total) * 2;
+
+ $operations = array();
+ for ($i = 1; $i <= round($total / 2); $i++) {
+ $operations[] = array('_batch_test_callback_1', array($i, $sleep));
+ }
+ $operations[] = array('_batch_test_nested_batch_callback', array());
+ for ($i = round($total / 2) + 1; $i <= $total; $i++) {
+ $operations[] = array('_batch_test_callback_1', array($i, $sleep));
+ }
+ $batch = array(
+ 'operations' => $operations,
+ 'finished' => '_batch_test_finished_4',
+ 'file' => drupal_get_path('module', 'batch_test') . '/batch_test.callbacks.inc',
+ );
+ return $batch;
+}
+
+/**
+ * Menu callback: run a batch for testing theme used on the progress page.
+ */
+function batch_test_theme_batch() {
+ batch_test_stack(NULL, TRUE);
+ $batch = array(
+ 'operations' => array(
+ array('_batch_test_theme_callback', array()),
+ ),
+ );
+ batch_set($batch);
+ batch_process('batch_test/redirect');
+}
+
+/**
+ * Batch callback function for testing the theme used on the progress page.
+ */
+function _batch_test_theme_callback() {
+ // Because drupalGet() steps through the full progressive batch before
+ // returning control to the test function, we cannot test that the correct
+ // theme is being used on the batch processing page by viewing that page
+ // directly. Instead, we save the theme being used in a variable here, so
+ // that it can be loaded and inspected in the thread running the test.
+ global $theme;
+ batch_test_stack($theme);
+}
+
+/**
+ * Helper function: store or retrieve traced execution data.
+ */
+function batch_test_stack($data = NULL, $reset = FALSE) {
+ if ($reset) {
+ variable_del('batch_test_stack');
+ }
+ if (is_null($data)) {
+ return variable_get('batch_test_stack', array());
+ }
+ $stack = variable_get('batch_test_stack', array());
+ $stack[] = $data;
+ variable_set('batch_test_stack', $stack);
+}
diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test
index 108391777..b7dec7b6a 100644
--- a/modules/simpletest/tests/form.test
+++ b/modules/simpletest/tests/form.test
@@ -512,45 +512,6 @@ class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
}
/**
- * Test using drupal_form_submit in a batch.
- */
-class FormAPITestCase extends DrupalWebTestCase {
-
- public static function getInfo() {
- return array(
- 'name' => 'Drupal Execute and Batch API',
- 'description' => 'Tests the compatibility of drupal_form_submit and the Batch API',
- 'group' => 'Form API',
- );
- }
-
- /**
- * Check that we can run drupal_form_submit during a batch.
- */
- function testDrupalFormSubmitInBatch() {
-
- // Our test is going to modify the following variable.
- variable_set('form_test_mock_submit', 'initial_state');
-
- // This is a page that sets a batch, which calls drupal_form_submit, which
- // modifies the variable we set up above.
- $this->drupalGet('form_test/drupal_form_submit_batch_api');
-
- // If the drupal_form_submit call executed correctly our test variable will be
- // set to 'form_submitted'.
- $this->assertEqual('form_submitted', variable_get('form_test_mock_submit', 'initial_state'), t('Check drupal_form_submit called submit handlers when running in a batch'));
-
- // Clean our variable up.
- variable_del('form_test_mock_submit');
- }
-
- function setUp() {
- parent::setUp('form_test');
- }
-
-}
-
-/**
* Test the form storage on a multistep form.
*
* The tested form puts data into the storage during the initial form
diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module
index c8f565938..54b6299f8 100644
--- a/modules/simpletest/tests/form_test.module
+++ b/modules/simpletest/tests/form_test.module
@@ -54,13 +54,6 @@ function form_test_menu() {
'type' => MENU_CALLBACK,
);
- $items['form_test/drupal_form_submit_batch_api'] = array(
- 'title' => 'BatchAPI Drupal_form_submit tests',
- 'page callback' => 'form_test_drupal_form_submit_batch_api',
- 'access arguments' => array('access content'),
- 'type' => MENU_CALLBACK,
- );
-
$items['form_test/form-storage'] = array(
'title' => 'Form storage test',
'page callback' => 'drupal_get_form',
@@ -376,67 +369,6 @@ function _form_test_tableselect_js_select_form($form, $form_state, $action) {
}
/**
- * Page callback for the batch/drupal_form_submit interaction test.
- *
- * When called without any arguments we set up a batch that calls
- * form_test_batch_callback. That function will submit a form using
- * drupal_form_submit using the values specified in this function.
- *
- * The form's field test_value begins at 'initial_value', and is changed
- * to 'form_submitted' when the form is submitted successfully. On
- * completion this function is passed 'done' to complete the process.
- */
-function form_test_drupal_form_submit_batch_api($arg = '') {
- // If we're at the end of the batch process, return.
- if ($arg == 'done') {
- return t('Done');
- }
-
- // Otherwise set up the batch.
- $batch['operations'] = array(
- array('form_test_batch_callback', array('form_submitted')),
- );
-
- // Set the batch and process it.
- batch_set($batch);
- batch_process('form_test/drupal_form_submit_batch_api/done');
-}
-
-/**
- * Submits form_test_mock_form using drupal_form_submit using the given $value.
- */
-function form_test_batch_callback($value) {
- $state['values']['test_value'] = $value;
- drupal_form_submit('form_test_mock_form', $state);
-}
-
-/**
- * A simple form with a textfield and submit button.
- */
-function form_test_mock_form($form, $form_state) {
- $form['test_value'] = array(
- '#type' => 'textfield',
- '#default_value' => 'initial_state',
- );
-
- $form['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Submit'),
- );
-
- return $form;
-}
-
-/**
- * Form submission callback.
- *
- * Updates the variable 'form_test_mock_submit' to the submitted form value.
- */
-function form_test_mock_form_submit($form, &$form_state) {
- variable_set('form_test_mock_submit', $form_state['values']['test_value']);
-}
-
-/**
* A multistep form for testing the form storage.
*
* It uses two steps for editing a virtual "thing". Any changes to it are saved
diff --git a/modules/simpletest/tests/system_test.module b/modules/simpletest/tests/system_test.module
index 5c57ee20d..b37019f8c 100644
--- a/modules/simpletest/tests/system_test.module
+++ b/modules/simpletest/tests/system_test.module
@@ -5,11 +5,6 @@
* Implements hook_menu().
*/
function system_test_menu() {
- $items['admin/system-test/batch-theme'] = array(
- 'page callback' => 'system_test_batch_theme',
- 'access callback' => TRUE,
- 'type' => MENU_CALLBACK,
- );
$items['system-test/sleep/%'] = array(
'page callback' => 'system_test_sleep',
'page arguments' => array(2),
@@ -102,34 +97,6 @@ function system_test_menu() {
return $items;
}
-/**
- * Menu callback; start a new batch for testing the batch progress page theme.
- */
-function system_test_batch_theme() {
- $batch = array(
- 'operations' => array(
- array('system_test_batch_theme_callback', array()),
- ),
- );
- batch_set($batch);
- // Force the batch to redirect to some page other than this one (to avoid an
- // infinite loop).
- batch_process('node');
-}
-
-/**
- * Batch callback function for testing the theme used by a batch.
- */
-function system_test_batch_theme_callback() {
- // Because drupalGet() steps through the full progressive batch before
- // returning control to the test function, we cannot test that the correct
- // theme is being used on the batch processing page by viewing that page
- // directly. Instead, we save the theme being used in a variable here, so
- // that it can be loaded and inspected in the thread running the test.
- global $theme;
- variable_set('system_test_batch_theme_used', $theme);
-}
-
function system_test_sleep($seconds) {
sleep($seconds);
}
diff --git a/modules/system/system.install b/modules/system/system.install
index add4c37c4..519cbabda 100644
--- a/modules/system/system.install
+++ b/modules/system/system.install
@@ -496,7 +496,9 @@ function system_schema() {
'fields' => array(
'bid' => array(
'description' => 'Primary Key: Unique batch ID.',
- 'type' => 'serial',
+ // This is not a serial column, to allow both progressive and
+ // non-progressive batches. See batch_process().
+ 'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
),
@@ -2257,50 +2259,7 @@ function system_update_7021() {
* Add the queue tables.
*/
function system_update_7022() {
- $schema['queue'] = array(
- 'description' => 'Stores items in queues.',
- 'fields' => array(
- 'item_id' => array(
- 'type' => 'serial',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- 'description' => 'Primary Key: Unique item ID.',
- ),
- 'name' => array(
- 'type' => 'varchar',
- 'length' => 255,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'The queue name.',
- ),
- 'data' => array(
- 'type' => 'text',
- 'not null' => FALSE,
- 'size' => 'big',
- 'serialize' => TRUE,
- 'description' => 'The arbitrary data for the item.',
- ),
- 'expire' => array(
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- 'description' => 'Timestamp when the claim lease expires on the item.',
- ),
- 'created' => array(
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- 'description' => 'Timestamp when the item was created.',
- ),
- ),
- 'primary key' => array('item_id'),
- 'indexes' => array(
- 'name_created' => array('name', 'created'),
- 'expire' => array('expire'),
- ),
- );
-
- db_create_table('queue', $schema['queue']);
+ // Moved to update_fix_d7_requirements().
}
/**
@@ -2728,6 +2687,13 @@ function system_update_7049() {
}
/**
+ * Change {batch}.id column from serial to regular int.
+ */
+function system_update_7050() {
+ db_change_field('batch', 'bid', 'bid', array('description' => 'Primary Key: Unique batch ID.', 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE));
+}
+
+/**
* @} End of "defgroup updates-6.x-to-7.x"
* The next series of updates should start at 8000.
*/
diff --git a/modules/system/system.module b/modules/system/system.module
index e0da1720b..6247fcb2f 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -2695,10 +2695,6 @@ function system_cron() {
db_delete('flood')
->condition('expiration', REQUEST_TIME, '<')
->execute();
- // Cleanup the batch table.
- db_delete('batch')
- ->condition('timestamp', REQUEST_TIME - 864000, '<')
- ->execute();
// Remove temporary files that are older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
// Use separate placeholders for the status to avoid a bug in some versions
@@ -2722,6 +2718,15 @@ function system_cron() {
cache_clear_all(NULL, $table);
}
+ // Cleanup the batch table and the queue for failed batches.
+ db_delete('batch')
+ ->condition('timestamp', REQUEST_TIME - 864000, '<')
+ ->execute();
+ db_delete('queue')
+ ->condition('created', REQUEST_TIME - 864000, '<')
+ ->condition('name', 'drupal_batch:%', 'LIKE')
+ ->execute();
+
// Reset expired items in the default queue implementation table. If that's
// not used, this will simply be a no-op.
db_update('queue')
diff --git a/modules/system/system.queue.inc b/modules/system/system.queue.inc
index 5174a0a65..bac1ff26e 100644
--- a/modules/system/system.queue.inc
+++ b/modules/system/system.queue.inc
@@ -254,5 +254,78 @@ class SystemQueue implements DrupalQueueInterface {
}
/**
+ * Static queue implementation.
+ *
+ * This allows "undelayed" variants of processes relying on the Queue
+ * interface. The queue data resides in memory. It should only be used for
+ * items that will be queued and dequeued within a given page request.
+ */
+class MemoryQueue implements DrupalQueueInterface {
+ /**
+ * The queue data.
+ *
+ * @var array
+ */
+ protected $queue;
+
+ /**
+ * Counter for item ids.
+ *
+ * @var int
+ */
+ protected $id_sequence;
+
+ public function __construct($name) {
+ $this->queue = array();
+ $this->id_sequence = 0;
+ }
+
+ public function createItem($data) {
+ $item = new stdClass();
+ $item->item_id = $this->id_sequence++;
+ $item->data = $data;
+ $item->created = time();
+ $item->expire = 0;
+ $this->queue[$item->item_id] = $item;
+ }
+
+ public function numberOfItems() {
+ return count($this->queue);
+ }
+
+ public function claimItem($lease_time = 30) {
+ foreach ($this->queue as $key => $item) {
+ if ($item->expire == 0) {
+ $item->expire = time() + $lease_time;
+ $this->queue[$key] = $item;
+ return $item;
+ }
+ }
+ return FALSE;
+ }
+
+ public function deleteItem($item) {
+ unset($this->queue[$item->item_id]);
+ }
+
+ public function releaseItem($item) {
+ if (isset($this->queue[$item->item_id]) && $this->queue[$item->item_id]->expire != 0) {
+ $this->queue[$item->item_id]->expire = 0;
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ public function createQueue() {
+ // Nothing needed here.
+ }
+
+ public function deleteQueue() {
+ $this->queue = array();
+ $this->id_sequence = 0;
+ }
+}
+
+/**
* @} End of "defgroup queue".
*/