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


class TestTargetSourceMap(TestBase):
    @no_debug_info_test
    def test_source_map_via_setting_api(self):
        """
        Test that ensures SBDebugger::GetSetting("target.source-map") API
        can correctly fetch source mapping entries.
        """
        # Set the target soure map to map "./" to the current test directory
        src_dir = self.getSourceDir()

        source_map_setting_path = "target.source-map"
        initial_source_map = self.dbg.GetSetting(source_map_setting_path)
        self.assertEqual(
            initial_source_map.GetSize(), 0, "Initial source map should be empty"
        )

        src_dir = self.getSourceDir()
        self.runCmd('settings set %s . "%s"' % (source_map_setting_path, src_dir))

        source_map = self.dbg.GetSetting(source_map_setting_path)
        self.assertEqual(
            source_map.GetSize(), 1, "source map should be have one appended entry"
        )

        stream = lldb.SBStream()
        source_map.GetAsJSON(stream)
        serialized_source_map = json.loads(stream.GetData())

        self.assertEqual(
            len(serialized_source_map[0]), 2, "source map entry should have two parts"
        )
        self.assertEqual(
            serialized_source_map[0][0],
            ".",
            "source map entry's first part does not match",
        )
        self.assertEqual(
            serialized_source_map[0][1],
            src_dir,
            "source map entry's second part does not match",
        )

    @no_debug_info_test
    def test_source_map(self):
        """Test target.source-map' functionality."""

        def assertBreakpointWithSourceMap(src_path):
            # Set a breakpoint after we remap source and verify that it succeeds
            bp = target.BreakpointCreateByLocation(src_path, 2)
            self.assertEqual(
                bp.GetNumLocations(), 1, "make sure breakpoint was resolved with map"
            )

            # Now make sure that we can actually FIND the source file using this
            # remapping:
            retval = lldb.SBCommandReturnObject()
            self.dbg.GetCommandInterpreter().HandleCommand(
                "source list -f main.c -l 2", retval
            )
            self.assertTrue(retval.Succeeded(), "source list didn't succeed.")
            self.assertNotEqual(
                retval.GetOutput(), None, "We got no ouput from source list"
            )
            self.assertIn(
                "return", retval.GetOutput(), "We didn't find the source file..."
            )

        # Set the target soure map to map "./" to the current test directory
        src_dir = self.getSourceDir()
        src_path = os.path.join(src_dir, "main.c")
        yaml_path = os.path.join(src_dir, "a.yaml")
        yaml_base, ext = os.path.splitext(yaml_path)
        obj_path = self.getBuildArtifact("main.o")
        self.yaml2obj(yaml_path, obj_path)

        # Create a target with the object file we just created from YAML
        target = self.dbg.CreateTarget(obj_path)

        # Set a breakpoint before we remap source and verify that it fails
        bp = target.BreakpointCreateByLocation(src_path, 2)
        self.assertEqual(
            bp.GetNumLocations(),
            0,
            "make sure no breakpoints were resolved without map",
        )

        valid_path = os.path.dirname(src_dir)
        valid_path2 = os.path.dirname(valid_path)
        invalid_path = src_dir + "invalid_path"
        invalid_path2 = src_dir + "invalid_path2"

        # We make sure the error message contains all the invalid paths
        self.expect(
            'settings set target.source-map . "%s" . "%s" . "%s" . "%s'
            % (invalid_path, src_dir, invalid_path2, valid_path),
            substrs=[
                'error: the replacement path doesn\'t exist: "%s"' % (invalid_path),
                'the replacement path doesn\'t exist: "%s"' % (invalid_path2),
            ],
            error=True,
        )
        self.expect(
            "settings show target.source-map",
            substrs=[
                '[0] "." -> "%s"' % (src_dir),
                '[1] "." -> "%s"' % (valid_path),
            ],
        )
        assertBreakpointWithSourceMap(src_path)

        # Attempts to replace an index to an invalid mapping should have no effect.
        # Modifications to valid mappings should work.
        self.expect(
            'settings replace target.source-map 0 . "%s" . "%s"'
            % (invalid_path, valid_path2),
            substrs=[
                'error: the replacement path doesn\'t exist: "%s"' % (invalid_path),
            ],
            error=True,
        )
        self.expect(
            "settings show target.source-map",
            substrs=[
                '[0] "." -> "%s"' % (src_dir),
                '[1] "." -> "%s"' % (valid_path2),
            ],
        )
        assertBreakpointWithSourceMap(src_path)

        # Let's clear and add the mapping back with insert-after
        self.runCmd("settings remove target.source-map 0")
        self.expect(
            "settings show target.source-map",
            substrs=['[0] "." -> "%s"' % (valid_path2)],
        )

        self.expect(
            'settings insert-after target.source-map 0 . "%s" . "%s" . "%s"'
            % (invalid_path, invalid_path2, src_dir),
            substrs=[
                'error: the replacement path doesn\'t exist: "%s"' % (invalid_path),
                'the replacement path doesn\'t exist: "%s"' % (invalid_path2),
            ],
            error=True,
        )
        self.expect(
            "settings show target.source-map",
            substrs=[
                '[0] "." -> "%s"' % (valid_path2),
                '[1] "." -> "%s"' % (src_dir),
            ],
        )

        # Let's clear using remove and add the mapping in with append
        self.runCmd("settings remove target.source-map 1")
        self.expect(
            "settings show target.source-map",
            substrs=[
                '[0] "." -> "%s"' % (valid_path2),
            ],
        )
        self.runCmd("settings clear target.source-map")
        self.expect(
            'settings append target.source-map . "%s" . "%s" . "%s"'
            % (invalid_path, src_dir, invalid_path2),
            substrs=[
                'error: the replacement path doesn\'t exist: "%s"' % (invalid_path),
                'the replacement path doesn\'t exist: "%s"' % (invalid_path2),
            ],
            error=True,
        )
        self.expect(
            "settings show target.source-map",
            substrs=[
                '[0] "." -> "%s"' % (src_dir),
            ],
        )
        assertBreakpointWithSourceMap(src_path)
