xref: /llvm-project/lldb/test/API/python_api/was_interrupted/TestDebuggerInterruption.py (revision 2238dcc39358353cac21df75c3c3286ab20b8f53)
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