Steve Constable
Steve Constable

Reputation: 41

How do I fix IntegrityErrors during the Django TestCase teardown?

I'm porting a large webapp from Python 2.7 to Python 3.6. I have managed the database migrations and converted the code using 2to3, but I'm having problems running my testing suite. For a large number of tests, I get errors like so:

  62 self = <django.db.backends.utils.CursorWrapper object at 0x7fc7d0abbeb8>
  63 sql = 'SET CONSTRAINTS ALL IMMEDIATE', params = None
  64 
  65     def execute(self, sql, params=None):
  66         self.db.validate_no_broken_transaction()
  67         with self.db.wrap_database_errors:
  68             if params is None:
  69 >               return self.cursor.execute(sql)
  70 E               psycopg2.IntegrityError: insert or update on table "probex_historicalproject" violates forei
     gn key constraint "probex_historicalpro_history_user_id_88371c5c_fk_auth_user"
  71 E               DETAIL:  Key (history_user_id)=(303) is not present in table "auth_user".
  72 
  73 ../../venv3/lib/python3.6/site-packages/django/db/backends/utils.py:62: IntegrityError
  74 
  75 The above exception was the direct cause of the following exception:
  76 
  77 self = <django.test.testcases.TestCase testMethod=__init__>
  78 
  79     def _post_teardown(self):
  80         """Performs any post-test things. This includes:
  81 
  82             * Flushing the contents of the database, to leave a clean slate. If
  83               the class has an 'available_apps' attribute, post_migrate isn't fired.
  84             * Force-closing the connection, so the next test gets a clean cursor.
  85             """
  86         try:
  87 >           self._fixture_teardown()
  88 
  89 ../../venv3/lib/python3.6/site-packages/django/test/testcases.py:925:
  90 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
  91 ../../venv3/lib/python3.6/site-packages/django/test/testcases.py:1081: in _fixture_teardown
  92     connections[db_name].check_constraints()
  93 ../../venv3/lib/python3.6/site-packages/django/db/backends/postgresql/base.py:243: in check_constraints
  94     self.cursor().execute('SET CONSTRAINTS ALL IMMEDIATE')
  95 ../../venv3/lib/python3.6/site-packages/raven/contrib/django/client.py:127: in execute
  96     return real_execute(self, sql, params)
  97 ../../venv3/lib/python3.6/site-packages/django/db/backends/utils.py:64: in execute                          
  98     return self.cursor.execute(sql, params)                                                                 
  99 ../../venv3/lib/python3.6/site-packages/django/db/utils.py:94: in __exit__                                  
 100     six.reraise(dj_exc_type, dj_exc_value, traceback)                                                       
 101 ../../venv3/lib/python3.6/site-packages/django/utils/six.py:685: in reraise
 102     raise value.with_traceback(tb)
 103 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

I have tried running the tests on some of my coworker's machines, and some of the people have no trouble running the tests, but some have the same problem as myself.

Also notable: If I run an individual test file by itself (i.e. py.test /path/to/file) then this error does not occur. I'm thinking it must have something to do with the way database data is handled between tests.

Here's an example test that is failing:

  1 
  2 from django.utils import timezone
  3 from tests.tests_probex.tests_python.testing_utilities import LoggedInTestCase
  4 from tests.tests_probex.tests_python.factories import JobFactory
  5 from probex.models import Profile, Tenant, Job
  6 from probex.views.reports import build_tenant_report
  7 from django.contrib.auth.models import User
  8 import pytest
  9 
 10 pytestmark = pytest.mark.xfail(reason="Not working in Python 3.6")
 11                                                                                                                             
 12                                                                                                                             
 13 class TestTenantReport(LoggedInTestCase):                                                                                   
 14     def setUp(self):                                                                                                        
 15         super(TestTenantReport, self).setUp()                                                                               
 16         self.end_date = timezone.now()                                                                                      
 17         self.delta = timezone.timedelta(days=7)                                                                             
 18         self.start_date = self.end_date - self.delta                                                                        
 19                                                                                                                             
 20         self.date = timezone.now() - timezone.timedelta(days=3)                                                             
 21         self.job = JobFactory(absolute_num=1600, relative_num=1600, protocol=self.protocol, project=self.project, status='Co
    mplete', created=self.date, submitter_id=self.user.id)                                                                      
 22                                                                                                                             
 23     def test_returns_new_users(self):                                                                                       
 24         users = User.objects.all()                                                                                          
 25         for user in users:                                                                                                  
 26             user.date_joined = self.date                                                                                    
 27             p = Profile.objects.get(user_id=user.id)                                                                        
 28             tenant = Tenant.objects.get(id=p.tenant_id)                                                                     
 29             tenant.app_scientist = self.user                                                                                
 30             user.save()                                                                                                     
 31             tenant.save()                                                                                                   
 32                                                                                                                             
 33         report = build_tenant_report(self.user)                                                                             
 34         self.assertGreaterEqual(report['new_users'].count(), users.count()) 

The LoggedInTestCase is a subclass of django TestCase:

 15 class LoggedInTestCase(TestCase):
 16     def setUp(self):
 17         self.user = User.objects.create_user(username='[email protected]', email='[email protected]', password="secret123")
 18         self.user.user_permissions.add(Permission.objects.get(codename='add_job'))
 19         self.user.user_permissions.add(Permission.objects.get(codename='change_job'))
 20         self.user.user_permissions.add(Permission.objects.get(codename='add_project'))
 21         self.user.user_permissions.add(Permission.objects.get(codename='add_user'))
 22         self.user.user_permissions.add(Permission.objects.get(codename='change_user'))                                                               
 23         self.tenant = Tenant.objects.create(name="testtenant")                                                                                       
 24         self.user.profile.tenant = self.tenant                                                                                                       
 25         self.user.profile.save()                                                                                                                     
 26         self.project = ProjectFactory(name="testproject", tenant=self.tenant, active=True, identifier='AAA')                                         
 27         self.protocol = ProtocolFactory()                                                                                                            
 28         self.root_term = TermFactory(is_root=True, acc='all')                                                                                        
 29         self.login()                                                                                                                                 
 30                                                                                                                                                      
 31     def login(self):                                                                                                                                 
 32         self.client.login(username='[email protected]', password='secret123')                                                                        
 33                                                                                                                                                      
 34     def logout(self):                                                                                                                                
 35         self.client.logout()                                                                                                                         
 36                                                                                                                                                      
 37     def get_user(self):                                                                                                                              
 38         return self.client                                                                                                                           
 39                                                                                                                                                      
 40     def permission_restricted(self, response, first_stop_url):                                                                                       
 41         first_redirect, status_1 = response.redirect_chain[0]                                                                                        
 42         second_redirect, status_2 = response.redirect_chain[1]                                                                                       
 43         self.assertEqual(first_redirect, first_stop_url)                                                                                             
 44         self.assertEqual(status_1, 302)                                                                                                              
 45         self.assertEqual(second_redirect, reverse('home'))                                                                                           
 46         self.assertEqual(status_2, 302)   

These tests used to pass when run under Python 2.7, and I haven't changed much except for updating to 3.6. I would still expect them to pass now. If anyone has any pointers, I would greatly appreciate it. First time poster here.

Upvotes: 2

Views: 1270

Answers (2)

AetherUnbound
AetherUnbound

Reputation: 1744

I also encountered this issue while using the django-simple-history library. It turned out that our issue was caused by having the multiple models that depend on each other in different databases. For testing we changed the database router to set all of the models to be created in the same database, and that resolved the IntegrityError for us. I think having the history and models in different databases may also have been a problem.

Upvotes: 0

Steve Constable
Steve Constable

Reputation: 41

For anyone who stumbles across this: the hint was in the keys that were violated. historicalproject and history_user are related to the django-simple-history package. We ended up updating our factory boy generators to also include a History Factory. This resolved the problem.

Upvotes: 2

Related Questions