2 A stress-test of sorts for LLDB's handling of threads in the inferior.
4 This test sets a breakpoint in the main thread where test parameters (numbers of
5 threads) can be adjusted, runs the inferior to that point, and modifies the
6 locals that control the event thread counts. This test also sets a breakpoint in
7 breakpoint_func (the function executed by each 'breakpoint' thread) and a
8 watchpoint on a global modified in watchpoint_func. The inferior is continued
9 until exit or a crash takes place, and the number of events seen by LLDB is
10 verified to match the expected number of events.
17 from lldbsuite
.test
.decorators
import *
18 from lldbsuite
.test
.lldbtest
import *
19 from lldbsuite
.test
import lldbutil
22 class ConcurrentEventsBase(TestBase
):
24 # Concurrency is the primary test factor here, not debug info variants.
25 NO_DEBUG_INFO_TESTCASE
= True
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')
39 def describe_threads(self
):
41 for x
in self
.inferior_process
:
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
62 location
= "\t".join([lldbutil
.get_description(
63 x
.GetFrameAtIndex(i
)) for i
in range(x
.GetNumFrames())])
65 "thread %d %s due to %s at\n\t%s" %
66 (id, status
, reason_str
, location
))
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.
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
)
78 ": file = 'main.cpp', line = %d" %
79 self
.finish_breakpoint_line
)
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.
86 return self
.finish_breakpoint
.GetHitCount() > 0 or \
87 self
.crash_count
> 0 or \
88 self
.inferior_process
.GetState() == lldb
.eStateExited
90 def count_signaled_threads(self
):
92 for thread
in self
.inferior_process
:
93 if thread
.GetStopReason() == lldb
.eStopReasonSignal
and thread
.GetStopReasonDataAtIndex(
94 0) == self
.inferior_process
.GetUnixSignals().GetSignalNumberFromName('SIGUSR1'):
98 def do_thread_actions(self
,
99 num_breakpoint_threads
=0,
100 num_signal_threads
=0,
101 num_watchpoint_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.
113 exe
= self
.getBuildArtifact("a.out")
114 self
.runCmd("file " + exe
, CURRENT_EXECUTABLE_SET
)
117 self
.inferior_target
= self
.dbg
.GetSelectedTarget()
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
)
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
)
134 "breakpoint list -f",
135 "Breakpoint locations shown correctly",
136 substrs
=expected_bps
)
139 self
.runCmd("run", RUN_SUCCEEDED
)
141 # Check we are at line self.setup_breakpoint
142 self
.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT
,
143 substrs
=["stop reason = breakpoint 1."])
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
152 self
.thread_watchpoint
),
153 "Watchpoint location not shown correctly")
156 self
.inferior_process
= self
.inferior_target
.GetProcess()
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)
161 self
.inferior_process
.GetNumThreads(),
163 'Expected to stop before any additional threads are spawned.')
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
)
171 "expr num_delay_breakpoint_threads=%d" %
172 num_delay_breakpoint_threads
)
174 "expr num_delay_crash_threads=%d" %
175 num_delay_crash_threads
)
177 "expr num_delay_signal_threads=%d" %
178 num_delay_signal_threads
)
180 "expr num_delay_watchpoint_threads=%d" %
181 num_delay_watchpoint_threads
)
183 # Continue the inferior so threads are spawned
184 self
.runCmd("continue")
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
196 expected_num_threads
,
197 'Expected to see %d threads, but seeing %d. Details:\n%s' %
198 (expected_num_threads
,
201 self
.describe_threads())))
203 self
.signal_count
= self
.count_signaled_threads()
204 self
.crash_count
= len(
205 lldbutil
.get_crashed_threads(
206 self
, self
.inferior_process
))
208 # Run to completion (or crash)
209 while not self
.inferior_done():
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
))
218 if num_crash_threads
> 0 or num_delay_crash_threads
> 0:
221 self
.crash_count
> 0,
222 "Expecting at least one thread to crash. Details: %s" %
224 self
.describe_threads()))
226 # Ensure the zombie process is reaped
227 self
.runCmd("process kill")
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
234 self
.finish_breakpoint
.GetHitCount(),
235 "Expected main thread (finish) breakpoint to be hit once")
237 num_threads
= self
.inferior_process
.GetNumThreads()
241 "Expecting 1 thread but seeing %d. Details:%s" %
244 self
.describe_threads())))
245 self
.runCmd("continue")
247 # The inferior process should have exited without crashing
251 "Unexpected thread(s) in crashed state")
253 self
.inferior_process
.GetState(),
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
262 expected_breakpoint_threads
,
263 breakpoint_hit_count
,
264 "Expected %d breakpoint hits, but got %d" %
265 (expected_breakpoint_threads
,
266 breakpoint_hit_count
))
268 expected_signal_threads
= num_delay_signal_threads
+ num_signal_threads
270 expected_signal_threads
,
272 "Expected %d stops due to signal delivery, but got %d" %
273 (expected_signal_threads
,
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
280 expected_watchpoint_threads
,
281 watchpoint_hit_count
,
282 "Expected %d watchpoint hits, got %d" %
283 (expected_watchpoint_threads
,
284 watchpoint_hit_count
))