Skip to content

Commit f41581a

Browse files
Modularize testing suite (redis#278)
This PR introduces a few improvements to our test suite to make it more modular. 1) Adds the ability to run tests against specific file(s) using the poetry scripting: ```bash poetry run test tests/unit/test_fields.py ``` 2) Adds the ability to toggle the API-enabled tests via command line arg using the poetry scripting ```bash poetry run test-verbose --run-api-tests ``` OR ```bash make test-all ``` *This removes the need to set those ugly `SKIP_VECTORIZER=true` flags.* And run without the API dependencies: ```bash poetry run test-verbose ``` ```bash make test ``` 3) Wraps notebook tests into a convenient poetry script and makefile wrapper: ```bash poetry run test-notebooks ``` OR ```bash make test-notebooks ```
1 parent 5ad7d00 commit f41581a

File tree

8 files changed

+141
-132
lines changed

8 files changed

+141
-132
lines changed

Diff for: .github/workflows/test.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,12 @@ jobs:
8383
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
8484
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
8585
run: |
86-
poetry run test-verbose
86+
make test-all
8787
8888
- name: Run tests
8989
if: matrix.connection != 'plain' || matrix.redis-stack-version != 'latest'
9090
run: |
91-
SKIP_VECTORIZERS=True SKIP_RERANKERS=True poetry run test-verbose
91+
make test
9292
9393
- name: Run notebooks
9494
if: matrix.connection == 'plain' && matrix.redis-stack-version == 'latest'
@@ -106,7 +106,7 @@ jobs:
106106
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
107107
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
108108
run: |
109-
cd docs/ && poetry run pytest --nbval-lax ./user_guide -vv
109+
make test-notebooks
110110
111111
docs:
112112
runs-on: ubuntu-latest

Diff for: CONTRIBUTING.md

+20-8
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ If you use `make`, we've created shortcuts for running the commands in this docu
5050
| make format | Runs code formatting and import sorting |
5151
| make check-types | Runs mypy type checking |
5252
| make lint | Runs formatting, import sorting, and type checking |
53-
| make test | Runs tests, excluding those that require API keys and/or remote network calls)|
54-
| make test-all | Runs all tests, including those that require API keys and/or remote network calls)|
53+
| make test | Runs tests, excluding those that require API keys and/or remote network calls|
54+
| make test-all | Runs all tests, including those that require API keys and/or remote network calls|
55+
| make test-notebooks | Runs all notebook tests|
5556
| make check | Runs all linting targets and a subset of tests |
5657
| make docs-build | Builds the documentation |
5758
| make docs-serve | Serves the documentation locally |
@@ -76,19 +77,19 @@ To run Testcontainers-based tests you need a local Docker installation such as:
7677

7778
#### Running the Tests
7879

79-
Tests w/ vectorizers:
80+
Tests w/ external APIs:
8081
```bash
81-
poetry run test-verbose
82+
poetry run test-verbose --run-api-tests
8283
```
8384

84-
Tests w/out vectorizers:
85+
Tests w/out external APIs:
8586
```bash
86-
SKIP_VECTORIZERS=true poetry run test-verbose
87+
poetry run test-verbose
8788
```
8889

89-
Tests w/out rerankers:
90+
Run a test on a specific file:
9091
```bash
91-
SKIP_RERANKERS=true poetry run test-verbose
92+
poetry run test-verbose tests/unit/test_fields.py
9293
```
9394

9495
### Documentation
@@ -112,6 +113,17 @@ In order for your applications to use RedisVL, you must have [Redis](https://red
112113
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
113114
```
114115

116+
Or from your makefile simply run:
117+
118+
```bash
119+
make redis-start
120+
```
121+
122+
And then:
123+
```bash
124+
make redis-stop
125+
```
126+
115127
This will also spin up the [FREE RedisInsight GUI](https://redis.io/insight/) at `http://localhost:8001`.
116128

117129
## How to Report a Bug

Diff for: Makefile

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: install format lint test clean redis-start redis-stop check-types integration-test docs-build docs-serve check
1+
.PHONY: install format lint test test-all test-notebooks clean redis-start redis-stop check-types docs-build docs-serve check
22

33
install:
44
poetry install --all-extras
@@ -19,10 +19,13 @@ check-types:
1919
lint: format check-types
2020

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

2424
test-all:
25-
poetry run test-verbose
25+
poetry run test-verbose --run-api-tests
26+
27+
test-notebooks:
28+
poetry run test-notebooks
2629

2730
check: lint test
2831

Diff for: scripts.py

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import subprocess
2+
import sys
23

34

45
def format():
@@ -29,17 +30,25 @@ def check_mypy():
2930

3031

3132
def test():
32-
subprocess.run(["python", "-m", "pytest", "-n", "auto", "--log-level=CRITICAL"], check=True)
33+
test_cmd = ["python", "-m", "pytest", "-n", "auto", "--log-level=CRITICAL"]
34+
# Get any extra arguments passed to the script
35+
extra_args = sys.argv[1:]
36+
if extra_args:
37+
test_cmd.extend(extra_args)
38+
subprocess.run(test_cmd, check=True)
3339

3440

3541
def test_verbose():
36-
subprocess.run(
37-
["python", "-m", "pytest", "-n", "auto", "-vv", "-s", "--log-level=CRITICAL"], check=True
38-
)
42+
test_cmd = ["python", "-m", "pytest", "-n", "auto", "-vv", "-s", "--log-level=CRITICAL"]
43+
# Get any extra arguments passed to the script
44+
extra_args = sys.argv[1:]
45+
if extra_args:
46+
test_cmd.extend(extra_args)
47+
subprocess.run(test_cmd, check=True)
3948

4049

4150
def test_notebooks():
42-
subprocess.run(["cd", "docs/", "&&", "poetry run treon", "-v"], check=True)
51+
subprocess.run("cd docs/ && python -m pytest --nbval-lax ./user_guide -vv", shell=True, check=True)
4352

4453

4554
def build_docs():

Diff for: tests/conftest.py

+30-60
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,10 @@ async def async_client(redis_url):
5454
"""
5555
An async Redis client that uses the dynamic `redis_url`.
5656
"""
57-
client = await RedisConnectionFactory.get_async_redis_connection(redis_url)
58-
yield client
59-
try:
60-
await client.aclose()
61-
except RuntimeError as e:
62-
if "Event loop is closed" not in str(e):
63-
raise
57+
async with await RedisConnectionFactory.get_async_redis_connection(
58+
redis_url
59+
) as client:
60+
yield client
6461

6562

6663
@pytest.fixture
@@ -70,51 +67,6 @@ def client(redis_url):
7067
"""
7168
conn = RedisConnectionFactory.get_redis_connection(redis_url)
7269
yield conn
73-
conn.close()
74-
75-
76-
@pytest.fixture
77-
def openai_key():
78-
return os.getenv("OPENAI_API_KEY")
79-
80-
81-
@pytest.fixture
82-
def openai_version():
83-
return os.getenv("OPENAI_API_VERSION")
84-
85-
86-
@pytest.fixture
87-
def azure_endpoint():
88-
return os.getenv("AZURE_OPENAI_ENDPOINT")
89-
90-
91-
@pytest.fixture
92-
def cohere_key():
93-
return os.getenv("COHERE_API_KEY")
94-
95-
96-
@pytest.fixture
97-
def mistral_key():
98-
return os.getenv("MISTRAL_API_KEY")
99-
100-
101-
@pytest.fixture
102-
def gcp_location():
103-
return os.getenv("GCP_LOCATION")
104-
105-
106-
@pytest.fixture
107-
def gcp_project_id():
108-
return os.getenv("GCP_PROJECT_ID")
109-
110-
111-
@pytest.fixture
112-
def aws_credentials():
113-
return {
114-
"aws_access_key_id": os.getenv("AWS_ACCESS_KEY_ID"),
115-
"aws_secret_access_key": os.getenv("AWS_SECRET_ACCESS_KEY"),
116-
"aws_region": os.getenv("AWS_REGION", "us-east-1"),
117-
}
11870

11971

12072
@pytest.fixture
@@ -179,13 +131,31 @@ def sample_data():
179131
]
180132

181133

182-
@pytest.fixture
183-
def clear_db(redis):
184-
redis.flushall()
185-
yield
186-
redis.flushall()
134+
def pytest_addoption(parser: pytest.Parser) -> None:
135+
parser.addoption(
136+
"--run-api-tests",
137+
action="store_true",
138+
default=False,
139+
help="Run tests that require API keys",
140+
)
187141

188142

189-
@pytest.fixture
190-
def app_name():
191-
return "test_app"
143+
def pytest_configure(config: pytest.Config) -> None:
144+
config.addinivalue_line(
145+
"markers", "requires_api_keys: mark test as requiring API keys"
146+
)
147+
148+
149+
def pytest_collection_modifyitems(
150+
config: pytest.Config, items: list[pytest.Item]
151+
) -> None:
152+
if config.getoption("--run-api-tests"):
153+
return
154+
155+
# Otherwise skip all tests requiring an API key
156+
skip_api = pytest.mark.skip(
157+
reason="Skipping test because API keys are not provided. Use --run-api-tests to run these tests."
158+
)
159+
for item in items:
160+
if item.get_closest_marker("requires_api_keys"):
161+
item.add_marker(skip_api)

Diff for: tests/integration/test_rerankers.py

+4-14
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,14 @@
99
)
1010

1111

12-
@pytest.fixture
13-
def skip_reranker() -> bool:
14-
# os.getenv returns a string
15-
v = os.getenv("SKIP_RERANKERS", "False").lower() == "true"
16-
return v
17-
18-
1912
# Fixture for the reranker instance
2013
@pytest.fixture(
2114
params=[
2215
CohereReranker,
2316
VoyageAIReranker,
2417
]
2518
)
26-
def reranker(request, skip_reranker):
27-
if skip_reranker:
28-
pytest.skip("Skipping reranker instantiation...")
29-
19+
def reranker(request):
3020
if request.param == CohereReranker:
3121
return request.param()
3222
elif request.param == VoyageAIReranker:
@@ -43,7 +33,7 @@ def hfCrossEncoderRerankerWithCustomModel():
4333
return HFCrossEncoderReranker("cross-encoder/stsb-distilroberta-base")
4434

4535

46-
# Test for basic ranking functionality
36+
@pytest.mark.requires_api_keys
4737
def test_rank_documents(reranker):
4838
docs = ["document one", "document two", "document three"]
4939
query = "search query"
@@ -55,7 +45,7 @@ def test_rank_documents(reranker):
5545
assert all(isinstance(score, float) for score in scores) # Scores should be floats
5646

5747

58-
# Test for asynchronous ranking functionality
48+
@pytest.mark.requires_api_keys
5949
@pytest.mark.asyncio
6050
async def test_async_rank_documents(reranker):
6151
docs = ["document one", "document two", "document three"]
@@ -68,7 +58,7 @@ async def test_async_rank_documents(reranker):
6858
assert all(isinstance(score, float) for score in scores) # Scores should be floats
6959

7060

71-
# Test handling of bad input
61+
@pytest.mark.requires_api_keys
7262
def test_bad_input(reranker):
7363
with pytest.raises(Exception):
7464
reranker.rank("", []) # Empty query or documents

Diff for: tests/integration/test_session_manager.py

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
from redisvl.utils.vectorize.text.huggingface import HFTextVectorizer
1313

1414

15+
@pytest.fixture
16+
def app_name():
17+
return "test_app"
18+
19+
1520
@pytest.fixture
1621
def standard_session(app_name, client):
1722
session = StandardSessionManager(app_name, redis_client=client)

0 commit comments

Comments
 (0)