"""
Test lldb data formatter subsystem.
"""

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


class StdFunctionTestCase(TestBase):
    SHARED_BUILD_TESTCASE = False

    # Run frame var for a variable twice. Verify we do not hit the cache
    # the first time but do the second time.
    def run_frame_var_check_cache_use(
        self, variable, result_to_match, skip_find_function=False
    ):
        self.runCmd("log timers reset")
        self.expect(
            "frame variable " + variable, substrs=[variable + " =  " + result_to_match]
        )
        if not skip_find_function:
            self.expect(
                "log timers dump", substrs=["lldb_private::CompileUnit::FindFunction"]
            )

        self.runCmd("log timers reset")
        self.expect(
            "frame variable " + variable, substrs=[variable + " =  " + result_to_match]
        )
        self.expect(
            "log timers dump",
            matching=False,
            substrs=["lldb_private::CompileUnit::FindFunction"],
        )

    def do_test(self):
        """Test that std::function is correctly printed by LLDB"""
        self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)

        bkpt = self.target().FindBreakpointByID(
            lldbutil.run_break_set_by_source_regexp(
                self, "Set break point at this line."
            )
        )

        self.runCmd("run", RUN_SUCCEEDED)

        # The stop reason of the thread should be breakpoint.
        self.expect(
            "thread list",
            STOPPED_DUE_TO_BREAKPOINT,
            substrs=["stopped", "stop reason = breakpoint"],
        )

        self.run_frame_var_check_cache_use(
            "foo2_f", "Lambda in File main.cpp at Line 16"
        )

        lldbutil.continue_to_breakpoint(self.process(), bkpt)

        self.run_frame_var_check_cache_use(
            "add_num2_f", "Lambda in File main.cpp at Line 9"
        )

        lldbutil.continue_to_breakpoint(self.process(), bkpt)

        self.run_frame_var_check_cache_use("f2", "Lambda in File main.cpp at Line 26")
        self.run_frame_var_check_cache_use(
            "f3", "Lambda in File main.cpp at Line 30", True
        )
        # TODO reenable this case when std::function formatter supports
        # general callable object case.
        # self.run_frame_var_check_cache_use("f4", "Function in File main.cpp at Line 8")

        # These cases won't hit the cache at all but also don't require
        # an expensive lookup.
        self.expect("frame variable f1", substrs=["f1 =  Function = foo(int, int)"])

        self.expect(
            "frame variable f5", substrs=["f5 =  Function = Bar::add_num(int) const"]
        )

    @add_test_categories(["libc++"])
    def test_libcxx(self):
        self.build(dictionary={"USE_LIBCPP": 1})
        self.do_test()
