Improve TestConcurrentEvents.py

- code cleanup, improved reporting when failures take place
- ensure known thread is interrupted by using pthread_kill() instead of kill() in the signal worker thread
- above should avoid llvm.org/pr16567 on Mac OS X (though kill() could still cause threads to pop out of existance temporarily)
- added an additional check that all threads have exited after worker threads are all join()ed
- logged llvm.org/pr16603 for the new Linux bug discovered

llvm-svn: 186124
This commit is contained in:
Daniel Malea 2013-07-11 22:14:47 +00:00
parent 1c7fcccb49
commit 7df860a629
2 changed files with 124 additions and 84 deletions

View File

@ -10,12 +10,19 @@ until exit or a crash takes place, and the number of events seen by LLDB is
verified to match the expected number of events.
"""
import os, time
import os, signal, time
import unittest2
import lldb
from lldbtest import *
import lldbutil
# ==================================================
# Dictionary of signal names
# ==================================================
signal_names = dict((getattr(signal, n), n) \
for n in dir(signal) if n.startswith('SIG') and '_' not in n )
class ConcurrentEventsTestCase(TestBase):
mydir = os.path.join("functionalities", "thread", "concurrent_events")
@ -321,27 +328,54 @@ class ConcurrentEventsTestCase(TestBase):
num_breakpoint_threads=1,
num_signal_threads=1)
def setUp(self):
# Call super's setUp().
TestBase.setUp(self)
# Find the line number for our breakpoint.
self.thread_breakpoint = line_number('main.cpp', '// Set breakpoint here')
self.setup_breakpoint = line_number('main.cpp', '// Break here and adjust num')
self.filename = 'main.cpp'
self.thread_breakpoint_line = line_number(self.filename, '// Set breakpoint here')
self.setup_breakpoint_line = line_number(self.filename, '// Break here and adjust num')
self.finish_breakpoint_line = line_number(self.filename, '// Break here and verify one thread is active')
def print_threads(self, threads):
ret = ""
for x in threads:
ret += "\t thread %d stopped due to reason %s" % (x.GetIndexID(), lldbutil.stop_reason_to_str(x.GetStopReason()))
def describe_threads(self):
ret = []
for x in self.inferior_process:
id = x.GetIndexID()
reason = x.GetStopReason()
status = "stopped" if x.IsStopped() else "running"
reason_str = lldbutil.stop_reason_to_str(reason)
if reason == lldb.eStopReasonBreakpoint:
bpid = x.GetStopReasonDataAtIndex(0)
bp = self.inferior_target.FindBreakpointByID(bpid)
reason_str = "%s hit %d times" % (lldbutil.get_description(bp), bp.GetHitCount())
elif reason == lldb.eStopReasonWatchpoint:
watchid = x.GetStopReasonDataAtIndex(0)
watch = self.inferior_target.FindWatchpointByID(watchid)
reason_str = "%s hit %d times" % (lldbutil.get_description(watch), watch.GetHitCount())
elif reason == lldb.eStopReasonSignal:
reason_str = "signal %s" % (signal_names[x.GetStopReasonDataAtIndex(0)])
location = "\t".join([lldbutil.get_description(x.GetFrameAtIndex(i)) for i in range(x.GetNumFrames())])
ret.append("thread %d %s due to %s at\n\t%s" % (id, status, reason_str, location))
return ret
def debug_threads(self, bps, crashed, exiting, wps, signals, others):
print "%d threads stopped at bp:\n%s" % (len(bps), self.print_threads(bps))
print "%d threads crashed:\n%s" % (len(crashed), self.print_threads(crashed))
print "%d threads stopped due to watchpoint:\n%s" % (len(wps), self.print_threads(wps))
print "%d threads stopped at signal:\n%s" % (len(signals), self.print_threads(signals))
print "%d threads exiting:\n%s" % (len(exiting), self.print_threads(exiting))
print "%d threads stopped due to other/unknown reason:\n%s" % (len(others), self.print_threads(others))
def add_breakpoint(self, line, descriptions):
""" Adds a breakpoint at self.filename:line and appends its description to descriptions, and
returns the LLDB SBBreakpoint object.
"""
bpno = lldbutil.run_break_set_by_file_and_line(self, self.filename, line, num_expected_locations=1)
bp = self.inferior_target.FindBreakpointByID(bpno)
descriptions.append(": file = 'main.cpp', line = %d, locations = 1" % self.finish_breakpoint_line)
return bp
def inferior_done(self):
""" Returns true if the inferior is done executing all the event threads (and is stopped at self.finish_breakpoint,
or has terminated execution.
"""
return self.finish_breakpoint.GetHitCount() > 0 or \
self.crash_count > 0 or \
self.inferior_process.GetState == lldb.eStateExited
def do_thread_actions(self,
num_breakpoint_threads = 0,
@ -361,16 +395,21 @@ class ConcurrentEventsTestCase(TestBase):
exe = os.path.join(os.getcwd(), "a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
# Initialize all the breakpoints (main thread/aux thread)
lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.setup_breakpoint,
num_expected_locations=1)
lldbutil.run_break_set_by_file_and_line (self, "main.cpp", self.thread_breakpoint,
num_expected_locations=1)
# Get the target
self.inferior_target = self.dbg.GetSelectedTarget()
# The breakpoint list should show 2 breakpoints with 1 location.
self.expect("breakpoint list -f", "Breakpoint location shown correctly",
substrs = ["1: file = 'main.cpp', line = %d, locations = 1" % self.setup_breakpoint,
"2: file = 'main.cpp', line = %d, locations = 1" % self.thread_breakpoint])
expected_bps = []
# Initialize all the breakpoints (main thread/aux thread)
self.setup_breakpoint = self.add_breakpoint(self.setup_breakpoint_line, expected_bps)
self.finish_breakpoint = self.add_breakpoint(self.finish_breakpoint_line, expected_bps)
# Set the thread breakpoint
if num_breakpoint_threads + num_delay_breakpoint_threads > 0:
self.thread_breakpoint = self.add_breakpoint(self.thread_breakpoint_line, expected_bps)
# Verify breakpoints
self.expect("breakpoint list -f", "Breakpoint locations shown correctly", substrs = expected_bps)
# Run the program.
self.runCmd("run", RUN_SUCCEEDED)
@ -379,17 +418,19 @@ class ConcurrentEventsTestCase(TestBase):
self.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT,
substrs = ["stop reason = breakpoint 1."])
# Initialize the watchpoint on the global variable (g_watchme)
# Initialize the (single) watchpoint on the global variable (g_watchme)
if num_watchpoint_threads + num_delay_watchpoint_threads > 0:
self.runCmd("watchpoint set variable g_watchme")
for w in self.inferior_target.watchpoint_iter():
self.thread_watchpoint = w
self.assertTrue("g_watchme" in str(self.thread_watchpoint), "Watchpoint location not shown correctly")
# Get the target process
target = self.dbg.GetSelectedTarget()
process = target.GetProcess()
# Get the process
self.inferior_process = self.inferior_target.GetProcess()
# We should be stopped at the setup site where we can set the number of
# threads doing each action (break/crash/signal/watch)
self.assertEqual(process.GetNumThreads(), 1, 'Expected to stop before any additional threads are spawned.')
self.assertEqual(self.inferior_process.GetNumThreads(), 1, 'Expected to stop before any additional threads are spawned.')
self.runCmd("expr num_breakpoint_threads=%d" % num_breakpoint_threads)
self.runCmd("expr num_crash_threads=%d" % num_crash_threads)
@ -401,73 +442,68 @@ class ConcurrentEventsTestCase(TestBase):
self.runCmd("expr num_delay_signal_threads=%d" % num_delay_signal_threads)
self.runCmd("expr num_delay_watchpoint_threads=%d" % num_delay_watchpoint_threads)
# Continue the inferior so threads are spawned
self.runCmd("continue")
# Make sure we see all the threads. The inferior program's threads all synchronize with a pseudo-barrier; that is,
# the inferior program ensures all threads are started and running before any thread triggers its 'event'.
num_threads = process.GetNumThreads()
num_threads = self.inferior_process.GetNumThreads()
expected_num_threads = num_breakpoint_threads + num_delay_breakpoint_threads \
+ num_signal_threads + num_delay_signal_threads \
+ num_watchpoint_threads + num_delay_watchpoint_threads \
+ num_crash_threads + num_delay_crash_threads + 1
self.assertEqual(num_threads, expected_num_threads,
'Number of expected threads and actual threads do not match.')
'Expected to see %d threads, but seeing %d. Details:\n%s' % (expected_num_threads,
num_threads,
"\n\t".join(self.describe_threads())))
# Get the thread objects
(breakpoint_threads, crashed_threads, exiting_threads, other_threads, signal_threads, watchpoint_threads) = ([], [], [], [], [], [])
lldbutil.sort_stopped_threads(process,
breakpoint_threads=breakpoint_threads,
crashed_threads=crashed_threads,
exiting_threads=exiting_threads,
signal_threads=signal_threads,
watchpoint_threads=watchpoint_threads,
other_threads=other_threads)
self.signal_count = len(lldbutil.get_stopped_threads(self.inferior_process, lldb.eStopReasonSignal))
self.crash_count = len(lldbutil.get_stopped_threads(self.inferior_process, lldb.eStopReasonException))
if self.TraceOn():
self.debug_threads(breakpoint_threads, crashed_threads, exiting_threads, watchpoint_threads, signal_threads, other_threads)
# The threads that are doing signal handling must be unblocked or the inferior will hang. We keep
# a counter of threads that stop due to a signal so we have something to verify later on.
seen_signal_threads = len(signal_threads)
seen_breakpoint_threads = len(breakpoint_threads)
seen_watchpoint_threads = len(watchpoint_threads)
seen_crashed_threads = len(crashed_threads)
# Run to completion
while len(crashed_threads) == 0 and process.GetState() != lldb.eStateExited:
# Run to completion (or crash)
while not self.inferior_done():
if self.TraceOn():
self.runCmd("thread backtrace all")
self.debug_threads(breakpoint_threads, crashed_threads, exiting_threads, watchpoint_threads, signal_threads, other_threads)
self.runCmd("continue")
lldbutil.sort_stopped_threads(process,
breakpoint_threads=breakpoint_threads,
crashed_threads=crashed_threads,
exiting_threads=exiting_threads,
signal_threads=signal_threads,
watchpoint_threads=watchpoint_threads,
other_threads=other_threads)
seen_signal_threads += len(signal_threads)
seen_breakpoint_threads += len(breakpoint_threads)
seen_watchpoint_threads += len(watchpoint_threads)
seen_crashed_threads += len(crashed_threads)
self.signal_count += len(lldbutil.get_stopped_threads(self.inferior_process, lldb.eStopReasonSignal))
self.crash_count += len(lldbutil.get_stopped_threads(self.inferior_process, lldb.eStopReasonException))
if num_crash_threads > 0 or num_delay_crash_threads > 0:
# Expecting a crash
self.assertTrue(seen_crashed_threads > 0, "Expecting at least one thread to crash")
self.assertTrue(self.crash_count > 0,
"Expecting at least one thread to crash. Details: %s" % "\t\n".join(self.describe_threads()))
# Ensure the zombie process is reaped
self.runCmd("process kill")
elif num_crash_threads == 0 and num_delay_crash_threads == 0:
# There should be a single active thread (the main one) which hit the breakpoint after joining
self.assertEqual(1, self.finish_breakpoint.GetHitCount(), "Expected main thread (finish) breakpoint to be hit once")
# llvm.org/pr16603 -- LLDB on Linux sometimes reports exited threads as still 'running'
#num_threads = self.inferior_process.GetNumThreads()
#self.assertEqual(1, num_threads, "Expecting 1 thread but seeing %d. Details:%s" % (num_threads,
# "\n\t".join(self.describe_threads())))
self.runCmd("continue")
# The inferior process should have exited without crashing
self.assertEqual(0, seen_crashed_threads, "Unexpected thread(s) in crashed state")
self.assertTrue(process.GetState() == lldb.eStateExited, PROCESS_EXITED)
self.assertEqual(0, self.crash_count, "Unexpected thread(s) in crashed state")
self.assertTrue(self.inferior_process.GetState() == lldb.eStateExited, PROCESS_EXITED)
# Verify the number of actions took place matches expected numbers
self.assertEqual(num_delay_breakpoint_threads + num_breakpoint_threads, seen_breakpoint_threads)
self.assertEqual(num_delay_signal_threads + num_signal_threads, seen_signal_threads)
self.assertEqual(num_delay_watchpoint_threads + num_watchpoint_threads, seen_watchpoint_threads)
expected_breakpoint_threads = num_delay_breakpoint_threads + num_breakpoint_threads
breakpoint_hit_count = self.thread_breakpoint.GetHitCount() if expected_breakpoint_threads > 0 else 0
self.assertEqual(expected_breakpoint_threads, breakpoint_hit_count,
"Expected %d breakpoint hits, but got %d" % (expected_breakpoint_threads, breakpoint_hit_count))
expected_signal_threads = num_delay_signal_threads + num_signal_threads
self.assertEqual(expected_signal_threads, self.signal_count,
"Expected %d stops due to signal delivery, but got %d" % (expected_breakpoint_threads, self.signal_count))
expected_watchpoint_threads = num_delay_watchpoint_threads + num_watchpoint_threads
watchpoint_hit_count = self.thread_watchpoint.GetHitCount() if expected_watchpoint_threads > 0 else 0
self.assertEqual(expected_watchpoint_threads, watchpoint_hit_count,
"Expected %d watchpoint hits, got %d" % (expected_watchpoint_threads, watchpoint_hit_count))
if __name__ == '__main__':

View File

@ -7,10 +7,11 @@
//
//===----------------------------------------------------------------------===//
// This test is intended to create a situation in which a watchpoint will be hit
// while a breakpoint is being handled in another thread. The expected result is
// that the watchpoint in the second thread will be hit while the breakpoint handler
// in the first thread is trying to stop all threads.
// This test is intended to create a situation in which multiple events
// (breakpoints, watchpoints, crashes, and signal generation/delivery) happen
// from multiple threads. The test expects the debugger to set a breakpoint on
// the main thread (before any worker threads are spawned) and modify variables
// which control the number of therads that are spawned for each action.
#include <atomic>
#include <vector>
@ -72,8 +73,10 @@ signal_func (void *input) {
pseudo_barrier_wait(g_barrier);
do_action_args(input);
// Generate a user-defined signal to current process
kill(getpid(), SIGUSR1);
// Send a user-defined signal to the current process
//kill(getpid(), SIGUSR1);
// Send a user-defined signal to the current thread
pthread_kill(pthread_self(), SIGUSR1);
return 0;
}
@ -133,17 +136,20 @@ int main ()
{
g_watchme = 0;
// Actions are triggered immediately after the thread is spawned
unsigned num_breakpoint_threads = 1;
unsigned num_watchpoint_threads = 0;
unsigned num_watchpoint_threads = 1;
unsigned num_signal_threads = 0;
unsigned num_crash_threads = 1;
unsigned num_crash_threads = 0;
// Actions below are triggered after a 1-second delay
unsigned num_delay_breakpoint_threads = 0;
unsigned num_delay_watchpoint_threads = 0;
unsigned num_delay_watchpoint_threads = 1;
unsigned num_delay_signal_threads = 0;
unsigned num_delay_crash_threads = 0;
// Break here and adjust num_[breakpoint|watchpoint|signal|crash]_threads
thread_vector threads; // Break here and adjust num_[breakpoint|watchpoint|signal|crash]_threads
unsigned total_threads = num_breakpoint_threads \
+ num_watchpoint_threads \
+ num_signal_threads \
@ -156,7 +162,6 @@ int main ()
// Don't let either thread do anything until they're both ready.
pseudo_barrier_init(g_barrier, total_threads);
thread_vector threads;
action_counts actions;
actions.push_back(std::make_pair(num_breakpoint_threads, breakpoint_func));
@ -185,6 +190,5 @@ int main ()
for(thread_iterator t = threads.begin(); t != threads.end(); ++t)
pthread_join(*t, 0);
// Break here and verify one thread is active.
return 0;
return 0; // Break here and verify one thread is active.
}