diff --git a/commands/core/drupal/batch.inc b/commands/core/drupal/batch.inc index c7ea875..51db763 100644 --- a/commands/core/drupal/batch.inc +++ b/commands/core/drupal/batch.inc @@ -14,8 +14,7 @@ * The command to call to process the batch. * */ -function _drush_backend_batch_process($command = 'batch-process', $args) { - global $user; +function _drush_backend_batch_process($command = 'batch-process', $args, $options) { $batch =& batch_get(); if (isset($batch)) { @@ -31,6 +30,7 @@ function _drush_backend_batch_process($command = 'batch-process', $args) { // 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(); + $args[] = $batch['id']; $batch['progressive'] = TRUE; @@ -52,12 +52,7 @@ function _drush_backend_batch_process($command = 'batch-process', $args) { $finished = FALSE; while (!$finished) { - if ($user->uid) { - $data = drush_invoke_process('@self', $command, $args, array($batch['id'], '-u', $user->uid)); - } - else { - $data = drush_invoke_process('@self', $command, $args, array($batch['id'])); - } + $data = drush_invoke_process('@self', $command, $args, $options); $finished = drush_get_error() || !$data || ($data['context']['drush_batch_process_finished'] == TRUE); } } @@ -140,6 +135,8 @@ function _drush_batch_worker() { 'finished' => &$finished, 'message' => &$task_message, ); + // Magic wrap to catch changes to 'message' key. + $batch_context = new DrushBatchContext($batch_context); call_user_func_array($function, array_merge($args, array(&$batch_context))); if ($finished == 1) { diff --git a/commands/core/drupal/batch_6.inc b/commands/core/drupal/batch_6.inc index 3396253..cebfeb4 100644 --- a/commands/core/drupal/batch_6.inc +++ b/commands/core/drupal/batch_6.inc @@ -14,8 +14,7 @@ * The command to call to process the batch. * */ -function _drush_backend_batch_process($command = 'batch-process', $args) { - global $user; +function _drush_backend_batch_process($command = 'batch-process', $args, $options) { $batch =& batch_get(); if (isset($batch)) { @@ -28,6 +27,7 @@ function _drush_backend_batch_process($command = 'batch-process', $args) { // at least an empty string for the (not null) 'token' column. db_query("INSERT INTO {batch} (token, timestamp) VALUES ('', %d)", time()); $batch['id'] = db_last_insert_id('batch', 'bid'); + $args[] = $batch['id']; // Actually store the batch data and the token generated form the batch id. db_query("UPDATE {batch} SET token = '%s', batch = '%s' WHERE bid = %d", drupal_get_token($batch['id']), serialize($batch), $batch['id']); @@ -35,13 +35,7 @@ function _drush_backend_batch_process($command = 'batch-process', $args) { $finished = FALSE; while (!$finished) { - if ($user->uid) { - $data = drush_invoke_process('@self', $command, $args, array($batch['id'], '-u', $user->uid)); - } - else { - $data = drush_invoke_process('@self', $command, $args, array($batch['id'])); - } - + $data = drush_invoke_process('@self', $command, $args, $options); $finished = drush_get_error() || !$data || (isset($data['context']['drush_batch_process_finished']) && $data['context']['drush_batch_process_finished'] == TRUE); } } @@ -105,6 +99,8 @@ function _drush_batch_worker() { // Build the 'context' array, execute the function call, // and retrieve the user message. $batch_context = array('sandbox' => &$current_set['sandbox'], 'results' => &$current_set['results'], 'finished' => &$finished, 'message' => &$task_message); + // Magic wrap to catch changes to 'message' key. + $batch_context = new DrushBatchContext($batch_context); // Process the current operation. call_user_func_array($function, array_merge($args, array(&$batch_context))); } diff --git a/includes/batch.inc b/includes/batch.inc index 5260046..acd9cef 100644 --- a/includes/batch.inc +++ b/includes/batch.inc @@ -22,9 +22,28 @@ * Each major release of Drupal has also had slightly different implementations * of the batch API, and this provides a uniform interface to all of these * implementations. - * */ +/** + * Class extending ArrayObject to allow the batch API to perform logging when + * some keys of the array change. + * + * It is used to wrap batch's $context array and set log messages when values + * are assigned to keys 'message' or 'error_message'. + * + * @see _drush_batch_worker(). + */ +class DrushBatchContext extends ArrayObject { + function offsetSet($name, $value) { + if ($name == 'message') { + drush_log($value, 'ok'); + } + elseif ($name == 'error_message') { + drush_log($value, 'error'); + } + parent::offsetSet($name, $value); + } +} /** * Process a Drupal batch by spawning multiple Drush processes. @@ -48,9 +67,22 @@ * use their own command. * */ -function drush_backend_batch_process($command = 'batch-process', $args = array()) { +function drush_backend_batch_process($command = 'batch-process', $args = array(), $options = array()) { + // Command line options to pass to the command. + global $user; + if ($user->uid) { + $options['u'] = $user->uid; + } + // Pass through --include. It allow to provide the command to call for the + // backend process in a file that is not in the standard drush search paths. + // Needed by batchTest.php that rely on backend process commands defined in + // tests/unit.drush.inc. + if ($include = drush_get_option(array('i', 'include'), FALSE)) { + $options['include'] = $include; + } + drush_include_engine('drupal', 'batch', drush_drupal_major_version()); - _drush_backend_batch_process($command, $args); + _drush_backend_batch_process($command, $args, $options); } /** diff --git a/tests/backendTest.php b/tests/backendTest.php index 3411a09..db3f332 100644 --- a/tests/backendTest.php +++ b/tests/backendTest.php @@ -14,7 +14,6 @@ */ class backendCase extends Drush_CommandTestCase { - const DRUSH_BACKEND_OUTPUT_DELIMITER = 'DRUSH_BACKEND_OUTPUT_START>>>%s<<'sql')); $exec = sprintf('echo %s | %s help --backend 2>/dev/null', self::unish_escapeshellarg($stdin), self::unish_escapeshellarg(UNISH_DRUSH)); $this->execute($exec); - $parsed = $this->parse($this->getOutput()); + $parsed = parse_backend_output($this->getOutput()); $this->assertTrue((bool) $parsed, 'Successfully parsed backend output'); $this->assertArrayHasKey('log', $parsed); $this->assertArrayHasKey('output', $parsed); @@ -56,7 +55,7 @@ class backendCase extends Drush_CommandTestCase { // Check error propogation by requesting an invalid command (missing Drupal site). $exec = sprintf('%s core-cron --backend 2>/dev/null', self::unish_escapeshellarg(UNISH_DRUSH)); $this->execute($exec, self::EXIT_ERROR); - $parsed = $this->parse($this->getOutput()); + $parsed = parse_backend_output($this->getOutput()); $this->assertEquals(1, $parsed['error_status']); $this->assertArrayHasKey('DRUSH_NO_DRUPAL_ROOT', $parsed['error_log']); } @@ -79,26 +78,4 @@ class backendCase extends Drush_CommandTestCase { $this->assertTrue($backend_output_offset !== FALSE, "Drush backend output marker appears in output."); $this->assertTrue($drush_version_offset < $backend_output_offset, "Drush version string appears in output before the backend output marker."); } - - /* - * A slightly less functional copy of drush_backend_parse_output(). - */ - function parse($string) { - $regex = sprintf(self::DRUSH_BACKEND_OUTPUT_DELIMITER, '(.*)'); - preg_match("/$regex/s", $string, $match); - if ($match[1]) { - // we have our JSON encoded string - $output = $match[1]; - // remove the match we just made and any non printing characters - $string = trim(str_replace(sprintf(self::DRUSH_BACKEND_OUTPUT_DELIMITER, $match[1]), '', $string)); - } - - if ($output) { - $data = json_decode($output, TRUE); - if (is_array($data)) { - return $data; - } - } - return $string; - } } diff --git a/tests/batchTest.php b/tests/batchTest.php new file mode 100644 index 0000000..83eec06 --- /dev/null +++ b/tests/batchTest.php @@ -0,0 +1,26 @@ +setUpDrupal($env, TRUE, '7.x'); + $root = $this->sites[$env]['root']; + $name = "example"; + $options = array( + 'root' => $root, + 'uri' => $env, + 'yes' => NULL, + 'include' => dirname(__FILE__), + ); + $this->drush('unit-batch', array(), $options); + $parsed = parse_backend_output($this->getOutput()); + } +} + diff --git a/tests/drush_testcase.inc b/tests/drush_testcase.inc index 33a81ba..6d660f5 100644 --- a/tests/drush_testcase.inc +++ b/tests/drush_testcase.inc @@ -342,6 +342,29 @@ function unish_init() { return rmdir($dir); } +/** + * A slightly less functional copy of drush_backend_parse_output(). + */ +define('DRUSH_BACKEND_OUTPUT_DELIMITER', 'DRUSH_BACKEND_OUTPUT_START>>>%s<< 'Return an array indicating which invoke hooks got called.', 'bootstrap' => DRUSH_BOOTSTRAP_NONE, ); + $items['unit-batch'] = array( + 'description' => 'Run a batch process.', + 'bootstrap' => DRUSH_BOOTSTRAP_MAX, + ); return $items; } @@ -73,4 +77,42 @@ function unit_invoke_log($function = NULL) { else { return $called; } -} \ No newline at end of file +} + +/** + * Command callback. + */ +function drush_unit_batch() { + // Reduce php memory/time limits to test backend respawn. + // TODO. + + $batch = array( + 'operations' => array( + array('_drush_unit_batch_operation', array()), + ), + 'finished' => '_drush_unit_batch_finished', + // 'file' => Doesn't work for us. Drupal 7 enforces this path + // to be relative to DRUPAL_ROOT. + // @see _batch_process(). + ); + batch_set($batch); + drush_backend_batch_process(); + + // Print the batch output. + drush_backend_output(); +} + +function _drush_unit_batch_operation(&$context) { + $context['message'] = "ArrayObject does it job."; + + for ($i = 0; $i < 5; $i++) { + drush_print("Iteration $i"); + } + $context['finished'] = 1; +} + +function _drush_unit_batch_finished() { + // Restore php limits. + // TODO. +} +