diff --git a/tests/integrations/unleash/test_unleash.py b/tests/integrations/unleash/test_unleash.py index 221a0fbfdd..2dc49e5a43 100644 --- a/tests/integrations/unleash/test_unleash.py +++ b/tests/integrations/unleash/test_unleash.py @@ -1,6 +1,10 @@ +import concurrent.futures as cf +import sys from random import random from unittest.mock import patch +import pytest + import sentry_sdk from sentry_sdk.integrations.unleash import UnleashIntegration from tests.integrations.unleash.testutils import MockUnleashClient @@ -57,6 +61,204 @@ def test_get_variant(sentry_init, capture_events, uninstall_integration): } +@patch("sentry_sdk.integrations.unleash.UnleashClient", MockUnleashClient) +def test_is_enabled_threaded(sentry_init, capture_events, uninstall_integration): + client = MockUnleashClient() + uninstall_integration(UnleashIntegration) + sentry_init(integrations=[UnleashIntegration()]) + events = capture_events() + + def task(flag_key): + # Creates a new isolation scope for the thread. + # This means the evaluations in each task are captured separately. + with sentry_sdk.isolation_scope(): + client.is_enabled(flag_key) + # use a tag to identify to identify events later on + sentry_sdk.set_tag("task_id", flag_key) + sentry_sdk.capture_exception(Exception("something wrong!")) + + # Capture an eval before we split isolation scopes. + client.is_enabled("hello") + + with cf.ThreadPoolExecutor(max_workers=2) as pool: + pool.map(task, ["world", "other"]) + + # Capture error in original scope + sentry_sdk.set_tag("task_id", "0") + sentry_sdk.capture_exception(Exception("something wrong!")) + + assert len(events) == 3 + events.sort(key=lambda e: e["tags"]["task_id"]) + + assert events[0]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": True}, + ] + } + assert events[1]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": True}, + {"flag": "other", "result": False}, + ] + } + assert events[2]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": True}, + {"flag": "world", "result": False}, + ] + } + + +@patch("sentry_sdk.integrations.unleash.UnleashClient", MockUnleashClient) +def test_get_variant_threaded(sentry_init, capture_events, uninstall_integration): + client = MockUnleashClient() + uninstall_integration(UnleashIntegration) + sentry_init(integrations=[UnleashIntegration()]) + events = capture_events() + + def task(flag_key): + # Creates a new isolation scope for the thread. + # This means the evaluations in each task are captured separately. + with sentry_sdk.isolation_scope(): + client.get_variant(flag_key) + # use a tag to identify to identify events later on + sentry_sdk.set_tag("task_id", flag_key) + sentry_sdk.capture_exception(Exception("something wrong!")) + + # Capture an eval before we split isolation scopes. + client.get_variant("hello") + + with cf.ThreadPoolExecutor(max_workers=2) as pool: + pool.map(task, ["other", "toggle_feature"]) + + # Capture error in original scope + sentry_sdk.set_tag("task_id", "0") + sentry_sdk.capture_exception(Exception("something wrong!")) + + assert len(events) == 3 + events.sort(key=lambda e: e["tags"]["task_id"]) + + assert events[0]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": False}, + ] + } + assert events[1]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": False}, + {"flag": "other", "result": False}, + ] + } + assert events[2]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": False}, + {"flag": "toggle_feature", "result": True}, + ] + } + + +@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") +@patch("sentry_sdk.integrations.unleash.UnleashClient", MockUnleashClient) +def test_is_enabled_asyncio(sentry_init, capture_events, uninstall_integration): + asyncio = pytest.importorskip("asyncio") + + client = MockUnleashClient() + uninstall_integration(UnleashIntegration) + sentry_init(integrations=[UnleashIntegration()]) + events = capture_events() + + async def task(flag_key): + with sentry_sdk.isolation_scope(): + client.is_enabled(flag_key) + # use a tag to identify to identify events later on + sentry_sdk.set_tag("task_id", flag_key) + sentry_sdk.capture_exception(Exception("something wrong!")) + + async def runner(): + return asyncio.gather(task("world"), task("other")) + + # Capture an eval before we split isolation scopes. + client.is_enabled("hello") + + asyncio.run(runner()) + + # Capture error in original scope + sentry_sdk.set_tag("task_id", "0") + sentry_sdk.capture_exception(Exception("something wrong!")) + + assert len(events) == 3 + events.sort(key=lambda e: e["tags"]["task_id"]) + + assert events[0]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": True}, + ] + } + assert events[1]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": True}, + {"flag": "other", "result": False}, + ] + } + assert events[2]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": True}, + {"flag": "world", "result": False}, + ] + } + + +@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") +@patch("sentry_sdk.integrations.unleash.UnleashClient", MockUnleashClient) +def test_get_variant_asyncio(sentry_init, capture_events, uninstall_integration): + asyncio = pytest.importorskip("asyncio") + + client = MockUnleashClient() + uninstall_integration(UnleashIntegration) + sentry_init(integrations=[UnleashIntegration()]) + events = capture_events() + + async def task(flag_key): + with sentry_sdk.isolation_scope(): + client.get_variant(flag_key) + # use a tag to identify to identify events later on + sentry_sdk.set_tag("task_id", flag_key) + sentry_sdk.capture_exception(Exception("something wrong!")) + + async def runner(): + return asyncio.gather(task("other"), task("toggle_feature")) + + # Capture an eval before we split isolation scopes. + client.get_variant("hello") + + asyncio.run(runner()) + + # Capture error in original scope + sentry_sdk.set_tag("task_id", "0") + sentry_sdk.capture_exception(Exception("something wrong!")) + + assert len(events) == 3 + events.sort(key=lambda e: e["tags"]["task_id"]) + + assert events[0]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": False}, + ] + } + assert events[1]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": False}, + {"flag": "other", "result": False}, + ] + } + assert events[2]["contexts"]["flags"] == { + "values": [ + {"flag": "hello", "result": False}, + {"flag": "toggle_feature", "result": True}, + ] + } + + @patch("sentry_sdk.integrations.unleash.UnleashClient", MockUnleashClient) def test_wraps_original(sentry_init, uninstall_integration): uninstall_integration(UnleashIntegration)