Skip to content

Commit bbe5b8b

Browse files
committed
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.
1 parent f55c709 commit bbe5b8b

File tree

4 files changed

+168
-24
lines changed

4 files changed

+168
-24
lines changed

docs/database.rst

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,21 +191,43 @@ If you need to customize the location of your test database, this is the
191191
fixture you want to override.
192192

193193
The default implementation of this fixture requests the
194-
:fixture:`django_db_modify_db_settings_xdist_suffix` to provide compatibility
194+
:fixture:`django_db_modify_db_settings_parallel_suffix` to provide compatibility
195195
with pytest-xdist.
196196

197197
This fixture is by default requested from :fixture:`django_db_setup`.
198198

199+
django_db_modify_db_settings_parallel_suffix
200+
""""""""""""""""""""""""""""""""""""""""""""
201+
202+
.. fixture:: django_db_modify_db_settings_parallel_suffix
203+
204+
Requesting this fixture will add a suffix to the database name when the tests
205+
are run via `pytest-xdist`, or via `tox` in parallel mode.
206+
207+
This fixture is by default requested from
208+
:fixture:`django_db_modify_db_settings`.
209+
210+
django_db_modify_db_settings_tox_suffix
211+
"""""""""""""""""""""""""""""""""""""""
212+
213+
.. fixture:: django_db_modify_db_settings_tox_suffix
214+
215+
Requesting this fixture will add a suffix to the database name when the tests
216+
are run via `tox` in parallel mode.
217+
218+
This fixture is by default requested from
219+
:fixture:`django_db_modify_db_settings_parallel_suffix`.
220+
199221
django_db_modify_db_settings_xdist_suffix
200222
"""""""""""""""""""""""""""""""""""""""""
201223

202224
.. fixture:: django_db_modify_db_settings_xdist_suffix
203225

204226
Requesting this fixture will add a suffix to the database name when the tests
205-
are run via pytest-xdist.
227+
are run via `pytest-xdist`.
206228

207229
This fixture is by default requested from
208-
:fixture:`django_db_modify_db_settings`.
230+
:fixture:`django_db_modify_db_settings_parallel_suffix`.
209231

210232
django_db_use_migrations
211233
""""""""""""""""""""""""

pytest_django/fixtures.py

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,35 +33,35 @@
3333

3434

3535
@pytest.fixture(scope="session")
36-
def django_db_modify_db_settings_xdist_suffix(request):
36+
def django_db_modify_db_settings_tox_suffix(request):
3737
skip_if_no_django()
3838

39-
from django.conf import settings
39+
tox_environment = os.getenv("TOX_PARALLEL_ENV")
40+
if tox_environment:
41+
# Put a suffix like _py27-django21 on tox workers
42+
_set_suffix_to_test_databases(suffix=tox_environment)
4043

41-
for db_settings in settings.DATABASES.values():
4244

43-
try:
44-
test_name = db_settings["TEST"]["NAME"]
45-
except KeyError:
46-
test_name = None
47-
48-
if not test_name:
49-
if db_settings["ENGINE"] == "django.db.backends.sqlite3":
50-
continue
51-
52-
test_name = "test_{}".format(db_settings["NAME"])
45+
@pytest.fixture(scope="session")
46+
def django_db_modify_db_settings_xdist_suffix(request):
47+
skip_if_no_django()
5348

49+
xdist_suffix = getattr(request.config, "slaveinput", {}).get("slaveid")
50+
if xdist_suffix:
5451
# Put a suffix like _gw0, _gw1 etc on xdist processes
55-
xdist_suffix = getattr(request.config, "slaveinput", {}).get("slaveid")
56-
if test_name != ":memory:" and xdist_suffix is not None:
57-
test_name = "{}_{}".format(test_name, xdist_suffix)
52+
_set_suffix_to_test_databases(suffix=xdist_suffix)
5853

59-
db_settings.setdefault("TEST", {})
60-
db_settings["TEST"]["NAME"] = test_name
54+
55+
@pytest.fixture(scope="session")
56+
def django_db_modify_db_settings_parallel_suffix(
57+
django_db_modify_db_settings_tox_suffix,
58+
django_db_modify_db_settings_xdist_suffix,
59+
):
60+
skip_if_no_django()
6161

6262

6363
@pytest.fixture(scope="session")
64-
def django_db_modify_db_settings(django_db_modify_db_settings_xdist_suffix):
64+
def django_db_modify_db_settings(django_db_modify_db_settings_parallel_suffix):
6565
skip_if_no_django()
6666

6767

@@ -153,6 +153,31 @@ def _disable_native_migrations():
153153
settings.MIGRATION_MODULES = DisableMigrations()
154154

155155

156+
def _set_suffix_to_test_databases(suffix):
157+
if not suffix:
158+
return
159+
160+
from django.conf import settings
161+
162+
for db_settings in settings.DATABASES.values():
163+
164+
try:
165+
test_name = db_settings["TEST"]["NAME"]
166+
except KeyError:
167+
test_name = None
168+
169+
if not test_name:
170+
if db_settings["ENGINE"] == "django.db.backends.sqlite3":
171+
continue
172+
test_name = "test_{}".format(db_settings["NAME"])
173+
174+
if test_name == ":memory:":
175+
continue
176+
177+
db_settings.setdefault("TEST", {})
178+
db_settings["TEST"]["NAME"] = "{}_{}".format(test_name, suffix)
179+
180+
156181
# ############### User visible fixtures ################
157182

158183

pytest_django/plugin.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
from .fixtures import django_db_keepdb # noqa
2323
from .fixtures import django_db_createdb # noqa
2424
from .fixtures import django_db_modify_db_settings # noqa
25+
from .fixtures import django_db_modify_db_settings_parallel_suffix # noqa
26+
from .fixtures import django_db_modify_db_settings_tox_suffix # noqa
2527
from .fixtures import django_db_modify_db_settings_xdist_suffix # noqa
2628
from .fixtures import _live_server_helper # noqa
2729
from .fixtures import admin_client # noqa

tests/test_db_setup.py

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def test_a():
206206
207207
assert conn.vendor == 'sqlite'
208208
db_name = conn.creation._get_test_db_name()
209-
assert 'file:memorydb' in db_name or db_name == ':memory:'
209+
assert db_name.startswith('file:memorydb')
210210
"""
211211
)
212212

@@ -250,7 +250,7 @@ def test_a():
250250
251251
assert conn_db2.vendor == 'sqlite'
252252
db_name = conn_db2.creation._get_test_db_name()
253-
assert 'test_custom_db_name_gw' in db_name
253+
assert db_name.startswith('test_custom_db_name_gw')
254254
"""
255255
)
256256

@@ -259,6 +259,101 @@ def test_a():
259259
result.stdout.fnmatch_lines(["*PASSED*test_a*"])
260260

261261

262+
class TestSqliteWithTox:
263+
264+
db_settings = {
265+
"default": {
266+
"ENGINE": "django.db.backends.sqlite3",
267+
"NAME": "db_name",
268+
"TEST": {"NAME": "test_custom_db_name"},
269+
}
270+
}
271+
272+
def test_db_with_tox_suffix(self, django_testdir, monkeypatch):
273+
"A test to check that Tox DB suffix works when running in parallel."
274+
monkeypatch.setenv("TOX_PARALLEL_ENV", "py37-django21")
275+
276+
django_testdir.create_test_module(
277+
"""
278+
import pytest
279+
from django.db import connections
280+
281+
@pytest.mark.django_db
282+
def test_inner():
283+
284+
(conn, ) = connections.all()
285+
286+
assert conn.vendor == 'sqlite'
287+
db_name = conn.creation._get_test_db_name()
288+
assert db_name == 'test_custom_db_name_py37-django21'
289+
"""
290+
)
291+
292+
result = django_testdir.runpytest_subprocess("--tb=short", "-vv")
293+
assert result.ret == 0
294+
result.stdout.fnmatch_lines(["*test_inner*PASSED*"])
295+
296+
def test_db_with_empty_tox_suffix(self, django_testdir, monkeypatch):
297+
"A test to check that Tox DB suffix is not used when suffix would be empty."
298+
monkeypatch.setenv("TOX_PARALLEL_ENV", "")
299+
300+
django_testdir.create_test_module(
301+
"""
302+
import pytest
303+
from django.db import connections
304+
305+
@pytest.mark.django_db
306+
def test_inner():
307+
308+
(conn,) = connections.all()
309+
310+
assert conn.vendor == 'sqlite'
311+
db_name = conn.creation._get_test_db_name()
312+
assert db_name == 'test_custom_db_name'
313+
"""
314+
)
315+
316+
result = django_testdir.runpytest_subprocess("--tb=short", "-vv")
317+
assert result.ret == 0
318+
result.stdout.fnmatch_lines(["*test_inner*PASSED*"])
319+
320+
321+
class TestSqliteWithToxAndXdist:
322+
323+
db_settings = {
324+
"default": {
325+
"ENGINE": "django.db.backends.sqlite3",
326+
"NAME": "db_name",
327+
"TEST": {"NAME": "test_custom_db_name"},
328+
}
329+
}
330+
331+
def test_db_with_tox_suffix(self, django_testdir, monkeypatch):
332+
"A test to check that both Tox and xdist suffixes work together."
333+
pytest.importorskip("xdist")
334+
monkeypatch.setenv("TOX_PARALLEL_ENV", "py37-django21")
335+
336+
django_testdir.create_test_module(
337+
"""
338+
import pytest
339+
from django.db import connections
340+
341+
@pytest.mark.django_db
342+
def test_inner():
343+
344+
(conn, ) = connections.all()
345+
346+
assert conn.vendor == 'sqlite'
347+
db_name = conn.creation._get_test_db_name()
348+
assert db_name.startswith('test_custom_db_name_py37-django21_gw')
349+
"""
350+
)
351+
352+
result = django_testdir.runpytest_subprocess("--tb=short", "-vv", "-n1")
353+
assert result.ret == 0
354+
result.stdout.fnmatch_lines(["*PASSED*test_inner*"])
355+
356+
262357
@pytest.mark.skipif(
263358
get_django_version() >= (1, 9),
264359
reason=(

0 commit comments

Comments
 (0)