"""
Test NetBSD core file debugging.
"""

from __future__ import division, print_function

import shutil
import signal
import struct
import os

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


class NetBSDCoreCommonTestCase(TestBase):
    NO_DEBUG_INFO_TESTCASE = True

    mydir = TestBase.compute_mydir(__file__)

    def setUp(self):
        super(NetBSDCoreCommonTestCase, self).setUp()
        self._initial_platform = lldb.DBG.GetSelectedPlatform()

    def tearDown(self):
        lldb.DBG.SetSelectedPlatform(self._initial_platform)
        super(NetBSDCoreCommonTestCase, self).tearDown()

    def check_memory_regions(self, process, region_count):
        region_list = process.GetMemoryRegions()
        self.assertEqual(region_list.GetSize(), region_count)

        region = lldb.SBMemoryRegionInfo()

        # Check we have the right number of regions.
        self.assertEqual(region_list.GetSize(), region_count)

        # Check that getting a region beyond the last in the list fails.
        self.assertFalse(
            region_list.GetMemoryRegionAtIndex(
                region_count, region))

        # Check each region is valid.
        for i in range(region_list.GetSize()):
            # Check we can actually get this region.
            self.assertTrue(region_list.GetMemoryRegionAtIndex(i, region))

            # Every region in the list should be mapped.
            self.assertTrue(region.IsMapped())

            # Test the address at the start of a region returns it's enclosing
            # region.
            begin_address = region.GetRegionBase()
            region_at_begin = lldb.SBMemoryRegionInfo()
            error = process.GetMemoryRegionInfo(begin_address, region_at_begin)
            self.assertEqual(region, region_at_begin)

            # Test an address in the middle of a region returns it's enclosing
            # region.
            middle_address = (region.GetRegionBase() +
                              region.GetRegionEnd()) // 2
            region_at_middle = lldb.SBMemoryRegionInfo()
            error = process.GetMemoryRegionInfo(
                middle_address, region_at_middle)
            self.assertEqual(region, region_at_middle)

            # Test the address at the end of a region returns it's enclosing
            # region.
            end_address = region.GetRegionEnd() - 1
            region_at_end = lldb.SBMemoryRegionInfo()
            error = process.GetMemoryRegionInfo(end_address, region_at_end)
            self.assertEqual(region, region_at_end)

            # Check that quering the end address does not return this region but
            # the next one.
            next_region = lldb.SBMemoryRegionInfo()
            error = process.GetMemoryRegionInfo(
                region.GetRegionEnd(), next_region)
            self.assertNotEqual(region, next_region)
            self.assertEqual(
                region.GetRegionEnd(),
                next_region.GetRegionBase())

        # Check that query beyond the last region returns an unmapped region
        # that ends at LLDB_INVALID_ADDRESS
        last_region = lldb.SBMemoryRegionInfo()
        region_list.GetMemoryRegionAtIndex(region_count - 1, last_region)
        end_region = lldb.SBMemoryRegionInfo()
        error = process.GetMemoryRegionInfo(
            last_region.GetRegionEnd(), end_region)
        self.assertFalse(end_region.IsMapped())
        self.assertEqual(
            last_region.GetRegionEnd(),
            end_region.GetRegionBase())
        self.assertEqual(end_region.GetRegionEnd(), lldb.LLDB_INVALID_ADDRESS)

    def check_state(self, process):
        with open(os.devnull) as devnul:
            # sanitize test output
            self.dbg.SetOutputFileHandle(devnul, False)
            self.dbg.SetErrorFileHandle(devnul, False)

            self.assertTrue(process.is_stopped)

            # Process.Continue
            error = process.Continue()
            self.assertFalse(error.Success())
            self.assertTrue(process.is_stopped)

            # Thread.StepOut
            thread = process.GetSelectedThread()
            thread.StepOut()
            self.assertTrue(process.is_stopped)

            # command line
            self.dbg.HandleCommand('s')
            self.assertTrue(process.is_stopped)
            self.dbg.HandleCommand('c')
            self.assertTrue(process.is_stopped)

            # restore file handles
            self.dbg.SetOutputFileHandle(None, False)
            self.dbg.SetErrorFileHandle(None, False)

    def check_backtrace(self, thread, filename, backtrace):
        self.assertGreaterEqual(thread.GetNumFrames(), len(backtrace))
        src = filename.rpartition('.')[0] + '.c'
        for i in range(len(backtrace)):
            frame = thread.GetFrameAtIndex(i)
            self.assertTrue(frame)
            if not backtrace[i].startswith('_'):
                self.assertEqual(frame.GetFunctionName(), backtrace[i])
                self.assertEqual(frame.GetLineEntry().GetLine(),
                                 line_number(src, "Frame " + backtrace[i]))
                self.assertEqual(
                    frame.FindVariable("F").GetValueAsUnsigned(), ord(
                        backtrace[i][0]))

    def do_test(self, filename, pid, region_count):
        target = self.dbg.CreateTarget(filename)
        process = target.LoadCore(filename + ".core")

        self.assertTrue(process, PROCESS_IS_VALID)
        self.assertEqual(process.GetNumThreads(), self.THREAD_COUNT)
        self.assertEqual(process.GetProcessID(), pid)

        self.check_state(process)

        self.check_stack(process, pid, filename)

        self.check_memory_regions(process, region_count)

        self.dbg.DeleteTarget(target)


class NetBSD1LWPCoreTestCase(NetBSDCoreCommonTestCase):
    THREAD_COUNT = 1

    def check_stack(self, process, pid, filename):
        thread = process.GetSelectedThread()
        self.assertTrue(thread)
        self.assertEqual(thread.GetThreadID(), 1)
        self.assertEqual(thread.GetStopReason(), lldb.eStopReasonSignal)
        self.assertEqual(thread.GetStopReasonDataCount(), 1)
        self.assertEqual(thread.GetStopReasonDataAtIndex(0), signal.SIGSEGV)
        backtrace = ["bar", "foo", "main"]
        self.check_backtrace(thread, filename, backtrace)

    @skipIfLLVMTargetMissing("AArch64")
    def test_aarch64(self):
        """Test single-threaded aarch64 core dump."""
        self.do_test("1lwp_SIGSEGV.aarch64", pid=8339, region_count=32)

    @skipIfLLVMTargetMissing("X86")
    def test_amd64(self):
        """Test single-threaded amd64 core dump."""
        self.do_test("1lwp_SIGSEGV.amd64", pid=693, region_count=21)


class NetBSD2LWPT2CoreTestCase(NetBSDCoreCommonTestCase):
    THREAD_COUNT = 2

    def check_stack(self, process, pid, filename):
        thread = process.GetSelectedThread()
        self.assertTrue(thread)
        self.assertEqual(thread.GetThreadID(), 2)
        self.assertEqual(thread.GetStopReason(), lldb.eStopReasonSignal)
        self.assertEqual(thread.GetStopReasonDataCount(), 1)
        self.assertEqual(thread.GetStopReasonDataAtIndex(0), signal.SIGSEGV)
        backtrace = ["bar", "foo", "lwp_main"]
        self.check_backtrace(thread, filename, backtrace)

        # thread 1 should have no signal
        thread = process.GetThreadByID(1)
        self.assertEqual(thread.GetStopReason(), lldb.eStopReasonSignal)
        self.assertEqual(thread.GetStopReasonDataCount(), 1)
        self.assertEqual(thread.GetStopReasonDataAtIndex(0), 0)

    @skipIfLLVMTargetMissing("AArch64")
    def test_aarch64(self):
        """Test double-threaded aarch64 core dump where thread 2 is signalled."""
        self.do_test("2lwp_t2_SIGSEGV.aarch64", pid=14142, region_count=31)

    @skipIfLLVMTargetMissing("X86")
    def test_amd64(self):
        """Test double-threaded amd64 core dump where thread 2 is signalled."""
        self.do_test("2lwp_t2_SIGSEGV.amd64", pid=622, region_count=24)


class NetBSD2LWPProcessSigCoreTestCase(NetBSDCoreCommonTestCase):
    THREAD_COUNT = 2

    def check_stack(self, process, pid, filename):
        thread = process.GetSelectedThread()
        self.assertTrue(thread)
        self.assertEqual(thread.GetThreadID(), 2)
        self.assertEqual(thread.GetStopReason(), lldb.eStopReasonSignal)
        self.assertEqual(thread.GetStopReasonDataCount(), 1)
        self.assertEqual(thread.GetStopReasonDataAtIndex(0), signal.SIGSEGV)
        backtrace = ["bar", "foo", "lwp_main"]
        self.check_backtrace(thread, filename, backtrace)

        # thread 1 should have the same signal
        thread = process.GetThreadByID(1)
        self.assertEqual(thread.GetStopReason(), lldb.eStopReasonSignal)
        self.assertEqual(thread.GetStopReasonDataCount(), 1)
        self.assertEqual(thread.GetStopReasonDataAtIndex(0), signal.SIGSEGV)

    @skipIfLLVMTargetMissing("AArch64")
    def test_aarch64(self):
        """Test double-threaded aarch64 core dump where process is signalled."""
        self.do_test("2lwp_process_SIGSEGV.aarch64", pid=1403, region_count=30)

    @skipIfLLVMTargetMissing("X86")
    def test_amd64(self):
        """Test double-threaded amd64 core dump where process is signalled."""
        self.do_test("2lwp_process_SIGSEGV.amd64", pid=665, region_count=24)
