"""
Test that the FrameRecognizer for __abort_with_payload
works properly
"""


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


class TestAbortWithPayload(TestBase):
    NO_DEBUG_INFO_TESTCASE = True

    @skipUnlessAppleSilicon
    def test_abort_with_payload(self):
        """There can be many tests in a test case - describe this test here."""
        self.build()
        self.abort_with_test(True)

    @skipUnlessAppleSilicon
    def test_abort_with_reason(self):
        """There can be many tests in a test case - describe this test here."""
        self.build()
        self.abort_with_test(False)

    def setUp(self):
        # Call super's setUp().
        TestBase.setUp(self)
        self.main_source_file = lldb.SBFileSpec("main.c")

    def abort_with_test(self, with_payload):
        """If with_payload is True, we test the abort_with_payload call,
        if false, we test abort_with_reason."""
        launch_info = lldb.SBLaunchInfo([])
        if not with_payload:
            launch_info.SetArguments(["use_reason"], True)
        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
            self,
            "Stop here before abort",
            self.main_source_file,
            launch_info=launch_info,
        )

        frame = thread.GetFrameAtIndex(0)
        payload_str_var = frame.FindVariable("payload_string")
        self.assertSuccess(payload_str_var.GetError(), "Got payload string var")
        payload_var_addr = payload_str_var.unsigned

        payload_size_var = frame.FindVariable("payload_string_len")
        self.assertSuccess(payload_size_var.GetError(), "Got payload string len var")
        payload_size_val = payload_size_var.unsigned

        # Not let it run to crash:
        process.Continue()

        # At this point we should have stopped at the internal function.
        # Make sure we selected the right thread:
        sel_thread = process.GetSelectedThread()
        self.assertEqual(thread, sel_thread, "Selected the original thread")
        # Make sure the stop reason is right:
        self.assertEqual(
            thread.stop_description,
            "abort with payload or reason",
            "Description was right",
        )
        frame_0 = thread.frames[0]
        self.assertEqual(frame_0.name, "__abort_with_payload", "Frame 0 was right")

        # Now check the recognized argument values and the ExtendedCrashInformation version:
        options = lldb.SBVariablesOptions()
        options.SetIncludeRecognizedArguments(True)
        options.SetIncludeArguments(False)
        options.SetIncludeLocals(False)
        options.SetIncludeStatics(False)
        options.SetIncludeRuntimeSupportValues(False)

        arguments = frame_0.GetVariables(options)

        correct_values = {
            "namespace": 5,
            "code": 100,
            "payload_addr": payload_var_addr,
            "payload_size": payload_size_val,
            "payload_string": '"This is a payload that happens to be a string"',
            "reason_string": '"This is the reason string"',
            "reason_no_quote": "This is the reason string",
            "flags": 0x85,
        }

        # First check the recognized argument values:
        self.assertEqual(len(arguments), 6, "Got all six values")
        self.assertEqual(arguments[0].name, "namespace")
        self.assertEqual(
            arguments[0].unsigned,
            correct_values["namespace"],
            "Namespace value correct",
        )

        self.assertEqual(arguments[1].name, "code")
        self.assertEqual(
            arguments[1].unsigned, correct_values["code"], "code value correct"
        )

        # We always stop at __abort_with_payload, regardless of whether the caller
        # was abort_with_reason or abort_with_payload or any future API that
        # funnels here. Since I don't want to have to know too much about the
        # callers, I just always report what is in the function I've 
        # 
        # add the payload ones if it is the payload not the reason function.
        self.assertEqual(arguments[2].name, "payload_addr")
        self.assertEqual(arguments[3].name, "payload_size")
        if with_payload:
            self.assertEqual(
                arguments[2].unsigned,
                correct_values["payload_addr"],
                "Payload matched variable address",
            )
            # We've made a payload that is a string, try to fetch that:
            char_ptr_type = target.FindFirstType("char").GetPointerType()
            self.assertTrue(char_ptr_type.IsValid(), "Got char ptr type")

            str_val = arguments[2].Cast(char_ptr_type)
            self.assertEqual(
                str_val.summary, correct_values["payload_string"], "Got payload string"
            )

            self.assertEqual(
                arguments[3].unsigned,
                correct_values["payload_size"],
                "payload size value correct",
            )
        else:
            self.assertEqual(
                arguments[2].unsigned, 0, "Got 0 payload addr for reason call"
            )
            self.assertEqual(
                arguments[3].unsigned, 0, "Got 0 payload size for reason call"
            )

        self.assertEqual(arguments[4].name, "reason")
        self.assertEqual(
            arguments[4].summary,
            correct_values["reason_string"],
            "Reason value correct",
        )

        self.assertEqual(arguments[5].name, "flags")
        self.assertEqual(
            arguments[5].unsigned, correct_values["flags"], "Flags value correct"
        )

        # Also check that the same info was stored in the ExtendedCrashInformation dict:
        dict = process.GetExtendedCrashInformation()
        self.assertTrue(dict.IsValid(), "Got extended crash information dict")
        self.assertEqual(
            dict.GetType(), lldb.eStructuredDataTypeDictionary, "It is a dictionary"
        )

        abort_dict = dict.GetValueForKey("abort_with_payload")
        self.assertTrue(abort_dict.IsValid(), "Got an abort_with_payload dict")
        self.assertEqual(
            abort_dict.GetType(),
            lldb.eStructuredDataTypeDictionary,
            "It is a dictionary",
        )

        namespace_val = abort_dict.GetValueForKey("namespace")
        self.assertTrue(namespace_val.IsValid(), "Got a valid namespace")
        self.assertEqual(
            namespace_val.GetIntegerValue(0),
            correct_values["namespace"],
            "Namespace value correct",
        )

        code_val = abort_dict.GetValueForKey("code")
        self.assertTrue(code_val.IsValid(), "Got a valid code")
        self.assertEqual(
            code_val.GetIntegerValue(0), correct_values["code"], "Code value correct"
        )

        if with_payload:
            addr_val = abort_dict.GetValueForKey("payload_addr")
            self.assertTrue(addr_val.IsValid(), "Got a payload_addr")
            self.assertEqual(
                addr_val.GetIntegerValue(0),
                correct_values["payload_addr"],
                "payload_addr right in dictionary",
            )

            size_val = abort_dict.GetValueForKey("payload_size")
            self.assertTrue(size_val.IsValid(), "Got a payload size value")
            self.assertEqual(
                size_val.GetIntegerValue(0),
                correct_values["payload_size"],
                "payload size right in dictionary",
            )

        reason_val = abort_dict.GetValueForKey("reason")
        self.assertTrue(reason_val.IsValid(), "Got a reason key")
        self.assertEqual(
            reason_val.GetStringValue(100),
            correct_values["reason_no_quote"],
            "reason right in dictionary",
        )

        flags_val = abort_dict.GetValueForKey("flags")
        self.assertTrue(flags_val.IsValid(), "Got a flags value")
        self.assertEqual(
            flags_val.GetIntegerValue(0),
            correct_values["flags"],
            "flags right in dictionary",
        )
