From b51d79846fcb92d59b6d81777c316fc4a4eb0aa3 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:25:16 -0800 Subject: [PATCH 1/6] ref(flags): change LaunchDarkly and OpenFeature integrations to track a single client instance --- sentry_sdk/integrations/launchdarkly.py | 24 +++++------ sentry_sdk/integrations/openfeature.py | 21 ++++++++-- .../launchdarkly/test_launchdarkly.py | 28 ++----------- .../openfeature/test_openfeature.py | 40 +++++++++---------- 4 files changed, 51 insertions(+), 62 deletions(-) diff --git a/sentry_sdk/integrations/launchdarkly.py b/sentry_sdk/integrations/launchdarkly.py index 066464cc22..6b9d654cd1 100644 --- a/sentry_sdk/integrations/launchdarkly.py +++ b/sentry_sdk/integrations/launchdarkly.py @@ -4,39 +4,37 @@ from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.flag_utils import flag_error_processor +if TYPE_CHECKING: + from typing import Any, Optional + try: - import ldclient from ldclient.hook import Hook, Metadata if TYPE_CHECKING: from ldclient import LDClient from ldclient.hook import EvaluationSeriesContext from ldclient.evaluation import EvaluationDetail - - from typing import Any except ImportError: raise DidNotEnable("LaunchDarkly is not installed") class LaunchDarklyIntegration(Integration): identifier = "launchdarkly" - _ld_client = None # type: LDClient | None + _client = None # type: Optional[LDClient] - def __init__(self, ld_client=None): - # type: (LDClient | None) -> None + def __init__(self, client): + # type: (LDClient) -> None """ - :param client: An initialized LDClient instance. If a client is not provided, this - integration will attempt to use the shared global instance. + :param client: An initialized LDClient instance. """ - self.__class__._ld_client = ld_client + self.__class__._client = client @staticmethod def setup_once(): # type: () -> None - try: - client = LaunchDarklyIntegration._ld_client or ldclient.get() - except Exception as exc: - raise DidNotEnable("Error getting LaunchDarkly client. " + repr(exc)) + client = LaunchDarklyIntegration._client + if not client: + raise DidNotEnable("Error getting LDClient instance") # Register the flag collection hook with the LD client. client.add_hook(LaunchDarklyHook()) diff --git a/sentry_sdk/integrations/openfeature.py b/sentry_sdk/integrations/openfeature.py index 18f968a703..02ebb3673c 100644 --- a/sentry_sdk/integrations/openfeature.py +++ b/sentry_sdk/integrations/openfeature.py @@ -4,29 +4,42 @@ from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.flag_utils import flag_error_processor +if TYPE_CHECKING: + from typing import Optional + try: - from openfeature import api from openfeature.hook import Hook if TYPE_CHECKING: from openfeature.flag_evaluation import FlagEvaluationDetails from openfeature.hook import HookContext, HookHints + from openfeature.client import OpenFeatureClient except ImportError: raise DidNotEnable("OpenFeature is not installed") class OpenFeatureIntegration(Integration): identifier = "openfeature" + _client = None # type: Optional[OpenFeatureClient] + + def __init__(self, client): + # type: (OpenFeatureClient) -> None + self.__class__._client = client @staticmethod def setup_once(): # type: () -> None + + client = OpenFeatureIntegration._client + if not client: + raise DidNotEnable("Error getting OpenFeatureClient instance") + + # Register the hook within the openfeature client. + client.add_hooks(hooks=[OpenFeatureHook()]) + scope = sentry_sdk.get_current_scope() scope.add_error_processor(flag_error_processor) - # Register the hook within the global openfeature hooks list. - api.add_hooks(hooks=[OpenFeatureHook()]) - class OpenFeatureHook(Hook): diff --git a/tests/integrations/launchdarkly/test_launchdarkly.py b/tests/integrations/launchdarkly/test_launchdarkly.py index e7576bb469..73f4f71886 100644 --- a/tests/integrations/launchdarkly/test_launchdarkly.py +++ b/tests/integrations/launchdarkly/test_launchdarkly.py @@ -10,7 +10,6 @@ from ldclient.integrations.test_data import TestData import sentry_sdk -from sentry_sdk.integrations import DidNotEnable from sentry_sdk.integrations.launchdarkly import LaunchDarklyIntegration @@ -27,11 +26,10 @@ def test_launchdarkly_integration( uninstall_integration(LaunchDarklyIntegration.identifier) if use_global_client: ldclient.set_config(config) - sentry_init(integrations=[LaunchDarklyIntegration()]) client = ldclient.get() else: client = LDClient(config=config) - sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) + sentry_init(integrations=[LaunchDarklyIntegration(client)]) # Set test values td.update(td.flag("hello").variation_for_all(True)) @@ -63,7 +61,7 @@ def test_launchdarkly_integration_threaded( context = Context.create("user1") uninstall_integration(LaunchDarklyIntegration.identifier) - sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) + sentry_init(integrations=[LaunchDarklyIntegration(client)]) events = capture_events() def task(flag_key): @@ -122,7 +120,7 @@ def test_launchdarkly_integration_asyncio( context = Context.create("user1") uninstall_integration(LaunchDarklyIntegration.identifier) - sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) + sentry_init(integrations=[LaunchDarklyIntegration(client)]) events = capture_events() async def task(flag_key): @@ -166,23 +164,3 @@ async def runner(): {"flag": "world", "result": False}, ] } - - -def test_launchdarkly_integration_did_not_enable(sentry_init, uninstall_integration): - """ - Setup should fail when using global client and ldclient.set_config wasn't called. - - We're accessing ldclient internals to set up this test, so it might break if launchdarkly's - implementation changes. - """ - - ldclient._reset_client() - try: - ldclient.__lock.lock() - ldclient.__config = None - finally: - ldclient.__lock.unlock() - - uninstall_integration(LaunchDarklyIntegration.identifier) - with pytest.raises(DidNotEnable): - sentry_init(integrations=[LaunchDarklyIntegration()]) diff --git a/tests/integrations/openfeature/test_openfeature.py b/tests/integrations/openfeature/test_openfeature.py index c180211c3f..cd2a0d47b0 100644 --- a/tests/integrations/openfeature/test_openfeature.py +++ b/tests/integrations/openfeature/test_openfeature.py @@ -11,16 +11,16 @@ def test_openfeature_integration(sentry_init, capture_events, uninstall_integration): - uninstall_integration(OpenFeatureIntegration.identifier) - sentry_init(integrations=[OpenFeatureIntegration()]) - flags = { "hello": InMemoryFlag("on", {"on": True, "off": False}), "world": InMemoryFlag("off", {"on": True, "off": False}), } api.set_provider(InMemoryProvider(flags)) - client = api.get_client() + + uninstall_integration(OpenFeatureIntegration.identifier) + sentry_init(integrations=[OpenFeatureIntegration(client)]) + client.get_boolean_value("hello", default_value=False) client.get_boolean_value("world", default_value=False) client.get_boolean_value("other", default_value=True) @@ -41,18 +41,18 @@ def test_openfeature_integration(sentry_init, capture_events, uninstall_integrat def test_openfeature_integration_threaded( sentry_init, capture_events, uninstall_integration ): - uninstall_integration(OpenFeatureIntegration.identifier) - sentry_init(integrations=[OpenFeatureIntegration()]) - events = capture_events() - flags = { "hello": InMemoryFlag("on", {"on": True, "off": False}), "world": InMemoryFlag("off", {"on": True, "off": False}), } api.set_provider(InMemoryProvider(flags)) + client = api.get_client() + + uninstall_integration(OpenFeatureIntegration.identifier) + sentry_init(integrations=[OpenFeatureIntegration(client)]) + events = capture_events() # Capture an eval before we split isolation scopes. - client = api.get_client() client.get_boolean_value("hello", default_value=False) def task(flag): @@ -101,10 +101,20 @@ def test_openfeature_integration_asyncio( asyncio = pytest.importorskip("asyncio") + flags = { + "hello": InMemoryFlag("on", {"on": True, "off": False}), + "world": InMemoryFlag("off", {"on": True, "off": False}), + } + api.set_provider(InMemoryProvider(flags)) + client = api.get_client() + uninstall_integration(OpenFeatureIntegration.identifier) - sentry_init(integrations=[OpenFeatureIntegration()]) + sentry_init(integrations=[OpenFeatureIntegration(client)]) events = capture_events() + # Capture an eval before we split isolation scopes. + client.get_boolean_value("hello", default_value=False) + async def task(flag): with sentry_sdk.isolation_scope(): client.get_boolean_value(flag, default_value=False) @@ -115,16 +125,6 @@ async def task(flag): async def runner(): return asyncio.gather(task("world"), task("other")) - flags = { - "hello": InMemoryFlag("on", {"on": True, "off": False}), - "world": InMemoryFlag("off", {"on": True, "off": False}), - } - api.set_provider(InMemoryProvider(flags)) - - # Capture an eval before we split isolation scopes. - client = api.get_client() - client.get_boolean_value("hello", default_value=False) - asyncio.run(runner()) # Capture error in original scope From 0a75f7e10879c261d5cd820b12090995d16b48b4 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:07:24 -0800 Subject: [PATCH 2/6] Revert LD changes --- sentry_sdk/integrations/launchdarkly.py | 24 ++++++++-------- .../launchdarkly/test_launchdarkly.py | 28 +++++++++++++++++-- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/sentry_sdk/integrations/launchdarkly.py b/sentry_sdk/integrations/launchdarkly.py index 6b9d654cd1..066464cc22 100644 --- a/sentry_sdk/integrations/launchdarkly.py +++ b/sentry_sdk/integrations/launchdarkly.py @@ -4,37 +4,39 @@ from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.flag_utils import flag_error_processor -if TYPE_CHECKING: - from typing import Any, Optional - try: + import ldclient from ldclient.hook import Hook, Metadata if TYPE_CHECKING: from ldclient import LDClient from ldclient.hook import EvaluationSeriesContext from ldclient.evaluation import EvaluationDetail + + from typing import Any except ImportError: raise DidNotEnable("LaunchDarkly is not installed") class LaunchDarklyIntegration(Integration): identifier = "launchdarkly" - _client = None # type: Optional[LDClient] + _ld_client = None # type: LDClient | None - def __init__(self, client): - # type: (LDClient) -> None + def __init__(self, ld_client=None): + # type: (LDClient | None) -> None """ - :param client: An initialized LDClient instance. + :param client: An initialized LDClient instance. If a client is not provided, this + integration will attempt to use the shared global instance. """ - self.__class__._client = client + self.__class__._ld_client = ld_client @staticmethod def setup_once(): # type: () -> None - client = LaunchDarklyIntegration._client - if not client: - raise DidNotEnable("Error getting LDClient instance") + try: + client = LaunchDarklyIntegration._ld_client or ldclient.get() + except Exception as exc: + raise DidNotEnable("Error getting LaunchDarkly client. " + repr(exc)) # Register the flag collection hook with the LD client. client.add_hook(LaunchDarklyHook()) diff --git a/tests/integrations/launchdarkly/test_launchdarkly.py b/tests/integrations/launchdarkly/test_launchdarkly.py index 73f4f71886..e7576bb469 100644 --- a/tests/integrations/launchdarkly/test_launchdarkly.py +++ b/tests/integrations/launchdarkly/test_launchdarkly.py @@ -10,6 +10,7 @@ from ldclient.integrations.test_data import TestData import sentry_sdk +from sentry_sdk.integrations import DidNotEnable from sentry_sdk.integrations.launchdarkly import LaunchDarklyIntegration @@ -26,10 +27,11 @@ def test_launchdarkly_integration( uninstall_integration(LaunchDarklyIntegration.identifier) if use_global_client: ldclient.set_config(config) + sentry_init(integrations=[LaunchDarklyIntegration()]) client = ldclient.get() else: client = LDClient(config=config) - sentry_init(integrations=[LaunchDarklyIntegration(client)]) + sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) # Set test values td.update(td.flag("hello").variation_for_all(True)) @@ -61,7 +63,7 @@ def test_launchdarkly_integration_threaded( context = Context.create("user1") uninstall_integration(LaunchDarklyIntegration.identifier) - sentry_init(integrations=[LaunchDarklyIntegration(client)]) + sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) events = capture_events() def task(flag_key): @@ -120,7 +122,7 @@ def test_launchdarkly_integration_asyncio( context = Context.create("user1") uninstall_integration(LaunchDarklyIntegration.identifier) - sentry_init(integrations=[LaunchDarklyIntegration(client)]) + sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) events = capture_events() async def task(flag_key): @@ -164,3 +166,23 @@ async def runner(): {"flag": "world", "result": False}, ] } + + +def test_launchdarkly_integration_did_not_enable(sentry_init, uninstall_integration): + """ + Setup should fail when using global client and ldclient.set_config wasn't called. + + We're accessing ldclient internals to set up this test, so it might break if launchdarkly's + implementation changes. + """ + + ldclient._reset_client() + try: + ldclient.__lock.lock() + ldclient.__config = None + finally: + ldclient.__lock.unlock() + + uninstall_integration(LaunchDarklyIntegration.identifier) + with pytest.raises(DidNotEnable): + sentry_init(integrations=[LaunchDarklyIntegration()]) From 16917f7505d69eab1161ef618e496febae9015f0 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:13:52 -0800 Subject: [PATCH 3/6] Make OFClient optional arg --- sentry_sdk/integrations/openfeature.py | 16 +++++++++------- .../openfeature/test_openfeature.py | 17 +++++++++++++---- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/sentry_sdk/integrations/openfeature.py b/sentry_sdk/integrations/openfeature.py index 02ebb3673c..e62632f72b 100644 --- a/sentry_sdk/integrations/openfeature.py +++ b/sentry_sdk/integrations/openfeature.py @@ -8,6 +8,7 @@ from typing import Optional try: + from openfeature import api from openfeature.hook import Hook if TYPE_CHECKING: @@ -22,8 +23,8 @@ class OpenFeatureIntegration(Integration): identifier = "openfeature" _client = None # type: Optional[OpenFeatureClient] - def __init__(self, client): - # type: (OpenFeatureClient) -> None + def __init__(self, client=None): + # type: (Optional[OpenFeatureClient]) -> None self.__class__._client = client @staticmethod @@ -31,11 +32,12 @@ def setup_once(): # type: () -> None client = OpenFeatureIntegration._client - if not client: - raise DidNotEnable("Error getting OpenFeatureClient instance") - - # Register the hook within the openfeature client. - client.add_hooks(hooks=[OpenFeatureHook()]) + if client: + # Register the hook within the openfeature client. + client.add_hooks(hooks=[OpenFeatureHook()]) + else: + # Register the hook within the global openfeature hooks list. + api.add_hooks(hooks=[OpenFeatureHook()]) scope = sentry_sdk.get_current_scope() scope.add_error_processor(flag_error_processor) diff --git a/tests/integrations/openfeature/test_openfeature.py b/tests/integrations/openfeature/test_openfeature.py index cd2a0d47b0..29a11e333d 100644 --- a/tests/integrations/openfeature/test_openfeature.py +++ b/tests/integrations/openfeature/test_openfeature.py @@ -10,7 +10,13 @@ from sentry_sdk.integrations.openfeature import OpenFeatureIntegration -def test_openfeature_integration(sentry_init, capture_events, uninstall_integration): +@pytest.mark.parametrize( + "use_global_client", + (False, True), +) +def test_openfeature_integration( + sentry_init, use_global_client, capture_events, uninstall_integration +): flags = { "hello": InMemoryFlag("on", {"on": True, "off": False}), "world": InMemoryFlag("off", {"on": True, "off": False}), @@ -19,7 +25,10 @@ def test_openfeature_integration(sentry_init, capture_events, uninstall_integrat client = api.get_client() uninstall_integration(OpenFeatureIntegration.identifier) - sentry_init(integrations=[OpenFeatureIntegration(client)]) + if use_global_client: + sentry_init(integrations=[OpenFeatureIntegration()]) + else: + sentry_init(integrations=[OpenFeatureIntegration(client=client)]) client.get_boolean_value("hello", default_value=False) client.get_boolean_value("world", default_value=False) @@ -49,7 +58,7 @@ def test_openfeature_integration_threaded( client = api.get_client() uninstall_integration(OpenFeatureIntegration.identifier) - sentry_init(integrations=[OpenFeatureIntegration(client)]) + sentry_init(integrations=[OpenFeatureIntegration(client=client)]) events = capture_events() # Capture an eval before we split isolation scopes. @@ -109,7 +118,7 @@ def test_openfeature_integration_asyncio( client = api.get_client() uninstall_integration(OpenFeatureIntegration.identifier) - sentry_init(integrations=[OpenFeatureIntegration(client)]) + sentry_init(integrations=[OpenFeatureIntegration(client=client)]) events = capture_events() # Capture an eval before we split isolation scopes. From 58e412eeb81bd80b61fee6ea491db3c61cd010a7 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:38:48 -0800 Subject: [PATCH 4/6] OF client isolation test --- sentry_sdk/integrations/openfeature.py | 1 + .../openfeature/test_openfeature.py | 48 ++++++++++++++++--- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/sentry_sdk/integrations/openfeature.py b/sentry_sdk/integrations/openfeature.py index e62632f72b..525330e1d3 100644 --- a/sentry_sdk/integrations/openfeature.py +++ b/sentry_sdk/integrations/openfeature.py @@ -35,6 +35,7 @@ def setup_once(): if client: # Register the hook within the openfeature client. client.add_hooks(hooks=[OpenFeatureHook()]) + print("added hook to", client) else: # Register the hook within the global openfeature hooks list. api.add_hooks(hooks=[OpenFeatureHook()]) diff --git a/tests/integrations/openfeature/test_openfeature.py b/tests/integrations/openfeature/test_openfeature.py index 29a11e333d..f37ddd79dc 100644 --- a/tests/integrations/openfeature/test_openfeature.py +++ b/tests/integrations/openfeature/test_openfeature.py @@ -1,6 +1,5 @@ import concurrent.futures as cf import sys - import pytest from openfeature import api @@ -10,12 +9,22 @@ from sentry_sdk.integrations.openfeature import OpenFeatureIntegration +@pytest.fixture +def reset_openfeature(uninstall_integration): + yield + + # Teardown + uninstall_integration(OpenFeatureIntegration.identifier) + api.clear_hooks() + api.shutdown() # provider clean up + + @pytest.mark.parametrize( "use_global_client", (False, True), ) def test_openfeature_integration( - sentry_init, use_global_client, capture_events, uninstall_integration + sentry_init, use_global_client, capture_events, reset_openfeature ): flags = { "hello": InMemoryFlag("on", {"on": True, "off": False}), @@ -24,7 +33,6 @@ def test_openfeature_integration( api.set_provider(InMemoryProvider(flags)) client = api.get_client() - uninstall_integration(OpenFeatureIntegration.identifier) if use_global_client: sentry_init(integrations=[OpenFeatureIntegration()]) else: @@ -48,7 +56,7 @@ def test_openfeature_integration( def test_openfeature_integration_threaded( - sentry_init, capture_events, uninstall_integration + sentry_init, capture_events, reset_openfeature ): flags = { "hello": InMemoryFlag("on", {"on": True, "off": False}), @@ -57,7 +65,6 @@ def test_openfeature_integration_threaded( api.set_provider(InMemoryProvider(flags)) client = api.get_client() - uninstall_integration(OpenFeatureIntegration.identifier) sentry_init(integrations=[OpenFeatureIntegration(client=client)]) events = capture_events() @@ -104,7 +111,7 @@ def task(flag): @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") def test_openfeature_integration_asyncio( - sentry_init, capture_events, uninstall_integration + sentry_init, capture_events, reset_openfeature ): """Assert concurrently evaluated flags do not pollute one another.""" @@ -117,7 +124,6 @@ def test_openfeature_integration_asyncio( api.set_provider(InMemoryProvider(flags)) client = api.get_client() - uninstall_integration(OpenFeatureIntegration.identifier) sentry_init(integrations=[OpenFeatureIntegration(client=client)]) events = capture_events() @@ -160,3 +166,31 @@ async def runner(): {"flag": "world", "result": False}, ] } + + +def test_openfeature_integration_client_isolation( + sentry_init, capture_events, reset_openfeature +): + """ + If the integration is tracking a single client, evaluations from other clients should not be + captured. + """ + flags = { + "hello": InMemoryFlag("on", {"on": True, "off": False}), + "world": InMemoryFlag("off", {"on": True, "off": False}), + } + api.set_provider(InMemoryProvider(flags)) + client = api.get_client() + sentry_init(integrations=[OpenFeatureIntegration(client=client)]) + + other_client = api.get_client() + other_client.get_boolean_value("hello", default_value=False) + other_client.get_boolean_value("world", default_value=False) + other_client.get_boolean_value("other", default_value=True) + + events = capture_events() + sentry_sdk.set_tag("apple", "0") + sentry_sdk.capture_exception(Exception("something wrong!")) + + assert len(events) == 1 + assert events[0]["contexts"]["flags"] == {"values": []} From 349217a44e97bda0ae92041fccffa64536d8302a Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:51:31 -0800 Subject: [PATCH 5/6] LD client isolation test --- .../launchdarkly/test_launchdarkly.py | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/tests/integrations/launchdarkly/test_launchdarkly.py b/tests/integrations/launchdarkly/test_launchdarkly.py index e7576bb469..1ce4b8cf15 100644 --- a/tests/integrations/launchdarkly/test_launchdarkly.py +++ b/tests/integrations/launchdarkly/test_launchdarkly.py @@ -14,17 +14,31 @@ from sentry_sdk.integrations.launchdarkly import LaunchDarklyIntegration +@pytest.fixture +def reset_launchdarkly(uninstall_integration): + yield + + # Teardown. We're using ldclient internals here, so this might break if their implementation + # changes. + uninstall_integration(LaunchDarklyIntegration.identifier) + ldclient._reset_client() + try: + ldclient.__lock.lock() + ldclient.__config = None + finally: + ldclient.__lock.unlock() + + @pytest.mark.parametrize( "use_global_client", (False, True), ) def test_launchdarkly_integration( - sentry_init, use_global_client, capture_events, uninstall_integration + sentry_init, use_global_client, capture_events, reset_launchdarkly ): td = TestData.data_source() config = Config("sdk-key", update_processor_class=td) - uninstall_integration(LaunchDarklyIntegration.identifier) if use_global_client: ldclient.set_config(config) sentry_init(integrations=[LaunchDarklyIntegration()]) @@ -56,13 +70,12 @@ def test_launchdarkly_integration( def test_launchdarkly_integration_threaded( - sentry_init, capture_events, uninstall_integration + sentry_init, capture_events, reset_launchdarkly ): td = TestData.data_source() client = LDClient(config=Config("sdk-key", update_processor_class=td)) context = Context.create("user1") - uninstall_integration(LaunchDarklyIntegration.identifier) sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) events = capture_events() @@ -111,7 +124,7 @@ def task(flag_key): @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") def test_launchdarkly_integration_asyncio( - sentry_init, capture_events, uninstall_integration + sentry_init, capture_events, reset_launchdarkly ): """Assert concurrently evaluated flags do not pollute one another.""" @@ -121,7 +134,6 @@ def test_launchdarkly_integration_asyncio( client = LDClient(config=Config("sdk-key", update_processor_class=td)) context = Context.create("user1") - uninstall_integration(LaunchDarklyIntegration.identifier) sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) events = capture_events() @@ -168,21 +180,35 @@ async def runner(): } -def test_launchdarkly_integration_did_not_enable(sentry_init, uninstall_integration): +def test_launchdarkly_integration_client_isolation( + sentry_init, capture_events, reset_launchdarkly +): """ - Setup should fail when using global client and ldclient.set_config wasn't called. - - We're accessing ldclient internals to set up this test, so it might break if launchdarkly's - implementation changes. + If the integration is tracking a single client, evaluations from other clients should not be + captured. """ + td = TestData.data_source() + td.update(td.flag("hello").variation_for_all(True)) + td.update(td.flag("world").variation_for_all(True)) + client = LDClient(config=Config("sdk-key", update_processor_class=td)) + sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) - ldclient._reset_client() - try: - ldclient.__lock.lock() - ldclient.__config = None - finally: - ldclient.__lock.unlock() + # For isolation you must use a new Config object, but data source can be the same. + other_client = LDClient(Config("sdk-key", update_processor_class=td)) + other_client.variation("hello", Context.create("my-org", "organization"), False) + other_client.variation("world", Context.create("user1", "user"), False) + other_client.variation("other", Context.create("user2", "user"), False) - uninstall_integration(LaunchDarklyIntegration.identifier) + events = capture_events() + sentry_sdk.capture_exception(Exception("something wrong!")) + + assert len(events) == 1 + assert events[0]["contexts"]["flags"] == {"values": []} + + +def test_launchdarkly_integration_did_not_enable(sentry_init, reset_launchdarkly): + """ + Setup should fail when using global client and ldclient.set_config wasn't called. + """ with pytest.raises(DidNotEnable): sentry_init(integrations=[LaunchDarklyIntegration()]) From a8415ba6acef59db4eebe7daff2b307ee7d05dcb Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:54:59 -0800 Subject: [PATCH 6/6] Ref comment --- tests/integrations/launchdarkly/test_launchdarkly.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/integrations/launchdarkly/test_launchdarkly.py b/tests/integrations/launchdarkly/test_launchdarkly.py index 1ce4b8cf15..89fe232f59 100644 --- a/tests/integrations/launchdarkly/test_launchdarkly.py +++ b/tests/integrations/launchdarkly/test_launchdarkly.py @@ -18,9 +18,10 @@ def reset_launchdarkly(uninstall_integration): yield - # Teardown. We're using ldclient internals here, so this might break if their implementation - # changes. uninstall_integration(LaunchDarklyIntegration.identifier) + + # Resets global client and config only. We're using ldclient internals here, so this might + # break if their implementation changes. ldclient._reset_client() try: ldclient.__lock.lock()