1 //===- unittests/Basic/VirtualFileSystem.cpp ---------------- VFS tests ---===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "clang/Basic/VirtualFileSystem.h" 11 #include "llvm/Support/Errc.h" 12 #include "llvm/Support/MemoryBuffer.h" 13 #include "llvm/Support/Path.h" 14 #include "llvm/Support/SourceMgr.h" 15 #include "gtest/gtest.h" 16 #include <map> 17 using namespace clang; 18 using namespace llvm; 19 using llvm::sys::fs::UniqueID; 20 21 namespace { 22 class DummyFileSystem : public vfs::FileSystem { 23 int FSID; // used to produce UniqueIDs 24 int FileID; // used to produce UniqueIDs 25 std::map<std::string, vfs::Status> FilesAndDirs; 26 27 static int getNextFSID() { 28 static int Count = 0; 29 return Count++; 30 } 31 32 public: 33 DummyFileSystem() : FSID(getNextFSID()), FileID(0) {} 34 35 ErrorOr<vfs::Status> status(const Twine &Path) override { 36 std::map<std::string, vfs::Status>::iterator I = 37 FilesAndDirs.find(Path.str()); 38 if (I == FilesAndDirs.end()) 39 return make_error_code(llvm::errc::no_such_file_or_directory); 40 return I->second; 41 } 42 ErrorOr<std::unique_ptr<vfs::File>> 43 openFileForRead(const Twine &Path) override { 44 llvm_unreachable("unimplemented"); 45 } 46 47 struct DirIterImpl : public clang::vfs::detail::DirIterImpl { 48 std::map<std::string, vfs::Status> &FilesAndDirs; 49 std::map<std::string, vfs::Status>::iterator I; 50 std::string Path; 51 bool isInPath(StringRef S) { 52 if (Path.size() < S.size() && S.find(Path) == 0) { 53 auto LastSep = S.find_last_of('/'); 54 if (LastSep == Path.size() || LastSep == Path.size()-1) 55 return true; 56 } 57 return false; 58 } 59 DirIterImpl(std::map<std::string, vfs::Status> &FilesAndDirs, 60 const Twine &_Path) 61 : FilesAndDirs(FilesAndDirs), I(FilesAndDirs.begin()), 62 Path(_Path.str()) { 63 for ( ; I != FilesAndDirs.end(); ++I) { 64 if (isInPath(I->first)) { 65 CurrentEntry = I->second; 66 break; 67 } 68 } 69 } 70 std::error_code increment() override { 71 ++I; 72 for ( ; I != FilesAndDirs.end(); ++I) { 73 if (isInPath(I->first)) { 74 CurrentEntry = I->second; 75 break; 76 } 77 } 78 if (I == FilesAndDirs.end()) 79 CurrentEntry = vfs::Status(); 80 return std::error_code(); 81 } 82 }; 83 84 vfs::directory_iterator dir_begin(const Twine &Dir, 85 std::error_code &EC) override { 86 return vfs::directory_iterator( 87 std::make_shared<DirIterImpl>(FilesAndDirs, Dir)); 88 } 89 90 void addEntry(StringRef Path, const vfs::Status &Status) { 91 FilesAndDirs[Path] = Status; 92 } 93 94 void addRegularFile(StringRef Path, sys::fs::perms Perms = sys::fs::all_all) { 95 vfs::Status S(Path, Path, UniqueID(FSID, FileID++), sys::TimeValue::now(), 96 0, 0, 1024, sys::fs::file_type::regular_file, Perms); 97 addEntry(Path, S); 98 } 99 100 void addDirectory(StringRef Path, sys::fs::perms Perms = sys::fs::all_all) { 101 vfs::Status S(Path, Path, UniqueID(FSID, FileID++), sys::TimeValue::now(), 102 0, 0, 0, sys::fs::file_type::directory_file, Perms); 103 addEntry(Path, S); 104 } 105 106 void addSymlink(StringRef Path) { 107 vfs::Status S(Path, Path, UniqueID(FSID, FileID++), sys::TimeValue::now(), 108 0, 0, 0, sys::fs::file_type::symlink_file, sys::fs::all_all); 109 addEntry(Path, S); 110 } 111 }; 112 } // end anonymous namespace 113 114 TEST(VirtualFileSystemTest, StatusQueries) { 115 IntrusiveRefCntPtr<DummyFileSystem> D(new DummyFileSystem()); 116 ErrorOr<vfs::Status> Status((std::error_code())); 117 118 D->addRegularFile("/foo"); 119 Status = D->status("/foo"); 120 ASSERT_FALSE(Status.getError()); 121 EXPECT_TRUE(Status->isStatusKnown()); 122 EXPECT_FALSE(Status->isDirectory()); 123 EXPECT_TRUE(Status->isRegularFile()); 124 EXPECT_FALSE(Status->isSymlink()); 125 EXPECT_FALSE(Status->isOther()); 126 EXPECT_TRUE(Status->exists()); 127 128 D->addDirectory("/bar"); 129 Status = D->status("/bar"); 130 ASSERT_FALSE(Status.getError()); 131 EXPECT_TRUE(Status->isStatusKnown()); 132 EXPECT_TRUE(Status->isDirectory()); 133 EXPECT_FALSE(Status->isRegularFile()); 134 EXPECT_FALSE(Status->isSymlink()); 135 EXPECT_FALSE(Status->isOther()); 136 EXPECT_TRUE(Status->exists()); 137 138 D->addSymlink("/baz"); 139 Status = D->status("/baz"); 140 ASSERT_FALSE(Status.getError()); 141 EXPECT_TRUE(Status->isStatusKnown()); 142 EXPECT_FALSE(Status->isDirectory()); 143 EXPECT_FALSE(Status->isRegularFile()); 144 EXPECT_TRUE(Status->isSymlink()); 145 EXPECT_FALSE(Status->isOther()); 146 EXPECT_TRUE(Status->exists()); 147 148 EXPECT_TRUE(Status->equivalent(*Status)); 149 ErrorOr<vfs::Status> Status2 = D->status("/foo"); 150 ASSERT_FALSE(Status2.getError()); 151 EXPECT_FALSE(Status->equivalent(*Status2)); 152 } 153 154 TEST(VirtualFileSystemTest, BaseOnlyOverlay) { 155 IntrusiveRefCntPtr<DummyFileSystem> D(new DummyFileSystem()); 156 ErrorOr<vfs::Status> Status((std::error_code())); 157 EXPECT_FALSE(Status = D->status("/foo")); 158 159 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O(new vfs::OverlayFileSystem(D)); 160 EXPECT_FALSE(Status = O->status("/foo")); 161 162 D->addRegularFile("/foo"); 163 Status = D->status("/foo"); 164 EXPECT_FALSE(Status.getError()); 165 166 ErrorOr<vfs::Status> Status2((std::error_code())); 167 Status2 = O->status("/foo"); 168 EXPECT_FALSE(Status2.getError()); 169 EXPECT_TRUE(Status->equivalent(*Status2)); 170 } 171 172 TEST(VirtualFileSystemTest, OverlayFiles) { 173 IntrusiveRefCntPtr<DummyFileSystem> Base(new DummyFileSystem()); 174 IntrusiveRefCntPtr<DummyFileSystem> Middle(new DummyFileSystem()); 175 IntrusiveRefCntPtr<DummyFileSystem> Top(new DummyFileSystem()); 176 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O( 177 new vfs::OverlayFileSystem(Base)); 178 O->pushOverlay(Middle); 179 O->pushOverlay(Top); 180 181 ErrorOr<vfs::Status> Status1((std::error_code())), 182 Status2((std::error_code())), Status3((std::error_code())), 183 StatusB((std::error_code())), StatusM((std::error_code())), 184 StatusT((std::error_code())); 185 186 Base->addRegularFile("/foo"); 187 StatusB = Base->status("/foo"); 188 ASSERT_FALSE(StatusB.getError()); 189 Status1 = O->status("/foo"); 190 ASSERT_FALSE(Status1.getError()); 191 Middle->addRegularFile("/foo"); 192 StatusM = Middle->status("/foo"); 193 ASSERT_FALSE(StatusM.getError()); 194 Status2 = O->status("/foo"); 195 ASSERT_FALSE(Status2.getError()); 196 Top->addRegularFile("/foo"); 197 StatusT = Top->status("/foo"); 198 ASSERT_FALSE(StatusT.getError()); 199 Status3 = O->status("/foo"); 200 ASSERT_FALSE(Status3.getError()); 201 202 EXPECT_TRUE(Status1->equivalent(*StatusB)); 203 EXPECT_TRUE(Status2->equivalent(*StatusM)); 204 EXPECT_TRUE(Status3->equivalent(*StatusT)); 205 206 EXPECT_FALSE(Status1->equivalent(*Status2)); 207 EXPECT_FALSE(Status2->equivalent(*Status3)); 208 EXPECT_FALSE(Status1->equivalent(*Status3)); 209 } 210 211 TEST(VirtualFileSystemTest, OverlayDirsNonMerged) { 212 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 213 IntrusiveRefCntPtr<DummyFileSystem> Upper(new DummyFileSystem()); 214 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O( 215 new vfs::OverlayFileSystem(Lower)); 216 O->pushOverlay(Upper); 217 218 Lower->addDirectory("/lower-only"); 219 Upper->addDirectory("/upper-only"); 220 221 // non-merged paths should be the same 222 ErrorOr<vfs::Status> Status1 = Lower->status("/lower-only"); 223 ASSERT_FALSE(Status1.getError()); 224 ErrorOr<vfs::Status> Status2 = O->status("/lower-only"); 225 ASSERT_FALSE(Status2.getError()); 226 EXPECT_TRUE(Status1->equivalent(*Status2)); 227 228 Status1 = Upper->status("/upper-only"); 229 ASSERT_FALSE(Status1.getError()); 230 Status2 = O->status("/upper-only"); 231 ASSERT_FALSE(Status2.getError()); 232 EXPECT_TRUE(Status1->equivalent(*Status2)); 233 } 234 235 TEST(VirtualFileSystemTest, MergedDirPermissions) { 236 // merged directories get the permissions of the upper dir 237 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 238 IntrusiveRefCntPtr<DummyFileSystem> Upper(new DummyFileSystem()); 239 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O( 240 new vfs::OverlayFileSystem(Lower)); 241 O->pushOverlay(Upper); 242 243 ErrorOr<vfs::Status> Status((std::error_code())); 244 Lower->addDirectory("/both", sys::fs::owner_read); 245 Upper->addDirectory("/both", sys::fs::owner_all | sys::fs::group_read); 246 Status = O->status("/both"); 247 ASSERT_FALSE(Status.getError()); 248 EXPECT_EQ(0740, Status->getPermissions()); 249 250 // permissions (as usual) are not recursively applied 251 Lower->addRegularFile("/both/foo", sys::fs::owner_read); 252 Upper->addRegularFile("/both/bar", sys::fs::owner_write); 253 Status = O->status("/both/foo"); 254 ASSERT_FALSE( Status.getError()); 255 EXPECT_EQ(0400, Status->getPermissions()); 256 Status = O->status("/both/bar"); 257 ASSERT_FALSE(Status.getError()); 258 EXPECT_EQ(0200, Status->getPermissions()); 259 } 260 261 namespace { 262 struct ScopedDir { 263 SmallString<128> Path; 264 ScopedDir(const Twine &Name, bool Unique=false) { 265 std::error_code EC; 266 if (Unique) { 267 EC = llvm::sys::fs::createUniqueDirectory(Name, Path); 268 } else { 269 Path = Name.str(); 270 EC = llvm::sys::fs::create_directory(Twine(Path)); 271 } 272 if (EC) 273 Path = ""; 274 EXPECT_FALSE(EC); 275 } 276 ~ScopedDir() { 277 if (Path != "") 278 EXPECT_FALSE(llvm::sys::fs::remove(Path.str())); 279 } 280 operator StringRef() { return Path.str(); } 281 }; 282 } 283 284 TEST(VirtualFileSystemTest, BasicRealFSIteration) { 285 ScopedDir TestDirectory("virtual-file-system-test", /*Unique*/true); 286 IntrusiveRefCntPtr<vfs::FileSystem> FS = vfs::getRealFileSystem(); 287 288 std::error_code EC; 289 vfs::directory_iterator I = FS->dir_begin(Twine(TestDirectory), EC); 290 ASSERT_FALSE(EC); 291 EXPECT_EQ(vfs::directory_iterator(), I); // empty directory is empty 292 293 ScopedDir _a(TestDirectory+"/a"); 294 ScopedDir _ab(TestDirectory+"/a/b"); 295 ScopedDir _c(TestDirectory+"/c"); 296 ScopedDir _cd(TestDirectory+"/c/d"); 297 298 I = FS->dir_begin(Twine(TestDirectory), EC); 299 ASSERT_FALSE(EC); 300 ASSERT_NE(vfs::directory_iterator(), I); 301 // Check either a or c, since we can't rely on the iteration order. 302 EXPECT_TRUE(I->getName().endswith("a") || I->getName().endswith("c")); 303 I.increment(EC); 304 ASSERT_FALSE(EC); 305 ASSERT_NE(vfs::directory_iterator(), I); 306 EXPECT_TRUE(I->getName().endswith("a") || I->getName().endswith("c")); 307 I.increment(EC); 308 EXPECT_EQ(vfs::directory_iterator(), I); 309 } 310 311 TEST(VirtualFileSystemTest, BasicRealFSRecursiveIteration) { 312 ScopedDir TestDirectory("virtual-file-system-test", /*Unique*/true); 313 IntrusiveRefCntPtr<vfs::FileSystem> FS = vfs::getRealFileSystem(); 314 315 std::error_code EC; 316 auto I = vfs::recursive_directory_iterator(*FS, Twine(TestDirectory), EC); 317 ASSERT_FALSE(EC); 318 EXPECT_EQ(vfs::recursive_directory_iterator(), I); // empty directory is empty 319 320 ScopedDir _a(TestDirectory+"/a"); 321 ScopedDir _ab(TestDirectory+"/a/b"); 322 ScopedDir _c(TestDirectory+"/c"); 323 ScopedDir _cd(TestDirectory+"/c/d"); 324 325 I = vfs::recursive_directory_iterator(*FS, Twine(TestDirectory), EC); 326 ASSERT_FALSE(EC); 327 ASSERT_NE(vfs::recursive_directory_iterator(), I); 328 329 330 std::vector<std::string> Contents; 331 for (auto E = vfs::recursive_directory_iterator(); !EC && I != E; 332 I.increment(EC)) { 333 Contents.push_back(I->getName()); 334 } 335 336 // Check contents, which may be in any order 337 EXPECT_EQ(4U, Contents.size()); 338 int Counts[4] = { 0, 0, 0, 0 }; 339 for (const std::string &Name : Contents) { 340 ASSERT_FALSE(Name.empty()); 341 int Index = Name[Name.size()-1] - 'a'; 342 ASSERT_TRUE(Index >= 0 && Index < 4); 343 Counts[Index]++; 344 } 345 EXPECT_EQ(1, Counts[0]); // a 346 EXPECT_EQ(1, Counts[1]); // b 347 EXPECT_EQ(1, Counts[2]); // c 348 EXPECT_EQ(1, Counts[3]); // d 349 } 350 351 template <typename T, size_t N> 352 std::vector<StringRef> makeStringRefVector(const T (&Arr)[N]) { 353 std::vector<StringRef> Vec; 354 for (size_t i = 0; i != N; ++i) 355 Vec.push_back(Arr[i]); 356 return Vec; 357 } 358 359 template <typename DirIter> 360 static void checkContents(DirIter I, ArrayRef<StringRef> Expected) { 361 std::error_code EC; 362 auto ExpectedIter = Expected.begin(), ExpectedEnd = Expected.end(); 363 for (DirIter E; 364 !EC && I != E && ExpectedIter != ExpectedEnd; 365 I.increment(EC), ++ExpectedIter) 366 EXPECT_EQ(*ExpectedIter, I->getName()); 367 368 EXPECT_EQ(ExpectedEnd, ExpectedIter); 369 EXPECT_EQ(DirIter(), I); 370 } 371 372 TEST(VirtualFileSystemTest, OverlayIteration) { 373 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 374 IntrusiveRefCntPtr<DummyFileSystem> Upper(new DummyFileSystem()); 375 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O( 376 new vfs::OverlayFileSystem(Lower)); 377 O->pushOverlay(Upper); 378 379 std::error_code EC; 380 checkContents(O->dir_begin("/", EC), ArrayRef<StringRef>()); 381 382 Lower->addRegularFile("/file1"); 383 checkContents(O->dir_begin("/", EC), ArrayRef<StringRef>("/file1")); 384 385 Upper->addRegularFile("/file2"); 386 { 387 const char *Contents[] = {"/file2", "/file1"}; 388 checkContents(O->dir_begin("/", EC), makeStringRefVector(Contents)); 389 } 390 391 Lower->addDirectory("/dir1"); 392 Lower->addRegularFile("/dir1/foo"); 393 Upper->addDirectory("/dir2"); 394 Upper->addRegularFile("/dir2/foo"); 395 checkContents(O->dir_begin("/dir2", EC), ArrayRef<StringRef>("/dir2/foo")); 396 { 397 const char *Contents[] = {"/dir2", "/file2", "/dir1", "/file1"}; 398 checkContents(O->dir_begin("/", EC), makeStringRefVector(Contents)); 399 } 400 } 401 402 TEST(VirtualFileSystemTest, OverlayRecursiveIteration) { 403 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 404 IntrusiveRefCntPtr<DummyFileSystem> Middle(new DummyFileSystem()); 405 IntrusiveRefCntPtr<DummyFileSystem> Upper(new DummyFileSystem()); 406 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O( 407 new vfs::OverlayFileSystem(Lower)); 408 O->pushOverlay(Middle); 409 O->pushOverlay(Upper); 410 411 std::error_code EC; 412 checkContents(vfs::recursive_directory_iterator(*O, "/", EC), 413 ArrayRef<StringRef>()); 414 415 Lower->addRegularFile("/file1"); 416 checkContents(vfs::recursive_directory_iterator(*O, "/", EC), 417 ArrayRef<StringRef>("/file1")); 418 419 Upper->addDirectory("/dir"); 420 Upper->addRegularFile("/dir/file2"); 421 { 422 const char *Contents[] = {"/dir", "/dir/file2", "/file1"}; 423 checkContents(vfs::recursive_directory_iterator(*O, "/", EC), 424 makeStringRefVector(Contents)); 425 } 426 427 Lower->addDirectory("/dir1"); 428 Lower->addRegularFile("/dir1/foo"); 429 Lower->addDirectory("/dir1/a"); 430 Lower->addRegularFile("/dir1/a/b"); 431 Middle->addDirectory("/a"); 432 Middle->addDirectory("/a/b"); 433 Middle->addDirectory("/a/b/c"); 434 Middle->addRegularFile("/a/b/c/d"); 435 Middle->addRegularFile("/hiddenByUp"); 436 Upper->addDirectory("/dir2"); 437 Upper->addRegularFile("/dir2/foo"); 438 Upper->addRegularFile("/hiddenByUp"); 439 checkContents(vfs::recursive_directory_iterator(*O, "/dir2", EC), 440 ArrayRef<StringRef>("/dir2/foo")); 441 { 442 const char *Contents[] = { "/dir", "/dir/file2", "/dir2", "/dir2/foo", 443 "/hiddenByUp", "/a", "/a/b", "/a/b/c", "/a/b/c/d", "/dir1", "/dir1/a", 444 "/dir1/a/b", "/dir1/foo", "/file1" }; 445 checkContents(vfs::recursive_directory_iterator(*O, "/", EC), 446 makeStringRefVector(Contents)); 447 } 448 } 449 450 TEST(VirtualFileSystemTest, ThreeLevelIteration) { 451 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 452 IntrusiveRefCntPtr<DummyFileSystem> Middle(new DummyFileSystem()); 453 IntrusiveRefCntPtr<DummyFileSystem> Upper(new DummyFileSystem()); 454 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O( 455 new vfs::OverlayFileSystem(Lower)); 456 O->pushOverlay(Middle); 457 O->pushOverlay(Upper); 458 459 std::error_code EC; 460 checkContents(O->dir_begin("/", EC), ArrayRef<StringRef>()); 461 462 Middle->addRegularFile("/file2"); 463 checkContents(O->dir_begin("/", EC), ArrayRef<StringRef>("/file2")); 464 465 Lower->addRegularFile("/file1"); 466 Upper->addRegularFile("/file3"); 467 { 468 const char *Contents[] = {"/file3", "/file2", "/file1"}; 469 checkContents(O->dir_begin("/", EC), makeStringRefVector(Contents)); 470 } 471 } 472 473 TEST(VirtualFileSystemTest, HiddenInIteration) { 474 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 475 IntrusiveRefCntPtr<DummyFileSystem> Middle(new DummyFileSystem()); 476 IntrusiveRefCntPtr<DummyFileSystem> Upper(new DummyFileSystem()); 477 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O( 478 new vfs::OverlayFileSystem(Lower)); 479 O->pushOverlay(Middle); 480 O->pushOverlay(Upper); 481 482 std::error_code EC; 483 Lower->addRegularFile("/onlyInLow", sys::fs::owner_read); 484 Lower->addRegularFile("/hiddenByMid", sys::fs::owner_read); 485 Lower->addRegularFile("/hiddenByUp", sys::fs::owner_read); 486 Middle->addRegularFile("/onlyInMid", sys::fs::owner_write); 487 Middle->addRegularFile("/hiddenByMid", sys::fs::owner_write); 488 Middle->addRegularFile("/hiddenByUp", sys::fs::owner_write); 489 Upper->addRegularFile("/onlyInUp", sys::fs::owner_all); 490 Upper->addRegularFile("/hiddenByUp", sys::fs::owner_all); 491 { 492 const char *Contents[] = {"/hiddenByUp", "/onlyInUp", "/hiddenByMid", 493 "/onlyInMid", "/onlyInLow"}; 494 checkContents(O->dir_begin("/", EC), makeStringRefVector(Contents)); 495 } 496 497 // Make sure we get the top-most entry 498 { 499 std::error_code EC; 500 vfs::directory_iterator I = O->dir_begin("/", EC), E; 501 for ( ; !EC && I != E; I.increment(EC)) 502 if (I->getName() == "/hiddenByUp") 503 break; 504 ASSERT_NE(E, I); 505 EXPECT_EQ(sys::fs::owner_all, I->getPermissions()); 506 } 507 { 508 std::error_code EC; 509 vfs::directory_iterator I = O->dir_begin("/", EC), E; 510 for ( ; !EC && I != E; I.increment(EC)) 511 if (I->getName() == "/hiddenByMid") 512 break; 513 ASSERT_NE(E, I); 514 EXPECT_EQ(sys::fs::owner_write, I->getPermissions()); 515 } 516 } 517 518 // NOTE: in the tests below, we use '//root/' as our root directory, since it is 519 // a legal *absolute* path on Windows as well as *nix. 520 class VFSFromYAMLTest : public ::testing::Test { 521 public: 522 int NumDiagnostics; 523 524 void SetUp() { 525 NumDiagnostics = 0; 526 } 527 528 static void CountingDiagHandler(const SMDiagnostic &, void *Context) { 529 VFSFromYAMLTest *Test = static_cast<VFSFromYAMLTest *>(Context); 530 ++Test->NumDiagnostics; 531 } 532 533 IntrusiveRefCntPtr<vfs::FileSystem> 534 getFromYAMLRawString(StringRef Content, 535 IntrusiveRefCntPtr<vfs::FileSystem> ExternalFS) { 536 std::unique_ptr<MemoryBuffer> Buffer = MemoryBuffer::getMemBuffer(Content); 537 return getVFSFromYAML(std::move(Buffer), CountingDiagHandler, this, 538 ExternalFS); 539 } 540 541 IntrusiveRefCntPtr<vfs::FileSystem> getFromYAMLString( 542 StringRef Content, 543 IntrusiveRefCntPtr<vfs::FileSystem> ExternalFS = new DummyFileSystem()) { 544 std::string VersionPlusContent("{\n 'version':0,\n"); 545 VersionPlusContent += Content.slice(Content.find('{') + 1, StringRef::npos); 546 return getFromYAMLRawString(VersionPlusContent, ExternalFS); 547 } 548 }; 549 550 TEST_F(VFSFromYAMLTest, BasicVFSFromYAML) { 551 IntrusiveRefCntPtr<vfs::FileSystem> FS; 552 FS = getFromYAMLString(""); 553 EXPECT_EQ(nullptr, FS.get()); 554 FS = getFromYAMLString("[]"); 555 EXPECT_EQ(nullptr, FS.get()); 556 FS = getFromYAMLString("'string'"); 557 EXPECT_EQ(nullptr, FS.get()); 558 EXPECT_EQ(3, NumDiagnostics); 559 } 560 561 TEST_F(VFSFromYAMLTest, MappedFiles) { 562 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 563 Lower->addRegularFile("//root/foo/bar/a"); 564 IntrusiveRefCntPtr<vfs::FileSystem> FS = 565 getFromYAMLString("{ 'roots': [\n" 566 "{\n" 567 " 'type': 'directory',\n" 568 " 'name': '//root/',\n" 569 " 'contents': [ {\n" 570 " 'type': 'file',\n" 571 " 'name': 'file1',\n" 572 " 'external-contents': '//root/foo/bar/a'\n" 573 " },\n" 574 " {\n" 575 " 'type': 'file',\n" 576 " 'name': 'file2',\n" 577 " 'external-contents': '//root/foo/b'\n" 578 " }\n" 579 " ]\n" 580 "}\n" 581 "]\n" 582 "}", 583 Lower); 584 ASSERT_TRUE(FS.get() != nullptr); 585 586 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O( 587 new vfs::OverlayFileSystem(Lower)); 588 O->pushOverlay(FS); 589 590 // file 591 ErrorOr<vfs::Status> S = O->status("//root/file1"); 592 ASSERT_FALSE(S.getError()); 593 EXPECT_EQ("//root/foo/bar/a", S->getName()); 594 595 ErrorOr<vfs::Status> SLower = O->status("//root/foo/bar/a"); 596 EXPECT_EQ("//root/foo/bar/a", SLower->getName()); 597 EXPECT_TRUE(S->equivalent(*SLower)); 598 599 // directory 600 S = O->status("//root/"); 601 ASSERT_FALSE(S.getError()); 602 EXPECT_TRUE(S->isDirectory()); 603 EXPECT_TRUE(S->equivalent(*O->status("//root/"))); // non-volatile UniqueID 604 605 // broken mapping 606 EXPECT_EQ(O->status("//root/file2").getError(), 607 llvm::errc::no_such_file_or_directory); 608 EXPECT_EQ(0, NumDiagnostics); 609 } 610 611 TEST_F(VFSFromYAMLTest, CaseInsensitive) { 612 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 613 Lower->addRegularFile("//root/foo/bar/a"); 614 IntrusiveRefCntPtr<vfs::FileSystem> FS = 615 getFromYAMLString("{ 'case-sensitive': 'false',\n" 616 " 'roots': [\n" 617 "{\n" 618 " 'type': 'directory',\n" 619 " 'name': '//root/',\n" 620 " 'contents': [ {\n" 621 " 'type': 'file',\n" 622 " 'name': 'XX',\n" 623 " 'external-contents': '//root/foo/bar/a'\n" 624 " }\n" 625 " ]\n" 626 "}]}", 627 Lower); 628 ASSERT_TRUE(FS.get() != nullptr); 629 630 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O( 631 new vfs::OverlayFileSystem(Lower)); 632 O->pushOverlay(FS); 633 634 ErrorOr<vfs::Status> S = O->status("//root/XX"); 635 ASSERT_FALSE(S.getError()); 636 637 ErrorOr<vfs::Status> SS = O->status("//root/xx"); 638 ASSERT_FALSE(SS.getError()); 639 EXPECT_TRUE(S->equivalent(*SS)); 640 SS = O->status("//root/xX"); 641 EXPECT_TRUE(S->equivalent(*SS)); 642 SS = O->status("//root/Xx"); 643 EXPECT_TRUE(S->equivalent(*SS)); 644 EXPECT_EQ(0, NumDiagnostics); 645 } 646 647 TEST_F(VFSFromYAMLTest, CaseSensitive) { 648 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 649 Lower->addRegularFile("//root/foo/bar/a"); 650 IntrusiveRefCntPtr<vfs::FileSystem> FS = 651 getFromYAMLString("{ 'case-sensitive': 'true',\n" 652 " 'roots': [\n" 653 "{\n" 654 " 'type': 'directory',\n" 655 " 'name': '//root/',\n" 656 " 'contents': [ {\n" 657 " 'type': 'file',\n" 658 " 'name': 'XX',\n" 659 " 'external-contents': '//root/foo/bar/a'\n" 660 " }\n" 661 " ]\n" 662 "}]}", 663 Lower); 664 ASSERT_TRUE(FS.get() != nullptr); 665 666 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O( 667 new vfs::OverlayFileSystem(Lower)); 668 O->pushOverlay(FS); 669 670 ErrorOr<vfs::Status> SS = O->status("//root/xx"); 671 EXPECT_EQ(SS.getError(), llvm::errc::no_such_file_or_directory); 672 SS = O->status("//root/xX"); 673 EXPECT_EQ(SS.getError(), llvm::errc::no_such_file_or_directory); 674 SS = O->status("//root/Xx"); 675 EXPECT_EQ(SS.getError(), llvm::errc::no_such_file_or_directory); 676 EXPECT_EQ(0, NumDiagnostics); 677 } 678 679 TEST_F(VFSFromYAMLTest, IllegalVFSFile) { 680 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 681 682 // invalid YAML at top-level 683 IntrusiveRefCntPtr<vfs::FileSystem> FS = getFromYAMLString("{]", Lower); 684 EXPECT_EQ(nullptr, FS.get()); 685 // invalid YAML in roots 686 FS = getFromYAMLString("{ 'roots':[}", Lower); 687 // invalid YAML in directory 688 FS = getFromYAMLString( 689 "{ 'roots':[ { 'name': 'foo', 'type': 'directory', 'contents': [}", 690 Lower); 691 EXPECT_EQ(nullptr, FS.get()); 692 693 // invalid configuration 694 FS = getFromYAMLString("{ 'knobular': 'true', 'roots':[] }", Lower); 695 EXPECT_EQ(nullptr, FS.get()); 696 FS = getFromYAMLString("{ 'case-sensitive': 'maybe', 'roots':[] }", Lower); 697 EXPECT_EQ(nullptr, FS.get()); 698 699 // invalid roots 700 FS = getFromYAMLString("{ 'roots':'' }", Lower); 701 EXPECT_EQ(nullptr, FS.get()); 702 FS = getFromYAMLString("{ 'roots':{} }", Lower); 703 EXPECT_EQ(nullptr, FS.get()); 704 705 // invalid entries 706 FS = getFromYAMLString( 707 "{ 'roots':[ { 'type': 'other', 'name': 'me', 'contents': '' }", Lower); 708 EXPECT_EQ(nullptr, FS.get()); 709 FS = getFromYAMLString("{ 'roots':[ { 'type': 'file', 'name': [], " 710 "'external-contents': 'other' }", 711 Lower); 712 EXPECT_EQ(nullptr, FS.get()); 713 FS = getFromYAMLString( 714 "{ 'roots':[ { 'type': 'file', 'name': 'me', 'external-contents': [] }", 715 Lower); 716 EXPECT_EQ(nullptr, FS.get()); 717 FS = getFromYAMLString( 718 "{ 'roots':[ { 'type': 'file', 'name': 'me', 'external-contents': {} }", 719 Lower); 720 EXPECT_EQ(nullptr, FS.get()); 721 FS = getFromYAMLString( 722 "{ 'roots':[ { 'type': 'directory', 'name': 'me', 'contents': {} }", 723 Lower); 724 EXPECT_EQ(nullptr, FS.get()); 725 FS = getFromYAMLString( 726 "{ 'roots':[ { 'type': 'directory', 'name': 'me', 'contents': '' }", 727 Lower); 728 EXPECT_EQ(nullptr, FS.get()); 729 FS = getFromYAMLString( 730 "{ 'roots':[ { 'thingy': 'directory', 'name': 'me', 'contents': [] }", 731 Lower); 732 EXPECT_EQ(nullptr, FS.get()); 733 734 // missing mandatory fields 735 FS = getFromYAMLString("{ 'roots':[ { 'type': 'file', 'name': 'me' }", Lower); 736 EXPECT_EQ(nullptr, FS.get()); 737 FS = getFromYAMLString( 738 "{ 'roots':[ { 'type': 'file', 'external-contents': 'other' }", Lower); 739 EXPECT_EQ(nullptr, FS.get()); 740 FS = getFromYAMLString("{ 'roots':[ { 'name': 'me', 'contents': [] }", Lower); 741 EXPECT_EQ(nullptr, FS.get()); 742 743 // duplicate keys 744 FS = getFromYAMLString("{ 'roots':[], 'roots':[] }", Lower); 745 EXPECT_EQ(nullptr, FS.get()); 746 FS = getFromYAMLString( 747 "{ 'case-sensitive':'true', 'case-sensitive':'true', 'roots':[] }", 748 Lower); 749 EXPECT_EQ(nullptr, FS.get()); 750 FS = 751 getFromYAMLString("{ 'roots':[{'name':'me', 'name':'you', 'type':'file', " 752 "'external-contents':'blah' } ] }", 753 Lower); 754 EXPECT_EQ(nullptr, FS.get()); 755 756 // missing version 757 FS = getFromYAMLRawString("{ 'roots':[] }", Lower); 758 EXPECT_EQ(nullptr, FS.get()); 759 760 // bad version number 761 FS = getFromYAMLRawString("{ 'version':'foo', 'roots':[] }", Lower); 762 EXPECT_EQ(nullptr, FS.get()); 763 FS = getFromYAMLRawString("{ 'version':-1, 'roots':[] }", Lower); 764 EXPECT_EQ(nullptr, FS.get()); 765 FS = getFromYAMLRawString("{ 'version':100000, 'roots':[] }", Lower); 766 EXPECT_EQ(nullptr, FS.get()); 767 EXPECT_EQ(24, NumDiagnostics); 768 } 769 770 TEST_F(VFSFromYAMLTest, UseExternalName) { 771 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 772 Lower->addRegularFile("//root/external/file"); 773 774 IntrusiveRefCntPtr<vfs::FileSystem> FS = getFromYAMLString( 775 "{ 'roots': [\n" 776 " { 'type': 'file', 'name': '//root/A',\n" 777 " 'external-contents': '//root/external/file'\n" 778 " },\n" 779 " { 'type': 'file', 'name': '//root/B',\n" 780 " 'use-external-name': true,\n" 781 " 'external-contents': '//root/external/file'\n" 782 " },\n" 783 " { 'type': 'file', 'name': '//root/C',\n" 784 " 'use-external-name': false,\n" 785 " 'external-contents': '//root/external/file'\n" 786 " }\n" 787 "] }", Lower); 788 ASSERT_TRUE(nullptr != FS.get()); 789 790 // default true 791 EXPECT_EQ("//root/external/file", FS->status("//root/A")->getName()); 792 // explicit 793 EXPECT_EQ("//root/external/file", FS->status("//root/B")->getName()); 794 EXPECT_EQ("//root/C", FS->status("//root/C")->getName()); 795 796 // global configuration 797 FS = getFromYAMLString( 798 "{ 'use-external-names': false,\n" 799 " 'roots': [\n" 800 " { 'type': 'file', 'name': '//root/A',\n" 801 " 'external-contents': '//root/external/file'\n" 802 " },\n" 803 " { 'type': 'file', 'name': '//root/B',\n" 804 " 'use-external-name': true,\n" 805 " 'external-contents': '//root/external/file'\n" 806 " },\n" 807 " { 'type': 'file', 'name': '//root/C',\n" 808 " 'use-external-name': false,\n" 809 " 'external-contents': '//root/external/file'\n" 810 " }\n" 811 "] }", Lower); 812 ASSERT_TRUE(nullptr != FS.get()); 813 814 // default 815 EXPECT_EQ("//root/A", FS->status("//root/A")->getName()); 816 // explicit 817 EXPECT_EQ("//root/external/file", FS->status("//root/B")->getName()); 818 EXPECT_EQ("//root/C", FS->status("//root/C")->getName()); 819 } 820 821 TEST_F(VFSFromYAMLTest, MultiComponentPath) { 822 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 823 Lower->addRegularFile("//root/other"); 824 825 // file in roots 826 IntrusiveRefCntPtr<vfs::FileSystem> FS = getFromYAMLString( 827 "{ 'roots': [\n" 828 " { 'type': 'file', 'name': '//root/path/to/file',\n" 829 " 'external-contents': '//root/other' }]\n" 830 "}", Lower); 831 ASSERT_TRUE(nullptr != FS.get()); 832 EXPECT_FALSE(FS->status("//root/path/to/file").getError()); 833 EXPECT_FALSE(FS->status("//root/path/to").getError()); 834 EXPECT_FALSE(FS->status("//root/path").getError()); 835 EXPECT_FALSE(FS->status("//root/").getError()); 836 837 // at the start 838 FS = getFromYAMLString( 839 "{ 'roots': [\n" 840 " { 'type': 'directory', 'name': '//root/path/to',\n" 841 " 'contents': [ { 'type': 'file', 'name': 'file',\n" 842 " 'external-contents': '//root/other' }]}]\n" 843 "}", Lower); 844 ASSERT_TRUE(nullptr != FS.get()); 845 EXPECT_FALSE(FS->status("//root/path/to/file").getError()); 846 EXPECT_FALSE(FS->status("//root/path/to").getError()); 847 EXPECT_FALSE(FS->status("//root/path").getError()); 848 EXPECT_FALSE(FS->status("//root/").getError()); 849 850 // at the end 851 FS = getFromYAMLString( 852 "{ 'roots': [\n" 853 " { 'type': 'directory', 'name': '//root/',\n" 854 " 'contents': [ { 'type': 'file', 'name': 'path/to/file',\n" 855 " 'external-contents': '//root/other' }]}]\n" 856 "}", Lower); 857 ASSERT_TRUE(nullptr != FS.get()); 858 EXPECT_FALSE(FS->status("//root/path/to/file").getError()); 859 EXPECT_FALSE(FS->status("//root/path/to").getError()); 860 EXPECT_FALSE(FS->status("//root/path").getError()); 861 EXPECT_FALSE(FS->status("//root/").getError()); 862 } 863 864 TEST_F(VFSFromYAMLTest, TrailingSlashes) { 865 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 866 Lower->addRegularFile("//root/other"); 867 868 // file in roots 869 IntrusiveRefCntPtr<vfs::FileSystem> FS = getFromYAMLString( 870 "{ 'roots': [\n" 871 " { 'type': 'directory', 'name': '//root/path/to////',\n" 872 " 'contents': [ { 'type': 'file', 'name': 'file',\n" 873 " 'external-contents': '//root/other' }]}]\n" 874 "}", Lower); 875 ASSERT_TRUE(nullptr != FS.get()); 876 EXPECT_FALSE(FS->status("//root/path/to/file").getError()); 877 EXPECT_FALSE(FS->status("//root/path/to").getError()); 878 EXPECT_FALSE(FS->status("//root/path").getError()); 879 EXPECT_FALSE(FS->status("//root/").getError()); 880 } 881 882 TEST_F(VFSFromYAMLTest, DirectoryIteration) { 883 IntrusiveRefCntPtr<DummyFileSystem> Lower(new DummyFileSystem()); 884 Lower->addDirectory("//root/"); 885 Lower->addDirectory("//root/foo"); 886 Lower->addDirectory("//root/foo/bar"); 887 Lower->addRegularFile("//root/foo/bar/a"); 888 Lower->addRegularFile("//root/foo/bar/b"); 889 Lower->addRegularFile("//root/file3"); 890 IntrusiveRefCntPtr<vfs::FileSystem> FS = 891 getFromYAMLString("{ 'use-external-names': false,\n" 892 " 'roots': [\n" 893 "{\n" 894 " 'type': 'directory',\n" 895 " 'name': '//root/',\n" 896 " 'contents': [ {\n" 897 " 'type': 'file',\n" 898 " 'name': 'file1',\n" 899 " 'external-contents': '//root/foo/bar/a'\n" 900 " },\n" 901 " {\n" 902 " 'type': 'file',\n" 903 " 'name': 'file2',\n" 904 " 'external-contents': '//root/foo/bar/b'\n" 905 " }\n" 906 " ]\n" 907 "}\n" 908 "]\n" 909 "}", 910 Lower); 911 ASSERT_TRUE(FS.get() != NULL); 912 913 IntrusiveRefCntPtr<vfs::OverlayFileSystem> O( 914 new vfs::OverlayFileSystem(Lower)); 915 O->pushOverlay(FS); 916 917 std::error_code EC; 918 { 919 const char *Contents[] = {"//root/file1", "//root/file2", "//root/file3", 920 "//root/foo"}; 921 checkContents(O->dir_begin("//root/", EC), makeStringRefVector(Contents)); 922 } 923 924 { 925 const char *Contents[] = {"//root/foo/bar/a", "//root/foo/bar/b"}; 926 checkContents(O->dir_begin("//root/foo/bar", EC), 927 makeStringRefVector(Contents)); 928 } 929 } 930