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



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

USE_LIBSTDCPP = "USE_LIBSTDCPP"
USE_LIBCPP = "USE_LIBCPP"

class TestCoroutineHandle(TestBase):
    def do_test(self, stdlib_type):
        """Test std::coroutine_handle is displayed correctly."""
        self.build(dictionary={stdlib_type: "1"})
        is_clang = self.expectedCompiler(["clang"])

        test_generator_func_ptr_re = re.compile(
                r"^\(a.out`my_generator_func\(\) at main.cpp:[0-9]*\)$")

        # Run until the initial suspension point
        lldbutil.run_to_source_breakpoint(self, '// Break at initial_suspend',
                lldb.SBFileSpec("main.cpp", False))

        if self.frame().FindVariable("is_supported").GetValueAsUnsigned(1) == 0:
            self.skipTest("c++ library not supported")

        # Check that we show the correct function pointers and the `promise`. 
        self.expect_expr("gen.hdl",
            result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
            result_children=[
                ValueCheck(name="resume", summary = test_generator_func_ptr_re),
                ValueCheck(name="destroy", summary = test_generator_func_ptr_re),
                ValueCheck(name="promise", children=[
                    ValueCheck(name="current_value", value = "-1"),
                ])
            ])
        if is_clang:
            # For a type-erased `coroutine_handle<>`, we can still devirtualize
            # the promise call and display the correctly typed promise.
            self.expect_expr("type_erased_hdl",
                result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
                result_children=[
                    ValueCheck(name="resume", summary = test_generator_func_ptr_re),
                    ValueCheck(name="destroy", summary = test_generator_func_ptr_re),
                    ValueCheck(name="promise", children=[
                        ValueCheck(name="current_value", value = "-1"),
                    ])
                ])
            # For an incorrectly typed `coroutine_handle`, we use the user-supplied
            # incorrect type instead of inferring the correct type. Strictly speaking,
            # incorrectly typed coroutine handles are undefined behavior. However,
            # it provides probably a better debugging experience if we display the
            # promise as seen by the program instead of fixing this bug based on
            # the available debug info.
            self.expect_expr("incorrectly_typed_hdl",
                result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
                result_children=[
                    ValueCheck(name="resume", summary = test_generator_func_ptr_re),
                    ValueCheck(name="destroy", summary = test_generator_func_ptr_re),
                    ValueCheck(name="promise", dereference=ValueCheck(value="-1"))
                ])

        # Run until after the `co_yield`
        process = self.process()
        lldbutil.continue_to_source_breakpoint(self, process,
                '// Break after co_yield', lldb.SBFileSpec("main.cpp", False))
        # We correctly show the updated value inside `prommise.current_value`.
        self.expect_expr("gen.hdl",
            result_children=[
                ValueCheck(name="resume", summary = test_generator_func_ptr_re),
                ValueCheck(name="destroy", summary = test_generator_func_ptr_re),
                ValueCheck(name="promise", children=[
                    ValueCheck(name="current_value", value = "42"),
                ])
            ])
        
        # Run until the `final_suspend`
        lldbutil.continue_to_source_breakpoint(self, process,
                '// Break at final_suspend', lldb.SBFileSpec("main.cpp", False))
        # At the final suspension point, `resume` is set to a nullptr.
        # Check that we still show the remaining data correctly.
        self.expect_expr("gen.hdl",
            result_children=[
                ValueCheck(name="resume", value = re.compile("^0x0+$")),
                ValueCheck(name="destroy", summary = test_generator_func_ptr_re),
                ValueCheck(name="promise", children=[
                    ValueCheck(name="current_value", value = "42"),
                ])
            ])
        if is_clang:
            # Devirtualization still works, also at the final suspension point, despite
            # the `resume` pointer being reset to a nullptr
            self.expect_expr("type_erased_hdl",
                result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
                result_children=[
                    ValueCheck(name="resume", value = re.compile("^0x0+$")),
                    ValueCheck(name="destroy", summary = test_generator_func_ptr_re),
                    ValueCheck(name="promise", children=[
                        ValueCheck(name="current_value", value = "42"),
                    ])
                ])

    @add_test_categories(["libstdcxx"])
    def test_libstdcpp(self):
        self.do_test(USE_LIBSTDCPP)

    @add_test_categories(["libc++"])
    @skipIf(compiler="clang", compiler_version=['<', '15.0'])
    def test_libcpp(self):
        self.do_test(USE_LIBCPP)
