def test_commit_on_success_nesting(self): """ Test that commit_on_success raises an error if it is nested inside atomic or if the isolation level is changed when it is nested inside another commit_on_success. """ # pylint: disable=not-callable if connection.vendor != 'mysql': raise unittest.SkipTest('Only works on MySQL.') def do_nothing(): """Just return.""" return commit_on_success(read_committed=True)(do_nothing)() with self.assertRaisesRegexp( TransactionManagementError, 'Cannot change isolation level when nested.'): with commit_on_success(): commit_on_success(read_committed=True)(do_nothing)() with self.assertRaisesRegexp(TransactionManagementError, 'Cannot be inside an atomic block.'): with atomic(): commit_on_success(read_committed=True)(do_nothing)()
def test_commit_on_success_nesting(self): """ Test that commit_on_success raises an error if it is nested inside atomic or if the isolation level is changed when it is nested inside another commit_on_success. """ # pylint: disable=not-callable if connection.vendor != 'mysql': raise unittest.SkipTest('Only works on MySQL.') commit_on_success(read_committed=True)(do_nothing)() with self.assertRaisesRegexp(TransactionManagementError, 'Cannot change isolation level when nested.'): with commit_on_success(): commit_on_success(read_committed=True)(do_nothing)() with self.assertRaisesRegexp(TransactionManagementError, 'Cannot be inside an atomic block.'): with atomic(): commit_on_success(read_committed=True)(do_nothing)()
class TransactionManagersTestCase(TransactionTestCase): """ Tests commit_on_success and outer_atomic. Note: This TestCase only works with MySQL. To test do: "./manage.py lms --settings=test_with_mysql test util.tests.test_db" """ DECORATORS = { 'outer_atomic': outer_atomic(), 'outer_atomic_read_committed': outer_atomic(read_committed=True), 'commit_on_success': commit_on_success(), 'commit_on_success_read_committed': commit_on_success(read_committed=True), } @ddt.data( ('outer_atomic', IntegrityError, None, True), ('outer_atomic_read_committed', type(None), False, True), ('commit_on_success', IntegrityError, None, True), ('commit_on_success_read_committed', type(None), False, True), ) @ddt.unpack def test_concurrent_requests(self, transaction_decorator_name, exception_class, created_in_1, created_in_2): """ Test that when isolation level is set to READ COMMITTED get_or_create() for the same row in concurrent requests does not raise an IntegrityError. """ transaction_decorator = self.DECORATORS[transaction_decorator_name] if connection.vendor != 'mysql': raise unittest.SkipTest('Only works on MySQL.') class RequestThread(threading.Thread): """ A thread which runs a dummy view.""" def __init__(self, delay, **kwargs): super(RequestThread, self).__init__(**kwargs) self.delay = delay self.status = {} @transaction_decorator def run(self): """A dummy view.""" try: try: User.objects.get(username='******', email='*****@*****.**') except User.DoesNotExist: pass else: raise AssertionError( 'Did not raise User.DoesNotExist.') if self.delay > 0: time.sleep(self.delay) __, created = User.objects.get_or_create( username='******', email='*****@*****.**') except Exception as exception: # pylint: disable=broad-except self.status['exception'] = exception else: self.status['created'] = created thread1 = RequestThread(delay=1) thread2 = RequestThread(delay=0) thread1.start() thread2.start() thread2.join() thread1.join() self.assertIsInstance(thread1.status.get('exception'), exception_class) self.assertEqual(thread1.status.get('created'), created_in_1) self.assertIsNone(thread2.status.get('exception')) self.assertEqual(thread2.status.get('created'), created_in_2) def test_outer_atomic_nesting(self): """ Test that outer_atomic raises an error if it is nested inside another atomic. """ if connection.vendor != 'mysql': raise unittest.SkipTest('Only works on MySQL.') outer_atomic()(do_nothing)() with atomic(): atomic()(do_nothing)() with outer_atomic(): atomic()(do_nothing)() with self.assertRaisesRegexp(TransactionManagementError, 'Cannot be inside an atomic block.'): with atomic(): outer_atomic()(do_nothing)() with self.assertRaisesRegexp(TransactionManagementError, 'Cannot be inside an atomic block.'): with outer_atomic(): outer_atomic()(do_nothing)() def test_commit_on_success_nesting(self): """ Test that commit_on_success raises an error if it is nested inside atomic or if the isolation level is changed when it is nested inside another commit_on_success. """ # pylint: disable=not-callable if connection.vendor != 'mysql': raise unittest.SkipTest('Only works on MySQL.') commit_on_success(read_committed=True)(do_nothing)() with self.assertRaisesRegexp( TransactionManagementError, 'Cannot change isolation level when nested.'): with commit_on_success(): commit_on_success(read_committed=True)(do_nothing)() with self.assertRaisesRegexp(TransactionManagementError, 'Cannot be inside an atomic block.'): with atomic(): commit_on_success(read_committed=True)(do_nothing)() def test_named_outer_atomic_nesting(self): """ Test that a named outer_atomic raises an error only if nested in enable_named_outer_atomic and inside another atomic. """ if connection.vendor != 'mysql': raise unittest.SkipTest('Only works on MySQL.') outer_atomic(name='abc')(do_nothing)() with atomic(): outer_atomic(name='abc')(do_nothing)() with enable_named_outer_atomic('abc'): outer_atomic(name='abc')(do_nothing)() # Not nested. with atomic(): outer_atomic(name='pqr')(do_nothing)() # Not enabled. with self.assertRaisesRegexp(TransactionManagementError, 'Cannot be inside an atomic block.'): with atomic(): outer_atomic(name='abc')(do_nothing)() with enable_named_outer_atomic('abc', 'def'): outer_atomic(name='def')(do_nothing)() # Not nested. with atomic(): outer_atomic(name='pqr')(do_nothing)() # Not enabled. with self.assertRaisesRegexp(TransactionManagementError, 'Cannot be inside an atomic block.'): with atomic(): outer_atomic(name='def')(do_nothing)() with self.assertRaisesRegexp(TransactionManagementError, 'Cannot be inside an atomic block.'): with outer_atomic(): outer_atomic(name='def')(do_nothing)() with self.assertRaisesRegexp(TransactionManagementError, 'Cannot be inside an atomic block.'): with atomic(): outer_atomic(name='abc')(do_nothing)() with self.assertRaisesRegexp(TransactionManagementError, 'Cannot be inside an atomic block.'): with outer_atomic(): outer_atomic(name='abc')(do_nothing)()