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