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