| """Test queues inspection SB APIs.""" | |
| import os | |
| import lldb | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test.lldbplatformutil import findBacktraceRecordingDylib | |
| from lldbsuite.test import lldbutil | |
| class TestQueues(TestBase): | |
| @requireDarwin | |
| @add_test_categories(["pyapi"]) | |
| def test_with_python_api_queues(self): | |
| """Test queues inspection SB APIs.""" | |
| self.build() | |
| self.queues() | |
| @requireDarwin | |
| @add_test_categories(["pyapi"]) | |
| def test_queue_specific_breakpoints(self): | |
| self.build() | |
| self.queue_specific_breakpoints() | |
| @requireDarwin | |
| @add_test_categories(["pyapi"]) | |
| def test_with_python_api_queues_with_backtrace(self): | |
| """Test queues inspection SB APIs.""" | |
| self.build() | |
| self.queues_with_libBacktraceRecording() | |
| def setUp(self): | |
| # Call super's setUp(). | |
| TestBase.setUp(self) | |
| # Find the line numbers that we will step to in main: | |
| self.main_source = "main.c" | |
| def check_queue_for_valid_queue_id(self, queue): | |
| self.assertNotEqual( | |
| queue.GetQueueID(), | |
| 0, | |
| "Check queue %s for valid QueueID (got 0x%x)" | |
| % (queue.GetName(), queue.GetQueueID()), | |
| ) | |
| def check_running_and_pending_items_on_queue( | |
| self, queue, expected_running, expected_pending | |
| ): | |
| self.assertEqual( | |
| queue.GetNumPendingItems(), | |
| expected_pending, | |
| "queue %s should have %d pending items, instead has %d pending items" | |
| % (queue.GetName(), expected_pending, (queue.GetNumPendingItems())), | |
| ) | |
| self.assertEqual( | |
| queue.GetNumRunningItems(), | |
| expected_running, | |
| "queue %s should have %d running items, instead has %d running items" | |
| % (queue.GetName(), expected_running, (queue.GetNumRunningItems())), | |
| ) | |
| def describe_threads(self): | |
| desc = [] | |
| for x in self.inferior_process: | |
| id = x.GetIndexID() | |
| reason_str = lldbutil.stop_reason_to_str(x.GetStopReason()) | |
| location = "\t".join( | |
| [ | |
| lldbutil.get_description(x.GetFrameAtIndex(i)) | |
| for i in range(x.GetNumFrames()) | |
| ] | |
| ) | |
| desc.append( | |
| "thread %d: %s (queue id: %s) at\n\t%s" | |
| % (id, reason_str, x.GetQueueID(), location) | |
| ) | |
| print("\n".join(desc)) | |
| def check_number_of_threads_owned_by_queue(self, queue, number_threads): | |
| if queue.GetNumThreads() != number_threads: | |
| self.describe_threads() | |
| self.assertEqual( | |
| queue.GetNumThreads(), | |
| number_threads, | |
| "queue %s should have %d thread executing, but has %d" | |
| % (queue.GetName(), number_threads, queue.GetNumThreads()), | |
| ) | |
| def check_queue_kind(self, queue, kind): | |
| expected_kind_string = "Unknown" | |
| if kind == lldb.eQueueKindSerial: | |
| expected_kind_string = "Serial queue" | |
| if kind == lldb.eQueueKindConcurrent: | |
| expected_kind_string = "Concurrent queue" | |
| actual_kind_string = "Unknown" | |
| if queue.GetKind() == lldb.eQueueKindSerial: | |
| actual_kind_string = "Serial queue" | |
| if queue.GetKind() == lldb.eQueueKindConcurrent: | |
| actual_kind_string = "Concurrent queue" | |
| self.assertEqual( | |
| queue.GetKind(), | |
| kind, | |
| "queue %s is expected to be a %s but it is actually a %s" | |
| % (queue.GetName(), expected_kind_string, actual_kind_string), | |
| ) | |
| def check_queues_threads_match_queue(self, queue): | |
| for idx in range(0, queue.GetNumThreads()): | |
| t = queue.GetThreadAtIndex(idx) | |
| self.assertTrue( | |
| t.IsValid(), | |
| "Queue %s's thread #%d must be valid" % (queue.GetName(), idx), | |
| ) | |
| self.assertEqual( | |
| t.GetQueueID(), | |
| queue.GetQueueID(), | |
| "Queue %s has a QueueID of %d but its thread #%d has a QueueID of %d" | |
| % (queue.GetName(), queue.GetQueueID(), idx, t.GetQueueID()), | |
| ) | |
| self.assertEqual( | |
| t.GetQueueName(), | |
| queue.GetName(), | |
| "Queue %s has a QueueName of %s but its thread #%d has a QueueName of %s" | |
| % (queue.GetName(), queue.GetName(), idx, t.GetQueueName()), | |
| ) | |
| self.assertEqual( | |
| t.GetQueue().GetQueueID(), | |
| queue.GetQueueID(), | |
| "Thread #%d's Queue's QueueID of %d is not the same as the QueueID of its owning queue %d" | |
| % (idx, t.GetQueue().GetQueueID(), queue.GetQueueID()), | |
| ) | |
| def check_queue_breakpoints(self, queue1, queue2, queue_breakpoint): | |
| queue1_thread = queue1.GetThreadAtIndex(0) | |
| queue2_thread = queue2.GetThreadAtIndex(0) | |
| self.assertEqual( | |
| queue_breakpoint.GetQueueName(), | |
| queue1.GetName(), | |
| "The breakpoint was set for queue %s, but the breakpoint's queue name is %s" | |
| % (queue_breakpoint.GetQueueName(), queue1.GetName()), | |
| ) | |
| self.assertEqual( | |
| queue_breakpoint.GetHitCount(), | |
| 1, | |
| "The breakpoint for queue %s has not been hit" | |
| % (queue_breakpoint.GetQueueName()), | |
| ) | |
| self.assertStopReason( | |
| queue1_thread.GetStopReason(), | |
| lldb.eStopReasonBreakpoint, | |
| "Queue %s is not stopped at breakpoint %d" | |
| % (queue1.GetName(), queue_breakpoint.GetID()), | |
| ) | |
| self.assertNotEqual( | |
| queue2_thread.GetStopReason(), | |
| lldb.eStopReasonBreakpoint, | |
| "Queue %s is stopped at breakpoint %d, but this breakpoint should only be hit for queue %s" | |
| % ( | |
| queue2.GetName(), | |
| queue_breakpoint.GetID(), | |
| queue_breakpoint.GetQueueName(), | |
| ), | |
| ) | |
| def queues(self): | |
| """Test queues inspection SB APIs without libBacktraceRecording.""" | |
| exe = self.getBuildArtifact("a.out") | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| self.main_source_spec = lldb.SBFileSpec(self.main_source) | |
| break1 = target.BreakpointCreateByName("stopper", "a.out") | |
| self.assertTrue(break1, VALID_BREAKPOINT) | |
| process = target.LaunchSimple([], None, self.get_process_working_directory()) | |
| self.assertTrue(process, PROCESS_IS_VALID) | |
| threads = lldbutil.get_threads_stopped_at_breakpoint(process, break1) | |
| if len(threads) != 1: | |
| self.fail("Failed to stop at breakpoint 1.") | |
| self.inferior_process = process | |
| queue_submittor_1 = lldb.SBQueue() | |
| queue_performer_1 = lldb.SBQueue() | |
| queue_performer_2 = lldb.SBQueue() | |
| queue_performer_3 = lldb.SBQueue() | |
| for idx in range(0, process.GetNumQueues()): | |
| q = process.GetQueueAtIndex(idx) | |
| if q.GetName() == "com.apple.work_submittor_1": | |
| queue_submittor_1 = q | |
| if q.GetName() == "com.apple.work_performer_1": | |
| queue_performer_1 = q | |
| if q.GetName() == "com.apple.work_performer_2": | |
| queue_performer_2 = q | |
| if q.GetName() == "com.apple.work_performer_3": | |
| queue_performer_3 = q | |
| self.assertTrue( | |
| queue_submittor_1.IsValid() | |
| and queue_performer_1.IsValid() | |
| and queue_performer_2.IsValid() | |
| and queue_performer_3.IsValid(), | |
| "Got all four expected queues: %s %s %s %s" | |
| % ( | |
| queue_submittor_1.IsValid(), | |
| queue_performer_1.IsValid(), | |
| queue_performer_2.IsValid(), | |
| queue_performer_3.IsValid(), | |
| ), | |
| ) | |
| self.check_queue_for_valid_queue_id(queue_submittor_1) | |
| self.check_queue_for_valid_queue_id(queue_performer_1) | |
| self.check_queue_for_valid_queue_id(queue_performer_2) | |
| self.check_queue_for_valid_queue_id(queue_performer_3) | |
| self.check_number_of_threads_owned_by_queue(queue_submittor_1, 1) | |
| self.check_number_of_threads_owned_by_queue(queue_performer_1, 1) | |
| self.check_number_of_threads_owned_by_queue(queue_performer_2, 1) | |
| self.check_number_of_threads_owned_by_queue(queue_performer_3, 4) | |
| self.check_queue_kind(queue_submittor_1, lldb.eQueueKindSerial) | |
| self.check_queue_kind(queue_performer_1, lldb.eQueueKindSerial) | |
| self.check_queue_kind(queue_performer_2, lldb.eQueueKindSerial) | |
| self.check_queue_kind(queue_performer_3, lldb.eQueueKindConcurrent) | |
| self.check_queues_threads_match_queue(queue_submittor_1) | |
| self.check_queues_threads_match_queue(queue_performer_1) | |
| self.check_queues_threads_match_queue(queue_performer_2) | |
| self.check_queues_threads_match_queue(queue_performer_3) | |
| # We have threads running with all the different dispatch QoS service | |
| # levels - find those threads and check that we can get the correct | |
| # QoS name for each of them. | |
| user_initiated_thread = lldb.SBThread() | |
| user_interactive_thread = lldb.SBThread() | |
| utility_thread = lldb.SBThread() | |
| background_thread = lldb.SBThread() | |
| for th in process.threads: | |
| if th.GetName() == "user initiated QoS": | |
| user_initiated_thread = th | |
| if th.GetName() == "user interactive QoS": | |
| user_interactive_thread = th | |
| if th.GetName() == "utility QoS": | |
| utility_thread = th | |
| if th.GetName() == "background QoS": | |
| background_thread = th | |
| self.assertTrue( | |
| user_initiated_thread.IsValid(), "Found user initiated QoS thread" | |
| ) | |
| self.assertTrue( | |
| user_interactive_thread.IsValid(), "Found user interactive QoS thread" | |
| ) | |
| self.assertTrue(utility_thread.IsValid(), "Found utility QoS thread") | |
| self.assertTrue(background_thread.IsValid(), "Found background QoS thread") | |
| stream = lldb.SBStream() | |
| self.assertTrue( | |
| user_initiated_thread.GetInfoItemByPathAsString( | |
| "requested_qos.printable_name", stream | |
| ), | |
| "Get QoS printable string for user initiated QoS thread", | |
| ) | |
| self.assertEqual( | |
| stream.GetData(), | |
| "User Initiated", | |
| "user initiated QoS thread name is valid", | |
| ) | |
| stream.Clear() | |
| self.assertTrue( | |
| user_interactive_thread.GetInfoItemByPathAsString( | |
| "requested_qos.printable_name", stream | |
| ), | |
| "Get QoS printable string for user interactive QoS thread", | |
| ) | |
| self.assertEqual( | |
| stream.GetData(), | |
| "User Interactive", | |
| "user interactive QoS thread name is valid", | |
| ) | |
| stream.Clear() | |
| self.assertTrue( | |
| utility_thread.GetInfoItemByPathAsString( | |
| "requested_qos.printable_name", stream | |
| ), | |
| "Get QoS printable string for utility QoS thread", | |
| ) | |
| self.assertEqual( | |
| stream.GetData(), "Utility", "utility QoS thread name is valid" | |
| ) | |
| stream.Clear() | |
| self.assertTrue( | |
| background_thread.GetInfoItemByPathAsString( | |
| "requested_qos.printable_name", stream | |
| ), | |
| "Get QoS printable string for background QoS thread", | |
| ) | |
| self.assertEqual( | |
| stream.GetData(), "Background", "background QoS thread name is valid" | |
| ) | |
| @skipIfDarwin # rdar://50379398 | |
| def queues_with_libBacktraceRecording(self): | |
| """Test queues inspection SB APIs with libBacktraceRecording present.""" | |
| exe = self.getBuildArtifact("a.out") | |
| libbtr_path = findBacktraceRecordingDylib() | |
| if not libbtr_path: | |
| self.skipTest( | |
| "Skipped because libBacktraceRecording.dylib was not found on the system." | |
| ) | |
| if not os.path.isfile("/usr/lib/system/introspection/libdispatch.dylib"): | |
| self.skipTest( | |
| "Skipped because introspection libdispatch dylib is not present." | |
| ) | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| self.main_source_spec = lldb.SBFileSpec(self.main_source) | |
| break1 = target.BreakpointCreateByName("stopper", "a.out") | |
| self.assertTrue(break1, VALID_BREAKPOINT) | |
| process = target.LaunchSimple( | |
| [], | |
| [ | |
| "DYLD_INSERT_LIBRARIES=%s" % (libbtr_path), | |
| "DYLD_LIBRARY_PATH=/usr/lib/system/introspection", | |
| ], | |
| self.get_process_working_directory(), | |
| ) | |
| self.assertTrue(process, PROCESS_IS_VALID) | |
| # The stop reason of the thread should be breakpoint. | |
| threads = lldbutil.get_threads_stopped_at_breakpoint(process, break1) | |
| if len(threads) != 1: | |
| self.fail("Failed to stop at breakpoint 1.") | |
| self.inferior_process = process | |
| libbtr_module_filespec = lldb.SBFileSpec("libBacktraceRecording.dylib") | |
| libbtr_module = target.FindModule(libbtr_module_filespec) | |
| if not libbtr_module.IsValid(): | |
| self.skipTest( | |
| "Skipped because libBacktraceRecording.dylib was not loaded into the process." | |
| ) | |
| self.assertGreaterEqual( | |
| process.GetNumQueues(), 4, "Found the correct number of queues." | |
| ) | |
| queue_submittor_1 = lldb.SBQueue() | |
| queue_performer_1 = lldb.SBQueue() | |
| queue_performer_2 = lldb.SBQueue() | |
| queue_performer_3 = lldb.SBQueue() | |
| for idx in range(0, process.GetNumQueues()): | |
| q = process.GetQueueAtIndex(idx) | |
| if "LLDB_COMMAND_TRACE" in os.environ: | |
| print("Queue with id %s has name %s" % (q.GetQueueID(), q.GetName())) | |
| if q.GetName() == "com.apple.work_submittor_1": | |
| queue_submittor_1 = q | |
| if q.GetName() == "com.apple.work_performer_1": | |
| queue_performer_1 = q | |
| if q.GetName() == "com.apple.work_performer_2": | |
| queue_performer_2 = q | |
| if q.GetName() == "com.apple.work_performer_3": | |
| queue_performer_3 = q | |
| if q.GetName() == "com.apple.main-thread": | |
| if q.GetNumThreads() == 0: | |
| print("Cannot get thread <=> queue associations") | |
| return | |
| self.assertTrue( | |
| queue_submittor_1.IsValid() | |
| and queue_performer_1.IsValid() | |
| and queue_performer_2.IsValid() | |
| and queue_performer_3.IsValid(), | |
| "Got all four expected queues: %s %s %s %s" | |
| % ( | |
| queue_submittor_1.IsValid(), | |
| queue_performer_1.IsValid(), | |
| queue_performer_2.IsValid(), | |
| queue_performer_3.IsValid(), | |
| ), | |
| ) | |
| self.check_queue_for_valid_queue_id(queue_submittor_1) | |
| self.check_queue_for_valid_queue_id(queue_performer_1) | |
| self.check_queue_for_valid_queue_id(queue_performer_2) | |
| self.check_queue_for_valid_queue_id(queue_performer_3) | |
| self.check_running_and_pending_items_on_queue(queue_submittor_1, 1, 0) | |
| self.check_running_and_pending_items_on_queue(queue_performer_1, 1, 3) | |
| self.check_running_and_pending_items_on_queue(queue_performer_2, 1, 9999) | |
| self.check_running_and_pending_items_on_queue(queue_performer_3, 4, 0) | |
| self.check_number_of_threads_owned_by_queue(queue_submittor_1, 1) | |
| self.check_number_of_threads_owned_by_queue(queue_performer_1, 1) | |
| self.check_number_of_threads_owned_by_queue(queue_performer_2, 1) | |
| self.check_number_of_threads_owned_by_queue(queue_performer_3, 4) | |
| self.check_queue_kind(queue_submittor_1, lldb.eQueueKindSerial) | |
| self.check_queue_kind(queue_performer_1, lldb.eQueueKindSerial) | |
| self.check_queue_kind(queue_performer_2, lldb.eQueueKindSerial) | |
| self.check_queue_kind(queue_performer_3, lldb.eQueueKindConcurrent) | |
| self.check_queues_threads_match_queue(queue_submittor_1) | |
| self.check_queues_threads_match_queue(queue_performer_1) | |
| self.check_queues_threads_match_queue(queue_performer_2) | |
| self.check_queues_threads_match_queue(queue_performer_3) | |
| self.assertTrue( | |
| queue_performer_2.GetPendingItemAtIndex(0).IsValid(), | |
| "queue 2's pending item #0 is valid", | |
| ) | |
| self.assertEqual( | |
| queue_performer_2.GetPendingItemAtIndex(0) | |
| .GetAddress() | |
| .GetSymbol() | |
| .GetName(), | |
| "doing_the_work_2", | |
| "queue 2's pending item #0 should be doing_the_work_2", | |
| ) | |
| self.assertEqual( | |
| queue_performer_2.GetNumPendingItems(), | |
| 9999, | |
| "verify that queue 2 still has 9999 pending items", | |
| ) | |
| self.assertTrue( | |
| queue_performer_2.GetPendingItemAtIndex(9998).IsValid(), | |
| "queue 2's pending item #9998 is valid", | |
| ) | |
| self.assertEqual( | |
| queue_performer_2.GetPendingItemAtIndex(9998) | |
| .GetAddress() | |
| .GetSymbol() | |
| .GetName(), | |
| "doing_the_work_2", | |
| "queue 2's pending item #0 should be doing_the_work_2", | |
| ) | |
| self.assertFalse( | |
| queue_performer_2.GetPendingItemAtIndex(9999).IsValid(), | |
| "queue 2's pending item #9999 is invalid", | |
| ) | |
| def queue_specific_breakpoints(self): | |
| # Run the executable until the stopper function and get the breakpoint | |
| # that's created from that. Then set the queue name of the breakpoint | |
| # to be the name of the main thread | |
| ( | |
| target, | |
| process, | |
| main_thread, | |
| queue_breakpoint, | |
| ) = lldbutil.run_to_name_breakpoint(self, "stopper", only_one_thread=False) | |
| queue_breakpoint.SetQueueName(main_thread.GetQueue().GetName()) | |
| # Create a submittor queue | |
| queue_submittor_1 = lldb.SBQueue() | |
| for idx in range(0, process.GetNumQueues()): | |
| q = process.GetQueueAtIndex(idx) | |
| if q.GetName() == "com.apple.work_submittor_1": | |
| queue_submittor_1 = q | |
| self.assertTrue( | |
| queue_submittor_1.IsValid(), | |
| "Unable to get expected queue com.apple.work_submittor_1, instead got queue %s" | |
| % (queue_submittor_1.GetName()), | |
| ) | |
| self.check_queue_breakpoints( | |
| main_thread.GetQueue(), queue_submittor_1, queue_breakpoint | |
| ) |