Skip to content

Commit 82de51d

Browse files
test: fix race condition in delayed failure event test (#47)
**Requirements** - [x] I have added test coverage for new or changed functionality - [x] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests) - [x] I have validated my changes against all supported platform versions **Related issues** None. **Describe the solution you've provided** Three event emission tests (`test_provider_emits_ready_event_when_immediately_ready`, `test_provider_emits_error_event_immediately_failed`, `test_provider_emits_error_event_delayed_failure`) check a counter immediately after `set_provider` without waiting for the event to be dispatched. The OpenFeature SDK now dispatches provider events asynchronously, so these assertions fail. The fix uses `threading.Event` with `wait(timeout=5)`, matching the pattern already used by `test_provider_emits_stale_event` and `test_provider_emits_configuration_event`. **Describe alternatives you've considered** Could add `time.sleep()` calls, but event-based waiting is more robust and consistent with existing tests. **Additional context** All 66 tests pass consistently. Lint (mypy) also passes. Link to Devin session: https://app.devin.ai/sessions/385a2c260fe8433ea7e6af74fcde903c Requested by: @kinyoklion <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Test-only changes with no runtime or library behavior modified. > > **Overview** > Fixes flaky provider lifecycle tests that assumed **OpenFeature** dispatches `PROVIDER_READY` / `PROVIDER_ERROR` synchronously on `set_provider`. > > **`test_provider_emits_ready_event_when_immediately_ready`**, **`test_provider_emits_error_event_immediately_failed`**, and **`test_provider_emits_error_event_delayed_failure`** now wait on a **`threading.Event`** (with **`wait(timeout=5)`**) set from the handler before asserting the emission count, plus a short **`time.sleep(0.1)`** so the counter update is visible. Handlers were simplified (shared **`emission_count`**, **`thread_event.set()`**). This matches **`test_provider_emits_stale_event`** and **`test_provider_emits_configuration_event`**. > > No production/provider code changes—tests only. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0340a25. 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: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 3d422b4 commit 82de51d

1 file changed

Lines changed: 28 additions & 17 deletions

File tree

tests/test_provider.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import threading
2+
import time
23
from typing import List, Union
34
from unittest.mock import patch
45

@@ -150,39 +151,42 @@ def test_logger_changes_should_cascade_to_evaluation_converter(provider: LaunchD
150151

151152

152153
def test_provider_emits_ready_event_when_immediately_ready():
153-
ld_provider_ready_count = 0
154+
emission_count = 0
154155
lock = threading.Lock()
156+
thread_event = threading.Event()
155157

156158
def handle_status(details: EventDetails):
157159
if details.provider_name == 'launchdarkly-openfeature-server':
158-
nonlocal lock
159-
nonlocal ld_provider_ready_count
160+
nonlocal emission_count
160161
with lock:
161-
ld_provider_ready_count = ld_provider_ready_count + 1
162+
emission_count += 1
163+
thread_event.set()
162164

163-
# At the time of implementation this handler runs synchronously on the same
164-
# thread as initialization. The lock is in case this behavior changes.
165165
api.add_handler(ProviderEvent.PROVIDER_READY, handle_status)
166166

167167
openfeature_provider = LaunchDarklyProvider(Config("", offline=True))
168168
api.set_provider(openfeature_provider)
169169

170+
assert thread_event.wait(timeout=5)
171+
time.sleep(0.1)
172+
170173
with lock:
171-
assert ld_provider_ready_count == 1
174+
assert emission_count == 1
172175

173176
api.shutdown()
174177

175178

176179
def test_provider_emits_error_event_immediately_failed():
177-
ld_provider_error_count = 0
180+
emission_count = 0
178181
lock = threading.Lock()
182+
thread_event = threading.Event()
179183

180184
def handle_status(details: EventDetails):
181185
if details.provider_name == 'launchdarkly-openfeature-server':
182-
nonlocal lock
183-
nonlocal ld_provider_error_count
186+
nonlocal emission_count
184187
with lock:
185-
ld_provider_error_count = ld_provider_error_count + 1
188+
emission_count += 1
189+
thread_event.set()
186190

187191
api.add_handler(ProviderEvent.PROVIDER_ERROR, handle_status)
188192

@@ -191,22 +195,26 @@ def handle_status(details: EventDetails):
191195

192196
api.set_provider(openfeature_provider)
193197

198+
assert thread_event.wait(timeout=5)
199+
time.sleep(0.1)
200+
194201
with lock:
195-
assert ld_provider_error_count == 1
202+
assert emission_count == 1
196203

197204
api.shutdown()
198205

199206

200207
def test_provider_emits_error_event_delayed_failure():
201-
ld_provider_error_count = 0
208+
emission_count = 0
202209
lock = threading.Lock()
210+
thread_event = threading.Event()
203211

204212
def handle_status(details: EventDetails):
205213
if details.provider_name == 'launchdarkly-openfeature-server':
206-
nonlocal lock
207-
nonlocal ld_provider_error_count
214+
nonlocal emission_count
208215
with lock:
209-
ld_provider_error_count = ld_provider_error_count + 1
216+
emission_count += 1
217+
thread_event.set()
210218

211219
api.add_handler(ProviderEvent.PROVIDER_ERROR, handle_status)
212220

@@ -215,8 +223,11 @@ def handle_status(details: EventDetails):
215223

216224
api.set_provider(openfeature_provider)
217225

226+
assert thread_event.wait(timeout=5)
227+
time.sleep(0.1)
228+
218229
with lock:
219-
assert ld_provider_error_count == 1
230+
assert emission_count == 1
220231

221232
api.shutdown()
222233

0 commit comments

Comments
 (0)