"""
Test saving a mini dump, from yamilized examples.
"""

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


class AddressRange:
    begin: int
    end: int

    def __init__(self, begin, end):
        self.begin = begin
        self.end = end


# We skip all these tests on Windows because on Windows Minidumps
# are not generated by LLDB.
class ProcessSaveCoreMinidumpTestCaseYaml(TestBase):
    def process_from_yaml(self, yaml_file):
        minidump_path = self.getBuildArtifact(os.path.basename(yaml_file) + ".dmp")
        self.yaml2obj(yaml_file, minidump_path)
        self.target = self.dbg.CreateTarget(None)
        self.process = self.target.LoadCore(minidump_path)
        return self.process

    @skipIfWindows
    def validate_regions_saved_correctly(
        self, core_process, expected_region, expected_invalid_region=None
    ):
        """Validate that the expected_region is saved in the core_proc, and that the expected invalid region is not saved, if not not none."""

        # Validate we can read the entire expected_region
        error = lldb.SBError()
        core_process.ReadMemory(
            expected_region.begin, expected_region.end - expected_region.begin, error
        )
        self.assertTrue(error.Success(), error.GetCString())

        # Validate we can't read before and after the expected_region
        core_process.ReadMemory(expected_region.begin - 1, 1, error)
        self.assertTrue(error.Fail(), error.GetCString())

        core_process.ReadMemory(expected_region.end + 1, 1, error)
        self.assertTrue(error.Fail(), error.GetCString())

        if expected_invalid_region is None:
            return

        # Validate we can't read the original_region
        core_process.ReadMemory(
            expected_invalid_region.begin,
            expected_invalid_region.end - expected_invalid_region.begin,
            error,
        )
        self.assertTrue(error.Fail(), error.GetCString())

    @skipIfWindows
    def test_saving_sub_memory_range(self):
        """
        Validate we can save a Minidump for a subsection of a memory range.
        I.E.
        If our memory range is 0x2000-0x2020 and the user specifies 0x2000-0x2008
        we should still capture 0x2000-0x2008
        """
        yaml = "minidump_mem64.yaml"
        proc = self.process_from_yaml(yaml)
        new_minidump_path = self.getBuildArtifact(__name__ + ".dmp")
        options = lldb.SBSaveCoreOptions()
        options.SetOutputFile(lldb.SBFileSpec(new_minidump_path))
        options.SetPluginName("minidump")
        options.SetStyle(lldb.eSaveCoreCustomOnly)

        size = 8
        begin = 0x2000
        end = begin + size
        custom_range = lldb.SBMemoryRegionInfo("", begin, end, 3, True, False)
        options.AddMemoryRegionToSave(custom_range)

        error = proc.SaveCore(options)
        self.assertTrue(error.Success(), error.GetCString())
        core_target = self.dbg.CreateTarget(None)
        core_process = core_target.LoadCore(new_minidump_path)

        expected_address_range = AddressRange(begin, end)
        expected_invalid_range = AddressRange(begin, 0x2020)
        self.validate_regions_saved_correctly(
            core_process, expected_address_range, expected_invalid_range
        )

    @skipIfWindows
    def test_saving_super_memory_range(self):
        """
        Validate we can save a Minidump for a subsection of a memory range.
        I.E.
        If our memory range is 0x1000-0x1100 and the user specifies 0x900-x1200
        we should still capture 0x1000-0x1100
        """
        yaml = "minidump_mem64.yaml"
        proc = self.process_from_yaml(yaml)
        new_minidump_path = self.getBuildArtifact(__name__ + ".dmp")
        options = lldb.SBSaveCoreOptions()
        options.SetOutputFile(lldb.SBFileSpec(new_minidump_path))
        options.SetPluginName("minidump")
        options.SetStyle(lldb.eSaveCoreCustomOnly)

        size = 0x100
        begin = 0x1000
        end = begin + size
        custom_range = lldb.SBMemoryRegionInfo("", begin - 16, end + 16, 3, True, False)
        options.AddMemoryRegionToSave(custom_range)

        error = proc.SaveCore(options)
        self.assertTrue(error.Success(), error.GetCString())
        core_target = self.dbg.CreateTarget(None)
        core_process = core_target.LoadCore(new_minidump_path)

        expected_address_range = AddressRange(begin, end)
        expected_invalid_range = AddressRange(begin - 16, end + 16)
        self.validate_regions_saved_correctly(
            core_process, expected_address_range, expected_invalid_range
        )

    @skipIfWindows
    def test_region_that_goes_out_of_bounds(self):
        """
        Validate we can save a Minidump for a custom region
        that includes an end that enters an invalid (---) page.
        """
        yaml = "minidump_mem64.yaml"
        proc = self.process_from_yaml(yaml)
        new_minidump_path = self.getBuildArtifact(__name__ + ".dmp")
        options = lldb.SBSaveCoreOptions()
        options.SetOutputFile(lldb.SBFileSpec(new_minidump_path))
        options.SetPluginName("minidump")
        options.SetStyle(lldb.eSaveCoreCustomOnly)

        size = 0x120
        begin = 0x1000
        end = begin + size
        custom_range = lldb.SBMemoryRegionInfo("", begin, end, 3, True, False)
        options.AddMemoryRegionToSave(custom_range)

        error = proc.SaveCore(options)
        self.assertTrue(error.Success(), error.GetCString())
        core_target = self.dbg.CreateTarget(None)
        core_process = core_target.LoadCore(new_minidump_path)

        expected_address_range = AddressRange(begin, end)
        expected_invalid_range = AddressRange(begin - 16, end + 16)
        self.validate_regions_saved_correctly(
            core_process, expected_address_range, expected_invalid_range
        )

    @skipIfWindows
    def test_region_that_starts_out_of_bounds(self):
        """
        Validate we can save a Minidump for a custom region
        that includes a start in a (---) page but ends in a valid page.
        """
        yaml = "minidump_mem64.yaml"
        proc = self.process_from_yaml(yaml)
        new_minidump_path = self.getBuildArtifact(__name__ + ".dmp")
        options = lldb.SBSaveCoreOptions()
        options.SetOutputFile(lldb.SBFileSpec(new_minidump_path))
        options.SetPluginName("minidump")
        options.SetStyle(lldb.eSaveCoreCustomOnly)

        size = 0x20
        begin = 0x2000
        end = begin + size
        custom_range = lldb.SBMemoryRegionInfo("", begin - 16, end, 3, True, False)
        options.AddMemoryRegionToSave(custom_range)

        error = proc.SaveCore(options)
        self.assertTrue(error.Success(), error.GetCString())
        core_target = self.dbg.CreateTarget(None)
        core_process = core_target.LoadCore(new_minidump_path)

        expected_address_range = AddressRange(begin, end)
        expected_invalid_range = AddressRange(begin - 16, end)
        self.validate_regions_saved_correctly(
            core_process, expected_address_range, expected_invalid_range
        )

    @skipIfWindows
    def test_region_spans_multiple_regions(self):
        """
        Validate we can save a Minidump for a custom region
        that includes a start in a (---) page but ends in a valid page.
        """
        yaml = "minidump_mem64.yaml"
        proc = self.process_from_yaml(yaml)
        new_minidump_path = self.getBuildArtifact(__name__ + ".dmp")
        options = lldb.SBSaveCoreOptions()
        options.SetOutputFile(lldb.SBFileSpec(new_minidump_path))
        options.SetPluginName("minidump")
        options.SetStyle(lldb.eSaveCoreCustomOnly)

        size = 0x1000
        begin = 0x5000
        end = begin + size
        custom_range = lldb.SBMemoryRegionInfo("", begin, end, 3, True, False)
        options.AddMemoryRegionToSave(custom_range)

        error = proc.SaveCore(options)
        self.assertTrue(error.Success(), error.GetCString())
        core_target = self.dbg.CreateTarget(None)
        core_process = core_target.LoadCore(new_minidump_path)

        expected_address_range = AddressRange(begin, end)
        self.validate_regions_saved_correctly(core_process, expected_address_range)

    @skipIfWindows
    def test_region_spans_multiple_regions_with_one_subrange(self):
        """
        Validate we can save a Minidump for a custom region
        that includes a start in a (---) page but ends in a valid page.
        """
        yaml = "minidump_mem64.yaml"
        proc = self.process_from_yaml(yaml)
        new_minidump_path = self.getBuildArtifact(__name__ + ".dmp")
        options = lldb.SBSaveCoreOptions()
        options.SetOutputFile(lldb.SBFileSpec(new_minidump_path))
        options.SetPluginName("minidump")
        options.SetStyle(lldb.eSaveCoreCustomOnly)

        size = 0x800
        begin = 0x5000
        end = begin + size
        custom_range = lldb.SBMemoryRegionInfo("", begin, end, 3, True, False)
        options.AddMemoryRegionToSave(custom_range)

        error = proc.SaveCore(options)
        self.assertTrue(error.Success(), error.GetCString())
        core_target = self.dbg.CreateTarget(None)
        core_process = core_target.LoadCore(new_minidump_path)

        expected_address_range = AddressRange(begin, end)
        expected_invalid_range = AddressRange(begin, begin + 0x1000)
        self.validate_regions_saved_correctly(
            core_process, expected_address_range, expected_invalid_range
        )
