Skip to content

Commit beb9512

Browse files
committed
update ffIntegration test to be e2e, and fix LRU copy bug
1 parent 625969e commit beb9512

File tree

3 files changed

+105
-39
lines changed

3 files changed

+105
-39
lines changed

sentry_sdk/_lru_cache.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
6363
"""
6464

65-
from copy import copy
65+
from copy import copy, deepcopy
6666

6767
SENTINEL = object()
6868

@@ -92,10 +92,13 @@ def __init__(self, max_size):
9292
self.hits = self.misses = 0
9393

9494
def __copy__(self):
95+
"""
96+
Cache keys and values are shallow copied.
97+
"""
9598
cache = LRUCache(self.max_size)
9699
cache.full = self.full
97100
cache.cache = copy(self.cache)
98-
cache.root = copy(self.root)
101+
cache.root = deepcopy(self.root)
99102
return cache
100103

101104
def set(self, key, value):

tests/integrations/featureflags/test_featureflags.py

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,72 +2,116 @@
22
import concurrent.futures as cf
33

44
import sentry_sdk
5+
from sentry_sdk.integrations import _processed_integrations
56
from sentry_sdk.integrations.featureflags import FeatureFlagsIntegration
67

78

8-
def test_featureflags_integration(sentry_init):
9+
def test_featureflags_integration(sentry_init, capture_events):
10+
_processed_integrations.discard(
11+
FeatureFlagsIntegration.identifier
12+
) # force reinstall
913
sentry_init(integrations=[FeatureFlagsIntegration()])
1014
flags_integration = sentry_sdk.get_client().get_integration(FeatureFlagsIntegration)
1115

1216
flags_integration.set_flag("hello", False)
1317
flags_integration.set_flag("world", True)
1418
flags_integration.set_flag("other", False)
1519

16-
assert sentry_sdk.get_current_scope().flags.get() == [
17-
{"flag": "hello", "result": False},
18-
{"flag": "world", "result": True},
19-
{"flag": "other", "result": False},
20-
]
20+
events = capture_events()
21+
sentry_sdk.capture_exception(Exception("something wrong!"))
22+
[event] = events
2123

24+
assert event["contexts"]["flags"] == {
25+
"values": [
26+
{"flag": "hello", "result": False},
27+
{"flag": "world", "result": True},
28+
{"flag": "other", "result": False},
29+
]
30+
}
2231

23-
def test_featureflags_integration_threaded(sentry_init):
32+
33+
def test_featureflags_integration_threaded(sentry_init, capture_events):
34+
_processed_integrations.discard(
35+
FeatureFlagsIntegration.identifier
36+
) # force reinstall
2437
sentry_init(integrations=[FeatureFlagsIntegration()])
38+
events = capture_events()
39+
40+
# Capture an eval before we split isolation scopes.
2541
flags_integration = sentry_sdk.get_client().get_integration(FeatureFlagsIntegration)
42+
flags_integration.set_flag("hello", False)
2643

2744
def task(flag_key):
2845
# Creates a new isolation scope for the thread.
2946
# This means the evaluations in each task are captured separately.
3047
with sentry_sdk.isolation_scope():
48+
flags_integration = sentry_sdk.get_client().get_integration(
49+
FeatureFlagsIntegration
50+
)
3151
flags_integration.set_flag(flag_key, False)
32-
return sentry_sdk.get_current_scope().flags.get()
33-
34-
# Capture an eval before we split isolation scopes.
35-
flags_integration.set_flag("hello", False)
52+
# use a tag to identify to identify events later on
53+
sentry_sdk.set_tag("flag_key", flag_key)
54+
sentry_sdk.capture_exception(Exception("something wrong!"))
3655

56+
# Run tasks in separate threads
3757
with cf.ThreadPoolExecutor(max_workers=2) as pool:
38-
results = list(pool.map(task, ["world", "other"]))
39-
40-
assert results[0] == [
41-
{"flag": "hello", "result": False},
42-
{"flag": "world", "result": False},
43-
]
44-
assert results[1] == [
45-
{"flag": "hello", "result": False},
46-
{"flag": "other", "result": False},
47-
]
48-
49-
50-
def test_featureflags_integration_asyncio(sentry_init):
51-
"""Assert concurrently evaluated flags do not pollute one another."""
58+
pool.map(task, ["world", "other"])
59+
60+
assert len(events) == 2
61+
events.sort(key=lambda e: e["tags"]["flag_key"])
62+
assert events[0]["contexts"]["flags"] == {
63+
"values": [
64+
{"flag": "hello", "result": False},
65+
{"flag": "other", "result": False},
66+
]
67+
}
68+
assert events[1]["contexts"]["flags"] == {
69+
"values": [
70+
{"flag": "hello", "result": False},
71+
{"flag": "world", "result": False},
72+
]
73+
}
74+
75+
76+
def test_featureflags_integration_asyncio(sentry_init, capture_events):
77+
_processed_integrations.discard(
78+
FeatureFlagsIntegration.identifier
79+
) # force reinstall
5280
sentry_init(integrations=[FeatureFlagsIntegration()])
81+
events = capture_events()
82+
83+
# Capture an eval before we split isolation scopes.
5384
flags_integration = sentry_sdk.get_client().get_integration(FeatureFlagsIntegration)
85+
flags_integration.set_flag("hello", False)
5486

5587
async def task(flag_key):
88+
# Creates a new isolation scope for the thread.
89+
# This means the evaluations in each task are captured separately.
5690
with sentry_sdk.isolation_scope():
91+
flags_integration = sentry_sdk.get_client().get_integration(
92+
FeatureFlagsIntegration
93+
)
5794
flags_integration.set_flag(flag_key, False)
58-
return sentry_sdk.get_current_scope().flags.get()
95+
# use a tag to identify to identify events later on
96+
sentry_sdk.set_tag("flag_key", flag_key)
97+
sentry_sdk.capture_exception(Exception("something wrong!"))
5998

6099
async def runner():
61100
return asyncio.gather(task("world"), task("other"))
62101

63-
flags_integration.set_flag("hello", False)
64-
65-
results = asyncio.run(runner()).result()
66-
assert results[0] == [
67-
{"flag": "hello", "result": False},
68-
{"flag": "world", "result": False},
69-
]
70-
assert results[1] == [
71-
{"flag": "hello", "result": False},
72-
{"flag": "other", "result": False},
73-
]
102+
asyncio.run(runner())
103+
104+
assert len(events) == 2
105+
events.sort(key=lambda e: e["tags"]["flag_key"])
106+
assert events[0]["contexts"]["flags"] == {
107+
"values": [
108+
{"flag": "hello", "result": False},
109+
{"flag": "other", "result": False},
110+
]
111+
}
112+
assert events[1]["contexts"]["flags"] == {
113+
"values": [
114+
{"flag": "hello", "result": False},
115+
{"flag": "world", "result": False},
116+
]
117+
}

tests/test_lru_cache.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from copy import copy
2+
13
import pytest
24

35
from sentry_sdk._lru_cache import LRUCache
@@ -58,3 +60,20 @@ def test_cache_get_all():
5860
assert cache.get_all() == [(1, 1), (2, 2), (3, 3)]
5961
cache.get(1)
6062
assert cache.get_all() == [(2, 2), (3, 3), (1, 1)]
63+
64+
65+
def test_cache_copy():
66+
cache = LRUCache(3)
67+
cache.set(0, 0)
68+
cache.set(1, 1)
69+
70+
copied = copy(cache)
71+
cache.set(2, 2)
72+
cache.set(3, 3)
73+
assert copied.get_all() == [(0, 0), (1, 1)]
74+
assert cache.get_all() == [(1, 1), (2, 2), (3, 3)]
75+
76+
copied = copy(cache)
77+
cache.get(1)
78+
assert copied.get_all() == [(1, 1), (2, 2), (3, 3)]
79+
assert cache.get_all() == [(2, 2), (3, 3), (1, 1)]

0 commit comments

Comments
 (0)