mirror of
https://github.com/e107inc/e107.git
synced 2025-08-29 09:10:23 +02:00
Preparation for merge with e107 repository
Moved all test files to e107_tests subdirectory
This commit is contained in:
46
e107_tests/lib/PriorityCallbacks.php
Normal file
46
e107_tests/lib/PriorityCallbacks.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* Execute callbacks before Codeception does
|
||||
*/
|
||||
|
||||
class PriorityCallbacks
|
||||
{
|
||||
/** @var array */
|
||||
private $shutdown_functions = [];
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
register_shutdown_function([$this, 'call_shutdown_functions']);
|
||||
}
|
||||
|
||||
public static function instance()
|
||||
{
|
||||
static $instance = null;
|
||||
if (!$instance instanceof self)
|
||||
{
|
||||
$instance = new static();
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public function call_shutdown_functions()
|
||||
{
|
||||
foreach ($this->shutdown_functions as $shutdown_function)
|
||||
{
|
||||
call_user_func($shutdown_function);
|
||||
}
|
||||
}
|
||||
|
||||
public function register_shutdown_function($callable)
|
||||
{
|
||||
$this->shutdown_functions[] = $callable;
|
||||
}
|
||||
|
||||
private function __clone() {}
|
||||
|
||||
private function __sleep() {}
|
||||
|
||||
private function __wakeup() {}
|
||||
}
|
||||
|
||||
PriorityCallbacks::instance();
|
10
e107_tests/lib/ci/config.ci.yml
Normal file
10
e107_tests/lib/ci/config.ci.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
deployer: 'local'
|
||||
url: 'http://set-this-to-your-acceptance-test-url.local/'
|
||||
db:
|
||||
host: 'mysql'
|
||||
dbname: 'app'
|
||||
user: 'root'
|
||||
password: 'Database Password for Continuous Integration'
|
||||
populate: true
|
||||
dump_path: 'tests/_data/e107_v2.1.9.sample.sql'
|
21
e107_tests/lib/ci/setup.sh
Executable file
21
e107_tests/lib/ci/setup.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
apt-get update
|
||||
apt-get install -y git zip libzip-dev
|
||||
|
||||
pecl install zip
|
||||
docker-php-ext-enable zip
|
||||
pecl install xdebug
|
||||
docker-php-ext-enable xdebug
|
||||
docker-php-ext-install pdo_mysql
|
||||
|
||||
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
|
||||
php composer-setup.php
|
||||
php -r "unlink('composer-setup.php');"
|
||||
|
||||
php composer.phar install
|
||||
php composer.phar update
|
||||
|
||||
git submodule update --init --recursive --remote
|
||||
|
||||
cp ./lib/ci/config.ci.yml ./config.yml
|
18
e107_tests/lib/config.php
Normal file
18
e107_tests/lib/config.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
$params = [];
|
||||
|
||||
foreach ([
|
||||
'config.sample.yml',
|
||||
'config.yml',
|
||||
'config.local.yml'
|
||||
] as $config_filename)
|
||||
{
|
||||
$absolute_config_path = codecept_root_dir() . '/' . $config_filename;
|
||||
if (file_exists($absolute_config_path))
|
||||
$params = array_replace_recursive($params, Yaml::parse(file_get_contents($absolute_config_path)));
|
||||
}
|
||||
|
||||
return $params;
|
1
e107_tests/lib/cpaneluapi
Submodule
1
e107_tests/lib/cpaneluapi
Submodule
Submodule e107_tests/lib/cpaneluapi added at f07eff729e
52
e107_tests/lib/deployers/Deployer.php
Normal file
52
e107_tests/lib/deployers/Deployer.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
abstract class Deployer
|
||||
{
|
||||
abstract public function start();
|
||||
abstract public function stop();
|
||||
|
||||
protected $params;
|
||||
|
||||
public function __construct($params = [])
|
||||
{
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
protected static function println($text = '')
|
||||
{
|
||||
codecept_debug($text);
|
||||
|
||||
//echo("${text}\n");
|
||||
|
||||
//$prefix = debug_backtrace()[1]['function'];
|
||||
//echo("[\033[1m${prefix}\033[0m] ${text}\n");
|
||||
}
|
||||
|
||||
protected $components = array();
|
||||
|
||||
/**
|
||||
* @param array $components
|
||||
*/
|
||||
public function setComponents($components)
|
||||
{
|
||||
$this->components = $components;
|
||||
}
|
||||
|
||||
public function unlinkAppFile($relative_path)
|
||||
{
|
||||
throw new \PHPUnit\Framework\SkippedTestError("Test wants \"$relative_path\" to be deleted from the app, ".
|
||||
"but the configured deployer ".get_class($this)." is not capable of doing that.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods not implemented
|
||||
*
|
||||
* @param $method_name
|
||||
* @param $arguments
|
||||
* @return null
|
||||
*/
|
||||
public function __call($method_name, $arguments)
|
||||
{
|
||||
throw new BadMethodCallException(get_class($this)."::$method_name is not implemented");
|
||||
}
|
||||
}
|
42
e107_tests/lib/deployers/DeployerFactory.php
Normal file
42
e107_tests/lib/deployers/DeployerFactory.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
spl_autoload_register(function($class_name) {
|
||||
$candidate_path = __DIR__ . "/$class_name.php";
|
||||
if (file_exists($candidate_path))
|
||||
{
|
||||
include_once($candidate_path);
|
||||
}
|
||||
});
|
||||
#include_once("$deployers_path/Deployer.php");
|
||||
#foreach (glob("$deployers_path/*.php") as $path)
|
||||
#{
|
||||
# include_once($path);
|
||||
#}
|
||||
|
||||
// here you can define custom actions
|
||||
// all public methods declared in helper class will be available in $I
|
||||
|
||||
class DeployerFactory
|
||||
{
|
||||
/**
|
||||
* @return \Deployer
|
||||
*/
|
||||
public static function create()
|
||||
{
|
||||
$params = unserialize(PARAMS_SERIALIZED);
|
||||
|
||||
$deployer = new NoopDeployer();
|
||||
switch ($params['deployer'])
|
||||
{
|
||||
case "local":
|
||||
$deployer = new LocalDeployer($params);
|
||||
break;
|
||||
case "sftp":
|
||||
$deployer = new SFTPDeployer($params);
|
||||
break;
|
||||
case "cpanel":
|
||||
$deployer = new cPanelDeployer($params);
|
||||
break;
|
||||
}
|
||||
return $deployer;
|
||||
}
|
||||
}
|
18
e107_tests/lib/deployers/LocalDeployer.php
Normal file
18
e107_tests/lib/deployers/LocalDeployer.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
class LocalDeployer extends NoopDeployer
|
||||
{
|
||||
public function unlinkAppFile($relative_path)
|
||||
{
|
||||
self::println("Deleting file \"$relative_path\" from deployed test location…");
|
||||
if (file_exists(APP_PATH."/$relative_path"))
|
||||
{
|
||||
unlink(APP_PATH."/$relative_path");
|
||||
self::println("Deleted file \"$relative_path\" from deployed test location");
|
||||
}
|
||||
else
|
||||
{
|
||||
self::println("No such file to delete: \"$relative_path\"");
|
||||
}
|
||||
}
|
||||
}
|
17
e107_tests/lib/deployers/NoopDeployer.php
Normal file
17
e107_tests/lib/deployers/NoopDeployer.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
class NoopDeployer extends Deployer
|
||||
{
|
||||
|
||||
public function start()
|
||||
{
|
||||
// Noop
|
||||
return null;
|
||||
}
|
||||
|
||||
public function stop()
|
||||
{
|
||||
// Noop
|
||||
return null;
|
||||
}
|
||||
}
|
107
e107_tests/lib/deployers/SFTPDeployer.php
Normal file
107
e107_tests/lib/deployers/SFTPDeployer.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
class SFTPDeployer extends Deployer
|
||||
{
|
||||
public function start()
|
||||
{
|
||||
self::println();
|
||||
self::println("=== SFTP Deployer – Bring Up ===");
|
||||
if (in_array('fs', $this->components))
|
||||
{
|
||||
$this->start_fs();
|
||||
}
|
||||
}
|
||||
|
||||
private function getFsParams()
|
||||
{
|
||||
return $this->params['fs'];
|
||||
}
|
||||
|
||||
private function generateSshpassPrefix()
|
||||
{
|
||||
if (empty($this->getFsParam('privkey_path')) &&
|
||||
!empty($this->getFsParam('password')))
|
||||
{
|
||||
return 'sshpass -p '.escapeshellarg($this->getFsParam('password')).' ';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getFsParam($key)
|
||||
{
|
||||
return $this->getFsParams()[$key];
|
||||
}
|
||||
|
||||
private function generateRsyncRemoteShell()
|
||||
{
|
||||
$prefix = 'ssh -p '.escapeshellarg($this->getFsParam('port'));
|
||||
if (!empty($this->getFsParam('privkey_path')))
|
||||
return $prefix.' -i ' . escapeshellarg($this->getFsParam('privkey_path'));
|
||||
else
|
||||
return $prefix;
|
||||
}
|
||||
|
||||
private static function runCommand($command, &$stdout = null, &$stderr = null)
|
||||
{
|
||||
$descriptorSpec = [
|
||||
1 => ['pipe', 'w'],
|
||||
2 => ['pipe', 'w'],
|
||||
];
|
||||
$pipes = [];
|
||||
self::println("Running this command…:");
|
||||
self::println($command);
|
||||
$resource = proc_open($command, $descriptorSpec, $pipes, APP_PATH);
|
||||
$stdout = stream_get_contents($pipes[1]);
|
||||
$stderr = stream_get_contents($pipes[2]);
|
||||
self::println("---------- stdout ----------");
|
||||
self::println(trim($stdout));
|
||||
self::println("---------- stderr ----------");
|
||||
self::println(trim($stderr));
|
||||
self::println("----------------------------");
|
||||
foreach ($pipes as $pipe)
|
||||
{
|
||||
fclose($pipe);
|
||||
}
|
||||
return proc_close($resource);
|
||||
}
|
||||
|
||||
public function stop()
|
||||
{
|
||||
self::println("=== SFTP Deployer – Tear Down ===");
|
||||
}
|
||||
|
||||
public function unlinkAppFile($relative_path)
|
||||
{
|
||||
self::println("Deleting file \"$relative_path\" from deployed test location…");
|
||||
$fs_params = $this->getFsParams();
|
||||
$command = $this->generateSshpassPrefix().
|
||||
$this->generateRsyncRemoteShell().
|
||||
" ".escapeshellarg("{$fs_params['user']}@{$fs_params['host']}").
|
||||
" ".escapeshellarg("rm -v " . escapeshellarg(rtrim($fs_params['path'], '/')."/$relative_path"));
|
||||
$retcode = self::runCommand($command);
|
||||
if ($retcode === 0)
|
||||
{
|
||||
self::println("Deleted file \"$relative_path\" from deployed test location");
|
||||
}
|
||||
else
|
||||
{
|
||||
self::println("No such file to delete: \"$relative_path\"");
|
||||
}
|
||||
}
|
||||
|
||||
private function start_fs()
|
||||
{
|
||||
$fs_params = $this->getFsParams();
|
||||
$fs_params['path'] = rtrim($fs_params['path'], '/') . '/';
|
||||
$command = $this->generateSshpassPrefix() .
|
||||
'rsync -e ' .
|
||||
escapeshellarg($this->generateRsyncRemoteShell()) .
|
||||
' --delete -avzHXShs ' .
|
||||
escapeshellarg(rtrim(APP_PATH, '/') . '/') . ' ' .
|
||||
escapeshellarg("{$fs_params['user']}@{$fs_params['host']}:{$fs_params['path']}");
|
||||
$retcode = self::runCommand($command);
|
||||
if ($retcode !== 0) {
|
||||
throw new Exception("SFTP deployment failed. Run with --debug to see stdout and stderr.");
|
||||
}
|
||||
}
|
||||
}
|
371
e107_tests/lib/deployers/cPanelDeployer.php
Normal file
371
e107_tests/lib/deployers/cPanelDeployer.php
Normal file
@@ -0,0 +1,371 @@
|
||||
<?php
|
||||
|
||||
include_once(__DIR__ . "/../cpaneluapi/cpaneluapi.class.php");
|
||||
include_once(__DIR__ . "/Deployer.php");
|
||||
|
||||
class cPanelDeployer extends Deployer
|
||||
{
|
||||
const TEST_PREFIX = 'test_';
|
||||
const TARGET_RELPATH = 'public_html/';
|
||||
const DEFAULT_COMPONENTS = ['db', 'fs'];
|
||||
protected $credentials;
|
||||
protected $cPanel;
|
||||
protected $run_id;
|
||||
protected $db_id;
|
||||
protected $homedir;
|
||||
protected $docroot;
|
||||
protected $domain;
|
||||
private $skip_mysql_remote_hosts = false;
|
||||
|
||||
function __construct($params = [])
|
||||
{
|
||||
parent::__construct($params);
|
||||
$this->credentials = $params['hosting'];
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
self::println();
|
||||
self::println("=== cPanel Deployer – Bring Up ===");
|
||||
$creds = $this->credentials;
|
||||
if (!$creds['hostname'] ||
|
||||
!$creds['username'] ||
|
||||
!$creds['password'])
|
||||
{
|
||||
throw new Exception("Cannot deploy cPanel environment because credentials are missing.");
|
||||
}
|
||||
|
||||
$this->prepare();
|
||||
|
||||
foreach ($this->components as $component)
|
||||
{
|
||||
$method = "prepare_${component}";
|
||||
if (!method_exists($this, $method))
|
||||
{
|
||||
throw new Exception("Unsupported component \"${component}\" requested.");
|
||||
}
|
||||
}
|
||||
foreach ($this->components as $component)
|
||||
{
|
||||
$method = "prepare_${component}";
|
||||
$this->$method();
|
||||
}
|
||||
}
|
||||
|
||||
private function prepare()
|
||||
{
|
||||
$username = &$this->credentials['username'];
|
||||
$password = &$this->credentials['password'];
|
||||
$hostname = &$this->credentials['hostname'];
|
||||
|
||||
$this->run_id = $run_id = uniqid(self::TEST_PREFIX);
|
||||
|
||||
self::println("Test run ID: ".$this->run_id);
|
||||
|
||||
$this->cPanel = $cPanel = new cpanelAPI($username, $password, $hostname);
|
||||
|
||||
self::println("Connecting to cPanel at \"${hostname}\" with username \"${username}\"…");
|
||||
|
||||
$domains_data = $cPanel->uapi->DomainInfo->domains_data();
|
||||
if (!$domains_data)
|
||||
{
|
||||
throw new Exception("Cannot connect to cPanel at \"${hostname}\" with username \"${username}\" and password \"${password}\"");
|
||||
}
|
||||
$userdata = $domains_data->{'data'};
|
||||
$this->homedir = $homedir = $userdata->{'main_domain'}->{'homedir'};
|
||||
$this->docroot = $docroot = $userdata->{'main_domain'}->{'documentroot'};
|
||||
$this->domain = $domain = $userdata->{'main_domain'}->{'domain'};
|
||||
|
||||
self::println("Obtained home directory from cPanel: " . $this->homedir);
|
||||
self::println("Obtained document root from cPanel: " . $this->docroot);
|
||||
self::println("Obtained domain name from cPanel: " . $this->domain);
|
||||
|
||||
$acceptance_tests = self::get_active_acceptance_tests($cPanel, $homedir);
|
||||
|
||||
self::println("Adding this test (".$this->run_id.") to registered tests list…");
|
||||
$run_time = microtime(true);
|
||||
array_push($acceptance_tests,
|
||||
['id' => $run_id,
|
||||
'time' => $run_time
|
||||
]);
|
||||
|
||||
self::write_acceptance_tests($cPanel, $homedir, $acceptance_tests);
|
||||
|
||||
$valid_acceptance_test_ids = self::get_acceptance_test_ids($acceptance_tests);
|
||||
self::println("Current unexpired tests: [".implode(", ", $valid_acceptance_test_ids)."]");
|
||||
|
||||
self::prune_inactive_acceptance_test_resources($cPanel, $valid_acceptance_test_ids);
|
||||
}
|
||||
|
||||
private static function get_active_acceptance_tests($cPanel, $homedir)
|
||||
{
|
||||
self::println("Retrieving existing registered tests from cPanel account…");
|
||||
$acceptance_tests = [];
|
||||
$acceptance_tests_apiresponse = $cPanel->uapi->Fileman->get_file_content(['dir' => $homedir, 'file' => 'acceptance_tests.status.txt']);
|
||||
if (!is_null($acceptance_tests_apiresponse->{'data'}))
|
||||
{
|
||||
$acceptance_tests_raw = $acceptance_tests_apiresponse->{'data'}->{'content'};
|
||||
$acceptance_tests = (array) json_decode($acceptance_tests_raw, true);
|
||||
self::prune_acceptance_tests($acceptance_tests);
|
||||
}
|
||||
return $acceptance_tests;
|
||||
}
|
||||
|
||||
private static function prune_acceptance_tests(array &$list, $id_to_remove = null)
|
||||
{
|
||||
foreach ($list as $key => $item)
|
||||
{
|
||||
$time = $item['time'];
|
||||
if ($item['id'] === $id_to_remove || $time <= strtotime("now - 10 seconds"))
|
||||
{
|
||||
unset($list[$key]);
|
||||
}
|
||||
}
|
||||
$list = array_values($list);
|
||||
return $list;
|
||||
}
|
||||
|
||||
private static function write_acceptance_tests($cPanel, $homedir, $acceptance_tests)
|
||||
{
|
||||
$acceptance_tests_json = json_encode($acceptance_tests, JSON_PRETTY_PRINT);
|
||||
|
||||
self::println("Saving registered tests list to cPanel account…");
|
||||
$cPanel->uapi->Fileman->save_file_content(['dir' => $homedir, 'file' => 'acceptance_tests.status.txt', 'content' => $acceptance_tests_json]);
|
||||
}
|
||||
|
||||
private static function get_acceptance_test_ids(array $list)
|
||||
{
|
||||
$ids = [];
|
||||
foreach ($list as $item)
|
||||
{
|
||||
$ids[] = $item['id'];
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
private static function prune_inactive_acceptance_test_resources($cPanel, $valid_acceptance_test_ids)
|
||||
{
|
||||
self::println("Pruning expired tests…");
|
||||
$listdbs = $cPanel->api2->MysqlFE->listdbs()->{'cpanelresult'}->{'data'};
|
||||
self::prune_mysql_databases($listdbs, $valid_acceptance_test_ids, $cPanel);
|
||||
|
||||
$listdbusers = $cPanel->api2->MysqlFE->listusers()->{'cpanelresult'}->{'data'};
|
||||
self::prune_mysql_users($listdbusers, $valid_acceptance_test_ids, $cPanel);
|
||||
|
||||
$target_files_apiresponse = $cPanel->uapi->Fileman->list_files(['dir' => self::TARGET_RELPATH]);
|
||||
$target_files = $target_files_apiresponse->{'data'};
|
||||
foreach ($target_files as $target_file)
|
||||
{
|
||||
$questionable_filename = $target_file->{'file'};
|
||||
if (substr($questionable_filename, 0, strlen(self::TEST_PREFIX)) === self::TEST_PREFIX &&
|
||||
!in_array($questionable_filename, $valid_acceptance_test_ids))
|
||||
{
|
||||
self::println("Deleting expired test folder \"".self::TARGET_RELPATH.$questionable_filename."\"…");
|
||||
$cPanel->api2->Fileman->fileop(['op' => 'unlink', 'sourcefiles' => self::TARGET_RELPATH.$questionable_filename]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function prune_mysql_databases($dbs, $ids, $cPanel)
|
||||
{
|
||||
$prefix = $cPanel->user."_".self::TEST_PREFIX;
|
||||
foreach ($dbs as $db)
|
||||
{
|
||||
$db = (array) $db;
|
||||
if (substr($db['db'], 0, strlen($prefix)) !== $prefix)
|
||||
continue;
|
||||
$questionable_db = substr($db['db'], strlen($prefix));
|
||||
if (!in_array($questionable_db, $ids))
|
||||
{
|
||||
self::println("Deleting expired MySQL database \"".$db['db']."\"…");
|
||||
$cPanel->uapi->Mysql->delete_database(['name' => $db['db']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function prune_mysql_users($users, $ids, $cPanel)
|
||||
{
|
||||
$prefix = $cPanel->user."_".self::TEST_PREFIX;
|
||||
foreach ($users as $user)
|
||||
{
|
||||
$user = (array) $user;
|
||||
if (substr($user['user'], 0, strlen($prefix)) !== $prefix)
|
||||
continue;
|
||||
$questionable_user = substr($user['user'], strlen($prefix));
|
||||
if (!in_array($questionable_user, $ids))
|
||||
{
|
||||
self::println("Deleting expired MySQL user \"".$user['user']."\"…");
|
||||
$cPanel->uapi->Mysql->delete_user(['name' => $user['user']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function stop()
|
||||
{
|
||||
self::println("=== cPanel Deployer – Tear Down ===");
|
||||
$cPanel = $this->cPanel;
|
||||
$acceptance_tests = self::get_active_acceptance_tests($cPanel, $this->homedir);
|
||||
self::println("Removing this test (".$this->run_id.") from registered tests list…");
|
||||
self::prune_acceptance_tests($acceptance_tests, $this->run_id);
|
||||
self::write_acceptance_tests($cPanel, $this->homedir, $acceptance_tests);
|
||||
|
||||
$valid_acceptance_test_ids = self::get_acceptance_test_ids($acceptance_tests);
|
||||
self::println("Current unexpired tests: [".implode(", ", $valid_acceptance_test_ids)."]");
|
||||
self::prune_inactive_acceptance_test_resources($cPanel, $valid_acceptance_test_ids);
|
||||
|
||||
if (!$this->skip_mysql_remote_hosts)
|
||||
{
|
||||
self::clean_mysql_remote_hosts($cPanel);
|
||||
}
|
||||
}
|
||||
|
||||
private static function clean_mysql_remote_hosts($cPanel)
|
||||
{
|
||||
$remote_hosts = $cPanel->api2->MysqlFE->gethosts()->{'cpanelresult'}->{'data'};
|
||||
if (in_array('%', $remote_hosts, true))
|
||||
{
|
||||
self::println("Removing cPanel MySQL remote host '%'…");
|
||||
$cPanel->uapi->Mysql->delete_host(['host' => '%']);
|
||||
}
|
||||
}
|
||||
|
||||
public function reconfigure_db($module)
|
||||
{
|
||||
$db = $module->getDbModule();
|
||||
$Db_config = $db->_getConfig();
|
||||
$Db_config['dsn'] = $this->getDsn();
|
||||
$Db_config['user'] = $this->getDbUsername();
|
||||
$Db_config['password'] = $this->getDbPassword();
|
||||
$db->_reconfigure($Db_config);
|
||||
// Next line is used to make connection available to any code after this point
|
||||
//$this->getModule('\Helper\DelayedDb')->_delayedInitialize();
|
||||
}
|
||||
|
||||
private function getDsn()
|
||||
{
|
||||
$hostname = $this->credentials['hostname'];
|
||||
$db_id = $this->getDbName();
|
||||
return "mysql:host=${hostname};dbname=${db_id}";
|
||||
}
|
||||
|
||||
private function getDbName()
|
||||
{
|
||||
return $this->db_id;
|
||||
}
|
||||
|
||||
private function getDbUsername()
|
||||
{
|
||||
return $this->db_id;
|
||||
}
|
||||
|
||||
private function getDbPassword()
|
||||
{
|
||||
return $this->run_id;
|
||||
}
|
||||
|
||||
public function reconfigure_fs($module)
|
||||
{
|
||||
$url = $this->getUrl();
|
||||
$browser = $module->getBrowserModule();
|
||||
$browser->_reconfigure(array('url' => $url));
|
||||
}
|
||||
|
||||
private function getUrl()
|
||||
{
|
||||
return "http://".$this->domain."/".$this->run_id."/";
|
||||
}
|
||||
|
||||
public function unlinkAppFile($relative_path)
|
||||
{
|
||||
self::println("Deleting file \"$relative_path\" from deployed test location…");
|
||||
$this->cPanel->api2->Fileman->fileop(['op' => 'unlink',
|
||||
'sourcefiles' => self::TARGET_RELPATH.$this->run_id."/".$relative_path]);
|
||||
}
|
||||
|
||||
private function prepare_db()
|
||||
{
|
||||
$cPanel = $this->cPanel;
|
||||
$username = &$this->credentials['username'];
|
||||
$run_id = &$this->run_id;
|
||||
$this->db_id = $db_id = "${username}_${run_id}";
|
||||
|
||||
self::println("Ensuring that MySQL users allow any remote access hosts (%)…");
|
||||
$remote_hosts = $cPanel->api2->MysqlFE->gethosts()->{'cpanelresult'}->{'data'};
|
||||
if (!in_array('%', $remote_hosts, true))
|
||||
{
|
||||
$cPanel->uapi->Mysql->add_host(['host' => '%']);
|
||||
register_shutdown_function(function() use ($cPanel)
|
||||
{
|
||||
self::clean_mysql_remote_hosts($cPanel);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->skip_mysql_remote_hosts = true;
|
||||
}
|
||||
|
||||
self::println("Creating new MySQL database \"${db_id}\"…");
|
||||
$cPanel->uapi->Mysql->create_database(['name' => $db_id]);
|
||||
|
||||
self::println("Creating new MySQL user \"${db_id}\" with password \"${run_id}\"…");
|
||||
$cPanel->uapi->Mysql->create_user(['name' => $db_id, 'password' => $run_id]);
|
||||
self::println("Granting ALL PRIVILEGES to MySQL user \"${db_id}\"…");
|
||||
$cPanel->uapi->Mysql->set_privileges_on_database(['user' => $db_id,
|
||||
'database' => $db_id,
|
||||
'privileges' => 'ALL PRIVILEGES'
|
||||
]);
|
||||
}
|
||||
|
||||
private function prepare_fs()
|
||||
{
|
||||
$cPanel = $this->cPanel;
|
||||
$app_archive = self::archive_app(APP_PATH, $this->run_id);
|
||||
$app_archive_path = stream_get_meta_data($app_archive)['uri'];
|
||||
$app_archive_name = basename($app_archive_path);
|
||||
self::println("Sending archive to cPanel server…");
|
||||
$cPanel->uapi->post->Fileman
|
||||
->upload_files(['dir' => self::TARGET_RELPATH,
|
||||
'file-1' => new CURLFile($app_archive_path)
|
||||
]);
|
||||
self::println("Extracting archive on cPanel server…");
|
||||
$cPanel->api2->Fileman
|
||||
->fileop(['op' => 'extract',
|
||||
'sourcefiles' => self::TARGET_RELPATH.$app_archive_name,
|
||||
'destfiles' => '.'
|
||||
]);
|
||||
self::println("Deleting archive from cPanel server…");
|
||||
$cPanel->api2->Fileman
|
||||
->fileop(['op' => 'unlink',
|
||||
'sourcefiles' => self::TARGET_RELPATH.$app_archive_name
|
||||
]);
|
||||
}
|
||||
|
||||
private static function archive_app($path, $prefix = '')
|
||||
{
|
||||
$tmp_file = tmpfile();
|
||||
$tmp_file_path = stream_get_meta_data($tmp_file)['uri'];
|
||||
self::println("Touched temporary archive file; path: ".$tmp_file_path);
|
||||
$archive = new ZipArchive();
|
||||
$archive->open($tmp_file_path, ZipArchive::OVERWRITE);
|
||||
$i = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
|
||||
self::println("Adding app to temporary archive…");
|
||||
$path = realpath($path);
|
||||
foreach ($i as $relpath => $value)
|
||||
{
|
||||
$realpath = realpath($relpath);
|
||||
if (substr($realpath, 0, strlen($path)) === $path)
|
||||
$relpath = substr($realpath, strlen($path));
|
||||
if (substr($relpath, -3) === "/.." ||
|
||||
substr($relpath, -2) === "/." ||
|
||||
!file_exists($realpath) ||
|
||||
!is_file($realpath) ||
|
||||
empty($relpath)) continue;
|
||||
$relpath = $prefix . $relpath;
|
||||
$archive->addFile($realpath, $relpath);
|
||||
$archive->setExternalAttributesName($relpath, ZipArchive::OPSYS_UNIX, fileperms($realpath) << 16);
|
||||
}
|
||||
$archive->close();
|
||||
|
||||
return $tmp_file;
|
||||
}
|
||||
}
|
66
e107_tests/lib/preparers/E107Preparer.php
Normal file
66
e107_tests/lib/preparers/E107Preparer.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
class E107Preparer implements Preparer
|
||||
{
|
||||
const TEST_HASH = '000000test'; // see e107_config.php
|
||||
|
||||
public function snapshot()
|
||||
{
|
||||
return $this->deleteHashDirs();
|
||||
}
|
||||
|
||||
public function rollback()
|
||||
{
|
||||
return $this->deleteHashDirs();
|
||||
}
|
||||
|
||||
protected function deleteHashDirs()
|
||||
{
|
||||
$system = APP_PATH."/e107_system/".self::TEST_HASH;
|
||||
$this->deleteDir($system);
|
||||
|
||||
$media = APP_PATH."/e107_media/".self::TEST_HASH;
|
||||
$this->deleteDir($media);
|
||||
|
||||
if(is_dir($system))
|
||||
{
|
||||
throw new Exception(get_class() . " couldn't delete ".$system);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function deleteDir($dirPath)
|
||||
{
|
||||
codecept_debug(get_class() . ' is deleting '.escapeshellarg($dirPath).'…');
|
||||
|
||||
if(!is_dir($dirPath))
|
||||
{
|
||||
// echo ($dirPath . "must be a directory");
|
||||
return null;
|
||||
}
|
||||
|
||||
if(substr($dirPath, strlen($dirPath) - 1, 1) != '/')
|
||||
{
|
||||
$dirPath .= '/';
|
||||
}
|
||||
|
||||
$files = glob($dirPath . '*', GLOB_MARK);
|
||||
|
||||
foreach($files as $file)
|
||||
{
|
||||
if(is_dir($file))
|
||||
{
|
||||
$this->deleteDir($file);
|
||||
}
|
||||
else
|
||||
{
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
if(is_dir($dirPath))
|
||||
{
|
||||
rmdir($dirPath);
|
||||
}
|
||||
}
|
||||
}
|
122
e107_tests/lib/preparers/GitPreparer.php
Normal file
122
e107_tests/lib/preparers/GitPreparer.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
class GitPreparer implements Preparer
|
||||
{
|
||||
const TEST_IN_PROGRESS = 'TEST-IN-PROGRESS';
|
||||
const TEST_IN_PROGRESS_FILE = APP_PATH."/".self::TEST_IN_PROGRESS;
|
||||
|
||||
public function snapshot()
|
||||
{
|
||||
$this->debug('Snapshot requested');
|
||||
return $this->setVcsInProgress();
|
||||
}
|
||||
|
||||
public function rollback()
|
||||
{
|
||||
$this->debug('Rollback requested');
|
||||
return $this->unsetVcsInProgress();
|
||||
}
|
||||
|
||||
protected function setVcsInProgress()
|
||||
{
|
||||
// Cleanup in case of a fatal error
|
||||
PriorityCallbacks::instance()->register_shutdown_function([$this, 'rollback']);
|
||||
|
||||
if ($this->isVcsInProgress())
|
||||
{
|
||||
$this->debug('Git repo shows test in progress. Probably crashed test.');
|
||||
$this->unsetVcsInProgress();
|
||||
}
|
||||
|
||||
$this->debug('Setting test locks in Git…');
|
||||
|
||||
touch(self::TEST_IN_PROGRESS_FILE);
|
||||
$this->runCommand('git add -f '.escapeshellarg(self::TEST_IN_PROGRESS_FILE));
|
||||
$this->runCommand('git add -A -f');
|
||||
|
||||
$commit_command = 'git commit -a --no-gpg-sign ' .
|
||||
"-m '".self::TEST_IN_PROGRESS."! If test crashed, run `git log -1` for instructions' " .
|
||||
"-m 'Running the test again after fixing the crash will clear this commit\nand any related stashes.' " .
|
||||
"-m 'Alternatively, run these commands to restore the repository to its\npre-test state:' ";
|
||||
$unsetVcsInProgress_commands = [
|
||||
'git reset --hard HEAD',
|
||||
'git clean -fdx',
|
||||
// 'git stash pop',
|
||||
'git reset --mixed HEAD^',
|
||||
'rm -fv '.escapeshellarg(self::TEST_IN_PROGRESS)
|
||||
];
|
||||
foreach($unsetVcsInProgress_commands as $command)
|
||||
{
|
||||
$commit_command .= "-m ".escapeshellarg($command)." ";
|
||||
}
|
||||
$this->runCommand($commit_command);
|
||||
// $this->runCommand('git stash push --all -m '.escapeshellarg(self::TEST_IN_PROGRESS));
|
||||
}
|
||||
|
||||
protected function isVcsInProgress($case = '')
|
||||
{
|
||||
$in_progress = [];
|
||||
|
||||
$in_progress['file'] = file_exists(self::TEST_IN_PROGRESS_FILE);
|
||||
|
||||
$stdout = '';
|
||||
$this->runCommand('git log -1 --pretty=%B', $stdout);
|
||||
$in_progress['commit'] = strpos($stdout, self::TEST_IN_PROGRESS) !== false;
|
||||
|
||||
$stdout = '';
|
||||
$this->runCommand('git stash list', $stdout);
|
||||
$in_progress['stash'] = strpos($stdout, self::TEST_IN_PROGRESS) !== false;
|
||||
|
||||
if(!empty($case)) return $in_progress[$case];
|
||||
return in_array(true, $in_progress);
|
||||
}
|
||||
|
||||
protected function runCommand($command, &$stdout = "", &$stderr = "")
|
||||
{
|
||||
$descriptorspec = [
|
||||
1 => ['pipe', 'w'],
|
||||
2 => ['pipe', 'w'],
|
||||
];
|
||||
$pipes = [];
|
||||
$resource = proc_open($command, $descriptorspec, $pipes, APP_PATH);
|
||||
$stdout .= stream_get_contents($pipes[1]);
|
||||
$stderr .= stream_get_contents($pipes[2]);
|
||||
foreach ($pipes as $pipe)
|
||||
{
|
||||
fclose($pipe);
|
||||
}
|
||||
proc_close($resource);
|
||||
}
|
||||
|
||||
protected function unsetVcsInProgress()
|
||||
{
|
||||
if (!$this->isVcsInProgress())
|
||||
{
|
||||
$this->debug('No test locks found');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->debug('Rolling back Git repo to pre-test state…');
|
||||
$this->runCommand('git reset --hard HEAD');
|
||||
$this->runCommand('git clean -fdx');
|
||||
|
||||
while ($this->isVcsInProgress('commit'))
|
||||
{
|
||||
$this->debug('Going back one commit…');
|
||||
$this->runCommand('git reset --mixed HEAD^');
|
||||
}
|
||||
|
||||
while ($this->isVcsInProgress('stash'))
|
||||
{
|
||||
$this->debug('Popping top of stash…');
|
||||
$this->runCommand('git stash pop');
|
||||
}
|
||||
|
||||
@unlink(self::TEST_IN_PROGRESS_FILE);
|
||||
}
|
||||
|
||||
protected function debug($message)
|
||||
{
|
||||
codecept_debug(get_class() . ': ' . $message);
|
||||
}
|
||||
}
|
7
e107_tests/lib/preparers/Preparer.php
Normal file
7
e107_tests/lib/preparers/Preparer.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
interface Preparer
|
||||
{
|
||||
public function snapshot();
|
||||
public function rollback();
|
||||
}
|
57
e107_tests/lib/preparers/PreparerFactory.php
Normal file
57
e107_tests/lib/preparers/PreparerFactory.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
spl_autoload_register(function($class_name) {
|
||||
$candidate_path = __DIR__ . "/$class_name.php";
|
||||
if (file_exists($candidate_path))
|
||||
{
|
||||
include_once($candidate_path);
|
||||
}
|
||||
});
|
||||
|
||||
class PreparerFactory
|
||||
{
|
||||
/**
|
||||
* @return Preparer
|
||||
*/
|
||||
public static function create()
|
||||
{
|
||||
if (self::systemIsSlow())
|
||||
{
|
||||
return self::createFromName('E107Preparer');
|
||||
}
|
||||
elseif (self::systemHasGit() && self::appPathIsGitRepo())
|
||||
{
|
||||
return self::createFromName('GitPreparer');
|
||||
}
|
||||
return self::createFromName('E107Preparer');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $class_name
|
||||
* @return Preparer
|
||||
*/
|
||||
public static function createFromName($class_name)
|
||||
{
|
||||
codecept_debug('Instantiating Preparer: ' . $class_name);
|
||||
return new $class_name();
|
||||
}
|
||||
|
||||
private static function systemIsSlow()
|
||||
{
|
||||
return self::systemIsWindows();
|
||||
}
|
||||
|
||||
private static function systemIsWindows()
|
||||
{
|
||||
return strtolower(substr(php_uname('s'), 0, 3)) === 'win';
|
||||
}
|
||||
|
||||
private static function systemHasGit()
|
||||
{
|
||||
return stripos(shell_exec('git --version'), 'git version') !== false;
|
||||
}
|
||||
|
||||
private static function appPathIsGitRepo()
|
||||
{
|
||||
return file_exists(APP_PATH."/.git");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user