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