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

import gdbremote_testcase


class LldbGdbServerTestCase(gdbremote_testcase.GdbRemoteTestCaseBase):
    @skipIfWindows  # no SIGSEGV support
    @add_test_categories(["llgs"])
    def test_run(self):
        self.build()
        self.set_inferior_startup_launch()
        thread_num = 3
        procs = self.prep_debug_monitor_and_inferior(
            inferior_args=["thread:segfault"] + thread_num * ["thread:new"]
        )
        self.test_sequence.add_log_lines(
            [
                "read packet: $QNonStop:1#00",
                "send packet: $OK#00",
                "read packet: $c#63",
                "send packet: $OK#00",
            ],
            True,
        )
        self.expect_gdbremote_sequence()

        segv_signo = lldbutil.get_signal_number("SIGSEGV")
        all_threads = set()
        all_segv_threads = []

        # we should get segfaults from all the threads
        for segv_no in range(thread_num):
            # first wait for the notification event
            self.reset_test_sequence()
            self.test_sequence.add_log_lines(
                [
                    {
                        "direction": "send",
                        "regex": r"^%Stop:(T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+);)",
                        "capture": {1: "packet", 2: "signo", 3: "thread_id"},
                    },
                ],
                True,
            )
            m = self.expect_gdbremote_sequence()
            del m["O_content"]
            threads = [m]

            # then we may get events for the remaining threads
            # (but note that not all threads may have been started yet)
            while True:
                self.reset_test_sequence()
                self.test_sequence.add_log_lines(
                    [
                        "read packet: $vStopped#00",
                        {
                            "direction": "send",
                            "regex": r"^\$(OK|T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+);)",
                            "capture": {1: "packet", 2: "signo", 3: "thread_id"},
                        },
                    ],
                    True,
                )
                m = self.expect_gdbremote_sequence()
                if m["packet"] == "OK":
                    break
                del m["O_content"]
                threads.append(m)

            segv_threads = []
            other_threads = []
            for t in threads:
                signo = int(t["signo"], 16)
                if signo == segv_signo:
                    segv_threads.append(t["thread_id"])
                else:
                    self.assertEqual(signo, 0)
                    other_threads.append(t["thread_id"])

            # verify that exactly one thread segfaulted
            self.assertEqual(len(segv_threads), 1)
            # we should get only one segv from every thread
            self.assertNotIn(segv_threads[0], all_segv_threads)
            all_segv_threads.extend(segv_threads)
            # segv_threads + other_threads should always be a superset
            # of all_threads, i.e. we should get states for all threads
            # already started
            self.assertFalse(all_threads.difference(other_threads + segv_threads))
            all_threads.update(other_threads + segv_threads)

            # verify that `?` returns the same result
            self.reset_test_sequence()
            self.test_sequence.add_log_lines(
                [
                    "read packet: $?#00",
                ],
                True,
            )
            threads_verify = []
            while True:
                self.test_sequence.add_log_lines(
                    [
                        {
                            "direction": "send",
                            "regex": r"^\$(OK|T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+);)",
                            "capture": {1: "packet", 2: "signo", 3: "thread_id"},
                        },
                    ],
                    True,
                )
                m = self.expect_gdbremote_sequence()
                if m["packet"] == "OK":
                    break
                del m["O_content"]
                threads_verify.append(m)
                self.reset_test_sequence()
                self.test_sequence.add_log_lines(
                    [
                        "read packet: $vStopped#00",
                    ],
                    True,
                )

            self.assertEqual(threads, threads_verify)

            self.reset_test_sequence()
            self.test_sequence.add_log_lines(
                [
                    "read packet: $vCont;C{:02x}:{};c#00".format(
                        segv_signo, segv_threads[0]
                    ),
                    "send packet: $OK#00",
                ],
                True,
            )
            self.expect_gdbremote_sequence()

        # finally, verify that all threads have started
        self.assertEqual(len(all_threads), thread_num + 1)

    @add_test_categories(["llgs"])
    def test_vCtrlC(self):
        self.build()
        self.set_inferior_startup_launch()
        procs = self.prep_debug_monitor_and_inferior(inferior_args=["thread:new"])
        self.test_sequence.add_log_lines(
            [
                "read packet: $QNonStop:1#00",
                "send packet: $OK#00",
                "read packet: $c#63",
                "send packet: $OK#00",
                "read packet: $vCtrlC#00",
                "send packet: $OK#00",
                {
                    "direction": "send",
                    "regex": r"^%Stop:T",
                },
            ],
            True,
        )
        self.expect_gdbremote_sequence()

    @add_test_categories(["llgs"])
    def test_exit(self):
        self.build()
        self.set_inferior_startup_launch()
        procs = self.prep_debug_monitor_and_inferior()
        self.test_sequence.add_log_lines(
            [
                "read packet: $QNonStop:1#00",
                "send packet: $OK#00",
                "read packet: $c#63",
                "send packet: $OK#00",
                "send packet: %Stop:W00#00",
                "read packet: $vStopped#00",
                "send packet: $OK#00",
            ],
            True,
        )
        self.expect_gdbremote_sequence()

    @skipIfWindows  # no clue, the result makes zero sense
    @add_test_categories(["llgs"])
    def test_exit_query(self):
        self.build()
        self.set_inferior_startup_launch()
        procs = self.prep_debug_monitor_and_inferior()
        self.test_sequence.add_log_lines(
            [
                "read packet: $QNonStop:1#00",
                "send packet: $OK#00",
                "read packet: $c#63",
                "send packet: $OK#00",
                "send packet: %Stop:W00#00",
                "read packet: $?#00",
                "send packet: $W00#00",
                "read packet: $vStopped#00",
                "send packet: $OK#00",
            ],
            True,
        )
        self.expect_gdbremote_sequence()

    def multiple_resume_test(self, second_command):
        self.build()
        self.set_inferior_startup_launch()
        procs = self.prep_debug_monitor_and_inferior(inferior_args=["sleep:15"])
        self.test_sequence.add_log_lines(
            [
                "read packet: $QNonStop:1#00",
                "send packet: $OK#00",
                "read packet: $c#63",
                "send packet: $OK#00",
                "read packet: ${}#00".format(second_command),
                "send packet: $E37#00",
            ],
            True,
        )
        self.expect_gdbremote_sequence()

    @add_test_categories(["llgs"])
    def test_multiple_C_continue_with_signal(self):
        self.multiple_resume_test("C05")

    @add_test_categories(["llgs"])
    def test_multiple_c_continue_with_addr(self):
        self.multiple_resume_test("c")

    @add_test_categories(["llgs"])
    def test_multiple_s_single_step_with_addr(self):
        self.multiple_resume_test("s")

    @skipIfWindows
    @add_test_categories(["llgs"])
    def test_multiple_vCont(self):
        self.build()
        self.set_inferior_startup_launch()
        procs = self.prep_debug_monitor_and_inferior(
            inferior_args=["thread:new", "stop", "sleep:15"]
        )
        self.test_sequence.add_log_lines(
            [
                "read packet: $QNonStop:1#00",
                "send packet: $OK#00",
                "read packet: $c#63",
                "send packet: $OK#00",
                {
                    "direction": "send",
                    "regex": r"^%Stop:T[0-9a-fA-F]{2}thread:([0-9a-fA-F]+);",
                    "capture": {1: "tid1"},
                },
                "read packet: $vStopped#63",
                {
                    "direction": "send",
                    "regex": r"^[$]T[0-9a-fA-F]{2}thread:([0-9a-fA-F]+);",
                    "capture": {1: "tid2"},
                },
                "read packet: $vStopped#63",
                "send packet: $OK#00",
            ],
            True,
        )
        ret = self.expect_gdbremote_sequence()

        self.reset_test_sequence()
        self.test_sequence.add_log_lines(
            [
                "read packet: $vCont;c:{}#00".format(ret["tid1"]),
                "send packet: $OK#00",
                "read packet: $vCont;c:{}#00".format(ret["tid2"]),
                "send packet: $E37#00",
            ],
            True,
        )
        self.expect_gdbremote_sequence()

    @add_test_categories(["llgs"])
    @skipIfWindows  # Sometimes results in '$E37' instead of expected '$OK'
    def test_vCont_then_stop(self):
        self.build()
        self.set_inferior_startup_launch()
        procs = self.prep_debug_monitor_and_inferior(inferior_args=["sleep:15"])
        self.test_sequence.add_log_lines(
            [
                "read packet: $QNonStop:1#00",
                "send packet: $OK#00",
                "read packet: $c#63",
                "send packet: $OK#00",
                "read packet: $vCont;t#00",
                "send packet: $OK#00",
            ],
            True,
        )
        self.expect_gdbremote_sequence()

    def vCont_then_partial_stop_test(self, run_both):
        self.build()
        self.set_inferior_startup_launch()
        procs = self.prep_debug_monitor_and_inferior(
            inferior_args=["thread:new", "stop", "sleep:15"]
        )
        self.test_sequence.add_log_lines(
            [
                "read packet: $QNonStop:1#00",
                "send packet: $OK#00",
                "read packet: $c#63",
                "send packet: $OK#00",
                {
                    "direction": "send",
                    "regex": r"^%Stop:T[0-9a-fA-F]{2}thread:([0-9a-fA-F]+);",
                    "capture": {1: "tid1"},
                },
                "read packet: $vStopped#63",
                {
                    "direction": "send",
                    "regex": r"^[$]T[0-9a-fA-F]{2}thread:([0-9a-fA-F]+);",
                    "capture": {1: "tid2"},
                },
                "read packet: $vStopped#63",
                "send packet: $OK#00",
            ],
            True,
        )
        ret = self.expect_gdbremote_sequence()

        self.reset_test_sequence()
        if run_both:
            self.test_sequence.add_log_lines(
                [
                    "read packet: $vCont;c#00",
                ],
                True,
            )
        else:
            self.test_sequence.add_log_lines(
                [
                    "read packet: $vCont;c:{}#00".format(ret["tid1"]),
                ],
                True,
            )
        self.test_sequence.add_log_lines(
            [
                "send packet: $OK#00",
                "read packet: $vCont;t:{}#00".format(ret["tid2"]),
                "send packet: $E03#00",
            ],
            True,
        )
        self.expect_gdbremote_sequence()

    @skipIfWindows
    @add_test_categories(["llgs"])
    def test_vCont_then_partial_stop(self):
        self.vCont_then_partial_stop_test(False)

    @skipIfWindows
    @add_test_categories(["llgs"])
    def test_vCont_then_partial_stop_run_both(self):
        self.vCont_then_partial_stop_test(True)

    @skipIfWindows
    @add_test_categories(["llgs"])
    def test_stdio(self):
        self.build()
        self.set_inferior_startup_launch()
        # Since we can't easily ensure that lldb will send output in two parts,
        # just put a stop in the middle.  Since we don't clear vStdio,
        # the second message won't be delivered immediately.
        self.prep_debug_monitor_and_inferior(
            inferior_args=["message 1", "stop", "message 2"]
        )
        self.test_sequence.add_log_lines(
            [
                "read packet: $QNonStop:1#00",
                "send packet: $OK#00",
                "read packet: $c#63",
                "send packet: $OK#00",
                {"direction": "send", "regex": r"^%Stop:T.*"},
                "read packet: $vStopped#00",
                "send packet: $OK#00",
                "read packet: $c#63",
                "send packet: $OK#00",
                "send packet: %Stop:W00#00",
            ],
            True,
        )
        ret = self.expect_gdbremote_sequence()

        # We know there will be at least two messages, but there may be more.
        # Loop until we have everything. The first message waiting for us in the
        # packet queue.
        count = 1
        output = self._server.get_raw_output_packet()
        while not (b"message 2\r\n" in output):
            self._server.send_packet(b"vStdio")
            output += self._server.get_raw_output_packet()
            count += 1
        self.assertGreaterEqual(count, 2)

        self.reset_test_sequence()
        self.test_sequence.add_log_lines(
            [
                "read packet: $vStdio#00",
                "send packet: $OK#00",
                "read packet: $vStopped#00",
                "send packet: $OK#00",
            ],
            True,
        )
        self.expect_gdbremote_sequence()

    @skipIfWindows
    @add_test_categories(["llgs"])
    def test_stop_reason_while_running(self):
        self.build()
        self.set_inferior_startup_launch()
        procs = self.prep_debug_monitor_and_inferior(
            inferior_args=["thread:new", "thread:new", "stop", "sleep:15"]
        )
        self.test_sequence.add_log_lines(
            [
                "read packet: $QNonStop:1#00",
                "send packet: $OK#00",
                # stop is used to synchronize starting threads
                "read packet: $c#63",
                "send packet: $OK#00",
                {"direction": "send", "regex": "%Stop:T.*"},
                "read packet: $c#63",
                "send packet: $OK#00",
                "read packet: $?#00",
                "send packet: $OK#00",
            ],
            True,
        )
        self.expect_gdbremote_sequence()

    @skipIfWindows
    @add_test_categories(["llgs"])
    def test_leave_nonstop(self):
        self.build()
        self.set_inferior_startup_launch()
        procs = self.prep_debug_monitor_and_inferior(
            inferior_args=["thread:new", "thread:new", "stop", "sleep:15"]
        )
        self.test_sequence.add_log_lines(
            [
                "read packet: $QNonStop:1#00",
                "send packet: $OK#00",
                # stop is used to synchronize starting threads
                "read packet: $c#63",
                "send packet: $OK#00",
                {"direction": "send", "regex": "%Stop:T.*"},
                "read packet: $c#63",
                "send packet: $OK#00",
                # verify that the threads are running now
                "read packet: $?#00",
                "send packet: $OK#00",
                "read packet: $QNonStop:0#00",
                "send packet: $OK#00",
                # we should issue some random request now to verify that the stub
                # did not send stop reasons -- we may verify whether notification
                # queue was cleared while at it
                "read packet: $vStopped#00",
                "send packet: $Eff#00",
                "read packet: $?#00",
                {"direction": "send", "regex": "[$]T.*"},
            ],
            True,
        )
        self.expect_gdbremote_sequence()
