summaryrefslogtreecommitdiff
path: root/modules/simpletest/tests/database_test.test
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2009-01-08 09:39:48 +0000
committerDries Buytaert <dries@buytaert.net>2009-01-08 09:39:48 +0000
commita7b4bdef1d053bfd00342b5250b98153c7a6be5e (patch)
tree99c327cf7a10a84d0d01e53623de716e5398892b /modules/simpletest/tests/database_test.test
parent9a32ca468a320bec9769ed3c29c50b5a1f4459b1 (diff)
downloadbrdo-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.test224
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());
+ }
+ }
+}