#629794 by yched: Fix Scaling issues with batch API. (with tests)
parent
e07b9d35a1
commit
0dd1612770
|
@ -6,7 +6,7 @@
|
||||||
* @file
|
* @file
|
||||||
* Batch processing API for processes to run in multiple HTTP requests.
|
* Batch processing API for processes to run in multiple HTTP requests.
|
||||||
*
|
*
|
||||||
* Please note that batches are usually invoked by form submissions, which is
|
* Note that batches are usually invoked by form submissions, which is
|
||||||
* why the core interaction functions of the batch processing API live in
|
* why the core interaction functions of the batch processing API live in
|
||||||
* form.inc.
|
* form.inc.
|
||||||
*
|
*
|
||||||
|
@ -62,8 +62,10 @@ function _batch_page() {
|
||||||
|
|
||||||
// Add batch-specific CSS.
|
// Add batch-specific CSS.
|
||||||
foreach ($batch['sets'] as $batch_set) {
|
foreach ($batch['sets'] as $batch_set) {
|
||||||
foreach ($batch_set['css'] as $css) {
|
if (isset($batch_set['css'])) {
|
||||||
drupal_add_css($css);
|
foreach ($batch_set['css'] as $css) {
|
||||||
|
drupal_add_css($css);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,6 +254,12 @@ function _batch_process() {
|
||||||
timer_start('batch_processing');
|
timer_start('batch_processing');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (empty($current_set['start'])) {
|
||||||
|
$current_set['start'] = microtime(TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
$queue = _batch_queue($current_set);
|
||||||
|
|
||||||
while (!$current_set['success']) {
|
while (!$current_set['success']) {
|
||||||
// If this is the first time we iterate this batch set in the current
|
// If this is the first time we iterate this batch set in the current
|
||||||
// request, we check if it requires an additional file for functions
|
// request, we check if it requires an additional file for functions
|
||||||
|
@ -261,42 +269,49 @@ function _batch_process() {
|
||||||
}
|
}
|
||||||
|
|
||||||
$task_message = '';
|
$task_message = '';
|
||||||
// We assume a single pass operation and set the completion level to 1 by
|
// Assume a single pass operation and set the completion level to 1 by
|
||||||
// default.
|
// default.
|
||||||
$finished = 1;
|
$finished = 1;
|
||||||
if ((list($function, $args) = reset($current_set['operations'])) && function_exists($function)) {
|
|
||||||
// Build the 'context' array, execute the function call, and retrieve the
|
if ($item = $queue->claimItem()) {
|
||||||
// user message.
|
list($function, $args) = $item->data;
|
||||||
|
|
||||||
|
// Build the 'context' array and execute the function call.
|
||||||
$batch_context = array(
|
$batch_context = array(
|
||||||
'sandbox' => &$current_set['sandbox'],
|
'sandbox' => &$current_set['sandbox'],
|
||||||
'results' => &$current_set['results'],
|
'results' => &$current_set['results'],
|
||||||
'finished' => &$finished,
|
'finished' => &$finished,
|
||||||
'message' => &$task_message,
|
'message' => &$task_message,
|
||||||
);
|
);
|
||||||
// Process the current operation.
|
|
||||||
call_user_func_array($function, array_merge($args, array(&$batch_context)));
|
call_user_func_array($function, array_merge($args, array(&$batch_context)));
|
||||||
}
|
|
||||||
|
|
||||||
if ($finished == 1) {
|
if ($finished == 1) {
|
||||||
// Make sure this step is not counted twice when computing $current.
|
// Make sure this step is not counted twice when computing $current.
|
||||||
$finished = 0;
|
$finished = 0;
|
||||||
// Remove the processed operation and clear the sandbox.
|
// Remove the processed operation and clear the sandbox.
|
||||||
array_shift($current_set['operations']);
|
$queue->deleteItem($item);
|
||||||
$current_set['sandbox'] = array();
|
$current_set['count']--;
|
||||||
|
$current_set['sandbox'] = array();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When all operations in the current batch set are completed, browse
|
// When all operations in the current batch set are completed, browse
|
||||||
// through the remaining sets until we find a set that contains operations.
|
// through the remaining sets, marking them 'successfully processed'
|
||||||
// Note that _batch_next_set() executes stored form submit handlers in
|
// along the way, until we find a set that contains operations.
|
||||||
// remaining batch sets, which can add new sets to the batch.
|
// _batch_next_set() executes form submit handlers stored in 'control'
|
||||||
|
// sets (see form_execute_handlers()), which can in turn add new sets to
|
||||||
|
// the batch.
|
||||||
$set_changed = FALSE;
|
$set_changed = FALSE;
|
||||||
$old_set = $current_set;
|
$old_set = $current_set;
|
||||||
while (empty($current_set['operations']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
|
while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
|
||||||
$current_set = &_batch_current_set();
|
$current_set = &_batch_current_set();
|
||||||
|
$current_set['start'] = microtime(TRUE);
|
||||||
$set_changed = TRUE;
|
$set_changed = TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, either $current_set contains operations that need to be
|
// At this point, either $current_set contains operations that need to be
|
||||||
// processed or all sets have been completed.
|
// processed or all sets have been completed.
|
||||||
|
$queue = _batch_queue($current_set);
|
||||||
|
|
||||||
// If we are in progressive mode, break processing after 1 second.
|
// If we are in progressive mode, break processing after 1 second.
|
||||||
if ($batch['progressive'] && timer_read('batch_processing') > 1000) {
|
if ($batch['progressive'] && timer_read('batch_processing') > 1000) {
|
||||||
|
@ -312,33 +327,31 @@ function _batch_process() {
|
||||||
// Reporting 100% progress will cause the whole batch to be considered
|
// Reporting 100% progress will cause the whole batch to be considered
|
||||||
// processed. If processing was paused right after moving to a new set,
|
// processed. If processing was paused right after moving to a new set,
|
||||||
// we have to use the info from the new (unprocessed) set.
|
// we have to use the info from the new (unprocessed) set.
|
||||||
if ($set_changed && isset($current_set['operations'])) {
|
if ($set_changed && isset($current_set['queue'])) {
|
||||||
// Processing will continue with a fresh batch set.
|
// Processing will continue with a fresh batch set.
|
||||||
$remaining = count($current_set['operations']);
|
$remaining = $current_set['count'];
|
||||||
$total = $current_set['total'];
|
$total = $current_set['total'];
|
||||||
$progress_message = $current_set['init_message'];
|
$progress_message = $current_set['init_message'];
|
||||||
$task_message = '';
|
$task_message = '';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Processing will continue with the current batch set.
|
// Processing will continue with the current batch set.
|
||||||
$remaining = count($old_set['operations']);
|
$remaining = $old_set['count'];
|
||||||
$total = $old_set['total'];
|
$total = $old_set['total'];
|
||||||
$progress_message = $old_set['progress_message'];
|
$progress_message = $old_set['progress_message'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$current = $total - $remaining + $finished;
|
$current = $total - $remaining + $finished;
|
||||||
$percentage = _batch_api_percentage($total, $current);
|
$percentage = _batch_api_percentage($total, $current);
|
||||||
|
|
||||||
$elapsed = $current_set['elapsed'];
|
$elapsed = $current_set['elapsed'];
|
||||||
// Estimate remaining with percentage in floating format.
|
|
||||||
$estimate = $elapsed * ($total - $current) / $current;
|
|
||||||
$values = array(
|
$values = array(
|
||||||
'@remaining' => $remaining,
|
'@remaining' => $remaining,
|
||||||
'@total' => $total,
|
'@total' => $total,
|
||||||
'@current' => floor($current),
|
'@current' => floor($current),
|
||||||
'@percentage' => $percentage,
|
'@percentage' => $percentage,
|
||||||
'@elapsed' => format_interval($elapsed / 1000),
|
'@elapsed' => format_interval($elapsed / 1000),
|
||||||
'@estimate' => format_interval($estimate / 1000),
|
// If possible, estimate remaining processing time.
|
||||||
|
'@estimate' => ($current > 0) ? format_interval(($elapsed * ($total - $current) / $current) / 1000) : '-',
|
||||||
);
|
);
|
||||||
$message = strtr($progress_message, $values);
|
$message = strtr($progress_message, $values);
|
||||||
if (!empty($message)) {
|
if (!empty($message)) {
|
||||||
|
@ -410,7 +423,7 @@ function _batch_next_set() {
|
||||||
if (isset($current_set['form_submit']) && ($function = $current_set['form_submit']) && function_exists($function)) {
|
if (isset($current_set['form_submit']) && ($function = $current_set['form_submit']) && function_exists($function)) {
|
||||||
// We use our stored copies of $form and $form_state to account for
|
// We use our stored copies of $form and $form_state to account for
|
||||||
// possible alterations by previous form submit handlers.
|
// possible alterations by previous form submit handlers.
|
||||||
$function($batch['form'], $batch['form_state']);
|
$function($batch['form_state']['complete form'], $batch['form_state']);
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
@ -426,15 +439,16 @@ function _batch_finished() {
|
||||||
$batch = &batch_get();
|
$batch = &batch_get();
|
||||||
|
|
||||||
// Execute the 'finished' callbacks for each batch set, if defined.
|
// Execute the 'finished' callbacks for each batch set, if defined.
|
||||||
foreach ($batch['sets'] as $key => $batch_set) {
|
foreach ($batch['sets'] as $batch_set) {
|
||||||
if (isset($batch_set['finished'])) {
|
if (isset($batch_set['finished'])) {
|
||||||
// Check if the set requires an additional file for function definitions.
|
// Check if the set requires an additional file for function definitions.
|
||||||
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
|
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
|
||||||
include_once DRUPAL_ROOT . '/' . $batch_set['file'];
|
include_once DRUPAL_ROOT . '/' . $batch_set['file'];
|
||||||
}
|
}
|
||||||
if (function_exists($batch_set['finished'])) {
|
if (function_exists($batch_set['finished'])) {
|
||||||
// Format the elapsed time when batch complete.
|
$queue = _batch_queue($batch_set);
|
||||||
$batch_set['finished']($batch_set['success'], $batch_set['results'], $batch_set['operations'], format_interval($batch_set['elapsed'] / 1000));
|
$operations = $queue->getAllItems();
|
||||||
|
$batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,6 +458,11 @@ function _batch_finished() {
|
||||||
db_delete('batch')
|
db_delete('batch')
|
||||||
->condition('bid', $batch['id'])
|
->condition('bid', $batch['id'])
|
||||||
->execute();
|
->execute();
|
||||||
|
foreach ($batch['sets'] as $batch_set) {
|
||||||
|
if ($queue = _batch_queue($batch_set)) {
|
||||||
|
$queue->deleteQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$_batch = $batch;
|
$_batch = $batch;
|
||||||
$batch = NULL;
|
$batch = NULL;
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Queue handlers used by the Batch API.
|
||||||
|
*
|
||||||
|
* Those implementations:
|
||||||
|
* - ensure FIFO ordering,
|
||||||
|
* - let an item be repeatedly claimed until it is actually deleted (no notion
|
||||||
|
* of lease time or 'expire' date), to allow multipass operations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch queue implementation.
|
||||||
|
*
|
||||||
|
* Stale items from failed batches are cleaned from the {queue} table on cron
|
||||||
|
* using the 'created' date.
|
||||||
|
*/
|
||||||
|
class BatchQueue extends SystemQueue {
|
||||||
|
|
||||||
|
public function claimItem($lease_time = 0) {
|
||||||
|
$item = db_query('SELECT data, item_id FROM {queue} q WHERE name = :name ORDER BY item_id ASC', array(':name' => $this->name))->fetchObject();
|
||||||
|
if ($item) {
|
||||||
|
$item->data = unserialize($item->data);
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all remaining items in the queue.
|
||||||
|
*
|
||||||
|
* This is specific to Batch API and is not part of the DrupalQueueInterface,
|
||||||
|
*/
|
||||||
|
public function getAllItems() {
|
||||||
|
$result = array();
|
||||||
|
$items = db_query('SELECT data FROM {queue} q WHERE name = :name ORDER BY item_id ASC', array(':name' => $this->name))->fetchAll();
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$result[] = unserialize($item->data);
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch queue implementation used for non-progressive batches.
|
||||||
|
*/
|
||||||
|
class BatchMemoryQueue extends MemoryQueue {
|
||||||
|
|
||||||
|
public function claimItem($lease_time = 0) {
|
||||||
|
if (!empty($this->queue)) {
|
||||||
|
reset($this->queue);
|
||||||
|
return current($this->queue);
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all remaining items in the queue.
|
||||||
|
*
|
||||||
|
* This is specific to Batch API and is not part of the DrupalQueueInterface,
|
||||||
|
*/
|
||||||
|
public function getAllItems() {
|
||||||
|
$result = array();
|
||||||
|
foreach ($this->queue as $item) {
|
||||||
|
$result[] = $item->data;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -634,12 +634,23 @@ function drupal_process_form($form_id, &$form, &$form_state) {
|
||||||
// that is already being processed (if a batch operation performs a
|
// that is already being processed (if a batch operation performs a
|
||||||
// drupal_form_submit).
|
// drupal_form_submit).
|
||||||
if ($batch =& batch_get() && !isset($batch['current_set'])) {
|
if ($batch =& batch_get() && !isset($batch['current_set'])) {
|
||||||
// The batch uses its own copies of $form and $form_state for
|
// Store $form_state information in the batch definition.
|
||||||
// late execution of submit handlers and post-batch redirection.
|
// We need the full $form_state when either:
|
||||||
$batch['form'] = $form;
|
// - Some submit handlers were saved to be called during batch
|
||||||
$batch['form_state'] = $form_state;
|
// processing. See form_execute_handlers().
|
||||||
|
// - The form is multistep.
|
||||||
|
// In other cases, we only need the information expected by
|
||||||
|
// drupal_redirect_form().
|
||||||
|
if ($batch['has_form_submits'] || !empty($form_state['rebuild']) || !empty($form_state['storage'])) {
|
||||||
|
$batch['form_state'] = $form_state;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$batch['form_state'] = array_intersect_key($form_state, array_flip(array('programmed', 'rebuild', 'storage', 'no_redirect', 'redirect')));
|
||||||
|
}
|
||||||
|
|
||||||
$batch['progressive'] = !$form_state['programmed'];
|
$batch['progressive'] = !$form_state['programmed'];
|
||||||
batch_process();
|
batch_process();
|
||||||
|
|
||||||
// Execution continues only for programmatic forms.
|
// Execution continues only for programmatic forms.
|
||||||
// For 'regular' forms, we get redirected to the batch processing
|
// For 'regular' forms, we get redirected to the batch processing
|
||||||
// page. Form redirection will be handled in _batch_finished(),
|
// page. Form redirection will be handled in _batch_finished(),
|
||||||
|
@ -1004,14 +1015,15 @@ function form_execute_handlers($type, &$form, &$form_state) {
|
||||||
|
|
||||||
foreach ($handlers as $function) {
|
foreach ($handlers as $function) {
|
||||||
if (function_exists($function)) {
|
if (function_exists($function)) {
|
||||||
// Check to see if a previous _submit handler has set a batch, but
|
// Check if a previous _submit handler has set a batch, but make sure we
|
||||||
// make sure we do not react to a batch that is already being processed
|
// do not react to a batch that is already being processed (for instance
|
||||||
// (for instance if a batch operation performs a drupal_form_submit()).
|
// if a batch operation performs a drupal_form_submit()).
|
||||||
if ($type == 'submit' && ($batch =& batch_get()) && !isset($batch['current_set'])) {
|
if ($type == 'submit' && ($batch =& batch_get()) && !isset($batch['id'])) {
|
||||||
// Some previous _submit handler has set a batch. We store the call
|
// Some previous submit handler has set a batch. To ensure correct
|
||||||
// in a special 'control' batch set, for execution at the correct
|
// execution order, store the call in a special 'control' batch set.
|
||||||
// time during the batch processing workflow.
|
// See _batch_next_set().
|
||||||
$batch['sets'][] = array('form_submit' => $function);
|
$batch['sets'][] = array('form_submit' => $function);
|
||||||
|
$batch['has_form_submits'] = TRUE;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$function($form, $form_state);
|
$function($form, $form_state);
|
||||||
|
@ -3305,22 +3317,25 @@ function _form_set_class(&$element, $class = array()) {
|
||||||
function batch_set($batch_definition) {
|
function batch_set($batch_definition) {
|
||||||
if ($batch_definition) {
|
if ($batch_definition) {
|
||||||
$batch =& batch_get();
|
$batch =& batch_get();
|
||||||
// Initialize the batch
|
|
||||||
|
// Initialize the batch if needed.
|
||||||
if (empty($batch)) {
|
if (empty($batch)) {
|
||||||
$batch = array(
|
$batch = array(
|
||||||
'sets' => array(),
|
'sets' => array(),
|
||||||
|
'has_form_submits' => FALSE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Base and default properties for the batch set.
|
||||||
|
// Use get_t() to allow batches at install time.
|
||||||
|
$t = get_t();
|
||||||
$init = array(
|
$init = array(
|
||||||
'sandbox' => array(),
|
'sandbox' => array(),
|
||||||
'results' => array(),
|
'results' => array(),
|
||||||
'success' => FALSE,
|
'success' => FALSE,
|
||||||
'start' => microtime(TRUE),
|
'start' => 0,
|
||||||
'elapsed' => 0,
|
'elapsed' => 0,
|
||||||
);
|
);
|
||||||
// Use get_t() to allow batches at install time.
|
|
||||||
$t = get_t();
|
|
||||||
$defaults = array(
|
$defaults = array(
|
||||||
'title' => $t('Processing'),
|
'title' => $t('Processing'),
|
||||||
'init_message' => $t('Initializing.'),
|
'init_message' => $t('Initializing.'),
|
||||||
|
@ -3330,20 +3345,29 @@ function batch_set($batch_definition) {
|
||||||
);
|
);
|
||||||
$batch_set = $init + $batch_definition + $defaults;
|
$batch_set = $init + $batch_definition + $defaults;
|
||||||
|
|
||||||
// Tweak init_message to avoid the bottom of the page flickering down after init phase.
|
// Tweak init_message to avoid the bottom of the page flickering down after
|
||||||
|
// init phase.
|
||||||
$batch_set['init_message'] .= '<br/> ';
|
$batch_set['init_message'] .= '<br/> ';
|
||||||
$batch_set['total'] = count($batch_set['operations']);
|
|
||||||
|
|
||||||
// If the batch is being processed (meaning we are executing a stored submit handler),
|
// The non-concurrent workflow of batch execution allows us to save
|
||||||
// insert the new set after the current one.
|
// numberOfItems() queries by handling our own counter.
|
||||||
if (isset($batch['current_set'])) {
|
$batch_set['total'] = count($batch_set['operations']);
|
||||||
// array_insert does not exist...
|
$batch_set['count'] = $batch_set['total'];
|
||||||
$slice1 = array_slice($batch['sets'], 0, $batch['current_set'] + 1);
|
|
||||||
$slice2 = array_slice($batch['sets'], $batch['current_set'] + 1);
|
// Add the set to the batch.
|
||||||
$batch['sets'] = array_merge($slice1, array($batch_set), $slice2);
|
if (empty($batch['id'])) {
|
||||||
|
// The batch is not running yet. Simply add the new set.
|
||||||
|
$batch['sets'][] = $batch_set;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$batch['sets'][] = $batch_set;
|
// The set is being added while the batch is running. Insert the new set
|
||||||
|
// right after the current one to ensure execution order, and store its
|
||||||
|
// operations in a queue.
|
||||||
|
$index = $batch['current_set'] + 1;
|
||||||
|
$slice1 = array_slice($batch['sets'], 0, $index);
|
||||||
|
$slice2 = array_slice($batch['sets'], $index);
|
||||||
|
$batch['sets'] = array_merge($slice1, array($batch_set), $slice2);
|
||||||
|
_batch_populate_queue($batch, $index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3387,11 +3411,28 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd
|
||||||
);
|
);
|
||||||
$batch += $process_info;
|
$batch += $process_info;
|
||||||
|
|
||||||
// The batch is now completely built. Allow other modules to make changes to the
|
// The batch is now completely built. Allow other modules to make changes
|
||||||
// batch so that it is easier to reuse batch processes in other enviroments.
|
// to the batch so that it is easier to reuse batch processes in other
|
||||||
|
// enviroments.
|
||||||
drupal_alter('batch', $batch);
|
drupal_alter('batch', $batch);
|
||||||
|
|
||||||
|
// Assign an arbitrary id: don't rely on a serial column in the 'batch'
|
||||||
|
// table, since non-progressive batches skip database storage completely.
|
||||||
|
$batch['id'] = db_next_id();
|
||||||
|
|
||||||
|
// Move operations to a job queue. Non-progressive batches will use a
|
||||||
|
// memory-based queue.
|
||||||
|
foreach ($batch['sets'] as $key => $batch_set) {
|
||||||
|
_batch_populate_queue($batch, $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initiate processing.
|
||||||
if ($batch['progressive']) {
|
if ($batch['progressive']) {
|
||||||
|
// Now that we have a batch id, we can generate the redirection link in
|
||||||
|
// the generic error message.
|
||||||
|
$t = get_t();
|
||||||
|
$batch['error_message'] = $t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished')))));
|
||||||
|
|
||||||
// Clear the way for the drupal_goto() redirection to the batch processing
|
// Clear the way for the drupal_goto() redirection to the batch processing
|
||||||
// page, by saving and unsetting the 'destination', if there is any.
|
// page, by saving and unsetting the 'destination', if there is any.
|
||||||
if (isset($_GET['destination'])) {
|
if (isset($_GET['destination'])) {
|
||||||
|
@ -3399,24 +3440,11 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd
|
||||||
unset($_GET['destination']);
|
unset($_GET['destination']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initiate db storage in order to get a batch id. We have to provide
|
// Store the batch.
|
||||||
// at least an empty string for the (not null) 'token' column.
|
db_insert('batch')
|
||||||
$batch['id'] = db_insert('batch')
|
|
||||||
->fields(array(
|
->fields(array(
|
||||||
'token' => '',
|
'bid' => $batch['id'],
|
||||||
'timestamp' => REQUEST_TIME,
|
'timestamp' => REQUEST_TIME,
|
||||||
))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
// Now that we have a batch id, we can generate the redirection link in
|
|
||||||
// the generic error message.
|
|
||||||
$t = get_t();
|
|
||||||
$batch['error_message'] = $t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished')))));
|
|
||||||
|
|
||||||
// Actually store the batch data and the token generated form the batch id.
|
|
||||||
db_update('batch')
|
|
||||||
->condition('bid', $batch['id'])
|
|
||||||
->fields(array(
|
|
||||||
'token' => drupal_get_token($batch['id']),
|
'token' => drupal_get_token($batch['id']),
|
||||||
'batch' => serialize($batch),
|
'batch' => serialize($batch),
|
||||||
))
|
))
|
||||||
|
@ -3425,6 +3453,7 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'd
|
||||||
// Set the batch number in the session to guarantee that it will stay alive.
|
// Set the batch number in the session to guarantee that it will stay alive.
|
||||||
$_SESSION['batches'][$batch['id']] = TRUE;
|
$_SESSION['batches'][$batch['id']] = TRUE;
|
||||||
|
|
||||||
|
// Redirect for processing.
|
||||||
$function = $batch['redirect_callback'];
|
$function = $batch['redirect_callback'];
|
||||||
if (function_exists($function)) {
|
if (function_exists($function)) {
|
||||||
$function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id'])));
|
$function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id'])));
|
||||||
|
@ -3453,6 +3482,70 @@ function &batch_get() {
|
||||||
return $batch;
|
return $batch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates a job queue with the operations of a batch set.
|
||||||
|
*
|
||||||
|
* Depending on whether the batch is progressive or not, the BatchQueue or
|
||||||
|
* BatchStaticQueue handler classes will be used.
|
||||||
|
*
|
||||||
|
* @param $batch
|
||||||
|
* The batch array.
|
||||||
|
* @param $set_id
|
||||||
|
* The id of the set to process.
|
||||||
|
* @return
|
||||||
|
* The name and class of the queue are added by reference to the batch set.
|
||||||
|
*/
|
||||||
|
function _batch_populate_queue(&$batch, $set_id) {
|
||||||
|
$batch_set = &$batch['sets'][$set_id];
|
||||||
|
|
||||||
|
if (isset($batch_set['operations'])) {
|
||||||
|
$batch_set += array(
|
||||||
|
'queue' => array(
|
||||||
|
'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id,
|
||||||
|
'class' => $batch['progressive'] ? 'BatchQueue' : 'BatchMemoryQueue',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$queue = _batch_queue($batch_set);
|
||||||
|
$queue->createQueue();
|
||||||
|
foreach ($batch_set['operations'] as $operation) {
|
||||||
|
$queue->createItem($operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($batch_set['operations']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a queue object for a batch set.
|
||||||
|
*
|
||||||
|
* @param $batch_set
|
||||||
|
* The batch set.
|
||||||
|
* @return
|
||||||
|
* The queue object.
|
||||||
|
*/
|
||||||
|
function _batch_queue($batch_set) {
|
||||||
|
static $queues;
|
||||||
|
|
||||||
|
// The class autoloader is not available when running update.php, so make
|
||||||
|
// sure the files are manually included.
|
||||||
|
if (is_null($queues)) {
|
||||||
|
$queues = array();
|
||||||
|
require_once DRUPAL_ROOT . '/modules/system/system.queue.inc';
|
||||||
|
require_once DRUPAL_ROOT . '/includes/batch.queue.inc';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($batch_set['queue'])) {
|
||||||
|
$name = $batch_set['queue']['name'];
|
||||||
|
$class = $batch_set['queue']['class'];
|
||||||
|
|
||||||
|
if (!isset($queues[$class][$name])) {
|
||||||
|
$queues[$class][$name] = new $class($name);
|
||||||
|
}
|
||||||
|
return $queues[$class][$name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @} End of "defgroup batch".
|
* @} End of "defgroup batch".
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -338,6 +338,51 @@ function update_fix_d7_requirements() {
|
||||||
db_create_table('date_formats', $schema['date_formats']);
|
db_create_table('date_formats', $schema['date_formats']);
|
||||||
db_create_table('date_format_locale', $schema['date_format_locale']);
|
db_create_table('date_format_locale', $schema['date_format_locale']);
|
||||||
|
|
||||||
|
// Add the queue table.
|
||||||
|
$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']);
|
||||||
|
|
||||||
// Add column for locale context.
|
// Add column for locale context.
|
||||||
if (db_table_exists('locales_source')) {
|
if (db_table_exists('locales_source')) {
|
||||||
db_add_field('locales_source', 'context', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', 'description' => 'The context this string applies to.'));
|
db_add_field('locales_source', 'context', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', 'description' => 'The context this string applies to.'));
|
||||||
|
|
|
@ -3,40 +3,286 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @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() {
|
public static function getInfo() {
|
||||||
return array(
|
return array(
|
||||||
'name' => 'Batch API progress page theme',
|
'name' => 'Batch processing',
|
||||||
'description' => 'Tests that while a progressive batch is running, it correctly uses the theme of the page that started the batch.',
|
'description' => 'Test batch processing in form and non-form workflow.',
|
||||||
'group' => 'Batch API',
|
'group' => 'Batch API',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUp() {
|
function setUp() {
|
||||||
parent::setUp('system_test');
|
parent::setUp('batch_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');
|
* 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.
|
* 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
|
// Visit an administrative page that runs a test batch, and check that the
|
||||||
// theme that was used during batch execution (which the batch callback
|
// theme that was used during batch execution (which the batch callback
|
||||||
// function saved as a variable) matches the theme used on the
|
// function saved as a variable) matches the theme used on the
|
||||||
// administrative page.
|
// administrative page.
|
||||||
$this->drupalGet('admin/system-test/batch-theme');
|
$this->drupalGet('admin/batch_test/test_theme');
|
||||||
$batch_theme_used = variable_get('system_test_batch_theme_used', 'garland');
|
// The stack should contain the name of the the used on the progress page.
|
||||||
$this->assertEqual($batch_theme_used, 'seven', t('A progressive batch correctly uses the theme of the page that started the batch.'));
|
$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
|
* Tests the function _batch_api_percentage() to make sure that the rounding
|
||||||
* works properly in all cases.
|
* works properly in all cases.
|
||||||
*/
|
*/
|
||||||
class BatchAPIPercentagesTestCase extends DrupalWebTestCase {
|
class BatchPercentagesUnitTestCase extends DrupalUnitTestCase {
|
||||||
protected $testCases = array();
|
protected $testCases = array();
|
||||||
|
|
||||||
public static function getInfo() {
|
public static function getInfo() {
|
||||||
return array(
|
return array(
|
||||||
'name' => 'Batch API percentages',
|
'name' => 'Batch percentages',
|
||||||
'description' => 'Tests the handling of percentage rounding in the Drupal batch API. This is critical to Drupal user experience.',
|
'description' => 'Unit tests of progress percentage rounding.',
|
||||||
'group' => 'Batch API',
|
'group' => 'Batch API',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -99,10 +345,9 @@ class BatchAPIPercentagesTestCase extends DrupalWebTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the _batch_api_percentage() function with the data stored in the
|
* Test the _batch_api_percentage() function.
|
||||||
* testCases class variable.
|
|
||||||
*/
|
*/
|
||||||
function testBatchAPIPercentages() {
|
function testBatchPercentages() {
|
||||||
require_once DRUPAL_ROOT . '/includes/batch.inc';
|
require_once DRUPAL_ROOT . '/includes/batch.inc';
|
||||||
foreach ($this->testCases as $expected_result => $arguments) {
|
foreach ($this->testCases as $expected_result => $arguments) {
|
||||||
// PHP sometimes casts numeric strings that are array keys to integers,
|
// PHP sometimes casts numeric strings that are array keys to integers,
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -511,45 +511,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.
|
* Test the form storage on a multistep form.
|
||||||
*
|
*
|
||||||
|
|
|
@ -54,13 +54,6 @@ function form_test_menu() {
|
||||||
'type' => MENU_CALLBACK,
|
'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(
|
$items['form_test/form-storage'] = array(
|
||||||
'title' => 'Form storage test',
|
'title' => 'Form storage test',
|
||||||
'page callback' => 'drupal_get_form',
|
'page callback' => 'drupal_get_form',
|
||||||
|
@ -375,67 +368,6 @@ function _form_test_tableselect_js_select_form($form, $form_state, $action) {
|
||||||
return _form_test_tableselect_form_builder($form, $form_state, $options);
|
return _form_test_tableselect_form_builder($form, $form_state, $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* A multistep form for testing the form storage.
|
||||||
*
|
*
|
||||||
|
|
|
@ -5,11 +5,6 @@
|
||||||
* Implements hook_menu().
|
* Implements hook_menu().
|
||||||
*/
|
*/
|
||||||
function system_test_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(
|
$items['system-test/sleep/%'] = array(
|
||||||
'page callback' => 'system_test_sleep',
|
'page callback' => 'system_test_sleep',
|
||||||
'page arguments' => array(2),
|
'page arguments' => array(2),
|
||||||
|
@ -102,34 +97,6 @@ function system_test_menu() {
|
||||||
return $items;
|
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) {
|
function system_test_sleep($seconds) {
|
||||||
sleep($seconds);
|
sleep($seconds);
|
||||||
}
|
}
|
||||||
|
|
|
@ -496,7 +496,9 @@ function system_schema() {
|
||||||
'fields' => array(
|
'fields' => array(
|
||||||
'bid' => array(
|
'bid' => array(
|
||||||
'description' => 'Primary Key: Unique batch ID.',
|
'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,
|
'unsigned' => TRUE,
|
||||||
'not null' => TRUE,
|
'not null' => TRUE,
|
||||||
),
|
),
|
||||||
|
@ -2257,50 +2259,7 @@ function system_update_7021() {
|
||||||
* Add the queue tables.
|
* Add the queue tables.
|
||||||
*/
|
*/
|
||||||
function system_update_7022() {
|
function system_update_7022() {
|
||||||
$schema['queue'] = array(
|
// Moved to update_fix_d7_requirements().
|
||||||
'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']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2727,6 +2686,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"
|
* @} End of "defgroup updates-6.x-to-7.x"
|
||||||
* The next series of updates should start at 8000.
|
* The next series of updates should start at 8000.
|
||||||
|
|
|
@ -2695,10 +2695,6 @@ function system_cron() {
|
||||||
db_delete('flood')
|
db_delete('flood')
|
||||||
->condition('expiration', REQUEST_TIME, '<')
|
->condition('expiration', REQUEST_TIME, '<')
|
||||||
->execute();
|
->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.
|
// 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
|
// 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);
|
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
|
// Reset expired items in the default queue implementation table. If that's
|
||||||
// not used, this will simply be a no-op.
|
// not used, this will simply be a no-op.
|
||||||
db_update('queue')
|
db_update('queue')
|
||||||
|
|
|
@ -253,6 +253,79 @@ 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".
|
* @} End of "defgroup queue".
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue