Skip to content

Commit d2973e2

Browse files
adamantikeblueyed
authored andcommitted
Rename test databases when running parallel Tox (pytest-dev#680)
When tests are executed using Tox in parallel, modify the test database names, to avoid name collisions between processes. It also handles projects where both `pytest-xdist` and parallel `tox` are being using, generating database names like `test_default_py37-django21_gw0`. Resolves pytest-dev#678.
1 parent c1bdb8d commit d2973e2

File tree

5 files changed

+194
-23
lines changed

5 files changed

+194
-23
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ _build
99
.env
1010
/.coverage.*
1111
/.coverage
12+
/coverage.xml
1213
/htmlcov/
1314
.cache
1415
.pytest_cache/

Diff for: docs/database.rst

+25-3
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
""""""""""""""""""""""""

Diff for: pytest_django/fixtures.py

+37-19
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
40-
41-
for db_settings in settings.DATABASES.values():
42-
43-
try:
44-
test_name = db_settings["TEST"]["NAME"]
45-
except KeyError:
46-
test_name = None
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)
4743

48-
if not test_name:
49-
if db_settings["ENGINE"] == "django.db.backends.sqlite3":
50-
continue
5144

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

@@ -169,6 +169,24 @@ def handle(self, *args, **kwargs):
169169
migrate.Command = MigrateSilentCommand
170170

171171

172+
def _set_suffix_to_test_databases(suffix):
173+
from django.conf import settings
174+
175+
for db_settings in settings.DATABASES.values():
176+
test_name = db_settings.get("TEST", {}).get("NAME")
177+
178+
if not test_name:
179+
if db_settings["ENGINE"] == "django.db.backends.sqlite3":
180+
continue
181+
test_name = "test_{}".format(db_settings["NAME"])
182+
183+
if test_name == ":memory:":
184+
continue
185+
186+
db_settings.setdefault("TEST", {})
187+
db_settings["TEST"]["NAME"] = "{}_{}".format(test_name, suffix)
188+
189+
172190
# ############### User visible fixtures ################
173191

174192

Diff for: pytest_django/plugin.py

+2
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

Diff for: tests/test_db_setup.py

+129-1
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,135 @@ def test_a():
288288
289289
assert conn_db2.vendor == 'sqlite'
290290
db_name = conn_db2.creation._get_test_db_name()
291-
assert 'test_custom_db_name_gw' in db_name
291+
assert db_name.startswith('test_custom_db_name_gw')
292+
"""
293+
)
294+
295+
result = django_testdir.runpytest_subprocess("--tb=short", "-vv", "-n1")
296+
assert result.ret == 0
297+
result.stdout.fnmatch_lines(["*PASSED*test_a*"])
298+
299+
300+
class TestSqliteWithTox:
301+
302+
db_settings = {
303+
"default": {
304+
"ENGINE": "django.db.backends.sqlite3",
305+
"NAME": "db_name",
306+
"TEST": {"NAME": "test_custom_db_name"},
307+
}
308+
}
309+
310+
def test_db_with_tox_suffix(self, django_testdir, monkeypatch):
311+
"A test to check that Tox DB suffix works when running in parallel."
312+
monkeypatch.setenv("TOX_PARALLEL_ENV", "py37-django22")
313+
314+
django_testdir.create_test_module(
315+
"""
316+
import pytest
317+
from django.db import connections
318+
319+
@pytest.mark.django_db
320+
def test_inner():
321+
322+
(conn, ) = connections.all()
323+
324+
assert conn.vendor == 'sqlite'
325+
db_name = conn.creation._get_test_db_name()
326+
assert db_name == 'test_custom_db_name_py37-django22'
327+
"""
328+
)
329+
330+
result = django_testdir.runpytest_subprocess("--tb=short", "-vv")
331+
assert result.ret == 0
332+
result.stdout.fnmatch_lines(["*test_inner*PASSED*"])
333+
334+
def test_db_with_empty_tox_suffix(self, django_testdir, monkeypatch):
335+
"A test to check that Tox DB suffix is not used when suffix would be empty."
336+
monkeypatch.setenv("TOX_PARALLEL_ENV", "")
337+
338+
django_testdir.create_test_module(
339+
"""
340+
import pytest
341+
from django.db import connections
342+
343+
@pytest.mark.django_db
344+
def test_inner():
345+
346+
(conn,) = connections.all()
347+
348+
assert conn.vendor == 'sqlite'
349+
db_name = conn.creation._get_test_db_name()
350+
assert db_name == 'test_custom_db_name'
351+
"""
352+
)
353+
354+
result = django_testdir.runpytest_subprocess("--tb=short", "-vv")
355+
assert result.ret == 0
356+
result.stdout.fnmatch_lines(["*test_inner*PASSED*"])
357+
358+
359+
class TestSqliteWithToxAndXdist:
360+
361+
db_settings = {
362+
"default": {
363+
"ENGINE": "django.db.backends.sqlite3",
364+
"NAME": "db_name",
365+
"TEST": {"NAME": "test_custom_db_name"},
366+
}
367+
}
368+
369+
def test_db_with_tox_suffix(self, django_testdir, monkeypatch):
370+
"A test to check that both Tox and xdist suffixes work together."
371+
pytest.importorskip("xdist")
372+
monkeypatch.setenv("TOX_PARALLEL_ENV", "py37-django22")
373+
374+
django_testdir.create_test_module(
375+
"""
376+
import pytest
377+
from django.db import connections
378+
379+
@pytest.mark.django_db
380+
def test_inner():
381+
382+
(conn, ) = connections.all()
383+
384+
assert conn.vendor == 'sqlite'
385+
db_name = conn.creation._get_test_db_name()
386+
assert db_name.startswith('test_custom_db_name_py37-django22_gw')
387+
"""
388+
)
389+
390+
result = django_testdir.runpytest_subprocess("--tb=short", "-vv", "-n1")
391+
assert result.ret == 0
392+
result.stdout.fnmatch_lines(["*PASSED*test_inner*"])
393+
394+
395+
class TestSqliteInMemoryWithXdist:
396+
397+
db_settings = {
398+
"default": {
399+
"ENGINE": "django.db.backends.sqlite3",
400+
"NAME": ":memory:",
401+
"TEST": {"NAME": ":memory:"},
402+
}
403+
}
404+
405+
def test_sqlite_in_memory_used(self, django_testdir):
406+
pytest.importorskip("xdist")
407+
408+
django_testdir.create_test_module(
409+
"""
410+
import pytest
411+
from django.db import connections
412+
413+
@pytest.mark.django_db
414+
def test_a():
415+
(conn, ) = connections.all()
416+
417+
assert conn.vendor == 'sqlite'
418+
db_name = conn.creation._get_test_db_name()
419+
assert 'file:memorydb' in db_name or db_name == ':memory:'
292420
"""
293421
)
294422

0 commit comments

Comments
 (0)