diff --git a/lldb/test/functionalities/thread/concurrent_events/TestConcurrentEvents.py b/lldb/test/functionalities/thread/concurrent_events/TestConcurrentEvents.py index 028a7f392a7c..4ec911add0b0 100644 --- a/lldb/test/functionalities/thread/concurrent_events/TestConcurrentEvents.py +++ b/lldb/test/functionalities/thread/concurrent_events/TestConcurrentEvents.py @@ -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__': diff --git a/lldb/test/functionalities/thread/concurrent_events/main.cpp b/lldb/test/functionalities/thread/concurrent_events/main.cpp index 4bb85bec1bfa..98d7075450f1 100644 --- a/lldb/test/functionalities/thread/concurrent_events/main.cpp +++ b/lldb/test/functionalities/thread/concurrent_events/main.cpp @@ -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 #include @@ -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. }