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>
15namespace clang {
16namespace clangd {
17namespace {
18using ::testing::ElementsAre;
19MATCHER_P2(Mapping, ClientPath, ServerPath, "") {
20 return arg.ClientPath == ClientPath && arg.ServerPath == ServerPath;
21}
22
23bool failedParse(llvm::StringRef RawMappings) {
24 llvm::Expected<PathMappings> Mappings = parsePathMappings(RawPathMappings: RawMappings);
25 if (!Mappings) {
26 consumeError(Err: Mappings.takeError());
27 return true;
28 }
29 return false;
30}
31
32TEST(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(RawPathMappings: 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(RawPathMappings: R"(\\Server\C$=/root)");
45 ASSERT_TRUE(bool(ParsedMappings));
46 EXPECT_THAT(*ParsedMappings, ElementsAre(Mapping("//Server/C$", "/root")));
47}
48
49TEST(ParsePathMappingTests, UnixPath) {
50 // Relative unix path
51 EXPECT_TRUE(failedParse("a/b=/root"));
52 // Absolute unix path
53 llvm::Expected<PathMappings> ParsedMappings = parsePathMappings(RawPathMappings: "/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(RawPathMappings: R"(/a/b\\ar=/root)");
58 ASSERT_TRUE(bool(ParsedMappings));
59 EXPECT_THAT(*ParsedMappings, ElementsAre(Mapping(R"(/a/b\\ar)", "/root")));
60}
61
62TEST(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
75TEST(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
85bool mapsProperly(llvm::StringRef Orig, llvm::StringRef Expected,
86 llvm::StringRef RawMappings, PathMapping::Direction Dir) {
87 llvm::Expected<PathMappings> Mappings = parsePathMappings(RawPathMappings: RawMappings);
88 if (!Mappings)
89 return false;
90 std::optional<std::string> MappedPath = doPathMapping(S: Orig, Dir, Mappings: *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
96TEST(DoPathMappingTests, PreservesOriginal) {
97 // Preserves original path when no mapping
98 EXPECT_TRUE(mapsProperly("file:///home", "file:///home", "",
99 PathMapping::Direction::ClientToServer));
100}
101
102TEST(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
108TEST(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
115TEST(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
122TEST(DoPathMappingTests, OnlyMapFileUris) {
123 EXPECT_TRUE(mapsProperly("test:///home/foo.cpp", "test:///home/foo.cpp",
124 "/home=/workarea",
125 PathMapping::Direction::ClientToServer));
126}
127
128TEST(DoPathMappingTests, RespectsCaseSensitivity) {
129 EXPECT_TRUE(mapsProperly("file:///HOME/foo.cpp", "file:///HOME/foo.cpp",
130 "/home=/workarea",
131 PathMapping::Direction::ClientToServer));
132}
133
134TEST(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
141TEST(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
148TEST(ApplyPathMappingTests, PreservesOriginalParams) {
149 auto Params = llvm::json::parse(JSON: 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&: *Params, Dir: PathMapping::Direction::ClientToServer, Mappings);
157 EXPECT_EQ(*Params, ExpectedParams);
158}
159
160TEST(ApplyPathMappingTests, MapsAllMatchingPaths) {
161 // Handles nested objects and array values
162 auto Params = llvm::json::parse(JSON: R"({
163 "rootUri": {"uri": "file:///home/foo.cpp"},
164 "workspaceFolders": ["file:///home/src", "file:///tmp"]
165 })");
166 auto ExpectedParams = llvm::json::parse(JSON: R"({
167 "rootUri": {"uri": "file:///workarea/foo.cpp"},
168 "workspaceFolders": ["file:///workarea/src", "file:///tmp"]
169 })");
170 auto Mappings = parsePathMappings(RawPathMappings: "/home=/workarea");
171 ASSERT_TRUE(bool(Params) && bool(ExpectedParams) && bool(Mappings));
172 applyPathMappings(Params&: *Params, Dir: PathMapping::Direction::ClientToServer, Mappings: *Mappings);
173 EXPECT_EQ(*Params, *ExpectedParams);
174}
175
176TEST(ApplyPathMappingTests, MapsOutbound) {
177 auto Params = llvm::json::parse(JSON: 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(JSON: 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(RawPathMappings: "/home=/workarea,/home/.includes=/opt/include");
191 ASSERT_TRUE(bool(Params) && bool(ExpectedParams) && bool(Mappings));
192 applyPathMappings(Params&: *Params, Dir: PathMapping::Direction::ServerToClient, Mappings: *Mappings);
193 EXPECT_EQ(*Params, *ExpectedParams);
194}
195
196TEST(ApplyPathMappingTests, MapsKeys) {
197 auto Params = llvm::json::parse(JSON: R"({
198 "changes": {
199 "file:///home/foo.cpp": {"newText": "..."},
200 "file:///home/src/bar.cpp": {"newText": "..."}
201 }
202 })");
203 auto ExpectedParams = llvm::json::parse(JSON: R"({
204 "changes": {
205 "file:///workarea/foo.cpp": {"newText": "..."},
206 "file:///workarea/src/bar.cpp": {"newText": "..."}
207 }
208 })");
209 auto Mappings = parsePathMappings(RawPathMappings: "/home=/workarea");
210 ASSERT_TRUE(bool(Params) && bool(ExpectedParams) && bool(Mappings));
211 applyPathMappings(Params&: *Params, Dir: PathMapping::Direction::ClientToServer, Mappings: *Mappings);
212 EXPECT_EQ(*Params, *ExpectedParams);
213}
214
215} // namespace
216} // namespace clangd
217} // namespace clang
218

source code of clang-tools-extra/clangd/unittests/PathMappingTests.cpp