backendTest.php

  1. 8.0.x tests/backendTest.php
  2. 6.x tests/backendTest.php
  3. 7.x tests/backendTest.php
  4. 4.x tests/backendTest.php
  5. 5.x tests/backendTest.php
  6. master tests/backendTest.php

Classes

File

tests/backendTest.php
View source
  1. <?php
  2. /*
  3. * @file
  4. * We choose to test the backend system in two parts.
  5. * - Origin. These tests assure that we are generate a proper ssh command
  6. * when a backend invoke is needed.
  7. * - Target. These tests assure that drush generates a delimited JSON array
  8. * when called with --backend option.
  9. *
  10. * Advantages of this approach:
  11. * - No network calls and thus more robust.
  12. * - No network calls and thus faster.
  13. *
  14. * @group base
  15. */
  16. class backendCase extends Drush_CommandTestCase {
  17. // Test to insure that calling drush_invoke_process with 'dispatch-using-alias'
  18. // will build a command string that uses the alias instead of --root and --uri.
  19. function testDispatchUsingAlias() {
  20. $aliasPath = UNISH_SANDBOX . '/aliases';
  21. mkdir($aliasPath);
  22. $aliasFile = $aliasPath . '/foo.aliases.drushrc.php';
  23. $aliasContents = <<<EOD
  24. <?php
  25. // Writtne by Unish. This file is safe to delete.
  26. \$aliases['dev'] = array('root' => '/fake/path/to/root', 'uri' => 'default');
  27. EOD;
  28. file_put_contents($aliasFile, $aliasContents);
  29. $options = array(
  30. 'alias-path' => $aliasPath,
  31. 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile.
  32. 'backend' => TRUE,
  33. );
  34. $php = <<<EOD
  35. \$valuesUsingAlias = drush_invoke_process("@dev", "unit-return-argv", array(), array(), array("dispatch-using-alias" => TRUE));
  36. \$valuesWithoutAlias = drush_invoke_process("@dev", "unit-return-argv", array(), array(), array());
  37. return array('with' => \$valuesUsingAlias['object'], 'without' => \$valuesWithoutAlias['object']);
  38. EOD;
  39. $this->drush('php-eval', array($php), $options);
  40. $parsed = parse_backend_output($this->getOutput());
  41. // $parsed['with'] and $parsed['without'] now contain an array
  42. // each with the original arguments passed in with and without
  43. // 'dispatch-using-alias', respectively.
  44. $argDifference = array_diff($parsed['object']['with'], $parsed['object']['without']);
  45. $this->assertEquals(array_diff(array_values($argDifference), array('@foo.dev')), array());
  46. $argDifference = array_diff($parsed['object']['without'], $parsed['object']['with']);
  47. $this->assertEquals(array_diff(array_values($argDifference), array('--root=/fake/path/to/root', '--uri=default')), array());
  48. }
  49. /*
  50. * Covers the following origin responsibilities.
  51. * - A remote host is recognized in site specification.
  52. * - Generates expected ssh command.
  53. *
  54. * General handling of site aliases will be in sitealiasTest.php.
  55. */
  56. function testOrigin() {
  57. $exec = sprintf('%s %s version arg1 arg2 --simulate --ssh-options=%s | grep ssh', UNISH_DRUSH, self::escapeshellarg('user@server/path/to/drupal#sitename'), self::escapeshellarg('-i mysite_dsa'));
  58. $this->execute($exec);
  59. $bash = $this->escapeshellarg('drush --invoke --simulate --uri=sitename --root=/path/to/drupal version arg1 arg2 2>&1');
  60. $expected = "Simulating backend invoke: ssh -i mysite_dsa user@server $bash 2>&1";
  61. $output = $this->getOutput();
  62. $this->assertEquals($expected, $output, 'Expected ssh command was built');
  63. }
  64. /*
  65. * Covers the following target responsibilities.
  66. * - Interpret stdin as options as per REST API.
  67. * - Successfully execute specified command.
  68. * - JSON object has expected contents (including errors).
  69. * - JSON object is wrapped in expected delimiters.
  70. */
  71. function testTarget() {
  72. $stdin = json_encode(array('filter'=>'sql'));
  73. $exec = sprintf('echo %s | %s version --backend', self::escapeshellarg($stdin), UNISH_DRUSH);
  74. $this->execute($exec);
  75. $parsed = parse_backend_output($this->getOutput());
  76. $this->assertTrue((bool) $parsed, 'Successfully parsed backend output');
  77. $this->assertArrayHasKey('log', $parsed);
  78. $this->assertArrayHasKey('output', $parsed);
  79. $this->assertArrayHasKey('object', $parsed);
  80. $this->assertEquals(self::EXIT_SUCCESS, $parsed['error_status']);
  81. // This assertion shows that `help` was called and that stdin options were respected.
  82. $this->assertStringStartsWith('drush ', $parsed['output']);
  83. $this->assertEquals('Bootstrap to phase 0.', $parsed['log'][0]['message']);
  84. // Check error propogation by requesting an invalid command (missing Drupal site).
  85. $this->drush('core-cron', array(), array('backend' => NULL), NULL, NULL, self::EXIT_ERROR);
  86. $parsed = parse_backend_output($this->getOutput());
  87. $this->assertEquals(1, $parsed['error_status']);
  88. $this->assertArrayHasKey('DRUSH_NO_DRUPAL_ROOT', $parsed['error_log']);
  89. }
  90. /*
  91. * Covers the following target responsibilities.
  92. * - Insures that the 'Drush version' line from drush status appears in the output.
  93. * - Insures that the backend output start marker appears in the output (this is a backend command).
  94. * - Insures that the drush output appears before the backend output start marker (output is displayed in 'real time' as it is produced).
  95. */
  96. function testRealtimeOutput() {
  97. $exec = sprintf('%s core-status --backend --nocolor 2>&1', UNISH_DRUSH);
  98. $this->execute($exec);
  99. $output = $this->getOutput();
  100. $drush_version_offset = strpos($output, "Drush version");
  101. $backend_output_offset = strpos($output, "DRUSH_BACKEND_OUTPUT_START>>>");
  102. $this->assertTrue($drush_version_offset !== FALSE, "'Drush version' string appears in output.");
  103. $this->assertTrue($backend_output_offset !== FALSE, "Drush backend output marker appears in output.");
  104. $this->assertTrue($drush_version_offset < $backend_output_offset, "Drush version string appears in output before the backend output marker.");
  105. }
  106. /**
  107. * Covers the following target responsibilities.
  108. * - Insures that function result is returned in --backend mode
  109. */
  110. function testBackendFunctionResult() {
  111. $php = "return 'bar'";
  112. $this->drush('php-eval', array($php), array('backend' => NULL));
  113. $parsed = parse_backend_output($this->getOutput());
  114. // assert that $parsed has 'bar'
  115. $this->assertEquals("'bar'", var_export($parsed['object'], TRUE));
  116. }
  117. /**
  118. * Covers the following target responsibilities.
  119. * - Insures that backend_set_result is returned in --backend mode
  120. * - Insures that the result code for the function does not overwrite
  121. * the explicitly-set value
  122. */
  123. function testBackendSetResult() {
  124. $php = "drush_backend_set_result('foo'); return 'bar'";
  125. $this->drush('php-eval', array($php), array('backend' => NULL));
  126. $parsed = parse_backend_output($this->getOutput());
  127. // assert that $parsed has 'foo' and not 'bar'
  128. $this->assertEquals("'foo'", var_export($parsed['object'], TRUE));
  129. }
  130. /**
  131. * Covers the following target responsibilities.
  132. * - Insures that the backend option 'invoke-multiple' will cause multiple commands to be executed.
  133. * - Insures that the right number of commands run.
  134. * - Insures that the 'concurrent'-format result array is returned.
  135. * - Insures that correct results are returned from each command.
  136. */
  137. function testBackendInvokeMultiple() {
  138. $options = array(
  139. 'backend' => NULL,
  140. 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile.
  141. );
  142. $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('x' => 'y'), array('invoke-multiple' => '3')); return \$values;";
  143. $this->drush('php-eval', array($php), $options);
  144. $parsed = parse_backend_output($this->getOutput());
  145. // assert that $parsed has a 'concurrent'-format output result
  146. $this->assertEquals('concurrent', implode(',', array_keys($parsed['object'])));
  147. // assert that the concurrent output has indexes 0, 1 and 2 (in any order)
  148. $concurrent_indexes = array_keys($parsed['object']['concurrent']);
  149. sort($concurrent_indexes);
  150. $this->assertEquals('0,1,2', implode(',', $concurrent_indexes));
  151. foreach ($parsed['object']['concurrent'] as $index => $values) {
  152. // assert that each result contains 'x' => 'y' and nothing else
  153. $this->assertEquals("array (
  154. 'x' => 'y',
  155. )", var_export($values['object'], TRUE));
  156. }
  157. }
  158. /**
  159. * Covers the following target responsibilities.
  160. * - Insures that arrays are stripped when using --backend mode's method GET
  161. * - Insures that arrays can be returned as the function result of
  162. * backend invoke.
  163. */
  164. function testBackendMethodGet() {
  165. $options = array(
  166. 'backend' => NULL,
  167. 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile.
  168. );
  169. $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('x' => 'y', 'data' => array('a' => 1, 'b' => 2)), array('method' => 'GET')); return array_key_exists('object', \$values) ? \$values['object'] : 'no result';";
  170. $this->drush('php-eval', array($php), $options);
  171. $parsed = parse_backend_output($this->getOutput());
  172. // assert that $parsed has 'x' but not 'data'
  173. $this->assertEquals("array (
  174. 'x' => 'y',
  175. )", var_export($parsed['object'], TRUE));
  176. }
  177. /**
  178. * Covers the following target responsibilities.
  179. * - Insures that complex arrays can be passed through when using --backend mode's method POST
  180. * - Insures that arrays can be returned as the function result of
  181. * backend invoke.
  182. */
  183. function testBackendMethodPost() {
  184. $options = array(
  185. 'backend' => NULL,
  186. 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile.
  187. );
  188. $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('x' => 'y', 'data' => array('a' => 1, 'b' => 2)), array('method' => 'POST')); return array_key_exists('object', \$values) ? \$values['object'] : 'no result';";
  189. $this->drush('php-eval', array($php), $options);
  190. $parsed = parse_backend_output($this->getOutput());
  191. // assert that $parsed has 'x' and 'data'
  192. $this->assertEquals(array (
  193. 'x' => 'y',
  194. 'data' =>
  195. array (
  196. 'a' => 1,
  197. 'b' => 2,
  198. ),
  199. ), $parsed['object']);
  200. }
  201. /**
  202. * Covers the following target responsibilities.
  203. * - Insures that backend invoke can properly re-assemble packets
  204. * that are split across process-read-size boundaries.
  205. *
  206. * This test works by repeating testBackendMethodGet(), while setting
  207. * '#process-read-size' to a very small value, insuring that packets
  208. * will be split.
  209. */
  210. function testBackendReassembleSplitPackets() {
  211. $options = array(
  212. 'backend' => NULL,
  213. 'include' => dirname(__FILE__), // Find unit.drush.inc commandfile.
  214. );
  215. $read_sizes_to_test = array(4096, 128, 16);
  216. foreach ($read_sizes_to_test as $read_size) {
  217. $log_message="";
  218. for ($i = 1; $i <= 16; $i++) {
  219. $log_message .= "X";
  220. $php = "\$values = drush_invoke_process('@none', 'unit-return-options', array('value'), array('log-message' => '$log_message', 'x' => 'y$read_size', 'data' => array('a' => 1, 'b' => 2)), array('method' => 'GET', '#process-read-size' => $read_size)); return array_key_exists('object', \$values) ? \$values['object'] : 'no result';";
  221. $this->drush('php-eval', array($php), $options);
  222. $parsed = parse_backend_output($this->getOutput());
  223. // assert that $parsed has 'x' but not 'data'
  224. $all_warnings=array();
  225. foreach ($parsed['log'] as $log) {
  226. if ($log['type'] == 'warning') {
  227. $all_warnings[] = $log['message'];
  228. }
  229. }
  230. $this->assertEquals("$log_message,done", implode(',', $all_warnings), 'Log reconstruction with read_size ' . $read_size);
  231. $this->assertEquals("array (
  232. 'x' => 'y$read_size',
  233. )", var_export($parsed['object'], TRUE));
  234. }
  235. }
  236. }
  237. }
  238. class backendUnitCase extends Drush_UnitTestCase {
  239. /**
  240. * Covers the following target responsibilities.
  241. * - Insures that drush_invoke_process called with fork backend set is able
  242. * to invoke a non-blocking process.
  243. */
  244. function testBackendFork() {
  245. // Need to set DRUSH_COMMAND so that drush will be called and not phpunit
  246. define('DRUSH_COMMAND', UNISH_DRUSH);
  247. // Ensure that file that will be created by forked process does not exist
  248. // before invocation.
  249. $test_file = UNISH_SANDBOX . '/fork_test.txt';
  250. if (file_exists($test_file)) {
  251. unlink($test_file);
  252. }
  253. // Sleep for a milisecond, then create the file
  254. $ev_php = "usleep(1000);fopen('$test_file','a');";
  255. drush_invoke_process("@none", "ev", array($ev_php), array(), array("fork" => TRUE));
  256. // Test file does not exist immediate after process forked
  257. $this->assertEquals(file_exists($test_file), FALSE);
  258. // Check every 100th of a second for up to 4 seconds to see if the file appeared
  259. $repetitions = 400;
  260. while (!file_exists($test_file) && ($repetitions > 0)) {
  261. usleep(10000);
  262. }
  263. // Assert that the file did finally appear
  264. $this->assertEquals(file_exists($test_file), TRUE);
  265. }
  266. }