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