Skip to content

Commit ed27999

Browse files
authored
Merge pull request #757 from WisdomPill/parallel_tests
2 parents c076898 + f9f5d0c commit ed27999

25 files changed

+577
-546
lines changed

.github/workflows/ci.yml

+5-10
Original file line numberDiff line numberDiff line change
@@ -78,18 +78,13 @@ jobs:
7878
- name: Install dependencies
7979
run: |
8080
python -m pip install --upgrade pip
81-
python -m pip install --upgrade tox tox-gh-actions
81+
python -m pip install --upgrade tox-uv tox-gh-actions
82+
83+
- name: Docker compose up
84+
run: docker compose -f docker/docker-compose.yml up -d --wait
8285

8386
- name: Tox tests
84-
run: |
85-
REDIS_PRIMARY=$(tests/start_redis.sh)
86-
REDIS_SENTINEL=$(tests/start_redis.sh --sentinel)
87-
CONTAINERS="$REDIS_PRIMARY $REDIS_SENTINEL"
88-
trap "docker stop $CONTAINERS && docker rm $CONTAINERS" EXIT
89-
tests/wait_for_redis.sh $REDIS_PRIMARY 6379
90-
tests/wait_for_redis.sh $REDIS_SENTINEL 26379
91-
92-
tox
87+
run: tox
9388
env:
9489
DJANGO: ${{ matrix.django-version }}
9590
REDIS: ${{ matrix.redis-version }}

changelog.d/757.misc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Speed up tests by using `pytest-xdist` and separating settings on different redis databases.
2+
Dropped `pytest-django`
3+
Using `docker-compose` for setting up redis containers for testing
4+
Use `tox-uv`

docker/docker-compose.yml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
services:
2+
3+
redis:
4+
image: redis:latest
5+
container_name: redis-primary
6+
command: redis-server --enable-debug-command yes --protected-mode no
7+
ports:
8+
- 6379:6379
9+
healthcheck:
10+
test: redis-cli ping
11+
interval: 5s
12+
timeout: 5s
13+
retries: 5
14+
15+
sentinel:
16+
image: redis:latest
17+
container_name: redis-sentinel
18+
depends_on:
19+
redis:
20+
condition: service_healthy
21+
entrypoint: "redis-sentinel /redis.conf --port 26379"
22+
ports:
23+
- 26379:26379
24+
volumes:
25+
- "./sentinel.conf:/redis.conf"
26+
healthcheck:
27+
test: redis-cli -p 26379 ping
28+
interval: 5s
29+
timeout: 5s
30+
retries: 5

docker/sentinel.conf

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
sentinel monitor default_service 127.0.0.1 6379 1
2+
sentinel down-after-milliseconds default_service 3200
3+
sentinel failover-timeout default_service 10000
4+
sentinel parallel-syncs default_service 1

setup.cfg

+7-25
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,7 @@ REDIS =
8585
[testenv]
8686
passenv = CI, GITHUB*
8787
commands =
88-
{envpython} -m pytest --cov-report= --ds=settings.sqlite {posargs}
89-
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_herd {posargs}
90-
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_json {posargs}
91-
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_lz4 {posargs}
92-
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_msgpack {posargs}
93-
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_sentinel {posargs}
94-
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_sentinel_opts {posargs}
95-
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_sharding {posargs}
96-
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_usock {posargs}
97-
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_zlib {posargs}
98-
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_zstd {posargs}
99-
{envpython} -m pytest --cov-append --cov-report= --ds=settings.sqlite_gzip {posargs}
100-
{envpython} -m coverage report
101-
{envpython} -m coverage xml
88+
{envpython} -m pytest -n 4 {posargs}
10289

10390
deps =
10491
dj42: Django>=4.2,<5.0
@@ -108,9 +95,9 @@ deps =
10895
msgpack>=0.6.0
10996
pytest
11097
pytest-cov
111-
pytest-django
112-
pytest-pythonpath
11398
pytest-mock
99+
pytest-pythonpath
100+
pytest-xdist
114101
redismaster: https://github.com/redis/redis-py/archive/master.tar.gz
115102
lz4>=0.15
116103
pyzstd>=0.15
@@ -119,7 +106,7 @@ deps =
119106
basepython = python3
120107
envdir={toxworkdir}/lint
121108
commands =
122-
black: black --target-version py36 {posargs:--check --diff} setup.py django_redis/ tests/
109+
black: black --target-version py38 {posargs:--check --diff} setup.py django_redis/ tests/
123110
ruff: ruff {posargs:check --show-fixes} django_redis/ tests/
124111
mypy: mypy {posargs:--cobertura-xml-report .} django_redis tests
125112
deps =
@@ -130,24 +117,16 @@ deps =
130117
mypy
131118
# typing dependencies
132119
pytest
133-
pytest-django
134120
pytest-mock
135121
types-redis
136122
skip_install = true
137123

138124
[tool:pytest]
139-
DJANGO_SETTINGS_MODULE = settings.sqlite
140-
141125
addopts =
142126
--doctest-modules
143127
--cov=django_redis
144128
--cov-config=setup.cfg
145129
--no-cov-on-fail
146-
filterwarnings =
147-
error::DeprecationWarning
148-
error::FutureWarning
149-
error::PendingDeprecationWarning
150-
ignore:.*distutils package is deprecated.*:DeprecationWarning
151130
pythonpath = tests
152131
testpaths = tests
153132
xfail_strict = true
@@ -171,6 +150,9 @@ ignore_missing_settings = true
171150
[mypy-lz4.frame]
172151
ignore_missing_imports = true
173152

153+
[mypy-xdist.scheduler]
154+
ignore_missing_imports = true
155+
174156
[mypy-pyzstd]
175157
ignore_missing_imports = true
176158

tests/README.rst

+1-14
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,4 @@ Running the test suite
44
.. code-block:: bash
55
66
# start redis and a sentinel (uses docker with image redis:latest)
7-
PRIMARY=$(tests/start_redis.sh)
8-
SENTINEL=$(tests/start_redis.sh --sentinel)
9-
10-
# or just wait 5 - 10 seconds and most likely this would be the case
11-
tests/wait_for_redis.sh $PRIMARY 6379
12-
tests/wait_for_redis.sh $SENTINEL 26379
13-
14-
# run the tests
15-
tox
16-
17-
# shut down redis
18-
for container in $PRIMARY $SENTINEL; do
19-
docker stop $container && docker rm $container
20-
done
7+
docker compose -f docker/docker-compose.yml up -d --wait

tests/conftest.py

+59-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,68 @@
1+
import sys
2+
from os import environ
3+
from pathlib import Path
14
from typing import Iterable
25

36
import pytest
4-
from django.core.cache import cache as default_cache
7+
from xdist.scheduler import LoadScopeScheduling
58

69
from django_redis.cache import BaseCache
10+
from tests.settings_wrapper import SettingsWrapper
711

812

9-
@pytest.fixture
10-
def cache() -> Iterable[BaseCache]:
13+
class FixtureScheduling(LoadScopeScheduling):
14+
"""Split by [] value. This is very hackish and might blow up any time!"""
15+
16+
def _split_scope(self, nodeid):
17+
if "[sqlite" in nodeid:
18+
return nodeid.rsplit("[")[-1].replace("]", "")
19+
return None
20+
21+
22+
def pytest_xdist_make_scheduler(log, config):
23+
return FixtureScheduling(config, log)
24+
25+
26+
def pytest_configure(config):
27+
sys.path.insert(0, str(Path(__file__).absolute().parent))
28+
29+
30+
@pytest.fixture()
31+
def settings():
32+
"""A Django settings object which restores changes after the testrun"""
33+
wrapper = SettingsWrapper()
34+
yield wrapper
35+
wrapper.finalize()
36+
37+
38+
@pytest.fixture()
39+
def cache(cache_settings: str) -> Iterable[BaseCache]:
40+
from django import setup
41+
42+
environ["DJANGO_SETTINGS_MODULE"] = f"settings.{cache_settings}"
43+
setup()
44+
45+
from django.core.cache import cache as default_cache
46+
1147
yield default_cache
1248
default_cache.clear()
49+
50+
51+
def pytest_generate_tests(metafunc):
52+
if "cache" in metafunc.fixturenames or "session" in metafunc.fixturenames:
53+
# Mark
54+
settings = [
55+
"sqlite",
56+
"sqlite_gzip",
57+
"sqlite_herd",
58+
"sqlite_json",
59+
"sqlite_lz4",
60+
"sqlite_msgpack",
61+
"sqlite_sentinel",
62+
"sqlite_sentinel_opts",
63+
"sqlite_sharding",
64+
"sqlite_usock",
65+
"sqlite_zlib",
66+
"sqlite_zstd",
67+
]
68+
metafunc.parametrize("cache_settings", settings)

tests/settings/sqlite_gzip.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,31 @@
33
CACHES = {
44
"default": {
55
"BACKEND": "django_redis.cache.RedisCache",
6-
"LOCATION": ["redis://127.0.0.1:6379?db=1", "redis://127.0.0.1:6379?db=1"],
6+
"LOCATION": ["redis://127.0.0.1:6379?db=2", "redis://127.0.0.1:6379?db=2"],
77
"OPTIONS": {
88
"CLIENT_CLASS": "django_redis.client.DefaultClient",
99
"COMPRESSOR": "django_redis.compressors.gzip.GzipCompressor",
1010
},
1111
},
1212
"doesnotexist": {
1313
"BACKEND": "django_redis.cache.RedisCache",
14-
"LOCATION": "redis://127.0.0.1:56379?db=1",
14+
"LOCATION": "redis://127.0.0.1:56379?db=2",
1515
"OPTIONS": {
1616
"CLIENT_CLASS": "django_redis.client.DefaultClient",
1717
"COMPRESSOR": "django_redis.compressors.gzip.GzipCompressor",
1818
},
1919
},
2020
"sample": {
2121
"BACKEND": "django_redis.cache.RedisCache",
22-
"LOCATION": "redis://127.0.0.1:6379?db=1,redis://127.0.0.1:6379?db=1",
22+
"LOCATION": "redis://127.0.0.1:6379?db=2,redis://127.0.0.1:6379?db=2",
2323
"OPTIONS": {
2424
"CLIENT_CLASS": "django_redis.client.DefaultClient",
2525
"COMPRESSOR": "django_redis.compressors.gzip.GzipCompressor",
2626
},
2727
},
2828
"with_prefix": {
2929
"BACKEND": "django_redis.cache.RedisCache",
30-
"LOCATION": "redis://127.0.0.1:6379?db=1",
30+
"LOCATION": "redis://127.0.0.1:6379?db=2",
3131
"OPTIONS": {
3232
"CLIENT_CLASS": "django_redis.client.DefaultClient",
3333
"COMPRESSOR": "django_redis.compressors.gzip.GzipCompressor",

tests/settings/sqlite_herd.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@
33
CACHES = {
44
"default": {
55
"BACKEND": "django_redis.cache.RedisCache",
6-
"LOCATION": ["redis://127.0.0.1:6379?db=5"],
6+
"LOCATION": ["redis://127.0.0.1:6379?db=3"],
77
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.HerdClient"},
88
},
99
"doesnotexist": {
1010
"BACKEND": "django_redis.cache.RedisCache",
11-
"LOCATION": "redis://127.0.0.1:56379?db=1",
11+
"LOCATION": "redis://127.0.0.1:56379?db=3",
1212
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.HerdClient"},
1313
},
1414
"sample": {
1515
"BACKEND": "django_redis.cache.RedisCache",
16-
"LOCATION": "redis://127.0.0.1:6379?db=1,redis://127.0.0.1:6379?db=1",
16+
"LOCATION": "redis://127.0.0.1:6379?db=3,redis://127.0.0.1:6379?db=3",
1717
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.HerdClient"},
1818
},
1919
"with_prefix": {
2020
"BACKEND": "django_redis.cache.RedisCache",
21-
"LOCATION": "redis://127.0.0.1:6379?db=1",
21+
"LOCATION": "redis://127.0.0.1:6379?db=3",
2222
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.HerdClient"},
2323
"KEY_PREFIX": "test-prefix",
2424
},

tests/settings/sqlite_json.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,31 @@
33
CACHES = {
44
"default": {
55
"BACKEND": "django_redis.cache.RedisCache",
6-
"LOCATION": ["redis://127.0.0.1:6379?db=1", "redis://127.0.0.1:6379?db=1"],
6+
"LOCATION": ["redis://127.0.0.1:6379?db=4", "redis://127.0.0.1:6379?db=4"],
77
"OPTIONS": {
88
"CLIENT_CLASS": "django_redis.client.DefaultClient",
99
"SERIALIZER": "django_redis.serializers.json.JSONSerializer",
1010
},
1111
},
1212
"doesnotexist": {
1313
"BACKEND": "django_redis.cache.RedisCache",
14-
"LOCATION": "redis://127.0.0.1:56379?db=1",
14+
"LOCATION": "redis://127.0.0.1:56379?db=4",
1515
"OPTIONS": {
1616
"CLIENT_CLASS": "django_redis.client.DefaultClient",
1717
"SERIALIZER": "django_redis.serializers.json.JSONSerializer",
1818
},
1919
},
2020
"sample": {
2121
"BACKEND": "django_redis.cache.RedisCache",
22-
"LOCATION": "redis://127.0.0.1:6379?db=1,redis://127.0.0.1:6379?db=1",
22+
"LOCATION": "redis://127.0.0.1:6379?db=4,redis://127.0.0.1:6379?db=4",
2323
"OPTIONS": {
2424
"CLIENT_CLASS": "django_redis.client.DefaultClient",
2525
"SERIALIZER": "django_redis.serializers.json.JSONSerializer",
2626
},
2727
},
2828
"with_prefix": {
2929
"BACKEND": "django_redis.cache.RedisCache",
30-
"LOCATION": "redis://127.0.0.1:6379?db=1",
30+
"LOCATION": "redis://127.0.0.1:6379?db=4",
3131
"OPTIONS": {
3232
"CLIENT_CLASS": "django_redis.client.DefaultClient",
3333
"SERIALIZER": "django_redis.serializers.json.JSONSerializer",

tests/settings/sqlite_lz4.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,31 @@
33
CACHES = {
44
"default": {
55
"BACKEND": "django_redis.cache.RedisCache",
6-
"LOCATION": ["redis://127.0.0.1:6379?db=1", "redis://127.0.0.1:6379?db=1"],
6+
"LOCATION": ["redis://127.0.0.1:6379?db=5", "redis://127.0.0.1:6379?db=5"],
77
"OPTIONS": {
88
"CLIENT_CLASS": "django_redis.client.DefaultClient",
99
"COMPRESSOR": "django_redis.compressors.lz4.Lz4Compressor",
1010
},
1111
},
1212
"doesnotexist": {
1313
"BACKEND": "django_redis.cache.RedisCache",
14-
"LOCATION": "redis://127.0.0.1:56379?db=1",
14+
"LOCATION": "redis://127.0.0.1:56379?db=5",
1515
"OPTIONS": {
1616
"CLIENT_CLASS": "django_redis.client.DefaultClient",
1717
"COMPRESSOR": "django_redis.compressors.lz4.Lz4Compressor",
1818
},
1919
},
2020
"sample": {
2121
"BACKEND": "django_redis.cache.RedisCache",
22-
"LOCATION": "127.0.0.1:6379?db=1,127.0.0.1:6379?db=1",
22+
"LOCATION": "127.0.0.1:6379?db=5,127.0.0.1:6379?db=5",
2323
"OPTIONS": {
2424
"CLIENT_CLASS": "django_redis.client.DefaultClient",
2525
"COMPRESSOR": "django_redis.compressors.lz4.Lz4Compressor",
2626
},
2727
},
2828
"with_prefix": {
2929
"BACKEND": "django_redis.cache.RedisCache",
30-
"LOCATION": "redis://127.0.0.1:6379?db=1",
30+
"LOCATION": "redis://127.0.0.1:6379?db=5",
3131
"OPTIONS": {
3232
"CLIENT_CLASS": "django_redis.client.DefaultClient",
3333
"COMPRESSOR": "django_redis.compressors.lz4.Lz4Compressor",

0 commit comments

Comments
 (0)