33import glob
44import os
55import sys
6+ from typing import Type
7+ from typing import Union
68import uuid
79
810import pytest
911
1012from ddtrace import ext
1113from ddtrace .internal .datadog .profiling import ddup
12- from ddtrace .profiling .collector import asyncio as collector_asyncio
14+ from ddtrace .profiling .collector .asyncio import AsyncioLockCollector
15+ from ddtrace .profiling .collector .asyncio import AsyncioSemaphoreCollector
1316from tests .profiling .collector import pprof_utils
1417from tests .profiling .collector import test_collector
1518from tests .profiling .collector .lock_utils import get_lock_linenos
2023
2124PY_311_OR_ABOVE = sys .version_info [:2 ] >= (3 , 11 )
2225
23-
24- def test_repr ():
25- test_collector ._test_repr (
26- collector_asyncio .AsyncioLockCollector ,
27- "AsyncioLockCollector(status=<ServiceStatus.STOPPED: 'stopped'>, capture_pct=1.0, nframes=64, tracer=None)" , # noqa: E501
28- )
26+ # Type aliases for collector and lock types
27+ CollectorType = Union [
28+ Type [AsyncioLockCollector ],
29+ Type [AsyncioSemaphoreCollector ],
30+ ]
31+ LockType = Union [Type [asyncio .Lock ], Type [asyncio .Semaphore ]]
32+
33+
34+ @pytest .mark .parametrize (
35+ "collector_class,expected_repr" ,
36+ [
37+ (
38+ AsyncioLockCollector ,
39+ "AsyncioLockCollector(status=<ServiceStatus.STOPPED: 'stopped'>, capture_pct=1.0, nframes=64, tracer=None)" ,
40+ ),
41+ (
42+ AsyncioSemaphoreCollector ,
43+ "AsyncioSemaphoreCollector(status=<ServiceStatus.STOPPED: 'stopped'>, capture_pct=1.0, nframes=64, tracer=None)" , # noqa: E501
44+ ),
45+ ],
46+ )
47+ def test_collector_repr (collector_class : CollectorType , expected_repr : str ) -> None :
48+ test_collector ._test_repr (collector_class , expected_repr )
2949
3050
3151@pytest .mark .asyncio
32- class TestAsyncioLockCollector :
52+ class BaseAsyncioLockCollectorTest :
53+ """Base test class for asyncio lock collectors.
54+
55+ Child classes must implement:
56+ - collector_class: The collector class to test
57+ - lock_class: The asyncio lock class to test
58+ - lock_init_args: Arguments to pass to lock constructor (default: ())
59+ """
60+
61+ @property
62+ def collector_class (self ) -> CollectorType :
63+ raise NotImplementedError ("Child classes must implement collector_class" )
64+
65+ @property
66+ def lock_class (self ) -> LockType :
67+ raise NotImplementedError ("Child classes must implement lock_class" )
68+
69+ @property
70+ def lock_init_args (self ) -> tuple :
71+ """Arguments to pass to lock constructor. Override for Semaphore-like locks."""
72+ return ()
73+
74+ def create_lock (self ) -> Union [asyncio .Lock , asyncio .Semaphore ]:
75+ """Create a lock instance with the appropriate arguments."""
76+ return self .lock_class (* self .lock_init_args )
77+
3378 def setup_method (self , method ):
3479 self .test_name = method .__qualname__ if PY_311_OR_ABOVE else method .__name__
3580 self .output_prefix = "/tmp" + os .sep + self .test_name
@@ -51,16 +96,17 @@ def teardown_method(self):
5196 except Exception as e :
5297 print ("Error while deleting file: " , e )
5398
54- async def test_asyncio_lock_events (self ):
55- with collector_asyncio .AsyncioLockCollector (capture_pct = 100 ):
56- lock = asyncio .Lock () # !CREATE! test_asyncio_lock_events
57- await lock .acquire () # !ACQUIRE! test_asyncio_lock_events
99+ async def test_lock_events (self ):
100+ """Test basic acquire/release event profiling."""
101+ with self .collector_class (capture_pct = 100 ):
102+ lock = self .create_lock () # !CREATE! test_lock_events
103+ await lock .acquire () # !ACQUIRE! test_lock_events
58104 assert lock .locked ()
59- lock .release () # !RELEASE! test_asyncio_lock_events
105+ lock .release () # !RELEASE! test_lock_events
60106
61107 ddup .upload ()
62108
63- linenos = get_lock_linenos ("test_asyncio_lock_events " )
109+ linenos = get_lock_linenos ("test_lock_events " )
64110 profile = pprof_utils .parse_newest_profile (self .output_filename )
65111 expected_thread_id = _thread .get_ident ()
66112 pprof_utils .assert_lock_events (
@@ -85,29 +131,30 @@ async def test_asyncio_lock_events(self):
85131 ],
86132 )
87133
88- async def test_asyncio_lock_events_tracer (self , tracer ):
134+ async def test_lock_events_tracer (self , tracer ):
135+ """Test event profiling with tracer integration."""
89136 tracer ._endpoint_call_counter_span_processor .enable ()
90137 resource = str (uuid .uuid4 ())
91138 span_type = ext .SpanTypes .WEB
92139
93- with collector_asyncio . AsyncioLockCollector (capture_pct = 100 , tracer = tracer ):
94- lock = asyncio . Lock () # !CREATE! test_asyncio_lock_events_tracer_1
95- await lock .acquire () # !ACQUIRE! test_asyncio_lock_events_tracer_1
140+ with self . collector_class (capture_pct = 100 , tracer = tracer ):
141+ lock = self . create_lock () # !CREATE! test_lock_events_tracer_1
142+ await lock .acquire () # !ACQUIRE! test_lock_events_tracer_1
96143 with tracer .trace ("test" , resource = resource , span_type = span_type ) as t :
97- lock2 = asyncio . Lock () # !CREATE! test_asyncio_lock_events_tracer_2
98- await lock2 .acquire () # !ACQUIRE! test_asyncio_lock_events_tracer_2
99- lock .release () # !RELEASE! test_asyncio_lock_events_tracer_1
144+ lock2 = self . create_lock () # !CREATE! test_lock_events_tracer_2
145+ await lock2 .acquire () # !ACQUIRE! test_lock_events_tracer_2
146+ lock .release () # !RELEASE! test_lock_events_tracer_1
100147 span_id = t .span_id
101- lock2 .release () # !RELEASE! test_asyncio_lock_events_tracer_2
148+ lock2 .release () # !RELEASE! test_lock_events_tracer_2
102149
103- lock_ctx = asyncio . Lock () # !CREATE! test_asyncio_lock_events_tracer_3
104- async with lock_ctx : # !ACQUIRE! !RELEASE! test_asyncio_lock_events_tracer_3
150+ lock_ctx = self . create_lock () # !CREATE! test_lock_events_tracer_3
151+ async with lock_ctx : # !ACQUIRE! !RELEASE! test_lock_events_tracer_3
105152 pass
106153 ddup .upload (tracer = tracer )
107154
108- linenos_1 = get_lock_linenos ("test_asyncio_lock_events_tracer_1 " )
109- linenos_2 = get_lock_linenos ("test_asyncio_lock_events_tracer_2 " )
110- linenos_3 = get_lock_linenos ("test_asyncio_lock_events_tracer_3 " , with_stmt = True )
155+ linenos_1 = get_lock_linenos ("test_lock_events_tracer_1 " )
156+ linenos_2 = get_lock_linenos ("test_lock_events_tracer_2 " )
157+ linenos_3 = get_lock_linenos ("test_lock_events_tracer_3 " , with_stmt = True )
111158
112159 profile = pprof_utils .parse_newest_profile (self .output_filename )
113160 expected_thread_id = _thread .get_ident ()
@@ -167,3 +214,31 @@ async def test_asyncio_lock_events_tracer(self, tracer):
167214 ),
168215 ],
169216 )
217+
218+
219+ class TestAsyncioLockCollector (BaseAsyncioLockCollectorTest ):
220+ """Test asyncio.Lock profiling."""
221+
222+ @property
223+ def collector_class (self ):
224+ return AsyncioLockCollector
225+
226+ @property
227+ def lock_class (self ):
228+ return asyncio .Lock
229+
230+
231+ class TestAsyncioSemaphoreCollector (BaseAsyncioLockCollectorTest ):
232+ """Test asyncio.Semaphore profiling."""
233+
234+ @property
235+ def collector_class (self ):
236+ return AsyncioSemaphoreCollector
237+
238+ @property
239+ def lock_class (self ):
240+ return asyncio .Semaphore
241+
242+ @property
243+ def lock_init_args (self ):
244+ return (2 ,) # Initial semaphore value
0 commit comments