1fe61b382SJim Ingham""" 2fe61b382SJim InghamTest SBDebugger.InterruptRequested and SBCommandInterpreter.WasInterrupted. 3fe61b382SJim Ingham""" 4fe61b382SJim Ingham 5fe61b382SJim Inghamimport lldb 6fe61b382SJim Inghamimport lldbsuite.test.lldbutil as lldbutil 7fe61b382SJim Inghamfrom lldbsuite.test.lldbtest import * 8fe61b382SJim Inghamimport threading 9fe61b382SJim Inghamimport os 10fe61b382SJim Ingham 11*2238dcc3SJonas Devlieghere 12fe61b382SJim Inghamclass TestDebuggerInterruption(TestBase): 13fe61b382SJim Ingham """This test runs a command that starts up, rendevous with the test thread 14fe61b382SJim Ingham using threading barriers, then checks whether it has been interrupted. 15fe61b382SJim Ingham 16fe61b382SJim Ingham The command's first argument is either 'interp' or 'debugger', to test 17fe61b382SJim Ingham InterruptRequested and WasInterrupted respectively. 18fe61b382SJim Ingham 19fe61b382SJim Ingham The command has two modes, interrupt and check, the former is the one that 20fe61b382SJim Ingham waits for an interrupt. Then latter just returns whether an interrupt was 21fe61b382SJim Ingham requested. We use the latter to make sure we took down the flag correctly.""" 22fe61b382SJim Ingham 23fe61b382SJim Ingham NO_DEBUG_INFO_TESTCASE = True 24fe61b382SJim Ingham 25fe61b382SJim Ingham class CommandRunner(threading.Thread): 26fe61b382SJim Ingham """This class is for running a command, and for making a thread to run the command on. 27fe61b382SJim Ingham It gets passed the test it is working on behalf of, and most of the important 28fe61b382SJim Ingham objects come from the test.""" 29*2238dcc3SJonas Devlieghere 30fe61b382SJim Ingham def __init__(self, test): 31fe61b382SJim Ingham super().__init__() 32fe61b382SJim Ingham self.test = test 33fe61b382SJim Ingham 34fe61b382SJim Ingham def rendevous(self): 35fe61b382SJim Ingham # We smuggle out barriers and event to the runner thread using thread local data: 36fe61b382SJim Ingham import interruptible 37*2238dcc3SJonas Devlieghere 38*2238dcc3SJonas Devlieghere interruptible.local_data = interruptible.BarrierContainer( 39*2238dcc3SJonas Devlieghere self.test.before_interrupt_barrier, 40fe61b382SJim Ingham self.test.after_interrupt_barrier, 41*2238dcc3SJonas Devlieghere self.test.event, 42*2238dcc3SJonas Devlieghere ) 43fe61b382SJim Ingham 44fe61b382SJim Ingham class DirectCommandRunner(CommandRunner): 45fe61b382SJim Ingham """ "This version runs a single command using HandleCommand.""" 46*2238dcc3SJonas Devlieghere 47fe61b382SJim Ingham def __init__(self, test, command): 48fe61b382SJim Ingham super().__init__(test) 49fe61b382SJim Ingham self.command = command 50fe61b382SJim Ingham 51fe61b382SJim Ingham def run(self): 52fe61b382SJim Ingham self.rendevous() 53*2238dcc3SJonas Devlieghere result = self.test.dbg.GetCommandInterpreter().HandleCommand( 54*2238dcc3SJonas Devlieghere self.command, self.test.result 55*2238dcc3SJonas Devlieghere ) 56fe61b382SJim Ingham if self.test.result_barrier: 57fe61b382SJim Ingham self.test.result_barrier.wait() 58fe61b382SJim Ingham 59fe61b382SJim Ingham class CommandInterpreterRunner(CommandRunner): 60fe61b382SJim Ingham """This version runs the CommandInterpreter and feeds the command to it.""" 61*2238dcc3SJonas Devlieghere 62fe61b382SJim Ingham def __init__(self, test): 63fe61b382SJim Ingham super().__init__(test) 64fe61b382SJim Ingham 65fe61b382SJim Ingham def run(self): 66fe61b382SJim Ingham self.rendevous() 67fe61b382SJim Ingham 68fe61b382SJim Ingham test = self.test 69fe61b382SJim Ingham 70fe61b382SJim Ingham # We will use files for debugger input and output: 71fe61b382SJim Ingham 72fe61b382SJim Ingham # First write down the command: 73fe61b382SJim Ingham with open(test.getBuildArtifact(test.in_filename), "w") as f: 74fe61b382SJim Ingham f.write(f"{test.command}\n") 75fe61b382SJim Ingham 76fe61b382SJim Ingham # Now set the debugger's stdout & stdin to our files, and run 77fe61b382SJim Ingham # the CommandInterpreter: 78*2238dcc3SJonas Devlieghere with open(test.out_filename, "w") as outf, open( 79*2238dcc3SJonas Devlieghere test.in_filename, "r" 80*2238dcc3SJonas Devlieghere ) as inf: 81fe61b382SJim Ingham outsbf = lldb.SBFile(outf.fileno(), "w", False) 82fe61b382SJim Ingham orig_outf = test.dbg.GetOutputFile() 83fe61b382SJim Ingham error = test.dbg.SetOutputFile(outsbf) 84fe61b382SJim Ingham test.assertSuccess(error, "Could not set outfile") 85fe61b382SJim Ingham 86fe61b382SJim Ingham insbf = lldb.SBFile(inf.fileno(), "r", False) 87fe61b382SJim Ingham orig_inf = test.dbg.GetOutputFile() 88fe61b382SJim Ingham error = test.dbg.SetInputFile(insbf) 89fe61b382SJim Ingham test.assertSuccess(error, "Could not set infile") 90fe61b382SJim Ingham 91fe61b382SJim Ingham options = lldb.SBCommandInterpreterRunOptions() 92fe61b382SJim Ingham options.SetPrintResults(True) 93fe61b382SJim Ingham options.SetEchoCommands(False) 94fe61b382SJim Ingham 95fe61b382SJim Ingham test.dbg.RunCommandInterpreter(True, False, options, 0, False, False) 96fe61b382SJim Ingham test.dbg.GetOutputFile().Flush() 97fe61b382SJim Ingham 98fe61b382SJim Ingham error = test.dbg.SetOutputFile(orig_outf) 99fe61b382SJim Ingham test.assertSuccess(error, "Restored outfile") 100fe61b382SJim Ingham test.dbg.SetInputFile(orig_inf) 101fe61b382SJim Ingham test.assertSuccess(error, "Restored infile") 102fe61b382SJim Ingham 103fe61b382SJim Ingham def command_setup(self, args): 104fe61b382SJim Ingham """Insert our command, if needed. Then set up event and barriers if needed. 105fe61b382SJim Ingham Then return the command to run.""" 106fe61b382SJim Ingham 107fe61b382SJim Ingham self.interp = self.dbg.GetCommandInterpreter() 108fe61b382SJim Ingham self.command_name = "interruptible_command" 109fe61b382SJim Ingham self.cmd_result = lldb.SBCommandReturnObject() 110fe61b382SJim Ingham 111fe61b382SJim Ingham if not "check" in args: 112fe61b382SJim Ingham self.event = threading.Event() 113fe61b382SJim Ingham self.result_barrier = threading.Barrier(2, timeout=10) 114fe61b382SJim Ingham self.before_interrupt_barrier = threading.Barrier(2, timeout=10) 115fe61b382SJim Ingham self.after_interrupt_barrier = threading.Barrier(2, timeout=10) 116fe61b382SJim Ingham else: 117fe61b382SJim Ingham self.event = None 118fe61b382SJim Ingham self.result_barrier = None 119fe61b382SJim Ingham self.before_interrupt_barrier = None 120fe61b382SJim Ingham self.after_interrupt_barrier = None 121fe61b382SJim Ingham 122fe61b382SJim Ingham if not self.interp.UserCommandExists(self.command_name): 123fe61b382SJim Ingham # Make the command we're going to use - it spins calling WasInterrupted: 124fe61b382SJim Ingham cmd_filename = "interruptible" 125fe61b382SJim Ingham cmd_filename = os.path.join(self.getSourceDir(), "interruptible.py") 126fe61b382SJim Ingham self.runCmd(f"command script import {cmd_filename}") 127fe61b382SJim Ingham cmd_string = f"command script add {self.command_name} --class interruptible.WelcomeCommand" 128fe61b382SJim Ingham self.runCmd(cmd_string) 129fe61b382SJim Ingham 130fe61b382SJim Ingham if len(args) == 0: 131fe61b382SJim Ingham command = self.command_name 132fe61b382SJim Ingham else: 133fe61b382SJim Ingham command = self.command_name + " " + args 134fe61b382SJim Ingham return command 135fe61b382SJim Ingham 136fe61b382SJim Ingham def run_single_command(self, command): 137fe61b382SJim Ingham # Now start up a thread to run the command: 138fe61b382SJim Ingham self.result.Clear() 139fe61b382SJim Ingham self.runner = TestDebuggerInterruption.DirectCommandRunner(self, command) 140fe61b382SJim Ingham self.runner.start() 141fe61b382SJim Ingham 142fe61b382SJim Ingham def start_command_interp(self): 143fe61b382SJim Ingham self.runner = TestDebuggerInterruption.CommandInterpreterRunner(self) 144fe61b382SJim Ingham self.runner.start() 145fe61b382SJim Ingham 146fe61b382SJim Ingham def check_text(self, result_text, interrupted): 147fe61b382SJim Ingham if interrupted: 148*2238dcc3SJonas Devlieghere self.assertIn( 149*2238dcc3SJonas Devlieghere "Command was interrupted", result_text, "Got the interrupted message" 150*2238dcc3SJonas Devlieghere ) 151fe61b382SJim Ingham else: 152*2238dcc3SJonas Devlieghere self.assertIn( 153*2238dcc3SJonas Devlieghere "Command was not interrupted", 154*2238dcc3SJonas Devlieghere result_text, 155*2238dcc3SJonas Devlieghere "Got the not interrupted message", 156*2238dcc3SJonas Devlieghere ) 157fe61b382SJim Ingham 158fe61b382SJim Ingham def gather_output(self): 159fe61b382SJim Ingham # Now wait for the interrupt to interrupt the command: 160fe61b382SJim Ingham self.runner.join(10.0) 161fe61b382SJim Ingham finished = not self.runner.is_alive() 162fe61b382SJim Ingham # Don't leave the runner thread stranded if the interrupt didn't work. 163fe61b382SJim Ingham if not finished: 164fe61b382SJim Ingham self.event.set() 165fe61b382SJim Ingham self.runner.join(10.0) 166fe61b382SJim Ingham 167fe61b382SJim Ingham self.assertTrue(finished, "We did finish the command") 168fe61b382SJim Ingham 169fe61b382SJim Ingham def check_result(self, interrupted=True): 170fe61b382SJim Ingham self.gather_output() 171fe61b382SJim Ingham self.check_text(self.result.GetOutput(), interrupted) 172fe61b382SJim Ingham 173fe61b382SJim Ingham def check_result_output(self, interrupted=True): 174fe61b382SJim Ingham self.gather_output() 175fe61b382SJim Ingham buffer = "" 176fe61b382SJim Ingham # Okay, now open the file for reading, and read. 177fe61b382SJim Ingham with open(self.out_filename, "r") as f: 178fe61b382SJim Ingham buffer = f.read() 179fe61b382SJim Ingham 180fe61b382SJim Ingham self.assertNotEqual(len(buffer), 0, "No command data") 181fe61b382SJim Ingham self.check_text(buffer, interrupted) 182fe61b382SJim Ingham 183fe61b382SJim Ingham def debugger_interrupt_test(self, use_interrupt_requested): 184fe61b382SJim Ingham """Test that debugger interruption interrupts a command 185fe61b382SJim Ingham running directly through HandleCommand. 186fe61b382SJim Ingham If use_interrupt_requested is true, we'll check that API, 187fe61b382SJim Ingham otherwise we'll check WasInterrupted. They should both do 188fe61b382SJim Ingham the same thing.""" 189fe61b382SJim Ingham 190fe61b382SJim Ingham if use_interrupt_requested: 191fe61b382SJim Ingham command = self.command_setup("debugger") 192fe61b382SJim Ingham else: 193fe61b382SJim Ingham command = self.command_setup("interp") 194fe61b382SJim Ingham 195fe61b382SJim Ingham self.result = lldb.SBCommandReturnObject() 196fe61b382SJim Ingham self.run_single_command(command) 197fe61b382SJim Ingham 198fe61b382SJim Ingham # Okay now wait till the command has gotten started to issue the interrupt: 199fe61b382SJim Ingham self.before_interrupt_barrier.wait() 200fe61b382SJim Ingham # I'm going to do it twice here to test that it works as a counter: 201fe61b382SJim Ingham self.dbg.RequestInterrupt() 202fe61b382SJim Ingham self.dbg.RequestInterrupt() 203fe61b382SJim Ingham 204fe61b382SJim Ingham def cleanup(): 205fe61b382SJim Ingham self.dbg.CancelInterruptRequest() 206*2238dcc3SJonas Devlieghere 207fe61b382SJim Ingham self.addTearDownHook(cleanup) 208fe61b382SJim Ingham # Okay, now set both sides going: 209fe61b382SJim Ingham self.after_interrupt_barrier.wait() 210fe61b382SJim Ingham 211fe61b382SJim Ingham # Check that the command was indeed interrupted. First rendevous 212fe61b382SJim Ingham # after the runner thread had a chance to execute the command: 213fe61b382SJim Ingham self.result_barrier.wait() 214fe61b382SJim Ingham self.assertTrue(self.result.Succeeded(), "Our command succeeded") 215fe61b382SJim Ingham result_output = self.result.GetOutput() 216fe61b382SJim Ingham self.check_result(True) 217fe61b382SJim Ingham 218fe61b382SJim Ingham # Do it again to make sure that the counter is counting: 219fe61b382SJim Ingham self.dbg.CancelInterruptRequest() 220fe61b382SJim Ingham command = self.command_setup("debugger") 221fe61b382SJim Ingham self.run_single_command(command) 222fe61b382SJim Ingham 223fe61b382SJim Ingham # This time we won't even get to run the command, since HandleCommand 224fe61b382SJim Ingham # checks for the interrupt state on entry, so we don't wait on the command 225fe61b382SJim Ingham # barriers. 226fe61b382SJim Ingham self.result_barrier.wait() 227fe61b382SJim Ingham 228fe61b382SJim Ingham # Again check that we were 229fe61b382SJim Ingham self.assertFalse(self.result.Succeeded(), "Our command was not allowed to run") 230fe61b382SJim Ingham error_output = self.result.GetError() 231*2238dcc3SJonas Devlieghere self.assertIn( 232*2238dcc3SJonas Devlieghere "... Interrupted", error_output, "Command was cut short by interrupt" 233*2238dcc3SJonas Devlieghere ) 234fe61b382SJim Ingham 235fe61b382SJim Ingham # Now take down the flag, and make sure that we aren't interrupted: 236fe61b382SJim Ingham self.dbg.CancelInterruptRequest() 237fe61b382SJim Ingham 238fe61b382SJim Ingham # Now make sure that we really did take down the flag: 239fe61b382SJim Ingham command = self.command_setup("debugger check") 240fe61b382SJim Ingham self.run_single_command(command) 241fe61b382SJim Ingham result_output = self.result.GetOutput() 242fe61b382SJim Ingham self.check_result(False) 243fe61b382SJim Ingham 244fe61b382SJim Ingham def test_debugger_interrupt_use_dbg(self): 245fe61b382SJim Ingham self.debugger_interrupt_test(True) 246fe61b382SJim Ingham 247fe61b382SJim Ingham def test_debugger_interrupt_use_interp(self): 248fe61b382SJim Ingham self.debugger_interrupt_test(False) 249fe61b382SJim Ingham 250fe61b382SJim Ingham def test_interp_doesnt_interrupt_debugger(self): 251fe61b382SJim Ingham """Test that interpreter interruption does not interrupt a command 252fe61b382SJim Ingham running directly through HandleCommand. 253fe61b382SJim Ingham If use_interrupt_requested is true, we'll check that API, 254fe61b382SJim Ingham otherwise we'll check WasInterrupted. They should both do 255fe61b382SJim Ingham the same thing.""" 256fe61b382SJim Ingham 257fe61b382SJim Ingham command = self.command_setup("debugger poll") 258fe61b382SJim Ingham 259fe61b382SJim Ingham self.result = lldb.SBCommandReturnObject() 260fe61b382SJim Ingham self.run_single_command(command) 261fe61b382SJim Ingham 262fe61b382SJim Ingham # Now raise the debugger interrupt flag. It will also interrupt the command: 263fe61b382SJim Ingham self.before_interrupt_barrier.wait() 264fe61b382SJim Ingham self.dbg.GetCommandInterpreter().InterruptCommand() 265fe61b382SJim Ingham self.after_interrupt_barrier.wait() 266fe61b382SJim Ingham 267fe61b382SJim Ingham # Check that the command was indeed interrupted: 268fe61b382SJim Ingham self.result_barrier.wait() 269fe61b382SJim Ingham self.assertTrue(self.result.Succeeded(), "Our command succeeded") 270fe61b382SJim Ingham result_output = self.result.GetOutput() 271fe61b382SJim Ingham self.check_result(False) 272fe61b382SJim Ingham 273fe61b382SJim Ingham def interruptible_command_test(self, use_interrupt_requested): 274fe61b382SJim Ingham """Test that interpreter interruption interrupts a command 275fe61b382SJim Ingham running in the RunCommandInterpreter loop. 276fe61b382SJim Ingham If use_interrupt_requested is true, we'll check that API, 277fe61b382SJim Ingham otherwise we'll check WasInterrupted. They should both do 278fe61b382SJim Ingham the same thing.""" 279fe61b382SJim Ingham 280fe61b382SJim Ingham self.out_filename = self.getBuildArtifact("output") 281fe61b382SJim Ingham self.in_filename = self.getBuildArtifact("input") 282fe61b382SJim Ingham # We're going to overwrite the input file, but we 283fe61b382SJim Ingham # don't want data accumulating in the output file. 284fe61b382SJim Ingham 285fe61b382SJim Ingham if os.path.exists(self.out_filename): 286fe61b382SJim Ingham os.unlink(self.out_filename) 287fe61b382SJim Ingham 288fe61b382SJim Ingham # You should be able to use either check method interchangeably: 289fe61b382SJim Ingham if use_interrupt_requested: 290fe61b382SJim Ingham self.command = self.command_setup("debugger") + "\n" 291fe61b382SJim Ingham else: 292fe61b382SJim Ingham self.command = self.command_setup("interp") + "\n" 293fe61b382SJim Ingham 294fe61b382SJim Ingham self.start_command_interp() 295fe61b382SJim Ingham 296fe61b382SJim Ingham # Now give the interpreter a chance to run this command up 297fe61b382SJim Ingham # to the first barrier 298fe61b382SJim Ingham self.before_interrupt_barrier.wait() 299fe61b382SJim Ingham # Then issue the interrupt: 300fe61b382SJim Ingham sent_interrupt = self.dbg.GetCommandInterpreter().InterruptCommand() 301fe61b382SJim Ingham self.assertTrue(sent_interrupt, "Did send command interrupt.") 302fe61b382SJim Ingham # Now give the command a chance to finish: 303fe61b382SJim Ingham self.after_interrupt_barrier.wait() 304fe61b382SJim Ingham 305fe61b382SJim Ingham self.check_result_output(True) 306fe61b382SJim Ingham 307fe61b382SJim Ingham os.unlink(self.out_filename) 308fe61b382SJim Ingham 309fe61b382SJim Ingham # Now send the check command, and make sure the flag is now down. 310fe61b382SJim Ingham self.command = self.command_setup("interp check") + "\n" 311fe61b382SJim Ingham self.start_command_interp() 312fe61b382SJim Ingham 313fe61b382SJim Ingham self.check_result_output(False) 314fe61b382SJim Ingham 315fe61b382SJim Ingham def test_interruptible_command_check_dbg(self): 316fe61b382SJim Ingham self.interruptible_command_test(True) 317fe61b382SJim Ingham 318fe61b382SJim Ingham def test_interruptible_command_check_interp(self): 319fe61b382SJim Ingham self.interruptible_command_test(False) 320fe61b382SJim Ingham 321fe61b382SJim Ingham def test_debugger_doesnt_interrupt_command(self): 322fe61b382SJim Ingham """Test that debugger interruption doesn't interrupt a command 323fe61b382SJim Ingham running in the RunCommandInterpreter loop.""" 324fe61b382SJim Ingham 325fe61b382SJim Ingham self.out_filename = self.getBuildArtifact("output") 326fe61b382SJim Ingham self.in_filename = self.getBuildArtifact("input") 327fe61b382SJim Ingham # We're going to overwrite the input file, but we 328fe61b382SJim Ingham # don't want data accumulating in the output file. 329fe61b382SJim Ingham 330fe61b382SJim Ingham if os.path.exists(self.out_filename): 331fe61b382SJim Ingham os.unlink(self.out_filename) 332fe61b382SJim Ingham 333fe61b382SJim Ingham self.command = self.command_setup("interp poll") + "\n" 334fe61b382SJim Ingham 335fe61b382SJim Ingham self.start_command_interp() 336fe61b382SJim Ingham 337fe61b382SJim Ingham self.before_interrupt_barrier.wait() 338fe61b382SJim Ingham self.dbg.RequestInterrupt() 339*2238dcc3SJonas Devlieghere 340fe61b382SJim Ingham def cleanup(): 341fe61b382SJim Ingham self.dbg.CancelInterruptRequest() 342*2238dcc3SJonas Devlieghere 343fe61b382SJim Ingham self.addTearDownHook(cleanup) 344fe61b382SJim Ingham self.after_interrupt_barrier.wait() 345fe61b382SJim Ingham 346fe61b382SJim Ingham self.check_result_output(False) 347fe61b382SJim Ingham 348fe61b382SJim Ingham os.unlink(self.out_filename) 349