First commit

This commit is contained in:
Theodotos Andreou 2018-01-14 13:10:16 +00:00
commit c6e2478c40
13918 changed files with 2303184 additions and 0 deletions

View file

@ -0,0 +1,208 @@
<?php
namespace Civi\Test;
use Civi\Test\CiviEnvBuilder\CallbackStep;
use Civi\Test\CiviEnvBuilder\ExtensionsStep;
use Civi\Test\CiviEnvBuilder\SqlFileStep;
use Civi\Test\CiviEnvBuilder\SqlStep;
use Civi\Test\CiviEnvBuilder\StepInterface;
use RuntimeException;
/**
* Class CiviEnvBuilder
*
* Provides a fluent interface for tracking a set of steps.
* By computing and storing a signature for the list steps, we can
* determine whether to (a) do nothing with the list or (b)
* reapply all the steps.
*/
class CiviEnvBuilder {
protected $name;
private $steps = array();
/**
* @var string|NULL
* A digest of the values in $steps.
*/
private $targetSignature = NULL;
public function __construct($name) {
$this->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;
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Civi\Test\CiviEnvBuilder;
class CallbackStep implements StepInterface {
private $callback;
private $sig;
/**
* CallbackStep constructor.
* @param $callback
* @param $sig
*/
public function __construct($callback, $sig = NULL) {
$this->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);
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace Civi\Test\CiviEnvBuilder;
class ExtensionsStep implements StepInterface {
private $action;
private $names;
/**
* ExtensionStep constructor.
* @param string $action
* Ex: 'install', 'uninstall'.
* @param string|array $names
*/
public function __construct($action, $names) {
$this->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;
}
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Civi\Test\CiviEnvBuilder;
class SqlFileStep implements StepInterface {
private $file;
/**
* SqlFileStep constructor.
* @param string $file
*/
public function __construct($file) {
$this->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.");
}
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Civi\Test\CiviEnvBuilder;
class SqlStep implements StepInterface {
private $sql;
/**
* SqlFileStep constructor.
* @param string $sql
*/
public function __construct($sql) {
$this->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}");
}
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace Civi\Test\CiviEnvBuilder;
interface StepInterface {
public function getSig();
public function isValid();
public function run($ctx);
}

View file

@ -0,0 +1,290 @@
<?php
namespace Civi\Test;
/**
* Class CiviTestListener
* @package Civi\Test
*
* CiviTestListener participates in test-execution, looking for test-classes
* which have certain tags. If the tags are found, the listener will perform
* additional setup/teardown logic.
*
* @see EndToEndInterface
* @see HeadlessInterface
* @see HookInterface
*/
class CiviTestListener extends \PHPUnit_Framework_BaseTestListener {
/**
* @var \CRM_Core_TemporaryErrorScope
*/
private $errorScope;
/**
* @var array
* Ex: $cache['Some_Test_Class']['civicrm_foobar'] = 'hook_civicrm_foobar';
* Array(string $testClass => 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();
\CRM_Utils_System::flushCache();
\Civi::reset();
\CRM_Core_Session::singleton()->set('userID', NULL);
$config = \CRM_Core_Config::singleton(TRUE, TRUE); // ugh, performance
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";
}
}
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace Civi\Test;
use RuntimeException;
/**
* Class Data
*/
class Data {
/**
* @return bool
*/
public function populate() {
\Civi\Test::schema()->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;
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Civi\Test;
/**
* Interface EndToEndInterface
* @package Civi\Test
*
* To run your test against a live, CMS-integrated database, flag it with the the
* EndToEndInterface.
*
* Note: The global variable $_CV should be pre-populated with some interesting data:
*
* - $_CV['CMS_URL']
* - $_CV['ADMIN_USER']
* - $_CV['ADMIN_PASS']
* - $_CV['ADMIN_EMAIL']
* - $_CV['DEMO_USER']
* - $_CV['DEMO_PASS']
* - $_CV['DEMO_EMAIL']
*
* Alternatively, if you wish to run a test in a headless environment,
* flag it with HeadlessInterface.
*
* @see HeadlessInterface
*/
interface EndToEndInterface {
}

View file

@ -0,0 +1,40 @@
<?php
namespace Civi\Test;
/**
* Interface HeadlessInterface
* @package Civi\Test
*
* To run your test against a fake, headless database, flag it with the
* HeadlessInterface. CiviTestListener will automatically boot Civi.
*
* Alternatively, if you wish to run a test in a live (CMS-enabled) environment,
* flag it with EndToEndInterface.
*
* You may mix-in additional features for headless tests:
* - HookInterface: Auto-register any functions named "hook_civicrm_foo()".
* - TransactionalInterface: Wrap all work in a transaction, and rollback at the end.
*
* @see EndToEndInterface
* @see HookInterface
* @see TransactionalInterface
*/
interface HeadlessInterface {
/**
* The setupHeadless function runs at the start of each test case, right before
* the headless environment reboots.
*
* It should perform any necessary steps required for putting the database
* in a consistent baseline -- such as loading schema and extensions.
*
* The utility `\Civi\Test::headless()` provides a number of helper functions
* for managing this setup, and it includes optimizations to avoid redundant
* setup work.
*
* @see \Civi\Test
*/
public function setUpHeadless();
}

View file

@ -0,0 +1,30 @@
<?php
namespace Civi\Test;
/**
* Interface HookInterface
* @package Civi\Test
*
* This interface allows you to subscribe to hooks as part of the test.
* Simply create an eponymous hook function (e.g. `hook_civicrm_post()`).
*
* @code
* class MyTest extends \PHPUnit_Framework_TestCase implements \Civi\Test\HookInterface {
* public function hook_civicrm_post($op, $objectName, $objectId, &$objectRef) {
* echo "Running hook_civicrm_post\n";
* }
* }
* @endCode
*
* At time of writing, there are a few limitations in how HookInterface is handled
* by CiviTestListener:
*
* - The test must execute in-process (aka HeadlessInterface; aka CIVICRM_UF==UnitTests).
* End-to-end tests (multi-process tests) are not supported.
* - Early bootstrap hooks (e.g. hook_civicrm_config) are not supported.
*
* @see CiviTestListener
*/
interface HookInterface {
}

View file

@ -0,0 +1,128 @@
<?php
namespace Civi\Test;
use RuntimeException;
/**
* Class Schema
*
* Manage the entire database. This is useful for destroying or loading the schema.
*/
class Schema {
/**
* @param string $type
* 'BASE TABLE' or 'VIEW'.
* @return array
*/
public function getTables($type) {
$pdo = \Civi\Test::pdo();
// only consider real tables and not views
$query = sprintf(
"SELECT table_name FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = %s AND TABLE_TYPE = %s",
$pdo->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 Schema
*/
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;
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Civi\Test;
/**
* Interface HeadlessInterface
* @package Civi\Test
*
* Mark a test with TransactionalInterface to instruct CiviTestListener to wrap
* each test in a transaction (and rollback).
*
* Note: At time of writing, CiviTestListener only supports using TransactionalInterface if
* the test is in-process and runs with CIVICRM_UF==UnitTests.
*
* For end-to-end testing, it is expected that the CMS will not participate in the transaction,
* so the transaction mechanism will not work.
*
* @see HeadlessInterface
*/
interface TransactionalInterface {
}