From 1f00c10dc1e7e7b1fe90c2c9b7bb404607309d54 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 09:20:28 +0000 Subject: [PATCH 1/4] test: fix race condition in delayed failure event test The test_provider_emits_error_event_delayed_failure test was checking the error count immediately after set_provider without waiting for the delayed event timer (0.1s) to fire. This caused a consistent test failure. Use a threading.Event with a timeout, matching the pattern used by test_provider_emits_stale_event and test_provider_emits_configuration_event. Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com> --- tests/test_provider.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/test_provider.py b/tests/test_provider.py index f2e2c15..8a5d346 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -198,15 +198,11 @@ def handle_status(details: EventDetails): def test_provider_emits_error_event_delayed_failure(): - ld_provider_error_count = 0 - lock = threading.Lock() + thread_event = threading.Event() def handle_status(details: EventDetails): if details.provider_name == 'launchdarkly-openfeature-server': - nonlocal lock - nonlocal ld_provider_error_count - with lock: - ld_provider_error_count = ld_provider_error_count + 1 + thread_event.set() api.add_handler(ProviderEvent.PROVIDER_ERROR, handle_status) @@ -215,8 +211,7 @@ def handle_status(details: EventDetails): api.set_provider(openfeature_provider) - with lock: - assert ld_provider_error_count == 1 + assert thread_event.wait(timeout=5) api.shutdown() From 7bdf51dff452f6a430e1f14fa64945d0b8285fbe Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 09:22:22 +0000 Subject: [PATCH 2/4] test: fix remaining race conditions in event emission tests Apply the same event-based wait fix to test_provider_emits_ready_event_when_immediately_ready and test_provider_emits_error_event_immediately_failed. The OpenFeature SDK now dispatches provider events asynchronously, so checking counts immediately after set_provider is unreliable. Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com> --- tests/test_provider.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/tests/test_provider.py b/tests/test_provider.py index 8a5d346..c9c5f23 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -150,39 +150,28 @@ def test_logger_changes_should_cascade_to_evaluation_converter(provider: LaunchD def test_provider_emits_ready_event_when_immediately_ready(): - ld_provider_ready_count = 0 - lock = threading.Lock() + thread_event = threading.Event() def handle_status(details: EventDetails): if details.provider_name == 'launchdarkly-openfeature-server': - nonlocal lock - nonlocal ld_provider_ready_count - with lock: - ld_provider_ready_count = ld_provider_ready_count + 1 + thread_event.set() - # At the time of implementation this handler runs synchronously on the same - # thread as initialization. The lock is in case this behavior changes. api.add_handler(ProviderEvent.PROVIDER_READY, handle_status) openfeature_provider = LaunchDarklyProvider(Config("", offline=True)) api.set_provider(openfeature_provider) - with lock: - assert ld_provider_ready_count == 1 + assert thread_event.wait(timeout=5) api.shutdown() def test_provider_emits_error_event_immediately_failed(): - ld_provider_error_count = 0 - lock = threading.Lock() + thread_event = threading.Event() def handle_status(details: EventDetails): if details.provider_name == 'launchdarkly-openfeature-server': - nonlocal lock - nonlocal ld_provider_error_count - with lock: - ld_provider_error_count = ld_provider_error_count + 1 + thread_event.set() api.add_handler(ProviderEvent.PROVIDER_ERROR, handle_status) @@ -191,8 +180,7 @@ def handle_status(details: EventDetails): api.set_provider(openfeature_provider) - with lock: - assert ld_provider_error_count == 1 + assert thread_event.wait(timeout=5) api.shutdown() From a3324c05096a574ca8243c61ed4609a5ce793cf4 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:17:53 +0000 Subject: [PATCH 3/4] test: add duplicate emission detection to event tests Each event test now tracks emission_count alongside the threading.Event. After the event fires, a short sleep allows any duplicate emissions to arrive before asserting the count is exactly 1. Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com> --- tests/test_provider.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_provider.py b/tests/test_provider.py index c9c5f23..c0e1d5c 100644 --- a/tests/test_provider.py +++ b/tests/test_provider.py @@ -1,4 +1,5 @@ import threading +import time from typing import List, Union from unittest.mock import patch @@ -150,10 +151,15 @@ def test_logger_changes_should_cascade_to_evaluation_converter(provider: LaunchD def test_provider_emits_ready_event_when_immediately_ready(): + emission_count = 0 + lock = threading.Lock() thread_event = threading.Event() def handle_status(details: EventDetails): if details.provider_name == 'launchdarkly-openfeature-server': + nonlocal emission_count + with lock: + emission_count += 1 thread_event.set() api.add_handler(ProviderEvent.PROVIDER_READY, handle_status) @@ -162,15 +168,24 @@ def handle_status(details: EventDetails): api.set_provider(openfeature_provider) assert thread_event.wait(timeout=5) + time.sleep(0.1) + + with lock: + assert emission_count == 1 api.shutdown() def test_provider_emits_error_event_immediately_failed(): + emission_count = 0 + lock = threading.Lock() thread_event = threading.Event() def handle_status(details: EventDetails): if details.provider_name == 'launchdarkly-openfeature-server': + nonlocal emission_count + with lock: + emission_count += 1 thread_event.set() api.add_handler(ProviderEvent.PROVIDER_ERROR, handle_status) @@ -181,15 +196,24 @@ def handle_status(details: EventDetails): api.set_provider(openfeature_provider) assert thread_event.wait(timeout=5) + time.sleep(0.1) + + with lock: + assert emission_count == 1 api.shutdown() def test_provider_emits_error_event_delayed_failure(): + emission_count = 0 + lock = threading.Lock() thread_event = threading.Event() def handle_status(details: EventDetails): if details.provider_name == 'launchdarkly-openfeature-server': + nonlocal emission_count + with lock: + emission_count += 1 thread_event.set() api.add_handler(ProviderEvent.PROVIDER_ERROR, handle_status) @@ -200,6 +224,10 @@ def handle_status(details: EventDetails): api.set_provider(openfeature_provider) assert thread_event.wait(timeout=5) + time.sleep(0.1) + + with lock: + assert emission_count == 1 api.shutdown() From 0340a2581584716d8cbc00795243e9e39bf4e51a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:20:53 +0000 Subject: [PATCH 4/4] ci: retrigger after transient network failure on windows 3.11 Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>