"""
Test that stepping works even when the OS Plugin doesn't report
all threads at every stop.
"""

from __future__ import print_function


import os
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
import lldbsuite.test.lldbutil as lldbutil


class TestOSPluginStepping(TestBase):

    mydir = TestBase.compute_mydir(__file__)
    NO_DEBUG_INFO_TESTCASE = True

    @skipIfWindows
    @skipIf(oslist=["freebsd"], bugnumber="llvm.org/pr48352")
    def test_python_os_plugin(self):
        """Test that stepping works when the OS Plugin doesn't report all
           threads at every stop"""
        self.build()
        self.main_file = lldb.SBFileSpec('main.cpp')
        self.run_python_os_step_missing_thread(False)

    @skipIfWindows
    @skipIf(oslist=["freebsd"], bugnumber="llvm.org/pr48352")
    def test_python_os_plugin_prune(self):
        """Test that pruning the unreported PlanStacks works"""
        self.build()
        self.main_file = lldb.SBFileSpec('main.cpp')
        self.run_python_os_step_missing_thread(True)

    def get_os_thread(self):
        return self.process.GetThreadByID(0x111111111)

    def is_os_thread(self, thread):
        id = thread.GetID()
        return id == 0x111111111
    
    def run_python_os_step_missing_thread(self, do_prune):
        """Test that the Python operating system plugin works correctly"""

        # Our OS plugin does NOT report all threads:
        result = self.dbg.HandleCommand("settings set process.experimental.os-plugin-reports-all-threads false")

        python_os_plugin_path = os.path.join(self.getSourceDir(),
                                             "operating_system.py")
        (target, self.process, thread, thread_bkpt) = lldbutil.run_to_source_breakpoint(
            self, "first stop in thread - do a step out", self.main_file)

        main_bkpt = target.BreakpointCreateBySourceRegex('Stop here and do not make a memory thread for thread_1',
                                                         self.main_file)
        self.assertEqual(main_bkpt.GetNumLocations(), 1, "Main breakpoint has one location")

        # There should not be an os thread before we load the plugin:
        self.assertFalse(self.get_os_thread().IsValid(), "No OS thread before loading plugin")
        
        # Now load the python OS plug-in which should update the thread list and we should have
        # an OS plug-in thread overlaying thread_1 with id 0x111111111
        command = "settings set target.process.python-os-plugin-path '%s'" % python_os_plugin_path
        self.dbg.HandleCommand(command)

        # Verify our OS plug-in threads showed up
        os_thread = self.get_os_thread()
        self.assertTrue(
            os_thread.IsValid(),
            "Make sure we added the thread 0x111111111 after we load the python OS plug-in")
        
        # Now we are going to step-out.  This should get interrupted by main_bkpt.  We've
        # set up the OS plugin so at this stop, we have lost the OS thread 0x111111111.
        # Make sure both of these are true:
        os_thread.StepOut()
        
        stopped_threads = lldbutil.get_threads_stopped_at_breakpoint(self.process, main_bkpt)
        self.assertEqual(len(stopped_threads), 1, "Stopped at main_bkpt")
        thread = self.process.GetThreadByID(0x111111111)
        self.assertFalse(thread.IsValid(), "No thread 0x111111111 on second stop.")
        
        # Make sure we still have the thread plans for this thread:
        # First, don't show unreported threads, that should fail:
        command = "thread plan list -t 0x111111111"
        result = lldb.SBCommandReturnObject()
        interp = self.dbg.GetCommandInterpreter() 
        interp.HandleCommand(command, result)
        self.assertFalse(result.Succeeded(), "We found no plans for the unreported thread.")
        # Now do it again but with the -u flag:
        command	= "thread plan list -u -t 0x111111111"
        result = lldb.SBCommandReturnObject()
        interp.HandleCommand(command, result)
        self.assertTrue(result.Succeeded(), "We found plans for the unreported thread.")
        
        if do_prune:
            # Prune the thread plan and continue, and we will run to exit.
            interp.HandleCommand("thread plan prune 0x111111111", result)
            self.assertTrue(result.Succeeded(), "Found the plan for 0x111111111 and pruned it")

            # List again, make sure it doesn't work:
            command	= "thread plan list -u -t 0x111111111"
            interp.HandleCommand(command, result)
            self.assertFalse(result.Succeeded(), "We still found plans for the unreported thread.")
            
            self.process.Continue()
            self.assertEqual(self.process.GetState(), lldb.eStateExited, "We exited.")
        else:
            # Now we are going to continue, and when we hit the step-out breakpoint, we will
            # put the OS plugin thread back, lldb will recover its ThreadPlanStack, and
            # we will stop with a "step-out" reason.
            self.process.Continue()
            os_thread = self.get_os_thread()
            self.assertTrue(os_thread.IsValid(), "The OS thread is back after continue")
            self.assertIn("step out", os_thread.GetStopDescription(100), "Completed step out plan")
        
        
