5 abstract class UnishTestCase extends \PHPUnit_Framework_TestCase {
8 * A list of Drupal sites that have been recently installed. They key is the
9 * site name and values are details about each site.
13 private static $sites = array();
15 function __construct($name = NULL, array $data = array(), $dataName = '') {
16 parent::__construct($name, $data, $dataName);
20 * Assure that each class starts with an empty sandbox directory and
21 * a clean environment - http://drupal.org/node/1103568.
23 public static function setUpBeforeClass() {
24 self::setUpFreshSandBox();
28 * Remove any pre-existing sandbox, then create a new one.
30 public static function setUpFreshSandBox() {
31 // Avoid perm denied error on Windows by moving out of the dir to be deleted.
32 chdir(dirname(UNISH_SANDBOX));
33 $sandbox = UNISH_SANDBOX;
34 if (file_exists($sandbox)) {
35 unish_file_delete_recursive($sandbox);
37 $ret = mkdir($sandbox, 0777, TRUE);
40 mkdir(getenv('HOME') . '/.drush', 0777, TRUE);
41 mkdir($sandbox . '/etc/drush', 0777, TRUE);
42 mkdir($sandbox . '/share/drush/commands', 0777, TRUE);
44 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
45 // Hack to make git use unix line endings on windows
46 // We need it to make hashes of files pulled from git match ones hardcoded in tests
47 if (!file_exists($sandbox . '\home')) {
48 mkdir($sandbox . '\home');
50 exec("git config --file $sandbox\\home\\.gitconfig core.autocrlf false", $output, $return);
55 * Runs after all tests in a class are run. Remove sandbox directory.
57 public static function tearDownAfterClass() {
58 chdir(dirname(UNISH_SANDBOX));
59 $dirty = getenv('UNISH_DIRTY');
60 if (file_exists(UNISH_SANDBOX) && empty($dirty)) {
61 unish_file_delete_recursive(UNISH_SANDBOX, TRUE);
63 self::$sites = array();
67 * Print a log message to the console.
69 * @param string $message
71 * Supported types are:
76 function log($message, $type = 'notice') {
77 $line = "\nLog: $message\n";
78 switch ($this->log_level()) {
80 if (in_array($type, array('notice', 'verbose'))) fwrite(STDERR, $line);
83 fwrite(STDERR, $line);
86 if ($type == 'notice') fwrite(STDERR, $line);
91 function log_level() {
92 // -d is reserved by `phpunit`
93 if (in_array('--debug', $_SERVER['argv'])) {
96 elseif (in_array('--verbose', $_SERVER['argv']) || in_array('-v', $_SERVER['argv'])) {
101 public static function is_windows() {
102 return strtoupper(substr(PHP_OS, 0, 3)) == "WIN";
105 public static function get_tar_executable() {
106 return self::is_windows() ? "bsdtar.exe" : "tar";
110 * Print out a tick mark.
112 * Useful for longer running tests to indicate they're working.
115 static $chars = array('/', '-', '\\', '|');
117 // ANSI support is flaky on Win32, so don't try to do ticks there.
118 if (!$this->is_windows()) {
119 print $chars[($counter++ % 4)] . "\033[1D";
124 * Converts a Windows path (dir1\dir2\dir3) into a Unix path (dir1/dir2/dir3).
125 * Also converts a cygwin "drive emulation" path (/cygdrive/c/dir1) into a
126 * proper drive path, still with Unix slashes (c:/dir1).
128 * @copied from Drush's environment.inc
130 function convert_path($path) {
131 $path = str_replace('\\','/', $path);
132 $path = preg_replace('/^\/cygdrive\/([A-Za-z])(.*)$/', '\1:\2', $path);
138 * Borrowed from Drush.
139 * Checks operating system and returns
140 * supported bit bucket folder.
142 function bit_bucket() {
143 if (!$this->is_windows()) {
151 public static function escapeshellarg($arg) {
152 // Short-circuit escaping for simple params (keep stuff readable)
153 if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) {
156 elseif (self::is_windows()) {
157 return self::_escapeshellarg_windows($arg);
160 return escapeshellarg($arg);
164 public static function _escapeshellarg_windows($arg) {
165 // Double up existing backslashes
166 $arg = preg_replace('/\\\/', '\\\\\\\\', $arg);
168 // Double up double quotes
169 $arg = preg_replace('/"/', '""', $arg);
171 // Double up percents.
172 $arg = preg_replace('/%/', '%%', $arg);
174 // Add surrounding quotes.
175 $arg = '"' . $arg . '"';
181 * Helper function to generate a random string of arbitrary length.
183 * Copied from drush_generate_password(), which is otherwise not available here.
186 * Number of characters the generated string should contain.
188 * The generated string.
190 public function randomString($length = 10) {
191 // This variable contains the list of allowable characters for the
192 // password. Note that the number 0 and the letter 'O' have been
193 // removed to avoid confusion between the two. The same is true
194 // of 'I', 1, and 'l'.
195 $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
197 // Zero-based count of characters in the allowable list:
198 $len = strlen($allowable_characters) - 1;
200 // Declare the password as a blank string.
203 // Loop the number of times specified by $length.
204 for ($i = 0; $i < $length; $i++) {
206 // Each iteration, pick a random character from the
207 // allowable string and append it to the password:
208 $pass .= $allowable_characters[mt_rand(0, $len)];
214 public function mkdir($path) {
215 if (!is_dir($path)) {
216 if ($this->mkdir(dirname($path))) {
226 public function recursive_copy($src, $dst) {
227 $dir = opendir($src);
229 while(false !== ( $file = readdir($dir)) ) {
230 if (( $file != '.' ) && ( $file != '..' )) {
231 if ( is_dir($src . '/' . $file) ) {
232 $this->recursive_copy($src . '/' . $file,$dst . '/' . $file);
235 copy($src . '/' . $file,$dst . '/' . $file);
243 return UNISH_SANDBOX . DIRECTORY_SEPARATOR . 'web';
246 function getSites() {
250 function directory_cache($subdir = '') {
251 return getenv('CACHE_PREFIX') . '/' . $subdir;
258 function db_url($env) {
259 return substr(UNISH_DB_URL, 0, 6) == 'sqlite' ? "sqlite://sites/$env/files/unish.sqlite" : UNISH_DB_URL . '/unish_' . $env;
262 function db_driver($db_url = UNISH_DB_URL) {
263 return parse_url(UNISH_DB_URL, PHP_URL_SCHEME);
266 function defaultInstallationVerion() {
267 return UNISH_DRUPAL_MAJOR_VERSION . UNISH_DRUPAL_MINOR_VERSION;
270 function setUpDrupal($num_sites = 1, $install = FALSE, $version_string = NULL, $profile = NULL) {
271 if (!$version_string) {
272 $version_string = UNISH_DRUPAL_MAJOR_VERSION;
274 $sites_subdirs_all = array('dev', 'stage', 'prod', 'retired', 'elderly', 'dead', 'dust');
275 $sites_subdirs = array_slice($sites_subdirs_all, 0, $num_sites);
276 $root = $this->webroot();
277 $major_version = substr($version_string, 0, 1);
279 if (!isset($profile)) {
280 $profile = $major_version >= 7 ? 'testing' : 'default';
282 $db_driver = $this->db_driver(UNISH_DB_URL);
284 $cache_keys = array($num_sites, $install ? 'install' : 'noinstall', $version_string, $profile, $db_driver);
285 $source = $this->directory_cache('environments') . '/' . implode('-', $cache_keys) . '.tar.gz';
286 if (file_exists($source)) {
287 $this->log('Cache HIT. Environment: ' . $source, 'verbose');
288 $this->drush('archive-restore', array($source), array('destination' => $root, 'overwrite' => NULL));
291 $this->log('Cache MISS. Environment: ' . $source, 'verbose');
292 // Build the site(s), install (if needed), then cache.
293 foreach ($sites_subdirs as $subdir) {
294 $this->fetchInstallDrupal($subdir, $install, $version_string, $profile);
297 'destination' => $source,
299 'uri' => reset($sites_subdirs),
303 $this->drush('archive-dump', array('@sites'), $options);
306 // Write an empty sites.php if we are on D7+. Needed for multi-site on D8 and
307 // used on D7 in \Unish\saCase::testBackendHonorsAliasOverride.
308 if ($major_version >= 7 && !file_exists($root . '/sites/sites.php')) {
309 copy($root . '/sites/example.sites.php', $root . '/sites/sites.php');
312 // Stash details about each site.
313 foreach ($sites_subdirs as $subdir) {
314 self::$sites[$subdir] = array(
317 'db_url' => $this->db_url($subdir),
319 // Make an alias for the site
320 $this->writeSiteAlias($subdir, $root, $subdir);
325 function fetchInstallDrupal($env = 'dev', $install = FALSE, $version_string = NULL, $profile = NULL, $separate_roots = FALSE) {
326 if (!$version_string) {
327 $version_string = UNISH_DRUPAL_MAJOR_VERSION;
329 $root = $this->webroot();
330 $uri = $separate_roots ? "default" : "$env";
332 $site = "$root/sites/$uri";
334 if (substr($version_string, 0, 1) == 6 && $this->db_driver(UNISH_DB_URL) == 'sqlite') {
336 $this->markTestSkipped("Drupal 6 does not support SQLite.");
339 // Download Drupal if not already present.
340 if (!file_exists($root)) {
342 'destination' => dirname($root),
343 'drupal-project-rename' => basename($root),
348 $this->drush('pm-download', array("drupal-$version_string"), $options);
349 // @todo This path is not proper in D8.
350 mkdir($root . '/sites/all/drush', 0777, TRUE);
353 // If specified, install Drupal as a multi-site.
357 'db-url' => $this->db_url($env),
358 'sites-subdir' => $uri,
362 $this->drush('site-install', array($profile), $options);
363 // Give us our write perms back.
368 touch("$site/settings.php");
372 function writeSiteAlias($name, $root, $uri) {
373 $alias_definition = array($name => array('root' => $root, 'uri' => $uri));
374 file_put_contents(UNISH_SANDBOX . '/etc/drush/' . $name . '.alias.drushrc.php', $this->unish_file_aliases($alias_definition));
378 * Prepare the contents of an aliases file.
380 function unish_file_aliases($aliases) {
381 foreach ($aliases as $name => $alias) {
382 $records[] = sprintf('$aliases[\'%s\'] = %s;', $name, var_export($alias, TRUE));
384 $contents = "<?php\n\n" . implode("\n\n", $records);
389 * @see drush_drupal_sitewide_directory()
391 function drupalSitewideDirectory($major_version = NULL) {
392 if (!$major_version) {
393 $major_version = UNISH_DRUPAL_MAJOR_VERSION;
395 return ($major_version < 8) ? '/sites/all' : '';