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