Merge pull request #23981 from eileenmcnaughton/act_seq
[civicrm-core.git] / Civi / Test.php
1 <?php
2 namespace Civi;
3
4 use PDO;
5 use PDOException;
6
7 /**
8 * Class Test
9 *
10 * A facade for managing the test environment.
11 */
12 class Test {
13
14 /**
15 * @var array
16 */
17 private static $singletons = [];
18
19 /**
20 * @var array
21 */
22 public static $statics = [];
23
24 /**
25 * Run code in a pre-boot fashion.
26 *
27 * @param callable $callback
28 * @return mixed
29 * Pass through the result of the callback.
30 */
31 public static function asPreInstall($callback) {
32 $conn = \Civi\Test::pdo();
33
34 $oldEscaper = \CRM_Core_I18n::$SQL_ESCAPER;
35 \Civi::$statics['testPreInstall'] = (\Civi::$statics['testPreInstall'] ?? 0) + 1;
36 try {
37 \CRM_Core_I18n::$SQL_ESCAPER = function ($text) use ($conn) {
38 return substr($conn->quote($text), 1, -1);
39 };
40 return $callback();
41 } finally {
42 \CRM_Core_I18n::$SQL_ESCAPER = $oldEscaper;
43 \Civi::$statics['testPreInstall']--;
44 if (\Civi::$statics['testPreInstall'] <= 0) {
45 unset(\Civi::$statics['testPreInstall']);
46 }
47 }
48 }
49
50 /**
51 * Get the data source used for testing.
52 *
53 * @param string|null $part
54 * One of NULL, 'hostspec', 'port', 'username', 'password', 'database'.
55 * @return string|array|NULL
56 * If $part is omitted, return full DSN array.
57 * If $part is a string, return that part of the DSN.
58 */
59 public static function dsn($part = NULL) {
60 if (!isset(self::$singletons['dsn'])) {
61 require_once "DB.php";
62 $dsn = \CRM_Utils_SQL::autoSwitchDSN(CIVICRM_DSN);
63 self::$singletons['dsn'] = \DB::parseDSN($dsn);
64 }
65
66 if ($part === NULL) {
67 return self::$singletons['dsn'];
68 }
69
70 if (isset(self::$singletons['dsn'][$part])) {
71 return self::$singletons['dsn'][$part];
72 }
73
74 return NULL;
75 }
76
77 /**
78 * Get a connection to the test database.
79 *
80 * @return \PDO
81 */
82 public static function pdo() {
83 if (!isset(self::$singletons['pdo'])) {
84 $dsninfo = self::dsn();
85 $host = $dsninfo['hostspec'];
86 $port = @$dsninfo['port'];
87 try {
88 self::$singletons['pdo'] = new PDO("mysql:host={$host}" . ($port ? ";port=$port" : ""),
89 $dsninfo['username'], $dsninfo['password'],
90 [PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE]
91 );
92 }
93 catch (PDOException $e) {
94 echo "Can't connect to MySQL server:" . PHP_EOL . $e->getMessage() . PHP_EOL;
95 exit(1);
96 }
97 }
98 return self::$singletons['pdo'];
99 }
100
101 /**
102 * Create a builder for the headless environment.
103 *
104 * ```
105 * \Civi\Test::headless()->apply();
106 * \Civi\Test::headless()->sqlFile('ex.sql')->apply();
107 * ```
108 *
109 * @return \Civi\Test\CiviEnvBuilder
110 */
111 public static function headless() {
112 $civiRoot = dirname(__DIR__);
113 $builder = new \Civi\Test\CiviEnvBuilder('CiviEnvBuilder');
114 $builder
115 ->callback(function ($ctx) {
116 if (CIVICRM_UF !== 'UnitTests') {
117 throw new \RuntimeException("\\Civi\\Test::headless() requires CIVICRM_UF=UnitTests");
118 }
119 $dbName = \Civi\Test::dsn('database');
120 fprintf(STDERR, "Installing {$dbName} schema\n");
121 \Civi\Test::schema()->dropAll();
122 }, 'headless-drop')
123 ->coreSchema()
124 ->sql("DELETE FROM civicrm_extension")
125 ->callback(function ($ctx) {
126 \Civi\Test::data()->populate();
127 }, 'populate');
128 return $builder;
129 }
130
131 /**
132 * Create a builder for end-to-end testing on the live environment.
133 *
134 * ```
135 * \Civi\Test::e2e()->apply();
136 * \Civi\Test::e2e()->install('foo.bar')->apply();
137 * ```
138 *
139 * @return \Civi\Test\CiviEnvBuilder
140 */
141 public static function e2e() {
142 $builder = new \Civi\Test\CiviEnvBuilder('CiviEnvBuilder');
143 $builder
144 ->callback(function ($ctx) {
145 if (CIVICRM_UF === 'UnitTests') {
146 throw new \RuntimeException("\\Civi\\Test::e2e() requires a real CMS. Found CIVICRM_UF=UnitTests.");
147 }
148 }, 'e2e-check');
149 return $builder;
150 }
151
152 /**
153 * @return \Civi\Test\Schema
154 */
155 public static function schema() {
156 if (!isset(self::$singletons['schema'])) {
157 self::$singletons['schema'] = new \Civi\Test\Schema();
158 }
159 return self::$singletons['schema'];
160 }
161
162 /**
163 * @return \CRM_Core_CodeGen_Main
164 */
165 public static function codeGen() {
166 if (!isset(self::$singletons['codeGen'])) {
167 $civiRoot = str_replace(DIRECTORY_SEPARATOR, '/', dirname(__DIR__));
168 $codeGen = new \CRM_Core_CodeGen_Main("$civiRoot/CRM/Core/DAO", "$civiRoot/sql", $civiRoot, "$civiRoot/templates", NULL, "UnitTests", NULL, "$civiRoot/xml/schema/Schema.xml", NULL);
169 $codeGen->setVerbose(FALSE);
170 $codeGen->init();
171 self::$singletons['codeGen'] = $codeGen;
172 }
173 return self::$singletons['codeGen'];
174 }
175
176 /**
177 * @return \Civi\Test\Data
178 */
179 public static function data() {
180 if (!isset(self::$singletons['data'])) {
181 self::$singletons['data'] = new \Civi\Test\Data('CiviTesterData');
182 }
183 return self::$singletons['data'];
184 }
185
186 /**
187 * @return \Civi\Test\ExampleDataLoader
188 */
189 public static function examples(): \Civi\Test\ExampleDataLoader {
190 if (!isset(self::$singletons['examples'])) {
191 self::$singletons['examples'] = new \Civi\Test\ExampleDataLoader();
192 }
193 return self::$singletons['examples'];
194 }
195
196 /**
197 * Lookup the content of an example data-set.
198 *
199 * This helper is for the common case of looking up the data for a specific example.
200 * If you need more detailed information (eg the list of examples or other metadata),
201 * then use `\Civi\Test::examples(): ExampleDataLoader`. It provides more methods.
202 *
203 * @param string $name
204 * Symbolic name of the data-set.
205 * @return array
206 * The example data.
207 */
208 public static function example(string $name): array {
209 $result = static::examples()->getFull($name);
210 if (!isset($result['data'])) {
211 throw new \CRM_Core_Exception("Failed to load example data-set: $name");
212 }
213 return $result['data'];
214 }
215
216 /**
217 * @return \Civi\Test\EventChecker
218 */
219 public static function eventChecker() {
220 if (!isset(self::$singletons['eventChecker'])) {
221 self::$singletons['eventChecker'] = new \Civi\Test\EventChecker();
222 }
223 return self::$singletons['eventChecker'];
224 }
225
226 /**
227 * Prepare and execute a batch of SQL statements.
228 *
229 * @param string $query
230 * @return bool
231 */
232 public static function execute($query) {
233 $pdo = \Civi\Test::pdo();
234
235 $string = preg_replace("/^#[^\n]*$/m", "\n", $query);
236 $string = preg_replace("/^(--[^-]).*/m", "\n", $string);
237
238 $queries = preg_split('/;\s*$/m', $string);
239 foreach ($queries as $query) {
240 $query = trim($query);
241 if (!empty($query)) {
242 $result = $pdo->query($query);
243 if ($pdo->errorCode() == 0) {
244 continue;
245 }
246 else {
247 var_dump($result);
248 var_dump($pdo->errorInfo());
249 // die( "Cannot execute $query: " . $pdo->errorInfo() );
250 }
251 }
252 }
253 return TRUE;
254 }
255
256 }