22# This product includes software developed at Datadog (https://www.datadoghq.com/).
33# Copyright 2021 Datadog, Inc.
44
5+ from collections .abc import Callable
56import re
67import os
78import tests .debugger .utils as debugger
1112from utils import scenarios , features , bug , context , flaky , irrelevant , missing_feature , logger
1213
1314
14- def get_env_bool (env_var_name , * , default = False ) -> bool :
15+ def get_env_bool (env_var_name : str , * , default : bool = False ) -> bool :
1516 value = os .getenv (env_var_name , str (default )).lower ()
1617 return value in {"true" , "True" , "1" }
1718
@@ -33,11 +34,11 @@ def get_env_bool(env_var_name, *, default=False) -> bool:
3334@missing_feature (context .library == "golang" , reason = "Not yet implemented" , force_skip = True )
3435@irrelevant (context .library <= "[email protected] " , reason = "DEBUG-4582" ) 3536class Test_Debugger_Exception_Replay (debugger .BaseDebuggerTest ):
36- snapshots : dict = {}
37+ snapshots : list [ dict ] = []
3738 spans : dict = {}
3839
3940 ############ setup ############
40- def _setup (self , request_path , exception_message ):
41+ def _setup (self , request_path : str , exception_message : str ):
4142 self .weblog_responses = []
4243
4344 retries = 0
@@ -54,7 +55,7 @@ def _setup(self, request_path, exception_message):
5455 retries += 1
5556
5657 ############ assert ############
57- def _assert (self , test_name , expected_exception_messages ):
58+ def _assert (self , test_name : str , expected_exception_messages : list [ str ] ):
5859 def __filter_contents_by_message ():
5960 filtered_contents = []
6061
@@ -65,9 +66,9 @@ def __filter_contents_by_message():
6566 if expected_exception_message in exception_message :
6667 filtered_contents .append ((exception_message , content ))
6768
68- def get_sort_key (content_tuple ):
69+ def get_sort_key (content_tuple : tuple [ str , dict ] ):
6970 message , content = content_tuple
70- snapshot = content ["debugger" ]["snapshot" ]
71+ snapshot : dict = content ["debugger" ]["snapshot" ]
7172
7273 method_name = snapshot .get ("probe" , {}).get ("location" , {}).get ("method" , "" )
7374 line_number = snapshot .get ("probe" , {}).get ("location" , {}).get ("lines" , [])
@@ -86,7 +87,7 @@ def get_sort_key(content_tuple):
8687
8788 return [snapshot for _ , snapshot in filtered_contents ]
8889
89- def __filter_spans_by_snapshot_id (snapshots ):
90+ def __filter_spans_by_snapshot_id (snapshots : list [ dict ] ):
9091 filtered_spans = {}
9192
9293 for snapshot in snapshots :
@@ -103,7 +104,7 @@ def __filter_spans_by_snapshot_id(snapshots):
103104
104105 return filtered_spans
105106
106- def __filter_spans_by_span_id (contents ):
107+ def __filter_spans_by_span_id (contents : list [ dict ] ):
107108 filtered_spans = {}
108109 for content in contents :
109110 span_id = content .get ("dd" , {}).get ("span_id" ) or content .get ("dd.span_id" )
@@ -131,8 +132,13 @@ def __filter_spans_by_span_id(contents):
131132 spans = __filter_spans_by_snapshot_id (snapshots )
132133 self ._validate_spans (test_name , spans )
133134
134- def _validate_exception_replay_snapshots (self , test_name , snapshots ):
135- def __scrub (data ):
135+ def _validate_exception_replay_snapshots (self , test_name : str , snapshots : list [dict ]):
136+ def __scrub_dict (data : dict ) -> dict :
137+ result = __scrub (data )
138+ assert isinstance (result , dict )
139+ return result
140+
141+ def __scrub (data : dict | list | str ) -> dict | list | str :
136142 if isinstance (data , dict ):
137143 scrubbed_data = {}
138144 for key , value in data .items ():
@@ -149,10 +155,10 @@ def __scrub(data):
149155 else :
150156 return data
151157
152- def __scrub_java (key , value , parent ):
158+ def __scrub_java (key : str , value : dict | list , parent : dict ):
153159 runtime = ("jdk." , "org." , "java" )
154160
155- def skip_runtime (value , skip_condition , del_filename = None ):
161+ def skip_runtime (value : list , skip_condition : Callable , del_filename : Callable | None = None ):
156162 scrubbed = []
157163
158164 for entry in value :
@@ -175,36 +181,41 @@ def skip_runtime(value, skip_condition, del_filename=None):
175181 return "<scrubbed>"
176182
177183 if parent ["type" ] == "java.lang.Object[]" :
184+ assert isinstance (value , list )
178185 return skip_runtime (value , lambda e : "value" in e and e ["value" ].startswith (runtime ))
179186
180187 if parent ["type" ] == "java.lang.StackTraceElement[]" :
188+ assert isinstance (value , list )
181189 return skip_runtime (value , lambda e : e ["fields" ]["declaringClass" ]["value" ].startswith (runtime ))
182190
183191 return __scrub (value )
184192
185193 elif key == "moduleVersion" :
186194 return "<scrubbed>"
187195 elif key in ["stacktrace" , "stack" ]:
196+ assert isinstance (value , list )
188197 return skip_runtime (
189198 value , lambda e : "function" in e and e ["function" ].startswith (runtime ), lambda e : "fileName" in e
190199 )
191200
192201 return __scrub (value )
193202
194- def __scrub_dotnet (key , value , parent ): # noqa: ARG001
203+ def __scrub_dotnet (key : str , value : dict | list | str , parent : dict ): # noqa: ARG001
195204 if key == "Id" :
196205 return "<scrubbed>"
197206 elif key == "StackTrace" and isinstance (value , dict ):
198207 value ["value" ] = "<scrubbed>"
199208 return value
200209 elif key == "function" :
210+ assert isinstance (value , str )
201211 if "lambda_" in value :
202212 value = re .sub (r"(lambda_method)\d+" , r"\1<scrubbed>" , value )
203213 if re .search (r"<[^>]+>" , value ):
204214 value = re .sub (r"(.*>)(.*)" , r"\1<scrubbed>" , value )
205215 return value
206216 elif key in ["stacktrace" , "stack" ]:
207217 scrubbed = []
218+ assert isinstance (value , list )
208219 for entry in value :
209220 # skip inner runtime methods from stack traces since they are not relevant to debugger
210221 if entry ["function" ].startswith (("Microsoft" , "System" , "Unknown" )):
@@ -216,8 +227,9 @@ def __scrub_dotnet(key, value, parent): # noqa: ARG001
216227 return scrubbed
217228 return __scrub (value )
218229
219- def __scrub_python (key , value , parent ): # noqa: ARG001
230+ def __scrub_python (key : str , value : dict | list , parent : dict ): # noqa: ARG001
220231 if key == "@exception" :
232+ assert isinstance (value , dict )
221233 value ["fields" ] = "<scrubbed>"
222234 return value
223235
@@ -241,7 +253,7 @@ def __scrub_python(key, value, parent): # noqa: ARG001
241253
242254 return __scrub (value )
243255
244- def __scrub_none (key , value , parent ): # noqa: ARG001
256+ def __scrub_none (key : str , value : dict | list , parent : dict ): # noqa: ARG001
245257 return __scrub (value )
246258
247259 if self .get_tracer ()["language" ] == "java" :
@@ -253,7 +265,7 @@ def __scrub_none(key, value, parent): # noqa: ARG001
253265 else :
254266 scrub_language = __scrub_none
255267
256- def __approve (snapshots ):
268+ def __approve (snapshots : list ):
257269 self ._write_approval (snapshots , test_name , "snapshots_received" )
258270
259271 if _OVERRIDE_APROVALS or _STORE_NEW_APPROVALS :
@@ -268,14 +280,14 @@ def __approve(snapshots):
268280 assert snapshots , "Snapshots not found"
269281
270282 if not _SKIP_SCRUB :
271- snapshots = [__scrub (snapshot ) for snapshot in snapshots ]
283+ snapshots = [__scrub_dict (snapshot ) for snapshot in snapshots ]
272284
273285 self .snapshots = snapshots
274286 __approve (snapshots )
275287
276- def _validate_spans (self , test_name : str , spans ):
277- def __scrub (data ) :
278- def scrub_span (key , value ):
288+ def _validate_spans (self , test_name : str , spans : dict [ str , dict ] ):
289+ def __scrub (data : dict [ str , dict ]) -> dict [ str , dict ] :
290+ def scrub_span (key : str , value : dict | list ):
279291 if key in {"traceID" , "spanID" , "parentID" , "start" , "duration" , "metrics" }:
280292 return "<scrubbed>"
281293
@@ -320,7 +332,7 @@ def scrub_span(key, value):
320332
321333 return scrubbed_spans
322334
323- def __approve (spans ):
335+ def __approve (spans : dict [ str , dict ] ):
324336 self ._write_approval (spans , test_name , "spans_received" )
325337
326338 if _OVERRIDE_APROVALS or _STORE_NEW_APPROVALS :
@@ -356,15 +368,15 @@ def __approve(spans):
356368 self .spans = spans
357369 __approve (spans )
358370
359- def _validate_recursion_snapshots (self , snapshots , limit ):
371+ def _validate_recursion_snapshots (self , snapshots : list [ dict ] , limit : int ):
360372 assert len (snapshots ) == limit + 1 , (
361373 f"Expected { limit + 1 } snapshots for recursion limit { limit } , got { len (snapshots )} "
362374 )
363375
364376 entry_method = "exceptionReplayRecursion"
365377 helper_method = "exceptionReplayRecursionHelper"
366378
367- def get_frames (snapshot ) :
379+ def get_frames (snapshot : dict ) -> list [ dict ] :
368380 if self .get_tracer ()["language" ] in ["java" , "dotnet" ]:
369381 method = snapshot .get ("probe" , {}).get ("location" , {}).get ("method" , None )
370382 if method :
@@ -375,7 +387,7 @@ def get_frames(snapshot):
375387 found_top = False
376388 found_lowest = False
377389
378- def check_frames (frames ):
390+ def check_frames (frames : list [ dict ] ):
379391 nonlocal found_top , found_lowest
380392
381393 for frame in frames :
@@ -453,7 +465,7 @@ def _get_approval_version(self) -> str:
453465 compatible_versions .sort (key = lambda x : version .parse (x ), reverse = True )
454466 return compatible_versions [0 ]
455467
456- def _write_approval (self , data : list , test_name : str , suffix : str ) -> None :
468+ def _write_approval (self , data : list | dict , test_name : str , suffix : str ) -> None :
457469 """Write approval data to version-aware path."""
458470 version = self ._get_approval_version ()
459471 debugger .write_approval (data , test_name , suffix , version )
0 commit comments