From 51bd265f0b409f89ed8b7e96c093d1ea38e7007d Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Sat, 16 Jan 2016 15:31:08 +0100 Subject: [PATCH 01/19] Implement the reset_sequences_db fixture. --- pytest_django/fixtures.py | 40 ++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index cae7d4763..df2bb9295 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -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 ################ @@ -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 @@ -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. @@ -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(): @@ -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') @@ -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 + + This behaves like the ``transactional_db`` fixture, with the addition + of enforcing a reset of all auto increment sequence. If the enquiring + 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() From 3cad6786d8ba704f1b39a3fa55cc9d5900097a91 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Sat, 16 Jan 2016 15:32:06 +0100 Subject: [PATCH 02/19] Implement the reset_sequences option for the django_db mark. --- pytest_django/plugin.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 2484eedac..1aed1f9e2 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -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' @@ -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') @@ -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) From df1bbdb918918b0f212eb19074cc3508375a3910 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Sat, 16 Jan 2016 15:32:46 +0100 Subject: [PATCH 03/19] Implement tests for the reset_sequences mark option and fixture. --- tests/test_database.py | 53 +++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/tests/test_database.py b/tests/test_database.py index adbc51736..f433f8fd6 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -52,19 +52,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 @@ -80,8 +82,29 @@ 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() + + def test_reset_sequences_disabled_by_default(self, db): + testcase = db + + assert not testcase.reset_sequences + + def test_reset_sequences_disabled(self, transactional_db): + testcase = transactional_db + + assert not testcase.reset_sequences + + def test_reset_sequences_enabled(self, reset_sequences_db): + 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') @@ -93,13 +116,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')) @@ -163,6 +186,18 @@ def test_transactions_enabled(self): assert not noop_transactions() + @pytest.mark.django_db + def test_reset_sequences_disabled(self): + marker = self.test_reset_sequences_disabled.django_db + + assert not marker.kwargs + + @pytest.mark.django_db(reset_sequences=True) + def test_reset_sequences_enabled(self): + marker = self.test_reset_sequences_enabled.django_db + + assert marker.kwargs['reset_sequences'] + def test_unittest_interaction(django_testdir): "Test that (non-Django) unittests cannot access the DB." From 8c0dce4e44493b1a83c6a460d0f38ba02765be7a Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Sat, 16 Jan 2016 15:33:05 +0100 Subject: [PATCH 04/19] Update the docs about the reset_sequences support. --- docs/helpers.rst | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/docs/helpers.rst b/docs/helpers.rst index 7c60f9005..4b6c48552 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -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: @@ -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``. @@ -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 @@ -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 +needs the database. + ``live_server`` ~~~~~~~~~~~~~~~ From 9b7ec4b1ddec98551d06db28524e11d23f17392e Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Sat, 16 Jan 2016 16:04:42 +0100 Subject: [PATCH 05/19] Get the marker through the request object instead of using hardcoded function names. --- tests/test_database.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_database.py b/tests/test_database.py index f433f8fd6..c319197c4 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -187,14 +187,14 @@ def test_transactions_enabled(self): assert not noop_transactions() @pytest.mark.django_db - def test_reset_sequences_disabled(self): - marker = self.test_reset_sequences_disabled.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): - marker = self.test_reset_sequences_enabled.django_db + def test_reset_sequences_enabled(self, request): + marker = request.keywords['django_db'] assert marker.kwargs['reset_sequences'] From e45daf3e368c8cd4172de0ab740710accf74d06c Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Sat, 16 Jan 2016 16:35:19 +0100 Subject: [PATCH 06/19] Add missing checks for transaction support within tests. --- tests/test_database.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_database.py b/tests/test_database.py index c319197c4..93aeceb6e 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -89,16 +89,22 @@ def test_transactions_enabled_via_reset_seq(self, reset_sequences_db): assert not noop_transactions() 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 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 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 From 50001d35ee7fda84ed40a9cad534fb929a63a3a8 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Sat, 16 Jan 2016 16:52:35 +0100 Subject: [PATCH 07/19] Skip reset_sequences tests for django versions that do not support it. --- tests/test_database.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_database.py b/tests/test_database.py index 93aeceb6e..8ce39af6d 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -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 @@ -88,6 +89,8 @@ def test_transactions_enabled_via_reset_seq(self, reset_sequences_db): 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') @@ -95,6 +98,8 @@ def test_reset_sequences_disabled_by_default(self, 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') @@ -102,6 +107,8 @@ def test_reset_sequences_disabled(self, 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') From 216757f93e7ed2a7aaba81457603e789cd239b18 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Sat, 16 Jan 2016 18:03:38 +0100 Subject: [PATCH 08/19] Add punctuation and fix some docstring grammar. --- pytest_django/fixtures.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index df2bb9295..067b009ad 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -169,7 +169,7 @@ def _disable_native_migrations(): @pytest.fixture(scope='function') def db(request, _django_db_setup, _django_cursor_wrapper): - """Require a django test database + """Require a django test database. This database will be setup with the default fixtures and will have the transaction management disabled. At the end of the test the outer @@ -190,7 +190,7 @@ def db(request, _django_db_setup, _django_cursor_wrapper): @pytest.fixture(scope='function') def transactional_db(request, _django_db_setup, _django_cursor_wrapper): - """Require a django test database with transaction support + """Require a django test database with transaction support. This will re-initialise the django database for each test and is thus slower than the normal ``db`` fixture. @@ -206,10 +206,10 @@ def transactional_db(request, _django_db_setup, _django_cursor_wrapper): @pytest.fixture(scope='function') def reset_sequences_db(request, _django_db_setup, _django_cursor_wrapper): - """Require a transactional test database with sequence reset support + """Require a transactional test database with sequence reset support. This behaves like the ``transactional_db`` fixture, with the addition - of enforcing a reset of all auto increment sequence. If the enquiring + of enforcing a reset of all auto increment sequences. If the enquiring test relies on such values (e.g. ids as primary keys), you should request this resource to ensure they are consistent across tests. From def0121f38dbd3863369eb83ff5bf4fae605d996 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Sat, 16 Jan 2016 18:12:34 +0100 Subject: [PATCH 09/19] Fix db access fixture precedence. --- pytest_django/fixtures.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index 067b009ad..f710f3d57 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -178,12 +178,14 @@ def db(request, _django_db_setup, _django_cursor_wrapper): This is more limited than the ``transactional_db`` resource but faster. - If both this and ``transactional_db`` are requested then the - database setup will behave as only ``transactional_db`` was - requested. + If multiple database fixtures are requested, they take precedence + over each other in the following order (the last one wins): ``db``, + ``transactional_db``, ``reset_sequences_db``. """ - if 'transactional_db' in request.funcargnames \ - or 'live_server' in request.funcargnames: + if 'reset_sequences_db' in request.funcargnames: + return request.getfuncargvalue('reset_sequences_db') + if ('transactional_db' in request.funcargnames or + 'live_server' in request.funcargnames): return request.getfuncargvalue('transactional_db') return _django_db_fixture_helper(request, _django_cursor_wrapper) @@ -196,10 +198,14 @@ def transactional_db(request, _django_db_setup, _django_cursor_wrapper): thus slower than the normal ``db`` fixture. If you want to use the database with transactions you must request - this resource. If both this and ``db`` are requested then the - database setup will behave as only ``transactional_db`` was - requested. + this resource. + + If multiple database fixtures are requested, they take precedence + over each other in the following order (the last one wins): ``db``, + ``transactional_db``, ``reset_sequences_db``. """ + if 'reset_sequences_db' in request.funcargnames: + return request.getfuncargvalue('reset_sequences_db') return _django_db_fixture_helper(request, _django_cursor_wrapper, transactional=True) @@ -213,9 +219,9 @@ def reset_sequences_db(request, _django_db_setup, _django_cursor_wrapper): 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. + If multiple database fixtures are requested, they take precedence + over each other in the following order (the last one wins): ``db``, + ``transactional_db``, ``reset_sequences_db``. """ return _django_db_fixture_helper(request, _django_cursor_wrapper, transactional=True, reset_sequences=True) From c6ec6114a3cd2653f93e1eef418a43b79ed41ed1 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Sat, 16 Jan 2016 18:38:14 +0100 Subject: [PATCH 10/19] Update docs with a note about combining db access fixtures. --- docs/helpers.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/helpers.rst b/docs/helpers.rst index 4b6c48552..f037fec97 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -219,6 +219,16 @@ or by requesting it's string value: ``unicode(live_server)``. You can also directly concatenate a string to form a URL: ``live_server + '/foo``. +.. note:: Combining database access fixtures. + + When using multiple database fixtures together, only one of them is + used. Their order of precedence is as follows (the last one wins): + * ``db`` + * ``transactional_db`` + * ``reset_sequences_db`` + In addition, using ``live_server`` will also trigger transactional + database access, if not specified. + ``settings`` ~~~~~~~~~~~~ From 3e3287a461df5b97a838f749bd7b976157f08c4e Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Sun, 17 Jan 2016 12:07:07 +0100 Subject: [PATCH 11/19] Update docs about marker option usage. --- docs/helpers.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/helpers.rst b/docs/helpers.rst index f037fec97..1a1e3ef47 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -197,8 +197,8 @@ mark to signal it needs the database. This fixture can be used to request access to the database including 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. +database access themselves. A test function should normally use the +:py:func:`~pytest.mark.django_db` mark with ``transaction=True``. ``reset_sequences_db`` ~~~~~~~~~~~~~~~~~~~~ @@ -206,9 +206,8 @@ database access themselves. A test function would normally use the 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 -needs the database. +fixtures which need database access themselves. A test function should +normally use the :py:func:`~pytest.mark.django_db` mark with ``transaction=True`` and ``reset_sequences=True``. ``live_server`` ~~~~~~~~~~~~~~~ From add9d49b02b22d2b4f67b500345f28be5019d35c Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Fri, 1 Jul 2016 18:37:57 +0200 Subject: [PATCH 12/19] Do not return the TestCase instance when requesting db fixtures. This breaks the test for the reset_sequences feature. --- pytest_django/fixtures.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index f710f3d57..98c81ac14 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -114,7 +114,6 @@ def flushdb(): case = django_case(methodName='__init__') case._pre_setup() request.addfinalizer(case._post_teardown) - return django_case def _handle_south(): From eaa5e7b177f6d1f7822e542422efdfe833670b72 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Fri, 1 Jul 2016 20:08:09 +0200 Subject: [PATCH 13/19] Rework reset_sequences_db test. We actually check for the resulting sequence ids now instead of internal pytest-django-attributes. On the flipside this test must now be skipped if the current database does not support the feature. --- tests/test_database.py | 69 +++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/tests/test_database.py b/tests/test_database.py index 8ce39af6d..595d5f970 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -32,6 +32,12 @@ def noop_transactions(): return True +def db_supports_reset_sequences(): + """Return if the current db engine supports `reset_sequences`.""" + return (connection.features.supports_transactions and + connection.features.supports_sequence_reset) + + def test_noaccess(): with pytest.raises(pytest.fail.Exception): Item.objects.create(name='spam') @@ -52,6 +58,18 @@ def test_noaccess_fixture(noaccess): pass +@pytest.fixture +def non_zero_sequences_counter(db): + """Ensure that the db's internal sequence counter is > 0. + + This is used to test the `reset_sequences` feature. + """ + item_1 = Item.objects.create(name='item_1') + item_2 = Item.objects.create(name='item_2') + item_1.delete() + item_2.delete() + + class TestDatabaseFixtures: """Tests for the db, transactional_db and reset_sequences_db fixtures""" @@ -91,30 +109,33 @@ def test_transactions_enabled_via_reset_seq(self, reset_sequences_db): @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 + def test_reset_sequences_db_fixture( + self, db, django_testdir, non_zero_sequences_counter): + + if not db_supports_reset_sequences(): + pytest.skip('transactions and reset_sequences must be supported ' + 'by the database to run this test') + + # The test runs on a database that already contains objects, so its + # id counter is > 0. We check for the ids of newly created objects. + django_testdir.create_test_module(''' + import pytest + from .app.models import Item + + def test_reset_sequences_db_not_requested(db): + item = Item.objects.create(name='new_item') + assert item.id > 0 + + def test_reset_sequences_db_requested(reset_sequences_db): + item = Item.objects.create(name='new_item') + assert item.id == 0 + ''') + + result = django_testdir.runpytest_subprocess('-v', '--reuse-db') + result.stdout.fnmatch_lines([ + "*test_reset_sequences_db_not_requested PASSED*", + "*test_reset_sequences_db_requested PASSED*", + ]) @pytest.fixture def mydb(self, all_dbs): From da378e2b150de0b5515128f38bdb303ec2999da2 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Fri, 1 Jul 2016 21:22:08 +0200 Subject: [PATCH 14/19] Fix start id value. --- tests/test_database.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_database.py b/tests/test_database.py index 595d5f970..e93f09e1a 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -60,7 +60,7 @@ def test_noaccess_fixture(noaccess): @pytest.fixture def non_zero_sequences_counter(db): - """Ensure that the db's internal sequence counter is > 0. + """Ensure that the db's internal sequence counter is > 1. This is used to test the `reset_sequences` feature. """ @@ -117,18 +117,18 @@ def test_reset_sequences_db_fixture( 'by the database to run this test') # The test runs on a database that already contains objects, so its - # id counter is > 0. We check for the ids of newly created objects. + # id counter is > 1. We check for the ids of newly created objects. django_testdir.create_test_module(''' import pytest from .app.models import Item def test_reset_sequences_db_not_requested(db): item = Item.objects.create(name='new_item') - assert item.id > 0 + assert item.id > 1 def test_reset_sequences_db_requested(reset_sequences_db): item = Item.objects.create(name='new_item') - assert item.id == 0 + assert item.id == 1 ''') result = django_testdir.runpytest_subprocess('-v', '--reuse-db') From 0bfb9afe9503af5a9aad86db2797b883a3376a53 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Tue, 5 Jul 2016 00:16:19 +0200 Subject: [PATCH 15/19] Satisfy QA checks. --- pytest_django/plugin.py | 1 - tests/test_database.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 3d78f6e96..f0c1ba4ad 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -36,7 +36,6 @@ from .lazy_django import django_settings_is_configured, skip_if_no_django - SETTINGS_MODULE_ENV = 'DJANGO_SETTINGS_MODULE' CONFIGURATION_ENV = 'DJANGO_CONFIGURATION' INVALID_TEMPLATE_VARS_ENV = 'FAIL_INVALID_TEMPLATE_VARS' diff --git a/tests/test_database.py b/tests/test_database.py index cb77de06d..9ab4213c7 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -2,7 +2,7 @@ import pytest from django import get_version -from django.db import connection, transaction +from django.db import connection from django.test.testcases import connections_support_transactions from pytest_django_test.app.models import Item From b74c28d0e8626011e3d6696954dcd98aafd206fc Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Tue, 5 Jul 2016 00:24:45 +0200 Subject: [PATCH 16/19] Remove kwargs from headline for a more readable list of contents. --- docs/helpers.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/helpers.rst b/docs/helpers.rst index b2f860725..c5be489a3 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -13,8 +13,8 @@ on what marks are and for notes on using_ them. .. _using: http://pytest.org/latest/example/markers.html#marking-whole-classes-or-modules -``pytest.mark.django_db(transaction=False, reset_sequences=False)`` - request database access -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``pytest.mark.django_db`` - request database access +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. py:function:: pytest.mark.django_db([transaction=False, reset_sequences=False]) From 28a3876c45298672da4dd9dfff09f0db28622a79 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Fri, 15 Jul 2016 00:18:25 +0200 Subject: [PATCH 17/19] Fix version check against '1.10'. --- tests/test_database.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/test_database.py b/tests/test_database.py index 9ab4213c7..335d56941 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1,13 +1,26 @@ from __future__ import with_statement import pytest -from django import get_version +from django import VERSION from django.db import connection from django.test.testcases import connections_support_transactions from pytest_django_test.app.models import Item +def get_comparable_django_version(): + """Return the Django version as tuple of integers (major, minor, patch). + + Ignores any other version parts like 'final' or 'beta'. + + This is more reliable to compare against version requirements in the + same format, as opposed to comparing strings like: '1.10' > '1.5' + which would return False although that version is considered higher. + """ + major, minor, patch = VERSION[0], VERSION[1], VERSION[2] + return (major, minor, patch) + + def db_supports_reset_sequences(): """Return if the current db engine supports `reset_sequences`.""" return (connection.features.supports_transactions and @@ -83,7 +96,7 @@ def test_transactions_enabled_via_reset_seq(self, reset_sequences_db): assert not connection.in_atomic_block - @pytest.mark.skipif(get_version() < '1.5', + @pytest.mark.skipif(get_comparable_django_version() < (1, 5, 0), reason='reset_sequences needs Django >= 1.5') def test_reset_sequences_db_fixture( self, db, django_testdir, non_zero_sequences_counter): From f8d2e1d493853859c279c63341cdcc38774dc73b Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Fri, 15 Jul 2016 00:47:59 +0200 Subject: [PATCH 18/19] Remove trailing whitespace. --- tests/test_database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_database.py b/tests/test_database.py index 335d56941..822eaa7e0 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -14,7 +14,7 @@ def get_comparable_django_version(): Ignores any other version parts like 'final' or 'beta'. This is more reliable to compare against version requirements in the - same format, as opposed to comparing strings like: '1.10' > '1.5' + same format, as opposed to comparing strings like: '1.10' > '1.5' which would return False although that version is considered higher. """ major, minor, patch = VERSION[0], VERSION[1], VERSION[2] From fd3603ab3ab11147067d409081142535330193f6 Mon Sep 17 00:00:00 2001 From: Christoph Buelter Date: Fri, 15 Jul 2016 00:55:50 +0200 Subject: [PATCH 19/19] Rename fixture to `django_db_reset_sequences`. --- pytest_django/fixtures.py | 18 +++++++++--------- pytest_django/plugin.py | 6 +++--- tests/test_database.py | 30 +++++++++++++++++------------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index c86106c8a..7a4182f5c 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -13,7 +13,7 @@ from .lazy_django import get_django_version, skip_if_no_django __all__ = ['django_db_setup', 'db', 'transactional_db', - 'reset_sequences_db', 'admin_user', 'django_user_model', + 'django_db_reset_sequences', 'admin_user', 'django_user_model', 'django_username_field', 'client', 'admin_client', 'rf', 'settings', 'live_server', '_live_server_helper'] @@ -155,10 +155,10 @@ def db(request, django_db_setup, django_db_blocker): If multiple database fixtures are requested, they take precedence over each other in the following order (the last one wins): ``db``, - ``transactional_db``, ``reset_sequences_db``. + ``transactional_db``, ``django_db_reset_sequences``. """ - if 'reset_sequences_db' in request.funcargnames: - request.getfuncargvalue('reset_sequences_db') + if 'django_db_reset_sequences' in request.funcargnames: + request.getfuncargvalue('django_db_reset_sequences') if ('transactional_db' in request.funcargnames or 'live_server' in request.funcargnames): request.getfuncargvalue('transactional_db') @@ -178,16 +178,16 @@ def transactional_db(request, django_db_setup, django_db_blocker): If multiple database fixtures are requested, they take precedence over each other in the following order (the last one wins): ``db``, - ``transactional_db``, ``reset_sequences_db``. + ``transactional_db``, ``django_db_reset_sequences``. """ - if 'reset_sequences_db' in request.funcargnames: - request.getfuncargvalue('reset_sequences_db') + if 'django_db_reset_sequences' in request.funcargnames: + request.getfuncargvalue('django_db_reset_sequences') _django_db_fixture_helper(request, django_db_blocker, transactional=True) @pytest.fixture(scope='function') -def reset_sequences_db(request, django_db_setup, django_db_blocker): +def django_db_reset_sequences(request, django_db_setup, django_db_blocker): """Require a transactional test database with sequence reset support. This behaves like the ``transactional_db`` fixture, with the addition @@ -197,7 +197,7 @@ def reset_sequences_db(request, django_db_setup, django_db_blocker): If multiple database fixtures are requested, they take precedence over each other in the following order (the last one wins): ``db``, - ``transactional_db``, ``reset_sequences_db``. + ``transactional_db``, ``django_db_reset_sequences``. """ _django_db_fixture_helper(request, django_db_blocker, transactional=True, reset_sequences=True) diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index f0c1ba4ad..05bc8ef09 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -28,7 +28,7 @@ from .fixtures import django_user_model # noqa from .fixtures import django_username_field # noqa from .fixtures import live_server # noqa -from .fixtures import reset_sequences_db # noqa +from .fixtures import django_db_reset_sequences # noqa from .fixtures import rf # noqa from .fixtures import settings # noqa from .fixtures import transactional_db # noqa @@ -365,13 +365,13 @@ def _django_db_marker(request): """Implement the django_db marker, internal to pytest-django. This will dynamically request the ``db``, ``transactional_db`` or - ``reset_sequences_db`` fixtures as required by the django_db marker. + ``django_db_reset_sequences`` fixtures as required by the django_db marker. """ marker = request.keywords.get('django_db', None) if marker: validate_django_db(marker) if marker.reset_sequences: - request.getfuncargvalue('reset_sequences_db') + request.getfuncargvalue('django_db_reset_sequences') elif marker.transaction: request.getfuncargvalue('transactional_db') else: diff --git a/tests/test_database.py b/tests/test_database.py index 822eaa7e0..f5abc3d59 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -60,12 +60,14 @@ def non_zero_sequences_counter(db): class TestDatabaseFixtures: - """Tests for the db, transactional_db and reset_sequences_db fixtures""" + """Tests for the different database fixtures.""" - @pytest.fixture(params=['db', 'transactional_db', 'reset_sequences_db']) + @pytest.fixture(params=['db', + 'transactional_db', + 'django_db_reset_sequences']) def all_dbs(self, request): - if request.param == 'reset_sequences_db': - return request.getfuncargvalue('reset_sequences_db') + if request.param == 'django_db_reset_sequences': + return request.getfuncargvalue('django_db_reset_sequences') elif request.param == 'transactional_db': return request.getfuncargvalue('transactional_db') elif request.param == 'db': @@ -90,7 +92,8 @@ def test_transactions_enabled(self, transactional_db): assert not connection.in_atomic_block - def test_transactions_enabled_via_reset_seq(self, reset_sequences_db): + def test_transactions_enabled_via_reset_seq( + self, django_db_reset_sequences): if not connections_support_transactions(): pytest.skip('transactions required for this test') @@ -98,7 +101,7 @@ def test_transactions_enabled_via_reset_seq(self, reset_sequences_db): @pytest.mark.skipif(get_comparable_django_version() < (1, 5, 0), reason='reset_sequences needs Django >= 1.5') - def test_reset_sequences_db_fixture( + def test_django_db_reset_sequences_fixture( self, db, django_testdir, non_zero_sequences_counter): if not db_supports_reset_sequences(): @@ -111,19 +114,20 @@ def test_reset_sequences_db_fixture( import pytest from .app.models import Item - def test_reset_sequences_db_not_requested(db): + def test_django_db_reset_sequences_not_requested(db): item = Item.objects.create(name='new_item') assert item.id > 1 - def test_reset_sequences_db_requested(reset_sequences_db): + def test_django_db_reset_sequences_requested( + django_db_reset_sequences): item = Item.objects.create(name='new_item') assert item.id == 1 ''') result = django_testdir.runpytest_subprocess('-v', '--reuse-db') result.stdout.fnmatch_lines([ - "*test_reset_sequences_db_not_requested PASSED*", - "*test_reset_sequences_db_requested PASSED*", + "*test_django_db_reset_sequences_not_requested PASSED*", + "*test_django_db_reset_sequences_requested PASSED*", ]) @pytest.fixture @@ -257,7 +261,7 @@ def test_db_access_3(self): "*test_db_access_2 FAILED*", "*test_db_access_3 FAILED*", "*ERROR at setup of TestCase_setupClass.test_db_access_1*", - "*Failed: Database access not allowed, use the \"django_db\" mark to enable*", + "*Failed: Database access not allowed, use the \"django_db\" mark to enable*", # noqa ]) @@ -272,7 +276,7 @@ def test_db_access_in_conftest(self, django_testdir): result = django_testdir.runpytest_subprocess('-v') result.stderr.fnmatch_lines([ - '*Failed: Database access not allowed, use the "django_db" mark to enable it.*', + '*Failed: Database access not allowed, use the "django_db" mark to enable it.*', # noqa ]) def test_db_access_in_test_module(self, django_testdir): @@ -283,5 +287,5 @@ def test_db_access_in_test_module(self, django_testdir): result = django_testdir.runpytest_subprocess('-v') result.stdout.fnmatch_lines([ - '*Failed: Database access not allowed, use the "django_db" mark to enable it.*', + '*Failed: Database access not allowed, use the "django_db" mark to enable it.*', # noqa ])