Commit 7f33868
tests: eliminate NLTK stopwords download race under pytest-xdist (#644)
## Motivation
`TextQuery`, `HybridQuery`, and `AggregateHybridQuery` default to
`stopwords="english"` and lazily download the NLTK corpus the first time
they need it. Under pytest-xdist, several workers can hit the missing
corpus at the same moment and call `nltk.download()` concurrently —
their unzips clobber each other in the shared `nltk_data` directory, and
a worker then reads a half-written corpus. It surfaces only on Python
3.14 because there `sentence-transformers` is skipped, so nothing warms
the corpus serially before the parallel tests start; on 3.10–3.13 the
HuggingFace path fetches it first and the race never opens. It's
intermittent (only some 3.14 jobs, across `redis:8.2` and `redis:latest`
alike), which is the giveaway that Redis has nothing to do with it.
## Fix
Two complementary changes, following the pattern the unit tests already
use (they pass `stopwords=None` to avoid NLTK entirely):
**Pre-download the corpus once, before the workers race for it.** A new
session-scoped autouse fixture fetches `stopwords` a single time,
serialized across xdist workers with a file lock, so exactly one process
does the download and the rest find it already present. This covers
every test that legitimately needs english stopwords — including the
ones that assert on stopword filtering (`test_empty_query_string`,
`test_hybrid_query_stopwords`) and the count-sensitive `TextQuery` tests
in `test_query.py`, whose result counts depend on filtering and so must
keep real stopwords. Adds `filelock` (already present transitively) as
an explicit dev dependency.
**Decouple the tests that don't actually need stopwords.** The hybrid
tests in `test_aggregation.py` match text with an optional `~@field`
clause, so the result set is driven by the vector and filters, not the
text tokens — dropping stopwords there changes nothing about the
assertions. Passing `stopwords=None` on those constructions removes NLTK
from that file except the two tests that specifically exercise the
feature, shrinking the surface that can ever race.
## Verification
Ran the affected tests under `-n 8` with the stopwords corpus deleted
beforehand to simulate a cold runner. The fixture downloads once, no
worker sees a partial corpus, and all tests pass — including the
count-sensitive `TextQuery` tests, confirming english-stopword filtering
is still intact.
Independent of the `redis:latest` WORKERS fix (#641); this addresses a
separate, pre-existing flake.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Test-only and dev-dependency changes; no production library behavior
is modified.
>
> **Overview**
> Fixes intermittent CI failures when parallel pytest workers
concurrently download the NLTK **stopwords** corpus into a shared
`nltk_data` directory.
>
> A new session-scoped autouse fixture **`ensure_nltk_stopwords`** in
`conftest.py` ensures the corpus exists once before tests run; under
**pytest-xdist**, a **`filelock`** serializes the download so only one
worker fetches it. **`filelock`** is added as an explicit dev dependency
in **`pyproject.toml`** / **`uv.lock`**.
>
> Hybrid aggregation integration tests that do not assert on stopword
behavior now pass **`stopwords=None`** on **`AggregateHybridQuery`** so
those cases skip NLTK entirely; tests that exercise stopword filtering
keep default or explicit stopword lists.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
17f5435. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>1 parent 0ecf53e commit 7f33868
4 files changed
Lines changed: 53 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
| 108 | + | |
108 | 109 | | |
109 | 110 | | |
110 | 111 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
56 | 93 | | |
57 | 94 | | |
58 | 95 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
| 99 | + | |
99 | 100 | | |
100 | 101 | | |
101 | 102 | | |
| |||
122 | 123 | | |
123 | 124 | | |
124 | 125 | | |
| 126 | + | |
125 | 127 | | |
126 | 128 | | |
127 | 129 | | |
| |||
177 | 179 | | |
178 | 180 | | |
179 | 181 | | |
| 182 | + | |
180 | 183 | | |
181 | 184 | | |
182 | 185 | | |
| |||
203 | 206 | | |
204 | 207 | | |
205 | 208 | | |
| 209 | + | |
206 | 210 | | |
207 | 211 | | |
208 | 212 | | |
| |||
226 | 230 | | |
227 | 231 | | |
228 | 232 | | |
| 233 | + | |
229 | 234 | | |
230 | 235 | | |
231 | 236 | | |
| |||
291 | 296 | | |
292 | 297 | | |
293 | 298 | | |
| 299 | + | |
294 | 300 | | |
295 | 301 | | |
296 | 302 | | |
| |||
309 | 315 | | |
310 | 316 | | |
311 | 317 | | |
| 318 | + | |
312 | 319 | | |
313 | 320 | | |
314 | 321 | | |
| |||
339 | 346 | | |
340 | 347 | | |
341 | 348 | | |
| 349 | + | |
342 | 350 | | |
343 | 351 | | |
344 | 352 | | |
| |||
353 | 361 | | |
354 | 362 | | |
355 | 363 | | |
| 364 | + | |
356 | 365 | | |
357 | 366 | | |
358 | 367 | | |
| |||
372 | 381 | | |
373 | 382 | | |
374 | 383 | | |
| 384 | + | |
375 | 385 | | |
376 | 386 | | |
377 | 387 | | |
| |||
386 | 396 | | |
387 | 397 | | |
388 | 398 | | |
| 399 | + | |
389 | 400 | | |
390 | 401 | | |
391 | 402 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 commit comments