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