tests/phpunit/** - Remove unnecessary "require_once" statements
[civicrm-core.git] / tests / phpunit / CRM / Core / TransactionTest.php
1 <?php
2
3 /**
4 * Class CRM_Core_TransactionTest
5 */
6 class CRM_Core_TransactionTest extends CiviUnitTestCase {
7
8 /**
9 * @var array
10 */
11 private $callbackLog;
12
13 /**
14 * @var array (int $idx => int $contactId) list of contact IDs that have been created (in order of creation)
15 *
16 * Note that ID this is all IDs created by the test-case -- even if the creation was subsequently rolled back
17 */
18 private $cids;
19
20 public function setUp() {
21 parent::setUp();
22 $this->quickCleanup(array('civicrm_contact', 'civicrm_activity'));
23 $this->callbackLog = array();
24 $this->cids = array();
25 }
26
27 /**
28 * @return array
29 */
30 public function dataCreateStyle() {
31 return array(
32 array('sql-insert'),
33 array('bao-create'),
34 );
35 }
36
37 /**
38 * @return array
39 */
40 public function dataCreateAndCommitStyles() {
41 return array(
42 array('sql-insert', 'implicit-commit'),
43 array('sql-insert', 'explicit-commit'),
44 array('bao-create', 'implicit-commit'),
45 array('bao-create', 'explicit-commit'),
46 );
47 }
48
49 /**
50 * @param string $createStyle
51 * 'sql-insert'|'bao-create'.
52 * @param string $commitStyle
53 * 'implicit-commit'|'explicit-commit'.
54 * @dataProvider dataCreateAndCommitStyles
55 */
56 public function testBasicCommit($createStyle, $commitStyle) {
57 $this->createContactWithTransaction('reuse-tx', $createStyle, $commitStyle);
58 $this->assertCount(1, $this->cids);
59 $this->assertContactsExistByOffset(array(0 => TRUE));
60 }
61
62 /**
63 * @dataProvider dataCreateStyle
64 * @param $createStyle
65 */
66 public function testBasicRollback($createStyle) {
67 $this->createContactWithTransaction('reuse-tx', $createStyle, 'rollback');
68 $this->assertCount(1, $this->cids);
69 $this->assertContactsExistByOffset(array(0 => FALSE));
70 }
71
72 /**
73 * Test in which an outer function makes multiple calls to inner functions.
74 * but then rolls back the entire set.
75 *
76 * @param string $createStyle
77 * 'sql-insert'|'bao-create'.
78 * @param string $commitStyle
79 * 'implicit-commit'|'explicit-commit'.
80 * @dataProvider dataCreateAndCommitStyles
81 */
82 public function testBatchRollback($createStyle, $commitStyle) {
83 $this->runBatch(
84 'reuse-tx',
85 array(
86 array('reuse-tx', $createStyle, $commitStyle), // cid 0
87 array('reuse-tx', $createStyle, $commitStyle), // cid 1
88 ),
89 array(0 => TRUE, 1 => TRUE),
90 'rollback'
91 );
92 $this->assertCount(2, $this->cids);
93 $this->assertContactsExistByOffset(array(0 => FALSE, 1 => FALSE));
94 }
95
96 /**
97 * Test in which runBatch makes multiple calls to
98 * createContactWithTransaction using a mix of rollback/commit.
99 * createContactWithTransaction use nesting (savepoints), so the
100 * batch is able to commit.
101 *
102 * @param string $createStyle
103 * 'sql-insert'|'bao-create'.
104 * @param string $commitStyle
105 * 'implicit-commit'|'explicit-commit'.
106 * @dataProvider dataCreateAndCommitStyles
107 */
108 public function testMixedBatchCommit_nesting($createStyle, $commitStyle) {
109 $this->runBatch(
110 'reuse-tx',
111 array(
112 array('nest-tx', $createStyle, $commitStyle), // cid 0
113 array('nest-tx', $createStyle, 'rollback'), // cid 1
114 array('nest-tx', $createStyle, $commitStyle), // cid 2
115 ),
116 array(0 => TRUE, 1 => FALSE, 2 => TRUE),
117 $commitStyle
118 );
119 $this->assertCount(3, $this->cids);
120 $this->assertContactsExistByOffset(array(0 => TRUE, 1 => FALSE, 2 => TRUE));
121 }
122
123 /**
124 * Test in which runBatch makes multiple calls to
125 * createContactWithTransaction using a mix of rollback/commit.
126 * createContactWithTransaction reuses the main transaction,
127 * so the overall batch must rollback.
128 *
129 * @param string $createStyle
130 * 'sql-insert'|'bao-create'.
131 * @param string $commitStyle
132 * 'implicit-commit'|'explicit-commit'.
133 * @dataProvider dataCreateAndCommitStyles
134 */
135 public function testMixedBatchCommit_reuse($createStyle, $commitStyle) {
136 $this->runBatch(
137 'reuse-tx',
138 array(
139 array('reuse-tx', $createStyle, $commitStyle), // cid 0
140 array('reuse-tx', $createStyle, 'rollback'), // cid 1
141 array('reuse-tx', $createStyle, $commitStyle), // cid 2
142 ),
143 array(0 => TRUE, 1 => TRUE, 2 => TRUE),
144 $commitStyle
145 );
146 $this->assertCount(3, $this->cids);
147 $this->assertContactsExistByOffset(array(0 => FALSE, 1 => FALSE, 2 => FALSE));
148 }
149
150 /**
151 * Test in which runBatch makes multiple calls to
152 * createContactWithTransaction using a mix of rollback/commit.
153 * The overall batch is rolled back.
154 *
155 * @param string $createStyle
156 * 'sql-insert'|'bao-create'.
157 * @param string $commitStyle
158 * 'implicit-commit'|'explicit-commit'.
159 * @dataProvider dataCreateAndCommitStyles
160 */
161 public function testMixedBatchRollback_nesting($createStyle, $commitStyle) {
162 $this->assertFalse(CRM_Core_Transaction::isActive());
163 $this->runBatch(
164 'reuse-tx',
165 array(
166 array('nest-tx', $createStyle, $commitStyle), // cid 0
167 array('nest-tx', $createStyle, 'rollback'), // cid 1
168 array('nest-tx', $createStyle, $commitStyle), // cid 2
169 ),
170 array(0 => TRUE, 1 => FALSE, 2 => TRUE),
171 'rollback'
172 );
173 $this->assertFalse(CRM_Core_Transaction::isActive());
174 $this->assertCount(3, $this->cids);
175 $this->assertContactsExistByOffset(array(0 => FALSE, 1 => FALSE, 2 => FALSE));
176 }
177
178 public function testIsActive() {
179 $this->assertEquals(FALSE, CRM_Core_Transaction::isActive());
180 $this->assertEquals(TRUE, CRM_Core_Transaction::willCommit());
181 $tx = new CRM_Core_Transaction();
182 $this->assertEquals(TRUE, CRM_Core_Transaction::isActive());
183 $this->assertEquals(TRUE, CRM_Core_Transaction::willCommit());
184 $tx = NULL;
185 $this->assertEquals(FALSE, CRM_Core_Transaction::isActive());
186 $this->assertEquals(TRUE, CRM_Core_Transaction::willCommit());
187 }
188
189 public function testIsActive_rollback() {
190 $this->assertEquals(FALSE, CRM_Core_Transaction::isActive());
191 $this->assertEquals(TRUE, CRM_Core_Transaction::willCommit());
192
193 $tx = new CRM_Core_Transaction();
194 $this->assertEquals(TRUE, CRM_Core_Transaction::isActive());
195 $this->assertEquals(TRUE, CRM_Core_Transaction::willCommit());
196
197 $tx->rollback();
198 $this->assertEquals(TRUE, CRM_Core_Transaction::isActive());
199 $this->assertEquals(FALSE, CRM_Core_Transaction::willCommit());
200
201 $tx = NULL;
202 $this->assertEquals(FALSE, CRM_Core_Transaction::isActive());
203 $this->assertEquals(TRUE, CRM_Core_Transaction::willCommit());
204 }
205
206 public function testCallback_commit() {
207 $tx = new CRM_Core_Transaction();
208
209 CRM_Core_Transaction::addCallback(CRM_Core_Transaction::PHASE_PRE_COMMIT, array($this, '_preCommit'), array(
210 'qwe',
211 'rty',
212 ));
213 CRM_Core_Transaction::addCallback(CRM_Core_Transaction::PHASE_POST_COMMIT, array($this, '_postCommit'), array(
214 'uio',
215 'p[]',
216 ));
217 CRM_Core_Transaction::addCallback(CRM_Core_Transaction::PHASE_PRE_ROLLBACK, array(
218 $this,
219 '_preRollback',
220 ), array('asd', 'fgh'));
221 CRM_Core_Transaction::addCallback(CRM_Core_Transaction::PHASE_POST_ROLLBACK, array(
222 $this,
223 '_postRollback',
224 ), array('jkl', ';'));
225
226 CRM_Core_DAO::executeQuery('UPDATE civicrm_contact SET id = 100 WHERE id = 100');
227
228 $this->assertEquals(array(), $this->callbackLog);
229 $tx = NULL;
230 $this->assertEquals(array('_preCommit', 'qwe', 'rty'), $this->callbackLog[0]);
231 $this->assertEquals(array('_postCommit', 'uio', 'p[]'), $this->callbackLog[1]);
232 }
233
234 public function testCallback_rollback() {
235 $tx = new CRM_Core_Transaction();
236
237 CRM_Core_Transaction::addCallback(CRM_Core_Transaction::PHASE_PRE_COMMIT, array($this, '_preCommit'), array(
238 'ewq',
239 'ytr',
240 ));
241 CRM_Core_Transaction::addCallback(CRM_Core_Transaction::PHASE_POST_COMMIT, array($this, '_postCommit'), array(
242 'oiu',
243 '][p',
244 ));
245 CRM_Core_Transaction::addCallback(CRM_Core_Transaction::PHASE_PRE_ROLLBACK, array(
246 $this,
247 '_preRollback',
248 ), array('dsa', 'hgf'));
249 CRM_Core_Transaction::addCallback(CRM_Core_Transaction::PHASE_POST_ROLLBACK, array(
250 $this,
251 '_postRollback',
252 ), array('lkj', ';'));
253
254 CRM_Core_DAO::executeQuery('UPDATE civicrm_contact SET id = 100 WHERE id = 100');
255 $tx->rollback();
256
257 $this->assertEquals(array(), $this->callbackLog);
258 $tx = NULL;
259 $this->assertEquals(array('_preRollback', 'dsa', 'hgf'), $this->callbackLog[0]);
260 $this->assertEquals(array('_postRollback', 'lkj', ';'), $this->callbackLog[1]);
261 }
262
263 /**
264 * @param string $createStyle
265 * 'sql-insert'|'bao-create'.
266 * @param string $commitStyle
267 * 'implicit-commit'|'explicit-commit'.
268 * @dataProvider dataCreateAndCommitStyles
269 */
270 public function testRun_ok($createStyle, $commitStyle) {
271 $test = $this;
272 CRM_Core_Transaction::create(TRUE)->run(function ($tx) use (&$test, $createStyle, $commitStyle) {
273 $test->createContactWithTransaction('nest-tx', $createStyle, $commitStyle);
274 $test->assertContactsExistByOffset(array(0 => TRUE));
275 });
276 $this->assertContactsExistByOffset(array(0 => TRUE));
277 }
278
279 /**
280 * @param string $createStyle
281 * 'sql-insert'|'bao-create'.
282 * @param string $commitStyle
283 * 'implicit-commit'|'explicit-commit'.
284 * @dataProvider dataCreateAndCommitStyles
285 */
286 public function testRun_exception($createStyle, $commitStyle) {
287 $tx = new CRM_Core_Transaction();
288 $test = $this;
289 $e = NULL; // Exception
290 try {
291 CRM_Core_Transaction::create(TRUE)->run(function ($tx) use (&$test, $createStyle, $commitStyle) {
292 $test->createContactWithTransaction('nest-tx', $createStyle, $commitStyle);
293 $test->assertContactsExistByOffset(array(0 => TRUE));
294 throw new Exception("Ruh-roh");
295 });
296 }
297 catch (Exception $ex) {
298 $e = $ex;
299 if (get_class($e) != 'Exception' || $e->getMessage() != 'Ruh-roh') {
300 throw $e;
301 }
302 }
303 $this->assertTrue($e instanceof Exception);
304 $this->assertContactsExistByOffset(array(0 => FALSE));
305 }
306
307 /**
308 * @param $cids
309 * @param bool $exist
310 */
311 public function assertContactsExist($cids, $exist = TRUE) {
312 foreach ($cids as $cid) {
313 $this->assertTrue(is_numeric($cid));
314 $this->assertDBQuery($exist ? 1 : 0, 'SELECT count(*) FROM civicrm_contact WHERE id = %1', array(
315 1 => array($cid, 'Integer'),
316 ));
317 }
318 }
319
320 /**
321 * @param array $existsByOffset
322 * Array(int $cidOffset => bool $expectExists).
323 * @param int $generalOffset
324 */
325 public function assertContactsExistByOffset($existsByOffset, $generalOffset = 0) {
326 foreach ($existsByOffset as $offset => $expectExists) {
327 $this->assertTrue(isset($this->cids[$generalOffset + $offset]), "Find cid at offset($generalOffset + $offset)");
328 $cid = $this->cids[$generalOffset + $offset];
329 $this->assertTrue(is_numeric($cid));
330 $this->assertDBQuery($expectExists ? 1 : 0, 'SELECT count(*) FROM civicrm_contact WHERE id = %1', array(
331 1 => array($cid, 'Integer'),
332 ), "Check contact at offset($generalOffset + $offset)");
333 }
334 }
335
336 /**
337 * Use SQL to INSERT a contact and assert success. Perform
338 * work within a transaction.
339 *
340 * @param string $nesting
341 * 'reuse-tx'|'nest-tx' how to construct transaction.
342 * @param string $insert
343 * 'sql-insert'|'bao-create' how to add the example record.
344 * @param string $outcome
345 * 'rollback'|'implicit-commit'|'explicit-commit' how to finish transaction.
346 * @return int
347 * cid
348 */
349 public function createContactWithTransaction($nesting, $insert, $outcome) {
350 if ($nesting != 'reuse-tx' && $nesting != 'nest-tx') {
351 throw new RuntimeException('Bad test data: reuse=' . $nesting);
352 }
353 if ($insert != 'sql-insert' && $insert != 'bao-create') {
354 throw new RuntimeException('Bad test data: insert=' . $insert);
355 }
356 if ($outcome != 'rollback' && $outcome != 'implicit-commit' && $outcome != 'explicit-commit') {
357 throw new RuntimeException('Bad test data: outcome=' . $outcome);
358 }
359
360 $tx = new CRM_Core_Transaction($nesting === 'nest-tx');
361
362 if ($insert == 'sql-insert') {
363 $r = CRM_Core_DAO::executeQuery("INSERT INTO civicrm_contact(first_name,last_name) VALUES ('ff', 'll')");
364 $cid = mysql_insert_id();
365 }
366 elseif ($insert == 'bao-create') {
367 $params = array(
368 'contact_type' => 'Individual',
369 'first_name' => 'FF',
370 'last_name' => 'LL',
371 );
372 $r = CRM_Contact_BAO_Contact::create($params);
373 $cid = $r->id;
374 }
375
376 $this->cids[] = $cid;
377
378 $this->assertContactsExist(array($cid), TRUE);
379
380 if ($outcome == 'rollback') {
381 $tx->rollback();
382 }
383 elseif ($outcome == 'explicit-commit') {
384 $tx->commit();
385 } // else: implicit-commit
386
387 return $cid;
388 }
389
390 /**
391 * Perform a series of operations within smaller transactions.
392 *
393 * @param string $nesting
394 * 'reuse-tx'|'nest-tx' how to construct transaction.
395 * @param array $callbacks
396 * See createContactWithTransaction.
397 * @param array $existsByOffset
398 * See assertContactsMix.
399 * @param string $outcome
400 * 'rollback'|'implicit-commit'|'explicit-commit' how to finish transaction.
401 * @return void
402 */
403 public function runBatch($nesting, $callbacks, $existsByOffset, $outcome) {
404 if ($nesting != 'reuse-tx' && $nesting != 'nest-tx') {
405 throw new RuntimeException('Bad test data: nesting=' . $nesting);
406 }
407 if ($outcome != 'rollback' && $outcome != 'implicit-commit' && $outcome != 'explicit-commit') {
408 throw new RuntimeException('Bad test data: outcome=' . $nesting);
409 }
410
411 $tx = new CRM_Core_Transaction($nesting === 'reuse-tx');
412
413 $generalOffset = count($this->cids);
414 foreach ($callbacks as $callback) {
415 list ($cbNesting, $cbInsert, $cbOutcome) = $callback;
416 $this->createContactWithTransaction($cbNesting, $cbInsert, $cbOutcome);
417 }
418
419 $this->assertContactsExistByOffset($existsByOffset, $generalOffset);
420
421 if ($outcome == 'rollback') {
422 $tx->rollback();
423 }
424 elseif ($outcome == 'explicit-commit') {
425 $tx->commit();
426 } // else: implicit-commit
427 }
428
429 /**
430 * @param $arg1
431 * @param $arg2
432 */
433 public function _preCommit($arg1, $arg2) {
434 $this->callbackLog[] = array('_preCommit', $arg1, $arg2);
435 }
436
437 /**
438 * @param $arg1
439 * @param $arg2
440 */
441 public function _postCommit($arg1, $arg2) {
442 $this->callbackLog[] = array('_postCommit', $arg1, $arg2);
443 }
444
445 /**
446 * @param $arg1
447 * @param $arg2
448 */
449 public function _preRollback($arg1, $arg2) {
450 $this->callbackLog[] = array('_preRollback', $arg1, $arg2);
451 }
452
453 /**
454 * @param $arg1
455 * @param $arg2
456 */
457 public function _postRollback($arg1, $arg2) {
458 $this->callbackLog[] = array('_postRollback', $arg1, $arg2);
459 }
460
461 }