diff options
| author | Dimitry Andric <dim@FreeBSD.org> | 2017-01-02 19:26:05 +0000 | 
|---|---|---|
| committer | Dimitry Andric <dim@FreeBSD.org> | 2017-01-02 19:26:05 +0000 | 
| commit | 14f1b3e8826ce43b978db93a62d1166055db5394 (patch) | |
| tree | 0a00ad8d3498783fe0193f3b656bca17c4c8697d /packages/Python/lldbsuite/test/concurrent_base.py | |
| parent | 4ee8c119c71a06dcad1e0fecc8c675e480e59337 (diff) | |
Notes
Diffstat (limited to 'packages/Python/lldbsuite/test/concurrent_base.py')
| -rw-r--r-- | packages/Python/lldbsuite/test/concurrent_base.py | 288 | 
1 files changed, 288 insertions, 0 deletions
diff --git a/packages/Python/lldbsuite/test/concurrent_base.py b/packages/Python/lldbsuite/test/concurrent_base.py new file mode 100644 index 000000000000..4a7ae0b9c279 --- /dev/null +++ b/packages/Python/lldbsuite/test/concurrent_base.py @@ -0,0 +1,288 @@ +""" +A stress-test of sorts for LLDB's handling of threads in the inferior. + +This test sets a breakpoint in the main thread where test parameters (numbers of +threads) can be adjusted, runs the inferior to that point, and modifies the +locals that control the event thread counts. This test also sets a breakpoint in +breakpoint_func (the function executed by each 'breakpoint' thread) and a +watchpoint on a global modified in watchpoint_func. The inferior is continued +until exit or a crash takes place, and the number of events seen by LLDB is +verified to match the expected number of events. +""" + +from __future__ import print_function + + +import unittest2 +import os +import time +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class ConcurrentEventsBase(TestBase): + +    # Concurrency is the primary test factor here, not debug info variants. +    NO_DEBUG_INFO_TESTCASE = True + +    def setUp(self): +        # Call super's setUp(). +        super(ConcurrentEventsBase, self).setUp() +        # Find the line number for our breakpoint. +        self.filename = 'main.cpp' +        source_relpath = os.path.join(os.path.pardir, self.filename) +        self.thread_breakpoint_line = line_number( +            source_relpath, '// Set breakpoint here') +        self.setup_breakpoint_line = line_number( +            source_relpath, '// Break here and adjust num') +        self.finish_breakpoint_line = line_number( +            source_relpath, '// Break here and verify one thread is active') + +    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: +                signals = self.inferior_process.GetUnixSignals() +                signal_name = signals.GetSignalAsCString( +                    x.GetStopReasonDataAtIndex(0)) +                reason_str = "signal %s" % signal_name + +            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 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" % +            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 count_signaled_threads(self): +        count = 0 +        for thread in self.inferior_process: +            if thread.GetStopReason() == lldb.eStopReasonSignal and thread.GetStopReasonDataAtIndex( +                    0) == self.inferior_process.GetUnixSignals().GetSignalNumberFromName('SIGUSR1'): +                count += 1 +        return count + +    def do_thread_actions(self, +                          num_breakpoint_threads=0, +                          num_signal_threads=0, +                          num_watchpoint_threads=0, +                          num_crash_threads=0, +                          num_delay_breakpoint_threads=0, +                          num_delay_signal_threads=0, +                          num_delay_watchpoint_threads=0, +                          num_delay_crash_threads=0): +        """ Sets a breakpoint in the main thread where test parameters (numbers of threads) can be adjusted, runs the inferior +            to that point, and modifies the locals that control the event thread counts. Also sets a breakpoint in +            breakpoint_func (the function executed by each 'breakpoint' thread) and a watchpoint on a global modified in +            watchpoint_func. The inferior is continued until exit or a crash takes place, and the number of events seen by LLDB +            is verified to match the expected number of events. +        """ +        exe = os.path.join(os.getcwd(), "a.out") +        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + +        # Get the target +        self.inferior_target = self.dbg.GetSelectedTarget() + +        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) + +        # Check we are at line self.setup_breakpoint +        self.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT, +                    substrs=["stop reason = breakpoint 1."]) + +        # 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 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( +            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) +        self.runCmd("expr num_signal_threads=%d" % num_signal_threads) +        self.runCmd("expr num_watchpoint_threads=%d" % num_watchpoint_threads) + +        self.runCmd( +            "expr num_delay_breakpoint_threads=%d" % +            num_delay_breakpoint_threads) +        self.runCmd( +            "expr num_delay_crash_threads=%d" % +            num_delay_crash_threads) +        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 = 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, +            'Expected to see %d threads, but seeing %d. Details:\n%s' % +            (expected_num_threads, +             num_threads, +             "\n\t".join( +                 self.describe_threads()))) + +        self.signal_count = self.count_signaled_threads() +        self.crash_count = len( +            lldbutil.get_crashed_threads( +                self, self.inferior_process)) + +        # Run to completion (or crash) +        while not self.inferior_done(): +            if self.TraceOn(): +                self.runCmd("thread backtrace all") +            self.runCmd("continue") +            self.signal_count += self.count_signaled_threads() +            self.crash_count += len( +                lldbutil.get_crashed_threads( +                    self, self.inferior_process)) + +        if num_crash_threads > 0 or num_delay_crash_threads > 0: +            # Expecting a 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") + +            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, +                self.crash_count, +                "Unexpected thread(s) in crashed state") +            self.assertEqual( +                self.inferior_process.GetState(), +                lldb.eStateExited, +                PROCESS_EXITED) + +            # Verify the number of actions took place matches expected numbers +            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_signal_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))  | 
