+++ /dev/null
-<?php
-
-namespace Unish;
-
-use Symfony\Component\Yaml\Yaml;
-use Webmozart\PathUtil\Path;
-
-abstract class UnishTestCase extends \PHPUnit_Framework_TestCase
-{
-
- /**
- * A list of Drupal sites that have been recently installed. They key is the
- * site name and values are details about each site.
- *
- * @var array
- */
- private static $sites = [];
-
- private static $sandbox;
-
- private static $drush;
-
- private static $tmp;
-
- private static $db_url;
-
- private static $usergroup = null;
-
- private static $backendOutputDelimiter = 'DRUSH_BACKEND_OUTPUT_START>>>%s<<<DRUSH_BACKEND_OUTPUT_END';
-
- /**
- * @return array
- */
- public static function getSites()
- {
- return self::$sites;
- }
-
- /**
- * @return array
- */
- public static function getAliases()
- {
- // Prefix @sut. onto each site.
- foreach (self::$sites as $key => $site) {
- $aliases[$key] = '@sut.' . $key;
- }
- return $aliases;
- }
-
- public static function getUri($site = 'dev')
- {
- return self::$sites[$site]['uri'];
- }
-
- /**
- * @return string
- */
- public static function getDrush()
- {
- return self::$drush;
- }
-
- /**
- * @return string
- */
- public static function getTmp()
- {
- return self::$tmp;
- }
-
- /**
- * @return string
- */
- public static function getSandbox()
- {
- return self::$sandbox;
- }
-
- /**
- * @return string
- */
- public static function getSut()
- {
- return Path::join(self::getTmp(), 'drush-sut');
- }
-
- /**
- * - Remove sandbox directory.
- * - Empty /modules, /profiles, /themes in SUT.
- */
- public static function cleanDirs()
- {
- if (empty(getenv('UNISH_DIRTY'))) {
- $sandbox = self::getSandbox();
- if (file_exists($sandbox)) {
- self::recursiveDelete($sandbox);
- }
- foreach (['modules', 'themes', 'profiles', 'drush'] as $dir) {
- $target = Path::join(self::getSut(), 'web', $dir, 'contrib');
- if (file_exists($target)) {
- self::recursiveDeleteDirContents($target);
- }
- }
- foreach (['sites/dev', 'sites/stage', 'sites/prod'] as $dir) {
- $target = Path::join(self::getSut(), 'web', $dir);
- if (file_exists($target)) {
- self::recursiveDelete($target);
- }
- }
- }
- }
-
- /**
- * @return string
- */
- public static function getDbUrl()
- {
- return self::$db_url;
- }
-
- /**
- * @return string
- */
- public static function getUserGroup()
- {
- return self::$usergroup;
- }
-
- /**
- * @return string
- */
- public static function getBackendOutputDelimiter()
- {
- return self::$backendOutputDelimiter;
- }
-
- public function __construct($name = null, array $data = [], $dataName = '')
- {
- parent::__construct($name, $data, $dataName);
-
- // Default drupal major version to run tests over.
- // @todo Remove this.
- if (!defined('UNISH_DRUPAL_MAJOR_VERSION')) {
- define('UNISH_DRUPAL_MAJOR_VERSION', '8');
- }
-
- // We read from env then globals then default to mysql.
- self::$db_url = getenv('UNISH_DB_URL') ?: (isset($GLOBALS['UNISH_DB_URL']) ? $GLOBALS['UNISH_DB_URL'] : 'mysql://root:@127.0.0.1');
-
- require_once __DIR__ . '/unish.inc';
- list($unish_tmp, $unish_sandbox, $unish_drush_dir) = \unishGetPaths();
- $unish_cache = Path::join($unish_sandbox, 'cache');
-
- self::$drush = $unish_drush_dir . '/drush';
- self::$tmp = $unish_tmp;
- self::$sandbox = $unish_sandbox;
- self::$usergroup = isset($GLOBALS['UNISH_USERGROUP']) ? $GLOBALS['UNISH_USERGROUP'] : null;
-
- self::setEnv(['CACHE_PREFIX' => $unish_cache]);
- $home = $unish_sandbox . '/home';
- self::setEnv(['HOME' => $home]);
- self::setEnv(['HOMEDRIVE' => $home]);
- $composer_home = $unish_cache . '/.composer';
- self::setEnv(['COMPOSER_HOME' => $composer_home]);
- self::setEnv(['ETC_PREFIX' => $unish_sandbox]);
- self::setEnv(['SHARE_PREFIX' => $unish_sandbox]);
- self::setEnv(['TEMP' => Path::join($unish_sandbox, 'tmp')]);
- self::setEnv(['DRUSH_AUTOLOAD_PHP' => PHPUNIT_COMPOSER_INSTALL]);
- }
-
- /**
- * We used to assure that each class starts with an empty sandbox directory and
- * a clean environment except for the SUT. History: http://drupal.org/node/1103568.
- */
- public static function setUpBeforeClass()
- {
- self::cleanDirs();
-
- // Create all the dirs.
- $sandbox = self::getSandbox();
- $dirs = [getenv('HOME') . '/.drush', $sandbox . '/etc/drush', $sandbox . '/share/drush/commands', "$sandbox/cache", getenv('TEMP')];
- foreach ($dirs as $dir) {
- self::mkdir($dir);
- }
-
- if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
- // Hack to make git use unix line endings on windows
- exec("git config --file $sandbox\\home\\.gitconfig core.autocrlf false", $output, $return);
- }
- parent::setUpBeforeClass();
- }
-
- /**
- * Runs after all tests in a class are run.
- */
- public static function tearDownAfterClass()
- {
- self::cleanDirs();
-
- self::$sites = [];
- parent::tearDownAfterClass();
- }
-
- /**
- * Print a log message to the console.
- *
- * @param string $message
- * @param string $type
- * Supported types are:
- * - notice
- * - verbose
- * - debug
- */
- public function log($message, $type = 'notice')
- {
- $line = "\nLog: $message\n";
- switch ($this->logLevel()) {
- case 'verbose':
- if (in_array($type, ['notice', 'verbose'])) {
- fwrite(STDERR, $line);
- }
- break;
- case 'debug':
- fwrite(STDERR, $line);
- break;
- default:
- if ($type == 'notice') {
- fwrite(STDERR, $line);
- }
- break;
- }
- }
-
- public function logLevel()
- {
- // -d is reserved by `phpunit`
- if (in_array('--debug', $_SERVER['argv'])) {
- return 'debug';
- } elseif (in_array('--verbose', $_SERVER['argv']) || in_array('-v', $_SERVER['argv'])) {
- return 'verbose';
- }
- }
-
- public static function isWindows()
- {
- return strtoupper(substr(PHP_OS, 0, 3)) == "WIN";
- }
-
- public static function getTarExecutable()
- {
- return self::isWindows() ? "bsdtar.exe" : "tar";
- }
-
- /**
- * Print out a tick mark.
- *
- * Useful for longer running tests to indicate they're working.
- */
- public function tick()
- {
- static $chars = ['/', '-', '\\', '|'];
- static $counter = 0;
- // ANSI support is flaky on Win32, so don't try to do ticks there.
- if (!$this->isWindows()) {
- print $chars[($counter++ % 4)] . "\033[1D";
- }
- }
-
- /**
- * Borrowed from Drush.
- * Checks operating system and returns
- * supported bit bucket folder.
- */
- public function bitBucket()
- {
- if (!$this->isWindows()) {
- return '/dev/null';
- } else {
- return 'nul';
- }
- }
-
- public static function escapeshellarg($arg)
- {
- // Short-circuit escaping for simple params (keep stuff readable)
- if (preg_match('|^[a-zA-Z0-9.:/_-]*$|', $arg)) {
- return $arg;
- } elseif (self::isWindows()) {
- return self::_escapeshellargWindows($arg);
- } else {
- return escapeshellarg($arg);
- }
- }
-
- public static function _escapeshellargWindows($arg)
- {
- // Double up existing backslashes
- $arg = preg_replace('/\\\/', '\\\\\\\\', $arg);
-
- // Double up double quotes
- $arg = preg_replace('/"/', '""', $arg);
-
- // Double up percents.
- $arg = preg_replace('/%/', '%%', $arg);
-
- // Add surrounding quotes.
- $arg = '"' . $arg . '"';
-
- return $arg;
- }
-
- /**
- * Helper function to generate a random string of arbitrary length.
- *
- * Copied from drush_generate_password(), which is otherwise not available here.
- *
- * @param $length
- * Number of characters the generated string should contain.
- * @return
- * The generated string.
- */
- public function randomString($length = 10)
- {
- // This variable contains the list of allowable characters for the
- // password. Note that the number 0 and the letter 'O' have been
- // removed to avoid confusion between the two. The same is true
- // of 'I', 1, and 'l'.
- $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
-
- // Zero-based count of characters in the allowable list:
- $len = strlen($allowable_characters) - 1;
-
- // Declare the password as a blank string.
- $pass = '';
-
- // Loop the number of times specified by $length.
- for ($i = 0; $i < $length; $i++) {
- // Each iteration, pick a random character from the
- // allowable string and append it to the password:
- $pass .= $allowable_characters[mt_rand(0, $len)];
- }
-
- return $pass;
- }
-
- public static function mkdir($path)
- {
- if (!is_dir($path)) {
- if (self::mkdir(dirname($path))) {
- if (@mkdir($path)) {
- return true;
- }
- }
- return false;
- }
- return true;
- }
-
- public static function recursiveCopy($src, $dst)
- {
- $dir = opendir($src);
- self::mkdir($dst);
- while (false !== ( $file = readdir($dir))) {
- if (( $file != '.' ) && ( $file != '..' )) {
- if (is_dir($src . '/' . $file)) {
- self::recursiveCopy($src . '/' . $file, $dst . '/' . $file);
- } else {
- copy($src . '/' . $file, $dst . '/' . $file);
- }
- }
- }
- closedir($dir);
- }
-
-
- /**
- * Deletes the specified file or directory and everything inside it.
- *
- * Usually respects read-only files and folders. To do a forced delete use
- * drush_delete_tmp_dir() or set the parameter $forced.
- *
- * To avoid permission denied error on Windows, make sure your CWD is not
- * inside the directory being deleted.
- *
- * This is essentially a copy of drush_delete_dir().
- *
- * @todo This sort of duplication isn't very DRY. This is bound to get out of
- * sync with drush_delete_dir(), as in fact it already has before.
- *
- * @param string $dir
- * The file or directory to delete.
- * @param bool $force
- * Whether or not to try everything possible to delete the directory, even if
- * it's read-only. Defaults to FALSE.
- * @param bool $follow_symlinks
- * Whether or not to delete symlinked files. Defaults to FALSE--simply
- * unlinking symbolic links.
- *
- * @return bool
- * FALSE on failure, TRUE if everything was deleted.
- *
- * @see drush_delete_dir()
- */
- public static function recursiveDelete($dir, $force = true, $follow_symlinks = false)
- {
- // Do not delete symlinked files, only unlink symbolic links
- if (is_link($dir) && !$follow_symlinks) {
- return unlink($dir);
- }
- // Allow to delete symlinks even if the target doesn't exist.
- if (!is_link($dir) && !file_exists($dir)) {
- return true;
- }
- if (!is_dir($dir)) {
- if ($force) {
- // Force deletion of items with readonly flag.
- @chmod($dir, 0777);
- }
- return unlink($dir);
- }
- if (self::recursiveDeleteDirContents($dir, $force) === false) {
- return false;
- }
- if ($force) {
- // Force deletion of items with readonly flag.
- @chmod($dir, 0777);
- }
- return rmdir($dir);
- }
-
- /**
- * Deletes the contents of a directory.
- *
- * This is essentially a copy of drush_delete_dir_contents().
- *
- * @param string $dir
- * The directory to delete.
- * @param bool $force
- * Whether or not to try everything possible to delete the contents, even if
- * they're read-only. Defaults to FALSE.
- *
- * @return bool
- * FALSE on failure, TRUE if everything was deleted.
- *
- * @see drush_delete_dir_contents()
- */
- public static function recursiveDeleteDirContents($dir, $force = false)
- {
- $scandir = @scandir($dir);
- if (!is_array($scandir)) {
- return false;
- }
-
- foreach ($scandir as $item) {
- if ($item == '.' || $item == '..') {
- continue;
- }
- if ($force) {
- @chmod($dir, 0777);
- }
- if (!self::recursiveDelete($dir . '/' . $item, $force)) {
- return false;
- }
- }
- return true;
- }
-
- public function webroot()
- {
- return Path::join(self::getSut(), 'web');
- }
-
- public function directoryCache($subdir = '')
- {
- return getenv('CACHE_PREFIX') . '/' . $subdir;
- }
-
- /**
- * @param $env
- * @return string
- */
- public function dbUrl($env)
- {
- return substr(self::getDbUrl(), 0, 6) == 'sqlite' ? "sqlite://sites/$env/files/unish.sqlite" : self::getDbUrl() . '/unish_' . $env;
- }
-
- public function dbDriver($db_url = null)
- {
- return parse_url($db_url ?: self::getDbUrl(), PHP_URL_SCHEME);
- }
-
- /**
- * Create some fixture sites that only have a 'settings.php' file
- * with a database record.
- *
- * @param array $sites key=site_subder value=array of extra alias data
- * @param string $aliasGroup Write aliases into a file named group.alias.yml
- */
- public function setUpSettings(array $sites, $aliasGroup = 'fixture')
- {
- foreach ($sites as $subdir => $extra) {
- $this->createSettings($subdir);
- }
- // Create basic site alias data with root and uri
- $siteAliasData = $this->createAliasFileData(array_keys($sites), $aliasGroup);
- // Add in caller-provided site alias data
- $siteAliasData = array_merge_recursive($siteAliasData, $sites);
- $this->writeSiteAliases($siteAliasData, $aliasGroup);
- }
-
- public function createSettings($subdir)
- {
- $settingsContents = <<<EOT
-<?php
-
-\$databases['default']['default'] = array (
- 'database' => 'unish_$subdir',
- 'username' => 'root',
- 'password' => '',
- 'prefix' => '',
- 'host' => '127.0.0.1',
- 'port' => '',
- 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
- 'driver' => 'mysql',
-);
-\$settings['install_profile'] = 'testing';
-EOT;
-
- $root = $this->webroot();
- $settingsPath = "$root/sites/$subdir/settings.php";
- self::mkdir(dirname($settingsPath));
- file_put_contents($settingsPath, $settingsContents);
- }
- /**
- * Assemble (and optionally install) one or more Drupal sites using a single codebase.
- *
- * It is no longer supported to pass alternative versions of Drupal or an alternative install_profile.
- */
- public function setUpDrupal($num_sites = 1, $install = false)
- {
- $sites_subdirs_all = ['dev', 'stage', 'prod', 'retired', 'elderly', 'dead', 'dust'];
- $sites_subdirs = array_slice($sites_subdirs_all, 0, $num_sites);
- $root = $this->webroot();
-
- // Install (if needed).
- foreach ($sites_subdirs as $subdir) {
- $this->installDrupal($subdir, $install);
- }
-
- // Write an empty sites.php. Needed for multi-site on D8+.
- if (!file_exists($root . '/sites/sites.php')) {
- copy($root . '/sites/example.sites.php', $root . '/sites/sites.php');
- }
-
- $siteData = $this->createAliasFile($sites_subdirs, 'unish');
- self::$sites = [];
- foreach ($siteData as $key => $data) {
- self::$sites[$key] = $data;
- }
- return self::$sites;
- }
-
- public function createAliasFileData($sites_subdirs, $aliasGroup = 'unish')
- {
- $root = $this->webroot();
- // Stash details about each site.
- $sites = [];
- foreach ($sites_subdirs as $subdir) {
- $sites[$subdir] = [
- 'root' => $root,
- 'uri' => $subdir,
- 'dbUrl' => $this->dbUrl($subdir),
- ];
- }
- return $sites;
- }
-
- public function createAliasFile($sites_subdirs, $aliasGroup = 'unish')
- {
- // Make an alias group for the sites.
- $sites = $this->createAliasFileData($sites_subdirs, $aliasGroup);
- $this->writeSiteAliases($sites, $aliasGroup);
-
- return $sites;
- }
-
- /**
- * Install a Drupal site.
- *
- * It is no longer supported to pass alternative versions of Drupal or an alternative install_profile.
- */
- public function installDrupal($env = 'dev', $install = false)
- {
- $root = $this->webroot();
- $uri = $env;
- $site = "$root/sites/$uri";
-
- // If specified, install Drupal as a multi-site.
- if ($install) {
- $options = [
- 'root' => $root,
- 'db-url' => $this->dbUrl($env),
- 'sites-subdir' => $uri,
- 'yes' => null,
- 'quiet' => null,
- ];
- $this->drush('site-install', ['testing', 'install_configure_form.enable_update_status_emails=NULL'], $options);
- // Give us our write perms back.
- chmod($site, 0777);
- } else {
- $this->mkdir($site);
- touch("$site/settings.php");
- }
- }
-
- /**
- * Write an alias group file and a config file which points to same dir.
- *
- * @param $sites
- */
- public function writeSiteAliases($sites, $aliasGroup = 'unish')
- {
- $this->writeUnishConfig($sites, [], $aliasGroup);
- }
-
- public function writeUnishConfig($unishAliases, $config = [], $aliasGroup = 'unish')
- {
- $etc = self::getSandbox() . '/etc/drush';
- $aliases_dir = Path::join($etc, 'sites');
- @mkdir($aliases_dir);
- file_put_contents(Path::join($aliases_dir, $aliasGroup . '.site.yml'), Yaml::dump($unishAliases, PHP_INT_MAX, 2));
- $config['drush']['paths']['alias-path'][] = $aliases_dir;
- file_put_contents(Path::join($etc, 'drush.yml'), Yaml::dump($config, PHP_INT_MAX, 2));
- }
-
- /**
- * The sitewide directory for Drupal extensions.
- */
- public function drupalSitewideDirectory()
- {
- return '/sites/all';
- }
-
- /**
- * Set environment variables that should be passed to child processes.
- *
- * @param array $vars
- * The variables to set.
- *
- * We will change implementation to take advantage of https://github.com/symfony/symfony/pull/19053/files once we drop Symfony 2 compat.
- */
- public static function setEnv(array $vars)
- {
- foreach ($vars as $k => $v) {
- putenv($k . '=' . $v);
- // Value must be a string. See \Symfony\Component\Process\Process::getDefaultEnv.
- $_SERVER[$k]= (string) $v;
- }
- }
-}