+++ /dev/null
-<?php
-
-class CiviTestDB {
-
- /**
- * @var CiviTestPdoUtils
- */
- protected $pdoUtils;
-
- /**
- * CiviTestDB constructor.
- * @param CiviTestPdoUtils $pdoUtils
- */
- public function __construct($pdoUtils) {
- $this->pdoUtils = $pdoUtils;
- }
-
- /**
- * Autocreate the schema.
- *
- * Check civitest_schema_rev. If it's out-of-date, drop the old schema and load the
- * new one.
- *
- * @return mixed
- */
- public function updateSchema() {
- $schemaFile = dirname(dirname(dirname(dirname(__FILE__)))) . "/sql/civicrm.mysql";
- if (!is_file($schemaFile)) {
- return $this->fatal("Failed to find schema: $schemaFile");
- }
-
- // $schemaFileRev = md5(@file_get_contents($schemaFile));
- $schemaFileRev = filemtime($schemaFile) . ' ' . filectime($schemaFile);
-
- $tables = $this->getCurrentTables('BASE TABLE');
- $liveSchemaRev = '';
- if (in_array('civitest_schema_rev', $tables)) {
- $pdoStmt = $this->pdoUtils->pdo->query("SELECT schema_rev FROM {$this->pdoUtils->dbName}.civitest_schema_rev");
- foreach ($pdoStmt as $row) {
- $liveSchemaRev = $row['schema_rev'];
- }
- }
-
- if ($liveSchemaRev === $schemaFileRev) {
- return;
- }
-
- echo "Installing {$this->pdoUtils->dbName} schema\n";
- $this->dropSchema();
- if ($this->pdoUtils->do_query(@file_get_contents($schemaFile)) === FALSE) {
- return $this->fatal("Cannot load $schemaFile. Aborting.");
- }
- $query = sprintf(
- "USE {$this->pdoUtils->dbName};"
- . "DROP TABLE IF EXISTS civitest_schema_rev;"
- . "CREATE TABLE civitest_schema_rev (schema_rev VARCHAR(64));"
- . "INSERT INTO civitest_schema_rev (schema_rev) VALUES (%s);",
- $this->pdoUtils->pdo->quote($schemaFileRev)
- );
-
- if ($this->pdoUtils->do_query($query) === FALSE) {
- return $this->fatal("Failed to flag schema version: $query");
- }
- }
-
- public function dropSchema() {
- $queries = array(
- "USE {$this->pdoUtils->dbName};",
- "SET foreign_key_checks = 0",
- // SQL mode needs to be strict, that's our standard
- "SET SQL_MODE='STRICT_ALL_TABLES';",
- "SET global innodb_flush_log_at_trx_commit = 2;",
- );
-
- foreach ($this->getCurrentTables('VIEW') as $table) {
- if (preg_match('/^(civicrm_|log_)/', $table)) {
- $queries[] = "DROP VIEW $table";
- }
- }
-
- foreach ($this->getCurrentTables('BASE TABLE') as $table) {
- if (preg_match('/^(civicrm_|log_)/', $table)) {
- $queries[] = "DROP TABLE $table";
- }
- }
-
- $queries[] = "set global innodb_flush_log_at_trx_commit = 1;";
- $queries[] = "SET foreign_key_checks = 1";
-
- foreach ($queries as $query) {
- if ($this->pdoUtils->do_query($query) === FALSE) {
- return $this->fatal("dropSchema: Query failed: $query");
- }
- }
- }
-
- /**
- * @return bool
- */
- public function populate() {
- $pdoUtils = $this->pdoUtils;
- $tables = $this->getCurrentTables('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 . ';';
- }
- }
-
- $dbName = $pdoUtils->dbName;
- $queries = array(
- "USE {$dbName};",
- "SET foreign_key_checks = 0",
- // SQL mode needs to be strict, that's our standard
- "SET SQL_MODE='STRICT_ALL_TABLES';",
- "SET global innodb_flush_log_at_trx_commit = 2;",
- );
- $queries = array_merge($queries, $truncates);
- $queries = array_merge($queries, $drops);
- foreach ($queries as $query) {
- if ($pdoUtils->do_query($query) === FALSE) {
- return $this->fatal("Query failed: $query");
- }
- }
-
- // initialize test database
- $sql_file2 = dirname(dirname(dirname(dirname(__FILE__)))) . "/sql/civicrm_data.mysql";
- $sql_file3 = dirname(dirname(dirname(dirname(__FILE__)))) . "/sql/test_data.mysql";
- $sql_file4 = dirname(dirname(dirname(dirname(__FILE__)))) . "/sql/test_data_second_domain.mysql";
-
- $query2 = file_get_contents($sql_file2);
- $query3 = file_get_contents($sql_file3);
- $query4 = file_get_contents($sql_file4);
- if ($pdoUtils->do_query($query2) === FALSE) {
- return $this->fatal("Cannot load civicrm_data.mysql. Aborting.");
- }
- if ($pdoUtils->do_query($query3) === FALSE) {
- return $this->fatal("Cannot load test_data.mysql. Aborting.");
- }
- if ($pdoUtils->do_query($query4) === FALSE) {
- return $this->fatal("Cannot load test_data.mysql. Aborting.");
- }
-
- // done with all the loading, get transactions back
- if ($pdoUtils->do_query("set global innodb_flush_log_at_trx_commit = 1;") === FALSE) {
- return $this->fatal("Cannot set global? Huh?");
- }
-
- if ($pdoUtils->do_query("SET foreign_key_checks = 1") === FALSE) {
- return $this->fatal("Cannot get foreign keys back? Huh?");
- }
-
- unset($query, $query2, $query3);
-
- // 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;
- }
-
- /**
- * @param $message
- * @return mixed
- */
- protected function fatal($message) {
- echo "$message\n";
- exit(1);
- }
-
- /**
- * @param string $type
- * 'BASE TABLE' or 'VIEW'.
- * @return array
- */
- protected function getCurrentTables($type) {
- $pdo = $this->pdoUtils->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($this->pdoUtils->dbName),
- $pdo->quote($type)
- );
- $tables = $pdo->query($query);
- $result = array();
- foreach ($tables as $table) {
- $result[] = $table['table_name'];
- }
- return $result;
- }
-
-}
+++ /dev/null
-<?php
-// vim: set si ai expandtab tabstop=4 shiftwidth=4 softtabstop=4:
-
-/**
- * File for the Utils class
- *
- * (PHP 5)
- *
- * @author Walt Haas <walt@dharmatech.org> (801) 534-1262
- * @copyright Copyright CiviCRM LLC (C) 2009
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html
- * GNU Affero General Public License version 3
- * @version $Id: Utils.php 40328 2012-05-11 23:06:13Z allen $
- * @package CiviCRM
- *
- * This file is part of CiviCRM
- *
- * CiviCRM is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation; either version 3 of
- * the License, or (at your option) any later version.
- *
- * CiviCRM is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this program. If not, see
- * <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Utility functions
- * @package CiviCRM
- */
-class CiviTestPdoUtils {
-
- /**
- * PDO for the database.
- * @var PDO
- */
- public $pdo;
-
- public $dbName;
-
- /**
- * Construct an object for this database.
- */
- public function __construct($dsn) {
- require_once "DB.php";
- $dsninfo = DB::parseDSN($dsn);
- $host = $dsninfo['hostspec'];
- $port = @$dsninfo['port'];
- $user = $dsninfo['username'];
- $pass = $dsninfo['password'];
- $this->dbName = $dsninfo['database'];
-
- try {
- $this->pdo = new PDO("mysql:host={$host}" . ($port ? ";port=$port" : ""),
- $user, $pass,
- array(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE)
- );
- }
- catch (PDOException$e) {
- echo "Can't connect to MySQL server:" . PHP_EOL . $e->getMessage() . PHP_EOL;
- exit(1);
- }
- }
-
- /**
- * Prepare and execute a query.
- *
- * If the query fails, output a diagnostic message
- * @param string $query
- * Query to run
- * @return bool
- */
- public function do_query($query) {
- // echo "do_query($query)\n";
- // $stmt = $this->pdo->query( $query, PDO::FETCH_ASSOC );
- // echo "PDO returned";
- // var_dump($stmt);
- $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 = $this->pdo->query($query);
- if ($this->pdo->errorCode() == 0) {
- continue;
- }
- else {
- var_dump($result);
- var_dump($this->pdo->errorInfo());
- // die( "Cannot execute $query: " . $this->pdo->errorInfo() );
- }
- }
- }
-
- /*******
- * if ( $this->pdo->errorCode() == 0 ) {
- * //echo "returning the PDOStmt\n";
- * return $stmt;
- * }
- *
- * // operation failed, so output description of where and why
- * $errorInfo = $this->pdo->errorInfo();
- * echo "Oops, can't do query:\n {$query}\n in "
- * . basename( __FILE__) . " line " . __LINE__.":\n "
- * . $errorInfo[0] . ": " . $errorInfo[2] . "\n Call stack:\n";
- * $backtrace = debug_backtrace();
- * $dir_name = dirname( __FILE__ );
- * $cwd_len = strlen( $dir_name ) + 1;
- * foreach ($backtrace as $frame ) {
- * echo " ";
- * if ( array_key_exists( 'class', $frame ) ) {
- * echo " class {$frame['class']}";
- * if ( array_key_exists( 'function', $frame ) ) {
- * echo " method {$frame['function']}";
- * }
- * }
- * else {
- * if ( array_key_exists( 'function', $frame ) ) {
- * echo " function {$frame['function']}";
- * }
- * }
- * if ( array_key_exists( 'file', $frame ) ) {
- * echo " file ". substr( $frame['file'], $cwd_len );
- * }
- * if ( array_key_exists( 'line', $frame ) ) {
- * echo " line {$frame['line']}";
- * }
- * echo "\n";
- * }
- ******/
- return TRUE;
- }
-
-}
-// class Utils
-
-// -- set Emacs parameters --
-// Local variables:
-// mode: php;
-// tab-width: 4
-// c-basic-offset: 4
-// c-hanging-comment-ender-p: nil
-// indent-tabs-mode: nil
-// End:
--- /dev/null
+<?php
+
+class CiviTester {
+
+ /**
+ * @var array
+ */
+ private static $singletons = array();
+
+ /**
+ * Get the data source used for testing.
+ *
+ * @param string|NULL $part
+ * One of NULL, 'hostspec', 'port', 'username', 'password', 'database'.
+ * @return string|array|NULL
+ * If $part is omitted, return full DSN array.
+ * If $part is a string, return that part of the DSN.
+ */
+ public static function dsn($part = NULL) {
+ if (!isset(self::$singletons['dsn'])) {
+ require_once "DB.php";
+ self::$singletons['dsn'] = DB::parseDSN(CIVICRM_DSN);
+ }
+
+ if ($part === NULL) {
+ return self::$singletons['dsn'];
+ }
+
+ if (isset(self::$singletons['dsn'][$part])) {
+ return self::$singletons['dsn'][$part];
+ }
+
+ return NULL;
+ }
+
+ /**
+ * Get a connection to the test database.
+ *
+ * @return PDO
+ */
+ public static function pdo() {
+ if (!isset(self::$singletons['pdo'])) {
+ $dsninfo = self::dsn();
+ $host = $dsninfo['hostspec'];
+ $port = @$dsninfo['port'];
+ try {
+ self::$singletons['pdo'] = new PDO("mysql:host={$host}" . ($port ? ";port=$port" : ""),
+ $dsninfo['username'], $dsninfo['password'],
+ array(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE)
+ );
+ }
+ catch (PDOException$e) {
+ echo "Can't connect to MySQL server:" . PHP_EOL . $e->getMessage() . PHP_EOL;
+ exit(1);
+ }
+ }
+ return self::$singletons['pdo'];
+ }
+
+ /**
+ * Get the schema manager.
+ *
+ * @return \CiviTesterBuilder
+ *
+ * @code
+ * CiviTester::builder()->apply();
+ * CiviTester::builder()->sqlFile('ex.sql')->apply();
+ * @endCode
+ */
+ public static function builder() {
+ if (!isset(self::$singletons['builder'])) {
+ $civiRoot = dirname(dirname(dirname(dirname(__FILE__))));
+ self::$singletons['builder'] = new CiviTesterBuilder('CiviTesterSchema');
+ self::$singletons['builder']
+ ->callback(function ($ctx) {
+ $dbName = CiviTester::dsn('database');
+ echo "Installing {$dbName} schema\n";
+ CiviTester::schema()->dropAll();
+ }, 'msg-drop')
+ ->sqlFile($civiRoot . "/sql/civicrm.mysql")
+ ->callback(function ($ctx) {
+ CiviTester::data()->populate();
+ }, 'populate');
+ }
+ return self::$singletons['builder'];
+ }
+
+ /**
+ * @return \CiviTesterSchema
+ */
+ public static function schema() {
+ if (!isset(self::$singletons['schema'])) {
+ self::$singletons['schema'] = new CiviTesterSchema();
+ }
+ return self::$singletons['schema'];
+ }
+
+
+ /**
+ * @return \CiviTesterData
+ */
+ public static function data() {
+ if (!isset(self::$singletons['data'])) {
+ self::$singletons['data'] = new CiviTesterData('CiviTesterData');
+ }
+ return self::$singletons['data'];
+ }
+
+ /**
+ * Prepare and execute a batch of SQL statements.
+ *
+ * @param string $query
+ * @return bool
+ */
+ public static function execute($query) {
+ $pdo = CiviTester::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;
+ }
+
+}
+
+/**
+ * Class CiviTesterSchema
+ *
+ * Manage the entire database. This is useful for destroying or loading the schema.
+ */
+class CiviTesterSchema {
+
+ /**
+ * @param string $type
+ * 'BASE TABLE' or 'VIEW'.
+ * @return array
+ */
+ public function getTables($type) {
+ $pdo = CiviTester::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(CiviTester::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 = CiviTester::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 (CiviTester::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 (CiviTester::execute($query) === FALSE) {
+ throw new RuntimeException("dropSchema: Query failed: $query");
+ }
+ }
+ $this->setStrict(TRUE);
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function truncateAll() {
+ $tables = CiviTester::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 . ';';
+ }
+ }
+
+ CiviTester::schema()->setStrict(FALSE);
+ $queries = array_merge($truncates, $drops);
+ foreach ($queries as $query) {
+ if (CiviTester::execute($query) === FALSE) {
+ throw new RuntimeException("Query failed: $query");
+ }
+ }
+ CiviTester::schema()->setStrict(TRUE);
+
+ return $this;
+ }
+
+}
+
+/**
+ * Class CiviTesterData
+ */
+class CiviTesterData {
+
+ /**
+ * @return bool
+ */
+ public function populate() {
+ CiviTester::schema()->truncateAll();
+
+ CiviTester::schema()->setStrict(FALSE);
+ // initialize test database
+ $sql_file2 = dirname(dirname(dirname(dirname(__FILE__)))) . "/sql/civicrm_data.mysql";
+ $sql_file3 = dirname(dirname(dirname(dirname(__FILE__)))) . "/sql/test_data.mysql";
+ $sql_file4 = dirname(dirname(dirname(dirname(__FILE__)))) . "/sql/test_data_second_domain.mysql";
+
+ $query2 = file_get_contents($sql_file2);
+ $query3 = file_get_contents($sql_file3);
+ $query4 = file_get_contents($sql_file4);
+ if (CiviTester::execute($query2) === FALSE) {
+ throw new RuntimeException("Cannot load civicrm_data.mysql. Aborting.");
+ }
+ if (CiviTester::execute($query3) === FALSE) {
+ throw new RuntimeException("Cannot load test_data.mysql. Aborting.");
+ }
+ if (CiviTester::execute($query4) === FALSE) {
+ throw new RuntimeException("Cannot load test_data.mysql. Aborting.");
+ }
+
+ unset($query, $query2, $query3);
+
+ CiviTester::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;
+ }
+
+}
+
+/**
+ * Class CiviTesterBuilder
+ *
+ * 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 CiviTesterBuilder {
+ 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(CiviTesterStep $step) {
+ $this->targetSignature = NULL;
+ $this->steps[] = $step;
+ return $this;
+ }
+
+ public function callback($callback, $signature = NULL) {
+ return $this->addStep(new CiviTesterCallbackStep($callback, $signature));
+ }
+
+ public function sql($sql) {
+ return $this->addStep(new CiviTesterSqlStep($sql));
+ }
+
+ public function sqlFile($file) {
+ return $this->addStep(new CiviTesterSqlFileStep($file));
+ }
+
+ 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 = CiviTester::pdo();
+ $pdoStmt = $pdo->query(sprintf(
+ "SELECT rev FROM %s.civitest_revs WHERE name = %s",
+ CiviTester::dsn('database'),
+ $pdo->quote($this->name)
+ ));
+ foreach ($pdoStmt as $row) {
+ $liveSchemaRev = $row['rev'];
+ }
+ return $liveSchemaRev;
+ }
+
+ /**
+ * @param $newSignature
+ */
+ protected function setSavedSignature($newSignature) {
+ $pdo = CiviTester::pdo();
+ $query = sprintf(
+ 'INSERT INTO %s.civitest_revs (name,rev) VALUES (%s,%s) '
+ . 'ON DUPLICATE KEY UPDATE rev = %s;',
+ CiviTester::dsn('database'),
+ $pdo->quote($this->name),
+ $pdo->quote($newSignature),
+ $pdo->quote($newSignature)
+ );
+
+ if (CiviTester::execute($query) === FALSE) {
+ throw new RuntimeException("Failed to flag schema version: $query");
+ }
+ }
+
+ /**
+ * Determine if the schema is correct. If necessary, destroy and recreate.
+ *
+ * @param bool $force
+ * @return $this
+ */
+ public function apply($force = FALSE) {
+ $dbName = CiviTester::dsn('database');
+ $query = "USE {$dbName};"
+ . "CREATE TABLE IF NOT EXISTS civitest_revs (name VARCHAR(64) PRIMARY KEY, rev VARCHAR(64));";
+
+ if (CiviTester::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;
+ }
+
+}
+
+interface CiviTesterStep {
+ public function getSig();
+
+ public function isValid();
+
+ public function run($ctx);
+
+}
+
+class CiviTesterSqlFileStep implements CiviTesterStep {
+ private $file;
+
+ /**
+ * CiviTesterSqlFileStep constructor.
+ * @param $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 CiviTesterBuilder */
+ if (CiviTester::execute(@file_get_contents($this->file)) === FALSE) {
+ throw new RuntimeException("Cannot load {$this->file}. Aborting.");
+ }
+ }
+
+}
+
+class CiviTesterSqlStep implements CiviTesterStep {
+ private $sql;
+
+ /**
+ * CiviTesterSqlFileStep constructor.
+ * @param $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 CiviTesterBuilder */
+ if (CiviTester::execute($this->sql) === FALSE) {
+ throw new RuntimeException("Cannot execute: {$this->sql}");
+ }
+ }
+
+}
+
+class CiviTesterCallbackStep implements CiviTesterStep {
+ private $callback;
+ private $sig;
+
+ /**
+ * CiviTesterCallbackStep 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);
+ }
+
+}