summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAymeric Augustin <aymeric.augustin@m4x.org>2013-03-11 15:11:34 +0100
committerAymeric Augustin <aymeric.augustin@m4x.org>2013-03-11 15:11:34 +0100
commit14cddf51c5f001bb426ce7f7a83fdc52c8d8aee9 (patch)
tree7658f47dfaafe0dd09fe62fd5525e8f6e6a41ba7 /tests
parent9cec689e6a7e299b3416519ee075b2316ecc5a64 (diff)
parente654180ce2a11ef4c525497d6c40dc542e16806c (diff)
Merged branch 'database-level-autocommit'.
Fixed #2227: `atomic` supports nesting. Fixed #6623: `commit_manually` is deprecated and `atomic` doesn't suffer from this defect. Fixed #8320: the problem wasn't identified, but the legacy transaction management is deprecated. Fixed #10744: the problem wasn't identified, but the legacy transaction management is deprecated. Fixed #10813: since autocommit is enabled, it isn't necessary to rollback after errors any more. Fixed #13742: savepoints are now implemented for SQLite. Fixed #13870: transaction management in long running processes isn't a problem any more, and it's documented. Fixed #14970: while it digresses on transaction management, this ticket essentially asks for autocommit on PostgreSQL. Fixed #15694: `atomic` supports nesting. Fixed #17887: autocommit makes it impossible for a connection to stay "idle of transaction".
Diffstat (limited to 'tests')
-rw-r--r--tests/backends/tests.py19
-rw-r--r--tests/delete_regress/tests.py4
-rw-r--r--tests/fixtures_model_package/tests.py11
-rw-r--r--tests/fixtures_regress/tests.py7
-rw-r--r--tests/handlers/tests.py30
-rw-r--r--tests/handlers/urls.py9
-rw-r--r--tests/handlers/views.py17
-rw-r--r--tests/middleware/tests.py22
-rw-r--r--tests/requests/tests.py2
-rw-r--r--tests/select_for_update/tests.py3
-rw-r--r--tests/serializers/tests.py1
-rw-r--r--tests/transactions/models.py2
-rw-r--r--tests/transactions/tests.py314
-rw-r--r--tests/transactions_regress/tests.py95
14 files changed, 435 insertions, 101 deletions
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index 103a44684e..c7f09013d4 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -302,8 +302,8 @@ class PostgresNewConnectionTest(TestCase):
transaction is rolled back.
"""
@unittest.skipUnless(
- connection.vendor == 'postgresql' and connection.isolation_level > 0,
- "This test applies only to PostgreSQL without autocommit")
+ connection.vendor == 'postgresql',
+ "This test applies only to PostgreSQL")
def test_connect_and_rollback(self):
new_connections = ConnectionHandler(settings.DATABASES)
new_connection = new_connections[DEFAULT_DB_ALIAS]
@@ -522,7 +522,8 @@ class FkConstraintsTests(TransactionTestCase):
"""
When constraint checks are disabled, should be able to write bad data without IntegrityErrors.
"""
- with transaction.commit_manually():
+ transaction.set_autocommit(False)
+ try:
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
@@ -536,12 +537,15 @@ class FkConstraintsTests(TransactionTestCase):
self.fail("IntegrityError should not have occurred.")
finally:
transaction.rollback()
+ finally:
+ transaction.set_autocommit(True)
def test_disable_constraint_checks_context_manager(self):
"""
When constraint checks are disabled (using context manager), should be able to write bad data without IntegrityErrors.
"""
- with transaction.commit_manually():
+ transaction.set_autocommit(False)
+ try:
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
@@ -554,12 +558,15 @@ class FkConstraintsTests(TransactionTestCase):
self.fail("IntegrityError should not have occurred.")
finally:
transaction.rollback()
+ finally:
+ transaction.set_autocommit(True)
def test_check_constraints(self):
"""
Constraint checks should raise an IntegrityError when bad data is in the DB.
"""
- with transaction.commit_manually():
+ try:
+ transaction.set_autocommit(False)
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
@@ -572,6 +579,8 @@ class FkConstraintsTests(TransactionTestCase):
connection.check_constraints()
finally:
transaction.rollback()
+ finally:
+ transaction.set_autocommit(True)
class ThreadTests(TestCase):
diff --git a/tests/delete_regress/tests.py b/tests/delete_regress/tests.py
index 9fcc19ba71..e88c95e229 100644
--- a/tests/delete_regress/tests.py
+++ b/tests/delete_regress/tests.py
@@ -22,9 +22,7 @@ class DeleteLockingTest(TransactionTestCase):
self.conn2 = new_connections[DEFAULT_DB_ALIAS]
# Put both DB connections into managed transaction mode
transaction.enter_transaction_management()
- transaction.managed(True)
self.conn2.enter_transaction_management()
- self.conn2.managed(True)
def tearDown(self):
# Close down the second connection.
@@ -335,7 +333,7 @@ class Ticket19102Tests(TestCase):
).select_related('orgunit').delete()
self.assertFalse(Login.objects.filter(pk=self.l1.pk).exists())
self.assertTrue(Login.objects.filter(pk=self.l2.pk).exists())
-
+
@skipUnlessDBFeature("update_can_self_select")
def test_ticket_19102_defer(self):
with self.assertNumQueries(1):
diff --git a/tests/fixtures_model_package/tests.py b/tests/fixtures_model_package/tests.py
index d147fe68a7..c250f647ce 100644
--- a/tests/fixtures_model_package/tests.py
+++ b/tests/fixtures_model_package/tests.py
@@ -25,7 +25,8 @@ class SampleTestCase(TestCase):
class TestNoInitialDataLoading(TransactionTestCase):
def test_syncdb(self):
- with transaction.commit_manually():
+ transaction.set_autocommit(False)
+ try:
Book.objects.all().delete()
management.call_command(
@@ -35,6 +36,9 @@ class TestNoInitialDataLoading(TransactionTestCase):
)
self.assertQuerysetEqual(Book.objects.all(), [])
transaction.rollback()
+ finally:
+ transaction.set_autocommit(True)
+
def test_flush(self):
# Test presence of fixture (flush called by TransactionTestCase)
@@ -45,7 +49,8 @@ class TestNoInitialDataLoading(TransactionTestCase):
lambda a: a.name
)
- with transaction.commit_manually():
+ transaction.set_autocommit(False)
+ try:
management.call_command(
'flush',
verbosity=0,
@@ -55,6 +60,8 @@ class TestNoInitialDataLoading(TransactionTestCase):
)
self.assertQuerysetEqual(Book.objects.all(), [])
transaction.rollback()
+ finally:
+ transaction.set_autocommit(True)
class FixtureTestCase(TestCase):
diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py
index 61dc4460df..c76056b93a 100644
--- a/tests/fixtures_regress/tests.py
+++ b/tests/fixtures_regress/tests.py
@@ -684,5 +684,8 @@ class TestTicket11101(TransactionTestCase):
@skipUnlessDBFeature('supports_transactions')
def test_ticket_11101(self):
"""Test that fixtures can be rolled back (ticket #11101)."""
- ticket_11101 = transaction.commit_manually(self.ticket_11101)
- ticket_11101()
+ transaction.set_autocommit(False)
+ try:
+ self.ticket_11101()
+ finally:
+ transaction.set_autocommit(True)
diff --git a/tests/handlers/tests.py b/tests/handlers/tests.py
index 6eb9bd23fe..3680eecdd2 100644
--- a/tests/handlers/tests.py
+++ b/tests/handlers/tests.py
@@ -1,9 +1,8 @@
from django.core.handlers.wsgi import WSGIHandler
from django.core.signals import request_started, request_finished
-from django.db import close_old_connections
-from django.test import RequestFactory, TestCase
+from django.db import close_old_connections, connection
+from django.test import RequestFactory, TestCase, TransactionTestCase
from django.test.utils import override_settings
-from django.utils import six
class HandlerTests(TestCase):
@@ -37,6 +36,31 @@ class HandlerTests(TestCase):
self.assertEqual(response.status_code, 400)
+class TransactionsPerRequestTests(TransactionTestCase):
+ urls = 'handlers.urls'
+
+ def test_no_transaction(self):
+ response = self.client.get('/in_transaction/')
+ self.assertContains(response, 'False')
+
+ def test_auto_transaction(self):
+ old_atomic_requests = connection.settings_dict['ATOMIC_REQUESTS']
+ try:
+ connection.settings_dict['ATOMIC_REQUESTS'] = True
+ response = self.client.get('/in_transaction/')
+ finally:
+ connection.settings_dict['ATOMIC_REQUESTS'] = old_atomic_requests
+ self.assertContains(response, 'True')
+
+ def test_no_auto_transaction(self):
+ old_atomic_requests = connection.settings_dict['ATOMIC_REQUESTS']
+ try:
+ connection.settings_dict['ATOMIC_REQUESTS'] = True
+ response = self.client.get('/not_in_transaction/')
+ finally:
+ connection.settings_dict['ATOMIC_REQUESTS'] = old_atomic_requests
+ self.assertContains(response, 'False')
+
class SignalsTests(TestCase):
urls = 'handlers.urls'
diff --git a/tests/handlers/urls.py b/tests/handlers/urls.py
index 8570f04696..29858055ab 100644
--- a/tests/handlers/urls.py
+++ b/tests/handlers/urls.py
@@ -1,9 +1,12 @@
from __future__ import unicode_literals
from django.conf.urls import patterns, url
-from django.http import HttpResponse, StreamingHttpResponse
+
+from . import views
urlpatterns = patterns('',
- url(r'^regular/$', lambda request: HttpResponse(b"regular content")),
- url(r'^streaming/$', lambda request: StreamingHttpResponse([b"streaming", b" ", b"content"])),
+ url(r'^regular/$', views.regular),
+ url(r'^streaming/$', views.streaming),
+ url(r'^in_transaction/$', views.in_transaction),
+ url(r'^not_in_transaction/$', views.not_in_transaction),
)
diff --git a/tests/handlers/views.py b/tests/handlers/views.py
new file mode 100644
index 0000000000..22d9ea4c7d
--- /dev/null
+++ b/tests/handlers/views.py
@@ -0,0 +1,17 @@
+from __future__ import unicode_literals
+
+from django.db import connection
+from django.http import HttpResponse, StreamingHttpResponse
+
+def regular(request):
+ return HttpResponse(b"regular content")
+
+def streaming(request):
+ return StreamingHttpResponse([b"streaming", b" ", b"content"])
+
+def in_transaction(request):
+ return HttpResponse(str(connection.in_atomic_block))
+
+def not_in_transaction(request):
+ return HttpResponse(str(connection.in_atomic_block))
+not_in_transaction.transactions_per_request = False
diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py
index 4e5fd8ea6b..74b8ab9601 100644
--- a/tests/middleware/tests.py
+++ b/tests/middleware/tests.py
@@ -22,6 +22,9 @@ from django.test.utils import override_settings
from django.utils import six
from django.utils.encoding import force_str
from django.utils.six.moves import xrange
+from django.utils.unittest import expectedFailure
+
+from transactions.tests import IgnorePendingDeprecationWarningsMixin
from .models import Band
@@ -669,11 +672,12 @@ class ETagGZipMiddlewareTest(TestCase):
self.assertNotEqual(gzip_etag, nogzip_etag)
-class TransactionMiddlewareTest(TransactionTestCase):
+class TransactionMiddlewareTest(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
"""
Test the transaction middleware.
"""
def setUp(self):
+ super(TransactionMiddlewareTest, self).setUp()
self.request = HttpRequest()
self.request.META = {
'SERVER_NAME': 'testserver',
@@ -685,33 +689,22 @@ class TransactionMiddlewareTest(TransactionTestCase):
def tearDown(self):
transaction.abort()
+ super(TransactionMiddlewareTest, self).tearDown()
def test_request(self):
TransactionMiddleware().process_request(self.request)
- self.assertTrue(transaction.is_managed())
+ self.assertFalse(transaction.get_autocommit())
def test_managed_response(self):
transaction.enter_transaction_management()
- transaction.managed(True)
Band.objects.create(name='The Beatles')
self.assertTrue(transaction.is_dirty())
TransactionMiddleware().process_response(self.request, self.response)
self.assertFalse(transaction.is_dirty())
self.assertEqual(Band.objects.count(), 1)
- def test_unmanaged_response(self):
- transaction.enter_transaction_management()
- transaction.managed(False)
- self.assertEqual(Band.objects.count(), 0)
- TransactionMiddleware().process_response(self.request, self.response)
- self.assertFalse(transaction.is_managed())
- # The transaction middleware doesn't commit/rollback if management
- # has been disabled.
- self.assertTrue(transaction.is_dirty())
-
def test_exception(self):
transaction.enter_transaction_management()
- transaction.managed(True)
Band.objects.create(name='The Beatles')
self.assertTrue(transaction.is_dirty())
TransactionMiddleware().process_exception(self.request, None)
@@ -726,7 +719,6 @@ class TransactionMiddlewareTest(TransactionTestCase):
raise IntegrityError()
connections[DEFAULT_DB_ALIAS].commit = raise_exception
transaction.enter_transaction_management()
- transaction.managed(True)
Band.objects.create(name='The Beatles')
self.assertTrue(transaction.is_dirty())
with self.assertRaises(IntegrityError):
diff --git a/tests/requests/tests.py b/tests/requests/tests.py
index 4fdc17618b..2803d7995b 100644
--- a/tests/requests/tests.py
+++ b/tests/requests/tests.py
@@ -576,7 +576,6 @@ class DatabaseConnectionHandlingTests(TransactionTestCase):
# Make sure there is an open connection
connection.cursor()
connection.enter_transaction_management()
- connection.managed(True)
signals.request_finished.send(sender=response._handler_class)
self.assertEqual(len(connection.transaction_state), 0)
@@ -585,7 +584,6 @@ class DatabaseConnectionHandlingTests(TransactionTestCase):
connection.settings_dict['CONN_MAX_AGE'] = 0
connection.enter_transaction_management()
- connection.managed(True)
connection.set_dirty()
# Test that the rollback doesn't succeed (for example network failure
# could cause this).
diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py
index b9716bd797..c2fa22705a 100644
--- a/tests/select_for_update/tests.py
+++ b/tests/select_for_update/tests.py
@@ -25,7 +25,6 @@ class SelectForUpdateTests(TransactionTestCase):
def setUp(self):
transaction.enter_transaction_management()
- transaction.managed(True)
self.person = Person.objects.create(name='Reinhardt')
# We have to commit here so that code in run_select_for_update can
@@ -37,7 +36,6 @@ class SelectForUpdateTests(TransactionTestCase):
new_connections = ConnectionHandler(settings.DATABASES)
self.new_connection = new_connections[DEFAULT_DB_ALIAS]
self.new_connection.enter_transaction_management()
- self.new_connection.managed(True)
# We need to set settings.DEBUG to True so we can capture
# the output SQL to examine.
@@ -162,7 +160,6 @@ class SelectForUpdateTests(TransactionTestCase):
# We need to enter transaction management again, as this is done on
# per-thread basis
transaction.enter_transaction_management()
- transaction.managed(True)
people = list(
Person.objects.all().select_for_update(nowait=nowait)
)
diff --git a/tests/serializers/tests.py b/tests/serializers/tests.py
index 34d0f5f1b1..a96a1af748 100644
--- a/tests/serializers/tests.py
+++ b/tests/serializers/tests.py
@@ -268,7 +268,6 @@ class SerializersTransactionTestBase(object):
# within a transaction in order to test forward reference
# handling.
transaction.enter_transaction_management()
- transaction.managed(True)
objs = serializers.deserialize(self.serializer_name, self.fwd_ref_str)
with connection.constraint_checks_disabled():
for obj in objs:
diff --git a/tests/transactions/models.py b/tests/transactions/models.py
index 0f8d6b16ec..6c2bcfd23f 100644
--- a/tests/transactions/models.py
+++ b/tests/transactions/models.py
@@ -22,4 +22,4 @@ class Reporter(models.Model):
ordering = ('first_name', 'last_name')
def __str__(self):
- return "%s %s" % (self.first_name, self.last_name)
+ return ("%s %s" % (self.first_name, self.last_name)).strip()
diff --git a/tests/transactions/tests.py b/tests/transactions/tests.py
index a1edf53fcb..e5a608e583 100644
--- a/tests/transactions/tests.py
+++ b/tests/transactions/tests.py
@@ -1,12 +1,320 @@
from __future__ import absolute_import
+import sys
+import warnings
+
from django.db import connection, transaction, IntegrityError
from django.test import TransactionTestCase, skipUnlessDBFeature
+from django.utils import six
+from django.utils.unittest import skipUnless
from .models import Reporter
-class TransactionTests(TransactionTestCase):
+@skipUnless(connection.features.uses_savepoints,
+ "'atomic' requires transactions and savepoints.")
+class AtomicTests(TransactionTestCase):
+ """
+ Tests for the atomic decorator and context manager.
+
+ The tests make assertions on internal attributes because there isn't a
+ robust way to ask the database for its current transaction state.
+
+ Since the decorator syntax is converted into a context manager (see the
+ implementation), there are only a few basic tests with the decorator
+ syntax and the bulk of the tests use the context manager syntax.
+ """
+
+ def test_decorator_syntax_commit(self):
+ @transaction.atomic
+ def make_reporter():
+ Reporter.objects.create(first_name="Tintin")
+ make_reporter()
+ self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
+
+ def test_decorator_syntax_rollback(self):
+ @transaction.atomic
+ def make_reporter():
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ make_reporter()
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+ def test_alternate_decorator_syntax_commit(self):
+ @transaction.atomic()
+ def make_reporter():
+ Reporter.objects.create(first_name="Tintin")
+ make_reporter()
+ self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
+
+ def test_alternate_decorator_syntax_rollback(self):
+ @transaction.atomic()
+ def make_reporter():
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ make_reporter()
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+ def test_commit(self):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Tintin")
+ self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
+
+ def test_rollback(self):
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+ def test_nested_commit_commit(self):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Tintin")
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Archibald", last_name="Haddock")
+ self.assertQuerysetEqual(Reporter.objects.all(),
+ ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>'])
+
+ def test_nested_commit_rollback(self):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Tintin")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
+
+ def test_nested_rollback_commit(self):
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic():
+ Reporter.objects.create(last_name="Tintin")
+ with transaction.atomic():
+ Reporter.objects.create(last_name="Haddock")
+ raise Exception("Oops, that's his first name")
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+ def test_nested_rollback_rollback(self):
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic():
+ Reporter.objects.create(last_name="Tintin")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ raise Exception("Oops, that's his first name")
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+ def test_merged_commit_commit(self):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Tintin")
+ with transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Archibald", last_name="Haddock")
+ self.assertQuerysetEqual(Reporter.objects.all(),
+ ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>'])
+
+ def test_merged_commit_rollback(self):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Tintin")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ # Writes in the outer block are rolled back too.
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+ def test_merged_rollback_commit(self):
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic():
+ Reporter.objects.create(last_name="Tintin")
+ with transaction.atomic(savepoint=False):
+ Reporter.objects.create(last_name="Haddock")
+ raise Exception("Oops, that's his first name")
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+ def test_merged_rollback_rollback(self):
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic():
+ Reporter.objects.create(last_name="Tintin")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ raise Exception("Oops, that's his first name")
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+ def test_reuse_commit_commit(self):
+ atomic = transaction.atomic()
+ with atomic:
+ Reporter.objects.create(first_name="Tintin")
+ with atomic:
+ Reporter.objects.create(first_name="Archibald", last_name="Haddock")
+ self.assertQuerysetEqual(Reporter.objects.all(),
+ ['<Reporter: Archibald Haddock>', '<Reporter: Tintin>'])
+
+ def test_reuse_commit_rollback(self):
+ atomic = transaction.atomic()
+ with atomic:
+ Reporter.objects.create(first_name="Tintin")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with atomic:
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
+
+ def test_reuse_rollback_commit(self):
+ atomic = transaction.atomic()
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with atomic:
+ Reporter.objects.create(last_name="Tintin")
+ with atomic:
+ Reporter.objects.create(last_name="Haddock")
+ raise Exception("Oops, that's his first name")
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+ def test_reuse_rollback_rollback(self):
+ atomic = transaction.atomic()
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with atomic:
+ Reporter.objects.create(last_name="Tintin")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with atomic:
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ raise Exception("Oops, that's his first name")
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+
+class AtomicInsideTransactionTests(AtomicTests):
+ """All basic tests for atomic should also pass within an existing transaction."""
+
+ def setUp(self):
+ self.atomic = transaction.atomic()
+ self.atomic.__enter__()
+
+ def tearDown(self):
+ self.atomic.__exit__(*sys.exc_info())
+
+
+class AtomicInsideLegacyTransactionManagementTests(AtomicTests):
+
+ def setUp(self):
+ transaction.enter_transaction_management()
+
+ def tearDown(self):
+ # The tests access the database after exercising 'atomic', making the
+ # connection dirty; a rollback is required to make it clean.
+ transaction.rollback()
+ transaction.leave_transaction_management()
+
+
+@skipUnless(connection.features.uses_savepoints,
+ "'atomic' requires transactions and savepoints.")
+class AtomicMergeTests(TransactionTestCase):
+ """Test merging transactions with savepoint=False."""
+
+ def test_merged_outer_rollback(self):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Tintin")
+ with transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Archibald", last_name="Haddock")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Tournesol")
+ raise Exception("Oops, that's his last name")
+ # It wasn't possible to roll back
+ self.assertEqual(Reporter.objects.count(), 3)
+ # It wasn't possible to roll back
+ self.assertEqual(Reporter.objects.count(), 3)
+ # The outer block must roll back
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+ def test_merged_inner_savepoint_rollback(self):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Tintin")
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Archibald", last_name="Haddock")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Tournesol")
+ raise Exception("Oops, that's his last name")
+ # It wasn't possible to roll back
+ self.assertEqual(Reporter.objects.count(), 3)
+ # The first block with a savepoint must roll back
+ self.assertEqual(Reporter.objects.count(), 1)
+ self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
+
+ def test_merged_outer_rollback_after_inner_failure_and_inner_success(self):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Tintin")
+ # Inner block without a savepoint fails
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ # It wasn't possible to roll back
+ self.assertEqual(Reporter.objects.count(), 2)
+ # Inner block with a savepoint succeeds
+ with transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Archibald", last_name="Haddock")
+ # It still wasn't possible to roll back
+ self.assertEqual(Reporter.objects.count(), 3)
+ # The outer block must rollback
+ self.assertQuerysetEqual(Reporter.objects.all(), [])
+
+
+@skipUnless(connection.features.uses_savepoints,
+ "'atomic' requires transactions and savepoints.")
+class AtomicErrorsTests(TransactionTestCase):
+
+ def test_atomic_requires_autocommit(self):
+ transaction.set_autocommit(False)
+ try:
+ with self.assertRaises(transaction.TransactionManagementError):
+ with transaction.atomic():
+ pass
+ finally:
+ transaction.set_autocommit(True)
+
+ def test_atomic_prevents_disabling_autocommit(self):
+ autocommit = transaction.get_autocommit()
+ with transaction.atomic():
+ with self.assertRaises(transaction.TransactionManagementError):
+ transaction.set_autocommit(not autocommit)
+ # Make sure autocommit wasn't changed.
+ self.assertEqual(connection.autocommit, autocommit)
+
+ def test_atomic_prevents_calling_transaction_methods(self):
+ with transaction.atomic():
+ with self.assertRaises(transaction.TransactionManagementError):
+ transaction.commit()
+ with self.assertRaises(transaction.TransactionManagementError):
+ transaction.rollback()
+
+ def test_atomic_prevents_calling_transaction_management_methods(self):
+ with transaction.atomic():
+ with self.assertRaises(transaction.TransactionManagementError):
+ transaction.enter_transaction_management()
+ with self.assertRaises(transaction.TransactionManagementError):
+ transaction.leave_transaction_management()
+
+
+class IgnorePendingDeprecationWarningsMixin(object):
+
+ def setUp(self):
+ super(IgnorePendingDeprecationWarningsMixin, self).setUp()
+ self.catch_warnings = warnings.catch_warnings()
+ self.catch_warnings.__enter__()
+ warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+
+ def tearDown(self):
+ self.catch_warnings.__exit__(*sys.exc_info())
+ super(IgnorePendingDeprecationWarningsMixin, self).tearDown()
+
+
+class TransactionTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
+
def create_a_reporter_then_fail(self, first, last):
a = Reporter(first_name=first, last_name=last)
a.save()
@@ -161,7 +469,7 @@ class TransactionTests(TransactionTestCase):
)
-class TransactionRollbackTests(TransactionTestCase):
+class TransactionRollbackTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
def execute_bad_sql(self):
cursor = connection.cursor()
cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');")
@@ -178,7 +486,7 @@ class TransactionRollbackTests(TransactionTestCase):
self.assertRaises(IntegrityError, execute_bad_sql)
transaction.rollback()
-class TransactionContextManagerTests(TransactionTestCase):
+class TransactionContextManagerTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
def create_reporter_and_fail(self):
Reporter.objects.create(first_name="Bob", last_name="Holtzman")
raise Exception
diff --git a/tests/transactions_regress/tests.py b/tests/transactions_regress/tests.py
index 6ba04892cd..142c09d3cf 100644
--- a/tests/transactions_regress/tests.py
+++ b/tests/transactions_regress/tests.py
@@ -6,10 +6,12 @@ from django.test import TransactionTestCase, skipUnlessDBFeature
from django.test.utils import override_settings
from django.utils.unittest import skipIf, skipUnless
+from transactions.tests import IgnorePendingDeprecationWarningsMixin
+
from .models import Mod, M2mA, M2mB
-class TestTransactionClosing(TransactionTestCase):
+class TestTransactionClosing(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
"""
Tests to make sure that transactions are properly closed
when they should be, and aren't left pending after operations
@@ -166,17 +168,13 @@ class TestTransactionClosing(TransactionTestCase):
(connection.settings_dict['NAME'] == ':memory:' or
not connection.settings_dict['NAME']),
'Test uses multiple connections, but in-memory sqlite does not support this')
-class TestNewConnection(TransactionTestCase):
+class TestNewConnection(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
"""
Check that new connections don't have special behaviour.
"""
def setUp(self):
self._old_backend = connections[DEFAULT_DB_ALIAS]
settings = self._old_backend.settings_dict.copy()
- opts = settings['OPTIONS'].copy()
- if 'autocommit' in opts:
- opts['autocommit'] = False
- settings['OPTIONS'] = opts
new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS)
connections[DEFAULT_DB_ALIAS] = new_backend
@@ -193,16 +191,20 @@ class TestNewConnection(TransactionTestCase):
"""
Users are allowed to commit and rollback connections.
"""
- # The starting value is False, not None.
- self.assertIs(connection._dirty, False)
- list(Mod.objects.all())
- self.assertTrue(connection.is_dirty())
- connection.commit()
- self.assertFalse(connection.is_dirty())
- list(Mod.objects.all())
- self.assertTrue(connection.is_dirty())
- connection.rollback()
- self.assertFalse(connection.is_dirty())
+ connection.set_autocommit(False)
+ try:
+ # The starting value is False, not None.
+ self.assertIs(connection._dirty, False)
+ list(Mod.objects.all())
+ self.assertTrue(connection.is_dirty())
+ connection.commit()
+ self.assertFalse(connection.is_dirty())
+ list(Mod.objects.all())
+ self.assertTrue(connection.is_dirty())
+ connection.rollback()
+ self.assertFalse(connection.is_dirty())
+ finally:
+ connection.set_autocommit(True)
def test_enter_exit_management(self):
orig_dirty = connection._dirty
@@ -210,39 +212,10 @@ class TestNewConnection(TransactionTestCase):
connection.leave_transaction_management()
self.assertEqual(orig_dirty, connection._dirty)
- def test_commit_unless_managed(self):
- cursor = connection.cursor()
- cursor.execute("INSERT into transactions_regress_mod (fld) values (2)")
- connection.commit_unless_managed()
- self.assertFalse(connection.is_dirty())
- self.assertEqual(len(Mod.objects.all()), 1)
- self.assertTrue(connection.is_dirty())
- connection.commit_unless_managed()
- self.assertFalse(connection.is_dirty())
-
- def test_commit_unless_managed_in_managed(self):
- cursor = connection.cursor()
- connection.enter_transaction_management()
- transaction.managed(True)
- cursor.execute("INSERT into transactions_regress_mod (fld) values (2)")
- connection.commit_unless_managed()
- self.assertTrue(connection.is_dirty())
- connection.rollback()
- self.assertFalse(connection.is_dirty())
- self.assertEqual(len(Mod.objects.all()), 0)
- connection.commit()
- connection.leave_transaction_management()
- self.assertFalse(connection.is_dirty())
- self.assertEqual(len(Mod.objects.all()), 0)
- self.assertTrue(connection.is_dirty())
- connection.commit_unless_managed()
- self.assertFalse(connection.is_dirty())
- self.assertEqual(len(Mod.objects.all()), 0)
-
@skipUnless(connection.vendor == 'postgresql',
"This test only valid for PostgreSQL")
-class TestPostgresAutocommitAndIsolation(TransactionTestCase):
+class TestPostgresAutocommitAndIsolation(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
"""
Tests to make sure psycopg2's autocommit mode and isolation level
is restored after entering and leaving transaction management.
@@ -261,7 +234,6 @@ class TestPostgresAutocommitAndIsolation(TransactionTestCase):
self._old_backend = connections[DEFAULT_DB_ALIAS]
settings = self._old_backend.settings_dict.copy()
opts = settings['OPTIONS'].copy()
- opts['autocommit'] = True
opts['isolation_level'] = ISOLATION_LEVEL_SERIALIZABLE
settings['OPTIONS'] = opts
new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS)
@@ -275,40 +247,42 @@ class TestPostgresAutocommitAndIsolation(TransactionTestCase):
connections[DEFAULT_DB_ALIAS] = self._old_backend
def test_initial_autocommit_state(self):
- self.assertTrue(connection.features.uses_autocommit)
- self.assertEqual(connection.isolation_level, self._autocommit)
+ # Autocommit is activated when the connection is created.
+ connection.cursor().close()
+ self.assertTrue(connection.autocommit)
def test_transaction_management(self):
transaction.enter_transaction_management()
- transaction.managed(True)
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
transaction.leave_transaction_management()
- self.assertEqual(connection.isolation_level, self._autocommit)
+ self.assertTrue(connection.autocommit)
def test_transaction_stacking(self):
transaction.enter_transaction_management()
- transaction.managed(True)
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
transaction.enter_transaction_management()
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
transaction.leave_transaction_management()
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
transaction.leave_transaction_management()
- self.assertEqual(connection.isolation_level, self._autocommit)
+ self.assertTrue(connection.autocommit)
def test_enter_autocommit(self):
transaction.enter_transaction_management()
- transaction.managed(True)
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
list(Mod.objects.all())
self.assertTrue(transaction.is_dirty())
# Enter autocommit mode again.
transaction.enter_transaction_management(False)
- transaction.managed(False)
self.assertFalse(transaction.is_dirty())
self.assertEqual(
connection.connection.get_transaction_status(),
@@ -316,12 +290,13 @@ class TestPostgresAutocommitAndIsolation(TransactionTestCase):
list(Mod.objects.all())
self.assertFalse(transaction.is_dirty())
transaction.leave_transaction_management()
+ self.assertFalse(connection.autocommit)
self.assertEqual(connection.isolation_level, self._serializable)
transaction.leave_transaction_management()
- self.assertEqual(connection.isolation_level, self._autocommit)
+ self.assertTrue(connection.autocommit)
-class TestManyToManyAddTransaction(TransactionTestCase):
+class TestManyToManyAddTransaction(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
def test_manyrelated_add_commit(self):
"Test for https://code.djangoproject.com/ticket/16818"
a = M2mA.objects.create()
@@ -336,8 +311,10 @@ class TestManyToManyAddTransaction(TransactionTestCase):
self.assertEqual(a.others.count(), 1)
-class SavepointTest(TransactionTestCase):
+class SavepointTest(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
+ @skipIf(connection.vendor == 'sqlite',
+ "SQLite doesn't support savepoints in managed mode")
@skipUnlessDBFeature('uses_savepoints')
def test_savepoint_commit(self):
@commit_manually
@@ -353,6 +330,8 @@ class SavepointTest(TransactionTestCase):
work()
+ @skipIf(connection.vendor == 'sqlite',
+ "SQLite doesn't support savepoints in managed mode")
@skipIf(connection.vendor == 'mysql' and
connection.features._mysql_storage_engine == 'MyISAM',
"MyISAM MySQL storage engine doesn't support savepoints")