xref: /llvm-project/clang-tools-extra/clangd/unittests/PathMappingTests.cpp (revision 2c675be9b232c1d0b5c55cbcb196e71036c681ea)
1 //===-- PathMappingTests.cpp  ------------------------*- C++ -*-----------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "PathMapping.h"
10 #include "llvm/Support/JSON.h"
11 #include "gmock/gmock.h"
12 #include "gtest/gtest.h"
13 #include <optional>
14 #include <string>
15 namespace clang {
16 namespace clangd {
17 namespace {
18 using ::testing::ElementsAre;
19 MATCHER_P2(Mapping, ClientPath, ServerPath, "") {
20   return arg.ClientPath == ClientPath && arg.ServerPath == ServerPath;
21 }
22 
failedParse(llvm::StringRef RawMappings)23 bool failedParse(llvm::StringRef RawMappings) {
24   llvm::Expected<PathMappings> Mappings = parsePathMappings(RawMappings);
25   if (!Mappings) {
26     consumeError(Mappings.takeError());
27     return true;
28   }
29   return false;
30 }
31 
TEST(ParsePathMappingTests,WindowsPath)32 TEST(ParsePathMappingTests, WindowsPath) {
33   // Relative path to C drive
34   EXPECT_TRUE(failedParse(R"(C:a=/root)"));
35   EXPECT_TRUE(failedParse(R"(\C:a=/root)"));
36   // Relative path to current drive.
37   EXPECT_TRUE(failedParse(R"(\a=/root)"));
38   // Absolute paths
39   llvm::Expected<PathMappings> ParsedMappings =
40       parsePathMappings(R"(C:\a=/root)");
41   ASSERT_TRUE(bool(ParsedMappings));
42   EXPECT_THAT(*ParsedMappings, ElementsAre(Mapping("/C:/a", "/root")));
43   // Absolute UNC path
44   ParsedMappings = parsePathMappings(R"(\\Server\C$=/root)");
45   ASSERT_TRUE(bool(ParsedMappings));
46   EXPECT_THAT(*ParsedMappings, ElementsAre(Mapping("//Server/C$", "/root")));
47 }
48 
TEST(ParsePathMappingTests,UnixPath)49 TEST(ParsePathMappingTests, UnixPath) {
50   // Relative unix path
51   EXPECT_TRUE(failedParse("a/b=/root"));
52   // Absolute unix path
53   llvm::Expected<PathMappings> ParsedMappings = parsePathMappings("/A/b=/root");
54   ASSERT_TRUE(bool(ParsedMappings));
55   EXPECT_THAT(*ParsedMappings, ElementsAre(Mapping("/A/b", "/root")));
56   // Absolute unix path w/ backslash
57   ParsedMappings = parsePathMappings(R"(/a/b\\ar=/root)");
58   ASSERT_TRUE(bool(ParsedMappings));
59   EXPECT_THAT(*ParsedMappings, ElementsAre(Mapping(R"(/a/b\\ar)", "/root")));
60 }
61 
TEST(ParsePathMappingTests,ImproperFormat)62 TEST(ParsePathMappingTests, ImproperFormat) {
63   // uneven mappings
64   EXPECT_TRUE(failedParse("/home/myuser1="));
65   // mappings need to be absolute
66   EXPECT_TRUE(failedParse("home/project=/workarea/project"));
67   // duplicate delimiter
68   EXPECT_TRUE(failedParse("/home==/workarea"));
69   // no delimiter
70   EXPECT_TRUE(failedParse("/home"));
71   // improper delimiter
72   EXPECT_TRUE(failedParse("/home,/workarea"));
73 }
74 
TEST(ParsePathMappingTests,ParsesMultiple)75 TEST(ParsePathMappingTests, ParsesMultiple) {
76   std::string RawPathMappings =
77       "/home/project=/workarea/project,/home/project/.includes=/opt/include";
78   auto Parsed = parsePathMappings(RawPathMappings);
79   ASSERT_TRUE(bool(Parsed));
80   EXPECT_THAT(*Parsed,
81               ElementsAre(Mapping("/home/project", "/workarea/project"),
82                           Mapping("/home/project/.includes", "/opt/include")));
83 }
84 
mapsProperly(llvm::StringRef Orig,llvm::StringRef Expected,llvm::StringRef RawMappings,PathMapping::Direction Dir)85 bool mapsProperly(llvm::StringRef Orig, llvm::StringRef Expected,
86                   llvm::StringRef RawMappings, PathMapping::Direction Dir) {
87   llvm::Expected<PathMappings> Mappings = parsePathMappings(RawMappings);
88   if (!Mappings)
89     return false;
90   std::optional<std::string> MappedPath = doPathMapping(Orig, Dir, *Mappings);
91   std::string Actual = MappedPath ? *MappedPath : Orig.str();
92   EXPECT_STREQ(Expected.str().c_str(), Actual.c_str());
93   return Expected == Actual;
94 }
95 
TEST(DoPathMappingTests,PreservesOriginal)96 TEST(DoPathMappingTests, PreservesOriginal) {
97   // Preserves original path when no mapping
98   EXPECT_TRUE(mapsProperly("file:///home", "file:///home", "",
99                            PathMapping::Direction::ClientToServer));
100 }
101 
TEST(DoPathMappingTests,UsesFirstMatch)102 TEST(DoPathMappingTests, UsesFirstMatch) {
103   EXPECT_TRUE(mapsProperly("file:///home/foo.cpp", "file:///workarea1/foo.cpp",
104                            "/home=/workarea1,/home=/workarea2",
105                            PathMapping::Direction::ClientToServer));
106 }
107 
TEST(DoPathMappingTests,IgnoresSubstrings)108 TEST(DoPathMappingTests, IgnoresSubstrings) {
109   // Doesn't map substrings that aren't a proper path prefix
110   EXPECT_TRUE(mapsProperly("file://home/foo-bar.cpp", "file://home/foo-bar.cpp",
111                            "/home/foo=/home/bar",
112                            PathMapping::Direction::ClientToServer));
113 }
114 
TEST(DoPathMappingTests,MapsOutgoingPaths)115 TEST(DoPathMappingTests, MapsOutgoingPaths) {
116   // When IsIncoming is false (i.e.a  response), map the other way
117   EXPECT_TRUE(mapsProperly("file:///workarea/foo.cpp", "file:///home/foo.cpp",
118                            "/home=/workarea",
119                            PathMapping::Direction::ServerToClient));
120 }
121 
TEST(DoPathMappingTests,OnlyMapFileUris)122 TEST(DoPathMappingTests, OnlyMapFileUris) {
123   EXPECT_TRUE(mapsProperly("test:///home/foo.cpp", "test:///home/foo.cpp",
124                            "/home=/workarea",
125                            PathMapping::Direction::ClientToServer));
126 }
127 
TEST(DoPathMappingTests,RespectsCaseSensitivity)128 TEST(DoPathMappingTests, RespectsCaseSensitivity) {
129   EXPECT_TRUE(mapsProperly("file:///HOME/foo.cpp", "file:///HOME/foo.cpp",
130                            "/home=/workarea",
131                            PathMapping::Direction::ClientToServer));
132 }
133 
TEST(DoPathMappingTests,MapsWindowsPaths)134 TEST(DoPathMappingTests, MapsWindowsPaths) {
135   // Maps windows properly
136   EXPECT_TRUE(mapsProperly("file:///C:/home/foo.cpp",
137                            "file:///C:/workarea/foo.cpp", R"(C:\home=C:\workarea)",
138                            PathMapping::Direction::ClientToServer));
139 }
140 
TEST(DoPathMappingTests,MapsWindowsUnixInterop)141 TEST(DoPathMappingTests, MapsWindowsUnixInterop) {
142   // Path mappings with a windows-style client path and unix-style server path
143   EXPECT_TRUE(mapsProperly(
144       "file:///C:/home/foo.cpp", "file:///workarea/foo.cpp",
145       R"(C:\home=/workarea)", PathMapping::Direction::ClientToServer));
146 }
147 
TEST(ApplyPathMappingTests,PreservesOriginalParams)148 TEST(ApplyPathMappingTests, PreservesOriginalParams) {
149   auto Params = llvm::json::parse(R"({
150     "textDocument": {"uri": "file:///home/foo.cpp"},
151     "position": {"line": 0, "character": 0}
152   })");
153   ASSERT_TRUE(bool(Params));
154   llvm::json::Value ExpectedParams = *Params;
155   PathMappings Mappings;
156   applyPathMappings(*Params, PathMapping::Direction::ClientToServer, Mappings);
157   EXPECT_EQ(*Params, ExpectedParams);
158 }
159 
TEST(ApplyPathMappingTests,MapsAllMatchingPaths)160 TEST(ApplyPathMappingTests, MapsAllMatchingPaths) {
161   // Handles nested objects and array values
162   auto Params = llvm::json::parse(R"({
163     "rootUri": {"uri": "file:///home/foo.cpp"},
164     "workspaceFolders": ["file:///home/src", "file:///tmp"]
165   })");
166   auto ExpectedParams = llvm::json::parse(R"({
167     "rootUri": {"uri": "file:///workarea/foo.cpp"},
168     "workspaceFolders": ["file:///workarea/src", "file:///tmp"]
169   })");
170   auto Mappings = parsePathMappings("/home=/workarea");
171   ASSERT_TRUE(bool(Params) && bool(ExpectedParams) && bool(Mappings));
172   applyPathMappings(*Params, PathMapping::Direction::ClientToServer, *Mappings);
173   EXPECT_EQ(*Params, *ExpectedParams);
174 }
175 
TEST(ApplyPathMappingTests,MapsOutbound)176 TEST(ApplyPathMappingTests, MapsOutbound) {
177   auto Params = llvm::json::parse(R"({
178     "id": 1,
179     "result": [
180       {"uri": "file:///opt/include/foo.h"},
181       {"uri": "file:///workarea/src/foo.cpp"}]
182   })");
183   auto ExpectedParams = llvm::json::parse(R"({
184     "id": 1,
185     "result": [
186       {"uri": "file:///home/.includes/foo.h"},
187       {"uri": "file:///home/src/foo.cpp"}]
188   })");
189   auto Mappings =
190       parsePathMappings("/home=/workarea,/home/.includes=/opt/include");
191   ASSERT_TRUE(bool(Params) && bool(ExpectedParams) && bool(Mappings));
192   applyPathMappings(*Params, PathMapping::Direction::ServerToClient, *Mappings);
193   EXPECT_EQ(*Params, *ExpectedParams);
194 }
195 
TEST(ApplyPathMappingTests,MapsKeys)196 TEST(ApplyPathMappingTests, MapsKeys) {
197   auto Params = llvm::json::parse(R"({
198     "changes": {
199       "file:///home/foo.cpp": {"newText": "..."},
200       "file:///home/src/bar.cpp": {"newText": "..."}
201     }
202   })");
203   auto ExpectedParams = llvm::json::parse(R"({
204     "changes": {
205       "file:///workarea/foo.cpp": {"newText": "..."},
206       "file:///workarea/src/bar.cpp": {"newText": "..."}
207     }
208   })");
209   auto Mappings = parsePathMappings("/home=/workarea");
210   ASSERT_TRUE(bool(Params) && bool(ExpectedParams) && bool(Mappings));
211   applyPathMappings(*Params, PathMapping::Direction::ClientToServer, *Mappings);
212   EXPECT_EQ(*Params, *ExpectedParams);
213 }
214 
215 } // namespace
216 } // namespace clangd
217 } // namespace clang
218