batch.test

Tests for the Batch API.

File

drupal-7.x/modules/simpletest/tests/batch.test
View source
  1. <?php
  2. /**
  3. * @file
  4. * Tests for the Batch API.
  5. */
  6. /**
  7. * Tests for the Batch API.
  8. */
  9. class BatchProcessingTestCase extends DrupalWebTestCase {
  10. public static function getInfo() {
  11. return array(
  12. 'name' => 'Batch processing',
  13. 'description' => 'Test batch processing in form and non-form workflow.',
  14. 'group' => 'Batch API',
  15. );
  16. }
  17. function setUp() {
  18. parent::setUp('batch_test');
  19. }
  20. /**
  21. * Test batches triggered outside of form submission.
  22. */
  23. function testBatchNoForm() {
  24. // Displaying the page triggers batch 1.
  25. $this->drupalGet('batch-test/no-form');
  26. $this->assertBatchMessages($this->_resultMessages(1), t('Batch for step 2 performed successfully.'));
  27. $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), t('Execution order was correct.'));
  28. $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
  29. }
  30. /**
  31. * Test batches defined in a form submit handler.
  32. */
  33. function testBatchForm() {
  34. // Batch 0: no operation.
  35. $edit = array('batch' => 'batch_0');
  36. $this->drupalPost('batch-test/simple', $edit, 'Submit');
  37. $this->assertBatchMessages($this->_resultMessages('batch_0'), t('Batch with no operation performed successfully.'));
  38. $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
  39. // Batch 1: several simple operations.
  40. $edit = array('batch' => 'batch_1');
  41. $this->drupalPost('batch-test/simple', $edit, 'Submit');
  42. $this->assertBatchMessages($this->_resultMessages('batch_1'), t('Batch with simple operations performed successfully.'));
  43. $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), t('Execution order was correct.'));
  44. $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
  45. // Batch 2: one multistep operation.
  46. $edit = array('batch' => 'batch_2');
  47. $this->drupalPost('batch-test/simple', $edit, 'Submit');
  48. $this->assertBatchMessages($this->_resultMessages('batch_2'), t('Batch with multistep operation performed successfully.'));
  49. $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_2'), t('Execution order was correct.'));
  50. $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
  51. // Batch 3: simple + multistep combined.
  52. $edit = array('batch' => 'batch_3');
  53. $this->drupalPost('batch-test/simple', $edit, 'Submit');
  54. $this->assertBatchMessages($this->_resultMessages('batch_3'), t('Batch with simple and multistep operations performed successfully.'));
  55. $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_3'), t('Execution order was correct.'));
  56. $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
  57. // Batch 4: nested batch.
  58. $edit = array('batch' => 'batch_4');
  59. $this->drupalPost('batch-test/simple', $edit, 'Submit');
  60. $this->assertBatchMessages($this->_resultMessages('batch_4'), t('Nested batch performed successfully.'));
  61. $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_4'), t('Execution order was correct.'));
  62. $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
  63. }
  64. /**
  65. * Test batches defined in a multistep form.
  66. */
  67. function testBatchFormMultistep() {
  68. $this->drupalGet('batch-test/multistep');
  69. $this->assertText('step 1', t('Form is displayed in step 1.'));
  70. // First step triggers batch 1.
  71. $this->drupalPost(NULL, array(), 'Submit');
  72. $this->assertBatchMessages($this->_resultMessages('batch_1'), t('Batch for step 1 performed successfully.'));
  73. $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), t('Execution order was correct.'));
  74. $this->assertText('step 2', t('Form is displayed in step 2.'));
  75. // Second step triggers batch 2.
  76. $this->drupalPost(NULL, array(), 'Submit');
  77. $this->assertBatchMessages($this->_resultMessages('batch_2'), t('Batch for step 2 performed successfully.'));
  78. $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_2'), t('Execution order was correct.'));
  79. $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
  80. }
  81. /**
  82. * Test batches defined in different submit handlers on the same form.
  83. */
  84. function testBatchFormMultipleBatches() {
  85. // Batches 1, 2 and 3 are triggered in sequence by different submit
  86. // handlers. Each submit handler modify the submitted 'value'.
  87. $value = rand(0, 255);
  88. $edit = array('value' => $value);
  89. $this->drupalPost('batch-test/chained', $edit, 'Submit');
  90. // Check that result messages are present and in the correct order.
  91. $this->assertBatchMessages($this->_resultMessages('chained'), t('Batches defined in separate submit handlers performed successfully.'));
  92. // The stack contains execution order of batch callbacks and submit
  93. // hanlders and logging of corresponding $form_state[{values'].
  94. $this->assertEqual(batch_test_stack(), $this->_resultStack('chained', $value), t('Execution order was correct, and $form_state is correctly persisted.'));
  95. $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
  96. }
  97. /**
  98. * Test batches defined in a programmatically submitted form.
  99. *
  100. * Same as above, but the form is submitted through drupal_form_execute().
  101. */
  102. function testBatchFormProgrammatic() {
  103. // Batches 1, 2 and 3 are triggered in sequence by different submit
  104. // handlers. Each submit handler modify the submitted 'value'.
  105. $value = rand(0, 255);
  106. $this->drupalGet('batch-test/programmatic/' . $value);
  107. // Check that result messages are present and in the correct order.
  108. $this->assertBatchMessages($this->_resultMessages('chained'), t('Batches defined in separate submit handlers performed successfully.'));
  109. // The stack contains execution order of batch callbacks and submit
  110. // hanlders and logging of corresponding $form_state[{values'].
  111. $this->assertEqual(batch_test_stack(), $this->_resultStack('chained', $value), t('Execution order was correct, and $form_state is correctly persisted.'));
  112. $this->assertText('Got out of a programmatic batched form.', t('Page execution continues normally.'));
  113. }
  114. /**
  115. * Test that drupal_form_submit() can run within a batch operation.
  116. */
  117. function testDrupalFormSubmitInBatch() {
  118. // Displaying the page triggers a batch that programmatically submits a
  119. // form.
  120. $value = rand(0, 255);
  121. $this->drupalGet('batch-test/nested-programmatic/' . $value);
  122. $this->assertEqual(batch_test_stack(), array('mock form submitted with value = ' . $value), t('drupal_form_submit() ran successfully within a batch operation.'));
  123. }
  124. /**
  125. * Test batches that return $context['finished'] > 1 do in fact complete.
  126. * See http://drupal.org/node/600836
  127. */
  128. function testBatchLargePercentage() {
  129. // Displaying the page triggers batch 5.
  130. $this->drupalGet('batch-test/large-percentage');
  131. $this->assertBatchMessages($this->_resultMessages(1), t('Batch for step 2 performed successfully.'));
  132. $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_5'), t('Execution order was correct.'));
  133. $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.'));
  134. }
  135. /**
  136. * Will trigger a pass if the texts were found in order in the raw content.
  137. *
  138. * @param $texts
  139. * Array of raw strings to look for .
  140. * @param $message
  141. * Message to display.
  142. * @return
  143. * TRUE on pass, FALSE on fail.
  144. */
  145. function assertBatchMessages($texts, $message) {
  146. $pattern = '|' . implode('.*', $texts) .'|s';
  147. return $this->assertPattern($pattern, $message);
  148. }
  149. /**
  150. * Helper function: return expected execution stacks for the test batches.
  151. */
  152. function _resultStack($id, $value = 0) {
  153. $stack = array();
  154. switch ($id) {
  155. case 'batch_1':
  156. for ($i = 1; $i <= 10; $i++) {
  157. $stack[] = "op 1 id $i";
  158. }
  159. break;
  160. case 'batch_2':
  161. for ($i = 1; $i <= 10; $i++) {
  162. $stack[] = "op 2 id $i";
  163. }
  164. break;
  165. case 'batch_3':
  166. for ($i = 1; $i <= 5; $i++) {
  167. $stack[] = "op 1 id $i";
  168. }
  169. for ($i = 1; $i <= 5; $i++) {
  170. $stack[] = "op 2 id $i";
  171. }
  172. for ($i = 6; $i <= 10; $i++) {
  173. $stack[] = "op 1 id $i";
  174. }
  175. for ($i = 6; $i <= 10; $i++) {
  176. $stack[] = "op 2 id $i";
  177. }
  178. break;
  179. case 'batch_4':
  180. for ($i = 1; $i <= 5; $i++) {
  181. $stack[] = "op 1 id $i";
  182. }
  183. $stack[] = 'setting up batch 2';
  184. for ($i = 6; $i <= 10; $i++) {
  185. $stack[] = "op 1 id $i";
  186. }
  187. $stack = array_merge($stack, $this->_resultStack('batch_2'));
  188. break;
  189. case 'batch_5':
  190. for ($i = 1; $i <= 10; $i++) {
  191. $stack[] = "op 5 id $i";
  192. }
  193. break;
  194. case 'chained':
  195. $stack[] = 'submit handler 1';
  196. $stack[] = 'value = ' . $value;
  197. $stack = array_merge($stack, $this->_resultStack('batch_1'));
  198. $stack[] = 'submit handler 2';
  199. $stack[] = 'value = ' . ($value + 1);
  200. $stack = array_merge($stack, $this->_resultStack('batch_2'));
  201. $stack[] = 'submit handler 3';
  202. $stack[] = 'value = ' . ($value + 2);
  203. $stack[] = 'submit handler 4';
  204. $stack[] = 'value = ' . ($value + 3);
  205. $stack = array_merge($stack, $this->_resultStack('batch_3'));
  206. break;
  207. }
  208. return $stack;
  209. }
  210. /**
  211. * Helper function: return expected result messages for the test batches.
  212. */
  213. function _resultMessages($id) {
  214. $messages = array();
  215. switch ($id) {
  216. case 'batch_0':
  217. $messages[] = 'results for batch 0<br />none';
  218. break;
  219. case 'batch_1':
  220. $messages[] = 'results for batch 1<br />op 1: processed 10 elements';
  221. break;
  222. case 'batch_2':
  223. $messages[] = 'results for batch 2<br />op 2: processed 10 elements';
  224. break;
  225. case 'batch_3':
  226. $messages[] = 'results for batch 3<br />op 1: processed 10 elements<br />op 2: processed 10 elements';
  227. break;
  228. case 'batch_4':
  229. $messages[] = 'results for batch 4<br />op 1: processed 10 elements';
  230. $messages = array_merge($messages, $this->_resultMessages('batch_2'));
  231. break;
  232. case 'batch_5':
  233. $messages[] = 'results for batch 5<br />op 1: processed 10 elements. $context[\'finished\'] > 1 returned from batch process, with success.';
  234. break;
  235. case 'chained':
  236. $messages = array_merge($messages, $this->_resultMessages('batch_1'));
  237. $messages = array_merge($messages, $this->_resultMessages('batch_2'));
  238. $messages = array_merge($messages, $this->_resultMessages('batch_3'));
  239. break;
  240. }
  241. return $messages;
  242. }
  243. }
  244. /**
  245. * Tests for the Batch API Progress page.
  246. */
  247. class BatchPageTestCase extends DrupalWebTestCase {
  248. public static function getInfo() {
  249. return array(
  250. 'name' => 'Batch progress page',
  251. 'description' => 'Test the content of the progress page.',
  252. 'group' => 'Batch API',
  253. );
  254. }
  255. function setUp() {
  256. parent::setUp('batch_test');
  257. }
  258. /**
  259. * Tests that the batch API progress page uses the correct theme.
  260. */
  261. function testBatchProgressPageTheme() {
  262. // Make sure that the page which starts the batch (an administrative page)
  263. // is using a different theme than would normally be used by the batch API.
  264. variable_set('theme_default', 'bartik');
  265. variable_set('admin_theme', 'seven');
  266. // Log in as an administrator who can see the administrative theme.
  267. $admin_user = $this->drupalCreateUser(array('view the administration theme'));
  268. $this->drupalLogin($admin_user);
  269. // Visit an administrative page that runs a test batch, and check that the
  270. // theme that was used during batch execution (which the batch callback
  271. // function saved as a variable) matches the theme used on the
  272. // administrative page.
  273. $this->drupalGet('admin/batch-test/test-theme');
  274. // The stack should contain the name of the theme used on the progress
  275. // page.
  276. $this->assertEqual(batch_test_stack(), array('seven'), t('A progressive batch correctly uses the theme of the page that started the batch.'));
  277. }
  278. }
  279. /**
  280. * Tests the function _batch_api_percentage() to make sure that the rounding
  281. * works properly in all cases.
  282. */
  283. class BatchPercentagesUnitTestCase extends DrupalUnitTestCase {
  284. protected $testCases = array();
  285. public static function getInfo() {
  286. return array(
  287. 'name' => 'Batch percentages',
  288. 'description' => 'Unit tests of progress percentage rounding.',
  289. 'group' => 'Batch API',
  290. );
  291. }
  292. function setUp() {
  293. // Set up an array of test cases, where the expected values are the keys,
  294. // and the values are arrays with the keys 'total' and 'current',
  295. // corresponding with the function parameters of _batch_api_percentage().
  296. $this->testCases = array(
  297. // 1/2 is 50%.
  298. '50' => array('total' => 2, 'current' => 1),
  299. // Though we should never encounter a case where the current set is set
  300. // 0, if we did, we should get 0%.
  301. '0' => array('total' => 3, 'current' => 0),
  302. // 1/3 is closer to 33% than to 34%.
  303. '33' => array('total' => 3, 'current' => 1),
  304. // 2/3 is closer to 67% than to 66%.
  305. '67' => array('total' => 3, 'current' => 2),
  306. // 1/199 should round up to 1%.
  307. '1' => array('total' => 199, 'current' => 1),
  308. // 198/199 should round down to 99%.
  309. '99' => array('total' => 199, 'current' => 198),
  310. // 199/200 would have rounded up to 100%, which would give the false
  311. // impression of being finished, so we add another digit and should get
  312. // 99.5%.
  313. '99.5' => array('total' => 200, 'current' => 199),
  314. // The same logic holds for 1/200: we should get 0.5%.
  315. '0.5' => array('total' => 200, 'current' => 1),
  316. // Numbers that come out evenly, such as 50/200, should be forced to have
  317. // extra digits for consistancy.
  318. '25.0' => array('total' => 200, 'current' => 50),
  319. // Regardless of number of digits we're using, 100% should always just be
  320. // 100%.
  321. '100' => array('total' => 200, 'current' => 200),
  322. // 1998/1999 should similarly round down to 99.9%.
  323. '99.9' => array('total' => 1999, 'current' => 1998),
  324. // 1999/2000 should add another digit and go to 99.95%.
  325. '99.95' => array('total' => 2000, 'current' => 1999),
  326. // 19999/20000 should add yet another digit and go to 99.995%.
  327. '99.995' => array('total' => 20000, 'current' => 19999),
  328. // The next five test cases simulate a batch with a single operation
  329. // ('total' equals 1) that takes several steps to complete. Within the
  330. // operation, we imagine that there are 501 items to process, and 100 are
  331. // completed during each step. The percentages we get back should be
  332. // rounded the usual way for the first few passes (i.e., 20%, 40%, etc.),
  333. // but for the last pass through, when 500 out of 501 items have been
  334. // processed, we do not want to round up to 100%, since that would
  335. // erroneously indicate that the processing is complete.
  336. '20' => array('total' => 1, 'current' => 100/501),
  337. '40' => array('total' => 1, 'current' => 200/501),
  338. '60' => array('total' => 1, 'current' => 300/501),
  339. '80' => array('total' => 1, 'current' => 400/501),
  340. '99.8' => array('total' => 1, 'current' => 500/501),
  341. );
  342. require_once DRUPAL_ROOT . '/includes/batch.inc';
  343. parent::setUp();
  344. }
  345. /**
  346. * Test the _batch_api_percentage() function.
  347. */
  348. function testBatchPercentages() {
  349. foreach ($this->testCases as $expected_result => $arguments) {
  350. // PHP sometimes casts numeric strings that are array keys to integers,
  351. // cast them back here.
  352. $expected_result = (string) $expected_result;
  353. $total = $arguments['total'];
  354. $current = $arguments['current'];
  355. $actual_result = _batch_api_percentage($total, $current);
  356. if ($actual_result === $expected_result) {
  357. $this->pass(t('Expected the batch api percentage at the state @numerator/@denominator to be @expected%, and got @actual%.', array('@numerator' => $current, '@denominator' => $total, '@expected' => $expected_result, '@actual' => $actual_result)));
  358. }
  359. else {
  360. $this->fail(t('Expected the batch api percentage at the state @numerator/@denominator to be @expected%, but got @actual%.', array('@numerator' => $current, '@denominator' => $total, '@expected' => $expected_result, '@actual' => $actual_result)));
  361. }
  362. }
  363. }
  364. }