662 lines · python
1"""2Test lldb-dap setBreakpoints request3"""4 5from lldbsuite.test.decorators import *6from lldbsuite.test.lldbtest import *7import lldbdap_testcase8import os9import pathlib10import re11import tempfile12 13# Many tests are skipped on Windows because get_stdout() returns None there.14# Despite the test program printing correctly. See15# https://github.com/llvm/llvm-project/issues/137599.16 17 18class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase):19 @skipIfWindows20 def test_default(self):21 """22 Tests the default launch of a simple program. No arguments,23 environment, or anything else is specified.24 """25 program = self.getBuildArtifact("a.out")26 self.build_and_launch(program)27 self.continue_to_exit()28 # Now get the STDOUT and verify our program argument is correct29 output = self.get_stdout()30 self.assertTrue(output and len(output) > 0, "expect program output")31 lines = output.splitlines()32 self.assertIn(program, lines[0], "make sure program path is in first argument")33 34 def test_failing_launch_program(self):35 """36 Tests launching with an invalid program.37 """38 program = self.getBuildArtifact("a.out")39 self.create_debug_adapter()40 response = self.launch(program, expectFailure=True)41 self.assertFalse(response["success"])42 self.assertEqual(43 "'{0}' does not exist".format(program), response["body"]["error"]["format"]44 )45 46 def test_failing_launch_commands_and_console(self):47 """48 Tests launching with launch commands in an integrated terminal.49 """50 program = self.getBuildArtifact("a.out")51 self.create_debug_adapter()52 response = self.launch(53 program,54 launchCommands=["a b c"],55 console="integratedTerminal",56 expectFailure=True,57 )58 self.assertFalse(response["success"])59 self.assertTrue(self.get_dict_value(response, ["body", "error", "showUser"]))60 self.assertEqual(61 "'launchCommands' and non-internal 'console' are mutually exclusive",62 self.get_dict_value(response, ["body", "error", "format"]),63 )64 65 def test_failing_console(self):66 """67 Tests launching in console with an invalid terminal type.68 """69 program = self.getBuildArtifact("a.out")70 self.create_debug_adapter()71 response = self.launch(program, console="invalid", expectFailure=True)72 self.assertFalse(response["success"])73 self.assertTrue(self.get_dict_value(response, ["body", "error", "showUser"]))74 self.assertRegex(75 response["body"]["error"]["format"],76 r"unexpected value, expected 'internalConsole\', 'integratedTerminal\' or 'externalTerminal\' at arguments.console",77 )78 79 @skipIfWindows80 def test_termination(self):81 """82 Tests the correct termination of lldb-dap upon a 'disconnect'83 request.84 """85 self.create_debug_adapter()86 # The underlying lldb-dap process must be alive87 self.assertEqual(self.dap_server.process.poll(), None)88 89 # The lldb-dap process should finish even though90 # we didn't close the communication socket explicitly91 self.dap_server.request_disconnect()92 93 # Wait until the underlying lldb-dap process dies.94 self.dap_server.process.wait(timeout=self.DEFAULT_TIMEOUT)95 96 # Check the return code97 self.assertEqual(self.dap_server.process.poll(), 0)98 99 def test_stopOnEntry(self):100 """101 Tests the default launch of a simple program that stops at the102 entry point instead of continuing.103 """104 program = self.getBuildArtifact("a.out")105 self.build_and_launch(program, stopOnEntry=True)106 self.dap_server.request_configurationDone()107 self.dap_server.wait_for_stopped()108 self.assertTrue(109 len(self.dap_server.thread_stop_reasons) > 0,110 "expected stopped event during launch",111 )112 for _, body in self.dap_server.thread_stop_reasons.items():113 if "reason" in body:114 reason = body["reason"]115 self.assertNotEqual(116 reason, "breakpoint", 'verify stop isn\'t "main" breakpoint'117 )118 119 @skipIfWindows120 def test_cwd(self):121 """122 Tests the default launch of a simple program with a current working123 directory.124 """125 program = self.getBuildArtifact("a.out")126 program_parent_dir = os.path.realpath(os.path.dirname(os.path.dirname(program)))127 self.build_and_launch(program, cwd=program_parent_dir)128 self.continue_to_exit()129 # Now get the STDOUT and verify our program argument is correct130 output = self.get_stdout()131 self.assertTrue(output and len(output) > 0, "expect program output")132 lines = output.splitlines()133 found = False134 for line in lines:135 if line.startswith('cwd = "'):136 quote_path = '"%s"' % (program_parent_dir)137 found = True138 self.assertIn(139 quote_path,140 line,141 "working directory '%s' not in '%s'" % (program_parent_dir, line),142 )143 self.assertTrue(found, "verified program working directory")144 145 def test_debuggerRoot(self):146 """147 Tests the "debuggerRoot" will change the working directory of148 the lldb-dap debug adapter.149 """150 program = self.getBuildArtifact("a.out")151 program_parent_dir = os.path.realpath(os.path.dirname(os.path.dirname(program)))152 153 var = "%cd%" if lldbplatformutil.getHostPlatform() == "windows" else "$PWD"154 commands = [f"platform shell echo cwd = {var}"]155 156 self.build_and_launch(157 program, debuggerRoot=program_parent_dir, initCommands=commands158 )159 output = self.get_console()160 self.assertTrue(output and len(output) > 0, "expect console output")161 lines = output.splitlines()162 prefix = "cwd = "163 found = False164 for line in lines:165 if line.startswith(prefix):166 found = True167 self.assertEqual(168 program_parent_dir,169 line.strip()[len(prefix) :],170 "lldb-dap working dir '%s' == '%s'"171 % (program_parent_dir, line[len(prefix) :]),172 )173 self.assertTrue(found, "verified lldb-dap working directory")174 self.continue_to_exit()175 176 def test_sourcePath(self):177 """178 Tests the "sourcePath" will set the target.source-map.179 """180 program = self.getBuildArtifact("a.out")181 program_dir = os.path.dirname(program)182 self.build_and_launch(program, sourcePath=program_dir)183 output = self.get_console()184 self.assertTrue(output and len(output) > 0, "expect console output")185 lines = output.splitlines()186 prefix = '(lldb) settings set target.source-map "." '187 found = False188 for line in lines:189 if line.startswith(prefix):190 found = True191 quoted_path = '"%s"' % (program_dir)192 self.assertEqual(193 quoted_path,194 line[len(prefix) :],195 "lldb-dap working dir %s == %s" % (quoted_path, line[6:]),196 )197 self.assertTrue(found, 'found "sourcePath" in console output')198 self.continue_to_exit()199 200 @skipIfWindows201 def test_disableSTDIO(self):202 """203 Tests the default launch of a simple program with STDIO disabled.204 """205 program = self.getBuildArtifact("a.out")206 self.build_and_launch(program, disableSTDIO=True)207 self.continue_to_exit()208 # Now get the STDOUT and verify our program argument is correct209 output = self.get_stdout()210 self.assertEqual(output, "", "expect no program output")211 212 @skipIfWindows213 @skipIfLinux # shell argument expansion doesn't seem to work on Linux214 @expectedFailureAll(oslist=["freebsd", "netbsd"], bugnumber="llvm.org/pr48349")215 def test_shellExpandArguments_enabled(self):216 """217 Tests the default launch of a simple program with shell expansion218 enabled.219 """220 program = self.getBuildArtifact("a.out")221 program_dir = os.path.dirname(program)222 glob = os.path.join(program_dir, "*.out")223 self.build_and_launch(program, args=[glob], shellExpandArguments=True)224 self.continue_to_exit()225 # Now get the STDOUT and verify our program argument is correct226 output = self.get_stdout()227 self.assertTrue(output and len(output) > 0, "expect no program output")228 lines = output.splitlines()229 for line in lines:230 quote_path = '"%s"' % (program)231 if line.startswith("arg[1] ="):232 self.assertIn(233 quote_path, line, 'verify "%s" expanded to "%s"' % (glob, program)234 )235 236 @skipIfWindows237 def test_shellExpandArguments_disabled(self):238 """239 Tests the default launch of a simple program with shell expansion240 disabled.241 """242 program = self.getBuildArtifact("a.out")243 program_dir = os.path.dirname(program)244 glob = os.path.join(program_dir, "*.out")245 self.build_and_launch(program, args=[glob], shellExpandArguments=False)246 self.continue_to_exit()247 # Now get the STDOUT and verify our program argument is correct248 output = self.get_stdout()249 self.assertTrue(output and len(output) > 0, "expect no program output")250 lines = output.splitlines()251 for line in lines:252 quote_path = '"%s"' % (glob)253 if line.startswith("arg[1] ="):254 self.assertIn(255 quote_path, line, 'verify "%s" stayed to "%s"' % (glob, glob)256 )257 258 @skipIfWindows259 def test_args(self):260 """261 Tests launch of a simple program with arguments262 """263 program = self.getBuildArtifact("a.out")264 args = ["one", "with space", "'with single quotes'", '"with double quotes"']265 self.build_and_launch(program, args=args)266 self.continue_to_exit()267 268 # Now get the STDOUT and verify our arguments got passed correctly269 output = self.get_stdout()270 self.assertTrue(output and len(output) > 0, "expect program output")271 lines = output.splitlines()272 # Skip the first argument that contains the program name273 lines.pop(0)274 # Make sure arguments we specified are correct275 for i, arg in enumerate(args):276 quoted_arg = '"%s"' % (arg)277 self.assertIn(278 quoted_arg,279 lines[i],280 'arg[%i] "%s" not in "%s"' % (i + 1, quoted_arg, lines[i]),281 )282 283 @skipIfWindows284 def test_environment_with_object(self):285 """286 Tests launch of a simple program with environment variables287 """288 program = self.getBuildArtifact("a.out")289 env = {290 "NO_VALUE": "",291 "WITH_VALUE": "BAR",292 "EMPTY_VALUE": "",293 "SPACE": "Hello World",294 }295 296 self.build_and_launch(program, env=env)297 self.continue_to_exit()298 299 # Now get the STDOUT and verify our arguments got passed correctly300 output = self.get_stdout()301 self.assertTrue(output and len(output) > 0, "expect program output")302 lines = output.splitlines()303 # Skip the all arguments so we have only environment vars left304 while len(lines) and lines[0].startswith("arg["):305 lines.pop(0)306 # Make sure each environment variable in "env" is actually set in the307 # program environment that was printed to STDOUT308 for var in env:309 found = False310 for program_var in lines:311 if var in program_var:312 found = True313 break314 self.assertTrue(315 found, '"%s" must exist in program environment (%s)' % (var, lines)316 )317 318 @skipIfWindows319 def test_environment_with_array(self):320 """321 Tests launch of a simple program with environment variables322 """323 program = self.getBuildArtifact("a.out")324 env = ["NO_VALUE", "WITH_VALUE=BAR", "EMPTY_VALUE=", "SPACE=Hello World"]325 326 self.build_and_launch(program, env=env)327 self.continue_to_exit()328 329 # Now get the STDOUT and verify our arguments got passed correctly330 output = self.get_stdout()331 self.assertTrue(output and len(output) > 0, "expect program output")332 lines = output.splitlines()333 # Skip the all arguments so we have only environment vars left334 while len(lines) and lines[0].startswith("arg["):335 lines.pop(0)336 # Make sure each environment variable in "env" is actually set in the337 # program environment that was printed to STDOUT338 for var in env:339 found = False340 for program_var in lines:341 if var in program_var:342 found = True343 break344 self.assertTrue(345 found, '"%s" must exist in program environment (%s)' % (var, lines)346 )347 348 @skipIf(349 archs=["arm$", "aarch64"]350 ) # failed run https://lab.llvm.org/buildbot/#/builders/96/builds/6933351 def test_commands(self):352 """353 Tests the "initCommands", "preRunCommands", "stopCommands",354 "terminateCommands" and "exitCommands" that can be passed during355 launch.356 357 "initCommands" are a list of LLDB commands that get executed358 before the targt is created.359 "preRunCommands" are a list of LLDB commands that get executed360 after the target has been created and before the launch.361 "stopCommands" are a list of LLDB commands that get executed each362 time the program stops.363 "exitCommands" are a list of LLDB commands that get executed when364 the process exits365 "terminateCommands" are a list of LLDB commands that get executed when366 the debugger session terminates.367 """368 program = self.getBuildArtifact("a.out")369 initCommands = ["target list", "platform list"]370 preRunCommands = ["image list a.out", "image dump sections a.out"]371 postRunCommands = ["help trace", "help process trace"]372 stopCommands = ["frame variable", "bt"]373 exitCommands = ["expr 2+3", "expr 3+4"]374 terminateCommands = ["expr 4+2"]375 self.build_and_launch(376 program,377 initCommands=initCommands,378 preRunCommands=preRunCommands,379 postRunCommands=postRunCommands,380 stopCommands=stopCommands,381 exitCommands=exitCommands,382 terminateCommands=terminateCommands,383 )384 385 # Get output from the console. This should contain both the386 # "initCommands" and the "preRunCommands".387 output = self.get_console()388 # Verify all "initCommands" were found in console output389 self.verify_commands("initCommands", output, initCommands)390 # Verify all "preRunCommands" were found in console output391 self.verify_commands("preRunCommands", output, preRunCommands)392 # Verify all "postRunCommands" were found in console output393 self.verify_commands("postRunCommands", output, postRunCommands)394 395 source = "main.c"396 first_line = line_number(source, "// breakpoint 1")397 second_line = line_number(source, "// breakpoint 2")398 lines = [first_line, second_line]399 400 # Set 2 breakpoints so we can verify that "stopCommands" get run as the401 # breakpoints get hit402 breakpoint_ids = self.set_source_breakpoints(source, lines)403 self.assertEqual(404 len(breakpoint_ids), len(lines), "expect correct number of breakpoints"405 )406 407 # Continue after launch and hit the first breakpoint.408 # Get output from the console. This should contain both the409 # "stopCommands" that were run after the first breakpoint was hit410 self.continue_to_breakpoints(breakpoint_ids)411 output = self.get_console()412 self.verify_commands("stopCommands", output, stopCommands)413 414 # Continue again and hit the second breakpoint.415 # Get output from the console. This should contain both the416 # "stopCommands" that were run after the second breakpoint was hit417 self.continue_to_breakpoints(breakpoint_ids)418 output = self.get_console()419 self.verify_commands("stopCommands", output, stopCommands)420 421 # Continue until the program exits422 self.continue_to_exit()423 # Get output from the console. This should contain both the424 # "exitCommands" that were run after the second breakpoint was hit425 # and the "terminateCommands" due to the debugging session ending426 output = self.collect_console(pattern=terminateCommands[0])427 self.verify_commands("exitCommands", output, exitCommands)428 self.verify_commands("terminateCommands", output, terminateCommands)429 430 # Flakey on 32-bit Arm Linux.431 @skipIf(oslist=["linux"], archs=["arm$"])432 def test_extra_launch_commands(self):433 """434 Tests the "launchCommands" with extra launching settings435 """436 self.build_and_create_debug_adapter()437 program = self.getBuildArtifact("a.out")438 439 source = "main.c"440 first_line = line_number(source, "// breakpoint 1")441 second_line = line_number(source, "// breakpoint 2")442 # Set target binary and 2 breakpoints443 # then we can varify the "launchCommands" get run444 # also we can verify that "stopCommands" get run as the445 # breakpoints get hit446 launchCommands = [447 'target create "%s"' % (program),448 "breakpoint s -f main.c -l %d" % first_line,449 "breakpoint s -f main.c -l %d" % second_line,450 "process launch --stop-at-entry",451 ]452 453 initCommands = ["target list", "platform list"]454 preRunCommands = ["image list a.out", "image dump sections a.out"]455 stopCommands = ["frame variable", "bt"]456 exitCommands = ["expr 2+3", "expr 3+4"]457 self.launch(458 program,459 initCommands=initCommands,460 preRunCommands=preRunCommands,461 stopCommands=stopCommands,462 exitCommands=exitCommands,463 launchCommands=launchCommands,464 )465 466 # Get output from the console. This should contain both the467 # "initCommands" and the "preRunCommands".468 output = self.get_console()469 # Verify all "initCommands" were found in console output470 self.verify_commands("initCommands", output, initCommands)471 # Verify all "preRunCommands" were found in console output472 self.verify_commands("preRunCommands", output, preRunCommands)473 474 # Verify all "launchCommands" were found in console output475 # After execution, program should launch476 self.verify_commands("launchCommands", output, launchCommands)477 # Verify the "stopCommands" here478 self.continue_to_next_stop()479 output = self.get_console()480 self.verify_commands("stopCommands", output, stopCommands)481 482 # Continue and hit the second breakpoint.483 # Get output from the console. This should contain both the484 # "stopCommands" that were run after the first breakpoint was hit485 self.continue_to_next_stop()486 output = self.get_console()487 self.verify_commands("stopCommands", output, stopCommands)488 489 # Continue until the program exits490 self.continue_to_exit()491 # Get output from the console. This should contain both the492 # "exitCommands" that were run after the second breakpoint was hit493 output = self.get_console()494 self.verify_commands("exitCommands", output, exitCommands)495 496 def test_failing_launch_commands(self):497 """498 Tests "launchCommands" failures prevents a launch.499 """500 self.build_and_create_debug_adapter()501 program = self.getBuildArtifact("a.out")502 503 # Run an invalid launch command, in this case a bad path.504 bad_path = os.path.join("bad", "path")505 launchCommands = ['!target create "%s%s"' % (bad_path, program)]506 507 initCommands = ["target list", "platform list"]508 preRunCommands = ["image list a.out", "image dump sections a.out"]509 response = self.launch(510 program,511 initCommands=initCommands,512 preRunCommands=preRunCommands,513 launchCommands=launchCommands,514 expectFailure=True,515 )516 517 self.assertFalse(response["success"])518 self.assertRegex(519 response["body"]["error"]["format"],520 r"Failed to run launch commands\. See the Debug Console for more details",521 )522 523 # Get output from the console. This should contain both the524 # "initCommands" and the "preRunCommands".525 output = self.get_console()526 # Verify all "initCommands" were found in console output527 self.verify_commands("initCommands", output, initCommands)528 # Verify all "preRunCommands" were found in console output529 self.verify_commands("preRunCommands", output, preRunCommands)530 531 # Verify all "launchCommands" were founc in console output532 # The launch should fail due to the invalid command.533 self.verify_commands("launchCommands", output, launchCommands)534 self.assertRegex(output, re.escape(bad_path) + r".*does not exist")535 536 @skipIfNetBSD # Hangs on NetBSD as well537 @skipIf(archs=["arm$", "aarch64"], oslist=["linux"])538 def test_terminate_commands(self):539 """540 Tests that the "terminateCommands", that can be passed during541 launch, are run when the debugger is disconnected.542 """543 self.build_and_create_debug_adapter()544 program = self.getBuildArtifact("a.out")545 546 terminateCommands = ["expr 4+2"]547 self.launch(548 program,549 stopOnEntry=True,550 terminateCommands=terminateCommands,551 disconnectAutomatically=False,552 )553 self.get_console()554 # Once it's disconnected the console should contain the555 # "terminateCommands"556 self.dap_server.request_disconnect(terminateDebuggee=True)557 output = self.collect_console(pattern=terminateCommands[0])558 self.verify_commands("terminateCommands", output, terminateCommands)559 560 @skipIfWindows561 def test_version(self):562 """563 Tests that "initialize" response contains the "version" string the same564 as the one returned by "version" command.565 """566 program = self.getBuildArtifact("a.out")567 self.build_and_launch(program)568 569 source = "main.c"570 breakpoint_line = line_number(source, "// breakpoint 1")571 lines = [breakpoint_line]572 # Set breakpoint in the thread function so we can step the threads573 breakpoint_ids = self.set_source_breakpoints(source, lines)574 self.continue_to_breakpoints(breakpoint_ids)575 576 version_eval_response = self.dap_server.request_evaluate(577 "`version", context="repl"578 )579 version_eval_output = version_eval_response["body"]["result"]580 581 version_string = self.dap_server.get_capability("$__lldb_version")582 self.assertEqual(583 version_eval_output.splitlines(),584 version_string.splitlines(),585 "version string does not match",586 )587 588 def test_no_lldbinit_flag(self):589 """590 Test that the --no-lldbinit flag prevents sourcing .lldbinit files.591 """592 # Create a temporary .lldbinit file in the home directory593 with tempfile.TemporaryDirectory() as temp_home:594 lldbinit_path = os.path.join(temp_home, ".lldbinit")595 596 # Write a command to the .lldbinit file that would set a unique setting597 with open(lldbinit_path, "w") as f:598 f.write("settings set stop-disassembly-display never\n")599 f.write("settings set target.x86-disassembly-flavor intel\n")600 601 # Test with --no-lldbinit flag (should NOT source .lldbinit)602 self.build_and_create_debug_adapter(603 lldbDAPEnv={"HOME": temp_home}, additional_args=["--no-lldbinit"]604 )605 program = self.getBuildArtifact("a.out")606 607 # Use initCommands to check if .lldbinit was sourced608 initCommands = ["settings show stop-disassembly-display"]609 610 # Launch with initCommands to check the setting611 self.launch(program, initCommands=initCommands, stopOnEntry=True)612 613 # Get console output to verify the setting was NOT set from .lldbinit614 output = self.get_console()615 self.assertTrue(output and len(output) > 0, "expect console output")616 617 # Verify the setting has default value, not "never" from .lldbinit618 self.assertNotIn(619 "never",620 output,621 "Setting should have default value when --no-lldbinit is used",622 )623 624 # Verify the initCommands were executed625 self.verify_commands("initCommands", output, initCommands)626 627 def test_stdio_redirection(self):628 """629 Test stdio redirection.630 """631 self.build_and_create_debug_adapter()632 program = self.getBuildArtifact("a.out")633 634 with tempfile.NamedTemporaryFile("rt") as f:635 self.launch(program, stdio=[None, f.name])636 self.continue_to_exit()637 lines = f.readlines()638 self.assertIn(639 program, lines[0], "make sure program path is in first argument"640 )641 642 @skipIfAsan643 @skipIfWindows644 @skipIf(oslist=["linux"], archs=no_match(["x86_64"]))645 @skipIfBuildType(["debug"])646 def test_stdio_redirection_and_console(self):647 """648 Test stdio redirection and console.649 """650 self.build_and_create_debug_adapter()651 program = self.getBuildArtifact("a.out")652 653 with tempfile.NamedTemporaryFile("rt") as f:654 self.launch(655 program, console="integratedTerminal", stdio=[None, f.name, None]656 )657 self.continue_to_exit()658 lines = f.readlines()659 self.assertIn(660 program, lines[0], "make sure program path is in first argument"661 )662