xref: /llvm-project/clang-tools-extra/clangd/unittests/remote/MarshallingTests.cpp (revision f49a7ad8c0854a01b945c27de2fd313b9013ae0d)
1 //===--- MarshallingTests.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 "../TestTU.h"
10 #include "TestFS.h"
11 #include "index/Index.h"
12 #include "index/Ref.h"
13 #include "index/Serialization.h"
14 #include "index/Symbol.h"
15 #include "index/SymbolID.h"
16 #include "index/SymbolLocation.h"
17 #include "index/remote/marshalling/Marshalling.h"
18 #include "clang/Index/IndexSymbol.h"
19 #include "llvm/ADT/SmallString.h"
20 #include "llvm/ADT/StringRef.h"
21 #include "llvm/Support/Error.h"
22 #include "llvm/Support/Path.h"
23 #include "llvm/Support/StringSaver.h"
24 #include "gmock/gmock.h"
25 #include "gtest/gtest.h"
26 #include <cstring>
27 
28 namespace clang {
29 namespace clangd {
30 namespace remote {
31 namespace {
32 
33 using llvm::sys::path::convert_to_slash;
34 
35 const char *testPathURI(llvm::StringRef Path,
36                         llvm::UniqueStringSaver &Strings) {
37   auto URI = URI::createFile(testPath(Path));
38   return Strings.save(URI.toString()).begin();
39 }
40 
41 TEST(RemoteMarshallingTest, URITranslation) {
42   llvm::BumpPtrAllocator Arena;
43   llvm::UniqueStringSaver Strings(Arena);
44   Marshaller ProtobufMarshaller(
45       testPath("remote/machine/projects/llvm-project/"),
46       testPath("home/my-projects/llvm-project/"));
47   clangd::Ref Original;
48   Original.Location.FileURI =
49       testPathURI("remote/machine/projects/llvm-project/clang-tools-extra/"
50                   "clangd/unittests/remote/MarshallingTests.cpp",
51                   Strings);
52   auto Serialized = ProtobufMarshaller.toProtobuf(Original);
53   ASSERT_TRUE(Serialized);
54   EXPECT_EQ(Serialized->location().file_path(),
55             "clang-tools-extra/clangd/unittests/remote/MarshallingTests.cpp");
56   auto Deserialized = ProtobufMarshaller.fromProtobuf(*Serialized);
57   ASSERT_TRUE(Deserialized);
58   EXPECT_STREQ(Deserialized->Location.FileURI,
59                testPathURI("home/my-projects/llvm-project/clang-tools-extra/"
60                            "clangd/unittests/remote/MarshallingTests.cpp",
61                            Strings));
62 
63   // Can't have empty paths.
64   *Serialized->mutable_location()->mutable_file_path() = std::string();
65   EXPECT_FALSE(ProtobufMarshaller.fromProtobuf(*Serialized));
66 
67   clangd::Ref WithInvalidURI;
68   // Invalid URI results in serialization failure.
69   WithInvalidURI.Location.FileURI = "This is not a URI";
70   EXPECT_FALSE(ProtobufMarshaller.toProtobuf(WithInvalidURI));
71 
72   // Can not use URIs with scheme different from "file".
73   auto UnittestURI =
74       URI::create(testPath("project/lib/HelloWorld.cpp"), "unittest");
75   ASSERT_TRUE(bool(UnittestURI));
76   WithInvalidURI.Location.FileURI =
77       Strings.save(UnittestURI->toString()).begin();
78   EXPECT_FALSE(ProtobufMarshaller.toProtobuf(WithInvalidURI));
79 
80   // Paths transmitted over the wire can not be absolute, they have to be
81   // relative.
82   Ref WithAbsolutePath;
83   *WithAbsolutePath.mutable_location()->mutable_file_path() =
84       "/usr/local/user/home/HelloWorld.cpp";
85   EXPECT_FALSE(ProtobufMarshaller.fromProtobuf(WithAbsolutePath));
86 }
87 
88 TEST(RemoteMarshallingTest, SymbolSerialization) {
89   clangd::Symbol Sym;
90 
91   auto ID = SymbolID::fromStr("057557CEBF6E6B2D");
92   ASSERT_TRUE(bool(ID));
93   Sym.ID = *ID;
94 
95   index::SymbolInfo Info;
96   Info.Kind = index::SymbolKind::Function;
97   Info.SubKind = index::SymbolSubKind::AccessorGetter;
98   Info.Lang = index::SymbolLanguage::CXX;
99   Info.Properties = static_cast<index::SymbolPropertySet>(
100       index::SymbolProperty::TemplateSpecialization);
101   Sym.SymInfo = Info;
102 
103   llvm::BumpPtrAllocator Arena;
104   llvm::UniqueStringSaver Strings(Arena);
105 
106   Sym.Name = Strings.save("Foo");
107   Sym.Scope = Strings.save("llvm::foo::bar::");
108 
109   clangd::SymbolLocation Location;
110   Location.Start.setLine(1);
111   Location.Start.setColumn(15);
112   Location.End.setLine(3);
113   Location.End.setColumn(121);
114   Location.FileURI = testPathURI("home/Definition.cpp", Strings);
115   Sym.Definition = Location;
116 
117   Location.Start.setLine(42);
118   Location.Start.setColumn(31);
119   Location.End.setLine(20);
120   Location.End.setColumn(400);
121   Location.FileURI = testPathURI("home/Declaration.h", Strings);
122   Sym.CanonicalDeclaration = Location;
123 
124   Sym.References = 9000;
125   Sym.Origin = clangd::SymbolOrigin::Static;
126   Sym.Signature = Strings.save("(int X, char Y, Type T)");
127   Sym.TemplateSpecializationArgs = Strings.save("<int, char, bool, Type>");
128   Sym.CompletionSnippetSuffix =
129       Strings.save("({1: int X}, {2: char Y}, {3: Type T})");
130   Sym.Documentation = Strings.save("This is my amazing Foo constructor!");
131   Sym.ReturnType = Strings.save("Foo");
132 
133   Sym.Flags = clangd::Symbol::SymbolFlag::IndexedForCodeCompletion;
134 
135   Marshaller ProtobufMarshaller(testPath("home/"), testPath("home/"));
136 
137   // Check that symbols are exactly the same if the path to indexed project is
138   // the same on indexing machine and the client.
139   auto Serialized = ProtobufMarshaller.toProtobuf(Sym);
140   ASSERT_TRUE(Serialized);
141   auto Deserialized = ProtobufMarshaller.fromProtobuf(*Serialized);
142   ASSERT_TRUE(Deserialized);
143   EXPECT_EQ(toYAML(Sym), toYAML(*Deserialized));
144   // Serialized paths are relative and have UNIX slashes.
145   EXPECT_EQ(convert_to_slash(Serialized->definition().file_path(),
146                              llvm::sys::path::Style::posix),
147             Serialized->definition().file_path());
148   EXPECT_TRUE(
149       llvm::sys::path::is_relative(Serialized->definition().file_path()));
150 
151   // Missing definition is OK.
152   Sym.Definition = clangd::SymbolLocation();
153   Serialized = ProtobufMarshaller.toProtobuf(Sym);
154   ASSERT_TRUE(Serialized);
155   EXPECT_TRUE(ProtobufMarshaller.fromProtobuf(*Serialized));
156 
157   // Relative path is absolute.
158   *Serialized->mutable_canonical_declaration()->mutable_file_path() =
159       convert_to_slash("/path/to/Declaration.h");
160   EXPECT_FALSE(ProtobufMarshaller.fromProtobuf(*Serialized));
161 
162   // Fail with an invalid URI.
163   Location.FileURI = "Not A URI";
164   Sym.Definition = Location;
165   EXPECT_FALSE(ProtobufMarshaller.toProtobuf(Sym));
166 
167   // Schemes other than "file" can not be used.
168   auto UnittestURI = URI::create(testPath("home/SomePath.h"), "unittest");
169   ASSERT_TRUE(bool(UnittestURI));
170   Location.FileURI = Strings.save(UnittestURI->toString()).begin();
171   Sym.Definition = Location;
172   EXPECT_FALSE(ProtobufMarshaller.toProtobuf(Sym));
173 
174   // Passing root that is not prefix of the original file path.
175   Location.FileURI = testPathURI("home/File.h", Strings);
176   Sym.Definition = Location;
177   // Check that the symbol is valid and passing the correct path works.
178   Serialized = ProtobufMarshaller.toProtobuf(Sym);
179   ASSERT_TRUE(Serialized);
180   Deserialized = ProtobufMarshaller.fromProtobuf(*Serialized);
181   ASSERT_TRUE(Deserialized);
182   EXPECT_STREQ(Deserialized->Definition.FileURI,
183                testPathURI("home/File.h", Strings));
184   // Fail with a wrong root.
185   Marshaller WrongMarshaller(testPath("nothome/"), testPath("home/"));
186   EXPECT_FALSE(WrongMarshaller.toProtobuf(Sym));
187 }
188 
189 TEST(RemoteMarshallingTest, RefSerialization) {
190   clangd::Ref Ref;
191   Ref.Kind = clangd::RefKind::Spelled | clangd::RefKind::Declaration;
192 
193   llvm::BumpPtrAllocator Arena;
194   llvm::UniqueStringSaver Strings(Arena);
195 
196   clangd::SymbolLocation Location;
197   Location.Start.setLine(124);
198   Location.Start.setColumn(21);
199   Location.End.setLine(3213);
200   Location.End.setColumn(541);
201   Location.FileURI = testPathURI(
202       "llvm-project/llvm/clang-tools-extra/clangd/Protocol.h", Strings);
203   Ref.Location = Location;
204 
205   Marshaller ProtobufMarshaller(testPath("llvm-project/"),
206                                 testPath("llvm-project/"));
207 
208   auto Serialized = ProtobufMarshaller.toProtobuf(Ref);
209   ASSERT_TRUE(Serialized);
210   auto Deserialized = ProtobufMarshaller.fromProtobuf(*Serialized);
211   ASSERT_TRUE(Deserialized);
212   EXPECT_EQ(toYAML(Ref), toYAML(*Deserialized));
213 }
214 
215 TEST(RemoteMarshallingTest, IncludeHeaderURIs) {
216   llvm::BumpPtrAllocator Arena;
217   llvm::UniqueStringSaver Strings(Arena);
218 
219   llvm::SmallVector<clangd::Symbol::IncludeHeaderWithReferences, 1>
220       ValidHeaders;
221   clangd::Symbol::IncludeHeaderWithReferences Header;
222   Header.IncludeHeader = Strings.save(
223       URI::createFile("/usr/local/user/home/project/Header.h").toString());
224   Header.References = 21;
225   ValidHeaders.push_back(Header);
226   Header.IncludeHeader = Strings.save("<iostream>");
227   Header.References = 100;
228   ValidHeaders.push_back(Header);
229   Header.IncludeHeader = Strings.save("\"cstdio\"");
230   Header.References = 200;
231   ValidHeaders.push_back(Header);
232 
233   llvm::SmallVector<clangd::Symbol::IncludeHeaderWithReferences, 1>
234       InvalidHeaders;
235   // This is an absolute path to a header: can not be transmitted over the wire.
236   Header.IncludeHeader = Strings.save(testPath("project/include/Common.h"));
237   Header.References = 42;
238   InvalidHeaders.push_back(Header);
239   // This is not a valid header: can not be transmitted over the wire;
240   Header.IncludeHeader = Strings.save("NotAHeader");
241   Header.References = 5;
242   InvalidHeaders.push_back(Header);
243 
244   clangd::Symbol Sym;
245   // Fill in definition and declaration, Symbool will be invalid otherwise.
246   clangd::SymbolLocation Location;
247   Location.Start.setLine(1);
248   Location.Start.setColumn(2);
249   Location.End.setLine(3);
250   Location.End.setColumn(4);
251   Location.FileURI = testPathURI("File.h", Strings);
252   Sym.Definition = Location;
253   Sym.CanonicalDeclaration = Location;
254 
255   // Try to serialize all headers but only valid ones will end up in Protobuf
256   // message.
257   auto AllHeaders = ValidHeaders;
258   AllHeaders.insert(AllHeaders.end(), InvalidHeaders.begin(),
259                     InvalidHeaders.end());
260   Sym.IncludeHeaders = AllHeaders;
261 
262   Marshaller ProtobufMarshaller(convert_to_slash("/"), convert_to_slash("/"));
263 
264   auto Serialized = ProtobufMarshaller.toProtobuf(Sym);
265   ASSERT_TRUE(Serialized);
266   EXPECT_EQ(static_cast<size_t>(Serialized->headers_size()),
267             ValidHeaders.size());
268   auto Deserialized = ProtobufMarshaller.fromProtobuf(*Serialized);
269   ASSERT_TRUE(Deserialized);
270 
271   Sym.IncludeHeaders = ValidHeaders;
272   EXPECT_EQ(toYAML(Sym), toYAML(*Deserialized));
273 }
274 
275 TEST(RemoteMarshallingTest, LookupRequestSerialization) {
276   clangd::LookupRequest Request;
277   Request.IDs.insert(llvm::cantFail(SymbolID::fromStr("0000000000000001")));
278   Request.IDs.insert(llvm::cantFail(SymbolID::fromStr("0000000000000002")));
279 
280   Marshaller ProtobufMarshaller(testPath("remote/"), testPath("local/"));
281 
282   auto Serialized = ProtobufMarshaller.toProtobuf(Request);
283   EXPECT_EQ(static_cast<unsigned>(Serialized.ids_size()), Request.IDs.size());
284   auto Deserialized = ProtobufMarshaller.fromProtobuf(&Serialized);
285   ASSERT_TRUE(bool(Deserialized));
286   EXPECT_EQ(Deserialized->IDs, Request.IDs);
287 }
288 
289 TEST(RemoteMarshallingTest, LookupRequestFailingSerialization) {
290   clangd::LookupRequest Request;
291   Marshaller ProtobufMarshaller(testPath("remote/"), testPath("local/"));
292   auto Serialized = ProtobufMarshaller.toProtobuf(Request);
293   Serialized.add_ids("Invalid Symbol ID");
294   auto Deserialized = ProtobufMarshaller.fromProtobuf(&Serialized);
295   EXPECT_FALSE(Deserialized);
296   llvm::consumeError(Deserialized.takeError());
297 }
298 
299 TEST(RemoteMarshallingTest, FuzzyFindRequestSerialization) {
300   clangd::FuzzyFindRequest Request;
301   Request.ProximityPaths = {testPath("local/Header.h"),
302                             testPath("local/subdir/OtherHeader.h"),
303                             testPath("remote/File.h"), "Not a Path."};
304   Marshaller ProtobufMarshaller(testPath("remote/"), testPath("local/"));
305   auto Serialized = ProtobufMarshaller.toProtobuf(Request);
306   EXPECT_EQ(Serialized.proximity_paths_size(), 2);
307   auto Deserialized = ProtobufMarshaller.fromProtobuf(&Serialized);
308   ASSERT_TRUE(bool(Deserialized));
309   EXPECT_THAT(Deserialized->ProximityPaths,
310               testing::ElementsAre(testPath("remote/Header.h"),
311                                    testPath("remote/subdir/OtherHeader.h")));
312 }
313 
314 TEST(RemoteMarshallingTest, RefsRequestSerialization) {
315   clangd::RefsRequest Request;
316   Request.IDs.insert(llvm::cantFail(SymbolID::fromStr("0000000000000001")));
317   Request.IDs.insert(llvm::cantFail(SymbolID::fromStr("0000000000000002")));
318 
319   Request.Limit = 9000;
320   Request.Filter = RefKind::Spelled | RefKind::Declaration;
321 
322   Marshaller ProtobufMarshaller(testPath("remote/"), testPath("local/"));
323 
324   auto Serialized = ProtobufMarshaller.toProtobuf(Request);
325   EXPECT_EQ(static_cast<unsigned>(Serialized.ids_size()), Request.IDs.size());
326   EXPECT_EQ(Serialized.limit(), Request.Limit);
327   auto Deserialized = ProtobufMarshaller.fromProtobuf(&Serialized);
328   ASSERT_TRUE(bool(Deserialized));
329   EXPECT_EQ(Deserialized->IDs, Request.IDs);
330   ASSERT_TRUE(Deserialized->Limit);
331   EXPECT_EQ(*Deserialized->Limit, Request.Limit);
332   EXPECT_EQ(Deserialized->Filter, Request.Filter);
333 }
334 
335 TEST(RemoteMarshallingTest, RefsRequestFailingSerialization) {
336   clangd::RefsRequest Request;
337   Marshaller ProtobufMarshaller(testPath("remote/"), testPath("local/"));
338   auto Serialized = ProtobufMarshaller.toProtobuf(Request);
339   Serialized.add_ids("Invalid Symbol ID");
340   auto Deserialized = ProtobufMarshaller.fromProtobuf(&Serialized);
341   EXPECT_FALSE(Deserialized);
342   llvm::consumeError(Deserialized.takeError());
343 }
344 
345 TEST(RemoteMarshallingTest, RelativePathToURITranslation) {
346   Marshaller ProtobufMarshaller(/*RemoteIndexRoot=*/"",
347                                 /*LocalIndexRoot=*/testPath("home/project/"));
348   EXPECT_TRUE(ProtobufMarshaller.relativePathToURI("lib/File.cpp"));
349   // RelativePath can not be absolute.
350   EXPECT_FALSE(ProtobufMarshaller.relativePathToURI("/lib/File.cpp"));
351   // RelativePath can not be empty.
352   EXPECT_FALSE(ProtobufMarshaller.relativePathToURI(std::string()));
353 }
354 
355 TEST(RemoteMarshallingTest, URIToRelativePathTranslation) {
356   llvm::BumpPtrAllocator Arena;
357   llvm::UniqueStringSaver Strings(Arena);
358   Marshaller ProtobufMarshaller(/*RemoteIndexRoot=*/testPath("remote/project/"),
359                                 /*LocalIndexRoot=*/"");
360   EXPECT_TRUE(ProtobufMarshaller.uriToRelativePath(
361       testPathURI("remote/project/lib/File.cpp", Strings)));
362   // RemoteIndexRoot has to be be a prefix of the file path.
363   Marshaller WrongMarshaller(
364       /*RemoteIndexRoot=*/testPath("remote/other/project/"),
365       /*LocalIndexRoot=*/"");
366   EXPECT_FALSE(WrongMarshaller.uriToRelativePath(
367       testPathURI("remote/project/lib/File.cpp", Strings)));
368 }
369 
370 } // namespace
371 } // namespace remote
372 } // namespace clangd
373 } // namespace clang
374