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