Skip to content

Support reset_sequences #308

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
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
51bd265
Implement the reset_sequences_db fixture.
cb109 Jan 16, 2016
3cad678
Implement the reset_sequences option for the django_db mark.
cb109 Jan 16, 2016
df1bbdb
Implement tests for the reset_sequences mark option and fixture.
cb109 Jan 16, 2016
8c0dce4
Update the docs about the reset_sequences support.
cb109 Jan 16, 2016
9b7ec4b
Get the marker through the request object instead of using hardcoded …
cb109 Jan 16, 2016
e45daf3
Add missing checks for transaction support within tests.
cb109 Jan 16, 2016
50001d3
Skip reset_sequences tests for django versions that do not support it.
cb109 Jan 16, 2016
216757f
Add punctuation and fix some docstring grammar.
cb109 Jan 16, 2016
def0121
Fix db access fixture precedence.
cb109 Jan 16, 2016
c6ec611
Update docs with a note about combining db access fixtures.
cb109 Jan 16, 2016
3e3287a
Update docs about marker option usage.
cb109 Jan 17, 2016
add9d49
Do not return the TestCase instance when requesting db fixtures.
cb109 Jul 1, 2016
eaa5e7b
Rework reset_sequences_db test.
cb109 Jul 1, 2016
da378e2
Fix start id value.
cb109 Jul 1, 2016
a756f4e
Fix conflicts within docs, fixtures and plugin definition.
cb109 Jul 4, 2016
0bfb9af
Satisfy QA checks.
cb109 Jul 4, 2016
b74c28d
Remove kwargs from headline for a more readable list of contents.
cb109 Jul 4, 2016
28a3876
Fix version check against '1.10'.
cb109 Jul 14, 2016
f8d2e1d
Remove trailing whitespace.
cb109 Jul 14, 2016
fd3603a
Rename fixture to `django_db_reset_sequences`.
cb109 Jul 14, 2016
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
35 changes: 27 additions & 8 deletions docs/helpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ 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, reset_sequences=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
will ensure the database is setup correctly for the test. Each test
will run in its own transaction which will be rolled back at the end
of the test. This behavior is the same as Django's standard
`django.test.TestCase`_ class.

In order for a test to have access to the database it must either
be marked using the ``django_db`` mark or request one of the ``db``
or ``transactional_db`` fixtures. Otherwise the test will fail
when trying to access the database.
be marked using the ``django_db`` mark or request one of the ``db``,
``transactional_db`` or ``reset_sequences_db`` fixtures. Otherwise the
test will fail when trying to access the database.

:type transaction: bool
:param transaction:
Expand All @@ -38,14 +38,22 @@ 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 reset_sequences: bool
:param reset_sequences:
The ``reset_sequences`` argument will ask to reset auto increment sequence
values (e.g. primary keys) before running the test. Defaults to
``False``. Must be used together with ``transaction=True`` to have an
effect. Please be aware that not all databases support this feature.
For details see `django.test.TransactionTestCase.reset_sequences`_

.. note::

If you want access to the Django database *inside a fixture*
this marker will not help even if the function requesting your
fixture has this marker applied. To access the database in a
fixture, the fixture itself will have to request the ``db`` or
``transactional_db`` fixture. See below for a description of
them.
fixture, the fixture itself will have to request the ``db``,
``transactional_db`` or ``reset_sequences_db`` fixture. See below
for a description of them.

.. note:: Automatic usage with ``django.test.TestCase``.

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.

.. _django.test.TransactionTestCase.reset_sequences: https://docs.djangoproject.com/en/dev/topics/testing/advanced/#django.test.TransactionTestCase.reset_sequences
.. _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.

``reset_sequences_db``
~~~~~~~~~~~~~~~~~~~~

This fixture provides the same transactional database access as
``transactional_db``, with additional support for reset of auto increment
sequences (if your database supports it). 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
Copy link
Contributor

Choose a reason for hiding this comment

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

s/mark.django_db/mark.reset_sequences_db/

Copy link
Member Author

Choose a reason for hiding this comment

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

I am sorry, I don't understand 😕 Is that sphinx syntax?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, sed.. ;)
Change "mark.django_db" to "mark.reset_sequences_db".

It seems you have copied it from the paragraph above, but it should be changed here - if I understand it correctly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ahh okay 😌 ! There is only one marker (django_db), but it has some optional keywords that make the difference. I updated that section of the docs to make it clear which one to use for a test to get the same effect as using the fixture.

needs the database.

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

Expand Down
40 changes: 33 additions & 7 deletions pytest_django/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
from .django_compat import is_django_unittest
from .lazy_django import get_django_version, skip_if_no_django

__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']
__all__ = ['_django_db_setup', 'db', 'transactional_db',
'reset_sequences_db', 'admin_user', 'django_user_model',
'django_username_field', 'client', 'admin_client', 'rf',
'settings', 'live_server', '_live_server_helper']


# ############### Internal Fixtures ################
Expand Down Expand Up @@ -64,7 +64,10 @@ def teardown_database():
request.addfinalizer(teardown_database)


def _django_db_fixture_helper(transactional, request, _django_cursor_wrapper):
def _django_db_fixture_helper(request, _django_cursor_wrapper,
transactional=False, reset_sequences=False):
"""Setup the django test case and pytest execution context."""

if is_django_unittest(request):
return

Expand All @@ -83,6 +86,10 @@ def _django_db_fixture_helper(transactional, request, _django_cursor_wrapper):
if get_version() >= '1.5':
from django.test import TransactionTestCase as django_case

if reset_sequences:
class ResetSequenceTestCase(django_case):
reset_sequences = True
django_case = ResetSequenceTestCase
else:
# Django before 1.5 flushed the DB during setUp.
# Use pytest-django's old behavior with it.
Expand All @@ -107,6 +114,7 @@ def flushdb():
case = django_case(methodName='__init__')
case._pre_setup()
request.addfinalizer(case._post_teardown)
return django_case


def _handle_south():
Expand Down Expand Up @@ -177,7 +185,7 @@ def db(request, _django_db_setup, _django_cursor_wrapper):
if 'transactional_db' in request.funcargnames \
or 'live_server' in request.funcargnames:
return request.getfuncargvalue('transactional_db')
return _django_db_fixture_helper(False, request, _django_cursor_wrapper)
return _django_db_fixture_helper(request, _django_cursor_wrapper)


@pytest.fixture(scope='function')
Expand All @@ -192,7 +200,25 @@ def transactional_db(request, _django_db_setup, _django_cursor_wrapper):
database setup will behave as only ``transactional_db`` was
requested.
"""
return _django_db_fixture_helper(True, request, _django_cursor_wrapper)
return _django_db_fixture_helper(request, _django_cursor_wrapper,
transactional=True)


@pytest.fixture(scope='function')
def reset_sequences_db(request, _django_db_setup, _django_cursor_wrapper):
"""Require a transactional test database with sequence reset support
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add punctuation (a dot) at the end.

Copy link
Member Author

Choose a reason for hiding this comment

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

done


This behaves like the ``transactional_db`` fixture, with the addition
of enforcing a reset of all auto increment sequence. If the enquiring
Copy link
Contributor

Choose a reason for hiding this comment

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

s/sequence/sequences/

Copy link
Member Author

Choose a reason for hiding this comment

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

done

test relies on such values (e.g. ids as primary keys), you should
request this resource to ensure they are consistent across tests.

If a combination of this, ``db`` and ``transactional_db`` is requested
then the database setup will behave as only ``reset_sequences_db``
was requested.
"""
return _django_db_fixture_helper(request, _django_cursor_wrapper,
transactional=True, reset_sequences=True)


@pytest.fixture()
Expand Down
23 changes: 15 additions & 8 deletions pytest_django/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
from .fixtures import (_django_db_setup, _live_server_helper, admin_client,
admin_user, client, db, django_user_model,
django_username_field, live_server, rf, settings,
transactional_db)
transactional_db, reset_sequences_db)
from .lazy_django import django_settings_is_configured, skip_if_no_django

# Silence linters for imported fixtures.
(_django_db_setup, _live_server_helper, admin_client, admin_user, client, db,
django_user_model, django_username_field, live_server, rf, settings,
transactional_db)
transactional_db, reset_sequences_db)


SETTINGS_MODULE_ENV = 'DJANGO_SETTINGS_MODULE'
Expand Down Expand Up @@ -363,13 +363,15 @@ def _django_cursor_wrapper(request):
def _django_db_marker(request):
"""Implement the django_db marker, internal to pytest-django.

This will dynamically request the ``db`` or ``transactional_db``
fixtures as required by the django_db marker.
This will dynamically request the ``db``, ``transactional_db`` or
``reset_sequences_db`` fixtures as required by the django_db marker.
"""
marker = request.keywords.get('django_db', None)
if marker:
validate_django_db(marker)
if marker.transaction:
if marker.reset_sequences:
request.getfuncargvalue('reset_sequences_db')
elif marker.transaction:
request.getfuncargvalue('transactional_db')
else:
request.getfuncargvalue('db')
Expand Down Expand Up @@ -562,11 +564,16 @@ def __exit__(self, exc_type, exc_value, traceback):
def validate_django_db(marker):
"""Validate the django_db marker.

It checks the signature and creates the `transaction` attribute on
the marker which will have the correct value.
It checks the signature and creates the ``transaction`` and
``reset_sequences`` attributes on the marker which will have the
correct values.

A sequence reset is only allowed when combined with a transaction.
"""
def apifun(transaction=False):
def apifun(transaction=False, reset_sequences=False):
marker.transaction = transaction
marker.reset_sequences = transaction and reset_sequences

apifun(*marker.args, **marker.kwargs)


Expand Down
66 changes: 57 additions & 9 deletions tests/test_database.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import with_statement

import pytest
from django import get_version
from django.db import connection, transaction
from django.test.testcases import connections_support_transactions

Expand Down Expand Up @@ -52,19 +53,21 @@ def test_noaccess_fixture(noaccess):


class TestDatabaseFixtures:
"""Tests for the db and transactional_db fixtures"""
"""Tests for the db, transactional_db and reset_sequences_db fixtures"""

@pytest.fixture(params=['db', 'transactional_db'])
def both_dbs(self, request):
if request.param == 'transactional_db':
@pytest.fixture(params=['db', 'transactional_db', 'reset_sequences_db'])
def all_dbs(self, request):
if request.param == 'reset_sequences_db':
return request.getfuncargvalue('reset_sequences_db')
elif request.param == 'transactional_db':
return request.getfuncargvalue('transactional_db')
elif request.param == 'db':
return request.getfuncargvalue('db')

def test_access(self, both_dbs):
def test_access(self, all_dbs):
Item.objects.create(name='spam')

def test_clean_db(self, both_dbs):
def test_clean_db(self, all_dbs):
# Relies on the order: test_access created an object
assert Item.objects.count() == 0

Expand All @@ -80,8 +83,41 @@ def test_transactions_enabled(self, transactional_db):

assert not noop_transactions()

def test_transactions_enabled_via_reset_seq(self, reset_sequences_db):
if not connections_support_transactions():
pytest.skip('transactions required for this test')

assert not noop_transactions()

@pytest.mark.skipif(get_version() < '1.5',
reason='reset_sequences needs Django >= 1.5')
def test_reset_sequences_disabled_by_default(self, db):
if not connections_support_transactions():
pytest.skip('transactions required for this test')
testcase = db

assert not testcase.reset_sequences

@pytest.mark.skipif(get_version() < '1.5',
reason='reset_sequences needs Django >= 1.5')
def test_reset_sequences_disabled(self, transactional_db):
if not connections_support_transactions():
pytest.skip('transactions required for this test')
testcase = transactional_db

assert not testcase.reset_sequences

@pytest.mark.skipif(get_version() < '1.5',
reason='reset_sequences needs Django >= 1.5')
def test_reset_sequences_enabled(self, reset_sequences_db):
if not connections_support_transactions():
pytest.skip('transactions required for this test')
testcase = reset_sequences_db

assert testcase.reset_sequences

@pytest.fixture
def mydb(self, both_dbs):
def mydb(self, all_dbs):
# This fixture must be able to access the database
Item.objects.create(name='spam')

Expand All @@ -93,13 +129,13 @@ def test_mydb(self, mydb):
item = Item.objects.get(name='spam')
assert item

def test_fixture_clean(self, both_dbs):
def test_fixture_clean(self, all_dbs):
# Relies on the order: test_mydb created an object
# See https://github.com/pytest-dev/pytest-django/issues/17
assert Item.objects.count() == 0

@pytest.fixture
def fin(self, request, both_dbs):
def fin(self, request, all_dbs):
# This finalizer must be able to access the database
request.addfinalizer(lambda: Item.objects.create(name='spam'))

Expand Down Expand Up @@ -163,6 +199,18 @@ def test_transactions_enabled(self):

assert not noop_transactions()

@pytest.mark.django_db
def test_reset_sequences_disabled(self, request):
marker = request.keywords['django_db']

assert not marker.kwargs

@pytest.mark.django_db(reset_sequences=True)
def test_reset_sequences_enabled(self, request):
marker = request.keywords['django_db']

assert marker.kwargs['reset_sequences']


def test_unittest_interaction(django_testdir):
"Test that (non-Django) unittests cannot access the DB."
Expand Down