5 from lldbsuite
.test
.lldbtest
import *
8 class DAPTestCaseBase(TestBase
):
9 NO_DEBUG_INFO_TESTCASE
= True
11 def create_debug_adaptor(self
, lldbDAPEnv
=None):
12 """Create the Visual Studio Code debug adaptor"""
14 is_exe(self
.lldbDAPExec
), "lldb-dap must exist and be executable"
16 log_file_path
= self
.getBuildArtifact("dap.txt")
17 self
.dap_server
= dap_server
.DebugAdaptorServer(
18 executable
=self
.lldbDAPExec
,
19 init_commands
=self
.setUpCommands(),
20 log_file
=log_file_path
,
24 def build_and_create_debug_adaptor(self
, lldbDAPEnv
=None):
26 self
.create_debug_adaptor(lldbDAPEnv
)
28 def set_source_breakpoints(self
, source_path
, lines
, data
=None):
29 """Sets source breakpoints and returns an array of strings containing
30 the breakpoint IDs ("1", "2") for each breakpoint that was set.
31 Parameter data is array of data objects for breakpoints.
32 Each object in data is 1:1 mapping with the entry in lines.
33 It contains optional location/hitCondition/logMessage parameters.
35 response
= self
.dap_server
.request_setBreakpoints(source_path
, lines
, data
)
38 breakpoints
= response
["body"]["breakpoints"]
40 for breakpoint
in breakpoints
:
41 breakpoint_ids
.append("%i" % (breakpoint
["id"]))
44 def set_function_breakpoints(self
, functions
, condition
=None, hitCondition
=None):
45 """Sets breakpoints by function name given an array of function names
46 and returns an array of strings containing the breakpoint IDs
47 ("1", "2") for each breakpoint that was set.
49 response
= self
.dap_server
.request_setFunctionBreakpoints(
50 functions
, condition
=condition
, hitCondition
=hitCondition
54 breakpoints
= response
["body"]["breakpoints"]
56 for breakpoint
in breakpoints
:
57 breakpoint_ids
.append("%i" % (breakpoint
["id"]))
60 def waitUntil(self
, condition_callback
):
62 if condition_callback():
67 def verify_breakpoint_hit(self
, breakpoint_ids
):
68 """Wait for the process we are debugging to stop, and verify we hit
69 any breakpoint location in the "breakpoint_ids" array.
70 "breakpoint_ids" should be a list of breakpoint ID strings
71 (["1", "2"]). The return value from self.set_source_breakpoints()
72 or self.set_function_breakpoints() can be passed to this function"""
73 stopped_events
= self
.dap_server
.wait_for_stopped()
74 for stopped_event
in stopped_events
:
75 if "body" in stopped_event
:
76 body
= stopped_event
["body"]
77 if "reason" not in body
:
79 if body
["reason"] != "breakpoint":
81 if "description" not in body
:
83 # Descriptions for breakpoints will be in the form
84 # "breakpoint 1.1", so look for any description that matches
85 # ("breakpoint 1.") in the description field as verification
86 # that one of the breakpoint locations was hit. DAP doesn't
87 # allow breakpoints to have multiple locations, but LLDB does.
88 # So when looking at the description we just want to make sure
89 # the right breakpoint matches and not worry about the actual
91 description
= body
["description"]
92 for breakpoint_id
in breakpoint_ids
:
93 match_desc
= "breakpoint %s." % (breakpoint_id
)
94 if match_desc
in description
:
96 self
.assertTrue(False, "breakpoint not hit")
98 def verify_stop_exception_info(self
, expected_description
):
99 """Wait for the process we are debugging to stop, and verify the stop
100 reason is 'exception' and that the description matches
101 'expected_description'
103 stopped_events
= self
.dap_server
.wait_for_stopped()
104 for stopped_event
in stopped_events
:
105 if "body" in stopped_event
:
106 body
= stopped_event
["body"]
107 if "reason" not in body
:
109 if body
["reason"] != "exception":
111 if "description" not in body
:
113 description
= body
["description"]
114 if expected_description
== description
:
118 def verify_commands(self
, flavor
, output
, commands
):
119 self
.assertTrue(output
and len(output
) > 0, "expect console output")
120 lines
= output
.splitlines()
125 if line
.startswith(prefix
) and cmd
in line
:
129 found
, "verify '%s' found in console output for '%s'" % (cmd
, flavor
)
132 def get_dict_value(self
, d
, key_path
):
133 """Verify each key in the key_path array is in contained in each
134 dictionary within "d". Assert if any key isn't in the
135 corresponding dictionary. This is handy for grabbing values from VS
136 Code response dictionary like getting
137 response['body']['stackFrames']
146 'key "%s" from key_path "%s" not in "%s"' % (key
, key_path
, d
),
150 def get_stackFrames_and_totalFramesCount(
151 self
, threadId
=None, startFrame
=None, levels
=None, dump
=False
153 response
= self
.dap_server
.request_stackTrace(
154 threadId
=threadId
, startFrame
=startFrame
, levels
=levels
, dump
=dump
157 stackFrames
= self
.get_dict_value(response
, ["body", "stackFrames"])
158 totalFrames
= self
.get_dict_value(response
, ["body", "totalFrames"])
161 "verify totalFrames count is provided by extension that supports "
162 "async frames loading",
164 return (stackFrames
, totalFrames
)
167 def get_stackFrames(self
, threadId
=None, startFrame
=None, levels
=None, dump
=False):
168 (stackFrames
, totalFrames
) = self
.get_stackFrames_and_totalFramesCount(
169 threadId
=threadId
, startFrame
=startFrame
, levels
=levels
, dump
=dump
173 def get_source_and_line(self
, threadId
=None, frameIndex
=0):
174 stackFrames
= self
.get_stackFrames(
175 threadId
=threadId
, startFrame
=frameIndex
, levels
=1
177 if stackFrames
is not None:
178 stackFrame
= stackFrames
[0]
180 if "source" in stackFrame
:
181 source
= stackFrame
["source"]
183 if "line" in stackFrame
:
184 return (source
["path"], stackFrame
["line"])
187 def get_stdout(self
, timeout
=0.0):
188 return self
.dap_server
.get_output("stdout", timeout
=timeout
)
190 def get_console(self
, timeout
=0.0):
191 return self
.dap_server
.get_output("console", timeout
=timeout
)
193 def collect_console(self
, duration
):
194 return self
.dap_server
.collect_output("console", duration
=duration
)
196 def get_local_as_int(self
, name
, threadId
=None):
197 value
= self
.dap_server
.get_local_variable_value(name
, threadId
=threadId
)
198 if value
.startswith("0x"):
199 return int(value
, 16)
200 elif value
.startswith("0"):
205 def set_local(self
, name
, value
, id=None):
206 """Set a top level local variable only."""
207 return self
.dap_server
.request_setVariable(1, name
, str(value
), id=id)
209 def set_global(self
, name
, value
, id=None):
210 """Set a top level global variable only."""
211 return self
.dap_server
.request_setVariable(2, name
, str(value
), id=id)
213 def stepIn(self
, threadId
=None, waitForStop
=True):
214 self
.dap_server
.request_stepIn(threadId
=threadId
)
216 return self
.dap_server
.wait_for_stopped()
219 def stepOver(self
, threadId
=None, waitForStop
=True):
220 self
.dap_server
.request_next(threadId
=threadId
)
222 return self
.dap_server
.wait_for_stopped()
225 def stepOut(self
, threadId
=None, waitForStop
=True):
226 self
.dap_server
.request_stepOut(threadId
=threadId
)
228 return self
.dap_server
.wait_for_stopped()
231 def continue_to_next_stop(self
):
232 self
.dap_server
.request_continue()
233 return self
.dap_server
.wait_for_stopped()
235 def continue_to_breakpoints(self
, breakpoint_ids
):
236 self
.dap_server
.request_continue()
237 self
.verify_breakpoint_hit(breakpoint_ids
)
239 def continue_to_exception_breakpoint(self
, filter_label
):
240 self
.dap_server
.request_continue()
242 self
.verify_stop_exception_info(filter_label
),
243 'verify we got "%s"' % (filter_label
),
246 def continue_to_exit(self
, exitCode
=0):
247 self
.dap_server
.request_continue()
248 stopped_events
= self
.dap_server
.wait_for_stopped()
250 len(stopped_events
), 1, "stopped_events = {}".format(stopped_events
)
253 stopped_events
[0]["event"], "exited", "make sure program ran to completion"
256 stopped_events
[0]["body"]["exitCode"],
258 "exitCode == %i" % (exitCode
),
261 def disassemble(self
, threadId
=None, frameIndex
=None):
262 stackFrames
= self
.get_stackFrames(
263 threadId
=threadId
, startFrame
=frameIndex
, levels
=1
265 self
.assertIsNotNone(stackFrames
)
266 memoryReference
= stackFrames
[0]["instructionPointerReference"]
267 self
.assertIsNotNone(memoryReference
)
269 if memoryReference
not in self
.dap_server
.disassembled_instructions
:
270 self
.dap_server
.request_disassemble(memoryReference
=memoryReference
)
272 return self
.dap_server
.disassembled_instructions
[memoryReference
]
286 disconnectAutomatically
=True,
287 terminateCommands
=None,
288 postRunCommands
=None,
290 sourceInitFile
=False,
292 """Build the default Makefile target, create the DAP debug adaptor,
293 and attach to the process.
296 # Make sure we disconnect and terminate the DAP debug adaptor even
297 # if we throw an exception during the test case.
299 if disconnectAutomatically
:
300 self
.dap_server
.request_disconnect(terminateDebuggee
=True)
301 self
.dap_server
.terminate()
303 # Execute the cleanup function during test case tear down.
304 self
.addTearDownHook(cleanup
)
305 # Initialize and launch the program
306 self
.dap_server
.request_initialize(sourceInitFile
)
307 response
= self
.dap_server
.request_attach(
312 initCommands
=initCommands
,
313 preRunCommands
=preRunCommands
,
314 stopCommands
=stopCommands
,
315 exitCommands
=exitCommands
,
316 attachCommands
=attachCommands
,
317 terminateCommands
=terminateCommands
,
319 postRunCommands
=postRunCommands
,
322 if not (response
and response
["success"]):
324 response
["success"], "attach failed (%s)" % (response
["message"])
336 shellExpandArguments
=False,
342 terminateCommands
=None,
345 sourceInitFile
=False,
348 disconnectAutomatically
=True,
351 postRunCommands
=None,
352 enableAutoVariableSummaries
=False,
353 enableSyntheticChildDebugging
=False,
354 commandEscapePrefix
="`",
356 """Sending launch request to dap"""
358 # Make sure we disconnect and terminate the DAP debug adapter,
359 # if we throw an exception during the test case
361 if disconnectAutomatically
:
362 self
.dap_server
.request_disconnect(terminateDebuggee
=True)
363 self
.dap_server
.terminate()
365 # Execute the cleanup function during test case tear down.
366 self
.addTearDownHook(cleanup
)
368 # Initialize and launch the program
369 self
.dap_server
.request_initialize(sourceInitFile
)
370 response
= self
.dap_server
.request_launch(
375 stopOnEntry
=stopOnEntry
,
376 disableASLR
=disableASLR
,
377 disableSTDIO
=disableSTDIO
,
378 shellExpandArguments
=shellExpandArguments
,
380 initCommands
=initCommands
,
381 preRunCommands
=preRunCommands
,
382 stopCommands
=stopCommands
,
383 exitCommands
=exitCommands
,
384 terminateCommands
=terminateCommands
,
385 sourcePath
=sourcePath
,
386 debuggerRoot
=debuggerRoot
,
387 launchCommands
=launchCommands
,
389 runInTerminal
=runInTerminal
,
390 postRunCommands
=postRunCommands
,
391 enableAutoVariableSummaries
=enableAutoVariableSummaries
,
392 enableSyntheticChildDebugging
=enableSyntheticChildDebugging
,
393 commandEscapePrefix
=commandEscapePrefix
,
399 if not (response
and response
["success"]):
401 response
["success"], "launch failed (%s)" % (response
["message"])
405 def build_and_launch(
414 shellExpandArguments
=False,
420 terminateCommands
=None,
423 sourceInitFile
=False,
425 disconnectAutomatically
=True,
426 postRunCommands
=None,
428 enableAutoVariableSummaries
=False,
429 enableSyntheticChildDebugging
=False,
430 commandEscapePrefix
="`",
432 """Build the default Makefile target, create the DAP debug adaptor,
433 and launch the process.
435 self
.build_and_create_debug_adaptor(lldbDAPEnv
)
436 self
.assertTrue(os
.path
.exists(program
), "executable must exist")
446 shellExpandArguments
,
456 runInTerminal
=runInTerminal
,
457 disconnectAutomatically
=disconnectAutomatically
,
458 postRunCommands
=postRunCommands
,
459 enableAutoVariableSummaries
=enableAutoVariableSummaries
,
460 enableSyntheticChildDebugging
=enableSyntheticChildDebugging
,
461 commandEscapePrefix
=commandEscapePrefix
,