1 //===- unittests/Basic/FileMangerTest.cpp ------------ FileManger tests ---===// 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 "clang/Basic/FileManager.h" 10 #include "clang/Basic/FileSystemOptions.h" 11 #include "clang/Basic/FileSystemStatCache.h" 12 #include "llvm/ADT/STLExtras.h" 13 #include "llvm/Support/Path.h" 14 #include "llvm/Support/VirtualFileSystem.h" 15 #include "gtest/gtest.h" 16 17 using namespace llvm; 18 using namespace clang; 19 20 namespace { 21 22 // Used to create a fake file system for running the tests with such 23 // that the tests are not affected by the structure/contents of the 24 // file system on the machine running the tests. 25 class FakeStatCache : public FileSystemStatCache { 26 private: 27 // Maps a file/directory path to its desired stat result. Anything 28 // not in this map is considered to not exist in the file system. 29 llvm::StringMap<llvm::vfs::Status, llvm::BumpPtrAllocator> StatCalls; 30 31 void InjectFileOrDirectory(const char *Path, ino_t INode, bool IsFile) { 32 #ifndef _WIN32 33 SmallString<128> NormalizedPath(Path); 34 llvm::sys::path::native(NormalizedPath); 35 Path = NormalizedPath.c_str(); 36 #endif 37 38 auto fileType = IsFile ? 39 llvm::sys::fs::file_type::regular_file : 40 llvm::sys::fs::file_type::directory_file; 41 llvm::vfs::Status Status(Path, llvm::sys::fs::UniqueID(1, INode), 42 /*MTime*/{}, /*User*/0, /*Group*/0, 43 /*Size*/0, fileType, 44 llvm::sys::fs::perms::all_all); 45 StatCalls[Path] = Status; 46 } 47 48 public: 49 // Inject a file with the given inode value to the fake file system. 50 void InjectFile(const char *Path, ino_t INode) { 51 InjectFileOrDirectory(Path, INode, /*IsFile=*/true); 52 } 53 54 // Inject a directory with the given inode value to the fake file system. 55 void InjectDirectory(const char *Path, ino_t INode) { 56 InjectFileOrDirectory(Path, INode, /*IsFile=*/false); 57 } 58 59 // Implement FileSystemStatCache::getStat(). 60 LookupResult getStat(StringRef Path, llvm::vfs::Status &Status, bool isFile, 61 std::unique_ptr<llvm::vfs::File> *F, 62 llvm::vfs::FileSystem &FS) override { 63 #ifndef _WIN32 64 SmallString<128> NormalizedPath(Path); 65 llvm::sys::path::native(NormalizedPath); 66 Path = NormalizedPath.c_str(); 67 #endif 68 69 if (StatCalls.count(Path) != 0) { 70 Status = StatCalls[Path]; 71 return CacheExists; 72 } 73 74 return CacheMissing; // This means the file/directory doesn't exist. 75 } 76 }; 77 78 // The test fixture. 79 class FileManagerTest : public ::testing::Test { 80 protected: 81 FileManagerTest() : manager(options) { 82 } 83 84 FileSystemOptions options; 85 FileManager manager; 86 }; 87 88 // When a virtual file is added, its getDir() field is set correctly 89 // (not NULL, correct name). 90 TEST_F(FileManagerTest, getVirtualFileSetsTheDirFieldCorrectly) { 91 const FileEntry *file = manager.getVirtualFile("foo.cpp", 42, 0); 92 ASSERT_TRUE(file != nullptr); 93 94 const DirectoryEntry *dir = file->getDir(); 95 ASSERT_TRUE(dir != nullptr); 96 EXPECT_EQ(".", dir->getName()); 97 98 file = manager.getVirtualFile("x/y/z.cpp", 42, 0); 99 ASSERT_TRUE(file != nullptr); 100 101 dir = file->getDir(); 102 ASSERT_TRUE(dir != nullptr); 103 EXPECT_EQ("x/y", dir->getName()); 104 } 105 106 // Before any virtual file is added, no virtual directory exists. 107 TEST_F(FileManagerTest, NoVirtualDirectoryExistsBeforeAVirtualFileIsAdded) { 108 // An empty FakeStatCache causes all stat calls made by the 109 // FileManager to report "file/directory doesn't exist". This 110 // avoids the possibility of the result of this test being affected 111 // by what's in the real file system. 112 manager.setStatCache(llvm::make_unique<FakeStatCache>()); 113 114 EXPECT_EQ(nullptr, manager.getDirectory("virtual/dir/foo")); 115 EXPECT_EQ(nullptr, manager.getDirectory("virtual/dir")); 116 EXPECT_EQ(nullptr, manager.getDirectory("virtual")); 117 } 118 119 // When a virtual file is added, all of its ancestors should be created. 120 TEST_F(FileManagerTest, getVirtualFileCreatesDirectoryEntriesForAncestors) { 121 // Fake an empty real file system. 122 manager.setStatCache(llvm::make_unique<FakeStatCache>()); 123 124 manager.getVirtualFile("virtual/dir/bar.h", 100, 0); 125 EXPECT_EQ(nullptr, manager.getDirectory("virtual/dir/foo")); 126 127 const DirectoryEntry *dir = manager.getDirectory("virtual/dir"); 128 ASSERT_TRUE(dir != nullptr); 129 EXPECT_EQ("virtual/dir", dir->getName()); 130 131 dir = manager.getDirectory("virtual"); 132 ASSERT_TRUE(dir != nullptr); 133 EXPECT_EQ("virtual", dir->getName()); 134 } 135 136 // getFile() returns non-NULL if a real file exists at the given path. 137 TEST_F(FileManagerTest, getFileReturnsValidFileEntryForExistingRealFile) { 138 // Inject fake files into the file system. 139 auto statCache = llvm::make_unique<FakeStatCache>(); 140 statCache->InjectDirectory("/tmp", 42); 141 statCache->InjectFile("/tmp/test", 43); 142 143 #ifdef _WIN32 144 const char *DirName = "C:."; 145 const char *FileName = "C:test"; 146 statCache->InjectDirectory(DirName, 44); 147 statCache->InjectFile(FileName, 45); 148 #endif 149 150 manager.setStatCache(std::move(statCache)); 151 152 const FileEntry *file = manager.getFile("/tmp/test"); 153 ASSERT_TRUE(file != nullptr); 154 ASSERT_TRUE(file->isValid()); 155 EXPECT_EQ("/tmp/test", file->getName()); 156 157 const DirectoryEntry *dir = file->getDir(); 158 ASSERT_TRUE(dir != nullptr); 159 EXPECT_EQ("/tmp", dir->getName()); 160 161 #ifdef _WIN32 162 file = manager.getFile(FileName); 163 ASSERT_TRUE(file != NULL); 164 165 dir = file->getDir(); 166 ASSERT_TRUE(dir != NULL); 167 EXPECT_EQ(DirName, dir->getName()); 168 #endif 169 } 170 171 // getFile() returns non-NULL if a virtual file exists at the given path. 172 TEST_F(FileManagerTest, getFileReturnsValidFileEntryForExistingVirtualFile) { 173 // Fake an empty real file system. 174 manager.setStatCache(llvm::make_unique<FakeStatCache>()); 175 176 manager.getVirtualFile("virtual/dir/bar.h", 100, 0); 177 const FileEntry *file = manager.getFile("virtual/dir/bar.h"); 178 ASSERT_TRUE(file != nullptr); 179 ASSERT_TRUE(file->isValid()); 180 EXPECT_EQ("virtual/dir/bar.h", file->getName()); 181 182 const DirectoryEntry *dir = file->getDir(); 183 ASSERT_TRUE(dir != nullptr); 184 EXPECT_EQ("virtual/dir", dir->getName()); 185 } 186 187 // getFile() returns different FileEntries for different paths when 188 // there's no aliasing. 189 TEST_F(FileManagerTest, getFileReturnsDifferentFileEntriesForDifferentFiles) { 190 // Inject two fake files into the file system. Different inodes 191 // mean the files are not symlinked together. 192 auto statCache = llvm::make_unique<FakeStatCache>(); 193 statCache->InjectDirectory(".", 41); 194 statCache->InjectFile("foo.cpp", 42); 195 statCache->InjectFile("bar.cpp", 43); 196 manager.setStatCache(std::move(statCache)); 197 198 const FileEntry *fileFoo = manager.getFile("foo.cpp"); 199 const FileEntry *fileBar = manager.getFile("bar.cpp"); 200 ASSERT_TRUE(fileFoo != nullptr); 201 ASSERT_TRUE(fileFoo->isValid()); 202 ASSERT_TRUE(fileBar != nullptr); 203 ASSERT_TRUE(fileBar->isValid()); 204 EXPECT_NE(fileFoo, fileBar); 205 } 206 207 // getFile() returns NULL if neither a real file nor a virtual file 208 // exists at the given path. 209 TEST_F(FileManagerTest, getFileReturnsNULLForNonexistentFile) { 210 // Inject a fake foo.cpp into the file system. 211 auto statCache = llvm::make_unique<FakeStatCache>(); 212 statCache->InjectDirectory(".", 41); 213 statCache->InjectFile("foo.cpp", 42); 214 manager.setStatCache(std::move(statCache)); 215 216 // Create a virtual bar.cpp file. 217 manager.getVirtualFile("bar.cpp", 200, 0); 218 219 const FileEntry *file = manager.getFile("xyz.txt"); 220 EXPECT_EQ(nullptr, file); 221 } 222 223 // The following tests apply to Unix-like system only. 224 225 #ifndef _WIN32 226 227 // getFile() returns the same FileEntry for real files that are aliases. 228 TEST_F(FileManagerTest, getFileReturnsSameFileEntryForAliasedRealFiles) { 229 // Inject two real files with the same inode. 230 auto statCache = llvm::make_unique<FakeStatCache>(); 231 statCache->InjectDirectory("abc", 41); 232 statCache->InjectFile("abc/foo.cpp", 42); 233 statCache->InjectFile("abc/bar.cpp", 42); 234 manager.setStatCache(std::move(statCache)); 235 236 EXPECT_EQ(manager.getFile("abc/foo.cpp"), manager.getFile("abc/bar.cpp")); 237 } 238 239 // getFile() returns the same FileEntry for virtual files that have 240 // corresponding real files that are aliases. 241 TEST_F(FileManagerTest, getFileReturnsSameFileEntryForAliasedVirtualFiles) { 242 // Inject two real files with the same inode. 243 auto statCache = llvm::make_unique<FakeStatCache>(); 244 statCache->InjectDirectory("abc", 41); 245 statCache->InjectFile("abc/foo.cpp", 42); 246 statCache->InjectFile("abc/bar.cpp", 42); 247 manager.setStatCache(std::move(statCache)); 248 249 ASSERT_TRUE(manager.getVirtualFile("abc/foo.cpp", 100, 0)->isValid()); 250 ASSERT_TRUE(manager.getVirtualFile("abc/bar.cpp", 200, 0)->isValid()); 251 252 EXPECT_EQ(manager.getFile("abc/foo.cpp"), manager.getFile("abc/bar.cpp")); 253 } 254 255 // getFile() Should return the same entry as getVirtualFile if the file actually 256 // is a virtual file, even if the name is not exactly the same (but is after 257 // normalisation done by the file system, like on Windows). This can be checked 258 // here by checking the size. 259 TEST_F(FileManagerTest, getVirtualFileWithDifferentName) { 260 // Inject fake files into the file system. 261 auto statCache = llvm::make_unique<FakeStatCache>(); 262 statCache->InjectDirectory("c:\\tmp", 42); 263 statCache->InjectFile("c:\\tmp\\test", 43); 264 265 manager.setStatCache(std::move(statCache)); 266 267 // Inject the virtual file: 268 const FileEntry *file1 = manager.getVirtualFile("c:\\tmp\\test", 123, 1); 269 ASSERT_TRUE(file1 != nullptr); 270 ASSERT_TRUE(file1->isValid()); 271 EXPECT_EQ(43U, file1->getUniqueID().getFile()); 272 EXPECT_EQ(123, file1->getSize()); 273 274 // Lookup the virtual file with a different name: 275 const FileEntry *file2 = manager.getFile("c:/tmp/test", 100, 1); 276 ASSERT_TRUE(file2 != nullptr); 277 ASSERT_TRUE(file2->isValid()); 278 // Check that it's the same UFE: 279 EXPECT_EQ(file1, file2); 280 EXPECT_EQ(43U, file2->getUniqueID().getFile()); 281 // Check that the contents of the UFE are not overwritten by the entry in the 282 // filesystem: 283 EXPECT_EQ(123, file2->getSize()); 284 } 285 286 #endif // !_WIN32 287 288 TEST_F(FileManagerTest, makeAbsoluteUsesVFS) { 289 SmallString<64> CustomWorkingDir; 290 #ifdef _WIN32 291 CustomWorkingDir = "C:"; 292 #else 293 CustomWorkingDir = "/"; 294 #endif 295 llvm::sys::path::append(CustomWorkingDir, "some", "weird", "path"); 296 297 auto FS = IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>( 298 new llvm::vfs::InMemoryFileSystem); 299 // setCurrentworkingdirectory must finish without error. 300 ASSERT_TRUE(!FS->setCurrentWorkingDirectory(CustomWorkingDir)); 301 302 FileSystemOptions Opts; 303 FileManager Manager(Opts, FS); 304 305 SmallString<64> Path("a/foo.cpp"); 306 307 SmallString<64> ExpectedResult(CustomWorkingDir); 308 llvm::sys::path::append(ExpectedResult, Path); 309 310 ASSERT_TRUE(Manager.makeAbsolutePath(Path)); 311 EXPECT_EQ(Path, ExpectedResult); 312 } 313 314 // getVirtualFile should always fill the real path. 315 TEST_F(FileManagerTest, getVirtualFileFillsRealPathName) { 316 SmallString<64> CustomWorkingDir; 317 #ifdef _WIN32 318 CustomWorkingDir = "C:/"; 319 #else 320 CustomWorkingDir = "/"; 321 #endif 322 323 auto FS = IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>( 324 new llvm::vfs::InMemoryFileSystem); 325 // setCurrentworkingdirectory must finish without error. 326 ASSERT_TRUE(!FS->setCurrentWorkingDirectory(CustomWorkingDir)); 327 328 FileSystemOptions Opts; 329 FileManager Manager(Opts, FS); 330 331 // Inject fake files into the file system. 332 auto statCache = llvm::make_unique<FakeStatCache>(); 333 statCache->InjectDirectory("/tmp", 42); 334 statCache->InjectFile("/tmp/test", 43); 335 336 Manager.setStatCache(std::move(statCache)); 337 338 // Check for real path. 339 const FileEntry *file = Manager.getVirtualFile("/tmp/test", 123, 1); 340 ASSERT_TRUE(file != nullptr); 341 ASSERT_TRUE(file->isValid()); 342 SmallString<64> ExpectedResult = CustomWorkingDir; 343 344 llvm::sys::path::append(ExpectedResult, "tmp", "test"); 345 EXPECT_EQ(file->tryGetRealPathName(), ExpectedResult); 346 } 347 348 TEST_F(FileManagerTest, getFileDontOpenRealPath) { 349 SmallString<64> CustomWorkingDir; 350 #ifdef _WIN32 351 CustomWorkingDir = "C:/"; 352 #else 353 CustomWorkingDir = "/"; 354 #endif 355 356 auto FS = IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>( 357 new llvm::vfs::InMemoryFileSystem); 358 // setCurrentworkingdirectory must finish without error. 359 ASSERT_TRUE(!FS->setCurrentWorkingDirectory(CustomWorkingDir)); 360 361 FileSystemOptions Opts; 362 FileManager Manager(Opts, FS); 363 364 // Inject fake files into the file system. 365 auto statCache = llvm::make_unique<FakeStatCache>(); 366 statCache->InjectDirectory("/tmp", 42); 367 statCache->InjectFile("/tmp/test", 43); 368 369 Manager.setStatCache(std::move(statCache)); 370 371 // Check for real path. 372 const FileEntry *file = Manager.getFile("/tmp/test", /*OpenFile=*/false); 373 ASSERT_TRUE(file != nullptr); 374 ASSERT_TRUE(file->isValid()); 375 SmallString<64> ExpectedResult = CustomWorkingDir; 376 377 llvm::sys::path::append(ExpectedResult, "tmp", "test"); 378 EXPECT_EQ(file->tryGetRealPathName(), ExpectedResult); 379 } 380 381 } // anonymous namespace 382