From ea704d9dfef28f8e105666e3d7b1ce428c4626df Mon Sep 17 00:00:00 2001 From: lecchri1 Date: Wed, 6 Dec 2017 12:39:19 -0500 Subject: [PATCH 1/3] Copied most of the PR #353 with a few adjustments, like getfixturevalue --- docs/changelog.rst | 9 +++++++++ docs/helpers.rst | 29 +++++++++++++++++++++++++++-- pytest_django/fixtures.py | 26 +++++++++++++++++++++++--- pytest_django/plugin.py | 5 ++++- 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b9dcf20de..649ad3284 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,15 @@ Changelog ========= +3.2.0 +----- + +Features +^^^^^^^^ +* Add support for serialized rollback in transactional tests. + Thanks to Piotr Karkut for `the bug report + `_. + 3.1.2 ----- diff --git a/docs/helpers.rst b/docs/helpers.rst index c1be4258f..7924cc4b8 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -13,8 +13,8 @@ on what marks are and for notes on using_ them. .. _using: https://pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules -``pytest.mark.django_db(transaction=False)`` - request database access -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``pytest.mark.django_db(transaction=False, serialized_rollback=False)`` - request database access +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. :py:function:: pytest.mark.django_db: @@ -38,6 +38,14 @@ when trying to access the database. 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* @@ -54,6 +62,7 @@ when trying to access the database. 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 @@ -194,6 +203,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`` ~~~~~~~~~~~~~~~ @@ -203,6 +222,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`` ~~~~~~~~~~~~ diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index 692e0614a..3d672a222 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -103,7 +103,8 @@ def teardown_database(): request.addfinalizer(teardown_database) -def _django_db_fixture_helper(transactional, request, django_db_blocker): +def _django_db_fixture_helper(transactional, serialized_rollback, + request, django_db_blocker): if is_django_unittest(request): return @@ -120,6 +121,7 @@ def _django_db_fixture_helper(transactional, request, django_db_blocker): from django.test import TestCase as django_case test_case = django_case(methodName='__init__') + test_case.serialized_rollback = serialized_rollback test_case._pre_setup() request.addfinalizer(test_case._post_teardown) @@ -152,7 +154,9 @@ def db(request, django_db_setup, django_db_blocker): or 'live_server' in request.funcargnames: getfixturevalue(request, 'transactional_db') else: - _django_db_fixture_helper(False, request, django_db_blocker) + _django_db_fixture_helper( + transactional=False, serialized_rollback=False, + request=request, django_db_blocker=django_db_blocker) @pytest.fixture(scope='function') @@ -167,7 +171,23 @@ def transactional_db(request, django_db_setup, django_db_blocker): database setup will behave as only ``transactional_db`` was requested. """ - _django_db_fixture_helper(True, request, django_db_blocker) + serialized_rollback = False + if 'serialized_rollback' in request.funcargnames: + serialized_rollback = getfixturevalue(request, 'serialized_rollback') + + _django_db_fixture_helper( + transactional=True, serialized_rollback=serialized_rollback, + request=request, django_db_blocker=django_db_blocker) + + +@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 + ``live_server``. + """ @pytest.fixture() diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 7f85bcdc7..42266a7ba 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -374,6 +374,8 @@ def _django_db_marker(request): getfixturevalue(request, 'transactional_db') else: getfixturevalue(request, 'db') + if marker.serialized_rollback: + request.getfixturevalue(request, 'serialized_rollback') @pytest.fixture(autouse=True, scope='class') @@ -644,8 +646,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) From dac89e889992f9f85850acea547cdc4d2eeb3c37 Mon Sep 17 00:00:00 2001 From: lecchri1 Date: Wed, 6 Dec 2017 15:49:52 -0500 Subject: [PATCH 2/3] Small error with getfixture --- pytest_django/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 42266a7ba..d4307be06 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -375,7 +375,7 @@ def _django_db_marker(request): else: getfixturevalue(request, 'db') if marker.serialized_rollback: - request.getfixturevalue(request, 'serialized_rollback') + getfixturevalue(request, 'serialized_rollback') @pytest.fixture(autouse=True, scope='class') From 0e3cce3fac1f714d57d85f070613d908b7ee02c7 Mon Sep 17 00:00:00 2001 From: lecchri1 Date: Thu, 7 Dec 2017 08:39:53 -0500 Subject: [PATCH 3/3] Added serialized_rollback import to plugin.py --- pytest_django/fixtures.py | 3 ++- pytest_django/plugin.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index 3d672a222..93c71ed5a 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -18,7 +18,8 @@ __all__ = ['django_db_setup', 'db', 'transactional_db', 'admin_user', 'django_user_model', 'django_username_field', 'client', 'admin_client', 'rf', 'settings', 'live_server', - '_live_server_helper', 'django_assert_num_queries'] + 'serialized_rollback', '_live_server_helper', + 'django_assert_num_queries'] @pytest.fixture(scope='session') diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index d4307be06..507a8af4b 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -22,6 +22,7 @@ from .fixtures import django_db_modify_db_settings # noqa from .fixtures import django_db_modify_db_settings_xdist_suffix # noqa from .fixtures import _live_server_helper # noqa +from .fixtures import serialized_rollback # noqa from .fixtures import admin_client # noqa from .fixtures import admin_user # noqa from .fixtures import client # noqa