# encoding: utf-8
"""
Test lldb's frame recognizers.
"""

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

import recognizer


class FrameRecognizerTestCase(TestBase):
    NO_DEBUG_INFO_TESTCASE = True

    def test_frame_recognizer_1(self):
        self.build()
        exe = self.getBuildArtifact("a.out")
        target, process, thread, _ = lldbutil.run_to_name_breakpoint(
            self, "foo", exe_name=exe
        )
        frame = thread.selected_frame

        # Clear internal & plugins recognizers that get initialized at launch
        self.runCmd("frame recognizer clear")

        self.runCmd(
            "command script import "
            + os.path.join(self.getSourceDir(), "recognizer.py")
        )

        self.expect("frame recognizer list", substrs=["no matching results found."])

        self.runCmd(
            "frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n foo"
        )

        self.expect(
            "frame recognizer list",
            substrs=[
                "0: recognizer.MyFrameRecognizer, module a.out, demangled symbol foo"
            ],
        )

        self.runCmd(
            "frame recognizer add -l recognizer.MyOtherFrameRecognizer -s a.out -n bar -x"
        )

        self.expect(
            "frame recognizer list",
            substrs=[
                "1: recognizer.MyOtherFrameRecognizer, module a.out, demangled symbol regex bar",
                "0: recognizer.MyFrameRecognizer, module a.out, demangled symbol foo",
            ],
        )

        self.runCmd("frame recognizer delete 0")

        # Test that it deleted the recognizer with id 0.
        self.expect(
            "frame recognizer list",
            substrs=[
                "1: recognizer.MyOtherFrameRecognizer, module a.out, demangled symbol regex bar"
            ],
        )
        self.expect(
            "frame recognizer list", matching=False, substrs=["MyFrameRecognizer"]
        )

        # Test that an invalid index and deleting the same index again
        # is an error and doesn't do any changes.
        self.expect(
            "frame recognizer delete 2",
            error=True,
            substrs=["error: '2' is not a valid recognizer id."],
        )
        self.expect(
            "frame recognizer delete 0",
            error=True,
            substrs=["error: '0' is not a valid recognizer id."],
        )
        # Recognizers should have the same state as above.
        self.expect(
            "frame recognizer list",
            substrs=[
                "1: recognizer.MyOtherFrameRecognizer, module a.out, demangled symbol regex bar"
            ],
        )
        self.expect(
            "frame recognizer list", matching=False, substrs=["MyFrameRecognizer"]
        )

        self.runCmd("frame recognizer clear")

        self.expect("frame recognizer list", substrs=["no matching results found."])

        self.runCmd(
            "frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n foo"
        )

        self.expect("frame variable", substrs=["(int) a = 42", "(int) b = 56"])

        # Recognized arguments don't show up by default...
        variables = frame.GetVariables(lldb.SBVariablesOptions())
        self.assertEqual(variables.GetSize(), 0)

        # ...unless you set target.display-recognized-arguments to 1...
        self.runCmd("settings set target.display-recognized-arguments 1")
        variables = frame.GetVariables(lldb.SBVariablesOptions())
        self.assertEqual(variables.GetSize(), 2)

        # ...and you can reset it back to 0 to hide them again...
        self.runCmd("settings set target.display-recognized-arguments 0")
        variables = frame.GetVariables(lldb.SBVariablesOptions())
        self.assertEqual(variables.GetSize(), 0)

        # ... or explicitly ask for them with SetIncludeRecognizedArguments(True).
        opts = lldb.SBVariablesOptions()
        opts.SetIncludeRecognizedArguments(True)
        variables = frame.GetVariables(opts)

        self.assertEqual(variables.GetSize(), 2)
        self.assertEqual(variables.GetValueAtIndex(0).name, "a")
        self.assertEqual(variables.GetValueAtIndex(0).signed, 42)
        self.assertEqual(
            variables.GetValueAtIndex(0).GetValueType(), lldb.eValueTypeVariableArgument
        )
        self.assertEqual(variables.GetValueAtIndex(1).name, "b")
        self.assertEqual(variables.GetValueAtIndex(1).signed, 56)
        self.assertEqual(
            variables.GetValueAtIndex(1).GetValueType(), lldb.eValueTypeVariableArgument
        )

        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 is recognized by recognizer.MyFrameRecognizer"],
        )

        self.expect(
            "frame recognizer info 999", error=True, substrs=["no frame with index 999"]
        )

        self.expect(
            "frame recognizer info 1",
            substrs=["frame 1 not recognized by any recognizer"],
        )

        # FIXME: The following doesn't work yet, but should be fixed.
        """
        target, process, thread, _ = lldbutil.run_to_name_breakpoint(self, "bar",
                                                                 exe_name = exe)
        frame = thread.GetSelectedFrame()

        self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
                    substrs=['stopped', 'stop reason = breakpoint'])

        self.expect("frame variable -t",
                    substrs=['(int *) a = '])

        self.expect("frame variable -t *a",
                    substrs=['*a = 78'])
        """

    def test_frame_recognizer_hiding(self):
        self.build()

        target, process, thread, _ = lldbutil.run_to_name_breakpoint(self, "nested")
        frame = thread.selected_frame

        # Sanity check.
        self.expect(
            "thread backtrace", patterns=["frame.*nested", "frame.*baz", "frame.*main"]
        )

        self.expect("frame recognizer clear")
        self.expect(
            "command script import "
            + os.path.join(self.getSourceDir(), "recognizer.py")
        )

        self.expect(
            "frame recognizer add -l recognizer.BazFrameRecognizer -f false -s a.out -n baz"
        )

        self.expect(
            "frame recognizer list",
            substrs=["0: recognizer.BazFrameRecognizer"],
        )

        # Now main should be hidden.
        self.expect("thread backtrace", matching=False, patterns=["frame.*baz"])
        self.assertFalse(frame.IsHidden())
        frame = thread.SetSelectedFrame(1)
        self.assertIn("baz", frame.name)
        self.assertTrue(frame.IsHidden())

        # Test StepOut.
        frame = thread.SetSelectedFrame(0)
        thread.StepOut()
        frame = thread.GetSelectedFrame()
        self.assertIn("main", frame.name)

    def test_frame_recognizer_multi_symbol(self):
        self.build()
        exe = self.getBuildArtifact("a.out")

        # Clear internal & plugins recognizers that get initialized at launch
        self.runCmd("frame recognizer clear")

        self.runCmd(
            "command script import "
            + os.path.join(self.getSourceDir(), "recognizer.py")
        )

        self.expect("frame recognizer list", substrs=["no matching results found."])

        self.runCmd(
            "frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n foo -n bar"
        )

        self.expect(
            "frame recognizer list",
            substrs=[
                "recognizer.MyFrameRecognizer, module a.out, demangled symbol foo, bar"
            ],
        )

        target, process, thread, _ = lldbutil.run_to_name_breakpoint(
            self, "foo", exe_name=exe
        )

        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 is recognized by recognizer.MyFrameRecognizer"],
        )

        target, process, thread, _ = lldbutil.run_to_name_breakpoint(
            self, "bar", exe_name=exe
        )

        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 is recognized by recognizer.MyFrameRecognizer"],
        )

    def test_frame_recognizer_target_specific(self):
        self.build()
        exe = self.getBuildArtifact("a.out")

        # Clear internal & plugins recognizers that get initialized at launch
        self.runCmd("frame recognizer clear")

        # Create a target.
        target, process, thread, _ = lldbutil.run_to_name_breakpoint(
            self, "foo", exe_name=exe
        )

        self.runCmd(
            "command script import "
            + os.path.join(self.getSourceDir(), "recognizer.py")
        )

        # Check that this doesn't contain our own FrameRecognizer somehow.
        self.expect(
            "frame recognizer list", matching=False, substrs=["MyFrameRecognizer"]
        )

        # Add a frame recognizer in that target.
        self.runCmd(
            "frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n foo -n bar"
        )

        self.expect(
            "frame recognizer list",
            substrs=[
                "recognizer.MyFrameRecognizer, module a.out, demangled symbol foo, bar"
            ],
        )

        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 is recognized by recognizer.MyFrameRecognizer"],
        )

        # Create a second target. That one shouldn't have the frame recognizer.
        target, process, thread, _ = lldbutil.run_to_name_breakpoint(
            self, "bar", exe_name=exe
        )

        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 not recognized by any recognizer"],
        )

        # Add a frame recognizer to the new target.
        self.runCmd(
            "frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n bar"
        )

        self.expect(
            "frame recognizer list",
            substrs=[
                "recognizer.MyFrameRecognizer, module a.out, demangled symbol bar"
            ],
        )

        # Now the new target should also recognize the frame.
        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 is recognized by recognizer.MyFrameRecognizer"],
        )

    def test_frame_recognizer_not_only_first_instruction(self):
        self.build()
        exe = self.getBuildArtifact("a.out")

        # Clear internal & plugins recognizers that get initialized at launch.
        self.runCmd("frame recognizer clear")

        self.runCmd(
            "command script import "
            + os.path.join(self.getSourceDir(), "recognizer.py")
        )

        self.expect("frame recognizer list", substrs=["no matching results found."])

        # Create a target.
        target, process, thread, _ = lldbutil.run_to_name_breakpoint(
            self, "foo", exe_name=exe
        )

        # Move the PC one instruction further.
        self.runCmd("next")

        # Add a frame recognizer in that target.
        self.runCmd(
            "frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n foo -n bar"
        )

        # It's not applied to foo(), because frame's PC is not at the first instruction of the function.
        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 not recognized by any recognizer"],
        )

        # Add a frame recognizer with --first-instruction-only=true.
        self.runCmd("frame recognizer clear")

        self.runCmd(
            "frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n foo -n bar --first-instruction-only=true"
        )

        # It's not applied to foo(), because frame's PC is not at the first instruction of the function.
        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 not recognized by any recognizer"],
        )

        # Now add a frame recognizer with --first-instruction-only=false.
        self.runCmd("frame recognizer clear")

        self.runCmd(
            "frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n foo -n bar --first-instruction-only=false"
        )

        # This time it should recognize the frame.
        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 is recognized by recognizer.MyFrameRecognizer"],
        )

        opts = lldb.SBVariablesOptions()
        opts.SetIncludeRecognizedArguments(True)
        frame = thread.selected_frame
        variables = frame.GetVariables(opts)

        self.assertEqual(variables.GetSize(), 2)
        self.assertEqual(variables.GetValueAtIndex(0).name, "a")
        self.assertEqual(variables.GetValueAtIndex(0).signed, 42)
        self.assertEqual(
            variables.GetValueAtIndex(0).GetValueType(), lldb.eValueTypeVariableArgument
        )
        self.assertEqual(variables.GetValueAtIndex(1).name, "b")
        self.assertEqual(variables.GetValueAtIndex(1).signed, 56)
        self.assertEqual(
            variables.GetValueAtIndex(1).GetValueType(), lldb.eValueTypeVariableArgument
        )

    def test_frame_recognizer_disable(self):
        self.build()
        exe = self.getBuildArtifact("a.out")
        target, process, thread, _ = lldbutil.run_to_name_breakpoint(
            self, "foo", exe_name=exe
        )

        # Clear internal & plugins recognizers that get initialized at launch.
        self.runCmd("frame recognizer clear")

        self.runCmd(
            "command script import "
            + os.path.join(self.getSourceDir(), "recognizer.py")
        )

        # Add a frame recognizer in that target.
        self.runCmd(
            "frame recognizer add -l recognizer.MyFrameRecognizer -s a.out -n foo -n bar"
        )

        # The frame is recognized
        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 is recognized by recognizer.MyFrameRecognizer"],
        )

        # Disable the recognizer
        self.runCmd("frame recognizer disable 0")

        self.expect(
            "frame recognizer list",
            substrs=[
                "0: [disabled] recognizer.MyFrameRecognizer, module a.out, demangled symbol foo"
            ],
        )

        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 not recognized by any recognizer"],
        )

        # Re-enable the recognizer
        self.runCmd("frame recognizer enable 0")

        self.expect(
            "frame recognizer list",
            substrs=[
                "0: recognizer.MyFrameRecognizer, module a.out, demangled symbol foo"
            ],
        )

        self.expect(
            "frame recognizer info 0",
            substrs=["frame 0 is recognized by recognizer.MyFrameRecognizer"],
        )

    @no_debug_info_test
    def test_frame_recognizer_delete_invalid_arg(self):
        self.expect(
            "frame recognizer delete a",
            error=True,
            substrs=["error: 'a' is not a valid recognizer id."],
        )
        self.expect(
            'frame recognizer delete ""',
            error=True,
            substrs=["error: '' is not a valid recognizer id."],
        )
        self.expect(
            "frame recognizer delete -1",
            error=True,
            substrs=["error: '-1' is not a valid recognizer id."],
        )
        self.expect(
            "frame recognizer delete 4294967297",
            error=True,
            substrs=["error: '4294967297' is not a valid recognizer id."],
        )

    @no_debug_info_test
    def test_frame_recognizer_info_invalid_arg(self):
        self.expect(
            "frame recognizer info a",
            error=True,
            substrs=["error: 'a' is not a valid frame index."],
        )
        self.expect(
            'frame recognizer info ""',
            error=True,
            substrs=["error: '' is not a valid frame index."],
        )
        self.expect(
            "frame recognizer info -1",
            error=True,
            substrs=["error: '-1' is not a valid frame index."],
        )
        self.expect(
            "frame recognizer info 4294967297",
            error=True,
            substrs=["error: '4294967297' is not a valid frame index."],
        )

    @no_debug_info_test
    def test_frame_recognizer_add_invalid_arg(self):
        self.expect(
            "frame recognizer add -f",
            error=True,
            substrs=["error: last option requires an argument"],
        )
        self.expect(
            "frame recognizer add -f -1",
            error=True,
            substrs=["error: invalid boolean value '-1' passed for -f option"],
        )
        self.expect(
            "frame recognizer add -f foo",
            error=True,
            substrs=["error: invalid boolean value 'foo' passed for -f option"],
        )
