xref: /llvm-project/lldb/packages/Python/lldbsuite/test/concurrent_base.py (revision de872a697a9e41b0ee6ddece18dc33ff0d3f721c)
1"""
2A stress-test of sorts for LLDB's handling of threads in the inferior.
3
4This test sets a breakpoint in the main thread where test parameters (numbers of
5threads) can be adjusted, runs the inferior to that point, and modifies the
6locals that control the event thread counts. This test also sets a breakpoint in
7breakpoint_func (the function executed by each 'breakpoint' thread) and a
8watchpoint on a global modified in watchpoint_func. The inferior is continued
9until exit or a crash takes place, and the number of events seen by LLDB is
10verified to match the expected number of events.
11"""
12
13from __future__ import print_function
14
15
16import unittest2
17import os
18import time
19import lldb
20from lldbsuite.test.decorators import *
21from lldbsuite.test.lldbtest import *
22from lldbsuite.test import lldbutil
23
24
25class ConcurrentEventsBase(TestBase):
26
27    # Concurrency is the primary test factor here, not debug info variants.
28    NO_DEBUG_INFO_TESTCASE = True
29
30    def setUp(self):
31        # Call super's setUp().
32        super(ConcurrentEventsBase, self).setUp()
33        # Find the line number for our breakpoint.
34        self.filename = 'main.cpp'
35        self.thread_breakpoint_line = line_number(
36            self.filename, '// Set breakpoint here')
37        self.setup_breakpoint_line = line_number(
38            self.filename, '// Break here and adjust num')
39        self.finish_breakpoint_line = line_number(
40            self.filename, '// Break here and verify one thread is active')
41
42    def describe_threads(self):
43        ret = []
44        for x in self.inferior_process:
45            id = x.GetIndexID()
46            reason = x.GetStopReason()
47            status = "stopped" if x.IsStopped() else "running"
48            reason_str = lldbutil.stop_reason_to_str(reason)
49            if reason == lldb.eStopReasonBreakpoint:
50                bpid = x.GetStopReasonDataAtIndex(0)
51                bp = self.inferior_target.FindBreakpointByID(bpid)
52                reason_str = "%s hit %d times" % (
53                    lldbutil.get_description(bp), bp.GetHitCount())
54            elif reason == lldb.eStopReasonWatchpoint:
55                watchid = x.GetStopReasonDataAtIndex(0)
56                watch = self.inferior_target.FindWatchpointByID(watchid)
57                reason_str = "%s hit %d times" % (
58                    lldbutil.get_description(watch), watch.GetHitCount())
59            elif reason == lldb.eStopReasonSignal:
60                signals = self.inferior_process.GetUnixSignals()
61                signal_name = signals.GetSignalAsCString(
62                    x.GetStopReasonDataAtIndex(0))
63                reason_str = "signal %s" % signal_name
64
65            location = "\t".join([lldbutil.get_description(
66                x.GetFrameAtIndex(i)) for i in range(x.GetNumFrames())])
67            ret.append(
68                "thread %d %s due to %s at\n\t%s" %
69                (id, status, reason_str, location))
70        return ret
71
72    def add_breakpoint(self, line, descriptions):
73        """ Adds a breakpoint at self.filename:line and appends its description to descriptions, and
74            returns the LLDB SBBreakpoint object.
75        """
76
77        bpno = lldbutil.run_break_set_by_file_and_line(
78            self, self.filename, line, num_expected_locations=-1)
79        bp = self.inferior_target.FindBreakpointByID(bpno)
80        descriptions.append(
81            ": file = 'main.cpp', line = %d" %
82            self.finish_breakpoint_line)
83        return bp
84
85    def inferior_done(self):
86        """ Returns true if the inferior is done executing all the event threads (and is stopped at self.finish_breakpoint,
87            or has terminated execution.
88        """
89        return self.finish_breakpoint.GetHitCount() > 0 or \
90            self.crash_count > 0 or \
91            self.inferior_process.GetState() == lldb.eStateExited
92
93    def count_signaled_threads(self):
94        count = 0
95        for thread in self.inferior_process:
96            if thread.GetStopReason() == lldb.eStopReasonSignal and thread.GetStopReasonDataAtIndex(
97                    0) == self.inferior_process.GetUnixSignals().GetSignalNumberFromName('SIGUSR1'):
98                count += 1
99        return count
100
101    def do_thread_actions(self,
102                          num_breakpoint_threads=0,
103                          num_signal_threads=0,
104                          num_watchpoint_threads=0,
105                          num_crash_threads=0,
106                          num_delay_breakpoint_threads=0,
107                          num_delay_signal_threads=0,
108                          num_delay_watchpoint_threads=0,
109                          num_delay_crash_threads=0):
110        """ Sets a breakpoint in the main thread where test parameters (numbers of threads) can be adjusted, runs the inferior
111            to that point, and modifies the locals that control the event thread counts. Also sets a breakpoint in
112            breakpoint_func (the function executed by each 'breakpoint' thread) and a watchpoint on a global modified in
113            watchpoint_func. The inferior is continued until exit or a crash takes place, and the number of events seen by LLDB
114            is verified to match the expected number of events.
115        """
116        exe = self.getBuildArtifact("a.out")
117        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
118
119        # Get the target
120        self.inferior_target = self.dbg.GetSelectedTarget()
121
122        expected_bps = []
123
124        # Initialize all the breakpoints (main thread/aux thread)
125        self.setup_breakpoint = self.add_breakpoint(
126            self.setup_breakpoint_line, expected_bps)
127        self.finish_breakpoint = self.add_breakpoint(
128            self.finish_breakpoint_line, expected_bps)
129
130        # Set the thread breakpoint
131        if num_breakpoint_threads + num_delay_breakpoint_threads > 0:
132            self.thread_breakpoint = self.add_breakpoint(
133                self.thread_breakpoint_line, expected_bps)
134
135        # Verify breakpoints
136        self.expect(
137            "breakpoint list -f",
138            "Breakpoint locations shown correctly",
139            substrs=expected_bps)
140
141        # Run the program.
142        self.runCmd("run", RUN_SUCCEEDED)
143
144        # Check we are at line self.setup_breakpoint
145        self.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT,
146                    substrs=["stop reason = breakpoint 1."])
147
148        # Initialize the (single) watchpoint on the global variable (g_watchme)
149        if num_watchpoint_threads + num_delay_watchpoint_threads > 0:
150            self.runCmd("watchpoint set variable g_watchme")
151            for w in self.inferior_target.watchpoint_iter():
152                self.thread_watchpoint = w
153                self.assertTrue(
154                    "g_watchme" in str(
155                        self.thread_watchpoint),
156                    "Watchpoint location not shown correctly")
157
158        # Get the process
159        self.inferior_process = self.inferior_target.GetProcess()
160
161        # We should be stopped at the setup site where we can set the number of
162        # threads doing each action (break/crash/signal/watch)
163        self.assertEqual(
164            self.inferior_process.GetNumThreads(),
165            1,
166            'Expected to stop before any additional threads are spawned.')
167
168        self.runCmd("expr num_breakpoint_threads=%d" % num_breakpoint_threads)
169        self.runCmd("expr num_crash_threads=%d" % num_crash_threads)
170        self.runCmd("expr num_signal_threads=%d" % num_signal_threads)
171        self.runCmd("expr num_watchpoint_threads=%d" % num_watchpoint_threads)
172
173        self.runCmd(
174            "expr num_delay_breakpoint_threads=%d" %
175            num_delay_breakpoint_threads)
176        self.runCmd(
177            "expr num_delay_crash_threads=%d" %
178            num_delay_crash_threads)
179        self.runCmd(
180            "expr num_delay_signal_threads=%d" %
181            num_delay_signal_threads)
182        self.runCmd(
183            "expr num_delay_watchpoint_threads=%d" %
184            num_delay_watchpoint_threads)
185
186        # Continue the inferior so threads are spawned
187        self.runCmd("continue")
188
189        # Make sure we see all the threads. The inferior program's threads all synchronize with a pseudo-barrier; that is,
190        # the inferior program ensures all threads are started and running
191        # before any thread triggers its 'event'.
192        num_threads = self.inferior_process.GetNumThreads()
193        expected_num_threads = num_breakpoint_threads + num_delay_breakpoint_threads \
194            + num_signal_threads + num_delay_signal_threads \
195            + num_watchpoint_threads + num_delay_watchpoint_threads \
196            + num_crash_threads + num_delay_crash_threads + 1
197        self.assertEqual(
198            num_threads,
199            expected_num_threads,
200            'Expected to see %d threads, but seeing %d. Details:\n%s' %
201            (expected_num_threads,
202             num_threads,
203             "\n\t".join(
204                 self.describe_threads())))
205
206        self.signal_count = self.count_signaled_threads()
207        self.crash_count = len(
208            lldbutil.get_crashed_threads(
209                self, self.inferior_process))
210
211        # Run to completion (or crash)
212        while not self.inferior_done():
213            if self.TraceOn():
214                self.runCmd("thread backtrace all")
215            self.runCmd("continue")
216            self.signal_count += self.count_signaled_threads()
217            self.crash_count += len(
218                lldbutil.get_crashed_threads(
219                    self, self.inferior_process))
220
221        if num_crash_threads > 0 or num_delay_crash_threads > 0:
222            # Expecting a crash
223            self.assertTrue(
224                self.crash_count > 0,
225                "Expecting at least one thread to crash. Details: %s" %
226                "\t\n".join(
227                    self.describe_threads()))
228
229            # Ensure the zombie process is reaped
230            self.runCmd("process kill")
231
232        elif num_crash_threads == 0 and num_delay_crash_threads == 0:
233            # There should be a single active thread (the main one) which hit
234            # the breakpoint after joining
235            self.assertEqual(
236                1,
237                self.finish_breakpoint.GetHitCount(),
238                "Expected main thread (finish) breakpoint to be hit once")
239
240            num_threads = self.inferior_process.GetNumThreads()
241            self.assertEqual(
242                1,
243                num_threads,
244                "Expecting 1 thread but seeing %d. Details:%s" %
245                (num_threads,
246                 "\n\t".join(
247                     self.describe_threads())))
248            self.runCmd("continue")
249
250            # The inferior process should have exited without crashing
251            self.assertEqual(
252                0,
253                self.crash_count,
254                "Unexpected thread(s) in crashed state")
255            self.assertEqual(
256                self.inferior_process.GetState(),
257                lldb.eStateExited,
258                PROCESS_EXITED)
259
260            # Verify the number of actions took place matches expected numbers
261            expected_breakpoint_threads = num_delay_breakpoint_threads + num_breakpoint_threads
262            breakpoint_hit_count = self.thread_breakpoint.GetHitCount(
263            ) if expected_breakpoint_threads > 0 else 0
264            self.assertEqual(
265                expected_breakpoint_threads,
266                breakpoint_hit_count,
267                "Expected %d breakpoint hits, but got %d" %
268                (expected_breakpoint_threads,
269                 breakpoint_hit_count))
270
271            expected_signal_threads = num_delay_signal_threads + num_signal_threads
272            self.assertEqual(
273                expected_signal_threads,
274                self.signal_count,
275                "Expected %d stops due to signal delivery, but got %d" %
276                (expected_signal_threads,
277                 self.signal_count))
278
279            expected_watchpoint_threads = num_delay_watchpoint_threads + num_watchpoint_threads
280            watchpoint_hit_count = self.thread_watchpoint.GetHitCount(
281            ) if expected_watchpoint_threads > 0 else 0
282            self.assertEqual(
283                expected_watchpoint_threads,
284                watchpoint_hit_count,
285                "Expected %d watchpoint hits, got %d" %
286                (expected_watchpoint_threads,
287                 watchpoint_hit_count))
288