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        unspecified_thread = lldb.SBThread()
196        background_thread = lldb.SBThread()
197        for th in process.threads:
198            if th.GetName() == "user initiated QoS":
199                user_initiated_thread = th
200            if th.GetName() == "user interactive QoS":
201                user_interactive_thread = th
202            if th.GetName() == "utility QoS":
203                utility_thread = th
204            if th.GetName() == "unspecified QoS":
205                unspecified_thread = th
206            if th.GetName() == "background QoS":
207                background_thread = th
208
209        self.assertTrue(
210            user_initiated_thread.IsValid(),
211            "Found user initiated QoS thread")
212        self.assertTrue(
213            user_interactive_thread.IsValid(),
214            "Found user interactive QoS thread")
215        self.assertTrue(utility_thread.IsValid(), "Found utility QoS thread")
216        self.assertTrue(
217            unspecified_thread.IsValid(),
218            "Found unspecified QoS thread")
219        self.assertTrue(
220            background_thread.IsValid(),
221            "Found background QoS thread")
222
223        stream = lldb.SBStream()
224        self.assertTrue(
225            user_initiated_thread.GetInfoItemByPathAsString(
226                "requested_qos.printable_name",
227                stream),
228            "Get QoS printable string for user initiated QoS thread")
229        self.assertEqual(
230            stream.GetData(), "User Initiated",
231            "user initiated QoS thread name is valid")
232        stream.Clear()
233        self.assertTrue(
234            user_interactive_thread.GetInfoItemByPathAsString(
235                "requested_qos.printable_name",
236                stream),
237            "Get QoS printable string for user interactive QoS thread")
238        self.assertEqual(
239            stream.GetData(), "User Interactive",
240            "user interactive QoS thread name is valid")
241        stream.Clear()
242        self.assertTrue(
243            utility_thread.GetInfoItemByPathAsString(
244                "requested_qos.printable_name",
245                stream),
246            "Get QoS printable string for utility QoS thread")
247        self.assertEqual(
248            stream.GetData(), "Utility",
249            "utility QoS thread name is valid")
250        stream.Clear()
251        self.assertTrue(
252            unspecified_thread.GetInfoItemByPathAsString(
253                "requested_qos.printable_name",
254                stream),
255            "Get QoS printable string for unspecified QoS thread")
256        qosName = stream.GetData()
257        self.assertTrue(
258            qosName == "User Initiated" or qosName == "Default",
259            "unspecified QoS thread name is valid: " + str(qosName))
260        stream.Clear()
261        self.assertTrue(
262            background_thread.GetInfoItemByPathAsString(
263                "requested_qos.printable_name",
264                stream),
265            "Get QoS printable string for background QoS thread")
266        self.assertEqual(
267            stream.GetData(), "Background",
268            "background QoS thread name is valid")
269
270    @skipIfDarwin # rdar://50379398
271    def queues_with_libBacktraceRecording(self):
272        """Test queues inspection SB APIs with libBacktraceRecording present."""
273        exe = self.getBuildArtifact("a.out")
274
275        if not os.path.isfile(
276                '/Applications/Xcode.app/Contents/Developer/usr/lib/libBacktraceRecording.dylib'):
277            self.skipTest(
278                "Skipped because libBacktraceRecording.dylib was present on the system.")
279
280        if not os.path.isfile(
281                '/usr/lib/system/introspection/libdispatch.dylib'):
282            self.skipTest(
283                "Skipped because introspection libdispatch dylib is not present.")
284
285        target = self.dbg.CreateTarget(exe)
286        self.assertTrue(target, VALID_TARGET)
287
288        self.main_source_spec = lldb.SBFileSpec(self.main_source)
289
290        break1 = target.BreakpointCreateByName("stopper", 'a.out')
291        self.assertTrue(break1, VALID_BREAKPOINT)
292
293        # Now launch the process, and do not stop at entry point.
294        libbtr_path = "/Applications/Xcode.app/Contents/Developer/usr/lib/libBacktraceRecording.dylib"
295        if self.getArchitecture() in ['arm', 'arm64', 'arm64e', 'arm64_32', 'armv7', 'armv7k']:
296            libbtr_path = "/Developer/usr/lib/libBacktraceRecording.dylib"
297
298        process = target.LaunchSimple(
299            [],
300            [
301                'DYLD_INSERT_LIBRARIES=%s' % (libbtr_path),
302                'DYLD_LIBRARY_PATH=/usr/lib/system/introspection'],
303            self.get_process_working_directory())
304
305        self.assertTrue(process, PROCESS_IS_VALID)
306
307        # The stop reason of the thread should be breakpoint.
308        threads = lldbutil.get_threads_stopped_at_breakpoint(process, break1)
309        if len(threads) != 1:
310            self.fail("Failed to stop at breakpoint 1.")
311
312        self.inferior_process = process
313
314        libbtr_module_filespec = lldb.SBFileSpec("libBacktraceRecording.dylib")
315        libbtr_module = target.FindModule(libbtr_module_filespec)
316        if not libbtr_module.IsValid():
317            self.skipTest(
318                "Skipped because libBacktraceRecording.dylib was not loaded into the process.")
319
320        self.assertTrue(
321            process.GetNumQueues() >= 4,
322            "Found the correct number of queues.")
323
324        queue_submittor_1 = lldb.SBQueue()
325        queue_performer_1 = lldb.SBQueue()
326        queue_performer_2 = lldb.SBQueue()
327        queue_performer_3 = lldb.SBQueue()
328        for idx in range(0, process.GetNumQueues()):
329            q = process.GetQueueAtIndex(idx)
330            if "LLDB_COMMAND_TRACE" in os.environ:
331                print("Queue  with id %s has name %s" % (q.GetQueueID(), q.GetName()))
332            if q.GetName() == "com.apple.work_submittor_1":
333                queue_submittor_1 = q
334            if q.GetName() == "com.apple.work_performer_1":
335                queue_performer_1 = q
336            if q.GetName() == "com.apple.work_performer_2":
337                queue_performer_2 = q
338            if q.GetName() == "com.apple.work_performer_3":
339                queue_performer_3 = q
340            if q.GetName() == "com.apple.main-thread":
341                if q.GetNumThreads() == 0:
342                    print("Cannot get thread <=> queue associations")
343                    return
344
345        self.assertTrue(
346            queue_submittor_1.IsValid() and queue_performer_1.IsValid() and queue_performer_2.IsValid() and queue_performer_3.IsValid(),
347            "Got all four expected queues: %s %s %s %s" %
348            (queue_submittor_1.IsValid(),
349             queue_performer_1.IsValid(),
350             queue_performer_2.IsValid(),
351             queue_performer_3.IsValid()))
352
353        self.check_queue_for_valid_queue_id(queue_submittor_1)
354        self.check_queue_for_valid_queue_id(queue_performer_1)
355        self.check_queue_for_valid_queue_id(queue_performer_2)
356        self.check_queue_for_valid_queue_id(queue_performer_3)
357
358        self.check_running_and_pending_items_on_queue(queue_submittor_1, 1, 0)
359        self.check_running_and_pending_items_on_queue(queue_performer_1, 1, 3)
360        self.check_running_and_pending_items_on_queue(
361            queue_performer_2, 1, 9999)
362        self.check_running_and_pending_items_on_queue(queue_performer_3, 4, 0)
363
364        self.check_number_of_threads_owned_by_queue(queue_submittor_1, 1)
365        self.check_number_of_threads_owned_by_queue(queue_performer_1, 1)
366        self.check_number_of_threads_owned_by_queue(queue_performer_2, 1)
367        self.check_number_of_threads_owned_by_queue(queue_performer_3, 4)
368
369        self.check_queue_kind(queue_submittor_1, lldb.eQueueKindSerial)
370        self.check_queue_kind(queue_performer_1, lldb.eQueueKindSerial)
371        self.check_queue_kind(queue_performer_2, lldb.eQueueKindSerial)
372        self.check_queue_kind(queue_performer_3, lldb.eQueueKindConcurrent)
373
374        self.check_queues_threads_match_queue(queue_submittor_1)
375        self.check_queues_threads_match_queue(queue_performer_1)
376        self.check_queues_threads_match_queue(queue_performer_2)
377        self.check_queues_threads_match_queue(queue_performer_3)
378
379        self.assertTrue(queue_performer_2.GetPendingItemAtIndex(
380            0).IsValid(), "queue 2's pending item #0 is valid")
381        self.assertTrue(queue_performer_2.GetPendingItemAtIndex(0).GetAddress().GetSymbol(
382        ).GetName() == "doing_the_work_2", "queue 2's pending item #0 should be doing_the_work_2")
383        self.assertTrue(
384            queue_performer_2.GetNumPendingItems() == 9999,
385            "verify that queue 2 still has 9999 pending items")
386        self.assertTrue(queue_performer_2.GetPendingItemAtIndex(
387            9998).IsValid(), "queue 2's pending item #9998 is valid")
388        self.assertTrue(queue_performer_2.GetPendingItemAtIndex(9998).GetAddress().GetSymbol(
389        ).GetName() == "doing_the_work_2", "queue 2's pending item #0 should be doing_the_work_2")
390        self.assertTrue(queue_performer_2.GetPendingItemAtIndex(
391            9999).IsValid() == False, "queue 2's pending item #9999 is invalid")
392