diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index cae7d4763..ca2b4987a 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -28,6 +28,11 @@ def _django_db_setup(request, """Session-wide database setup, internal to pytest-django""" skip_if_no_django() + if _is_xdist_one_db_enabled(request.config): + with _django_cursor_wrapper: + _setup_reused_databases() + return + from .compat import setup_databases, teardown_databases # xdist @@ -43,18 +48,11 @@ def _django_db_setup(request, if request.config.getvalue('nomigrations'): _disable_native_migrations() - db_args = {} with _django_cursor_wrapper: - if (request.config.getvalue('reuse_db') and - not request.config.getvalue('create_db')): - if get_django_version() >= (1, 8): - db_args['keepdb'] = True - else: - monkey_patch_creation_for_db_reuse() - - # Create the database - db_cfg = setup_databases(verbosity=pytest.config.option.verbose, - interactive=False, **db_args) + if request.config.getvalue('reuse_db') and not request.config.getvalue('create_db'): + db_cfg = _setup_reused_databases() + else: + db_cfg = setup_databases(verbosity=pytest.config.option.verbose, interactive=False,) def teardown_database(): with _django_cursor_wrapper: @@ -64,6 +62,29 @@ def teardown_database(): request.addfinalizer(teardown_database) +def _setup_reused_databases(): + from .compat import setup_databases + db_args = {} + + if get_django_version() >= (1, 8): + db_args['keepdb'] = True + else: + monkey_patch_creation_for_db_reuse() + + return setup_databases(verbosity=pytest.config.option.verbose, interactive=False, **db_args) + + +def _is_xdist_one_db_enabled(config): + from django.conf import settings + is_sqlite = (settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3') + + one_db = config.getvalue('xdist_one_db') + if one_db and is_sqlite: + raise ValueError("xdist-one-db option can not be used together with sqlite3 backend") + + return one_db + + def _django_db_fixture_helper(transactional, request, _django_cursor_wrapper): if is_django_unittest(request): return diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index d499e6f50..724de69dd 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -14,12 +14,13 @@ import py import pytest +from .db_reuse import monkey_patch_creation_for_db_reuse from .django_compat import is_django_unittest 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) -from .lazy_django import django_settings_is_configured, skip_if_no_django + transactional_db, _handle_south, _disable_native_migrations) +from .lazy_django import django_settings_is_configured, skip_if_no_django, get_django_version # Silence linters for imported fixtures. (_django_db_setup, _live_server_helper, admin_client, admin_user, client, db, @@ -44,6 +45,10 @@ def pytest_addoption(parser): action='store_true', dest='create_db', default=False, help='Re-create the database, even if it exists. This ' 'option will be ignored if not --reuse-db is given.') + group._addoption('--xdist-one-db', + dest='xdist_one_db', default=False, action='store_true', + help="Use only one database with xdist plugin. " + "Doesn't work with sqlite3 backend due to db lock") group._addoption('--ds', action='store', type='string', dest='ds', default=None, help='Set DJANGO_SETTINGS_MODULE.') @@ -246,6 +251,32 @@ def pytest_report_header(config): return [config._dsm_report_header] +def pytest_xdist_setupnodes(config): + """called once before any remote node is set up. """ + if not config.getvalue('xdist_one_db'): + return + _setup_django() + + from django.conf import settings + if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3': + return + + _handle_south() + + if config.getvalue('nomigrations'): + _disable_native_migrations() + + db_args = {} + if get_django_version() >= (1, 8): + db_args['keepdb'] = True + else: + monkey_patch_creation_for_db_reuse() + + from django.test.runner import setup_databases + # Create the database + setup_databases(verbosity=config.option.verbose, interactive=False, **db_args) + + @pytest.mark.trylast def pytest_configure(): # Allow Django settings to be configured in a user pytest_configure call, diff --git a/pytest_django_test/db_helpers.py b/pytest_django_test/db_helpers.py index f7460fce5..29afec6c3 100644 --- a/pytest_django_test/db_helpers.py +++ b/pytest_django_test/db_helpers.py @@ -48,6 +48,13 @@ def run_mysql(*args): return run_cmd(*args) +def skip_if_sqlite(): + from django.conf import settings + + if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3': + pytest.skip('Skip if sqlite3 backend') + + def skip_if_sqlite_in_memory(): from django.conf import settings diff --git a/tests/test_db_setup.py b/tests/test_db_setup.py index 05022db27..d5c149e88 100644 --- a/tests/test_db_setup.py +++ b/tests/test_db_setup.py @@ -5,7 +5,8 @@ from pytest_django.lazy_django import get_django_version from pytest_django_test.db_helpers import (db_exists, drop_database, mark_database, mark_exists, - skip_if_sqlite_in_memory) + skip_if_sqlite_in_memory, + skip_if_sqlite) skip_on_python32 = pytest.mark.skipif(sys.version_info[:2] == (3, 2), reason='xdist is flaky with Python 3.2') @@ -190,6 +191,41 @@ def test_d(settings): result.stdout.fnmatch_lines(['*PASSED*test_d*']) +@skip_on_python32 +def test_xdist_one_db(django_testdir): + skip_if_sqlite() + + drop_database('gw0') + drop_database('gw1') + + django_testdir.create_test_module(''' + import pytest + + from .app.models import Item + + def _check(settings): + # Make sure that the database name looks correct + db_name = settings.DATABASES['default']['NAME'] + assert 'gw' not in db_name + + @pytest.mark.django_db + def test_a(settings): + _check(settings) + + @pytest.mark.django_db + def test_b(settings): + _check(settings) + + ''') + + result = django_testdir.runpytest_subprocess('-vv', '-n2', '-s', '--xdist-one-db') + assert result.ret == 0 + result.stdout.fnmatch_lines(['*PASSED*test_a*']) + result.stdout.fnmatch_lines(['*PASSED*test_b*']) + + assert db_exists() + + class TestSqliteWithXdist: pytestmark = skip_on_python32 @@ -217,6 +253,20 @@ def test_a(): assert result.ret == 0 result.stdout.fnmatch_lines(['*PASSED*test_a*']) + def test_xdist_with_one_db_does_not_work_with_sqlite(self, django_testdir): + django_testdir.create_test_module(''' + import pytest + + @pytest.mark.django_db + def test_a(settings): + pass + ''') + + result = django_testdir.runpytest_subprocess('-vv', '-n1', '-s', '--xdist-one-db') + assert result.ret == 1 + result.stdout.fnmatch_lines(['*= 1 error*']) + result.stdout.fnmatch_lines(['*ValueError*']) + @pytest.mark.skipif(get_django_version() >= (1, 9), reason=('Django 1.9 requires migration and has no concept '