diff options
author | Dries Buytaert <dries@buytaert.net> | 2009-01-08 09:39:48 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2009-01-08 09:39:48 +0000 |
commit | a7b4bdef1d053bfd00342b5250b98153c7a6be5e (patch) | |
tree | 99c327cf7a10a84d0d01e53623de716e5398892b /modules/simpletest/tests/database_test.test | |
parent | 9a32ca468a320bec9769ed3c29c50b5a1f4459b1 (diff) | |
download | brdo-a7b4bdef1d053bfd00342b5250b98153c7a6be5e.tar.gz brdo-a7b4bdef1d053bfd00342b5250b98153c7a6be5e.tar.bz2 |
- Patch #301049 by David Strauss, Josh Waihi, Crell, et al: transaction nesting was not tracked by connection, better documentation, and better tests.
Diffstat (limited to 'modules/simpletest/tests/database_test.test')
-rw-r--r-- | modules/simpletest/tests/database_test.test | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test index 9cbec229a..222aa3e10 100644 --- a/modules/simpletest/tests/database_test.test +++ b/modules/simpletest/tests/database_test.test @@ -2113,3 +2113,227 @@ class DatabaseQueryTestCase extends DatabaseTestCase { $this->assertEqual(count($names), 3, t('Correct number of names returned')); } } + +/** + * Test transaction support, particularly nesting. + * + * We test nesting by having two transaction layers, an outer and inner. The + * outer layer encapsulates the inner layer. Our transaction nesting abstraction + * should allow the outer layer function to call any function it wants, + * especially the inner layer that starts its own transaction, and be + * confident that, when the function it calls returns, its own transaction + * is still "alive." + * + * Call structure: + * transactionOuterLayer() + * Start transaction + * transactionInnerLayer() + * Start transaction (does nothing in database) + * [Maybe decide to roll back] + * Do more stuff + * Should still be in transaction A + * + */ +class DatabaseTransactionTestCase extends DatabaseTestCase { + + function getInfo() { + return array( + 'name' => t('Transaction tests'), + 'description' => t('Test the transaction abstraction system.'), + 'group' => t('Database'), + ); + } + + /** + * Helper method for transaction unit test. This "outer layer" transaction + * starts and then encapsulates the "inner layer" transaction. This nesting + * is used to evaluate whether the the database transaction API properly + * supports nesting. By "properly supports," we mean the outer transaction + * continues to exist regardless of what functions are called and whether + * those functions start their own transactions. + * + * In contrast, a typical database would commit the outer transaction, start + * a new transaction for the inner layer, commit the inner layer transaction, + * and then be confused when the outer layer transaction tries to commit its + * transaction (which was already committed when the inner transaction + * started). + * + * @param $suffix + * Suffix to add to field values to differentiate tests. + * @param $rollback + * Whether or not to try rolling back the transaction when we're done. + */ + protected function transactionOuterLayer($suffix, $rollback = FALSE) { + $connection = Database::getActiveConnection(); + $txn = db_transaction(); + + // Insert a single row into the testing table. + db_insert('test') + ->fields(array( + 'name' => 'David' . $suffix, + 'age' => '24', + )) + ->execute(); + + $this->assertTrue($connection->inTransaction(), t('In transaction before calling nested transaction.')); + + // We're already in a transaction, but we call ->transactionInnerLayer + // to nest another transaction inside the current one. + $this->transactionInnerLayer($suffix, $rollback); + + $this->assertTrue($connection->inTransaction(), t('In transaction after calling nested transaction.')); + } + + /** + * Helper method for transaction unit tests. This "inner layer" transaction + * is either used alone or nested inside of the "outer layer" transaction. + * + * @param $suffix + * Suffix to add to field values to differentiate tests. + * @param $rollback + * Whether or not to try rolling back the transaction when we're done. + */ + protected function transactionInnerLayer($suffix, $rollback = FALSE) { + $connection = Database::getActiveConnection(); + + // Start a transaction. If we're being called from ->transactionOuterLayer, + // then we're already in a transaction. Normally, that would make starting + // a transaction here dangerous, but the database API handles this problem + // for us by tracking the nesting and avoiding the danger. + $txn = db_transaction(); + + // Insert a single row into the testing table. + db_insert('test') + ->fields(array( + 'name' => 'Daniel' . $suffix, + 'age' => '19', + )) + ->execute(); + + $this->assertTrue($connection->inTransaction(), t('In transaction inside nested transaction.')); + + if ($rollback) { + // Roll back the transaction, if requested. + // This rollback should propagate to the the outer transaction, if present. + $connection->rollBack(); + $this->assertTrue($connection->willRollBack(), t('Transaction is scheduled to roll back after calling rollBack().')); + } + } + + /** + * Test that a database that claims to support transactions will return a transaction object. + * + * If the active connection does not support transactions, this test does nothing. + */ + function testTransactionsSupported() { + try { + $connection = Database::getActiveConnection(); + if ($connection->supportsTransactions()) { + + // Start a "required" transaction. This should fail if we do + // this on a database that does not actually support transactions. + $txn = db_transaction(TRUE); + } + $this->pass('Transaction started successfully.'); + } + catch (TransactionsNotSupportedException $e) { + $this->fail("Exception thrown when it shouldn't have been."); + } + } + + /** + * Test that a database that doesn't support transactions fails correctly. + * + * If the active connection supports transactions, this test does nothing. + */ + function testTransactionsNotSupported() { + try { + $connection = Database::getActiveConnection(); + if (!$connection->supportsTransactions()) { + + // Start a "required" transaction. This should fail if we do this + // on a database that does not actually support transactions, and + // the current database does claim to NOT support transactions. + $txn = db_transaction(TRUE); + } + $this->fail('No transaction failure registered.'); + } + catch (TransactionsNotSupportedException $e) { + $this->pass('Exception thrown for unsupported transactions.'); + } + } + + /** + * Test transaction rollback on a database that supports transactions. + * + * If the active connection does not support transactions, this test does nothing. + */ + function testTransactionRollBackSupported() { + // This test won't work right if transactions are not supported. + if (!Database::getActiveConnection()->supportsTransactions()) { + return; + } + try { + // Create two nested transactions. Roll back from the inner one. + $this->transactionOuterLayer('B', TRUE); + + // Neither of the rows we inserted in the two transaction layers + // should be present in the tables post-rollback. + $saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DavidB'))->fetchField(); + $this->assertNotIdentical($saved_age, '24', t('Cannot retrieve DavidB row after commit.')); + $saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DanielB'))->fetchField(); + $this->assertNotIdentical($saved_age, '19', t('Cannot retrieve DanielB row after commit.')); + } + catch(Exception $e) { + $this->fail($e->getMessage()); + } + } + + /** + * Test transaction rollback on a database that does not support transactions. + * + * If the active driver supports transactions, this test does nothing. + */ + function testTransactionRollBackNotSupported() { + // This test won't work right if transactions are supported. + if (Database::getActiveConnection()->supportsTransactions()) { + return; + } + try { + // Create two nested transactions. Attempt to roll back from the inner one. + $this->transactionOuterLayer('B', TRUE); + + // Because our current database claims to not support transactions, + // the inserted rows should be present despite the attempt to roll back. + $saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DavidB'))->fetchField(); + $this->assertIdentical($saved_age, '24', t('DavidB not rolled back, since transactions are not supported.')); + $saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DanielB'))->fetchField(); + $this->assertIdentical($saved_age, '19', t('DanielB not rolled back, since transactions are not supported.')); + } + catch(Exception $e) { + $this->fail($e->getMessage()); + } + } + + /** + * Test committed transaction. + * + * The behavior of this test should be identical for connections that support + * transactions and those that do not. + */ + function testCommittedTransaction() { + try { + // Create two nested transactions. The changes should be committed. + $this->transactionOuterLayer('A'); + + // Because we committed, both of the inserted rows should be present. + $saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DavidA'))->fetchField(); + $this->assertIdentical($saved_age, '24', t('Can retrieve DavidA row after commit.')); + $saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DanielA'))->fetchField(); + $this->assertIdentical($saved_age, '19', t('Can retrieve DanielA row after commit.')); + } + catch(Exception $e) { + $this->fail($e->getMessage()); + } + } +} |