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