Skip to content

Commit 96a886c

Browse files
authored
Run test database through Docker (#1198)
This PR makes pytest control Docker, rather than rely on it being manually started. This makes it easier to run tests from scratch, guarantees separate database servers for parallel test runs, and allows us to vary the database more easily too.
1 parent 81097d4 commit 96a886c

5 files changed

Lines changed: 89 additions & 55 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,8 @@ jobs:
3434
- mariadb:11.4
3535
- mariadb:10.11
3636

37-
services:
38-
database:
39-
image: ${{ matrix.database }}
40-
env:
41-
MYSQL_ROOT_PASSWORD: hunter2
42-
ports:
43-
- 3306:3306
44-
options: --tmpfs /var/lib/mysql
45-
4637
env:
47-
DB_HOST: 127.0.0.1
48-
DB_USER: root
49-
DB_PASSWORD: hunter2
38+
DB_IMAGE: ${{ matrix.database }}
5039

5140
steps:
5241
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

CONTRIBUTING.rst

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,27 @@ Contributing
55
Run the tests
66
-------------
77

8-
1. Install `tox <https://tox.wiki/en/latest/>`__.
8+
1. Install `tox <https://tox.wiki/en/latest/>`__ and ensure Docker is running.
99

10-
2. Run a supported version of MySQL or MariaDB.
11-
This is easiest with the official Docker images.
12-
For example:
10+
2. Run the tests:
1311

1412
.. code-block:: console
1513
16-
docker run --detach --name mariadb -e MYSQL_ROOT_PASSWORD=hunter2 --publish 3306:3306 mariadb:11.6
14+
tox -e py314-django61
1715
18-
3. Run the tests by passing environment variables with your connection parameters.
19-
For the above Docker command:
16+
By default this uses the ``mariadb:11.4`` image.
17+
To use a different image, set ``DB_IMAGE``:
2018

2119
.. code-block:: console
2220
23-
DB_HOST=127.0.0.1 DB_USER=root DB_PASSWORD='hunter2' tox -e py313-django52
21+
DB_IMAGE=mysql:8.4 tox -e py314-django61
2422
25-
tox environments are split per Python and Django version.
23+
tox environments are split per Python and Django version.
2624

27-
You can run a subset of tests by passing them after ``--`` like:
28-
29-
.. code-block:: console
30-
31-
DB_HOST=127.0.0.1 DB_USER=root DB_PASSWORD='hunter2' tox -e py313-django51 -- tests/testapp/test_cache.py
32-
33-
You can also pass other pytest arguments after the ``--``.
34-
35-
4. When you’re done, shut down the Docker container with:
25+
You can run a subset of tests by passing them after ``--`` like:
3626

3727
.. code-block:: console
3828
39-
docker rm --force mariadb
29+
tox -e py314-django61 -- tests/testapp/test_cache.py
30+
31+
You can also pass other pytest arguments after the ``--``.

tests/conftest.py

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,77 @@
11
from __future__ import annotations
22

3+
import os
4+
import subprocess
5+
import time
36
import warnings
47

5-
import django
6-
from django.db import connection
7-
from pytest_django.plugin import blocking_manager_key
8+
import MySQLdb
9+
import pytest
10+
from django.conf import settings
811

912

10-
def pytest_report_header(config):
11-
dot_version = ".".join(str(x) for x in django.VERSION)
12-
header = "Django version: " + dot_version
13+
@pytest.fixture(scope="session", autouse=True)
14+
def mysql_server():
15+
image = os.environ.get("DB_IMAGE", "mariadb:11.4")
16+
name = f"django-mysql-test-{os.getpid()}"
1317

14-
pytest_django_db_blocker = config.stash[blocking_manager_key]
15-
with pytest_django_db_blocker.unblock(), connection._nodb_cursor() as cursor:
16-
cursor.execute("SELECT VERSION()")
17-
version = cursor.fetchone()[0]
18-
header += f"\nMySQL version: {version}"
18+
subprocess.run(
19+
[
20+
"docker",
21+
"run",
22+
"--detach",
23+
"--name",
24+
name,
25+
"-e",
26+
"MYSQL_ROOT_PASSWORD=hunter2",
27+
"--publish",
28+
"127.0.0.1::3306",
29+
image,
30+
],
31+
check=True,
32+
)
1933

20-
return header
34+
try:
35+
port = (
36+
subprocess.check_output(
37+
[
38+
"docker",
39+
"inspect",
40+
name,
41+
"--format",
42+
'{{(index (index .NetworkSettings.Ports "3306/tcp") 0).HostPort}}',
43+
]
44+
)
45+
.decode()
46+
.strip()
47+
)
48+
49+
for db in settings.DATABASES.values():
50+
if db["ENGINE"] != "django.db.backends.sqlite3":
51+
db["HOST"] = "127.0.0.1"
52+
db["PORT"] = port
53+
db["USER"] = "root"
54+
db["PASSWORD"] = "hunter2"
55+
56+
deadline = time.monotonic() + 60
57+
while True:
58+
try:
59+
conn = MySQLdb.connect(
60+
host="127.0.0.1",
61+
port=int(port),
62+
user="root",
63+
password="hunter2",
64+
)
65+
conn.close()
66+
break
67+
except MySQLdb.OperationalError:
68+
if time.monotonic() > deadline: # pragma: no cover
69+
raise RuntimeError("MySQL did not become ready in time")
70+
time.sleep(0.5)
71+
72+
yield
73+
finally:
74+
subprocess.run(["docker", "rm", "--force", name], check=True)
2175

2276

2377
# MySQL 5.7 warns about some sql mode changes

tests/settings.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,22 @@
1212
"default": {
1313
"ENGINE": "tests.db_backend",
1414
"NAME": "django_mysql",
15-
"USER": os.environ.get("DB_USER", ""),
16-
"PASSWORD": os.environ.get("DB_PASSWORD", ""),
17-
"HOST": os.environ.get("DB_HOST", ""),
18-
"PORT": os.environ.get("DB_PORT", ""),
15+
# Filled in by conftest.py
16+
"USER": "",
17+
"PASSWORD": "",
18+
"HOST": "",
19+
"PORT": "",
1920
"OPTIONS": {"charset": "utf8mb4"},
2021
"TEST": {"COLLATION": "utf8mb4_general_ci", "CHARSET": "utf8mb4"},
2122
},
2223
"other": {
2324
"ENGINE": "tests.db_backend",
2425
"NAME": "django_mysql2",
25-
"USER": os.environ.get("DB_USER", ""),
26-
"PASSWORD": os.environ.get("DB_PASSWORD", ""),
27-
"HOST": os.environ.get("DB_HOST", ""),
28-
"PORT": os.environ.get("DB_PORT", ""),
26+
# Filled in by conftest.py
27+
"USER": "",
28+
"PASSWORD": "",
29+
"HOST": "",
30+
"PORT": "",
2931
"OPTIONS": {"charset": "utf8mb4"},
3032
"TEST": {"COLLATION": "utf8mb4_general_ci", "CHARSET": "utf8mb4"},
3133
},

tox.ini

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ runner = uv-venv-lock-runner
1313
package = wheel
1414
wheel_build_env = .pkg
1515
pass_env =
16-
DB_HOST
17-
DB_PASSWORD
18-
DB_PORT
19-
DB_USER
16+
DB_IMAGE
2017
set_env =
2118
PYTHONDEVMODE = 1
2219
commands =

0 commit comments

Comments
 (0)