1"""
2Test breakpoint serialization.
3"""
4
5import os
6import json
7import lldb
8from lldbsuite.test.decorators import *
9from lldbsuite.test.lldbtest import *
10from lldbsuite.test import lldbutil
11
12
13class BreakpointSerialization(TestBase):
14    NO_DEBUG_INFO_TESTCASE = True
15
16    @add_test_categories(['pyapi'])
17    def test_resolvers(self):
18        """Use Python APIs to test that we serialize resolvers."""
19        self.build()
20        self.setup_targets_and_cleanup()
21        self.do_check_resolvers()
22
23    def test_filters(self):
24        """Use Python APIs to test that we serialize search filters correctly."""
25        self.build()
26        self.setup_targets_and_cleanup()
27        self.do_check_filters()
28
29    def test_options(self):
30        """Use Python APIs to test that we serialize breakpoint options correctly."""
31        self.build()
32        self.setup_targets_and_cleanup()
33        self.do_check_options()
34
35    def test_appending(self):
36        """Use Python APIs to test that we serialize breakpoint options correctly."""
37        self.build()
38        self.setup_targets_and_cleanup()
39        self.do_check_appending()
40
41    def test_name_filters(self):
42        """Use python APIs to test that reading in by name works correctly."""
43        self.build()
44        self.setup_targets_and_cleanup()
45        self.do_check_names()
46
47    def test_scripted_extra_args(self):
48        self.build()
49        self.setup_targets_and_cleanup()
50        self.do_check_extra_args()
51
52    def test_structured_data_serialization(self):
53        target = self.dbg.GetDummyTarget()
54        self.assertTrue(target.IsValid(), VALID_TARGET)
55
56        interpreter = self.dbg.GetCommandInterpreter()
57        result = lldb.SBCommandReturnObject()
58        interpreter.HandleCommand("br set -f foo -l 42", result)
59        result = lldb.SBCommandReturnObject()
60        interpreter.HandleCommand("br set -c 'argc == 1' -n main", result)
61
62        bkp1 =  target.GetBreakpointAtIndex(0)
63        self.assertTrue(bkp1.IsValid(), VALID_BREAKPOINT)
64        stream = lldb.SBStream()
65        sd = bkp1.SerializeToStructuredData()
66        sd.GetAsJSON(stream)
67        serialized_data = json.loads(stream.GetData())
68        self.assertEqual(serialized_data["Breakpoint"]["BKPTResolver"]["Options"]["FileName"], "foo")
69        self.assertEqual(serialized_data["Breakpoint"]["BKPTResolver"]["Options"]["LineNumber"], 42)
70
71        bkp2 =  target.GetBreakpointAtIndex(1)
72        self.assertTrue(bkp2.IsValid(), VALID_BREAKPOINT)
73        stream = lldb.SBStream()
74        sd = bkp2.SerializeToStructuredData()
75        sd.GetAsJSON(stream)
76        serialized_data = json.loads(stream.GetData())
77        self.assertIn("main", serialized_data["Breakpoint"]["BKPTResolver"]["Options"]["SymbolNames"])
78        self.assertEqual(serialized_data["Breakpoint"]["BKPTOptions"]["ConditionText"],"argc == 1")
79
80        invalid_bkp = lldb.SBBreakpoint()
81        self.assertFalse(invalid_bkp.IsValid(), "Breakpoint should not be valid.")
82        stream = lldb.SBStream()
83        sd = invalid_bkp.SerializeToStructuredData()
84        sd.GetAsJSON(stream)
85        self.assertFalse(stream.GetData(), "Invalid breakpoint should have an empty structured data")
86
87    def setup_targets_and_cleanup(self):
88        def cleanup ():
89            self.RemoveTempFile(self.bkpts_file_path)
90
91            if self.orig_target.IsValid():
92                self.dbg.DeleteTarget(self.orig_target)
93                self.dbg.DeleteTarget(self.copy_target)
94
95        self.addTearDownHook(cleanup)
96        self.RemoveTempFile(self.bkpts_file_path)
97
98        exe = self.getBuildArtifact("a.out")
99
100        # Create the targets we are making breakpoints in and copying them to:
101        self.orig_target = self.dbg.CreateTarget(exe)
102        self.assertTrue(self.orig_target, VALID_TARGET)
103
104        self.copy_target = self.dbg.CreateTarget(exe)
105        self.assertTrue(self.copy_target, VALID_TARGET)
106
107    def setUp(self):
108        # Call super's setUp().
109        TestBase.setUp(self)
110
111        self.bkpts_file_path = self.getBuildArtifact("breakpoints.json")
112        self.bkpts_file_spec = lldb.SBFileSpec(self.bkpts_file_path)
113
114    def check_equivalence(self, source_bps, do_write = True):
115
116        error = lldb.SBError()
117
118        if (do_write):
119            error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, source_bps)
120            self.assertSuccess(error, "Failed writing breakpoints to file")
121
122        copy_bps = lldb.SBBreakpointList(self.copy_target)
123        error = self.copy_target.BreakpointsCreateFromFile(self.bkpts_file_spec, copy_bps)
124        self.assertSuccess(error, "Failed reading breakpoints from file")
125
126        num_source_bps = source_bps.GetSize()
127        num_copy_bps = copy_bps.GetSize()
128        self.assertEqual(num_source_bps, num_copy_bps, "Didn't get same number of input and output breakpoints - orig: %d copy: %d"%(num_source_bps, num_copy_bps))
129
130        for i in range(0, num_source_bps):
131            source_bp = source_bps.GetBreakpointAtIndex(i)
132            source_desc = lldb.SBStream()
133            source_bp.GetDescription(source_desc, False)
134            source_text = source_desc.GetData()
135
136            # I am assuming here that the breakpoints will get written out in breakpoint ID order, and
137            # read back in ditto.  That is true right now, and I can't see any reason to do it differently
138            # but if we do we can go to writing the breakpoints one by one, or sniffing the descriptions to
139            # see which one is which.
140            copy_id = source_bp.GetID()
141            copy_bp = copy_bps.FindBreakpointByID(copy_id)
142            self.assertTrue(copy_bp.IsValid(), "Could not find copy breakpoint %d."%(copy_id))
143
144            copy_desc = lldb.SBStream()
145            copy_bp.GetDescription(copy_desc, False)
146            copy_text = copy_desc.GetData()
147
148            # These two should be identical.
149            self.trace("Source text for %d is %s."%(i, source_text))
150            self.assertTrue (source_text == copy_text, "Source and dest breakpoints are not identical: \nsource: %s\ndest: %s"%(source_text, copy_text))
151
152    def do_check_resolvers(self):
153        """Use Python APIs to check serialization of breakpoint resolvers"""
154
155        empty_module_list = lldb.SBFileSpecList()
156        empty_cu_list = lldb.SBFileSpecList()
157        blubby_file_spec = lldb.SBFileSpec(os.path.join(self.getSourceDir(), "blubby.c"))
158
159        # It isn't actually important for these purposes that these breakpoint
160        # actually have locations.
161        source_bps = lldb.SBBreakpointList(self.orig_target)
162        source_bps.Append(self.orig_target.BreakpointCreateByLocation("blubby.c", 666))
163        # Make sure we do one breakpoint right:
164        self.check_equivalence(source_bps)
165        source_bps.Clear()
166
167        source_bps.Append(self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list))
168        source_bps.Append(self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeFull, empty_module_list,empty_cu_list))
169        source_bps.Append(self.orig_target.BreakpointCreateBySourceRegex("dont really care", blubby_file_spec))
170
171        # And some number greater than one:
172        self.check_equivalence(source_bps)
173
174    def do_check_filters(self):
175        """Use Python APIs to check serialization of breakpoint filters."""
176        module_list = lldb.SBFileSpecList()
177        module_list.Append(lldb.SBFileSpec("SomeBinary"))
178        module_list.Append(lldb.SBFileSpec("SomeOtherBinary"))
179
180        cu_list = lldb.SBFileSpecList()
181        cu_list.Append(lldb.SBFileSpec("SomeCU.c"))
182        cu_list.Append(lldb.SBFileSpec("AnotherCU.c"))
183        cu_list.Append(lldb.SBFileSpec("ThirdCU.c"))
184
185        blubby_file_spec = lldb.SBFileSpec(os.path.join(self.getSourceDir(), "blubby.c"))
186
187        # It isn't actually important for these purposes that these breakpoint
188        # actually have locations.
189        source_bps = lldb.SBBreakpointList(self.orig_target)
190        bkpt = self.orig_target.BreakpointCreateByLocation(blubby_file_spec, 666, 0, module_list)
191        source_bps.Append(bkpt)
192
193        # Make sure we do one right:
194        self.check_equivalence(source_bps)
195        source_bps.Clear()
196
197        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeAuto, module_list, cu_list)
198        source_bps.Append(bkpt)
199        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeFull, module_list, cu_list)
200        source_bps.Append(bkpt)
201        bkpt = self.orig_target.BreakpointCreateBySourceRegex("dont really care", blubby_file_spec)
202        source_bps.Append(bkpt)
203
204        # And some number greater than one:
205        self.check_equivalence(source_bps)
206
207    def do_check_options(self):
208        """Use Python APIs to check serialization of breakpoint options."""
209
210        empty_module_list = lldb.SBFileSpecList()
211        empty_cu_list = lldb.SBFileSpecList()
212        blubby_file_spec = lldb.SBFileSpec(os.path.join(self.getSourceDir(), "blubby.c"))
213
214        # It isn't actually important for these purposes that these breakpoint
215        # actually have locations.
216        source_bps = lldb.SBBreakpointList(self.orig_target)
217
218        bkpt = self.orig_target.BreakpointCreateByLocation(
219            lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList())
220        bkpt.SetEnabled(False)
221        bkpt.SetOneShot(True)
222        bkpt.SetThreadID(10)
223        source_bps.Append(bkpt)
224
225        # Make sure we get one right:
226        self.check_equivalence(source_bps)
227        source_bps.Clear()
228
229        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list)
230        bkpt.SetIgnoreCount(10)
231        bkpt.SetThreadName("grubby")
232        source_bps.Append(bkpt)
233
234        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list)
235        bkpt.SetCondition("gonna remove this")
236        bkpt.SetCondition("")
237        source_bps.Append(bkpt)
238
239        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeFull, empty_module_list,empty_cu_list)
240        bkpt.SetCondition("something != something_else")
241        bkpt.SetQueueName("grubby")
242        bkpt.AddName("FirstName")
243        bkpt.AddName("SecondName")
244        bkpt.SetScriptCallbackBody('\tprint("I am a function that prints.")\n\tprint("I don\'t do anything else")\n')
245        source_bps.Append(bkpt)
246
247        bkpt = self.orig_target.BreakpointCreateBySourceRegex("dont really care", blubby_file_spec)
248        cmd_list = lldb.SBStringList()
249        cmd_list.AppendString("frame var")
250        cmd_list.AppendString("thread backtrace")
251
252        bkpt.SetCommandLineCommands(cmd_list)
253        source_bps.Append(bkpt)
254
255        self.check_equivalence(source_bps)
256
257    def do_check_appending(self):
258        """Use Python APIs to check appending to already serialized options."""
259
260        empty_module_list = lldb.SBFileSpecList()
261        empty_cu_list = lldb.SBFileSpecList()
262        blubby_file_spec = lldb.SBFileSpec(os.path.join(self.getSourceDir(), "blubby.c"))
263
264        # It isn't actually important for these purposes that these breakpoint
265        # actually have locations.
266
267        all_bps = lldb.SBBreakpointList(self.orig_target)
268        source_bps = lldb.SBBreakpointList(self.orig_target)
269
270        bkpt = self.orig_target.BreakpointCreateByLocation(
271            lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList())
272        bkpt.SetEnabled(False)
273        bkpt.SetOneShot(True)
274        bkpt.SetThreadID(10)
275        source_bps.Append(bkpt)
276        all_bps.Append(bkpt)
277
278        error = lldb.SBError()
279        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, source_bps)
280        self.assertSuccess(error, "Failed writing breakpoints to file")
281
282        source_bps.Clear()
283
284        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list)
285        bkpt.SetIgnoreCount(10)
286        bkpt.SetThreadName("grubby")
287        source_bps.Append(bkpt)
288        all_bps.Append(bkpt)
289
290        bkpt = self.orig_target.BreakpointCreateByName("blubby", lldb.eFunctionNameTypeFull, empty_module_list,empty_cu_list)
291        bkpt.SetCondition("something != something_else")
292        bkpt.SetQueueName("grubby")
293        bkpt.AddName("FirstName")
294        bkpt.AddName("SecondName")
295
296        source_bps.Append(bkpt)
297        all_bps.Append(bkpt)
298
299        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, source_bps, True)
300        self.assertSuccess(error, "Failed appending breakpoints to file")
301
302        self.check_equivalence(all_bps)
303
304    def do_check_names(self):
305        bkpt = self.orig_target.BreakpointCreateByLocation(
306            lldb.SBFileSpec("blubby.c"), 666, 333, 0, lldb.SBFileSpecList())
307        good_bkpt_name = "GoodBreakpoint"
308        write_bps = lldb.SBBreakpointList(self.orig_target)
309        bkpt.AddName(good_bkpt_name)
310        write_bps.Append(bkpt)
311
312        error = lldb.SBError()
313        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, write_bps)
314        self.assertSuccess(error, "Failed writing breakpoints to file")
315
316        copy_bps = lldb.SBBreakpointList(self.copy_target)
317        names_list = lldb.SBStringList()
318        names_list.AppendString("NoSuchName")
319
320        error = self.copy_target.BreakpointsCreateFromFile(self.bkpts_file_spec, names_list, copy_bps)
321        self.assertSuccess(error, "Failed reading breakpoints from file")
322        self.assertEqual(copy_bps.GetSize(), 0, "Found breakpoints with a nonexistent name.")
323
324        names_list.AppendString(good_bkpt_name)
325        error = self.copy_target.BreakpointsCreateFromFile(self.bkpts_file_spec, names_list, copy_bps)
326        self.assertSuccess(error, "Failed reading breakpoints from file")
327        self.assertEqual(copy_bps.GetSize(), 1, "Found the matching breakpoint.")
328
329    def do_check_extra_args(self):
330
331        import side_effect
332        interp = self.dbg.GetCommandInterpreter()
333        error = lldb.SBError()
334
335        script_name = os.path.join(self.getSourceDir(), "resolver.py")
336
337        command = "command script import " + script_name
338        result = lldb.SBCommandReturnObject()
339        interp.HandleCommand(command, result)
340        self.assertTrue(result.Succeeded(), "com scr imp failed: %s"%(result.GetError()))
341
342        # First make sure a scripted breakpoint with no args works:
343        bkpt = self.orig_target.BreakpointCreateFromScript("resolver.Resolver", lldb.SBStructuredData(),
344                                                           lldb.SBFileSpecList(), lldb.SBFileSpecList())
345        self.assertTrue(bkpt.IsValid(), "Bkpt is valid")
346        write_bps = lldb.SBBreakpointList(self.orig_target)
347
348        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, write_bps)
349        self.assertSuccess(error, "Failed writing breakpoints")
350
351        side_effect.g_extra_args = None
352        copy_bps = lldb.SBBreakpointList(self.copy_target)
353        error = self.copy_target.BreakpointsCreateFromFile(self.bkpts_file_spec, copy_bps)
354        self.assertSuccess(error, "Failed reading breakpoints")
355
356        self.assertEqual(copy_bps.GetSize(), 1, "Got one breakpoint from file.")
357        no_keys = lldb.SBStringList()
358        side_effect.g_extra_args.GetKeys(no_keys)
359        self.assertEqual(no_keys.GetSize(), 0, "Should have no keys")
360
361        self.orig_target.DeleteAllBreakpoints()
362        self.copy_target.DeleteAllBreakpoints()
363
364        # Now try one with extra args:
365
366        extra_args = lldb.SBStructuredData()
367        stream = lldb.SBStream()
368        stream.Print('{"first_arg" : "first_value", "second_arg" : "second_value"}')
369        extra_args.SetFromJSON(stream)
370        self.assertTrue(extra_args.IsValid(), "SBStructuredData is valid.")
371
372        bkpt = self.orig_target.BreakpointCreateFromScript("resolver.Resolver",
373                                                           extra_args, lldb.SBFileSpecList(), lldb.SBFileSpecList())
374        self.assertTrue(bkpt.IsValid(), "Bkpt is valid")
375        write_bps = lldb.SBBreakpointList(self.orig_target)
376
377        error = self.orig_target.BreakpointsWriteToFile(self.bkpts_file_spec, write_bps)
378        self.assertSuccess(error, "Failed writing breakpoints")
379
380        orig_extra_args = side_effect.g_extra_args
381        self.assertTrue(orig_extra_args.IsValid(), "Extra args originally valid")
382
383        orig_keys = lldb.SBStringList()
384        orig_extra_args.GetKeys(orig_keys)
385        self.assertEqual(2, orig_keys.GetSize(), "Should have two keys")
386
387        side_effect.g_extra_args = None
388
389        copy_bps = lldb.SBBreakpointList(self.copy_target)
390        error = self.copy_target.BreakpointsCreateFromFile(self.bkpts_file_spec, copy_bps)
391        self.assertSuccess(error, "Failed reading breakpoints")
392
393        self.assertEqual(copy_bps.GetSize(), 1, "Got one breakpoint from file.")
394
395        copy_extra_args = side_effect.g_extra_args
396        copy_keys = lldb.SBStringList()
397        copy_extra_args.GetKeys(copy_keys)
398        self.assertEqual(2, copy_keys.GetSize(), "Copy should have two keys")
399
400        for idx in range(0, orig_keys.GetSize()):
401            key = orig_keys.GetStringAtIndex(idx)
402            copy_value = copy_extra_args.GetValueForKey(key).GetStringValue(100)
403
404            if key == "first_arg":
405                self.assertEqual(copy_value, "first_value")
406            elif key == "second_arg":
407                self.assertEqual(copy_value, "second_value")
408            else:
409                self.Fail("Unknown key: %s"%(key))
410