1"""
2Test that stepping works even when the OS Plugin doesn't report
3all threads at every stop.
4"""
5
6import os
7import lldb
8from lldbsuite.test.decorators import *
9from lldbsuite.test.lldbtest import *
10import lldbsuite.test.lldbutil as lldbutil
11
12
13class TestOSPluginStepping(TestBase):
14    NO_DEBUG_INFO_TESTCASE = True
15
16    @skipIfWindows
17    @skipIf(oslist=["freebsd"], bugnumber="llvm.org/pr48352")
18    def test_python_os_plugin(self):
19        """Test that stepping works when the OS Plugin doesn't report all
20        threads at every stop"""
21        self.build()
22        self.main_file = lldb.SBFileSpec("main.cpp")
23        self.run_python_os_step_missing_thread(False)
24
25    @skipIfWindows
26    @skipIf(oslist=["freebsd"], bugnumber="llvm.org/pr48352")
27    def test_python_os_plugin_prune(self):
28        """Test that pruning the unreported PlanStacks works"""
29        self.build()
30        self.main_file = lldb.SBFileSpec("main.cpp")
31        self.run_python_os_step_missing_thread(True)
32
33    def get_os_thread(self):
34        return self.process.GetThreadByID(0x111111111)
35
36    def is_os_thread(self, thread):
37        id = thread.GetID()
38        return id == 0x111111111
39
40    def run_python_os_step_missing_thread(self, do_prune):
41        """Test that the Python operating system plugin works correctly"""
42
43        python_os_plugin_path = os.path.join(self.getSourceDir(), "operating_system.py")
44        (target, self.process, thread, thread_bkpt) = lldbutil.run_to_source_breakpoint(
45            self, "first stop in thread - do a step out", self.main_file
46        )
47
48        main_bkpt = target.BreakpointCreateBySourceRegex(
49            "Stop here and do not make a memory thread for thread_1", self.main_file
50        )
51        self.assertEqual(
52            main_bkpt.GetNumLocations(), 1, "Main breakpoint has one location"
53        )
54
55        # There should not be an os thread before we load the plugin:
56        self.assertFalse(
57            self.get_os_thread().IsValid(), "No OS thread before loading plugin"
58        )
59
60        # Now load the python OS plug-in which should update the thread list and we should have
61        # an OS plug-in thread overlaying thread_1 with id 0x111111111
62        command = (
63            "settings set target.process.python-os-plugin-path '%s'"
64            % python_os_plugin_path
65        )
66        self.dbg.HandleCommand(command)
67
68        # Verify our OS plug-in threads showed up
69        os_thread = self.get_os_thread()
70        self.assertTrue(
71            os_thread.IsValid(),
72            "Make sure we added the thread 0x111111111 after we load the python OS plug-in",
73        )
74
75        # Now we are going to step-out.  This should get interrupted by main_bkpt.  We've
76        # set up the OS plugin so at this stop, we have lost the OS thread 0x111111111.
77        # Make sure both of these are true:
78        os_thread.StepOut()
79
80        stopped_threads = lldbutil.get_threads_stopped_at_breakpoint(
81            self.process, main_bkpt
82        )
83        self.assertEqual(len(stopped_threads), 1, "Stopped at main_bkpt")
84        thread = self.process.GetThreadByID(0x111111111)
85        self.assertFalse(thread.IsValid(), "No thread 0x111111111 on second stop.")
86
87        # Make sure we still have the thread plans for this thread:
88        # First, don't show unreported threads, that should fail:
89        command = "thread plan list -t 0x111111111"
90        result = lldb.SBCommandReturnObject()
91        interp = self.dbg.GetCommandInterpreter()
92        interp.HandleCommand(command, result)
93        self.assertFalse(
94            result.Succeeded(), "We found no plans for the unreported thread."
95        )
96        # Now do it again but with the -u flag:
97        command = "thread plan list -u -t 0x111111111"
98        result = lldb.SBCommandReturnObject()
99        interp.HandleCommand(command, result)
100        self.assertTrue(result.Succeeded(), "We found plans for the unreported thread.")
101
102        if do_prune:
103            # Prune the thread plan and continue, and we will run to exit.
104            interp.HandleCommand("thread plan prune 0x111111111", result)
105            self.assertTrue(
106                result.Succeeded(), "Found the plan for 0x111111111 and pruned it"
107            )
108
109            # List again, make sure it doesn't work:
110            command = "thread plan list -u -t 0x111111111"
111            interp.HandleCommand(command, result)
112            self.assertFalse(
113                result.Succeeded(), "We still found plans for the unreported thread."
114            )
115
116            self.process.Continue()
117            self.assertState(self.process.GetState(), lldb.eStateExited, "We exited.")
118        else:
119            # Now we are going to continue, and when we hit the step-out breakpoint, we will
120            # put the OS plugin thread back, lldb will recover its ThreadPlanStack, and
121            # we will stop with a "step-out" reason.
122            self.process.Continue()
123            os_thread = self.get_os_thread()
124            self.assertTrue(os_thread.IsValid(), "The OS thread is back after continue")
125            self.assertIn(
126                "step out", os_thread.GetStopDescription(100), "Completed step out plan"
127            )
128