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