xref: /llvm-project/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py (revision fa6377119c0624773cb698935692d46843e9f6ec)
1"""
2Test lldb-dap setBreakpoints request
3"""
4
5
6import dap_server
7import shutil
8from lldbsuite.test.decorators import *
9from lldbsuite.test.lldbtest import *
10from lldbsuite.test import lldbutil
11import lldbdap_testcase
12import os
13
14
15class TestDAP_setBreakpoints(lldbdap_testcase.DAPTestCaseBase):
16    def setUp(self):
17        lldbdap_testcase.DAPTestCaseBase.setUp(self)
18
19        self.main_basename = "main-copy.cpp"
20        self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename))
21
22    @skipIfWindows
23    def test_source_map(self):
24        """
25        This test simulates building two files in a folder, and then moving
26        each source to a different folder. Then, the debug session is started
27        with the corresponding source maps to have breakpoints and frames
28        working.
29        """
30        self.build_and_create_debug_adaptor()
31
32        other_basename = "other-copy.c"
33        other_path = self.getBuildArtifact(other_basename)
34
35        source_folder = os.path.dirname(self.main_path)
36
37        new_main_folder = os.path.join(source_folder, "moved_main")
38        new_other_folder = os.path.join(source_folder, "moved_other")
39
40        new_main_path = os.path.join(new_main_folder, self.main_basename)
41        new_other_path = os.path.join(new_other_folder, other_basename)
42
43        # move the sources
44        os.mkdir(new_main_folder)
45        os.mkdir(new_other_folder)
46        shutil.move(self.main_path, new_main_path)
47        shutil.move(other_path, new_other_path)
48
49        main_line = line_number("main.cpp", "break 12")
50        other_line = line_number("other.c", "break other")
51
52        program = self.getBuildArtifact("a.out")
53        source_map = [
54            [source_folder, new_main_folder],
55            [source_folder, new_other_folder],
56        ]
57        self.launch(program, sourceMap=source_map)
58
59        # breakpoint in main.cpp
60        response = self.dap_server.request_setBreakpoints(new_main_path, [main_line])
61        breakpoints = response["body"]["breakpoints"]
62        self.assertEqual(len(breakpoints), 1)
63        breakpoint = breakpoints[0]
64        self.assertEqual(breakpoint["line"], main_line)
65        self.assertTrue(breakpoint["verified"])
66        self.assertEqual(self.main_basename, breakpoint["source"]["name"])
67        self.assertEqual(new_main_path, breakpoint["source"]["path"])
68
69        # 2nd breakpoint, which is from a dynamically loaded library
70        response = self.dap_server.request_setBreakpoints(new_other_path, [other_line])
71        breakpoints = response["body"]["breakpoints"]
72        breakpoint = breakpoints[0]
73        self.assertEqual(breakpoint["line"], other_line)
74        self.assertFalse(breakpoint["verified"])
75        self.assertEqual(other_basename, breakpoint["source"]["name"])
76        self.assertEqual(new_other_path, breakpoint["source"]["path"])
77        other_breakpoint_id = breakpoint["id"]
78
79        self.dap_server.request_continue()
80        self.verify_breakpoint_hit([other_breakpoint_id])
81
82        # 2nd breakpoint again, which should be valid at this point
83        response = self.dap_server.request_setBreakpoints(new_other_path, [other_line])
84        breakpoints = response["body"]["breakpoints"]
85        breakpoint = breakpoints[0]
86        self.assertEqual(breakpoint["line"], other_line)
87        self.assertTrue(breakpoint["verified"])
88        self.assertEqual(other_basename, breakpoint["source"]["name"])
89        self.assertEqual(new_other_path, breakpoint["source"]["path"])
90
91        # now we check the stack trace making sure that we got mapped source paths
92        frames = self.dap_server.request_stackTrace()["body"]["stackFrames"]
93
94        self.assertEqual(frames[0]["source"]["name"], other_basename)
95        self.assertEqual(frames[0]["source"]["path"], new_other_path)
96
97        self.assertEqual(frames[1]["source"]["name"], self.main_basename)
98        self.assertEqual(frames[1]["source"]["path"], new_main_path)
99
100    @skipIfWindows
101    def test_set_and_clear(self):
102        """Tests setting and clearing source file and line breakpoints.
103        This packet is a bit tricky on the debug adaptor side since there
104        is no "clearBreakpoints" packet. Source file and line breakpoints
105        are set by sending a "setBreakpoints" packet with a source file
106        specified and zero or more source lines. If breakpoints have been
107        set in the source file before, any existing breakpoints must remain
108        set, and any new breakpoints must be created, and any breakpoints
109        that were in previous requests and are not in the current request
110        must be removed. This function tests this setting and clearing
111        and makes sure things happen correctly. It doesn't test hitting
112        breakpoints and the functionality of each breakpoint, like
113        'conditions' and 'hitCondition' settings."""
114        first_line = line_number("main.cpp", "break 12")
115        second_line = line_number("main.cpp", "break 13")
116        third_line = line_number("main.cpp", "break 14")
117        lines = [first_line, third_line, second_line]
118
119        # Visual Studio Code Debug Adaptors have no way to specify the file
120        # without launching or attaching to a process, so we must start a
121        # process in order to be able to set breakpoints.
122        program = self.getBuildArtifact("a.out")
123        self.build_and_launch(program)
124
125        # Set 3 breakpoints and verify that they got set correctly
126        response = self.dap_server.request_setBreakpoints(self.main_path, lines)
127        line_to_id = {}
128        if response:
129            breakpoints = response["body"]["breakpoints"]
130            self.assertEqual(
131                len(breakpoints),
132                len(lines),
133                "expect %u source breakpoints" % (len(lines)),
134            )
135            for breakpoint, index in zip(breakpoints, range(len(lines))):
136                line = breakpoint["line"]
137                self.assertTrue(line, lines[index])
138                # Store the "id" of the breakpoint that was set for later
139                line_to_id[line] = breakpoint["id"]
140                self.assertIn(line, lines, "line expected in lines array")
141                self.assertTrue(breakpoint["verified"], "expect breakpoint verified")
142
143        # There is no breakpoint delete packet, clients just send another
144        # setBreakpoints packet with the same source file with fewer lines.
145        # Below we remove the second line entry and call the setBreakpoints
146        # function again. We want to verify that any breakpoints that were set
147        # before still have the same "id". This means we didn't clear the
148        # breakpoint and set it again at the same location. We also need to
149        # verify that the second line location was actually removed.
150        lines.remove(second_line)
151        # Set 2 breakpoints and verify that the previous breakpoints that were
152        # set above are still set.
153        response = self.dap_server.request_setBreakpoints(self.main_path, lines)
154        if response:
155            breakpoints = response["body"]["breakpoints"]
156            self.assertEqual(
157                len(breakpoints),
158                len(lines),
159                "expect %u source breakpoints" % (len(lines)),
160            )
161            for breakpoint, index in zip(breakpoints, range(len(lines))):
162                line = breakpoint["line"]
163                self.assertTrue(line, lines[index])
164                # Verify the same breakpoints are still set within LLDB by
165                # making sure the breakpoint ID didn't change
166                self.assertEqual(
167                    line_to_id[line],
168                    breakpoint["id"],
169                    "verify previous breakpoints stayed the same",
170                )
171                self.assertIn(line, lines, "line expected in lines array")
172                self.assertTrue(
173                    breakpoint["verified"], "expect breakpoint still verified"
174                )
175
176        # Now get the full list of breakpoints set in the target and verify
177        # we have only 2 breakpoints set. The response above could have told
178        # us about 2 breakpoints, but we want to make sure we don't have the
179        # third one still set in the target
180        response = self.dap_server.request_testGetTargetBreakpoints()
181        if response:
182            breakpoints = response["body"]["breakpoints"]
183            self.assertEqual(
184                len(breakpoints),
185                len(lines),
186                "expect %u source breakpoints" % (len(lines)),
187            )
188            for breakpoint in breakpoints:
189                line = breakpoint["line"]
190                # Verify the same breakpoints are still set within LLDB by
191                # making sure the breakpoint ID didn't change
192                self.assertEqual(
193                    line_to_id[line],
194                    breakpoint["id"],
195                    "verify previous breakpoints stayed the same",
196                )
197                self.assertIn(line, lines, "line expected in lines array")
198                self.assertTrue(
199                    breakpoint["verified"], "expect breakpoint still verified"
200                )
201
202        # Now clear all breakpoints for the source file by passing down an
203        # empty lines array
204        lines = []
205        response = self.dap_server.request_setBreakpoints(self.main_path, lines)
206        if response:
207            breakpoints = response["body"]["breakpoints"]
208            self.assertEqual(
209                len(breakpoints),
210                len(lines),
211                "expect %u source breakpoints" % (len(lines)),
212            )
213
214        # Verify with the target that all breakpoints have been cleared
215        response = self.dap_server.request_testGetTargetBreakpoints()
216        if response:
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 it
225        # was added.
226        lines = [second_line]
227        response = self.dap_server.request_setBreakpoints(self.main_path, lines)
228        if response:
229            breakpoints = response["body"]["breakpoints"]
230            self.assertEqual(
231                len(breakpoints),
232                len(lines),
233                "expect %u source breakpoints" % (len(lines)),
234            )
235            for breakpoint in breakpoints:
236                line = breakpoint["line"]
237                self.assertIn(line, lines, "line expected in lines array")
238                self.assertTrue(
239                    breakpoint["verified"], "expect breakpoint still verified"
240                )
241
242        # Now get the full list of breakpoints set in the target and verify
243        # we have only 2 breakpoints set. The response above could have told
244        # us about 2 breakpoints, but we want to make sure we don't have the
245        # third one still set in the target
246        response = self.dap_server.request_testGetTargetBreakpoints()
247        if response:
248            breakpoints = response["body"]["breakpoints"]
249            self.assertEqual(
250                len(breakpoints),
251                len(lines),
252                "expect %u source breakpoints" % (len(lines)),
253            )
254            for breakpoint in breakpoints:
255                line = breakpoint["line"]
256                self.assertIn(line, lines, "line expected in lines array")
257                self.assertTrue(
258                    breakpoint["verified"], "expect breakpoint still verified"
259                )
260
261    @skipIfWindows
262    def test_clear_breakpoints_unset_breakpoints(self):
263        """Test clearing breakpoints like test_set_and_clear, but clear
264        breakpoints by omitting the breakpoints array instead of sending an
265        empty one."""
266        lines = [
267            line_number("main.cpp", "break 12"),
268            line_number("main.cpp", "break 13"),
269        ]
270
271        # Visual Studio Code Debug Adaptors have no way to specify the file
272        # without launching or attaching to a process, so we must start a
273        # process in order to be able to set breakpoints.
274        program = self.getBuildArtifact("a.out")
275        self.build_and_launch(program)
276
277        # Set one breakpoint and verify that it got set correctly.
278        response = self.dap_server.request_setBreakpoints(self.main_path, lines)
279        line_to_id = {}
280        breakpoints = response["body"]["breakpoints"]
281        self.assertEqual(
282            len(breakpoints), len(lines), "expect %u source breakpoints" % (len(lines))
283        )
284        for breakpoint, index in zip(breakpoints, range(len(lines))):
285            line = breakpoint["line"]
286            self.assertTrue(line, lines[index])
287            # Store the "id" of the breakpoint that was set for later
288            line_to_id[line] = breakpoint["id"]
289            self.assertIn(line, lines, "line expected in lines array")
290            self.assertTrue(breakpoint["verified"], "expect breakpoint verified")
291
292        # Now clear all breakpoints for the source file by not setting the
293        # lines array.
294        lines = None
295        response = self.dap_server.request_setBreakpoints(self.main_path, lines)
296        breakpoints = response["body"]["breakpoints"]
297        self.assertEqual(len(breakpoints), 0, "expect no source breakpoints")
298
299        # Verify with the target that all breakpoints have been cleared.
300        response = self.dap_server.request_testGetTargetBreakpoints()
301        breakpoints = response["body"]["breakpoints"]
302        self.assertEqual(len(breakpoints), 0, "expect no source breakpoints")
303
304    @skipIfWindows
305    def test_functionality(self):
306        """Tests hitting breakpoints and the functionality of a single
307        breakpoint, like 'conditions' and 'hitCondition' settings."""
308        loop_line = line_number("main.cpp", "// break loop")
309
310        program = self.getBuildArtifact("a.out")
311        self.build_and_launch(program)
312        # Set a breakpoint at the loop line with no condition and no
313        # hitCondition
314        breakpoint_ids = self.set_source_breakpoints(self.main_path, [loop_line])
315        self.assertEqual(len(breakpoint_ids), 1, "expect one breakpoint")
316        self.dap_server.request_continue()
317
318        # Verify we hit the breakpoint we just set
319        self.verify_breakpoint_hit(breakpoint_ids)
320
321        # Make sure i is zero at first breakpoint
322        i = int(self.dap_server.get_local_variable_value("i"))
323        self.assertEqual(i, 0, "i != 0 after hitting breakpoint")
324
325        # Update the condition on our breakpoint
326        new_breakpoint_ids = self.set_source_breakpoints(
327            self.main_path, [loop_line], [{"condition": "i==4"}]
328        )
329        self.assertEqual(
330            breakpoint_ids,
331            new_breakpoint_ids,
332            "existing breakpoint should have its condition " "updated",
333        )
334
335        self.continue_to_breakpoints(breakpoint_ids)
336        i = int(self.dap_server.get_local_variable_value("i"))
337        self.assertEqual(i, 4, "i != 4 showing conditional works")
338
339        new_breakpoint_ids = self.set_source_breakpoints(
340            self.main_path, [loop_line], [{"hitCondition": "2"}]
341        )
342
343        self.assertEqual(
344            breakpoint_ids,
345            new_breakpoint_ids,
346            "existing breakpoint should have its condition " "updated",
347        )
348
349        # Continue with a hitCondition of 2 and expect it to skip 1 value
350        self.continue_to_breakpoints(breakpoint_ids)
351        i = int(self.dap_server.get_local_variable_value("i"))
352        self.assertEqual(i, 6, "i != 6 showing hitCondition works")
353
354        # continue after hitting our hitCondition and make sure it only goes
355        # up by 1
356        self.continue_to_breakpoints(breakpoint_ids)
357        i = int(self.dap_server.get_local_variable_value("i"))
358        self.assertEqual(i, 7, "i != 7 showing post hitCondition hits every time")
359