[Django] #36429: IntegrityError on contentype creation when using transactional test with AND without serialized_rollback

14 views
Skip to first unread message

Django

unread,
Jun 2, 2025, 4:46:32 PM6/2/25
to django-...@googlegroups.com
#36429: IntegrityError on contentype creation when using transactional test with
AND without serialized_rollback
-----------------------------+---------------------------------------------
Reporter: Julie Rymer | Type: Bug
Status: new | Component: Testing framework
Version: 5.2 | Severity: Normal
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------+---------------------------------------------
Related to #23727

When using `TransactionTestCase`, if you use rollback emulation with
`serialized_rollback=True` for some test but **not all**, you'll get a db
error because of a duplicate on ContentType.

{{{
django.db.utils.IntegrityError: UNIQUE constraint failed:
django_content_type.app_label, django_content_type.model

Traceback (most recent call last):
File "[...]/site-packages/django/db/backends/base/creation.py", line
163, in deserialize_db_from_string
obj.save()

}}}


== Problem analysis

When using `serialized_rollback=True`, on test setup the content of the
database that was previously serialised will be applied.
On teardown, the data will be flushed and the post_migrate signal won't be
emitted because of `inhibit_post_migrate` argument.

When using `serialized_rollback=False` no database content is loaded on
setup and on teardown the database is flushed and post_migrate signal will
be emitted.

ContentType objects get created from a post_migrate handler. They also get
serialised from database content.

So, what happens if a test with `serialized_rollback=True` get run
**after** a test with `serialized_rollback=False`? You get an
IntegrityError because the ContentType object are loaded from
`connection._test_serialized_contents` after they already were created
from the post_migrate handler emitted when flushing.

I think this is what is happening, please correct me if my assumptions are
wrong.


== How to reproduce

Using the "Writing your first Django app" tutorial, you can reproduce by
adding the following tests to the polls app:

{{{

from django.test import TransactionTestCase

class QuestionModel1Tests(TransactionTestCase):
serialized_rollback = False
def test_was_published_recently_with_future_question(self):
self.assertTrue(True)


class QuestionModel2Tests(TransactionTestCase):
serialized_rollback = True
def test_was_published_recently_with_future_question(self):
self.assertTrue(True)
}}}

This error fully depends on the order of the test execution. If you don't
get the error the first time, just add some more tests alternating
`serialized_rollback` True and False until you get the error. you'll get
it as soon as a serialised rollback test execute after a non-serialised
one.
--
Ticket URL: <https://code.djangoproject.com/ticket/36429>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Jun 3, 2025, 3:18:51 AM6/3/25
to django-...@googlegroups.com
#36429: IntegrityError on contentype creation when using transactional test with
AND without serialized_rollback
-----------------------------------+------------------------------------
Reporter: Julie Rymer | Owner: (none)
Type: Bug | Status: new
Component: Testing framework | Version: 5.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+------------------------------------
Changes (by Sarah Boyce):

* stage: Unreviewed => Accepted

Comment:

Thank you, replicated. Also see this behavior on 5.1, 5.0, 4.2
--
Ticket URL: <https://code.djangoproject.com/ticket/36429#comment:1>

Django

unread,
Jun 3, 2025, 5:13:53 AM6/3/25
to django-...@googlegroups.com
#36429: IntegrityError on contentype creation when using transactional test with
AND without serialized_rollback
-----------------------------------+------------------------------------
Reporter: Julie Rymer | Owner: (none)
Type: Bug | Status: new
Component: Testing framework | Version: 5.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+------------------------------------
Comment (by Julie Rymer):

Also for those affected, I found a workaround I'm using with pytest.
You can use the `pytest_collection_modifyitems` hook to change the order
of the tests and have all transactional tests with
`serialized_rollback=False` all run at the end. This way the post_migrate
won't affect any test with `serialized_rollback=True`.

{{{
def pytest_collection_modifyitems(config, items):
def transactional_attr_order(item):
marker = item.get_closest_marker("django_db")
if (
marker
and marker.kwargs.get("transaction", False)
and not marker.kwargs.get("serialized_rollback", False)
):
return 1
return 0

items.sort(key=transactional_attr_order)

}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/36429#comment:2>

Django

unread,
Jun 3, 2025, 9:52:13 AM6/3/25
to django-...@googlegroups.com
#36429: IntegrityError on contentype creation when using transactional test with
AND without serialized_rollback
-----------------------------------+------------------------------------
Reporter: Julie Rymer | Owner: (none)
Type: Bug | Status: new
Component: Testing framework | Version: 5.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------------+------------------------------------
Comment (by Simon Charette):

A similar re-ordering strategy could be employed in this case. Something
like

{{{#!diff
+class _SerializedRollbackTransactionTestCase:
+ def __instancecheck__(self, instance):
+ return (
+ isinstance(instance, TransactionTestCase) and
instance.serialized_rollback
+ )
+
+
class DiscoverRunner:
"""A Django test runner that uses unittest2 test discovery."""

@@ -663,7 +670,7 @@ class DiscoverRunner:
parallel_test_suite = ParallelTestSuite
test_runner = unittest.TextTestRunner
test_loader = unittest.defaultTestLoader
- reorder_by = (TestCase, SimpleTestCase)
+ reorder_by = (TestCase, _SerializedRollbackTransactionTestCase,
SimpleTestCase)

def __init__(
self,
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/36429#comment:3>

Django

unread,
Jun 4, 2025, 2:39:47 AM6/4/25
to django-...@googlegroups.com
#36429: IntegrityError on contentype creation when using transactional test with
AND without serialized_rollback
-------------------------------------+-------------------------------------
Reporter: Julie Rymer | Owner: Clifford
| Gama
Type: Bug | Status: assigned
Component: Testing framework | Version: 5.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Clifford Gama):

* owner: (none) => Clifford Gama
* status: new => assigned

--
Ticket URL: <https://code.djangoproject.com/ticket/36429#comment:4>
Reply all
Reply to author
Forward
0 new messages