@@ -35,6 +35,119 @@ def create_debug_adapter(
3535 env = lldbDAPEnv ,
3636 )
3737
38+ def _get_dap_server (self , child_session_index : Optional [int ] = None ) -> dap_server .DebugAdapterServer :
39+ """Get a specific DAP server instance.
40+
41+ Args:
42+ child_session_index: Index of child session, or None for main session
43+
44+ Returns:
45+ The requested DAP server instance
46+ """
47+ if child_session_index is None :
48+ return self .dap_server
49+ else :
50+ child_sessions = self .dap_server .get_child_sessions ()
51+ if child_session_index >= len (child_sessions ):
52+ raise IndexError (f"Child session index { child_session_index } out of range. Found { len (child_sessions )} child sessions." )
53+ return child_sessions [child_session_index ]
54+
55+ def _set_source_breakpoints_impl (self , dap_server_instance , source_path , lines , data = None , wait_for_resolve = True ):
56+ """Implementation for setting source breakpoints on any DAP server"""
57+ response = dap_server_instance .request_setBreakpoints (Source (source_path ), lines , data )
58+ if response is None or not response ["success" ]:
59+ return []
60+ breakpoints = response ["body" ]["breakpoints" ]
61+ breakpoint_ids = []
62+ for breakpoint in breakpoints :
63+ breakpoint_ids .append ("%i" % (breakpoint ["id" ]))
64+ if wait_for_resolve :
65+ self ._wait_for_breakpoints_to_resolve_impl (dap_server_instance , breakpoint_ids )
66+ return breakpoint_ids
67+
68+ def _wait_for_breakpoints_to_resolve_impl (self , dap_server_instance , breakpoint_ids , timeout = None ):
69+ """Implementation for waiting for breakpoints to resolve on any DAP server"""
70+ if timeout is None :
71+ timeout = self .DEFAULT_TIMEOUT
72+ unresolved_breakpoints = dap_server_instance .wait_for_breakpoints_to_be_verified (breakpoint_ids , timeout )
73+ self .assertEqual (
74+ len (unresolved_breakpoints ),
75+ 0 ,
76+ f"Expected to resolve all breakpoints. Unresolved breakpoint ids: { unresolved_breakpoints } " ,
77+ )
78+
79+ def _verify_breakpoint_hit_impl (self , dap_server_instance , breakpoint_ids , timeout = None ):
80+ """Implementation for verifying breakpoint hit on any DAP server"""
81+ if timeout is None :
82+ timeout = self .DEFAULT_TIMEOUT
83+ stopped_events = dap_server_instance .wait_for_stopped (timeout )
84+ for stopped_event in stopped_events :
85+ if "body" in stopped_event :
86+ body = stopped_event ["body" ]
87+ if "reason" not in body :
88+ continue
89+ if (
90+ body ["reason" ] != "breakpoint"
91+ and body ["reason" ] != "instruction breakpoint"
92+ ):
93+ continue
94+ if "description" not in body :
95+ continue
96+ # Descriptions for breakpoints will be in the form
97+ # "breakpoint 1.1", so look for any description that matches
98+ # ("breakpoint 1.") in the description field as verification
99+ # that one of the breakpoint locations was hit. DAP doesn't
100+ # allow breakpoints to have multiple locations, but LLDB does.
101+ # So when looking at the description we just want to make sure
102+ # the right breakpoint matches and not worry about the actual
103+ # location.
104+ description = body ["description" ]
105+ for breakpoint_id in breakpoint_ids :
106+ match_desc = f"breakpoint { breakpoint_id } ."
107+ if match_desc in description :
108+ return
109+ self .assertTrue (False , f"breakpoint not hit, stopped_events={ stopped_events } " )
110+
111+ def _do_continue_impl (self , dap_server_instance ):
112+ """Implementation for continuing execution on any DAP server"""
113+ resp = dap_server_instance .request_continue ()
114+ self .assertTrue (resp ["success" ], f"continue request failed: { resp } " )
115+
116+ # Multi-session methods for operating on specific sessions without switching context
117+ def set_source_breakpoints_on (self , child_session_index : Optional [int ], source_path , lines , data = None , wait_for_resolve = True ):
118+ """Set source breakpoints on a specific DAP session without switching the active session."""
119+ return self ._set_source_breakpoints_impl (
120+ self ._get_dap_server (child_session_index ), source_path , lines , data , wait_for_resolve
121+ )
122+
123+ def verify_breakpoint_hit_on (self , child_session_index : Optional [int ], breakpoint_ids : list [str ], timeout = DEFAULT_TIMEOUT ):
124+ """Verify breakpoint hit on a specific DAP session without switching the active session."""
125+ return self ._verify_breakpoint_hit_impl (
126+ self ._get_dap_server (child_session_index ), breakpoint_ids , timeout
127+ )
128+
129+ def do_continue_on (self , child_session_index : Optional [int ]):
130+ """Continue execution on a specific DAP session without switching the active session."""
131+ return self ._do_continue_impl (self ._get_dap_server (child_session_index ))
132+
133+ def start_server (self , connection ):
134+ """
135+ Start an lldb-dap server process listening on the specified connection.
136+ """
137+ log_file_path = self .getBuildArtifact ("dap.txt" )
138+ (process , connection ) = dap_server .DebugAdapterServer .launch (
139+ executable = self .lldbDAPExec ,
140+ connection = connection ,
141+ log_file = log_file_path
142+ )
143+
144+ def cleanup ():
145+ process .terminate ()
146+
147+ self .addTearDownHook (cleanup )
148+
149+ return (process , connection )
150+
38151 def build_and_create_debug_adapter (
39152 self ,
40153 lldbDAPEnv : Optional [dict [str , str ]] = None ,
@@ -59,18 +172,9 @@ def set_source_breakpoints(
59172 Each object in data is 1:1 mapping with the entry in lines.
60173 It contains optional location/hitCondition/logMessage parameters.
61174 """
62- response = self .dap_server . request_setBreakpoints (
63- Source ( source_path ) , lines , data
175+ return self ._set_source_breakpoints_impl (
176+ self . dap_server , source_path , lines , data , wait_for_resolve
64177 )
65- if response is None or not response ["success" ]:
66- return []
67- breakpoints = response ["body" ]["breakpoints" ]
68- breakpoint_ids = []
69- for breakpoint in breakpoints :
70- breakpoint_ids .append ("%i" % (breakpoint ["id" ]))
71- if wait_for_resolve :
72- self .wait_for_breakpoints_to_resolve (breakpoint_ids )
73- return breakpoint_ids
74178
75179 def set_source_breakpoints_assembly (
76180 self , source_reference , lines , data = None , wait_for_resolve = True
@@ -113,13 +217,8 @@ def set_function_breakpoints(
113217 def wait_for_breakpoints_to_resolve (
114218 self , breakpoint_ids : list [str ], timeout : Optional [float ] = DEFAULT_TIMEOUT
115219 ):
116- unresolved_breakpoints = self .dap_server .wait_for_breakpoints_to_be_verified (
117- breakpoint_ids , timeout
118- )
119- self .assertEqual (
120- len (unresolved_breakpoints ),
121- 0 ,
122- f"Expected to resolve all breakpoints. Unresolved breakpoint ids: { unresolved_breakpoints } " ,
220+ return self ._wait_for_breakpoints_to_resolve_impl (
221+ self .dap_server , breakpoint_ids , timeout
123222 )
124223
125224 def waitUntil (self , condition_callback ):
@@ -145,33 +244,7 @@ def verify_breakpoint_hit(self, breakpoint_ids, timeout=DEFAULT_TIMEOUT):
145244 "breakpoint_ids" should be a list of breakpoint ID strings
146245 (["1", "2"]). The return value from self.set_source_breakpoints()
147246 or self.set_function_breakpoints() can be passed to this function"""
148- stopped_events = self .dap_server .wait_for_stopped (timeout )
149- for stopped_event in stopped_events :
150- if "body" in stopped_event :
151- body = stopped_event ["body" ]
152- if "reason" not in body :
153- continue
154- if (
155- body ["reason" ] != "breakpoint"
156- and body ["reason" ] != "instruction breakpoint"
157- ):
158- continue
159- if "description" not in body :
160- continue
161- # Descriptions for breakpoints will be in the form
162- # "breakpoint 1.1", so look for any description that matches
163- # ("breakpoint 1.") in the description field as verification
164- # that one of the breakpoint locations was hit. DAP doesn't
165- # allow breakpoints to have multiple locations, but LLDB does.
166- # So when looking at the description we just want to make sure
167- # the right breakpoint matches and not worry about the actual
168- # location.
169- description = body ["description" ]
170- for breakpoint_id in breakpoint_ids :
171- match_desc = f"breakpoint { breakpoint_id } ."
172- if match_desc in description :
173- return
174- self .assertTrue (False , f"breakpoint not hit, stopped_events={ stopped_events } " )
247+ self ._verify_breakpoint_hit_impl (self .dap_server , breakpoint_ids , timeout )
175248
176249 def verify_all_breakpoints_hit (self , breakpoint_ids , timeout = DEFAULT_TIMEOUT ):
177250 """Wait for the process we are debugging to stop, and verify we hit
@@ -384,8 +457,7 @@ def stepOut(self, threadId=None, waitForStop=True, timeout=DEFAULT_TIMEOUT):
384457 return None
385458
386459 def do_continue (self ): # `continue` is a keyword.
387- resp = self .dap_server .request_continue ()
388- self .assertTrue (resp ["success" ], f"continue request failed: { resp } " )
460+ self ._do_continue_impl (self .dap_server )
389461
390462 def continue_to_next_stop (self , timeout = DEFAULT_TIMEOUT ):
391463 self .do_continue ()
@@ -478,7 +550,6 @@ def launch(
478550 ** kwargs ,
479551 ):
480552 """Sending launch request to dap"""
481-
482553 # Make sure we disconnect and terminate the DAP debug adapter,
483554 # if we throw an exception during the test case
484555 def cleanup ():
0 commit comments