| """ | |
| Test that we page getting a long backtrace on more than one thread | |
| """ | |
| import lldb | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test import lldbutil | |
| @skipIfTargetDoesNotSupportThreads() | |
| class TestThreadBacktracePage(TestBase): | |
| NO_DEBUG_INFO_TESTCASE = True | |
| def test_thread_backtrace_one_thread(self): | |
| """Run a simplified version of the test that just hits one breakpoint and | |
| doesn't care about synchronizing the two threads - hopefully this will | |
| run on more systems.""" | |
| self.build() | |
| ( | |
| self.inferior_target, | |
| self.process, | |
| thread, | |
| bkpt, | |
| ) = lldbutil.run_to_source_breakpoint( | |
| self, self.bkpt_string, lldb.SBFileSpec("main.cpp"), only_one_thread=False | |
| ) | |
| # We hit the breakpoint on at least one thread. If we hit it on both threads | |
| # simultaneously, we are ready to run our tests. Otherwise, suspend the thread | |
| # that hit the breakpoint, and continue till the second thread hits | |
| # the breakpoint: | |
| (breakpoint_threads, other_threads) = ([], []) | |
| lldbutil.sort_stopped_threads( | |
| self.process, | |
| breakpoint_threads=breakpoint_threads, | |
| other_threads=other_threads, | |
| ) | |
| self.assertGreater( | |
| len(breakpoint_threads), 0, "We hit at least one breakpoint thread" | |
| ) | |
| self.assertGreater(len(breakpoint_threads[0].frames), 2, "I can go up") | |
| thread_id = breakpoint_threads[0].idx | |
| name = breakpoint_threads[0].frame[1].name.split("(")[0] | |
| self.check_one_thread(thread_id, name) | |
| def setUp(self): | |
| # Call super's setUp(). | |
| TestBase.setUp(self) | |
| # Find the line number for our breakpoint. | |
| self.bkpt_string = "// Set breakpoint here" | |
| def check_one_thread(self, thread_id, func_name): | |
| # Now issue some thread backtrace commands and make sure they | |
| # get the right answer back. | |
| interp = self.dbg.GetCommandInterpreter() | |
| result = lldb.SBCommandReturnObject() | |
| # Run the real backtrace, remember to pass True for add_to_history since | |
| # we don't generate repeat commands for commands that aren't going into the history. | |
| interp.HandleCommand( | |
| "thread backtrace --count 10 {0}".format(thread_id), result, True | |
| ) | |
| self.assertTrue(result.Succeeded(), "bt with count succeeded") | |
| # There should be 11 lines: | |
| lines = result.GetOutput().splitlines() | |
| self.assertEqual(len(lines), 11, "Got the right number of lines") | |
| # First frame is stop_here: | |
| self.assertIn("stop_here", lines[1], "Found Stop Here") | |
| for line in lines[2:10]: | |
| self.assertIn( | |
| func_name, | |
| line, | |
| "Name {0} not found in line: {1}".format(func_name, line), | |
| ) | |
| # The last entry should be 43: | |
| self.assertIn("count=43", lines[10], "First show ends at 43") | |
| # Now try a repeat, and make sure we get 10 more on this thread: | |
| interp.HandleCommand("", result, True) | |
| self.assertTrue( | |
| result.Succeeded(), "repeat command failed: {0}".format(result.GetError()) | |
| ) | |
| lines = result.GetOutput().splitlines() | |
| self.assertEqual(len(lines), 11, "Repeat got 11 lines") | |
| # Every line should now be the recurse function: | |
| for line in lines[1:10]: | |
| self.assertIn(func_name, line, "Name in every line") | |
| self.assertIn("count=33", lines[10], "Last one is now 33") | |
| def check_two_threads( | |
| self, result_str, thread_id_1, name_1, thread_id_2, name_2, start_idx, end_idx | |
| ): | |
| # We should have 2 occurrences ot the thread header: | |
| self.assertEqual( | |
| result_str.count("thread #{0}".format(thread_id_1)), 1, "One for thread 1" | |
| ) | |
| self.assertEqual( | |
| result_str.count("thread #{0}".format(thread_id_2)), 1, "One for thread 2" | |
| ) | |
| # We should have 10 occurrences of each name: | |
| self.assertEqual(result_str.count(name_1), 10, "Found 10 of {0}".format(name_1)) | |
| self.assertEqual(result_str.count(name_2), 10, "Found 10 of {0}".format(name_1)) | |
| # There should be two instances of count=<start_idx> and none of count=<start-1>: | |
| self.assertEqual( | |
| result_str.count("count={0}".format(start_idx)), | |
| 2, | |
| "Two instances of start_idx", | |
| ) | |
| self.assertEqual( | |
| result_str.count("count={0}".format(start_idx - 1)), | |
| 0, | |
| "No instances of start_idx - 1", | |
| ) | |
| # There should be two instances of count=<end_idx> and none of count=<end_idx+1>: | |
| self.assertEqual( | |
| result_str.count("count={0}".format(end_idx)), 2, "Two instances of end_idx" | |
| ) | |
| self.assertEqual( | |
| result_str.count("count={0}".format(end_idx + 1)), | |
| 0, | |
| "No instances after end idx", | |
| ) | |
| # The setup of this test was copied from the step-out test, and I can't tell from | |
| # the comments whether it was getting two threads to the same breakpoint that was | |
| # problematic, or the step-out part. This test stops at the rendevous point so I'm | |
| # removing the skipIfLinux to see if we see any flakiness in just this part of the test. | |
| @skipIfWindows # This test will hang on windows llvm.org/pr21753 | |
| @expectedFailureAll(oslist=["windows"]) | |
| @expectedFailureNetBSD | |
| def test_thread_backtrace_two_threads(self): | |
| """Test that repeat works even when backtracing on more than one thread.""" | |
| self.build() | |
| ( | |
| self.inferior_target, | |
| self.process, | |
| thread, | |
| bkpt, | |
| ) = lldbutil.run_to_source_breakpoint( | |
| self, self.bkpt_string, lldb.SBFileSpec("main.cpp"), only_one_thread=False | |
| ) | |
| # We hit the breakpoint on at least one thread. If we hit it on both threads | |
| # simultaneously, we are ready to run our tests. Otherwise, suspend the thread | |
| # that hit the breakpoint, and continue till the second thread hits | |
| # the breakpoint: | |
| (breakpoint_threads, other_threads) = ([], []) | |
| lldbutil.sort_stopped_threads( | |
| self.process, | |
| breakpoint_threads=breakpoint_threads, | |
| other_threads=other_threads, | |
| ) | |
| if len(breakpoint_threads) == 1: | |
| success = thread.Suspend() | |
| self.assertTrue(success, "Couldn't suspend a thread") | |
| breakpoint_threads = lldbutil.continue_to_breakpoint(self.process, bkpt) | |
| self.assertEqual(len(breakpoint_threads), 2, "Second thread stopped") | |
| # Figure out which thread is which: | |
| thread_id_1 = breakpoint_threads[0].idx | |
| self.assertGreater(len(breakpoint_threads[0].frames), 2, "I can go up") | |
| name_1 = breakpoint_threads[0].frame[1].name.split("(")[0] | |
| thread_id_2 = breakpoint_threads[1].idx | |
| self.assertGreater(len(breakpoint_threads[1].frames), 2, "I can go up") | |
| name_2 = breakpoint_threads[1].frame[1].name.split("(")[0] | |
| # Check that backtrace and repeat works on one thread, then works on the second | |
| # when we switch to it: | |
| self.check_one_thread(thread_id_1, name_1) | |
| self.check_one_thread(thread_id_2, name_2) | |
| # The output is looking right at this point, let's just do a couple more quick checks | |
| # to see we handle two threads and a start count: | |
| interp = self.dbg.GetCommandInterpreter() | |
| result = lldb.SBCommandReturnObject() | |
| interp.HandleCommand( | |
| "thread backtrace --count 10 --start 10 {0} {1}".format( | |
| thread_id_1, thread_id_2 | |
| ), | |
| result, | |
| True, | |
| ) | |
| self.assertTrue(result.Succeeded(), "command succeeded for two threads") | |
| result.Clear() | |
| interp.HandleCommand("", result, True) | |
| self.assertTrue(result.Succeeded(), "repeat command succeeded for two threads") | |
| result_str = result.GetOutput() | |
| self.check_two_threads( | |
| result_str, thread_id_1, name_1, thread_id_2, name_2, 23, 32 | |
| ) | |
| # Finally make sure the repeat repeats: | |
| result.Clear() | |
| interp.HandleCommand("", result, True) | |
| self.assertTrue(result.Succeeded(), "repeat command succeeded for two threads") | |
| result_str = result.GetOutput() | |
| self.check_two_threads( | |
| result_str, thread_id_1, name_1, thread_id_2, name_2, 13, 22 | |
| ) |