2 require_once 'CiviTest/CiviUnitTestCase.php';
3 require_once 'CiviTest/Contact.php';
4 require_once 'CiviTest/Custom.php';
7 * Class CRM_Core_TransactionTest
9 class CRM_Core_TransactionTest
extends CiviUnitTestCase
{
17 * @var array (int $idx => int $contactId) list of contact IDs that have been created (in order of creation)
19 * Note that ID this is all IDs created by the test-case -- even if the creation was subsequently rolled back
23 public function setUp() {
25 $this->quickCleanup(array('civicrm_contact', 'civicrm_activity'));
26 $this->callbackLog
= array();
27 $this->cids
= array();
30 public function dataCreateStyle() {
37 public function dataCreateAndCommitStyles() {
39 array('sql-insert', 'implicit-commit'),
40 array('sql-insert', 'explicit-commit'),
41 array('bao-create', 'implicit-commit'),
42 array('bao-create', 'explicit-commit'),
47 * @param string $createStyle 'sql-insert'|'bao-create'
48 * @param string $commitStyle 'implicit-commit'|'explicit-commit'
49 * @dataProvider dataCreateAndCommitStyles
51 public function testBasicCommit($createStyle, $commitStyle) {
52 $this->createContactWithTransaction('reuse-tx', $createStyle, $commitStyle);
53 $this->assertCount(1, $this->cids
);
54 $this->assertContactsExistByOffset(array(0 => TRUE));
58 * @dataProvider dataCreateStyle
60 public function testBasicRollback($createStyle) {
61 $this->createContactWithTransaction('reuse-tx', $createStyle, 'rollback');
62 $this->assertCount(1, $this->cids
);
63 $this->assertContactsExistByOffset(array(0 => FALSE));
67 * Test in which an outer function makes multiple calls to inner functions
68 * but then rolls back the entire set.
70 * @param string $createStyle 'sql-insert'|'bao-create'
71 * @param string $commitStyle 'implicit-commit'|'explicit-commit'
72 * @dataProvider dataCreateAndCommitStyles
74 public function testBatchRollback($createStyle, $commitStyle) {
78 array('reuse-tx', $createStyle, $commitStyle), // cid 0
79 array('reuse-tx', $createStyle, $commitStyle), // cid 1
81 array(0 => TRUE, 1 => TRUE),
84 $this->assertCount(2, $this->cids
);
85 $this->assertContactsExistByOffset(array(0 => FALSE, 1 => FALSE));
89 * Test in which runBatch makes multiple calls to
90 * createContactWithTransaction using a mix of rollback/commit.
91 * createContactWithTransaction use nesting (savepoints), so the
92 * batch is able to commit.
94 * @param string $createStyle 'sql-insert'|'bao-create'
95 * @param string $commitStyle 'implicit-commit'|'explicit-commit'
96 * @dataProvider dataCreateAndCommitStyles
98 public function testMixedBatchCommit_nesting($createStyle, $commitStyle) {
102 array('nest-tx', $createStyle, $commitStyle), // cid 0
103 array('nest-tx', $createStyle, 'rollback'), // cid 1
104 array('nest-tx', $createStyle, $commitStyle), // cid 2
106 array(0 => TRUE, 1 => FALSE, 2 => TRUE),
109 $this->assertCount(3, $this->cids
);
110 $this->assertContactsExistByOffset(array(0 => TRUE, 1 => FALSE, 2 => TRUE));
114 * Test in which runBatch makes multiple calls to
115 * createContactWithTransaction using a mix of rollback/commit.
116 * createContactWithTransaction reuses the main transaction,
117 * so the overall batch must rollback.
119 * @param string $createStyle 'sql-insert'|'bao-create'
120 * @param string $commitStyle 'implicit-commit'|'explicit-commit'
121 * @dataProvider dataCreateAndCommitStyles
123 public function testMixedBatchCommit_reuse($createStyle, $commitStyle) {
127 array('reuse-tx', $createStyle, $commitStyle), // cid 0
128 array('reuse-tx', $createStyle, 'rollback'), // cid 1
129 array('reuse-tx', $createStyle, $commitStyle), // cid 2
131 array(0 => TRUE, 1 => TRUE, 2 => TRUE),
134 $this->assertCount(3, $this->cids
);
135 $this->assertContactsExistByOffset(array(0 => FALSE, 1 => FALSE, 2 => FALSE));
139 * Test in which runBatch makes multiple calls to
140 * createContactWithTransaction using a mix of rollback/commit.
141 * The overall batch is rolled back.
143 * @param string $createStyle 'sql-insert'|'bao-create'
144 * @param string $commitStyle 'implicit-commit'|'explicit-commit'
145 * @dataProvider dataCreateAndCommitStyles
147 public function testMixedBatchRollback_nesting($createStyle, $commitStyle) {
148 $this->assertFalse(CRM_Core_Transaction
::isActive());
152 array('nest-tx', $createStyle, $commitStyle), // cid 0
153 array('nest-tx', $createStyle, 'rollback'), // cid 1
154 array('nest-tx', $createStyle, $commitStyle), // cid 2
156 array(0 => TRUE, 1 => FALSE, 2 => TRUE),
159 $this->assertFalse(CRM_Core_Transaction
::isActive());
160 $this->assertCount(3, $this->cids
);
161 $this->assertContactsExistByOffset(array(0 => FALSE, 1 => FALSE, 2 => FALSE));
164 public function testIsActive() {
165 $this->assertEquals(FALSE, CRM_Core_Transaction
::isActive());
166 $this->assertEquals(TRUE, CRM_Core_Transaction
::willCommit());
167 $tx = new CRM_Core_Transaction();
168 $this->assertEquals(TRUE, CRM_Core_Transaction
::isActive());
169 $this->assertEquals(TRUE, CRM_Core_Transaction
::willCommit());
171 $this->assertEquals(FALSE, CRM_Core_Transaction
::isActive());
172 $this->assertEquals(TRUE, CRM_Core_Transaction
::willCommit());
175 public function testIsActive_rollback() {
176 $this->assertEquals(FALSE, CRM_Core_Transaction
::isActive());
177 $this->assertEquals(TRUE, CRM_Core_Transaction
::willCommit());
179 $tx = new CRM_Core_Transaction();
180 $this->assertEquals(TRUE, CRM_Core_Transaction
::isActive());
181 $this->assertEquals(TRUE, CRM_Core_Transaction
::willCommit());
184 $this->assertEquals(TRUE, CRM_Core_Transaction
::isActive());
185 $this->assertEquals(FALSE, CRM_Core_Transaction
::willCommit());
188 $this->assertEquals(FALSE, CRM_Core_Transaction
::isActive());
189 $this->assertEquals(TRUE, CRM_Core_Transaction
::willCommit());
192 public function testCallback_commit() {
193 $tx = new CRM_Core_Transaction();
195 CRM_Core_Transaction
::addCallback(CRM_Core_Transaction
::PHASE_PRE_COMMIT
, array($this, '_preCommit'), array('qwe','rty'));
196 CRM_Core_Transaction
::addCallback(CRM_Core_Transaction
::PHASE_POST_COMMIT
, array($this, '_postCommit'), array('uio','p[]'));
197 CRM_Core_Transaction
::addCallback(CRM_Core_Transaction
::PHASE_PRE_ROLLBACK
, array($this, '_preRollback'), array('asd','fgh'));
198 CRM_Core_Transaction
::addCallback(CRM_Core_Transaction
::PHASE_POST_ROLLBACK
, array($this, '_postRollback'), array('jkl',';'));
200 CRM_Core_DAO
::executeQuery('UPDATE civicrm_contact SET id = 100 WHERE id = 100');
202 $this->assertEquals(array(), $this->callbackLog
);
204 $this->assertEquals(array('_preCommit', 'qwe', 'rty'), $this->callbackLog
[0]);
205 $this->assertEquals(array('_postCommit', 'uio', 'p[]'), $this->callbackLog
[1]);
208 public function testCallback_rollback() {
209 $tx = new CRM_Core_Transaction();
211 CRM_Core_Transaction
::addCallback(CRM_Core_Transaction
::PHASE_PRE_COMMIT
, array($this, '_preCommit'), array('ewq','ytr'));
212 CRM_Core_Transaction
::addCallback(CRM_Core_Transaction
::PHASE_POST_COMMIT
, array($this, '_postCommit'), array('oiu','][p'));
213 CRM_Core_Transaction
::addCallback(CRM_Core_Transaction
::PHASE_PRE_ROLLBACK
, array($this, '_preRollback'), array('dsa','hgf'));
214 CRM_Core_Transaction
::addCallback(CRM_Core_Transaction
::PHASE_POST_ROLLBACK
, array($this, '_postRollback'), array('lkj',';'));
216 CRM_Core_DAO
::executeQuery('UPDATE civicrm_contact SET id = 100 WHERE id = 100');
219 $this->assertEquals(array(), $this->callbackLog
);
221 $this->assertEquals(array('_preRollback', 'dsa', 'hgf'), $this->callbackLog
[0]);
222 $this->assertEquals(array('_postRollback', 'lkj', ';'), $this->callbackLog
[1]);
226 * @param string $createStyle 'sql-insert'|'bao-create'
227 * @param string $commitStyle 'implicit-commit'|'explicit-commit'
228 * @dataProvider dataCreateAndCommitStyles
230 public function testRun_ok($createStyle, $commitStyle) {
232 CRM_Core_Transaction
::create(TRUE)->run(function($tx) use (&$test, $createStyle, $commitStyle) {
233 $test->createContactWithTransaction('nest-tx', $createStyle, $commitStyle);
234 $test->assertContactsExistByOffset(array(0 => TRUE));
236 $this->assertContactsExistByOffset(array(0 => TRUE));
240 * @param string $createStyle 'sql-insert'|'bao-create'
241 * @param string $commitStyle 'implicit-commit'|'explicit-commit'
242 * @dataProvider dataCreateAndCommitStyles
244 public function testRun_exception($createStyle, $commitStyle) {
245 $tx = new CRM_Core_Transaction();
247 $e = NULL; // Exception
249 CRM_Core_Transaction
::create(TRUE)->run(function($tx) use (&$test, $createStyle, $commitStyle) {
250 $test->createContactWithTransaction('nest-tx', $createStyle, $commitStyle);
251 $test->assertContactsExistByOffset(array(0 => TRUE));
252 throw new Exception("Ruh-roh");
254 } catch (Exception
$ex) {
256 if (get_class($e) != 'Exception' ||
$e->getMessage() != 'Ruh-roh') {
260 $this->assertTrue($e instanceof Exception
);
261 $this->assertContactsExistByOffset(array(0 => FALSE));
268 public function assertContactsExist($cids, $exist = TRUE) {
269 foreach ($cids as $cid) {
270 $this->assertTrue(is_numeric($cid));
271 $this->assertDBQuery($exist ?
1 : 0, 'SELECT count(*) FROM civicrm_contact WHERE id = %1', array(
272 1 => array($cid, 'Integer'),
278 * @param array $existsByOffset array(int $cidOffset => bool $expectExists)
279 * @param int $generalOffset
281 public function assertContactsExistByOffset($existsByOffset, $generalOffset = 0) {
282 foreach ($existsByOffset as $offset => $expectExists) {
283 $this->assertTrue(isset($this->cids
[$generalOffset +
$offset]), "Find cid at offset($generalOffset + $offset)");
284 $cid = $this->cids
[$generalOffset +
$offset];
285 $this->assertTrue(is_numeric($cid));
286 $this->assertDBQuery($expectExists ?
1 : 0, 'SELECT count(*) FROM civicrm_contact WHERE id = %1', array(
287 1 => array($cid, 'Integer'),
288 ), "Check contact at offset($generalOffset + $offset)");
293 * Use SQL to INSERT a contact and assert success. Perform
294 * work within a transaction.
296 * @param string $nesting 'reuse-tx'|'nest-tx' how to construct transaction
297 * @param string $insert 'sql-insert'|'bao-create' how to add the example record
298 * @param string $outcome 'rollback'|'implicit-commit'|'explicit-commit' how to finish transaction
301 public function createContactWithTransaction($nesting, $insert, $outcome) {
302 if ($nesting != 'reuse-tx' && $nesting != 'nest-tx') {
303 throw new RuntimeException('Bad test data: reuse=' . $nesting);
305 if ($insert != 'sql-insert' && $insert != 'bao-create') {
306 throw new RuntimeException('Bad test data: insert=' . $insert);
308 if ($outcome != 'rollback' && $outcome != 'implicit-commit' && $outcome != 'explicit-commit') {
309 throw new RuntimeException('Bad test data: outcome=' . $outcome);
312 $tx = new CRM_Core_Transaction($nesting === 'nest-tx');
314 if ($insert == 'sql-insert') {
315 $r = CRM_Core_DAO
::executeQuery("INSERT INTO civicrm_contact(first_name,last_name) VALUES ('ff', 'll')");
316 $cid = mysql_insert_id();
317 } elseif ($insert == 'bao-create') {
319 'contact_type' => 'Individual',
320 'first_name' => 'FF',
323 $r = CRM_Contact_BAO_Contact
::create($params);
327 $this->cids
[] = $cid;
329 $this->assertContactsExist(array($cid), TRUE);
331 if ($outcome == 'rollback') {
333 } elseif ($outcome == 'explicit-commit') {
335 } // else: implicit-commit
341 * Perform a series of operations within smaller transactions
343 * @param string $nesting 'reuse-tx'|'nest-tx' how to construct transaction
344 * @param array $callbacks see createContactWithTransaction
345 * @param array $existsByOffset see assertContactsMix
346 * @param string $outcome 'rollback'|'implicit-commit'|'explicit-commit' how to finish transaction
349 public function runBatch($nesting, $callbacks, $existsByOffset, $outcome) {
350 if ($nesting != 'reuse-tx' && $nesting != 'nest-tx') {
351 throw new RuntimeException('Bad test data: nesting=' . $nesting);
353 if ($outcome != 'rollback' && $outcome != 'implicit-commit' && $outcome != 'explicit-commit') {
354 throw new RuntimeException('Bad test data: outcome=' . $nesting);
357 $tx = new CRM_Core_Transaction($nesting === 'reuse-tx');
359 $generalOffset = count($this->cids
);
360 foreach ($callbacks as $callback) {
361 list ($cbNesting, $cbInsert, $cbOutcome) = $callback;
362 $this->createContactWithTransaction($cbNesting, $cbInsert, $cbOutcome);
365 $this->assertContactsExistByOffset($existsByOffset, $generalOffset);
367 if ($outcome == 'rollback') {
369 } elseif ($outcome == 'explicit-commit') {
371 } // else: implicit-commit
374 public function _preCommit($arg1, $arg2) {
375 $this->callbackLog
[] = array('_preCommit', $arg1, $arg2);
378 public function _postCommit($arg1, $arg2) {
379 $this->callbackLog
[] = array('_postCommit', $arg1, $arg2);
382 public function _preRollback($arg1, $arg2) {
383 $this->callbackLog
[] = array('_preRollback', $arg1, $arg2);
386 public function _postRollback($arg1, $arg2) {
387 $this->callbackLog
[] = array('_postRollback', $arg1, $arg2);