Skip to content

Commit f6e3bad

Browse files
Improve CI test runtime with pytest-xdist (#270)
`pytest-xdist` runs workers to tackle the test suite concurrently (using more cpus): https://pytest-xdist.readthedocs.io/en/stable/distribution.html#running-tests-across-multiple-cpus The addition of this tool AND some small updates to testcontainer usage (for Redis instances) helps us achieve a ~3-4x speed up locally.
1 parent e96d739 commit f6e3bad

File tree

16 files changed

+843
-853
lines changed

16 files changed

+843
-853
lines changed

.github/workflows/lint.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ jobs:
2525
# so linting on fewer versions makes CI faster.
2626
python-version:
2727
- "3.9"
28+
- "3.10"
2829
- "3.11"
30+
- "3.12"
2931

3032
steps:
3133
- uses: actions/checkout@v2

.github/workflows/run_tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
strategy:
1919
fail-fast: false
2020
matrix:
21-
python-version: [3.9, '3.10', 3.11]
21+
python-version: [3.9, '3.10', 3.11, 3.12]
2222
connection: ['hiredis', 'plain']
2323
redis-stack-version: ['6.2.6-v9', 'latest', 'edge']
2424

@@ -75,12 +75,12 @@ jobs:
7575
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
7676
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
7777
run: |
78-
poetry run test-cov
78+
poetry run test-verbose
7979
8080
- name: Run tests
8181
if: matrix.connection != 'plain' || matrix.redis-stack-version != 'latest'
8282
run: |
83-
SKIP_VECTORIZERS=True SKIP_RERANKERS=True poetry run test-cov
83+
SKIP_VECTORIZERS=True SKIP_RERANKERS=True poetry run test-verbose
8484
8585
- name: Run notebooks
8686
if: matrix.connection == 'plain' && matrix.redis-stack-version == 'latest'

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,17 @@ To run Testcontainers-based tests you need a local Docker installation such as:
7878

7979
Tests w/ vectorizers:
8080
```bash
81-
poetry run test-cov
81+
poetry run test-verbose
8282
```
8383

8484
Tests w/out vectorizers:
8585
```bash
86-
SKIP_VECTORIZERS=true poetry run test-cov
86+
SKIP_VECTORIZERS=true poetry run test-verbose
8787
```
8888

8989
Tests w/out rerankers:
9090
```bash
91-
SKIP_RERANKERS=true poetry run test-cov
91+
SKIP_RERANKERS=true poetry run test-verbose
9292
```
9393

9494
### Documentation

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ check-types:
1919
lint: format check-types
2020

2121
test:
22-
SKIP_RERANKERS=true SKIP_VECTORIZERS=true poetry run test-cov
22+
SKIP_RERANKERS=true SKIP_VECTORIZERS=true poetry run test-verbose
2323

2424
test-all:
25-
poetry run test-cov
25+
poetry run test-verbose
2626

2727
check: lint test
2828

conftest.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,55 @@
11
import os
22
import pytest
3-
import asyncio
43

54
from redisvl.redis.connection import RedisConnectionFactory
65
from testcontainers.compose import DockerCompose
76

87

8+
@pytest.fixture(autouse=True)
9+
def set_tokenizers_parallelism():
10+
"""Disable tokenizers parallelism in tests to avoid deadlocks"""
11+
os.environ["TOKENIZERS_PARALLELISM"] = "false"
12+
913

1014
@pytest.fixture(scope="session", autouse=True)
11-
def redis_container():
12-
# Set the default Redis version if not already set
15+
def redis_container(request):
16+
"""
17+
Create a unique Compose project for each xdist worker by setting
18+
COMPOSE_PROJECT_NAME. That prevents collisions on container/volume names.
19+
"""
20+
# In xdist, the config has "workerid" in workerinput
21+
worker_id = request.config.workerinput.get("workerid", "master")
22+
23+
# Set the Compose project name so containers do not clash across workers
24+
os.environ["COMPOSE_PROJECT_NAME"] = f"redis_test_{worker_id}"
1325
os.environ.setdefault("REDIS_VERSION", "edge")
1426

15-
compose = DockerCompose("tests", compose_file_name="docker-compose.yml", pull=True)
27+
compose = DockerCompose(
28+
context="tests",
29+
compose_file_name="docker-compose.yml",
30+
pull=True,
31+
)
1632
compose.start()
1733

18-
redis_host, redis_port = compose.get_service_host_and_port("redis", 6379)
19-
redis_url = f"redis://{redis_host}:{redis_port}"
20-
os.environ["REDIS_URL"] = redis_url
21-
2234
yield compose
2335

2436
compose.stop()
2537

38+
2639
@pytest.fixture(scope="session")
27-
def redis_url():
28-
return os.getenv("REDIS_URL", "redis://localhost:6379")
40+
def redis_url(redis_container):
41+
"""
42+
Use the `DockerCompose` fixture to get host/port of the 'redis' service
43+
on container port 6379 (mapped to an ephemeral port on the host).
44+
"""
45+
host, port = redis_container.get_service_host_and_port("redis", 6379)
46+
return f"redis://{host}:{port}"
2947

3048
@pytest.fixture
3149
async def async_client(redis_url):
50+
"""
51+
An async Redis client that uses the dynamic `redis_url`.
52+
"""
3253
client = await RedisConnectionFactory.get_async_redis_connection(redis_url)
3354
yield client
3455
try:
@@ -38,8 +59,11 @@ async def async_client(redis_url):
3859
raise
3960

4061
@pytest.fixture
41-
def client():
42-
conn = RedisConnectionFactory.get_redis_connection(os.environ["REDIS_URL"])
62+
def client(redis_url):
63+
"""
64+
A sync Redis client that uses the dynamic `redis_url`.
65+
"""
66+
conn = RedisConnectionFactory.get_redis_connection(redis_url)
4367
yield conn
4468
conn.close()
4569

poetry.lock

Lines changed: 558 additions & 610 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ classifiers = [
1313
"Programming Language :: Python :: 3.9",
1414
"Programming Language :: Python :: 3.10",
1515
"Programming Language :: Python :: 3.11",
16+
"Programming Language :: Python :: 3.12",
1617
"License :: OSI Approved :: MIT License",
1718
]
1819
packages = [{ include = "redisvl", from = "." }]
@@ -35,6 +36,7 @@ cohere = { version = ">=4.44", optional = true }
3536
mistralai = { version = ">=1.0.0", optional = true }
3637
boto3 = { version = ">=1.34.0", optional = true }
3738
voyageai = { version = ">=0.2.2", optional = true }
39+
pytest-xdist = {extras = ["psutil"], version = "^3.6.1"}
3840

3941
[tool.poetry.extras]
4042
openai = ["openai"]
@@ -50,7 +52,6 @@ black = ">=20.8b1"
5052
isort = ">=5.6.4"
5153
pylint = "3.1.0"
5254
pytest = "8.1.1"
53-
pytest-cov = "5.0.0"
5455
pytest-asyncio = "0.23.6"
5556
mypy = "1.9.0"
5657
types-redis = "*"
@@ -81,8 +82,6 @@ check-lint = "scripts:check_lint"
8182
check-mypy = "scripts:check_mypy"
8283
test = "scripts:test"
8384
test-verbose = "scripts:test_verbose"
84-
test-cov = "scripts:test_cov"
85-
cov = "scripts:cov"
8685
test-notebooks = "scripts:test_notebooks"
8786
build-docs = "scripts:build_docs"
8887
serve-docs = "scripts:serve_docs"
@@ -92,7 +91,7 @@ requires = ["poetry-core>=1.0.0"]
9291
build-backend = "poetry.core.masonry.api"
9392

9493
[tool.black]
95-
target-version = ['py39', 'py310', 'py311']
94+
target-version = ['py39', 'py310', 'py311', 'py312']
9695
exclude = '''
9796
(
9897
| \.egg

redisvl/schema/fields.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ def as_redis_field(self) -> RedisField:
235235

236236
class FlatVectorField(BaseField):
237237
"Vector field with a FLAT index (brute force nearest neighbors search)"
238+
238239
type: str = Field(default="vector", const=True)
239240
attrs: FlatVectorFieldAttributes
240241

0 commit comments

Comments
 (0)