Skip to content

Add support for serialized rollback #353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ Bug fixes
Thanks to Will Harris for `the bug report
<https://github.com/pytest-dev/pytest-django/issues/289>`_.

Features
^^^^^^^^
* Add support for serialized rollback in transactional tests.
Thanks to Piotr Karkut for `the bug report
<https://github.com/pytest-dev/pytest-django/issues/329>`_.

Features
^^^^^^^^
* Added a new option `--migrations` to negate a default usage of
Expand Down
27 changes: 26 additions & 1 deletion docs/helpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on what marks are and for notes on using_ them.
``pytest.mark.django_db`` - request database access
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. py:function:: pytest.mark.django_db([transaction=False])
.. py:function:: pytest.mark.django_db([transaction=False, serialized_rollback=False])

This is used to mark a test function as requiring the database. It
will ensure the database is setup correctly for the test. Each test
Expand All @@ -38,6 +38,14 @@ on what marks are and for notes on using_ them.
uses. When ``transaction=True``, the behavior will be the same as
`django.test.TransactionTestCase`_

:type serialized_rollback: bool
:param serialized_rollback:
The ``serialized_rollback`` argument enables `rollback emulation`_.
After a `django.test.TransactionTestCase`_ runs, the database is
flushed, destroying data created in data migrations. This is the
default behavior of Django. Setting ``serialized_rollback=True``
tells Django to restore that data.

.. note::

If you want access to the Django database *inside a fixture*
Expand All @@ -54,6 +62,7 @@ on what marks are and for notes on using_ them.
Test classes that subclass Python's ``unittest.TestCase`` need to have the
marker applied in order to access the database.

.. _rollback emulation: https://docs.djangoproject.com/en/stable/topics/testing/overview/#rollback-emulation
.. _django.test.TestCase: https://docs.djangoproject.com/en/dev/topics/testing/overview/#testcase
.. _django.test.TransactionTestCase: https://docs.djangoproject.com/en/dev/topics/testing/overview/#transactiontestcase

Expand Down Expand Up @@ -191,6 +200,16 @@ transaction support. This is only required for fixtures which need
database access themselves. A test function would normally use the
:py:func:`~pytest.mark.django_db` mark to signal it needs the database.

``serialized_rollback``
~~~~~~~~~~~~~~~~~~~~~~~

When the ``transactional_db`` fixture is enabled, this fixture can be
added to trigger `rollback emulation`_ and thus restores data created
in data migrations after each transaction test. This is only required
for fixtures which need to enforce this behavior. A test function
would use :py:func:`~pytest.mark.django_db(serialized_rollback=True)`
to request this behavior.

``live_server``
~~~~~~~~~~~~~~~

Expand All @@ -200,6 +219,12 @@ or by requesting it's string value: ``unicode(live_server)``. You can
also directly concatenate a string to form a URL: ``live_server +
'/foo``.

Since the live server and the tests run in different threads, they
cannot share a database transaction. For this reason, ``live_server``
depends on the ``transactional_db`` fixture. If tests depend on data
created in data migrations, you should add the ``serialized_rollback``
fixture.

``settings``
~~~~~~~~~~~~

Expand Down
26 changes: 23 additions & 3 deletions pytest_django/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ def teardown_database():
request.addfinalizer(teardown_database)


def _django_db_fixture_helper(transactional, request, _django_cursor_wrapper):
def _django_db_fixture_helper(transactional, serialized_rollback,
request, _django_cursor_wrapper):
if is_django_unittest(request):
return

Expand All @@ -83,6 +84,7 @@ def _django_db_fixture_helper(transactional, request, _django_cursor_wrapper):

if django_case:
case = django_case(methodName='__init__')
case.serialized_rollback = serialized_rollback
case._pre_setup()
request.addfinalizer(case._post_teardown)

Expand Down Expand Up @@ -115,7 +117,9 @@ def db(request, _django_db_setup, _django_cursor_wrapper):
or 'live_server' in request.funcargnames:
request.getfuncargvalue('transactional_db')
else:
_django_db_fixture_helper(False, request, _django_cursor_wrapper)
_django_db_fixture_helper(
transactional=False, serialized_rollback=False,
request=request, _django_cursor_wrapper=_django_cursor_wrapper)


@pytest.fixture(scope='function')
Expand All @@ -130,7 +134,23 @@ def transactional_db(request, _django_db_setup, _django_cursor_wrapper):
database setup will behave as only ``transactional_db`` was
requested.
"""
_django_db_fixture_helper(True, request, _django_cursor_wrapper)
# TODO -- is request.getfuncargvalue('serialized_rollback') enough
# to add 'serialized_rollback' to request.funcargnames?
serialized_rollback = 'serialized_rollback' in request.funcargnames
_django_db_fixture_helper(transactional=True,
serialized_rollback=serialized_rollback,
request=request,
_django_cursor_wrapper=_django_cursor_wrapper)


@pytest.fixture(scope='function')
def serialized_rollback(request):
"""Enable serialized rollback after transaction test cases

This fixture only has an effect when the ``transactional_db``
fixture is active, which happen as a side-effect of requesting
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/happen/happens/

``live_server``.
"""


@pytest.fixture()
Expand Down
5 changes: 4 additions & 1 deletion pytest_django/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ def _django_db_marker(request):
request.getfuncargvalue('transactional_db')
else:
request.getfuncargvalue('db')
if marker.serialized_rollback:
request.getfuncargvalue('serialized_rollback')


@pytest.fixture(autouse=True, scope='class')
Expand Down Expand Up @@ -559,8 +561,9 @@ def validate_django_db(marker):
It checks the signature and creates the `transaction` attribute on
the marker which will have the correct value.
"""
def apifun(transaction=False):
def apifun(transaction=False, serialized_rollback=False):
marker.transaction = transaction
marker.serialized_rollback = serialized_rollback
apifun(*marker.args, **marker.kwargs)


Expand Down