Merge branch 'CRM-15202' of https://github.com/jmcclelland/civicrm-core into 4.7...
[civicrm-core.git] / Civi / Test / CiviEnvBuilder.php
1 <?php
2 namespace Civi\Test;
3
4 use Civi\Test\CiviEnvBuilder\CallbackStep;
5 use Civi\Test\CiviEnvBuilder\ExtensionsStep;
6 use Civi\Test\CiviEnvBuilder\SqlFileStep;
7 use Civi\Test\CiviEnvBuilder\SqlStep;
8 use Civi\Test\CiviEnvBuilder\StepInterface;
9 use RuntimeException;
10
11 /**
12 * Class CiviEnvBuilder
13 *
14 * Provides a fluent interface for tracking a set of steps.
15 * By computing and storing a signature for the list steps, we can
16 * determine whether to (a) do nothing with the list or (b)
17 * reapply all the steps.
18 */
19 class CiviEnvBuilder {
20 protected $name;
21
22 private $steps = array();
23
24 /**
25 * @var string|NULL
26 * A digest of the values in $steps.
27 */
28 private $targetSignature = NULL;
29
30 public function __construct($name) {
31 $this->name = $name;
32 }
33
34 public function addStep(StepInterface $step) {
35 $this->targetSignature = NULL;
36 $this->steps[] = $step;
37 return $this;
38 }
39
40 public function callback($callback, $signature = NULL) {
41 return $this->addStep(new CallbackStep($callback, $signature));
42 }
43
44 public function sql($sql) {
45 return $this->addStep(new SqlStep($sql));
46 }
47
48 public function sqlFile($file) {
49 return $this->addStep(new SqlFileStep($file));
50 }
51
52 /**
53 * Require that an extension be installed.
54 *
55 * @param string|array $names
56 * One or more extension names. You may use a wildcard '*'.
57 * @return $this
58 */
59 public function install($names) {
60 return $this->addStep(new ExtensionsStep('install', $names));
61 }
62
63 /**
64 * Require an extension be installed (identified by its directory).
65 *
66 * @param string $dir
67 * The current test directory. We'll search for info.xml to
68 * see what this extension is.
69 * @return $this
70 * @throws \CRM_Extension_Exception_ParseException
71 */
72 public function installMe($dir) {
73 return $this->addStep(new ExtensionsStep('install', $this->whoAmI($dir)));
74 }
75
76 /**
77 * Require an extension be uninstalled.
78 *
79 * @param string|array $names
80 * One or more extension names. You may use a wildcard '*'.
81 * @return $this
82 */
83 public function uninstall($names) {
84 return $this->addStep(new ExtensionsStep('uninstall', $names));
85 }
86
87 /**
88 * Require an extension be uninstalled (identified by its directory).
89 *
90 * @param string $dir
91 * The current test directory. We'll search for info.xml to
92 * see what this extension is.
93 * @return $this
94 * @throws \CRM_Extension_Exception_ParseException
95 */
96 public function uninstallMe($dir) {
97 return $this->addStep(new ExtensionsStep('uninstall', $this->whoAmI($dir)));
98 }
99
100 protected function assertValid() {
101 foreach ($this->steps as $step) {
102 if (!$step->isValid()) {
103 throw new RuntimeException("Found invalid step: " . var_dump($step, 1));
104 }
105 }
106 }
107
108 /**
109 * @return string
110 */
111 protected function getTargetSignature() {
112 if ($this->targetSignature === NULL) {
113 $buf = '';
114 foreach ($this->steps as $step) {
115 $buf .= $step->getSig();
116 }
117 $this->targetSignature = md5($buf);
118 }
119
120 return $this->targetSignature;
121 }
122
123 /**
124 * @return string
125 */
126 protected function getSavedSignature() {
127 $liveSchemaRev = NULL;
128 $pdo = \Civi\Test::pdo();
129 $pdoStmt = $pdo->query(sprintf(
130 "SELECT rev FROM %s.civitest_revs WHERE name = %s",
131 \Civi\Test::dsn('database'),
132 $pdo->quote($this->name)
133 ));
134 foreach ($pdoStmt as $row) {
135 $liveSchemaRev = $row['rev'];
136 }
137 return $liveSchemaRev;
138 }
139
140 /**
141 * @param $newSignature
142 */
143 protected function setSavedSignature($newSignature) {
144 $pdo = \Civi\Test::pdo();
145 $query = sprintf(
146 'INSERT INTO %s.civitest_revs (name,rev) VALUES (%s,%s) '
147 . 'ON DUPLICATE KEY UPDATE rev = %s;',
148 \Civi\Test::dsn('database'),
149 $pdo->quote($this->name),
150 $pdo->quote($newSignature),
151 $pdo->quote($newSignature)
152 );
153
154 if (\Civi\Test::execute($query) === FALSE) {
155 throw new RuntimeException("Failed to flag schema version: $query");
156 }
157 }
158
159 /**
160 * Determine if there's been a change in the preferred configuration.
161 * If the preferred-configuration matches the last test, keep it. Otherwise,
162 * destroy and recreate.
163 *
164 * @param bool $force
165 * Forcibly execute the build, even if the configuration hasn't changed.
166 * This will slow-down the tests, but it may be appropriate for some very sloppy
167 * tests.
168 * @return $this
169 */
170 public function apply($force = FALSE) {
171 $dbName = \Civi\Test::dsn('database');
172 $query = "USE {$dbName};"
173 . "CREATE TABLE IF NOT EXISTS civitest_revs (name VARCHAR(64) PRIMARY KEY, rev VARCHAR(64));";
174
175 if (\Civi\Test::execute($query) === FALSE) {
176 throw new \RuntimeException("Failed to flag schema version: $query");
177 }
178
179 $this->assertValid();
180
181 if (!$force && $this->getSavedSignature() === $this->getTargetSignature()) {
182 return $this;
183 }
184 foreach ($this->steps as $step) {
185 $step->run($this);
186 }
187 $this->setSavedSignature($this->getTargetSignature());
188 return $this;
189 }
190
191 /**
192 * @param $dir
193 * @return null
194 * @throws \CRM_Extension_Exception_ParseException
195 */
196 protected function whoAmI($dir) {
197 while ($dir && dirname($dir) !== $dir && !file_exists("$dir/info.xml")) {
198 $dir = dirname($dir);
199 }
200 if (file_exists("$dir/info.xml")) {
201 $info = \CRM_Extension_Info::loadFromFile("$dir/info.xml");
202 $name = $info->key;
203 return $name;
204 }
205 return $name;
206 }
207
208 }