diff --git a/.gitignore b/.gitignore index c5872bc1f..90131acf9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ _build .env /.coverage.* /.coverage +/coverage.xml /htmlcov/ .cache .pytest_cache/ diff --git a/docs/database.rst b/docs/database.rst index 36d2248ee..c0fa9f0d6 100644 --- a/docs/database.rst +++ b/docs/database.rst @@ -191,21 +191,43 @@ If you need to customize the location of your test database, this is the fixture you want to override. The default implementation of this fixture requests the -:fixture:`django_db_modify_db_settings_xdist_suffix` to provide compatibility +:fixture:`django_db_modify_db_settings_parallel_suffix` to provide compatibility with pytest-xdist. This fixture is by default requested from :fixture:`django_db_setup`. +django_db_modify_db_settings_parallel_suffix +"""""""""""""""""""""""""""""""""""""""""""" + +.. fixture:: django_db_modify_db_settings_parallel_suffix + +Requesting this fixture will add a suffix to the database name when the tests +are run via `pytest-xdist`, or via `tox` in parallel mode. + +This fixture is by default requested from +:fixture:`django_db_modify_db_settings`. + +django_db_modify_db_settings_tox_suffix +""""""""""""""""""""""""""""""""""""""" + +.. fixture:: django_db_modify_db_settings_tox_suffix + +Requesting this fixture will add a suffix to the database name when the tests +are run via `tox` in parallel mode. + +This fixture is by default requested from +:fixture:`django_db_modify_db_settings_parallel_suffix`. + django_db_modify_db_settings_xdist_suffix """"""""""""""""""""""""""""""""""""""""" .. fixture:: django_db_modify_db_settings_xdist_suffix Requesting this fixture will add a suffix to the database name when the tests -are run via pytest-xdist. +are run via `pytest-xdist`. This fixture is by default requested from -:fixture:`django_db_modify_db_settings`. +:fixture:`django_db_modify_db_settings_parallel_suffix`. django_db_use_migrations """""""""""""""""""""""" diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index 02dc619a5..519522c80 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -33,35 +33,35 @@ @pytest.fixture(scope="session") -def django_db_modify_db_settings_xdist_suffix(request): +def django_db_modify_db_settings_tox_suffix(request): skip_if_no_django() - from django.conf import settings - - for db_settings in settings.DATABASES.values(): - - try: - test_name = db_settings["TEST"]["NAME"] - except KeyError: - test_name = None + tox_environment = os.getenv("TOX_PARALLEL_ENV") + if tox_environment: + # Put a suffix like _py27-django21 on tox workers + _set_suffix_to_test_databases(suffix=tox_environment) - if not test_name: - if db_settings["ENGINE"] == "django.db.backends.sqlite3": - continue - test_name = "test_{}".format(db_settings["NAME"]) +@pytest.fixture(scope="session") +def django_db_modify_db_settings_xdist_suffix(request): + skip_if_no_django() + xdist_suffix = getattr(request.config, "slaveinput", {}).get("slaveid") + if xdist_suffix: # Put a suffix like _gw0, _gw1 etc on xdist processes - xdist_suffix = getattr(request.config, "slaveinput", {}).get("slaveid") - if test_name != ":memory:" and xdist_suffix is not None: - test_name = "{}_{}".format(test_name, xdist_suffix) + _set_suffix_to_test_databases(suffix=xdist_suffix) - db_settings.setdefault("TEST", {}) - db_settings["TEST"]["NAME"] = test_name + +@pytest.fixture(scope="session") +def django_db_modify_db_settings_parallel_suffix( + django_db_modify_db_settings_tox_suffix, + django_db_modify_db_settings_xdist_suffix, +): + skip_if_no_django() @pytest.fixture(scope="session") -def django_db_modify_db_settings(django_db_modify_db_settings_xdist_suffix): +def django_db_modify_db_settings(django_db_modify_db_settings_parallel_suffix): skip_if_no_django() @@ -169,6 +169,24 @@ def handle(self, *args, **kwargs): migrate.Command = MigrateSilentCommand +def _set_suffix_to_test_databases(suffix): + from django.conf import settings + + for db_settings in settings.DATABASES.values(): + test_name = db_settings.get("TEST", {}).get("NAME") + + if not test_name: + if db_settings["ENGINE"] == "django.db.backends.sqlite3": + continue + test_name = "test_{}".format(db_settings["NAME"]) + + if test_name == ":memory:": + continue + + db_settings.setdefault("TEST", {}) + db_settings["TEST"]["NAME"] = "{}_{}".format(test_name, suffix) + + # ############### User visible fixtures ################ diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 1fd1686ff..8ac10fb55 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -22,6 +22,8 @@ from .fixtures import django_db_keepdb # noqa from .fixtures import django_db_createdb # noqa from .fixtures import django_db_modify_db_settings # noqa +from .fixtures import django_db_modify_db_settings_parallel_suffix # noqa +from .fixtures import django_db_modify_db_settings_tox_suffix # noqa from .fixtures import django_db_modify_db_settings_xdist_suffix # noqa from .fixtures import _live_server_helper # noqa from .fixtures import admin_client # noqa diff --git a/tests/test_db_setup.py b/tests/test_db_setup.py index 0b3d516e0..4da897688 100644 --- a/tests/test_db_setup.py +++ b/tests/test_db_setup.py @@ -288,7 +288,135 @@ def test_a(): assert conn_db2.vendor == 'sqlite' db_name = conn_db2.creation._get_test_db_name() - assert 'test_custom_db_name_gw' in db_name + assert db_name.startswith('test_custom_db_name_gw') + """ + ) + + result = django_testdir.runpytest_subprocess("--tb=short", "-vv", "-n1") + assert result.ret == 0 + result.stdout.fnmatch_lines(["*PASSED*test_a*"]) + + +class TestSqliteWithTox: + + db_settings = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": "db_name", + "TEST": {"NAME": "test_custom_db_name"}, + } + } + + def test_db_with_tox_suffix(self, django_testdir, monkeypatch): + "A test to check that Tox DB suffix works when running in parallel." + monkeypatch.setenv("TOX_PARALLEL_ENV", "py37-django22") + + django_testdir.create_test_module( + """ + import pytest + from django.db import connections + + @pytest.mark.django_db + def test_inner(): + + (conn, ) = connections.all() + + assert conn.vendor == 'sqlite' + db_name = conn.creation._get_test_db_name() + assert db_name == 'test_custom_db_name_py37-django22' + """ + ) + + result = django_testdir.runpytest_subprocess("--tb=short", "-vv") + assert result.ret == 0 + result.stdout.fnmatch_lines(["*test_inner*PASSED*"]) + + def test_db_with_empty_tox_suffix(self, django_testdir, monkeypatch): + "A test to check that Tox DB suffix is not used when suffix would be empty." + monkeypatch.setenv("TOX_PARALLEL_ENV", "") + + django_testdir.create_test_module( + """ + import pytest + from django.db import connections + + @pytest.mark.django_db + def test_inner(): + + (conn,) = connections.all() + + assert conn.vendor == 'sqlite' + db_name = conn.creation._get_test_db_name() + assert db_name == 'test_custom_db_name' + """ + ) + + result = django_testdir.runpytest_subprocess("--tb=short", "-vv") + assert result.ret == 0 + result.stdout.fnmatch_lines(["*test_inner*PASSED*"]) + + +class TestSqliteWithToxAndXdist: + + db_settings = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": "db_name", + "TEST": {"NAME": "test_custom_db_name"}, + } + } + + def test_db_with_tox_suffix(self, django_testdir, monkeypatch): + "A test to check that both Tox and xdist suffixes work together." + pytest.importorskip("xdist") + monkeypatch.setenv("TOX_PARALLEL_ENV", "py37-django22") + + django_testdir.create_test_module( + """ + import pytest + from django.db import connections + + @pytest.mark.django_db + def test_inner(): + + (conn, ) = connections.all() + + assert conn.vendor == 'sqlite' + db_name = conn.creation._get_test_db_name() + assert db_name.startswith('test_custom_db_name_py37-django22_gw') + """ + ) + + result = django_testdir.runpytest_subprocess("--tb=short", "-vv", "-n1") + assert result.ret == 0 + result.stdout.fnmatch_lines(["*PASSED*test_inner*"]) + + +class TestSqliteInMemoryWithXdist: + + db_settings = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + "TEST": {"NAME": ":memory:"}, + } + } + + def test_sqlite_in_memory_used(self, django_testdir): + pytest.importorskip("xdist") + + django_testdir.create_test_module( + """ + import pytest + from django.db import connections + + @pytest.mark.django_db + def test_a(): + (conn, ) = connections.all() + + assert conn.vendor == 'sqlite' + db_name = conn.creation._get_test_db_name() + assert 'file:memorydb' in db_name or db_name == ':memory:' """ )