"""
Test lldb-dap setBreakpoints request
"""
import dap_server
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
import lldbdap_testcase
import time
import os
import re
class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase):
def test_default(self):
"""
Tests the default launch of a simple program. No arguments,
environment, or anything else is specified.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)
self.continue_to_exit()
# Now get the STDOUT and verify our program argument is correct
output = self.get_stdout()
self.assertTrue(output and len(output) > 0, "expect program output")
lines = output.splitlines()
self.assertIn(program, lines[0], "make sure program path is in first argument")
def test_termination(self):
"""
Tests the correct termination of lldb-dap upon a 'disconnect'
request.
"""
self.create_debug_adaptor()
# The underlying lldb-dap process must be alive
self.assertEqual(self.dap_server.process.poll(), None)
# The lldb-dap process should finish even though
# we didn't close the communication socket explicitly
self.dap_server.request_disconnect()
# Wait until the underlying lldb-dap process dies.
self.dap_server.process.wait(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
# Check the return code
self.assertEqual(self.dap_server.process.poll(), 0)
def test_stopOnEntry(self):
"""
Tests the default launch of a simple program that stops at the
entry point instead of continuing.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program, stopOnEntry=True)
self.set_function_breakpoints(["main"])
stopped_events = self.continue_to_next_stop()
for stopped_event in stopped_events:
if "body" in stopped_event:
body = stopped_event["body"]
if "reason" in body:
reason = body["reason"]
self.assertNotEqual(
reason, "breakpoint", 'verify stop isn\'t "main" breakpoint'
)
def test_cwd(self):
"""
Tests the default launch of a simple program with a current working
directory.
"""
program = self.getBuildArtifact("a.out")
program_parent_dir = os.path.realpath(os.path.dirname(os.path.dirname(program)))
self.build_and_launch(program, cwd=program_parent_dir)
self.continue_to_exit()
# Now get the STDOUT and verify our program argument is correct
output = self.get_stdout()
self.assertTrue(output and len(output) > 0, "expect program output")
lines = output.splitlines()
found = False
for line in lines:
if line.startswith('cwd = "'):
quote_path = '"%s"' % (program_parent_dir)
found = True
self.assertIn(
quote_path,
line,
"working directory '%s' not in '%s'" % (program_parent_dir, line),
)
self.assertTrue(found, "verified program working directory")
def test_debuggerRoot(self):
"""
Tests the "debuggerRoot" will change the working directory of
the lldb-dap debug adaptor.
"""
program = self.getBuildArtifact("a.out")
program_parent_dir = os.path.realpath(os.path.dirname(os.path.dirname(program)))
var = "%cd%" if lldbplatformutil.getHostPlatform() == "windows" else "$PWD"
commands = [f"platform shell echo cwd = {var}"]
self.build_and_launch(
program, debuggerRoot=program_parent_dir, initCommands=commands
)
output = self.get_console()
self.assertTrue(output and len(output) > 0, "expect console output")
lines = output.splitlines()
prefix = "cwd = "
found = False
for line in lines:
if line.startswith(prefix):
found = True
self.assertEqual(
program_parent_dir,
line.strip()[len(prefix) :],
"lldb-dap working dir '%s' == '%s'"
% (program_parent_dir, line[len(prefix) :]),
)
self.assertTrue(found, "verified lldb-dap working directory")
self.continue_to_exit()
def test_sourcePath(self):
"""
Tests the "sourcePath" will set the target.source-map.
"""
program = self.getBuildArtifact("a.out")
program_dir = os.path.dirname(program)
self.build_and_launch(program, sourcePath=program_dir)
output = self.get_console()
self.assertTrue(output and len(output) > 0, "expect console output")
lines = output.splitlines()
prefix = '(lldb) settings set target.source-map "." '
found = False
for line in lines:
if line.startswith(prefix):
found = True
quoted_path = '"%s"' % (program_dir)
self.assertEqual(
quoted_path,
line[len(prefix) :],
"lldb-dap working dir %s == %s" % (quoted_path, line[6:]),
)
self.assertTrue(found, 'found "sourcePath" in console output')
self.continue_to_exit()
def test_disableSTDIO(self):
"""
Tests the default launch of a simple program with STDIO disabled.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program, disableSTDIO=True)
self.continue_to_exit()
# Now get the STDOUT and verify our program argument is correct
output = self.get_stdout()
self.assertEqual(output, None, "expect no program output")
@skipIfWindows
@skipIfLinux # shell argument expansion doesn't seem to work on Linux
@expectedFailureAll(oslist=["freebsd", "netbsd"], bugnumber="llvm.org/pr48349")
def test_shellExpandArguments_enabled(self):
"""
Tests the default launch of a simple program with shell expansion
enabled.
"""
program = self.getBuildArtifact("a.out")
program_dir = os.path.dirname(program)
glob = os.path.join(program_dir, "*.out")
self.build_and_launch(program, args=[glob], shellExpandArguments=True)
self.continue_to_exit()
# Now get the STDOUT and verify our program argument is correct
output = self.get_stdout()
self.assertTrue(output and len(output) > 0, "expect no program output")
lines = output.splitlines()
for line in lines:
quote_path = '"%s"' % (program)
if line.startswith("arg[1] ="):
self.assertIn(
quote_path, line, 'verify "%s" expanded to "%s"' % (glob, program)
)
def test_shellExpandArguments_disabled(self):
"""
Tests the default launch of a simple program with shell expansion
disabled.
"""
program = self.getBuildArtifact("a.out")
program_dir = os.path.dirname(program)
glob = os.path.join(program_dir, "*.out")
self.build_and_launch(program, args=[glob], shellExpandArguments=False)
self.continue_to_exit()
# Now get the STDOUT and verify our program argument is correct
output = self.get_stdout()
self.assertTrue(output and len(output) > 0, "expect no program output")
lines = output.splitlines()
for line in lines:
quote_path = '"%s"' % (glob)
if line.startswith("arg[1] ="):
self.assertIn(
quote_path, line, 'verify "%s" stayed to "%s"' % (glob, glob)
)
def test_args(self):
"""
Tests launch of a simple program with arguments
"""
program = self.getBuildArtifact("a.out")
args = ["one", "with space", "'with single quotes'", '"with double quotes"']
self.build_and_launch(program, args=args)
self.continue_to_exit()
# Now get the STDOUT and verify our arguments got passed correctly
output = self.get_stdout()
self.assertTrue(output and len(output) > 0, "expect program output")
lines = output.splitlines()
# Skip the first argument that contains the program name
lines.pop(0)
# Make sure arguments we specified are correct
for i, arg in enumerate(args):
quoted_arg = '"%s"' % (arg)
self.assertIn(
quoted_arg,
lines[i],
'arg[%i] "%s" not in "%s"' % (i + 1, quoted_arg, lines[i]),
)
def test_environment(self):
"""
Tests launch of a simple program with environment variables
"""
program = self.getBuildArtifact("a.out")
env = ["NO_VALUE", "WITH_VALUE=BAR", "EMPTY_VALUE=", "SPACE=Hello World"]
self.build_and_launch(program, env=env)
self.continue_to_exit()
# Now get the STDOUT and verify our arguments got passed correctly
output = self.get_stdout()
self.assertTrue(output and len(output) > 0, "expect program output")
lines = output.splitlines()
# Skip the all arguments so we have only environment vars left
while len(lines) and lines[0].startswith("arg["):
lines.pop(0)
# Make sure each environment variable in "env" is actually set in the
# program environment that was printed to STDOUT
for var in env:
found = False
for program_var in lines:
if var in program_var:
found = True
break
self.assertTrue(
found, '"%s" must exist in program environment (%s)' % (var, lines)
)
@skipIf(
archs=["arm", "aarch64"]
) # failed run https://lab.llvm.org/buildbot/#/builders/96/builds/6933
def test_commands(self):
"""
Tests the "initCommands", "preRunCommands", "stopCommands",
"terminateCommands" and "exitCommands" that can be passed during
launch.
"initCommands" are a list of LLDB commands that get executed
before the targt is created.
"preRunCommands" are a list of LLDB commands that get executed
after the target has been created and before the launch.
"stopCommands" are a list of LLDB commands that get executed each
time the program stops.
"exitCommands" are a list of LLDB commands that get executed when
the process exits
"terminateCommands" are a list of LLDB commands that get executed when
the debugger session terminates.
"""
program = self.getBuildArtifact("a.out")
initCommands = ["target list", "platform list"]
preRunCommands = ["image list a.out", "image dump sections a.out"]
postRunCommands = ["help trace", "help process trace"]
stopCommands = ["frame variable", "bt"]
exitCommands = ["expr 2+3", "expr 3+4"]
terminateCommands = ["expr 4+2"]
self.build_and_launch(
program,
initCommands=initCommands,
preRunCommands=preRunCommands,
postRunCommands=postRunCommands,
stopCommands=stopCommands,
exitCommands=exitCommands,
terminateCommands=terminateCommands,
)
# Get output from the console. This should contain both the
# "initCommands" and the "preRunCommands".
output = self.get_console()
# Verify all "initCommands" were found in console output
self.verify_commands("initCommands", output, initCommands)
# Verify all "preRunCommands" were found in console output
self.verify_commands("preRunCommands", output, preRunCommands)
# Verify all "postRunCommands" were found in console output
self.verify_commands("postRunCommands", output, postRunCommands)
source = "main.c"
first_line = line_number(source, "// breakpoint 1")
second_line = line_number(source, "// breakpoint 2")
lines = [first_line, second_line]
# Set 2 breakpoints so we can verify that "stopCommands" get run as the
# breakpoints get hit
breakpoint_ids = self.set_source_breakpoints(source, lines)
self.assertEqual(
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
)
# Continue after launch and hit the first breakpoint.
# Get output from the console. This should contain both the
# "stopCommands" that were run after the first breakpoint was hit
self.continue_to_breakpoints(breakpoint_ids)
output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
self.verify_commands("stopCommands", output, stopCommands)
# Continue again and hit the second breakpoint.
# Get output from the console. This should contain both the
# "stopCommands" that were run after the second breakpoint was hit
self.continue_to_breakpoints(breakpoint_ids)
output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
self.verify_commands("stopCommands", output, stopCommands)
# Continue until the program exits
self.continue_to_exit()
# Get output from the console. This should contain both the
# "exitCommands" that were run after the second breakpoint was hit
# and the "terminateCommands" due to the debugging session ending
output = self.collect_console(
timeout_secs=1.0,
pattern=terminateCommands[0],
)
self.verify_commands("exitCommands", output, exitCommands)
self.verify_commands("terminateCommands", output, terminateCommands)
def test_extra_launch_commands(self):
"""
Tests the "launchCommands" with extra launching settings
"""
self.build_and_create_debug_adaptor()
program = self.getBuildArtifact("a.out")
source = "main.c"
first_line = line_number(source, "// breakpoint 1")
second_line = line_number(source, "// breakpoint 2")
# Set target binary and 2 breakpoints
# then we can varify the "launchCommands" get run
# also we can verify that "stopCommands" get run as the
# breakpoints get hit
launchCommands = [
'target create "%s"' % (program),
"breakpoint s -f main.c -l %d" % first_line,
"breakpoint s -f main.c -l %d" % second_line,
"process launch --stop-at-entry",
]
initCommands = ["target list", "platform list"]
preRunCommands = ["image list a.out", "image dump sections a.out"]
stopCommands = ["frame variable", "bt"]
exitCommands = ["expr 2+3", "expr 3+4"]
self.launch(
program,
initCommands=initCommands,
preRunCommands=preRunCommands,
stopCommands=stopCommands,
exitCommands=exitCommands,
launchCommands=launchCommands,
)
# Get output from the console. This should contain both the
# "initCommands" and the "preRunCommands".
output = self.get_console()
# Verify all "initCommands" were found in console output
self.verify_commands("initCommands", output, initCommands)
# Verify all "preRunCommands" were found in console output
self.verify_commands("preRunCommands", output, preRunCommands)
# Verify all "launchCommands" were found in console output
# After execution, program should launch
self.verify_commands("launchCommands", output, launchCommands)
# Verify the "stopCommands" here
self.continue_to_next_stop()
output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
self.verify_commands("stopCommands", output, stopCommands)
# Continue and hit the second breakpoint.
# Get output from the console. This should contain both the
# "stopCommands" that were run after the first breakpoint was hit
self.continue_to_next_stop()
output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
self.verify_commands("stopCommands", output, stopCommands)
# Continue until the program exits
self.continue_to_exit()
# Get output from the console. This should contain both the
# "exitCommands" that were run after the second breakpoint was hit
output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
self.verify_commands("exitCommands", output, exitCommands)
def test_failing_launch_commands(self):
"""
Tests "launchCommands" failures prevents a launch.
"""
self.build_and_create_debug_adaptor()
program = self.getBuildArtifact("a.out")
# Run an invalid launch command, in this case a bad path.
bad_path = os.path.join("bad", "path")
launchCommands = ['!target create "%s%s"' % (bad_path, program)]
initCommands = ["target list", "platform list"]
preRunCommands = ["image list a.out", "image dump sections a.out"]
response = self.launch(
program,
initCommands=initCommands,
preRunCommands=preRunCommands,
launchCommands=launchCommands,
expectFailure=True,
)
self.assertFalse(response["success"])
self.assertRegex(
response["message"],
r"Failed to run launch commands\. See the Debug Console for more details",
)
# Get output from the console. This should contain both the
# "initCommands" and the "preRunCommands".
output = self.get_console()
# Verify all "initCommands" were found in console output
self.verify_commands("initCommands", output, initCommands)
# Verify all "preRunCommands" were found in console output
self.verify_commands("preRunCommands", output, preRunCommands)
# Verify all "launchCommands" were founc in console output
# The launch should fail due to the invalid command.
self.verify_commands("launchCommands", output, launchCommands)
self.assertRegex(output, re.escape(bad_path) + r".*does not exist")
@skipIfNetBSD # Hangs on NetBSD as well
@skipIf(archs=["arm", "aarch64"], oslist=["linux"])
def test_terminate_commands(self):
"""
Tests that the "terminateCommands", that can be passed during
launch, are run when the debugger is disconnected.
"""
self.build_and_create_debug_adaptor()
program = self.getBuildArtifact("a.out")
terminateCommands = ["expr 4+2"]
self.launch(
program=program,
terminateCommands=terminateCommands,
disconnectAutomatically=False,
)
self.get_console()
# Once it's disconnected the console should contain the
# "terminateCommands"
self.dap_server.request_disconnect(terminateDebuggee=True)
output = self.collect_console(
timeout_secs=1.0,
pattern=terminateCommands[0],
)
self.verify_commands("terminateCommands", output, terminateCommands)
def test_version(self):
"""
Tests that "initialize" response contains the "version" string the same
as the one returned by "version" command.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)
source = "main.c"
breakpoint_line = line_number(source, "// breakpoint 1")
lines = [breakpoint_line]
# Set breakpoint in the thread function so we can step the threads
breakpoint_ids = self.set_source_breakpoints(source, lines)
self.continue_to_breakpoints(breakpoint_ids)
version_eval_response = self.dap_server.request_evaluate(
"`version", context="repl"
)
version_eval_output = version_eval_response["body"]["result"]
# The first line is the prompt line like "(lldb) version", so we skip it.
version_eval_output_without_prompt_line = version_eval_output.splitlines()[1:]
lldb_json = self.dap_server.get_initialize_value("__lldb")
version_string = lldb_json["version"]
self.assertEqual(
version_eval_output_without_prompt_line,
version_string.splitlines(),
"version string does not match",
)