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