From 88845b0cbe898bdab35dffb625be98153c1969a9 Mon Sep 17 00:00:00 2001 From: Sergii Bondarenko Date: Sun, 13 Dec 2015 03:16:11 +0200 Subject: [PATCH 1/3] Added logic for dopping temporary database --- src/Utils/DatabaseManager.php | 36 +++++++++++++++++++++++++------- src/Utils/FormValueAssertion.php | 2 +- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Utils/DatabaseManager.php b/src/Utils/DatabaseManager.php index 305a8d0..79d912b 100644 --- a/src/Utils/DatabaseManager.php +++ b/src/Utils/DatabaseManager.php @@ -45,7 +45,8 @@ public function __construct($connection, $callee) throw new \InvalidArgumentException(sprintf('An object of "%s" type does not exist.', $callee)); } - /** @var array $databases */ + $databases = []; + require sprintf('%s/%s/settings.php', DRUPAL_ROOT, conf_path()); if (empty($databases[$connection])) { @@ -59,11 +60,7 @@ public function __construct($connection, $callee) $this->newName = "tqextension_$this->originalName"; $this->credentials = sprintf($this->credentials, $info['username'], $info['password']); - foreach (['drop', 'create'] as $action) { - $this->exec("mysql $this->credentials -e '$action database $this->newName;'"); - } - - $this->exec("mysqldump $this->credentials $this->originalName | mysql $this->credentials $this->newName"); + $this->cloneDatabase(); } /** @@ -74,10 +71,35 @@ public function __destruct() $this->exec("mysqldump $this->credentials $this->newName | mysql $this->credentials $this->originalName"); } + /** + * Store original database to temporary for future restoring. + */ + private function cloneDatabase() + { + $actions = []; + + // Need to drop temporary database if it was created previously. + if (!empty($this->exec("mysql $this->credentials -e 'show databases' | grep $this->newName"))) { + $actions[] = "drop"; + } + + $actions[] = "create"; + + foreach ($actions as $action) { + $this->exec("mysql $this->credentials -e '$action database $this->newName;'"); + } + + $this->exec("mysqldump $this->credentials $this->originalName | mysql $this->credentials $this->newName"); + } + /** * Executes a shell command. * * @param string $command + * Command to execute. + * + * @return string + * Result of a shell command. */ private function exec($command) { @@ -87,6 +109,6 @@ private function exec($command) call_user_func([$this->callee, 'debug'], [$command]); } - shell_exec($command); + return trim(shell_exec($command)); } } diff --git a/src/Utils/FormValueAssertion.php b/src/Utils/FormValueAssertion.php index 13ff848..3d2c795 100644 --- a/src/Utils/FormValueAssertion.php +++ b/src/Utils/FormValueAssertion.php @@ -127,7 +127,7 @@ public function checkable() } /** - * @param string[] $allowedElements + * @param array[] $allowedElements * Element machine names. */ private function restrictElements(array $allowedElements) From c2c5d21d009ae10905c512d88c9b84ccc42bf2e6 Mon Sep 17 00:00:00 2001 From: Sergii Bondarenko Date: Sun, 13 Dec 2015 13:15:00 +0200 Subject: [PATCH 2/3] Improved DatabaseManager. Added small methods for creation, dropping and copying databases. --- src/Utils/DatabaseManager.php | 87 ++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/src/Utils/DatabaseManager.php b/src/Utils/DatabaseManager.php index 79d912b..be71b40 100644 --- a/src/Utils/DatabaseManager.php +++ b/src/Utils/DatabaseManager.php @@ -1,27 +1,29 @@ + * @author Sergii Bondarenko, */ namespace Drupal\TqExtension\Utils; class DatabaseManager { /** + * MySQL and MySQLDump login arguments. + * * @var string */ - private $credentials = '-u%s -p%s'; + private $credentials = '-u%s -p%s -h%s -P%s'; /** * Name of original database. * * @var string */ - private $originalName = ''; + private $source = ''; /** * Name of temporary database that will store data from original. * * @var string */ - private $newName = ''; + private $temporary = ''; /** * Name of an object where this class is called. * @@ -53,14 +55,21 @@ public function __construct($connection, $callee) throw new \InvalidArgumentException(sprintf('The "%s" database connection does not exist.', $connection)); } - $info = $databases[$connection]['default']; + $db = $databases[$connection]['default']; + + foreach (['port' => 3306, 'host' => '127.0.0.1'] as $option => $default) { + if (empty($db[$option])) { + $db[$option] = $default; + } + } $this->callee = $callee; - $this->originalName = $info['database']; - $this->newName = "tqextension_$this->originalName"; - $this->credentials = sprintf($this->credentials, $info['username'], $info['password']); + $this->source = $db['database']; + $this->temporary = "tqextension_$this->source"; + $this->credentials = sprintf($this->credentials, $db['username'], $db['password'], $db['host'], $db['port']); - $this->cloneDatabase(); + // Drop and create temporary DB and copy source into it. + $this->copy($this->source, $this->temporary); } /** @@ -68,28 +77,57 @@ public function __construct($connection, $callee) */ public function __destruct() { - $this->exec("mysqldump $this->credentials $this->newName | mysql $this->credentials $this->originalName"); + // Drop and create source DB and copy temporary into it. + $this->copy($this->temporary, $this->source); + // Kill temporary DB. + $this->drop($this->temporary); } /** - * Store original database to temporary for future restoring. + * @param string $name + * Name of the database to check. + * + * @return bool + * Checking state. */ - private function cloneDatabase() + public function exist($name) { - $actions = []; + return !empty($this->exec("mysql -e 'show databases' | grep '^$name$'")); + } - // Need to drop temporary database if it was created previously. - if (!empty($this->exec("mysql $this->credentials -e 'show databases' | grep $this->newName"))) { - $actions[] = "drop"; + /** + * @param string $name + * Name of the database to drop. + */ + public function drop($name) + { + if ($this->exist($name)) { + $this->exec("mysql -e '%s database $name;'", __FUNCTION__); } + } - $actions[] = "create"; - - foreach ($actions as $action) { - $this->exec("mysql $this->credentials -e '$action database $this->newName;'"); + /** + * @param string $name + * Name of the database to create. + */ + public function create($name) + { + if (!$this->exist($name)) { + $this->exec("mysql -e '%s database $name;'", __FUNCTION__); } + } - $this->exec("mysqldump $this->credentials $this->originalName | mysql $this->credentials $this->newName"); + /** + * @param string $source + * Source DB name. + * @param string $destination + * Name of the new DB. + */ + public function copy($source, $destination) + { + $this->drop($destination); + $this->create($destination); + $this->exec("mysqldump $source | mysql $destination"); } /** @@ -103,7 +141,12 @@ private function cloneDatabase() */ private function exec($command) { - $command = vsprintf($command, array_slice(func_get_args(), 1)); + // Adding credentials after "mysql" and "mysqldump" commands. + $command = preg_replace( + '/(mysql(?:dump)?)/', + "\\1 $this->credentials", + vsprintf($command, array_slice(func_get_args(), 1)) + ); if (method_exists($this->callee, 'debug')) { call_user_func([$this->callee, 'debug'], [$command]); From 80127da4153d8548e0d34d08866b2ff5a6ac2e11 Mon Sep 17 00:00:00 2001 From: Sergii Bondarenko Date: Sun, 13 Dec 2015 13:51:48 +0200 Subject: [PATCH 3/3] Added documentation for working with copy of database per feature --- docs/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/README.md b/docs/README.md index 2991e53..51ab8af 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,6 +4,18 @@ - To see all, available in your system, steps execute the `bin/behat -dl`. - Some examples can be found [here](examples). +## Work with copy of DB on the fly + +If your steps makes an irreversible changes in database and you want to be sure that works with clean, initial database, then **TqExtension** can give such possibility for you. + +Note, database will be copied before feature started and will be restored after completion. This means that changes, made by scenarios in feature, will be available until next feature started. This gives a possibility to operate changes per scenarios in one feature. + +Also you able to adjust settings for every feature. By default you WILL NOT WORK WITH A COPY OF DB and, to enable this, you MUST ADD `@cloneDB` tag to FEATURE DEFINITION. **Its important**: not to scenario definition, to feature. + +And even you want to work with specific connection (key in `$databases` array from `settings.php`) - it's possible. Just add it name after the `@cloneDB` tag, separated by `:` (e.g. `@cloneDB:default`). + +All this sounds great, but there are drawbacks. **You will lost execution time before and after every feature.** The value of this time depends on your hardware and size of database. So try to not use large databases for testing. + ## Table of contents - [TqContext](#tqcontext)