Skip to content

Commit 3f865a6

Browse files
authored
Uses testcontainers to provide a redis instance for the unit tests (#470)
1 parent 1315583 commit 3f865a6

File tree

12 files changed

+108
-46
lines changed

12 files changed

+108
-46
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,10 @@ jobs:
6767
env:
6868
PYTHON: ${{ matrix.python }}
6969
OS: ${{ matrix.os }}
70+
ARQ_TEST_REDIS_VERSION: ${{ matrix.redis }}
7071

7172
runs-on: ${{ matrix.os }}-latest
7273

73-
services:
74-
redis:
75-
image: redis:${{ matrix.redis }}
76-
ports:
77-
- 6379:6379
78-
options: --entrypoint redis-server
79-
8074
steps:
8175
- uses: actions/checkout@v4
8276

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ __pycache__/
1818
.venv/
1919
/.auto-format
2020
/scratch/
21+
.python-version

requirements/docs.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ alabaster==0.7.16
88
# via sphinx
99
babel==2.14.0
1010
# via sphinx
11-
certifi==2024.2.2
11+
certifi==2024.7.4
1212
# via requests
1313
charset-normalizer==3.3.2
1414
# via requests
1515
docutils==0.19
1616
# via sphinx
17-
idna==3.6
17+
idna==3.7
1818
# via requests
1919
imagesize==1.4.1
2020
# via sphinx
@@ -26,7 +26,7 @@ packaging==24.0
2626
# via sphinx
2727
pygments==2.17.2
2828
# via sphinx
29-
requests==2.31.0
29+
requests==2.32.3
3030
# via sphinx
3131
snowballstemmer==2.2.0
3232
# via sphinx
@@ -44,5 +44,5 @@ sphinxcontrib-qthelp==1.0.7
4444
# via sphinx
4545
sphinxcontrib-serializinghtml==1.1.10
4646
# via sphinx
47-
urllib3==2.2.1
47+
urllib3==2.2.2
4848
# via requests

requirements/pyproject.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ click==8.1.7
1010
# via arq (pyproject.toml)
1111
hiredis==2.3.2
1212
# via redis
13-
idna==3.6
13+
idna==3.7
1414
# via anyio
1515
redis==4.6.0
1616
# via arq (pyproject.toml)

requirements/testing.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ pytest-mock
88
pytest-pretty
99
pytest-timeout
1010
pytz
11+
testcontainers<4 # until we remove 3.8 support

requirements/testing.txt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
#
2-
# This file is autogenerated by pip-compile with Python 3.12
2+
# This file is autogenerated by pip-compile with Python 3.9
33
# by the following command:
44
#
55
# pip-compile --output-file=requirements/testing.txt --strip-extras requirements/testing.in
66
#
77
annotated-types==0.6.0
88
# via pydantic
9+
certifi==2024.7.4
10+
# via requests
11+
charset-normalizer==3.3.2
12+
# via requests
913
coverage==7.4.4
1014
# via -r requirements/testing.in
15+
deprecation==2.1.0
16+
# via testcontainers
1117
dirty-equals==0.7.1.post0
1218
# via -r requirements/testing.in
19+
docker==7.1.0
20+
# via testcontainers
21+
exceptiongroup==1.2.2
22+
# via pytest
23+
idna==3.7
24+
# via requests
1325
iniconfig==2.0.0
1426
# via pytest
1527
markdown-it-py==3.0.0
@@ -19,7 +31,9 @@ mdurl==0.1.2
1931
msgpack==1.0.8
2032
# via -r requirements/testing.in
2133
packaging==24.0
22-
# via pytest
34+
# via
35+
# deprecation
36+
# pytest
2337
pluggy==1.4.0
2438
# via pytest
2539
pydantic==2.6.4
@@ -47,9 +61,23 @@ pytz==2024.1
4761
# via
4862
# -r requirements/testing.in
4963
# dirty-equals
64+
requests==2.32.3
65+
# via docker
5066
rich==13.7.1
5167
# via pytest-pretty
68+
testcontainers==3.7.1
69+
# via -r requirements/testing.in
70+
tomli==2.0.1
71+
# via
72+
# coverage
73+
# pytest
5274
typing-extensions==4.10.0
5375
# via
5476
# pydantic
5577
# pydantic-core
78+
urllib3==2.2.2
79+
# via
80+
# docker
81+
# requests
82+
wrapt==1.16.0
83+
# via testcontainers

tests/conftest.py

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,55 @@
22
import functools
33
import os
44
import sys
5+
from typing import Generator
56

67
import msgpack
78
import pytest
89
import redis.exceptions
910
from redis.asyncio.retry import Retry
1011
from redis.backoff import NoBackoff
12+
from testcontainers.redis import RedisContainer
1113

12-
from arq.connections import ArqRedis, create_pool
14+
from arq.connections import ArqRedis, RedisSettings, create_pool
1315
from arq.worker import Worker
1416

1517

1618
@pytest.fixture(name='loop')
17-
def _fix_loop(event_loop):
19+
def _fix_loop(event_loop: asyncio.AbstractEventLoop) -> asyncio.AbstractEventLoop:
1820
return event_loop
1921

2022

23+
@pytest.fixture(scope='session')
24+
def redis_version() -> str:
25+
return os.getenv('ARQ_TEST_REDIS_VERSION', 'latest')
26+
27+
28+
@pytest.fixture(scope='session')
29+
def redis_container(redis_version: str) -> Generator[RedisContainer, None, None]:
30+
with RedisContainer(f'redis:{redis_version}') as redis:
31+
yield redis
32+
33+
34+
@pytest.fixture(scope='session')
35+
def test_redis_host(redis_container: RedisContainer) -> str:
36+
return redis_container.get_container_host_ip()
37+
38+
39+
@pytest.fixture(scope='session')
40+
def test_redis_port(redis_container: RedisContainer) -> int:
41+
return redis_container.get_exposed_port(redis_container.port_to_expose)
42+
43+
44+
@pytest.fixture(scope='session')
45+
def test_redis_settings(test_redis_host: str, test_redis_port: int) -> RedisSettings:
46+
return RedisSettings(host=test_redis_host, port=test_redis_port)
47+
48+
2149
@pytest.fixture
22-
async def arq_redis(loop):
50+
async def arq_redis(test_redis_host: str, test_redis_port: int):
2351
redis_ = ArqRedis(
24-
host='localhost',
25-
port=6379,
52+
host=test_redis_host,
53+
port=test_redis_port,
2654
encoding='utf-8',
2755
)
2856

@@ -34,10 +62,10 @@ async def arq_redis(loop):
3462

3563

3664
@pytest.fixture
37-
async def arq_redis_msgpack(loop):
65+
async def arq_redis_msgpack(test_redis_host: str, test_redis_port: int):
3866
redis_ = ArqRedis(
39-
host='localhost',
40-
port=6379,
67+
host=test_redis_host,
68+
port=test_redis_port,
4169
encoding='utf-8',
4270
job_serializer=msgpack.packb,
4371
job_deserializer=functools.partial(msgpack.unpackb, raw=False),
@@ -48,10 +76,10 @@ async def arq_redis_msgpack(loop):
4876

4977

5078
@pytest.fixture
51-
async def arq_redis_retry(loop):
79+
async def arq_redis_retry(test_redis_host: str, test_redis_port: int):
5280
redis_ = ArqRedis(
53-
host='localhost',
54-
port=6379,
81+
host=test_redis_host,
82+
port=test_redis_port,
5583
encoding='utf-8',
5684
retry=Retry(backoff=NoBackoff(), retries=3),
5785
retry_on_timeout=True,

tests/test_cli.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from arq import logs
55
from arq.cli import cli
6+
from arq.connections import RedisSettings
67

78

89
async def foobar(ctx):
@@ -14,6 +15,11 @@ class WorkerSettings:
1415
functions = [foobar]
1516

1617

18+
@pytest.fixture(scope='module', autouse=True)
19+
def setup_worker_connection(test_redis_host: str, test_redis_port: int):
20+
WorkerSettings.redis_settings = RedisSettings(host=test_redis_host, port=test_redis_port)
21+
22+
1723
def test_help():
1824
runner = CliRunner()
1925
result = runner.invoke(cli, ['--help'])

tests/test_jobs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ async def test_enqueue_job_alt_queue(arq_redis: ArqRedis, worker):
114114
await test_enqueue_job(arq_redis, worker, queue_name='custom_queue')
115115

116116

117-
async def test_enqueue_job_nondefault_queue(worker):
117+
async def test_enqueue_job_nondefault_queue(test_redis_settings: RedisSettings, worker):
118118
"""Test initializing arq_redis with a queue name, and the worker using it."""
119-
arq_redis = await create_pool(RedisSettings(), default_queue_name='test_queue')
119+
arq_redis = await create_pool(test_redis_settings, default_queue_name='test_queue')
120120
await test_enqueue_job(
121121
arq_redis,
122122
lambda functions, **_: worker(functions=functions, arq_redis=arq_redis, queue_name=None),

tests/test_main.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import pytest
1212
from dirty_equals import IsInt, IsNow
1313

14-
from arq.connections import ArqRedis
14+
from arq.connections import ArqRedis, RedisSettings
1515
from arq.constants import default_queue_name
1616
from arq.jobs import Job, JobDef, SerializationError
1717
from arq.utils import timestamp_ms
@@ -65,7 +65,9 @@ async def parent_job(ctx):
6565
assert inner_result == 42
6666

6767

68-
async def test_enqueue_job_nested_custom_serializer(arq_redis_msgpack: ArqRedis, worker):
68+
async def test_enqueue_job_nested_custom_serializer(
69+
arq_redis_msgpack: ArqRedis, test_redis_settings: RedisSettings, worker
70+
):
6971
async def foobar(ctx):
7072
return 42
7173

@@ -78,6 +80,7 @@ async def parent_job(ctx):
7880
worker: Worker = worker(
7981
functions=[func(parent_job, name='parent_job'), func(foobar, name='foobar')],
8082
arq_redis=None,
83+
redis_settings=test_redis_settings,
8184
job_serializer=msgpack.packb,
8285
job_deserializer=functools.partial(msgpack.unpackb, raw=False),
8386
)
@@ -90,7 +93,7 @@ async def parent_job(ctx):
9093
assert inner_result == 42
9194

9295

93-
async def test_enqueue_job_custom_queue(arq_redis: ArqRedis, worker):
96+
async def test_enqueue_job_custom_queue(arq_redis: ArqRedis, test_redis_settings: RedisSettings, worker):
9497
async def foobar(ctx):
9598
return 42
9699

@@ -103,6 +106,7 @@ async def parent_job(ctx):
103106
worker: Worker = worker(
104107
functions=[func(parent_job, name='parent_job'), func(foobar, name='foobar')],
105108
arq_redis=None,
109+
redis_settings=test_redis_settings,
106110
queue_name='spanner',
107111
)
108112

0 commit comments

Comments
 (0)