xref: /llvm-project/lldb/test/API/functionalities/thread_plan/TestThreadPlanCommands.py (revision 9c2468821ec51defd09c246fea4a47886fff8c01)
1"""
2Test that thread plan listing, and deleting works.
3"""
4
5
6import lldb
7from lldbsuite.test.decorators import *
8import lldbsuite.test.lldbutil as lldbutil
9from lldbsuite.test.lldbtest import *
10
11
12class TestThreadPlanCommands(TestBase):
13    NO_DEBUG_INFO_TESTCASE = True
14
15    @skipIfWindows
16    def test_thread_plan_actions(self):
17        self.build()
18        self.main_source_file = lldb.SBFileSpec("main.c")
19        self.thread_plan_test()
20
21    def check_list_output(
22        self, command, active_plans=[], completed_plans=[], discarded_plans=[]
23    ):
24        # Check the "thread plan list" output against a list of active & completed and discarded plans.
25        # If all three check arrays are empty, that means the command is expected to fail.
26
27        interp = self.dbg.GetCommandInterpreter()
28        result = lldb.SBCommandReturnObject()
29
30        num_active = len(active_plans)
31        num_completed = len(completed_plans)
32        num_discarded = len(discarded_plans)
33
34        interp.HandleCommand(command, result)
35        print("Command: %s" % (command))
36        print(result.GetOutput())
37
38        if num_active == 0 and num_completed == 0 and num_discarded == 0:
39            self.assertFalse(
40                result.Succeeded(),
41                "command: '%s' succeeded when it should have failed: '%s'"
42                % (command, result.GetError()),
43            )
44            return
45
46        self.assertTrue(
47            result.Succeeded(),
48            "command: '%s' failed: '%s'" % (command, result.GetError()),
49        )
50        result_arr = result.GetOutput().splitlines()
51        num_results = len(result_arr)
52
53        # Now iterate through the results array and pick out the results.
54        result_idx = 0
55        self.assertIn("thread #", result_arr[result_idx], "Found thread header")
56        result_idx += 1
57        self.assertIn(
58            "Active plan stack", result_arr[result_idx], "Found active header"
59        )
60        result_idx += 1
61        self.assertIn(
62            "Element 0: Base thread plan", result_arr[result_idx], "Found base plan"
63        )
64        result_idx += 1
65
66        for text in active_plans:
67            self.assertIn(
68                text, result_arr[result_idx], "Didn't find active plan: %s" % (text)
69            )
70            result_idx += 1
71
72        if len(completed_plans) > 0:
73            # First consume any remaining active plans:
74            while not "Completed plan stack:" in result_arr[result_idx]:
75                result_idx += 1
76                if result_idx == num_results:
77                    self.fail(
78                        "There should have been completed plans, but I never saw the completed stack header"
79                    )
80            # We are at the Completed header, skip it:
81            result_idx += 1
82            for text in completed_plans:
83                self.assertIn(
84                    text,
85                    result_arr[result_idx],
86                    "Didn't find completed plan: %s" % (text),
87                )
88                result_idx += 1
89
90        if len(discarded_plans) > 0:
91            # First consume any remaining completed plans:
92            while not "Discarded plan stack:" in result_arr[result_idx]:
93                result_idx += 1
94                if result_idx == num_results:
95                    self.fail(
96                        "There should have been discarded plans, but I never saw the discarded stack header"
97                    )
98
99            # We are at the Discarded header, skip it:
100            result_idx += 1
101            for text in discarded_plans:
102                self.assertIn(
103                    text,
104                    result_arr[result_idx],
105                    "Didn't find discarded plan: %s" % (text),
106                )
107                result_idx += 1
108
109    def thread_plan_test(self):
110        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
111            self, "Set a breakpoint here", self.main_source_file
112        )
113
114        # We need to have an internal plan so we can test listing one.
115        # The most consistent way to do that is to use a scripted thread plan
116        # that uses a sub-plan.  Source that in now.
117        source_path = os.path.join(self.getSourceDir(), "wrap_step_over.py")
118        self.runCmd("command script import '%s'" % (source_path))
119
120        # Now set a breakpoint that we will hit by running our scripted step.
121        call_me_bkpt = target.BreakpointCreateBySourceRegex(
122            "Set another here", self.main_source_file
123        )
124        self.assertGreater(
125            call_me_bkpt.GetNumLocations(), 0, "Set the breakpoint successfully"
126        )
127        thread.StepUsingScriptedThreadPlan("wrap_step_over.WrapStepOver")
128        threads = lldbutil.get_threads_stopped_at_breakpoint(process, call_me_bkpt)
129        self.assertEqual(len(threads), 1, "Hit my breakpoint while stepping over")
130
131        current_id = threads[0].GetIndexID()
132        current_tid = threads[0].GetThreadID()
133        # Run thread plan list without the -i flag:
134        command = "thread plan list %d" % (current_id)
135        self.check_list_output(command, ["wrap_step_over.WrapStepOver"], [])
136
137        # Run thread plan list with the -i flag:
138        command = "thread plan list -i %d" % (current_id)
139        self.check_list_output(command, ["WrapStepOver", "Stepping over line main.c"])
140
141        # Run thread plan list providing TID, output should be the same:
142        command = "thread plan list -t %d" % (current_tid)
143        self.check_list_output(command, ["wrap_step_over.WrapStepOver"])
144
145        # Provide both index & tid, and make sure we only print once:
146        command = "thread plan list -t %d %d" % (current_tid, current_id)
147        self.check_list_output(command, ["wrap_step_over.WrapStepOver"])
148
149        # Try a fake TID, and make sure that fails:
150        fake_tid = 0
151        for i in range(100, 10000, 100):
152            fake_tid = current_tid + i
153            thread = process.GetThreadByID(fake_tid)
154            if not thread:
155                break
156
157        command = "thread plan list -t %d" % (fake_tid)
158        self.check_list_output(command)
159
160        # Now continue, and make sure we printed the completed plan:
161        process.Continue()
162        threads = lldbutil.get_stopped_threads(process, lldb.eStopReasonPlanComplete)
163        self.assertEqual(len(threads), 1, "One thread completed a step")
164
165        # Run thread plan list - there aren't any private plans at this point:
166        command = "thread plan list %d" % (current_id)
167        self.check_list_output(command, [], ["wrap_step_over.WrapStepOver"])
168
169        # Set another breakpoint that we can run to, to try deleting thread plans.
170        second_step_bkpt = target.BreakpointCreateBySourceRegex(
171            "Run here to step over again", self.main_source_file
172        )
173        self.assertGreater(
174            second_step_bkpt.GetNumLocations(), 0, "Set the breakpoint successfully"
175        )
176        final_bkpt = target.BreakpointCreateBySourceRegex(
177            "Make sure we get here on last continue", self.main_source_file
178        )
179        self.assertGreater(
180            final_bkpt.GetNumLocations(), 0, "Set the breakpoint successfully"
181        )
182
183        threads = lldbutil.continue_to_breakpoint(process, second_step_bkpt)
184        self.assertEqual(len(threads), 1, "Hit the second step breakpoint")
185
186        threads[0].StepOver()
187        threads = lldbutil.get_threads_stopped_at_breakpoint(process, call_me_bkpt)
188
189        result = lldb.SBCommandReturnObject()
190        interp = self.dbg.GetCommandInterpreter()
191        interp.HandleCommand("thread plan discard 1", result)
192        self.assertTrue(
193            result.Succeeded(), "Deleted the step over plan: %s" % (result.GetOutput())
194        )
195
196        # Make sure the plan gets listed in the discarded plans:
197        command = "thread plan list %d" % (current_id)
198        self.check_list_output(command, [], [], ["Stepping over line main.c:"])
199
200        process.Continue()
201        threads = lldbutil.get_threads_stopped_at_breakpoint(process, final_bkpt)
202        self.assertEqual(len(threads), 1, "Ran to final breakpoint")
203        threads = lldbutil.get_stopped_threads(process, lldb.eStopReasonPlanComplete)
204        self.assertEqual(len(threads), 0, "Did NOT complete the step over plan")
205