1
1
import asyncio
2
+ import inspect
2
3
import sys
3
4
4
5
import pytest
5
6
6
7
import sentry_sdk
7
8
from sentry_sdk .consts import OP
8
- from sentry_sdk .integrations .asyncio import AsyncioIntegration
9
+ from sentry_sdk .integrations .asyncio import AsyncioIntegration , patch_asyncio
10
+
11
+ try :
12
+ from unittest .mock import MagicMock , patch
13
+ except ImportError :
14
+ from mock import MagicMock , patch
15
+
16
+ try :
17
+ from contextvars import Context , ContextVar
18
+ except ImportError :
19
+ pass # All tests will be skipped with incompatible versions
9
20
10
21
11
22
minimum_python_37 = pytest .mark .skipif (
12
23
sys .version_info < (3 , 7 ), reason = "Asyncio tests need Python >= 3.7"
13
24
)
14
25
15
26
27
+ minimum_python_311 = pytest .mark .skipif (
28
+ sys .version_info < (3 , 11 ),
29
+ reason = "Asyncio task context parameter was introduced in Python 3.11" ,
30
+ )
31
+
32
+
16
33
async def foo ():
17
34
await asyncio .sleep (0.01 )
18
35
@@ -33,6 +50,17 @@ def event_loop(request):
33
50
loop .close ()
34
51
35
52
53
+ def get_sentry_task_factory (mock_get_running_loop ):
54
+ """
55
+ Patches (mocked) asyncio and gets the sentry_task_factory.
56
+ """
57
+ mock_loop = mock_get_running_loop .return_value
58
+ patch_asyncio ()
59
+ patched_factory = mock_loop .set_task_factory .call_args [0 ][0 ]
60
+
61
+ return patched_factory
62
+
63
+
36
64
@minimum_python_37
37
65
@pytest .mark .asyncio
38
66
async def test_create_task (
@@ -170,3 +198,173 @@ async def add(a, b):
170
198
171
199
result = await asyncio .create_task (add (1 , 2 ))
172
200
assert result == 3 , result
201
+
202
+
203
+ @minimum_python_311
204
+ @pytest .mark .asyncio
205
+ async def test_task_with_context (sentry_init ):
206
+ """
207
+ Integration test to ensure working context parameter in Python 3.11+
208
+ """
209
+ sentry_init (
210
+ integrations = [
211
+ AsyncioIntegration (),
212
+ ],
213
+ )
214
+
215
+ var = ContextVar ("var" )
216
+ var .set ("original value" )
217
+
218
+ async def change_value ():
219
+ var .set ("changed value" )
220
+
221
+ async def retrieve_value ():
222
+ return var .get ()
223
+
224
+ # Create a context and run both tasks within the context
225
+ ctx = Context ()
226
+ async with asyncio .TaskGroup () as tg :
227
+ tg .create_task (change_value (), context = ctx )
228
+ retrieve_task = tg .create_task (retrieve_value (), context = ctx )
229
+
230
+ assert retrieve_task .result () == "changed value"
231
+
232
+
233
+ @minimum_python_37
234
+ @patch ("asyncio.get_running_loop" )
235
+ def test_patch_asyncio (mock_get_running_loop ):
236
+ """
237
+ Test that the patch_asyncio function will patch the task factory.
238
+ """
239
+ mock_loop = mock_get_running_loop .return_value
240
+
241
+ patch_asyncio ()
242
+
243
+ assert mock_loop .set_task_factory .called
244
+
245
+ set_task_factory_args , _ = mock_loop .set_task_factory .call_args
246
+ assert len (set_task_factory_args ) == 1
247
+
248
+ sentry_task_factory , * _ = set_task_factory_args
249
+ assert callable (sentry_task_factory )
250
+
251
+
252
+ @minimum_python_37
253
+ @pytest .mark .forked
254
+ @patch ("asyncio.get_running_loop" )
255
+ @patch ("sentry_sdk.integrations.asyncio.Task" )
256
+ def test_sentry_task_factory_no_factory (MockTask , mock_get_running_loop ): # noqa: N803
257
+ mock_loop = mock_get_running_loop .return_value
258
+ mock_coro = MagicMock ()
259
+
260
+ # Set the original task factory to None
261
+ mock_loop .get_task_factory .return_value = None
262
+
263
+ # Retieve sentry task factory (since it is an inner function within patch_asyncio)
264
+ sentry_task_factory = get_sentry_task_factory (mock_get_running_loop )
265
+
266
+ # The call we are testing
267
+ ret_val = sentry_task_factory (mock_loop , mock_coro )
268
+
269
+ assert MockTask .called
270
+ assert ret_val == MockTask .return_value
271
+
272
+ task_args , task_kwargs = MockTask .call_args
273
+ assert len (task_args ) == 1
274
+
275
+ coro_param , * _ = task_args
276
+ assert inspect .iscoroutine (coro_param )
277
+
278
+ assert "loop" in task_kwargs
279
+ assert task_kwargs ["loop" ] == mock_loop
280
+
281
+
282
+ @minimum_python_37
283
+ @pytest .mark .forked
284
+ @patch ("asyncio.get_running_loop" )
285
+ def test_sentry_task_factory_with_factory (mock_get_running_loop ):
286
+ mock_loop = mock_get_running_loop .return_value
287
+ mock_coro = MagicMock ()
288
+
289
+ # The original task factory will be mocked out here, let's retrieve the value for later
290
+ orig_task_factory = mock_loop .get_task_factory .return_value
291
+
292
+ # Retieve sentry task factory (since it is an inner function within patch_asyncio)
293
+ sentry_task_factory = get_sentry_task_factory (mock_get_running_loop )
294
+
295
+ # The call we are testing
296
+ ret_val = sentry_task_factory (mock_loop , mock_coro )
297
+
298
+ assert orig_task_factory .called
299
+ assert ret_val == orig_task_factory .return_value
300
+
301
+ task_factory_args , _ = orig_task_factory .call_args
302
+ assert len (task_factory_args ) == 2
303
+
304
+ loop_arg , coro_arg = task_factory_args
305
+ assert loop_arg == mock_loop
306
+ assert inspect .iscoroutine (coro_arg )
307
+
308
+
309
+ @minimum_python_311
310
+ @patch ("asyncio.get_running_loop" )
311
+ @patch ("sentry_sdk.integrations.asyncio.Task" )
312
+ def test_sentry_task_factory_context_no_factory (
313
+ MockTask , mock_get_running_loop # noqa: N803
314
+ ):
315
+ mock_loop = mock_get_running_loop .return_value
316
+ mock_coro = MagicMock ()
317
+ mock_context = MagicMock ()
318
+
319
+ # Set the original task factory to None
320
+ mock_loop .get_task_factory .return_value = None
321
+
322
+ # Retieve sentry task factory (since it is an inner function within patch_asyncio)
323
+ sentry_task_factory = get_sentry_task_factory (mock_get_running_loop )
324
+
325
+ # The call we are testing
326
+ ret_val = sentry_task_factory (mock_loop , mock_coro , context = mock_context )
327
+
328
+ assert MockTask .called
329
+ assert ret_val == MockTask .return_value
330
+
331
+ task_args , task_kwargs = MockTask .call_args
332
+ assert len (task_args ) == 1
333
+
334
+ coro_param , * _ = task_args
335
+ assert inspect .iscoroutine (coro_param )
336
+
337
+ assert "loop" in task_kwargs
338
+ assert task_kwargs ["loop" ] == mock_loop
339
+ assert "context" in task_kwargs
340
+ assert task_kwargs ["context" ] == mock_context
341
+
342
+
343
+ @minimum_python_311
344
+ @patch ("asyncio.get_running_loop" )
345
+ def test_sentry_task_factory_context_with_factory (mock_get_running_loop ):
346
+ mock_loop = mock_get_running_loop .return_value
347
+ mock_coro = MagicMock ()
348
+ mock_context = MagicMock ()
349
+
350
+ # The original task factory will be mocked out here, let's retrieve the value for later
351
+ orig_task_factory = mock_loop .get_task_factory .return_value
352
+
353
+ # Retieve sentry task factory (since it is an inner function within patch_asyncio)
354
+ sentry_task_factory = get_sentry_task_factory (mock_get_running_loop )
355
+
356
+ # The call we are testing
357
+ ret_val = sentry_task_factory (mock_loop , mock_coro , context = mock_context )
358
+
359
+ assert orig_task_factory .called
360
+ assert ret_val == orig_task_factory .return_value
361
+
362
+ task_factory_args , task_factory_kwargs = orig_task_factory .call_args
363
+ assert len (task_factory_args ) == 2
364
+
365
+ loop_arg , coro_arg = task_factory_args
366
+ assert loop_arg == mock_loop
367
+ assert inspect .iscoroutine (coro_arg )
368
+
369
+ assert "context" in task_factory_kwargs
370
+ assert task_factory_kwargs ["context" ] == mock_context
0 commit comments