| //===-- PathMappingTests.cpp ------------------------*- C++ -*-----------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "PathMapping.h" |
| #include "llvm/Support/JSON.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include <string> |
| namespace clang { |
| namespace clangd { |
| namespace { |
| using ::testing::ElementsAre; |
| MATCHER_P2(Mapping, ClientPath, ServerPath, "") { |
| return arg.ClientPath == ClientPath && arg.ServerPath == ServerPath; |
| } |
| |
| bool failedParse(llvm::StringRef RawMappings) { |
| llvm::Expected<PathMappings> Mappings = parsePathMappings(RawMappings); |
| if (!Mappings) { |
| consumeError(Mappings.takeError()); |
| return true; |
| } |
| return false; |
| } |
| |
| TEST(ParsePathMappingTests, WindowsPath) { |
| // Relative path to C drive |
| EXPECT_TRUE(failedParse(R"(C:a=/root)")); |
| EXPECT_TRUE(failedParse(R"(\C:a=/root)")); |
| // Relative path to current drive. |
| EXPECT_TRUE(failedParse(R"(\a=/root)")); |
| // Absolute paths |
| llvm::Expected<PathMappings> ParsedMappings = |
| parsePathMappings(R"(C:\a=/root)"); |
| ASSERT_TRUE(bool(ParsedMappings)); |
| EXPECT_THAT(*ParsedMappings, ElementsAre(Mapping("/C:/a", "/root"))); |
| // Absolute UNC path |
| ParsedMappings = parsePathMappings(R"(\\Server\C$=/root)"); |
| ASSERT_TRUE(bool(ParsedMappings)); |
| EXPECT_THAT(*ParsedMappings, ElementsAre(Mapping("//Server/C$", "/root"))); |
| } |
| |
| TEST(ParsePathMappingTests, UnixPath) { |
| // Relative unix path |
| EXPECT_TRUE(failedParse("a/b=/root")); |
| // Absolute unix path |
| llvm::Expected<PathMappings> ParsedMappings = parsePathMappings("/A/b=/root"); |
| ASSERT_TRUE(bool(ParsedMappings)); |
| EXPECT_THAT(*ParsedMappings, ElementsAre(Mapping("/A/b", "/root"))); |
| // Absolute unix path w/ backslash |
| ParsedMappings = parsePathMappings(R"(/a/b\\ar=/root)"); |
| ASSERT_TRUE(bool(ParsedMappings)); |
| EXPECT_THAT(*ParsedMappings, ElementsAre(Mapping(R"(/a/b\\ar)", "/root"))); |
| } |
| |
| TEST(ParsePathMappingTests, ImproperFormat) { |
| // uneven mappings |
| EXPECT_TRUE(failedParse("/home/myuser1=")); |
| // mappings need to be absolute |
| EXPECT_TRUE(failedParse("home/project=/workarea/project")); |
| // duplicate delimiter |
| EXPECT_TRUE(failedParse("/home==/workarea")); |
| // no delimiter |
| EXPECT_TRUE(failedParse("/home")); |
| // improper delimiter |
| EXPECT_TRUE(failedParse("/home,/workarea")); |
| } |
| |
| TEST(ParsePathMappingTests, ParsesMultiple) { |
| std::string RawPathMappings = |
| "/home/project=/workarea/project,/home/project/.includes=/opt/include"; |
| auto Parsed = parsePathMappings(RawPathMappings); |
| ASSERT_TRUE(bool(Parsed)); |
| EXPECT_THAT(*Parsed, |
| ElementsAre(Mapping("/home/project", "/workarea/project"), |
| Mapping("/home/project/.includes", "/opt/include"))); |
| } |
| |
| bool mapsProperly(llvm::StringRef Orig, llvm::StringRef Expected, |
| llvm::StringRef RawMappings, PathMapping::Direction Dir) { |
| llvm::Expected<PathMappings> Mappings = parsePathMappings(RawMappings); |
| if (!Mappings) |
| return false; |
| llvm::Optional<std::string> MappedPath = doPathMapping(Orig, Dir, *Mappings); |
| std::string Actual = MappedPath ? *MappedPath : Orig.str(); |
| EXPECT_STREQ(Expected.str().c_str(), Actual.c_str()); |
| return Expected == Actual; |
| } |
| |
| TEST(DoPathMappingTests, PreservesOriginal) { |
| // Preserves original path when no mapping |
| EXPECT_TRUE(mapsProperly("file:///home", "file:///home", "", |
| PathMapping::Direction::ClientToServer)); |
| } |
| |
| TEST(DoPathMappingTests, UsesFirstMatch) { |
| EXPECT_TRUE(mapsProperly("file:///home/foo.cpp", "file:///workarea1/foo.cpp", |
| "/home=/workarea1,/home=/workarea2", |
| PathMapping::Direction::ClientToServer)); |
| } |
| |
| TEST(DoPathMappingTests, IgnoresSubstrings) { |
| // Doesn't map substrings that aren't a proper path prefix |
| EXPECT_TRUE(mapsProperly("file://home/foo-bar.cpp", "file://home/foo-bar.cpp", |
| "/home/foo=/home/bar", |
| PathMapping::Direction::ClientToServer)); |
| } |
| |
| TEST(DoPathMappingTests, MapsOutgoingPaths) { |
| // When IsIncoming is false (i.e.a response), map the other way |
| EXPECT_TRUE(mapsProperly("file:///workarea/foo.cpp", "file:///home/foo.cpp", |
| "/home=/workarea", |
| PathMapping::Direction::ServerToClient)); |
| } |
| |
| TEST(DoPathMappingTests, OnlyMapFileUris) { |
| EXPECT_TRUE(mapsProperly("test:///home/foo.cpp", "test:///home/foo.cpp", |
| "/home=/workarea", |
| PathMapping::Direction::ClientToServer)); |
| } |
| |
| TEST(DoPathMappingTests, RespectsCaseSensitivity) { |
| EXPECT_TRUE(mapsProperly("file:///HOME/foo.cpp", "file:///HOME/foo.cpp", |
| "/home=/workarea", |
| PathMapping::Direction::ClientToServer)); |
| } |
| |
| TEST(DoPathMappingTests, MapsWindowsPaths) { |
| // Maps windows properly |
| EXPECT_TRUE(mapsProperly("file:///C:/home/foo.cpp", |
| "file:///C:/workarea/foo.cpp", R"(C:\home=C:\workarea)", |
| PathMapping::Direction::ClientToServer)); |
| } |
| |
| TEST(DoPathMappingTests, MapsWindowsUnixInterop) { |
| // Path mappings with a windows-style client path and unix-style server path |
| EXPECT_TRUE(mapsProperly( |
| "file:///C:/home/foo.cpp", "file:///workarea/foo.cpp", |
| R"(C:\home=/workarea)", PathMapping::Direction::ClientToServer)); |
| } |
| |
| TEST(ApplyPathMappingTests, PreservesOriginalParams) { |
| auto Params = llvm::json::parse(R"({ |
| "textDocument": {"uri": "file:///home/foo.cpp"}, |
| "position": {"line": 0, "character": 0} |
| })"); |
| ASSERT_TRUE(bool(Params)); |
| llvm::json::Value ExpectedParams = *Params; |
| PathMappings Mappings; |
| applyPathMappings(*Params, PathMapping::Direction::ClientToServer, Mappings); |
| EXPECT_EQ(*Params, ExpectedParams); |
| } |
| |
| TEST(ApplyPathMappingTests, MapsAllMatchingPaths) { |
| // Handles nested objects and array values |
| auto Params = llvm::json::parse(R"({ |
| "rootUri": {"uri": "file:///home/foo.cpp"}, |
| "workspaceFolders": ["file:///home/src", "file:///tmp"] |
| })"); |
| auto ExpectedParams = llvm::json::parse(R"({ |
| "rootUri": {"uri": "file:///workarea/foo.cpp"}, |
| "workspaceFolders": ["file:///workarea/src", "file:///tmp"] |
| })"); |
| auto Mappings = parsePathMappings("/home=/workarea"); |
| ASSERT_TRUE(bool(Params) && bool(ExpectedParams) && bool(Mappings)); |
| applyPathMappings(*Params, PathMapping::Direction::ClientToServer, *Mappings); |
| EXPECT_EQ(*Params, *ExpectedParams); |
| } |
| |
| TEST(ApplyPathMappingTests, MapsOutbound) { |
| auto Params = llvm::json::parse(R"({ |
| "id": 1, |
| "result": [ |
| {"uri": "file:///opt/include/foo.h"}, |
| {"uri": "file:///workarea/src/foo.cpp"}] |
| })"); |
| auto ExpectedParams = llvm::json::parse(R"({ |
| "id": 1, |
| "result": [ |
| {"uri": "file:///home/.includes/foo.h"}, |
| {"uri": "file:///home/src/foo.cpp"}] |
| })"); |
| auto Mappings = |
| parsePathMappings("/home=/workarea,/home/.includes=/opt/include"); |
| ASSERT_TRUE(bool(Params) && bool(ExpectedParams) && bool(Mappings)); |
| applyPathMappings(*Params, PathMapping::Direction::ServerToClient, *Mappings); |
| EXPECT_EQ(*Params, *ExpectedParams); |
| } |
| |
| TEST(ApplyPathMappingTests, MapsKeys) { |
| auto Params = llvm::json::parse(R"({ |
| "changes": { |
| "file:///home/foo.cpp": {"newText": "..."}, |
| "file:///home/src/bar.cpp": {"newText": "..."} |
| } |
| })"); |
| auto ExpectedParams = llvm::json::parse(R"({ |
| "changes": { |
| "file:///workarea/foo.cpp": {"newText": "..."}, |
| "file:///workarea/src/bar.cpp": {"newText": "..."} |
| } |
| })"); |
| auto Mappings = parsePathMappings("/home=/workarea"); |
| ASSERT_TRUE(bool(Params) && bool(ExpectedParams) && bool(Mappings)); |
| applyPathMappings(*Params, PathMapping::Direction::ClientToServer, *Mappings); |
| EXPECT_EQ(*Params, *ExpectedParams); |
| } |
| |
| } // namespace |
| } // namespace clangd |
| } // namespace clang |