1"""Test queues inspection SB APIs."""
2
3from __future__ import print_function
4
5
6import unittest2
7import os
8import lldb
9from lldbsuite.test.decorators import *
10from lldbsuite.test.lldbtest import *
11from lldbsuite.test import lldbutil
12
13
14class TestQueues(TestBase):
15
16    mydir = TestBase.compute_mydir(__file__)
17
18    @skipUnlessDarwin
19    @add_test_categories(['pyapi'])
20    def test_with_python_api_queues(self):
21        """Test queues inspection SB APIs."""
22        self.build()
23        self.queues()
24
25    @skipUnlessDarwin
26    @add_test_categories(['pyapi'])
27    def test_with_python_api_queues_with_backtrace(self):
28        """Test queues inspection SB APIs."""
29        self.build()
30        self.queues_with_libBacktraceRecording()
31
32    def setUp(self):
33        # Call super's setUp().
34        TestBase.setUp(self)
35        # Find the line numbers that we will step to in main:
36        self.main_source = "main.c"
37
38    def check_queue_for_valid_queue_id(self, queue):
39        self.assertTrue(
40            queue.GetQueueID() != 0, "Check queue %s for valid QueueID (got 0x%x)" %
41            (queue.GetName(), queue.GetQueueID()))
42
43    def check_running_and_pending_items_on_queue(
44            self, queue, expected_running, expected_pending):
45        self.assertTrue(
46            queue.GetNumPendingItems() == expected_pending,
47            "queue %s should have %d pending items, instead has %d pending items" %
48            (queue.GetName(),
49             expected_pending,
50             (queue.GetNumPendingItems())))
51        self.assertTrue(
52            queue.GetNumRunningItems() == expected_running,
53            "queue %s should have %d running items, instead has %d running items" %
54            (queue.GetName(),
55             expected_running,
56             (queue.GetNumRunningItems())))
57
58    def describe_threads(self):
59        desc = []
60        for x in self.inferior_process:
61            id = x.GetIndexID()
62            reason_str = lldbutil.stop_reason_to_str(x.GetStopReason())
63
64            location = "\t".join([lldbutil.get_description(
65                x.GetFrameAtIndex(i)) for i in range(x.GetNumFrames())])
66            desc.append(
67                "thread %d: %s (queue id: %s) at\n\t%s" %
68                (id, reason_str, x.GetQueueID(), location))
69        print('\n'.join(desc))
70
71    def check_number_of_threads_owned_by_queue(self, queue, number_threads):
72        if (queue.GetNumThreads() != number_threads):
73            self.describe_threads()
74
75        self.assertTrue(
76            queue.GetNumThreads() == number_threads,
77            "queue %s should have %d thread executing, but has %d" %
78            (queue.GetName(),
79             number_threads,
80             queue.GetNumThreads()))
81
82    def check_queue_kind(self, queue, kind):
83        expected_kind_string = "Unknown"
84        if kind == lldb.eQueueKindSerial:
85            expected_kind_string = "Serial queue"
86        if kind == lldb.eQueueKindConcurrent:
87            expected_kind_string = "Concurrent queue"
88        actual_kind_string = "Unknown"
89        if queue.GetKind() == lldb.eQueueKindSerial:
90            actual_kind_string = "Serial queue"
91        if queue.GetKind() == lldb.eQueueKindConcurrent:
92            actual_kind_string = "Concurrent queue"
93        self.assertTrue(
94            queue.GetKind() == kind,
95            "queue %s is expected to be a %s but it is actually a %s" %
96            (queue.GetName(),
97             expected_kind_string,
98             actual_kind_string))
99
100    def check_queues_threads_match_queue(self, queue):
101        for idx in range(0, queue.GetNumThreads()):
102            t = queue.GetThreadAtIndex(idx)
103            self.assertTrue(
104                t.IsValid(), "Queue %s's thread #%d must be valid" %
105                (queue.GetName(), idx))
106            self.assertTrue(
107                t.GetQueueID() == queue.GetQueueID(),
108                "Queue %s has a QueueID of %d but its thread #%d has a QueueID of %d" %
109                (queue.GetName(),
110                 queue.GetQueueID(),
111                 idx,
112                 t.GetQueueID()))
113            self.assertTrue(
114                t.GetQueueName() == queue.GetName(),
115                "Queue %s has a QueueName of %s but its thread #%d has a QueueName of %s" %
116                (queue.GetName(),
117                 queue.GetName(),
118                 idx,
119                 t.GetQueueName()))
120            self.assertTrue(
121                t.GetQueue().GetQueueID() == queue.GetQueueID(),
122                "Thread #%d's Queue's QueueID of %d is not the same as the QueueID of its owning queue %d" %
123                (idx,
124                 t.GetQueue().GetQueueID(),
125                    queue.GetQueueID()))
126
127    def queues(self):
128        """Test queues inspection SB APIs without libBacktraceRecording."""
129        exe = self.getBuildArtifact("a.out")
130
131        target = self.dbg.CreateTarget(exe)
132        self.assertTrue(target, VALID_TARGET)
133        self.main_source_spec = lldb.SBFileSpec(self.main_source)
134        break1 = target.BreakpointCreateByName("stopper", 'a.out')
135        self.assertTrue(break1, VALID_BREAKPOINT)
136        process = target.LaunchSimple(
137            [], None, self.get_process_working_directory())
138        self.assertTrue(process, PROCESS_IS_VALID)
139        threads = lldbutil.get_threads_stopped_at_breakpoint(process, break1)
140        if len(threads) != 1:
141            self.fail("Failed to stop at breakpoint 1.")
142
143        self.inferior_process = process
144
145        queue_submittor_1 = lldb.SBQueue()
146        queue_performer_1 = lldb.SBQueue()
147        queue_performer_2 = lldb.SBQueue()
148        queue_performer_3 = lldb.SBQueue()
149        for idx in range(0, process.GetNumQueues()):
150            q = process.GetQueueAtIndex(idx)
151            if q.GetName() == "com.apple.work_submittor_1":
152                queue_submittor_1 = q
153            if q.GetName() == "com.apple.work_performer_1":
154                queue_performer_1 = q
155            if q.GetName() == "com.apple.work_performer_2":
156                queue_performer_2 = q
157            if q.GetName() == "com.apple.work_performer_3":
158                queue_performer_3 = q
159
160        self.assertTrue(
161            queue_submittor_1.IsValid() and queue_performer_1.IsValid() and queue_performer_2.IsValid() and queue_performer_3.IsValid(),
162            "Got all four expected queues: %s %s %s %s" %
163            (queue_submittor_1.IsValid(),
164             queue_performer_1.IsValid(),
165             queue_performer_2.IsValid(),
166             queue_performer_3.IsValid()))
167
168        self.check_queue_for_valid_queue_id(queue_submittor_1)
169        self.check_queue_for_valid_queue_id(queue_performer_1)
170        self.check_queue_for_valid_queue_id(queue_performer_2)
171        self.check_queue_for_valid_queue_id(queue_performer_3)
172
173        self.check_number_of_threads_owned_by_queue(queue_submittor_1, 1)
174        self.check_number_of_threads_owned_by_queue(queue_performer_1, 1)
175        self.check_number_of_threads_owned_by_queue(queue_performer_2, 1)
176        self.check_number_of_threads_owned_by_queue(queue_performer_3, 4)
177
178        self.check_queue_kind(queue_submittor_1, lldb.eQueueKindSerial)
179        self.check_queue_kind(queue_performer_1, lldb.eQueueKindSerial)
180        self.check_queue_kind(queue_performer_2, lldb.eQueueKindSerial)
181        self.check_queue_kind(queue_performer_3, lldb.eQueueKindConcurrent)
182
183        self.check_queues_threads_match_queue(queue_submittor_1)
184        self.check_queues_threads_match_queue(queue_performer_1)
185        self.check_queues_threads_match_queue(queue_performer_2)
186        self.check_queues_threads_match_queue(queue_performer_3)
187
188        # We have threads running with all the different dispatch QoS service
189        # levels - find those threads and check that we can get the correct
190        # QoS name for each of them.
191
192        user_initiated_thread = lldb.SBThread()
193        user_interactive_thread = lldb.SBThread()
194        utility_thread = lldb.SBThread()
195        background_thread = lldb.SBThread()
196        for th in process.threads:
197            if th.GetName() == "user initiated QoS":
198                user_initiated_thread = th
199            if th.GetName() == "user interactive QoS":
200                user_interactive_thread = th
201            if th.GetName() == "utility QoS":
202                utility_thread = th
203            if th.GetName() == "background QoS":
204                background_thread = th
205
206        self.assertTrue(
207            user_initiated_thread.IsValid(),
208            "Found user initiated QoS thread")
209        self.assertTrue(
210            user_interactive_thread.IsValid(),
211            "Found user interactive QoS thread")
212        self.assertTrue(utility_thread.IsValid(), "Found utility QoS thread")
213        self.assertTrue(
214            background_thread.IsValid(),
215            "Found background QoS thread")
216
217        stream = lldb.SBStream()
218        self.assertTrue(
219            user_initiated_thread.GetInfoItemByPathAsString(
220                "requested_qos.printable_name",
221                stream),
222            "Get QoS printable string for user initiated QoS thread")
223        self.assertEqual(
224            stream.GetData(), "User Initiated",
225            "user initiated QoS thread name is valid")
226        stream.Clear()
227        self.assertTrue(
228            user_interactive_thread.GetInfoItemByPathAsString(
229                "requested_qos.printable_name",
230                stream),
231            "Get QoS printable string for user interactive QoS thread")
232        self.assertEqual(
233            stream.GetData(), "User Interactive",
234            "user interactive QoS thread name is valid")
235        stream.Clear()
236        self.assertTrue(
237            utility_thread.GetInfoItemByPathAsString(
238                "requested_qos.printable_name",
239                stream),
240            "Get QoS printable string for utility QoS thread")
241        self.assertEqual(
242            stream.GetData(), "Utility",
243            "utility QoS thread name is valid")
244        stream.Clear()
245        self.assertTrue(
246            background_thread.GetInfoItemByPathAsString(
247                "requested_qos.printable_name",
248                stream),
249            "Get QoS printable string for background QoS thread")
250        self.assertEqual(
251            stream.GetData(), "Background",
252            "background QoS thread name is valid")
253
254    @skipIfDarwin # rdar://50379398
255    def queues_with_libBacktraceRecording(self):
256        """Test queues inspection SB APIs with libBacktraceRecording present."""
257        exe = self.getBuildArtifact("a.out")
258
259        if not os.path.isfile(
260                '/Applications/Xcode.app/Contents/Developer/usr/lib/libBacktraceRecording.dylib'):
261            self.skipTest(
262                "Skipped because libBacktraceRecording.dylib was present on the system.")
263
264        if not os.path.isfile(
265                '/usr/lib/system/introspection/libdispatch.dylib'):
266            self.skipTest(
267                "Skipped because introspection libdispatch dylib is not present.")
268
269        target = self.dbg.CreateTarget(exe)
270        self.assertTrue(target, VALID_TARGET)
271
272        self.main_source_spec = lldb.SBFileSpec(self.main_source)
273
274        break1 = target.BreakpointCreateByName("stopper", 'a.out')
275        self.assertTrue(break1, VALID_BREAKPOINT)
276
277        # Now launch the process, and do not stop at entry point.
278        libbtr_path = "/Applications/Xcode.app/Contents/Developer/usr/lib/libBacktraceRecording.dylib"
279        if self.getArchitecture() in ['arm', 'arm64', 'arm64e', 'arm64_32', 'armv7', 'armv7k']:
280            libbtr_path = "/Developer/usr/lib/libBacktraceRecording.dylib"
281
282        process = target.LaunchSimple(
283            [],
284            [
285                'DYLD_INSERT_LIBRARIES=%s' % (libbtr_path),
286                'DYLD_LIBRARY_PATH=/usr/lib/system/introspection'],
287            self.get_process_working_directory())
288
289        self.assertTrue(process, PROCESS_IS_VALID)
290
291        # The stop reason of the thread should be breakpoint.
292        threads = lldbutil.get_threads_stopped_at_breakpoint(process, break1)
293        if len(threads) != 1:
294            self.fail("Failed to stop at breakpoint 1.")
295
296        self.inferior_process = process
297
298        libbtr_module_filespec = lldb.SBFileSpec("libBacktraceRecording.dylib")
299        libbtr_module = target.FindModule(libbtr_module_filespec)
300        if not libbtr_module.IsValid():
301            self.skipTest(
302                "Skipped because libBacktraceRecording.dylib was not loaded into the process.")
303
304        self.assertTrue(
305            process.GetNumQueues() >= 4,
306            "Found the correct number of queues.")
307
308        queue_submittor_1 = lldb.SBQueue()
309        queue_performer_1 = lldb.SBQueue()
310        queue_performer_2 = lldb.SBQueue()
311        queue_performer_3 = lldb.SBQueue()
312        for idx in range(0, process.GetNumQueues()):
313            q = process.GetQueueAtIndex(idx)
314            if "LLDB_COMMAND_TRACE" in os.environ:
315                print("Queue  with id %s has name %s" % (q.GetQueueID(), q.GetName()))
316            if q.GetName() == "com.apple.work_submittor_1":
317                queue_submittor_1 = q
318            if q.GetName() == "com.apple.work_performer_1":
319                queue_performer_1 = q
320            if q.GetName() == "com.apple.work_performer_2":
321                queue_performer_2 = q
322            if q.GetName() == "com.apple.work_performer_3":
323                queue_performer_3 = q
324            if q.GetName() == "com.apple.main-thread":
325                if q.GetNumThreads() == 0:
326                    print("Cannot get thread <=> queue associations")
327                    return
328
329        self.assertTrue(
330            queue_submittor_1.IsValid() and queue_performer_1.IsValid() and queue_performer_2.IsValid() and queue_performer_3.IsValid(),
331            "Got all four expected queues: %s %s %s %s" %
332            (queue_submittor_1.IsValid(),
333             queue_performer_1.IsValid(),
334             queue_performer_2.IsValid(),
335             queue_performer_3.IsValid()))
336
337        self.check_queue_for_valid_queue_id(queue_submittor_1)
338        self.check_queue_for_valid_queue_id(queue_performer_1)
339        self.check_queue_for_valid_queue_id(queue_performer_2)
340        self.check_queue_for_valid_queue_id(queue_performer_3)
341
342        self.check_running_and_pending_items_on_queue(queue_submittor_1, 1, 0)
343        self.check_running_and_pending_items_on_queue(queue_performer_1, 1, 3)
344        self.check_running_and_pending_items_on_queue(
345            queue_performer_2, 1, 9999)
346        self.check_running_and_pending_items_on_queue(queue_performer_3, 4, 0)
347
348        self.check_number_of_threads_owned_by_queue(queue_submittor_1, 1)
349        self.check_number_of_threads_owned_by_queue(queue_performer_1, 1)
350        self.check_number_of_threads_owned_by_queue(queue_performer_2, 1)
351        self.check_number_of_threads_owned_by_queue(queue_performer_3, 4)
352
353        self.check_queue_kind(queue_submittor_1, lldb.eQueueKindSerial)
354        self.check_queue_kind(queue_performer_1, lldb.eQueueKindSerial)
355        self.check_queue_kind(queue_performer_2, lldb.eQueueKindSerial)
356        self.check_queue_kind(queue_performer_3, lldb.eQueueKindConcurrent)
357
358        self.check_queues_threads_match_queue(queue_submittor_1)
359        self.check_queues_threads_match_queue(queue_performer_1)
360        self.check_queues_threads_match_queue(queue_performer_2)
361        self.check_queues_threads_match_queue(queue_performer_3)
362
363        self.assertTrue(queue_performer_2.GetPendingItemAtIndex(
364            0).IsValid(), "queue 2's pending item #0 is valid")
365        self.assertTrue(queue_performer_2.GetPendingItemAtIndex(0).GetAddress().GetSymbol(
366        ).GetName() == "doing_the_work_2", "queue 2's pending item #0 should be doing_the_work_2")
367        self.assertTrue(
368            queue_performer_2.GetNumPendingItems() == 9999,
369            "verify that queue 2 still has 9999 pending items")
370        self.assertTrue(queue_performer_2.GetPendingItemAtIndex(
371            9998).IsValid(), "queue 2's pending item #9998 is valid")
372        self.assertTrue(queue_performer_2.GetPendingItemAtIndex(9998).GetAddress().GetSymbol(
373        ).GetName() == "doing_the_work_2", "queue 2's pending item #0 should be doing_the_work_2")
374        self.assertTrue(queue_performer_2.GetPendingItemAtIndex(
375            9999).IsValid() == False, "queue 2's pending item #9999 is invalid")
376