435 lines · python
1"""2Test lldb-dap setBreakpoints request3"""4 5from dap_server import Source6import shutil7from lldbsuite.test.decorators import *8from lldbsuite.test.lldbtest import *9from lldbsuite.test import lldbutil10import lldbdap_testcase11import os12 13 14class TestDAP_setBreakpoints(lldbdap_testcase.DAPTestCaseBase):15 def setUp(self):16 lldbdap_testcase.DAPTestCaseBase.setUp(self)17 18 self.main_basename = "main-copy.cpp"19 self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename))20 21 @skipIfWindows22 def test_source_map(self):23 """24 This test simulates building two files in a folder, and then moving25 each source to a different folder. Then, the debug session is started26 with the corresponding source maps to have breakpoints and frames27 working.28 """29 self.build_and_create_debug_adapter()30 31 other_basename = "other-copy.c"32 other_path = self.getBuildArtifact(other_basename)33 34 source_folder = os.path.dirname(self.main_path)35 36 new_main_folder = os.path.join(source_folder, "moved_main")37 new_other_folder = os.path.join(source_folder, "moved_other")38 39 new_main_path = os.path.join(new_main_folder, self.main_basename)40 new_other_path = os.path.join(new_other_folder, other_basename)41 42 # move the sources43 os.mkdir(new_main_folder)44 os.mkdir(new_other_folder)45 shutil.move(self.main_path, new_main_path)46 shutil.move(other_path, new_other_path)47 48 main_line = line_number("main.cpp", "break 12")49 other_line = line_number("other.c", "break other")50 51 program = self.getBuildArtifact("a.out")52 source_map = [53 [source_folder, new_main_folder],54 [source_folder, new_other_folder],55 ]56 self.launch(program, sourceMap=source_map)57 58 # breakpoint in main.cpp59 response = self.dap_server.request_setBreakpoints(60 Source.build(path=new_main_path), [main_line]61 )62 breakpoints = response["body"]["breakpoints"]63 self.assertEqual(len(breakpoints), 1)64 breakpoint = breakpoints[0]65 self.assertEqual(breakpoint["line"], main_line)66 self.assertTrue(breakpoint["verified"])67 self.assertEqual(self.main_basename, breakpoint["source"]["name"])68 self.assertEqual(new_main_path, breakpoint["source"]["path"])69 70 # 2nd breakpoint, which is from a dynamically loaded library71 response = self.dap_server.request_setBreakpoints(72 Source.build(path=new_other_path), [other_line]73 )74 breakpoints = response["body"]["breakpoints"]75 breakpoint = breakpoints[0]76 self.assertEqual(breakpoint["line"], other_line)77 self.assertFalse(breakpoint["verified"])78 self.assertEqual(other_basename, breakpoint["source"]["name"])79 self.assertEqual(new_other_path, breakpoint["source"]["path"])80 other_breakpoint_id = breakpoint["id"]81 82 self.dap_server.request_continue()83 self.verify_breakpoint_hit([other_breakpoint_id])84 85 # 2nd breakpoint again, which should be valid at this point86 response = self.dap_server.request_setBreakpoints(87 Source.build(path=new_other_path), [other_line]88 )89 breakpoints = response["body"]["breakpoints"]90 breakpoint = breakpoints[0]91 self.assertEqual(breakpoint["line"], other_line)92 self.assertTrue(breakpoint["verified"])93 self.assertEqual(other_basename, breakpoint["source"]["name"])94 self.assertEqual(new_other_path, breakpoint["source"]["path"])95 96 # now we check the stack trace making sure that we got mapped source paths97 frames = self.dap_server.request_stackTrace()["body"]["stackFrames"]98 99 self.assertEqual(frames[0]["source"]["name"], other_basename)100 self.assertEqual(frames[0]["source"]["path"], new_other_path)101 102 self.assertEqual(frames[1]["source"]["name"], self.main_basename)103 self.assertEqual(frames[1]["source"]["path"], new_main_path)104 105 @skipIfWindows106 def test_set_and_clear(self):107 """Tests setting and clearing source file and line breakpoints.108 This packet is a bit tricky on the debug adapter side since there109 is no "clearBreakpoints" packet. Source file and line breakpoints110 are set by sending a "setBreakpoints" packet with a source file111 specified and zero or more source lines. If breakpoints have been112 set in the source file before, any existing breakpoints must remain113 set, and any new breakpoints must be created, and any breakpoints114 that were in previous requests and are not in the current request115 must be removed. This function tests this setting and clearing116 and makes sure things happen correctly. It doesn't test hitting117 breakpoints and the functionality of each breakpoint, like118 'conditions' and 'hitCondition' settings."""119 first_line = line_number("main.cpp", "break 12")120 second_line = line_number("main.cpp", "break 13")121 third_line = line_number("main.cpp", "break 14")122 lines = [first_line, third_line, second_line]123 124 # Visual Studio Code Debug Adapters have no way to specify the file125 # without launching or attaching to a process, so we must start a126 # process in order to be able to set breakpoints.127 program = self.getBuildArtifact("a.out")128 self.build_and_launch(program)129 130 # Set 3 breakpoints and verify that they got set correctly131 response = self.dap_server.request_setBreakpoints(132 Source.build(path=self.main_path), lines133 )134 line_to_id = {}135 breakpoints = response["body"]["breakpoints"]136 self.assertEqual(137 len(breakpoints),138 len(lines),139 "expect %u source breakpoints" % (len(lines)),140 )141 for index, breakpoint in enumerate(breakpoints):142 line = breakpoint["line"]143 self.assertEqual(line, lines[index])144 # Store the "id" of the breakpoint that was set for later145 line_to_id[line] = breakpoint["id"]146 self.assertTrue(breakpoint["verified"], "expect breakpoint verified")147 148 # There is no breakpoint delete packet, clients just send another149 # setBreakpoints packet with the same source file with fewer lines.150 # Below we remove the second line entry and call the setBreakpoints151 # function again. We want to verify that any breakpoints that were set152 # before still have the same "id". This means we didn't clear the153 # breakpoint and set it again at the same location. We also need to154 # verify that the second line location was actually removed.155 lines.remove(second_line)156 # Set 2 breakpoints and verify that the previous breakpoints that were157 # set above are still set.158 response = self.dap_server.request_setBreakpoints(159 Source.build(path=self.main_path), lines160 )161 breakpoints = response["body"]["breakpoints"]162 self.assertEqual(163 len(breakpoints),164 len(lines),165 "expect %u source breakpoints" % (len(lines)),166 )167 for index, breakpoint in enumerate(breakpoints):168 line = breakpoint["line"]169 self.assertEqual(line, lines[index])170 # Verify the same breakpoints are still set within LLDB by171 # making sure the breakpoint ID didn't change172 self.assertEqual(173 line_to_id[line],174 breakpoint["id"],175 "verify previous breakpoints stayed the same",176 )177 self.assertTrue(breakpoint["verified"], "expect breakpoint still verified")178 179 # Now get the full list of breakpoints set in the target and verify180 # we have only 2 breakpoints set. The response above could have told181 # us about 2 breakpoints, but we want to make sure we don't have the182 # third one still set in the target183 response = self.dap_server.request_testGetTargetBreakpoints()184 breakpoints = response["body"]["breakpoints"]185 self.assertEqual(186 len(breakpoints),187 len(lines),188 "expect %u source breakpoints" % (len(lines)),189 )190 for breakpoint in breakpoints:191 line = breakpoint["line"]192 # Verify the same breakpoints are still set within LLDB by193 # making sure the breakpoint ID didn't change194 self.assertEqual(195 line_to_id[line],196 breakpoint["id"],197 "verify previous breakpoints stayed the same",198 )199 self.assertIn(line, lines, "line expected in lines array")200 self.assertTrue(breakpoint["verified"], "expect breakpoint still verified")201 202 # Now clear all breakpoints for the source file by passing down an203 # empty lines array204 lines = []205 response = self.dap_server.request_setBreakpoints(206 Source.build(path=self.main_path), lines207 )208 breakpoints = response["body"]["breakpoints"]209 self.assertEqual(210 len(breakpoints),211 len(lines),212 "expect %u source breakpoints" % (len(lines)),213 )214 215 # Verify with the target that all breakpoints have been cleared216 response = self.dap_server.request_testGetTargetBreakpoints()217 breakpoints = response["body"]["breakpoints"]218 self.assertEqual(219 len(breakpoints),220 len(lines),221 "expect %u source breakpoints" % (len(lines)),222 )223 224 # Now set a breakpoint again in the same source file and verify it225 # was added.226 lines = [second_line]227 response = self.dap_server.request_setBreakpoints(228 Source.build(path=self.main_path), lines229 )230 if response:231 breakpoints = response["body"]["breakpoints"]232 self.assertEqual(233 len(breakpoints),234 len(lines),235 "expect %u source breakpoints" % (len(lines)),236 )237 for breakpoint in breakpoints:238 line = breakpoint["line"]239 self.assertIn(line, lines, "line expected in lines array")240 self.assertTrue(241 breakpoint["verified"], "expect breakpoint still verified"242 )243 244 # Now get the full list of breakpoints set in the target and verify245 # we have only 2 breakpoints set. The response above could have told246 # us about 2 breakpoints, but we want to make sure we don't have the247 # third one still set in the target248 response = self.dap_server.request_testGetTargetBreakpoints()249 if response:250 breakpoints = response["body"]["breakpoints"]251 self.assertEqual(252 len(breakpoints),253 len(lines),254 "expect %u source breakpoints" % (len(lines)),255 )256 for breakpoint in breakpoints:257 line = breakpoint["line"]258 self.assertIn(line, lines, "line expected in lines array")259 self.assertTrue(260 breakpoint["verified"], "expect breakpoint still verified"261 )262 263 @skipIfWindows264 def test_clear_breakpoints_unset_breakpoints(self):265 """Test clearing breakpoints like test_set_and_clear, but clear266 breakpoints by omitting the breakpoints array instead of sending an267 empty one."""268 lines = [269 line_number("main.cpp", "break 12"),270 line_number("main.cpp", "break 13"),271 ]272 273 # Visual Studio Code Debug Adapters have no way to specify the file274 # without launching or attaching to a process, so we must start a275 # process in order to be able to set breakpoints.276 program = self.getBuildArtifact("a.out")277 self.build_and_launch(program)278 279 # Set one breakpoint and verify that it got set correctly.280 response = self.dap_server.request_setBreakpoints(281 Source.build(path=self.main_path), lines282 )283 line_to_id = {}284 breakpoints = response["body"]["breakpoints"]285 self.assertEqual(286 len(breakpoints), len(lines), "expect %u source breakpoints" % (len(lines))287 )288 for index, breakpoint in enumerate(breakpoints):289 line = breakpoint["line"]290 self.assertEqual(line, lines[index])291 # Store the "id" of the breakpoint that was set for later292 line_to_id[line] = breakpoint["id"]293 self.assertTrue(breakpoint["verified"], "expect breakpoint verified")294 295 # Now clear all breakpoints for the source file by not setting the296 # lines array.297 lines = None298 response = self.dap_server.request_setBreakpoints(299 Source.build(path=self.main_path), lines300 )301 breakpoints = response["body"]["breakpoints"]302 self.assertEqual(len(breakpoints), 0, "expect no source breakpoints")303 304 # Verify with the target that all breakpoints have been cleared.305 response = self.dap_server.request_testGetTargetBreakpoints()306 breakpoints = response["body"]["breakpoints"]307 self.assertEqual(len(breakpoints), 0, "expect no source breakpoints")308 309 @skipIfWindows310 def test_functionality(self):311 """Tests hitting breakpoints and the functionality of a single312 breakpoint, like 'conditions' and 'hitCondition' settings."""313 loop_line = line_number("main.cpp", "// break loop")314 315 program = self.getBuildArtifact("a.out")316 self.build_and_launch(program)317 # Set a breakpoint at the loop line with no condition and no318 # hitCondition319 breakpoint_ids = self.set_source_breakpoints(self.main_path, [loop_line])320 self.assertEqual(len(breakpoint_ids), 1, "expect one breakpoint")321 self.dap_server.request_continue()322 323 # Verify we hit the breakpoint we just set324 self.verify_breakpoint_hit(breakpoint_ids)325 326 # Make sure i is zero at first breakpoint327 i = int(self.dap_server.get_local_variable_value("i"))328 self.assertEqual(i, 0, "i != 0 after hitting breakpoint")329 330 # Update the condition on our breakpoint331 new_breakpoint_ids = self.set_source_breakpoints(332 self.main_path, [loop_line], [{"condition": "i==4"}]333 )334 self.assertEqual(335 breakpoint_ids,336 new_breakpoint_ids,337 "existing breakpoint should have its condition " "updated",338 )339 340 self.continue_to_breakpoints(breakpoint_ids)341 i = int(self.dap_server.get_local_variable_value("i"))342 self.assertEqual(i, 4, "i != 4 showing conditional works")343 344 new_breakpoint_ids = self.set_source_breakpoints(345 self.main_path, [loop_line], [{"hitCondition": "2"}]346 )347 348 self.assertEqual(349 breakpoint_ids,350 new_breakpoint_ids,351 "existing breakpoint should have its condition " "updated",352 )353 354 # Continue with a hitCondition of 2 and expect it to skip 1 value355 self.continue_to_breakpoints(breakpoint_ids)356 i = int(self.dap_server.get_local_variable_value("i"))357 self.assertEqual(i, 6, "i != 6 showing hitCondition works")358 359 # continue after hitting our hitCondition and make sure it only goes360 # up by 1361 self.continue_to_breakpoints(breakpoint_ids)362 i = int(self.dap_server.get_local_variable_value("i"))363 self.assertEqual(i, 7, "i != 7 showing post hitCondition hits every time")364 365 @skipIfWindows366 def test_column_breakpoints(self):367 """Test setting multiple breakpoints in the same line at different columns."""368 loop_line = line_number("main.cpp", "// break loop")369 370 program = self.getBuildArtifact("a.out")371 self.build_and_launch(program)372 373 # Set two breakpoints on the loop line at different columns.374 columns = [13, 39]375 response = self.dap_server.request_setBreakpoints(376 Source.build(path=self.main_path),377 [loop_line, loop_line],378 list({"column": c} for c in columns),379 )380 381 # Verify the breakpoints were set correctly382 breakpoints = response["body"]["breakpoints"]383 breakpoint_ids = []384 self.assertEqual(385 len(breakpoints),386 len(columns),387 "expect %u source breakpoints" % (len(columns)),388 )389 for index, breakpoint in enumerate(breakpoints):390 self.assertEqual(breakpoint["line"], loop_line)391 self.assertEqual(breakpoint["column"], columns[index])392 self.assertTrue(breakpoint["verified"], "expect breakpoint verified")393 breakpoint_ids.append(breakpoint["id"])394 395 # Continue to the first breakpoint,396 self.continue_to_breakpoints([breakpoint_ids[0]])397 398 # We should have stopped right before the call to `twelve`.399 # Step into and check we are inside `twelve`.400 self.stepIn()401 func_name = self.get_stackFrames()[0]["name"]402 self.assertEqual(func_name, "twelve(int)")403 404 # Continue to the second breakpoint.405 self.continue_to_breakpoints([breakpoint_ids[1]])406 407 # We should have stopped right before the call to `fourteen`.408 # Step into and check we are inside `fourteen`.409 self.stepIn()410 func_name = self.get_stackFrames()[0]["name"]411 self.assertEqual(func_name, "a::fourteen(int)")412 413 @skipIfWindows414 def test_hit_multiple_breakpoints(self):415 """Test that if we hit multiple breakpoints at the same address, they416 all appear in the stop reason."""417 breakpoint_lines = [418 line_number("main.cpp", "// break non-breakpointable line"),419 line_number("main.cpp", "// before loop"),420 ]421 422 program = self.getBuildArtifact("a.out")423 self.build_and_launch(program)424 425 # Set a pair of breakpoints that will both resolve to the same address.426 breakpoint_ids = [427 int(bp_id)428 for bp_id in self.set_source_breakpoints(self.main_path, breakpoint_lines)429 ]430 self.assertEqual(len(breakpoint_ids), 2, "expected two breakpoints")431 self.dap_server.request_continue()432 print(breakpoint_ids)433 # Verify we hit both of the breakpoints we just set434 self.verify_all_breakpoints_hit(breakpoint_ids)435