From 3da74373f7f33036d9db962b0ae9d8b9a0c39f5b Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Mon, 17 Dec 2018 16:21:53 -0300 Subject: [PATCH] Rename test databases when running parallel Tox When tests are executed using Tox in parallel, modify the test database names, to avoid name collisions between processes. This change renames the existing `django_db_modify_db_settings_xdist_suffix` fixture, to a generic `django_db_modify_db_settings_parallel_suffix` one, in case more scenarios/tools have to be considered in the future. It also handles projects where both `pytest-xdist` and parallel `tox` are being using, generating database names like `test_default_py37-django21_gw0`. Resolves #678. --- .gitignore | 1 + docs/database.rst | 28 +++++++- pytest_django/fixtures.py | 56 ++++++++++------ pytest_django/plugin.py | 2 + tests/test_db_setup.py | 130 +++++++++++++++++++++++++++++++++++++- 5 files changed, 194 insertions(+), 23 deletions(-) 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:' """ )