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