1""" 2Test lldb-dap setBreakpoints request 3""" 4 5import dap_server 6from lldbsuite.test.decorators import * 7from lldbsuite.test.lldbtest import * 8from lldbsuite.test import lldbutil 9import lldbdap_testcase 10import time 11import os 12import re 13 14 15class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase): 16 def test_default(self): 17 """ 18 Tests the default launch of a simple program. No arguments, 19 environment, or anything else is specified. 20 """ 21 program = self.getBuildArtifact("a.out") 22 self.build_and_launch(program) 23 self.continue_to_exit() 24 # Now get the STDOUT and verify our program argument is correct 25 output = self.get_stdout() 26 self.assertTrue(output and len(output) > 0, "expect program output") 27 lines = output.splitlines() 28 self.assertIn(program, lines[0], "make sure program path is in first argument") 29 30 def test_termination(self): 31 """ 32 Tests the correct termination of lldb-dap upon a 'disconnect' 33 request. 34 """ 35 self.create_debug_adaptor() 36 # The underlying lldb-dap process must be alive 37 self.assertEqual(self.dap_server.process.poll(), None) 38 39 # The lldb-dap process should finish even though 40 # we didn't close the communication socket explicitly 41 self.dap_server.request_disconnect() 42 43 # Wait until the underlying lldb-dap process dies. 44 self.dap_server.process.wait(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval) 45 46 # Check the return code 47 self.assertEqual(self.dap_server.process.poll(), 0) 48 49 def test_stopOnEntry(self): 50 """ 51 Tests the default launch of a simple program that stops at the 52 entry point instead of continuing. 53 """ 54 program = self.getBuildArtifact("a.out") 55 self.build_and_launch(program, stopOnEntry=True) 56 self.set_function_breakpoints(["main"]) 57 stopped_events = self.continue_to_next_stop() 58 for stopped_event in stopped_events: 59 if "body" in stopped_event: 60 body = stopped_event["body"] 61 if "reason" in body: 62 reason = body["reason"] 63 self.assertNotEqual( 64 reason, "breakpoint", 'verify stop isn\'t "main" breakpoint' 65 ) 66 67 def test_cwd(self): 68 """ 69 Tests the default launch of a simple program with a current working 70 directory. 71 """ 72 program = self.getBuildArtifact("a.out") 73 program_parent_dir = os.path.realpath(os.path.dirname(os.path.dirname(program))) 74 self.build_and_launch(program, cwd=program_parent_dir) 75 self.continue_to_exit() 76 # Now get the STDOUT and verify our program argument is correct 77 output = self.get_stdout() 78 self.assertTrue(output and len(output) > 0, "expect program output") 79 lines = output.splitlines() 80 found = False 81 for line in lines: 82 if line.startswith('cwd = "'): 83 quote_path = '"%s"' % (program_parent_dir) 84 found = True 85 self.assertIn( 86 quote_path, 87 line, 88 "working directory '%s' not in '%s'" % (program_parent_dir, line), 89 ) 90 self.assertTrue(found, "verified program working directory") 91 92 def test_debuggerRoot(self): 93 """ 94 Tests the "debuggerRoot" will change the working directory of 95 the lldb-dap debug adaptor. 96 """ 97 program = self.getBuildArtifact("a.out") 98 program_parent_dir = os.path.realpath(os.path.dirname(os.path.dirname(program))) 99 100 var = "%cd%" if lldbplatformutil.getHostPlatform() == "windows" else "$PWD" 101 commands = [f"platform shell echo cwd = {var}"] 102 103 self.build_and_launch( 104 program, debuggerRoot=program_parent_dir, initCommands=commands 105 ) 106 output = self.get_console() 107 self.assertTrue(output and len(output) > 0, "expect console output") 108 lines = output.splitlines() 109 prefix = "cwd = " 110 found = False 111 for line in lines: 112 if line.startswith(prefix): 113 found = True 114 self.assertEqual( 115 program_parent_dir, 116 line.strip()[len(prefix) :], 117 "lldb-dap working dir '%s' == '%s'" 118 % (program_parent_dir, line[len(prefix) :]), 119 ) 120 self.assertTrue(found, "verified lldb-dap working directory") 121 self.continue_to_exit() 122 123 def test_sourcePath(self): 124 """ 125 Tests the "sourcePath" will set the target.source-map. 126 """ 127 program = self.getBuildArtifact("a.out") 128 program_dir = os.path.dirname(program) 129 self.build_and_launch(program, sourcePath=program_dir) 130 output = self.get_console() 131 self.assertTrue(output and len(output) > 0, "expect console output") 132 lines = output.splitlines() 133 prefix = '(lldb) settings set target.source-map "." ' 134 found = False 135 for line in lines: 136 if line.startswith(prefix): 137 found = True 138 quoted_path = '"%s"' % (program_dir) 139 self.assertEqual( 140 quoted_path, 141 line[len(prefix) :], 142 "lldb-dap working dir %s == %s" % (quoted_path, line[6:]), 143 ) 144 self.assertTrue(found, 'found "sourcePath" in console output') 145 self.continue_to_exit() 146 147 def test_disableSTDIO(self): 148 """ 149 Tests the default launch of a simple program with STDIO disabled. 150 """ 151 program = self.getBuildArtifact("a.out") 152 self.build_and_launch(program, disableSTDIO=True) 153 self.continue_to_exit() 154 # Now get the STDOUT and verify our program argument is correct 155 output = self.get_stdout() 156 self.assertEqual(output, None, "expect no program output") 157 158 @skipIfWindows 159 @skipIfLinux # shell argument expansion doesn't seem to work on Linux 160 @expectedFailureAll(oslist=["freebsd", "netbsd"], bugnumber="llvm.org/pr48349") 161 def test_shellExpandArguments_enabled(self): 162 """ 163 Tests the default launch of a simple program with shell expansion 164 enabled. 165 """ 166 program = self.getBuildArtifact("a.out") 167 program_dir = os.path.dirname(program) 168 glob = os.path.join(program_dir, "*.out") 169 self.build_and_launch(program, args=[glob], shellExpandArguments=True) 170 self.continue_to_exit() 171 # Now get the STDOUT and verify our program argument is correct 172 output = self.get_stdout() 173 self.assertTrue(output and len(output) > 0, "expect no program output") 174 lines = output.splitlines() 175 for line in lines: 176 quote_path = '"%s"' % (program) 177 if line.startswith("arg[1] ="): 178 self.assertIn( 179 quote_path, line, 'verify "%s" expanded to "%s"' % (glob, program) 180 ) 181 182 def test_shellExpandArguments_disabled(self): 183 """ 184 Tests the default launch of a simple program with shell expansion 185 disabled. 186 """ 187 program = self.getBuildArtifact("a.out") 188 program_dir = os.path.dirname(program) 189 glob = os.path.join(program_dir, "*.out") 190 self.build_and_launch(program, args=[glob], shellExpandArguments=False) 191 self.continue_to_exit() 192 # Now get the STDOUT and verify our program argument is correct 193 output = self.get_stdout() 194 self.assertTrue(output and len(output) > 0, "expect no program output") 195 lines = output.splitlines() 196 for line in lines: 197 quote_path = '"%s"' % (glob) 198 if line.startswith("arg[1] ="): 199 self.assertIn( 200 quote_path, line, 'verify "%s" stayed to "%s"' % (glob, glob) 201 ) 202 203 def test_args(self): 204 """ 205 Tests launch of a simple program with arguments 206 """ 207 program = self.getBuildArtifact("a.out") 208 args = ["one", "with space", "'with single quotes'", '"with double quotes"'] 209 self.build_and_launch(program, args=args) 210 self.continue_to_exit() 211 212 # Now get the STDOUT and verify our arguments got passed correctly 213 output = self.get_stdout() 214 self.assertTrue(output and len(output) > 0, "expect program output") 215 lines = output.splitlines() 216 # Skip the first argument that contains the program name 217 lines.pop(0) 218 # Make sure arguments we specified are correct 219 for i, arg in enumerate(args): 220 quoted_arg = '"%s"' % (arg) 221 self.assertIn( 222 quoted_arg, 223 lines[i], 224 'arg[%i] "%s" not in "%s"' % (i + 1, quoted_arg, lines[i]), 225 ) 226 227 def test_environment_with_object(self): 228 """ 229 Tests launch of a simple program with environment variables 230 """ 231 program = self.getBuildArtifact("a.out") 232 env = { 233 "NO_VALUE": "", 234 "WITH_VALUE": "BAR", 235 "EMPTY_VALUE": "", 236 "SPACE": "Hello World", 237 } 238 239 self.build_and_launch(program, env=env) 240 self.continue_to_exit() 241 242 # Now get the STDOUT and verify our arguments got passed correctly 243 output = self.get_stdout() 244 self.assertTrue(output and len(output) > 0, "expect program output") 245 lines = output.splitlines() 246 # Skip the all arguments so we have only environment vars left 247 while len(lines) and lines[0].startswith("arg["): 248 lines.pop(0) 249 # Make sure each environment variable in "env" is actually set in the 250 # program environment that was printed to STDOUT 251 for var in env: 252 found = False 253 for program_var in lines: 254 if var in program_var: 255 found = True 256 break 257 self.assertTrue( 258 found, '"%s" must exist in program environment (%s)' % (var, lines) 259 ) 260 261 def test_environment_with_array(self): 262 """ 263 Tests launch of a simple program with environment variables 264 """ 265 program = self.getBuildArtifact("a.out") 266 env = ["NO_VALUE", "WITH_VALUE=BAR", "EMPTY_VALUE=", "SPACE=Hello World"] 267 268 self.build_and_launch(program, env=env) 269 self.continue_to_exit() 270 271 # Now get the STDOUT and verify our arguments got passed correctly 272 output = self.get_stdout() 273 self.assertTrue(output and len(output) > 0, "expect program output") 274 lines = output.splitlines() 275 # Skip the all arguments so we have only environment vars left 276 while len(lines) and lines[0].startswith("arg["): 277 lines.pop(0) 278 # Make sure each environment variable in "env" is actually set in the 279 # program environment that was printed to STDOUT 280 for var in env: 281 found = False 282 for program_var in lines: 283 if var in program_var: 284 found = True 285 break 286 self.assertTrue( 287 found, '"%s" must exist in program environment (%s)' % (var, lines) 288 ) 289 290 @skipIf( 291 archs=["arm", "aarch64"] 292 ) # failed run https://lab.llvm.org/buildbot/#/builders/96/builds/6933 293 def test_commands(self): 294 """ 295 Tests the "initCommands", "preRunCommands", "stopCommands", 296 "terminateCommands" and "exitCommands" that can be passed during 297 launch. 298 299 "initCommands" are a list of LLDB commands that get executed 300 before the targt is created. 301 "preRunCommands" are a list of LLDB commands that get executed 302 after the target has been created and before the launch. 303 "stopCommands" are a list of LLDB commands that get executed each 304 time the program stops. 305 "exitCommands" are a list of LLDB commands that get executed when 306 the process exits 307 "terminateCommands" are a list of LLDB commands that get executed when 308 the debugger session terminates. 309 """ 310 program = self.getBuildArtifact("a.out") 311 initCommands = ["target list", "platform list"] 312 preRunCommands = ["image list a.out", "image dump sections a.out"] 313 postRunCommands = ["help trace", "help process trace"] 314 stopCommands = ["frame variable", "bt"] 315 exitCommands = ["expr 2+3", "expr 3+4"] 316 terminateCommands = ["expr 4+2"] 317 self.build_and_launch( 318 program, 319 initCommands=initCommands, 320 preRunCommands=preRunCommands, 321 postRunCommands=postRunCommands, 322 stopCommands=stopCommands, 323 exitCommands=exitCommands, 324 terminateCommands=terminateCommands, 325 ) 326 327 # Get output from the console. This should contain both the 328 # "initCommands" and the "preRunCommands". 329 output = self.get_console() 330 # Verify all "initCommands" were found in console output 331 self.verify_commands("initCommands", output, initCommands) 332 # Verify all "preRunCommands" were found in console output 333 self.verify_commands("preRunCommands", output, preRunCommands) 334 # Verify all "postRunCommands" were found in console output 335 self.verify_commands("postRunCommands", output, postRunCommands) 336 337 source = "main.c" 338 first_line = line_number(source, "// breakpoint 1") 339 second_line = line_number(source, "// breakpoint 2") 340 lines = [first_line, second_line] 341 342 # Set 2 breakpoints so we can verify that "stopCommands" get run as the 343 # breakpoints get hit 344 breakpoint_ids = self.set_source_breakpoints(source, lines) 345 self.assertEqual( 346 len(breakpoint_ids), len(lines), "expect correct number of breakpoints" 347 ) 348 349 # Continue after launch and hit the first breakpoint. 350 # Get output from the console. This should contain both the 351 # "stopCommands" that were run after the first breakpoint was hit 352 self.continue_to_breakpoints(breakpoint_ids) 353 output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval) 354 self.verify_commands("stopCommands", output, stopCommands) 355 356 # Continue again and hit the second breakpoint. 357 # Get output from the console. This should contain both the 358 # "stopCommands" that were run after the second breakpoint was hit 359 self.continue_to_breakpoints(breakpoint_ids) 360 output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval) 361 self.verify_commands("stopCommands", output, stopCommands) 362 363 # Continue until the program exits 364 self.continue_to_exit() 365 # Get output from the console. This should contain both the 366 # "exitCommands" that were run after the second breakpoint was hit 367 # and the "terminateCommands" due to the debugging session ending 368 output = self.collect_console( 369 timeout_secs=1.0, 370 pattern=terminateCommands[0], 371 ) 372 self.verify_commands("exitCommands", output, exitCommands) 373 self.verify_commands("terminateCommands", output, terminateCommands) 374 375 def test_extra_launch_commands(self): 376 """ 377 Tests the "launchCommands" with extra launching settings 378 """ 379 self.build_and_create_debug_adaptor() 380 program = self.getBuildArtifact("a.out") 381 382 source = "main.c" 383 first_line = line_number(source, "// breakpoint 1") 384 second_line = line_number(source, "// breakpoint 2") 385 # Set target binary and 2 breakpoints 386 # then we can varify the "launchCommands" get run 387 # also we can verify that "stopCommands" get run as the 388 # breakpoints get hit 389 launchCommands = [ 390 'target create "%s"' % (program), 391 "breakpoint s -f main.c -l %d" % first_line, 392 "breakpoint s -f main.c -l %d" % second_line, 393 "process launch --stop-at-entry", 394 ] 395 396 initCommands = ["target list", "platform list"] 397 preRunCommands = ["image list a.out", "image dump sections a.out"] 398 stopCommands = ["frame variable", "bt"] 399 exitCommands = ["expr 2+3", "expr 3+4"] 400 self.launch( 401 program, 402 initCommands=initCommands, 403 preRunCommands=preRunCommands, 404 stopCommands=stopCommands, 405 exitCommands=exitCommands, 406 launchCommands=launchCommands, 407 ) 408 409 # Get output from the console. This should contain both the 410 # "initCommands" and the "preRunCommands". 411 output = self.get_console() 412 # Verify all "initCommands" were found in console output 413 self.verify_commands("initCommands", output, initCommands) 414 # Verify all "preRunCommands" were found in console output 415 self.verify_commands("preRunCommands", output, preRunCommands) 416 417 # Verify all "launchCommands" were found in console output 418 # After execution, program should launch 419 self.verify_commands("launchCommands", output, launchCommands) 420 # Verify the "stopCommands" here 421 self.continue_to_next_stop() 422 output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval) 423 self.verify_commands("stopCommands", output, stopCommands) 424 425 # Continue and hit the second breakpoint. 426 # Get output from the console. This should contain both the 427 # "stopCommands" that were run after the first breakpoint was hit 428 self.continue_to_next_stop() 429 output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval) 430 self.verify_commands("stopCommands", output, stopCommands) 431 432 # Continue until the program exits 433 self.continue_to_exit() 434 # Get output from the console. This should contain both the 435 # "exitCommands" that were run after the second breakpoint was hit 436 output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval) 437 self.verify_commands("exitCommands", output, exitCommands) 438 439 def test_failing_launch_commands(self): 440 """ 441 Tests "launchCommands" failures prevents a launch. 442 """ 443 self.build_and_create_debug_adaptor() 444 program = self.getBuildArtifact("a.out") 445 446 # Run an invalid launch command, in this case a bad path. 447 bad_path = os.path.join("bad", "path") 448 launchCommands = ['!target create "%s%s"' % (bad_path, program)] 449 450 initCommands = ["target list", "platform list"] 451 preRunCommands = ["image list a.out", "image dump sections a.out"] 452 response = self.launch( 453 program, 454 initCommands=initCommands, 455 preRunCommands=preRunCommands, 456 launchCommands=launchCommands, 457 expectFailure=True, 458 ) 459 460 self.assertFalse(response["success"]) 461 self.assertRegex( 462 response["message"], 463 r"Failed to run launch commands\. See the Debug Console for more details", 464 ) 465 466 # Get output from the console. This should contain both the 467 # "initCommands" and the "preRunCommands". 468 output = self.get_console() 469 # Verify all "initCommands" were found in console output 470 self.verify_commands("initCommands", output, initCommands) 471 # Verify all "preRunCommands" were found in console output 472 self.verify_commands("preRunCommands", output, preRunCommands) 473 474 # Verify all "launchCommands" were founc in console output 475 # The launch should fail due to the invalid command. 476 self.verify_commands("launchCommands", output, launchCommands) 477 self.assertRegex(output, re.escape(bad_path) + r".*does not exist") 478 479 @skipIfNetBSD # Hangs on NetBSD as well 480 @skipIf(archs=["arm", "aarch64"], oslist=["linux"]) 481 def test_terminate_commands(self): 482 """ 483 Tests that the "terminateCommands", that can be passed during 484 launch, are run when the debugger is disconnected. 485 """ 486 self.build_and_create_debug_adaptor() 487 program = self.getBuildArtifact("a.out") 488 489 terminateCommands = ["expr 4+2"] 490 self.launch( 491 program=program, 492 terminateCommands=terminateCommands, 493 disconnectAutomatically=False, 494 ) 495 self.get_console() 496 # Once it's disconnected the console should contain the 497 # "terminateCommands" 498 self.dap_server.request_disconnect(terminateDebuggee=True) 499 output = self.collect_console( 500 timeout_secs=1.0, 501 pattern=terminateCommands[0], 502 ) 503 self.verify_commands("terminateCommands", output, terminateCommands) 504 505 def test_version(self): 506 """ 507 Tests that "initialize" response contains the "version" string the same 508 as the one returned by "version" command. 509 """ 510 program = self.getBuildArtifact("a.out") 511 self.build_and_launch(program) 512 513 source = "main.c" 514 breakpoint_line = line_number(source, "// breakpoint 1") 515 lines = [breakpoint_line] 516 # Set breakpoint in the thread function so we can step the threads 517 breakpoint_ids = self.set_source_breakpoints(source, lines) 518 self.continue_to_breakpoints(breakpoint_ids) 519 520 version_eval_response = self.dap_server.request_evaluate( 521 "`version", context="repl" 522 ) 523 version_eval_output = version_eval_response["body"]["result"] 524 525 # The first line is the prompt line like "(lldb) version", so we skip it. 526 version_eval_output_without_prompt_line = version_eval_output.splitlines()[1:] 527 lldb_json = self.dap_server.get_initialize_value("__lldb") 528 version_string = lldb_json["version"] 529 self.assertEqual( 530 version_eval_output_without_prompt_line, 531 version_string.splitlines(), 532 "version string does not match", 533 ) 534