drush_testcase.inc 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. <?php
  2. /*
  3. * @file
  4. * Initialize a sandboxed environment. Starts with call unish_init() at bottom.
  5. */
  6. abstract class Drush_TestCase extends PHPUnit_Framework_TestCase {
  7. // Unix exit codes.
  8. const EXIT_SUCCESS = 0;
  9. const EXIT_ERROR = 1;
  10. /*
  11. * An array of Drupal sites that are setup in the drush-sandbox.
  12. */
  13. var $sites;
  14. function __construct() {
  15. $this->_output = false;
  16. }
  17. /**
  18. * Assure that each class starts with an empty sandbox directory and
  19. * a clean environment - http://drupal.org/node/1103568.
  20. */
  21. public static function setUpBeforeClass() {
  22. $sandbox = UNISH_SANDBOX;
  23. if (file_exists($sandbox)) {
  24. unish_file_delete_recursive($sandbox);
  25. }
  26. $ret = mkdir($sandbox, 0777, TRUE);
  27. chdir(UNISH_SANDBOX);
  28. mkdir(getenv('HOME') . '/.drush', 0777, TRUE);
  29. mkdir($sandbox . '/etc/drush', 0777, TRUE);
  30. mkdir($sandbox . '/share/drush/commands', 0777, TRUE);
  31. }
  32. /**
  33. * Runs after each test case. Remove sandbox directory.
  34. */
  35. public static function tearDownAfterClass() {
  36. if (file_exists(UNISH_SANDBOX)) {
  37. unish_file_delete_recursive(UNISH_SANDBOX);
  38. }
  39. }
  40. public static function is_windows() {
  41. return (strtoupper(substr(PHP_OS, 0, 3)) == "WIN");
  42. }
  43. public static function escapeshellarg($arg) {
  44. // Short-circuit escaping for simple params (keep stuff readable)
  45. if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) {
  46. return $arg;
  47. }
  48. elseif (self::is_windows()) {
  49. return self::_escapeshellarg_windows($arg);
  50. }
  51. else {
  52. return escapeshellarg($arg);
  53. }
  54. }
  55. public static function _escapeshellarg_windows($arg) {
  56. // Double up existing backslashes
  57. $arg = preg_replace('/\\\/', '\\\\\\\\', $arg);
  58. // Escape double quotes.
  59. $arg = preg_replace('/"/', '\\"', $arg);
  60. // Escape single quotes.
  61. $arg = preg_replace('/\'/', '\\\'', $arg);
  62. // Add surrounding quotes.
  63. $arg = '"' . $arg . '"';
  64. return $arg;
  65. }
  66. /**
  67. * Actually runs the command. Does not trap the error stream output as this
  68. * need PHP 4.3+.
  69. *
  70. * @param string $command
  71. * The actual command line to run.
  72. * @return integer
  73. * Exit code. Usually self::EXIT_ERROR or self::EXIT_SUCCESS.
  74. */
  75. function execute($command, $expected_return = self::EXIT_SUCCESS) {
  76. $this->_output = FALSE;
  77. // todo check verbose level from phpunit.
  78. if (TRUE) {
  79. print "\nExecuting: $command \n";
  80. }
  81. exec($command, $this->_output, $return);
  82. $this->assertEquals($expected_return, $return, 'Unexpected exit code: ' . $command);
  83. return $return;
  84. }
  85. /**
  86. * Invoke drush in via execute().
  87. *
  88. * @param command
  89. * A defined drush command such as 'cron', 'status' or any of the available ones such as 'drush pm'.
  90. * @param args
  91. * Command arguments.
  92. * @param $options
  93. * An associative array containing options.
  94. * @param $site_specification
  95. * A site alias or site specification. Include the '@' at start of a site alias.
  96. * @param $cd
  97. * A directory to change into before executing.
  98. * @return integer
  99. * An exit code.
  100. */
  101. function drush($command, array $args = array(), array $options = array(), $site_specification = NULL, $cd = NULL) {
  102. $cmd[] = $cd ? sprintf('cd %s;', self::escapeshellarg($cd)) : NULL;
  103. $cmd[] = UNISH_DRUSH;
  104. $cmd[] = empty($site_specification) ? NULL : self::escapeshellarg($site_specification);
  105. $cmd[] = $command;
  106. if (in_array('--verbose', $_SERVER['argv'])) $args[] = '--verbose';
  107. if (in_array('--debug', $_SERVER['argv'])) $args[] = '--debug';
  108. foreach ($args as $arg) {
  109. $cmd[] = self::escapeshellarg($arg);
  110. }
  111. $options['nocolor'] = NULL;
  112. foreach ($options as $key => $value) {
  113. if (is_null($value)) {
  114. $cmd[] = "--$key";
  115. }
  116. else {
  117. $cmd[] = "--$key=" . self::escapeshellarg($value);
  118. }
  119. }
  120. $exec = array_filter($cmd, 'strlen'); // Remove NULLs
  121. return $this->execute(implode(' ', $exec));
  122. }
  123. /**
  124. * Accessor for the last output.
  125. * @return string Output as text.
  126. * @access public
  127. */
  128. function getOutput() {
  129. return implode("\n", $this->_output);
  130. }
  131. /**
  132. * Accessor for the last output.
  133. * @return array Output as array of lines.
  134. * @access public
  135. */
  136. function getOutputAsList() {
  137. return $this->_output;
  138. }
  139. function setUpDrupal($env = 'dev', $install = FALSE, $version_string = '7.x', $profile = NULL) {
  140. $root = UNISH_SANDBOX . '/web';
  141. $this->sites[$env]['root'] = $root;
  142. $site = "$root/sites/$env";
  143. if (is_null($profile)) {
  144. $profile = substr($version_string, 0, 1) >= 7 ? 'testing' : 'default';
  145. }
  146. // Download Drupal if not already present.
  147. if (!file_exists($root)) {
  148. $options = array(
  149. 'destination' => UNISH_SANDBOX,
  150. 'drupal-project-rename' => 'web',
  151. 'yes' => NULL,
  152. 'quiet' => NULL,
  153. );
  154. $this->drush('pm-download', array("drupal-$version_string"), $options);
  155. }
  156. // If specified, install Drupal as a multi-site.
  157. if ($install) {
  158. $options = array(
  159. 'root' => $root,
  160. 'db-url' => UNISH_DB_URL . '/unish_' . $env,
  161. 'sites-subdir' => $env,
  162. 'yes' => NULL,
  163. 'quiet' => NULL,
  164. );
  165. $this->drush('site-install', array($profile), $options);
  166. // Give us our write perms back.
  167. $ret = chmod($site, 0777);
  168. // Stash the db_url for this site.
  169. $this->sites[$env]['db_url'] = UNISH_DB_URL . '/unish_' . $env;
  170. }
  171. else {
  172. mkdir($site);
  173. touch("$site/settings.php");
  174. }
  175. // Make an alias for the site
  176. $alias_definition = array($env => array('root' => $root, 'uri' => $env));
  177. file_put_contents(UNISH_SANDBOX . '/etc/drush/' . $env . '.alias.drushrc.php', $this->file_aliases($alias_definition));
  178. }
  179. // Copied from D7 - profiles/standard/standard.install
  180. function create_node_types_php() {
  181. $php = "
  182. \$types = array(
  183. array(
  184. 'type' => 'page',
  185. 'name' => 'Basic page',
  186. 'base' => 'node_content',
  187. 'description' => 'Use <em>basic pages</em> for your static content, such as an \'About us\' page.',
  188. 'custom' => 1,
  189. 'modified' => 1,
  190. 'locked' => 0,
  191. ),
  192. array(
  193. 'type' => 'article',
  194. 'name' => 'Article',
  195. 'base' => 'node_content',
  196. 'description' => 'Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.',
  197. 'custom' => 1,
  198. 'modified' => 1,
  199. 'locked' => 0,
  200. ),
  201. );
  202. foreach (\$types as \$type) {
  203. \$type = node_type_set_defaults(\$type);
  204. node_type_save(\$type);
  205. node_add_body_field(\$type);
  206. }
  207. ";
  208. return $php;
  209. }
  210. /*
  211. * Prepare the contents of an aliases file.
  212. */
  213. function file_aliases($aliases) {
  214. foreach ($aliases as $name => $alias) {
  215. $records[] = sprintf('$aliases[\'%s\'] = %s;', $name, var_export($alias, TRUE));
  216. }
  217. $contents = "<?php\n\n" . implode("\n\n", $records);
  218. return $contents;
  219. }
  220. /**
  221. * Same code as drush_delete_dir().
  222. * @see drush_delete_dir()
  223. *
  224. * @param string $dir
  225. * @return boolean
  226. */
  227. function file_delete_recursive($dir) {
  228. if (!file_exists($dir)) {
  229. return TRUE;
  230. }
  231. if (!is_dir($dir)) {
  232. @chmod($dir, 0777); // Make file writeable
  233. return unlink($dir);
  234. }
  235. foreach (scandir($dir) as $item) {
  236. if ($item == '.' || $item == '..') {
  237. continue;
  238. }
  239. if (!self::file_delete_recursive($dir.'/'.$item)) {
  240. return FALSE;
  241. }
  242. }
  243. return rmdir($dir);
  244. }
  245. }
  246. /*
  247. * Initialize our environment at he start of each run (i.e. suite).
  248. */
  249. function unish_init() {
  250. // We read from globals here because env can be empty and ini did not work in quick test.
  251. define('UNISH_DB_URL', getenv('UNISH_DB_URL') ? getenv('UNISH_DB_URL') : !empty($GLOBALS['UNISH_DB_URL']) ? $GLOBALS['UNISH_DB_URL'] : 'mysql://root:@127.0.0.1');
  252. // UNISH_DRUSH value can come from phpunit.xml or `which drush`.
  253. if (!defined('UNISH_DRUSH')) {
  254. // Let the UNISH_DRUSH environment variable override if set.
  255. $unish_drush = isset($_SERVER['UNISH_DRUSH']) ? $_SERVER['UNISH_DRUSH'] : NULL;
  256. $unish_drush = isset($GLOBALS['UNISH_DRUSH']) ? $GLOBALS['UNISH_DRUSH'] : $unish_drush;
  257. if (empty($unish_drush)) {
  258. $unish_drush = Drush_TestCase::is_windows() ? exec('for %i in (drush) do @echo. %~$PATH:i') : trim(`which drush`);
  259. }
  260. define('UNISH_DRUSH', $unish_drush);
  261. }
  262. define('UNISH_TMP', getenv('UNISH_TMP') ? getenv('UNISH_TMP') : (isset($GLOBALS['UNISH_TMP']) ? $GLOBALS['UNISH_TMP'] : sys_get_temp_dir()));
  263. define('UNISH_SANDBOX', UNISH_TMP . '/drush-sandbox');
  264. $home = UNISH_SANDBOX . '/home';
  265. putenv("HOME=$home");
  266. putenv("HOMEDRIVE=$home");
  267. putenv('ETC_PREFIX=' . UNISH_SANDBOX);
  268. putenv('SHARE_PREFIX=' . UNISH_SANDBOX);
  269. // Cache dir lives outside the sandbox so that we get persistence across classes.
  270. $cache = UNISH_TMP . '/drush_cache';
  271. putenv("CACHE_PREFIX=" . $cache);
  272. // Wipe at beginning of run.
  273. if (file_exists($cache)) {
  274. unish_file_delete_recursive($cache);
  275. }
  276. }
  277. /**
  278. * Same code as drush_delete_dir().
  279. * @see drush_delete_dir()
  280. *
  281. * @param string $dir
  282. * @return boolean
  283. */
  284. function unish_file_delete_recursive($dir) {
  285. if (!file_exists($dir)) {
  286. return TRUE;
  287. }
  288. if (!is_dir($dir)) {
  289. @chmod($dir, 0777); // Make file writeable
  290. return unlink($dir);
  291. }
  292. foreach (scandir($dir) as $item) {
  293. if ($item == '.' || $item == '..') {
  294. continue;
  295. }
  296. if (!unish_file_delete_recursive($dir.'/'.$item)) {
  297. return FALSE;
  298. }
  299. }
  300. return rmdir($dir);
  301. }
  302. // This code is in global scope.
  303. // TODO: I would rather this code at top of file, but I get Fatal error: Class 'Drush_TestCase' not found
  304. unish_init();