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