From c82960110c799dbb364de750ee174d7fbc496874 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Wed, 4 Jan 2017 20:21:52 -0800 Subject: [PATCH] Civi\Test - Backport helper test classes from 4.7. Note: These classes are not actually referenced by anything in core v4.6; however, we provide so that extensions can use them. --- Civi/Test.php | 171 ++++++++++++ Civi/Test/CiviEnvBuilder.php | 208 ++++++++++++++ Civi/Test/CiviEnvBuilder/CallbackStep.php | 29 ++ Civi/Test/CiviEnvBuilder/ExtensionsStep.php | 51 ++++ Civi/Test/CiviEnvBuilder/SqlFileStep.php | 35 +++ Civi/Test/CiviEnvBuilder/SqlStep.php | 30 ++ Civi/Test/CiviEnvBuilder/StepInterface.php | 11 + Civi/Test/CiviTestListener.php | 290 ++++++++++++++++++++ Civi/Test/Data.php | 52 ++++ Civi/Test/EndToEndInterface.php | 28 ++ Civi/Test/HeadlessInterface.php | 40 +++ Civi/Test/HookInterface.php | 30 ++ Civi/Test/Schema.php | 128 +++++++++ Civi/Test/TransactionalInterface.php | 22 ++ 14 files changed, 1125 insertions(+) create mode 100644 Civi/Test.php create mode 100644 Civi/Test/CiviEnvBuilder.php create mode 100644 Civi/Test/CiviEnvBuilder/CallbackStep.php create mode 100644 Civi/Test/CiviEnvBuilder/ExtensionsStep.php create mode 100644 Civi/Test/CiviEnvBuilder/SqlFileStep.php create mode 100644 Civi/Test/CiviEnvBuilder/SqlStep.php create mode 100644 Civi/Test/CiviEnvBuilder/StepInterface.php create mode 100644 Civi/Test/CiviTestListener.php create mode 100644 Civi/Test/Data.php create mode 100644 Civi/Test/EndToEndInterface.php create mode 100644 Civi/Test/HeadlessInterface.php create mode 100644 Civi/Test/HookInterface.php create mode 100644 Civi/Test/Schema.php create mode 100644 Civi/Test/TransactionalInterface.php diff --git a/Civi/Test.php b/Civi/Test.php new file mode 100644 index 000000000000..e70f0f40262f --- /dev/null +++ b/Civi/Test.php @@ -0,0 +1,171 @@ + TRUE) + ); + } + catch (PDOException $e) { + echo "Can't connect to MySQL server:" . PHP_EOL . $e->getMessage() . PHP_EOL; + exit(1); + } + } + return self::$singletons['pdo']; + } + + /** + * Create a builder for the headless environment. + * + * @return \Civi\Test\CiviEnvBuilder + * + * @code + * \Civi\Test::headless()->apply(); + * \Civi\Test::headless()->sqlFile('ex.sql')->apply(); + * @endCode + */ + public static function headless() { + $civiRoot = dirname(__DIR__); + $builder = new \Civi\Test\CiviEnvBuilder('CiviEnvBuilder'); + $builder + ->callback(function ($ctx) { + if (CIVICRM_UF !== 'UnitTests') { + throw new \RuntimeException("\\Civi\\Test::headless() requires CIVICRM_UF=UnitTests"); + } + $dbName = \Civi\Test::dsn('database'); + echo "Installing {$dbName} schema\n"; + \Civi\Test::schema()->dropAll(); + }, 'headless-drop') + ->sqlFile($civiRoot . "/sql/civicrm.mysql") + ->sql("DELETE FROM civicrm_extension") + ->callback(function ($ctx) { + \Civi\Test::data()->populate(); + }, 'populate'); + return $builder; + } + + /** + * Create a builder for end-to-end testing on the live environment. + * + * @return \Civi\Test\CiviEnvBuilder + * + * @code + * \Civi\Test::e2e()->apply(); + * \Civi\Test::e2e()->install('foo.bar')->apply(); + * @endCode + */ + public static function e2e() { + $builder = new \Civi\Test\CiviEnvBuilder('CiviEnvBuilder'); + $builder + ->callback(function ($ctx) { + if (CIVICRM_UF === 'UnitTests') { + throw new \RuntimeException("\\Civi\\Test::e2e() requires a real CMS. Found CIVICRM_UF=UnitTests."); + } + }, 'e2e-check'); + return $builder; + } + + /** + * @return \Civi\Test\Schema + */ + public static function schema() { + if (!isset(self::$singletons['schema'])) { + self::$singletons['schema'] = new \Civi\Test\Schema(); + } + return self::$singletons['schema']; + } + + + /** + * @return \Civi\Test\Data + */ + public static function data() { + if (!isset(self::$singletons['data'])) { + self::$singletons['data'] = new \Civi\Test\Data('CiviTesterData'); + } + return self::$singletons['data']; + } + + /** + * Prepare and execute a batch of SQL statements. + * + * @param string $query + * @return bool + */ + public static function execute($query) { + $pdo = \Civi\Test::pdo(); + + $string = preg_replace("/^#[^\n]*$/m", "\n", $query); + $string = preg_replace("/^(--[^-]).*/m", "\n", $string); + + $queries = preg_split('/;\s*$/m', $string); + foreach ($queries as $query) { + $query = trim($query); + if (!empty($query)) { + $result = $pdo->query($query); + if ($pdo->errorCode() == 0) { + continue; + } + else { + var_dump($result); + var_dump($pdo->errorInfo()); + // die( "Cannot execute $query: " . $pdo->errorInfo() ); + } + } + } + return TRUE; + } + +} diff --git a/Civi/Test/CiviEnvBuilder.php b/Civi/Test/CiviEnvBuilder.php new file mode 100644 index 000000000000..003e14545dbb --- /dev/null +++ b/Civi/Test/CiviEnvBuilder.php @@ -0,0 +1,208 @@ +name = $name; + } + + public function addStep(StepInterface $step) { + $this->targetSignature = NULL; + $this->steps[] = $step; + return $this; + } + + public function callback($callback, $signature = NULL) { + return $this->addStep(new CallbackStep($callback, $signature)); + } + + public function sql($sql) { + return $this->addStep(new SqlStep($sql)); + } + + public function sqlFile($file) { + return $this->addStep(new SqlFileStep($file)); + } + + /** + * Require that an extension be installed. + * + * @param string|array $names + * One or more extension names. You may use a wildcard '*'. + * @return CiviEnvBuilder + */ + public function install($names) { + return $this->addStep(new ExtensionsStep('install', $names)); + } + + /** + * Require an extension be installed (identified by its directory). + * + * @param string $dir + * The current test directory. We'll search for info.xml to + * see what this extension is. + * @return CiviEnvBuilder + * @throws \CRM_Extension_Exception_ParseException + */ + public function installMe($dir) { + return $this->addStep(new ExtensionsStep('install', $this->whoAmI($dir))); + } + + /** + * Require an extension be uninstalled. + * + * @param string|array $names + * One or more extension names. You may use a wildcard '*'. + * @return CiviEnvBuilder + */ + public function uninstall($names) { + return $this->addStep(new ExtensionsStep('uninstall', $names)); + } + + /** + * Require an extension be uninstalled (identified by its directory). + * + * @param string $dir + * The current test directory. We'll search for info.xml to + * see what this extension is. + * @return CiviEnvBuilder + * @throws \CRM_Extension_Exception_ParseException + */ + public function uninstallMe($dir) { + return $this->addStep(new ExtensionsStep('uninstall', $this->whoAmI($dir))); + } + + protected function assertValid() { + foreach ($this->steps as $step) { + if (!$step->isValid()) { + throw new RuntimeException("Found invalid step: " . var_dump($step, 1)); + } + } + } + + /** + * @return string + */ + protected function getTargetSignature() { + if ($this->targetSignature === NULL) { + $buf = ''; + foreach ($this->steps as $step) { + $buf .= $step->getSig(); + } + $this->targetSignature = md5($buf); + } + + return $this->targetSignature; + } + + /** + * @return string + */ + protected function getSavedSignature() { + $liveSchemaRev = NULL; + $pdo = \Civi\Test::pdo(); + $pdoStmt = $pdo->query(sprintf( + "SELECT rev FROM %s.civitest_revs WHERE name = %s", + \Civi\Test::dsn('database'), + $pdo->quote($this->name) + )); + foreach ($pdoStmt as $row) { + $liveSchemaRev = $row['rev']; + } + return $liveSchemaRev; + } + + /** + * @param $newSignature + */ + protected function setSavedSignature($newSignature) { + $pdo = \Civi\Test::pdo(); + $query = sprintf( + 'INSERT INTO %s.civitest_revs (name,rev) VALUES (%s,%s) ' + . 'ON DUPLICATE KEY UPDATE rev = %s;', + \Civi\Test::dsn('database'), + $pdo->quote($this->name), + $pdo->quote($newSignature), + $pdo->quote($newSignature) + ); + + if (\Civi\Test::execute($query) === FALSE) { + throw new RuntimeException("Failed to flag schema version: $query"); + } + } + + /** + * Determine if there's been a change in the preferred configuration. + * If the preferred-configuration matches the last test, keep it. Otherwise, + * destroy and recreate. + * + * @param bool $force + * Forcibly execute the build, even if the configuration hasn't changed. + * This will slow-down the tests, but it may be appropriate for some very sloppy + * tests. + * @return CiviEnvBuilder + */ + public function apply($force = FALSE) { + $dbName = \Civi\Test::dsn('database'); + $query = "USE {$dbName};" + . "CREATE TABLE IF NOT EXISTS civitest_revs (name VARCHAR(64) PRIMARY KEY, rev VARCHAR(64));"; + + if (\Civi\Test::execute($query) === FALSE) { + throw new \RuntimeException("Failed to flag schema version: $query"); + } + + $this->assertValid(); + + if (!$force && $this->getSavedSignature() === $this->getTargetSignature()) { + return $this; + } + foreach ($this->steps as $step) { + $step->run($this); + } + $this->setSavedSignature($this->getTargetSignature()); + return $this; + } + + /** + * @param $dir + * @return null + * @throws \CRM_Extension_Exception_ParseException + */ + protected function whoAmI($dir) { + while ($dir && dirname($dir) !== $dir && !file_exists("$dir/info.xml")) { + $dir = dirname($dir); + } + if (file_exists("$dir/info.xml")) { + $info = \CRM_Extension_Info::loadFromFile("$dir/info.xml"); + $name = $info->key; + return $name; + } + return $name; + } + +} diff --git a/Civi/Test/CiviEnvBuilder/CallbackStep.php b/Civi/Test/CiviEnvBuilder/CallbackStep.php new file mode 100644 index 000000000000..6d0c9f055c3a --- /dev/null +++ b/Civi/Test/CiviEnvBuilder/CallbackStep.php @@ -0,0 +1,29 @@ +callback = $callback; + $this->sig = $sig === NULL ? md5(var_export($callback, 1)) : $sig; + } + + public function getSig() { + return $this->sig; + } + + public function isValid() { + return is_callable($this->callback); + } + + public function run($ctx) { + call_user_func($this->callback, $ctx); + } + +} diff --git a/Civi/Test/CiviEnvBuilder/ExtensionsStep.php b/Civi/Test/CiviEnvBuilder/ExtensionsStep.php new file mode 100644 index 000000000000..9b9440a23534 --- /dev/null +++ b/Civi/Test/CiviEnvBuilder/ExtensionsStep.php @@ -0,0 +1,51 @@ +action = $action; + $this->names = (array) $names; + } + + public function getSig() { + return 'ext:' . implode(',', $this->names); + } + + public function isValid() { + if (!in_array($this->action, array('install', 'uninstall'))) { + return FALSE; + } + foreach ($this->names as $name) { + if (!is_string($name)) { + return FALSE; + } + } + return TRUE; + } + + public function run($ctx) { + $allKeys = \CRM_Extension_System::singleton()->getFullContainer()->getKeys(); + $names = \CRM_Utils_String::filterByWildcards($this->names, $allKeys, TRUE); + + $manager = \CRM_Extension_System::singleton()->getManager(); + switch ($this->action) { + case 'install': + $manager->install($names); + break; + + case 'uninstall': + $manager->disable($names); + $manager->uninstall($names); + break; + } + } + +} diff --git a/Civi/Test/CiviEnvBuilder/SqlFileStep.php b/Civi/Test/CiviEnvBuilder/SqlFileStep.php new file mode 100644 index 000000000000..c901931721a2 --- /dev/null +++ b/Civi/Test/CiviEnvBuilder/SqlFileStep.php @@ -0,0 +1,35 @@ +file = $file; + } + + + public function getSig() { + return implode(' ', array( + $this->file, + filemtime($this->file), + filectime($this->file), + )); + } + + public function isValid() { + return is_file($this->file) && is_readable($this->file); + } + + public function run($ctx) { + /** @var $ctx \CiviEnvBuilder */ + if (\Civi\Test::execute(@file_get_contents($this->file)) === FALSE) { + throw new \RuntimeException("Cannot load {$this->file}. Aborting."); + } + } + +} diff --git a/Civi/Test/CiviEnvBuilder/SqlStep.php b/Civi/Test/CiviEnvBuilder/SqlStep.php new file mode 100644 index 000000000000..7a2736b019c8 --- /dev/null +++ b/Civi/Test/CiviEnvBuilder/SqlStep.php @@ -0,0 +1,30 @@ +sql = $sql; + } + + + public function getSig() { + return md5($this->sql); + } + + public function isValid() { + return TRUE; + } + + public function run($ctx) { + /** @var $ctx \CiviEnvBuilder */ + if (\Civi\Test::execute($this->sql) === FALSE) { + throw new \RuntimeException("Cannot execute: {$this->sql}"); + } + } + +} diff --git a/Civi/Test/CiviEnvBuilder/StepInterface.php b/Civi/Test/CiviEnvBuilder/StepInterface.php new file mode 100644 index 000000000000..3d6dc95cc1e3 --- /dev/null +++ b/Civi/Test/CiviEnvBuilder/StepInterface.php @@ -0,0 +1,11 @@ + Array(string $hookName => string $methodName)). + */ + private $cache = array(); + + /** + * @var \CRM_Core_Transaction|NULL + */ + private $tx; + + public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) { + $byInterface = $this->indexTestsByInterface($suite->tests()); + $this->validateGroups($byInterface); + $this->autoboot($byInterface); + } + + public function endTestSuite(\PHPUnit_Framework_TestSuite $suite) { + $this->cache = array(); + } + + public function startTest(\PHPUnit_Framework_Test $test) { + if ($this->isCiviTest($test)) { + error_reporting(E_ALL); + $this->errorScope = \CRM_Core_TemporaryErrorScope::useException(); + } + + if ($test instanceof HeadlessInterface) { + $this->bootHeadless($test); + } + + if ($test instanceof HookInterface) { + // Note: bootHeadless() indirectly resets any hooks, which means that hook_civicrm_config + // is unsubscribable. However, after bootHeadless(), we're free to subscribe to hooks again. + $this->registerHooks($test); + } + + if ($test instanceof TransactionalInterface) { + $this->tx = new \CRM_Core_Transaction(TRUE); + $this->tx->rollback(); + } + else { + $this->tx = NULL; + } + } + + public function endTest(\PHPUnit_Framework_Test $test, $time) { + if ($test instanceof TransactionalInterface) { + $this->tx->rollback()->commit(); + $this->tx = NULL; + } + if ($test instanceof HookInterface) { + \CRM_Utils_Hook::singleton()->reset(); + } + if ($this->isCiviTest($test)) { + error_reporting(E_ALL & ~E_NOTICE); + $this->errorScope = NULL; + } + } + + /** + * @param HeadlessInterface|\PHPUnit_Framework_Test $test + */ + protected function bootHeadless($test) { + if (CIVICRM_UF !== 'UnitTests') { + throw new \RuntimeException('HeadlessInterface requires CIVICRM_UF=UnitTests'); + } + + // Hrm, this seems wrong. Shouldn't we be resetting the entire session? + $session = \CRM_Core_Session::singleton(); + $session->set('userID', NULL); + + $test->setUpHeadless(); + + $config = \CRM_Core_Config::singleton(TRUE, TRUE); // ugh, performance + \CRM_Utils_System::flushCache(); + \Civi::reset(); + \CRM_Core_Session::singleton()->set('userID', NULL); + + if (property_exists($config->userPermissionClass, 'permissions')) { + $config->userPermissionClass->permissions = NULL; + } + } + + /** + * @param \Civi\Test\HookInterface $test + * @return array + * Array(string $hookName => string $methodName)). + */ + protected function findTestHooks(HookInterface $test) { + $class = get_class($test); + if (!isset($this->cache[$class])) { + $funcs = array(); + foreach (get_class_methods($class) as $func) { + if (preg_match('/^hook_/', $func)) { + $funcs[substr($func, 5)] = $func; + } + } + $this->cache[$class] = $funcs; + } + return $this->cache[$class]; + } + + /** + * @param \PHPUnit_Framework_Test $test + * @return bool + */ + protected function isCiviTest(\PHPUnit_Framework_Test $test) { + return $test instanceof HookInterface || $test instanceof HeadlessInterface; + } + + /** + * Find any hook functions in $test and register them. + * + * @param \Civi\Test\HookInterface $test + */ + protected function registerHooks(HookInterface $test) { + if (CIVICRM_UF !== 'UnitTests') { + // This is not ideal -- it's just a side-effect of how hooks and E2E tests work. + // We can temporarily subscribe to hooks in-process, but for other processes, it gets messy. + throw new \RuntimeException('CiviHookTestInterface requires CIVICRM_UF=UnitTests'); + } + \CRM_Utils_Hook::singleton()->reset(); + /** @var \CRM_Utils_Hook_UnitTests $hooks */ + $hooks = \CRM_Utils_Hook::singleton(); + foreach ($this->findTestHooks($test) as $hook => $func) { + $hooks->setHook($hook, array($test, $func)); + } + } + + /** + * The first time we come across HeadlessInterface or EndToEndInterface, we'll + * try to autoboot. + * + * Once the system is booted, there's nothing we can do -- we're stuck with that + * environment. (Thank you, prolific define()s!) If there's a conflict between a + * test-class and the active boot-level, then we'll have to bail. + * + * @param array $byInterface + * List of test classes, keyed by major interface (HeadlessInterface vs EndToEndInterface). + */ + protected function autoboot($byInterface) { + if (defined('CIVICRM_UF')) { + // OK, nothing we can do. System has booted already. + } + elseif (!empty($byInterface['HeadlessInterface'])) { + putenv('CIVICRM_UF=UnitTests'); + eval($this->cv('php:boot --level=full', 'phpcode')); + } + elseif (!empty($byInterface['EndToEndInterface'])) { + putenv('CIVICRM_UF='); + eval($this->cv('php:boot --level=full', 'phpcode')); + } + + $blurb = "Tip: Run the headless tests and end-to-end tests separately, e.g.\n" + . " $ phpunit4 --group headless\n" + . " $ phpunit4 --group e2e \n"; + + if (!empty($byInterface['HeadlessInterface']) && CIVICRM_UF !== 'UnitTests') { + $testNames = implode(', ', array_keys($byInterface['HeadlessInterface'])); + throw new \RuntimeException("Suite includes headless tests ($testNames) which require CIVICRM_UF=UnitTests.\n\n$blurb"); + } + if (!empty($byInterface['EndToEndInterface']) && CIVICRM_UF === 'UnitTests') { + $testNames = implode(', ', array_keys($byInterface['EndToEndInterface'])); + throw new \RuntimeException("Suite includes end-to-end tests ($testNames) which do not support CIVICRM_UF=UnitTests.\n\n$blurb"); + } + } + + /** + * Call the "cv" command. + * + * This duplicates the standalone `cv()` wrapper that is recommended in bootstrap.php. + * This duplication is necessary because `cv()` is optional, and downstream implementers + * may alter, rename, or omit the wrapper, and (by virtue of its role in bootstrap) there + * it is impossible to define it centrally. + * + * @param string $cmd + * The rest of the command to send. + * @param string $decode + * Ex: 'json' or 'phpcode'. + * @return string + * Response output (if the command executed normally). + * @throws \RuntimeException + * If the command terminates abnormally. + */ + protected function cv($cmd, $decode = 'json') { + $cmd = 'cv ' . $cmd; + $descriptorSpec = array(0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => STDERR); + $oldOutput = getenv('CV_OUTPUT'); + putenv("CV_OUTPUT=json"); + $process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__); + putenv("CV_OUTPUT=$oldOutput"); + fclose($pipes[0]); + $result = stream_get_contents($pipes[1]); + fclose($pipes[1]); + if (proc_close($process) !== 0) { + throw new \RuntimeException("Command failed ($cmd):\n$result"); + } + switch ($decode) { + case 'raw': + return $result; + + case 'phpcode': + // If the last output is /*PHPCODE*/, then we managed to complete execution. + if (substr(trim($result), 0, 12) !== "/*BEGINPHP*/" || substr(trim($result), -10) !== "/*ENDPHP*/") { + throw new \RuntimeException("Command failed ($cmd):\n$result"); + } + return $result; + + case 'json': + return json_decode($result, 1); + + default: + throw new \RuntimeException("Bad decoder format ($decode)"); + } + } + + /** + * @param $tests + * @return array + */ + protected function indexTestsByInterface($tests) { + $byInterface = array('HeadlessInterface' => array(), 'EndToEndInterface' => array()); + foreach ($tests as $test) { + /** @var \PHPUnit_Framework_Test $test */ + if ($test instanceof HeadlessInterface) { + $byInterface['HeadlessInterface'][get_class($test)] = 1; + } + if ($test instanceof EndToEndInterface) { + $byInterface['EndToEndInterface'][get_class($test)] = 1; + } + } + return $byInterface; + } + + /** + * Ensure that any tests have sensible groups, e.g. + * + * `HeadlessInterface` ==> `group headless` + * `EndToEndInterface` ==> `group e2e` + * + * @param array $byInterface + */ + protected function validateGroups($byInterface) { + foreach ($byInterface['HeadlessInterface'] as $className => $nonce) { + $clazz = new \ReflectionClass($className); + $docComment = str_replace("\r\n", "\n", $clazz->getDocComment()); + if (strpos($docComment, "@group headless\n") === FALSE) { + echo "WARNING: Class $className implements HeadlessInterface. It should declare \"@group headless\".\n"; + } + if (strpos($docComment, "@group e2e\n") !== FALSE) { + echo "WARNING: Class $className implements HeadlessInterface. It should not declare \"@group e2e\".\n"; + } + } + foreach ($byInterface['EndToEndInterface'] as $className => $nonce) { + $clazz = new \ReflectionClass($className); + $docComment = str_replace("\r\n", "\n", $clazz->getDocComment()); + if (strpos($docComment, "@group e2e\n") === FALSE) { + echo "WARNING: Class $className implements EndToEndInterface. It should declare \"@group e2e\".\n"; + } + if (strpos($docComment, "@group headless\n") !== FALSE) { + echo "WARNING: Class $className implements EndToEndInterface. It should not declare \"@group headless\".\n"; + } + } + } + +} diff --git a/Civi/Test/Data.php b/Civi/Test/Data.php new file mode 100644 index 000000000000..d1e62e5903fb --- /dev/null +++ b/Civi/Test/Data.php @@ -0,0 +1,52 @@ +truncateAll(); + + \Civi\Test::schema()->setStrict(FALSE); + $sqlDir = dirname(dirname(__DIR__)) . "/sql"; + + $query2 = file_get_contents("$sqlDir/civicrm_data.mysql"); + $query3 = file_get_contents("$sqlDir/test_data.mysql"); + $query4 = file_get_contents("$sqlDir/test_data_second_domain.mysql"); + if (\Civi\Test::execute($query2) === FALSE) { + throw new RuntimeException("Cannot load civicrm_data.mysql. Aborting."); + } + if (\Civi\Test::execute($query3) === FALSE) { + throw new RuntimeException("Cannot load test_data.mysql. Aborting."); + } + if (\Civi\Test::execute($query4) === FALSE) { + throw new RuntimeException("Cannot load test_data.mysql. Aborting."); + } + + unset($query, $query2, $query3); + + \Civi\Test::schema()->setStrict(TRUE); + + // Rebuild triggers + civicrm_api('system', 'flush', array('version' => 3, 'triggers' => 1)); + + \CRM_Core_BAO_ConfigSetting::setEnabledComponents(array( + 'CiviEvent', + 'CiviContribute', + 'CiviMember', + 'CiviMail', + 'CiviReport', + 'CiviPledge', + )); + + return TRUE; + } + +} diff --git a/Civi/Test/EndToEndInterface.php b/Civi/Test/EndToEndInterface.php new file mode 100644 index 000000000000..9170b5c2278f --- /dev/null +++ b/Civi/Test/EndToEndInterface.php @@ -0,0 +1,28 @@ +quote(\Civi\Test::dsn('database')), + $pdo->quote($type) + ); + $tables = $pdo->query($query); + $result = array(); + foreach ($tables as $table) { + $result[] = $table['table_name']; + } + return $result; + } + + public function setStrict($checks) { + $dbName = \Civi\Test::dsn('database'); + if ($checks) { + $queries = array( + "USE {$dbName};", + "SET global innodb_flush_log_at_trx_commit = 1;", + "SET SQL_MODE='STRICT_ALL_TABLES';", + "SET foreign_key_checks = 1;", + ); + } + else { + $queries = array( + "USE {$dbName};", + "SET foreign_key_checks = 0", + "SET SQL_MODE='STRICT_ALL_TABLES';", + "SET global innodb_flush_log_at_trx_commit = 2;", + ); + } + foreach ($queries as $query) { + if (\Civi\Test::execute($query) === FALSE) { + throw new RuntimeException("Query failed: $query"); + } + } + return $this; + } + + public function dropAll() { + $queries = array(); + foreach ($this->getTables('VIEW') as $table) { + if (preg_match('/^(civicrm_|log_)/', $table)) { + $queries[] = "DROP VIEW $table"; + } + } + + foreach ($this->getTables('BASE TABLE') as $table) { + if (preg_match('/^(civicrm_|log_)/', $table)) { + $queries[] = "DROP TABLE $table"; + } + } + + $this->setStrict(FALSE); + foreach ($queries as $query) { + if (\Civi\Test::execute($query) === FALSE) { + throw new RuntimeException("dropSchema: Query failed: $query"); + } + } + $this->setStrict(TRUE); + + return $this; + } + + /** + * @return array + */ + public function truncateAll() { + $tables = \Civi\Test::schema()->getTables('BASE TABLE'); + + $truncates = array(); + $drops = array(); + foreach ($tables as $table) { + // skip log tables + if (substr($table, 0, 4) == 'log_') { + continue; + } + + // don't change list of installed extensions + if ($table == 'civicrm_extension') { + continue; + } + + if (substr($table, 0, 14) == 'civicrm_value_') { + $drops[] = 'DROP TABLE ' . $table . ';'; + } + elseif (substr($table, 0, 9) == 'civitest_') { + // ignore + } + else { + $truncates[] = 'TRUNCATE ' . $table . ';'; + } + } + + \Civi\Test::schema()->setStrict(FALSE); + $queries = array_merge($truncates, $drops); + foreach ($queries as $query) { + if (\Civi\Test::execute($query) === FALSE) { + throw new RuntimeException("Query failed: $query"); + } + } + \Civi\Test::schema()->setStrict(TRUE); + + return $this; + } + +} diff --git a/Civi/Test/TransactionalInterface.php b/Civi/Test/TransactionalInterface.php new file mode 100644 index 000000000000..31d13d02ac5b --- /dev/null +++ b/Civi/Test/TransactionalInterface.php @@ -0,0 +1,22 @@ +