xref: /llvm-project/lldb/unittests/Host/FileSystemTest.cpp (revision 9ca491da2f336150d700dd1e00f27bcee23bdd09)
1 //===-- FileSystemTest.cpp --------------------------------------*- C++ -*-===//
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 "gmock/gmock.h"
11 #include "gtest/gtest.h"
12 
13 #include "lldb/Host/FileSystem.h"
14 #include "llvm/Support/Errc.h"
15 
16 extern const char *TestMainArgv0;
17 
18 using namespace lldb_private;
19 using namespace llvm;
20 using llvm::sys::fs::UniqueID;
21 
22 // Modified from llvm/unittests/Support/VirtualFileSystemTest.cpp
23 namespace {
24 struct DummyFile : public vfs::File {
25   vfs::Status S;
26   explicit DummyFile(vfs::Status S) : S(S) {}
27   llvm::ErrorOr<vfs::Status> status() override { return S; }
28   llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
29   getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator,
30             bool IsVolatile) override {
31     llvm_unreachable("unimplemented");
32   }
33   std::error_code close() override { return std::error_code(); }
34 };
35 
36 class DummyFileSystem : public vfs::FileSystem {
37   int FSID;   // used to produce UniqueIDs
38   int FileID; // used to produce UniqueIDs
39   std::string cwd;
40   std::map<std::string, vfs::Status> FilesAndDirs;
41 
42   static int getNextFSID() {
43     static int Count = 0;
44     return Count++;
45   }
46 
47 public:
48   DummyFileSystem() : FSID(getNextFSID()), FileID(0) {}
49 
50   ErrorOr<vfs::Status> status(const Twine &Path) override {
51     std::map<std::string, vfs::Status>::iterator I =
52         FilesAndDirs.find(Path.str());
53     if (I == FilesAndDirs.end())
54       return make_error_code(llvm::errc::no_such_file_or_directory);
55     return I->second;
56   }
57   ErrorOr<std::unique_ptr<vfs::File>>
58   openFileForRead(const Twine &Path) override {
59     auto S = status(Path);
60     if (S)
61       return std::unique_ptr<vfs::File>(new DummyFile{*S});
62     return S.getError();
63   }
64   llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
65     return cwd;
66   }
67   std::error_code setCurrentWorkingDirectory(const Twine &Path) override {
68     cwd = Path.str();
69     return std::error_code();
70   }
71   // Map any symlink to "/symlink".
72   std::error_code getRealPath(const Twine &Path,
73                               SmallVectorImpl<char> &Output) const override {
74     auto I = FilesAndDirs.find(Path.str());
75     if (I == FilesAndDirs.end())
76       return make_error_code(llvm::errc::no_such_file_or_directory);
77     if (I->second.isSymlink()) {
78       Output.clear();
79       Twine("/symlink").toVector(Output);
80       return std::error_code();
81     }
82     Output.clear();
83     Path.toVector(Output);
84     return std::error_code();
85   }
86 
87   struct DirIterImpl : public llvm::vfs::detail::DirIterImpl {
88     std::map<std::string, vfs::Status> &FilesAndDirs;
89     std::map<std::string, vfs::Status>::iterator I;
90     std::string Path;
91     bool isInPath(StringRef S) {
92       if (Path.size() < S.size() && S.find(Path) == 0) {
93         auto LastSep = S.find_last_of('/');
94         if (LastSep == Path.size() || LastSep == Path.size() - 1)
95           return true;
96       }
97       return false;
98     }
99     DirIterImpl(std::map<std::string, vfs::Status> &FilesAndDirs,
100                 const Twine &_Path)
101         : FilesAndDirs(FilesAndDirs), I(FilesAndDirs.begin()),
102           Path(_Path.str()) {
103       for (; I != FilesAndDirs.end(); ++I) {
104         if (isInPath(I->first)) {
105           CurrentEntry =
106               vfs::directory_entry(I->second.getName(), I->second.getType());
107           break;
108         }
109       }
110     }
111     std::error_code increment() override {
112       ++I;
113       for (; I != FilesAndDirs.end(); ++I) {
114         if (isInPath(I->first)) {
115           CurrentEntry =
116               vfs::directory_entry(I->second.getName(), I->second.getType());
117           break;
118         }
119       }
120       if (I == FilesAndDirs.end())
121         CurrentEntry = vfs::directory_entry();
122       return std::error_code();
123     }
124   };
125 
126   vfs::directory_iterator dir_begin(const Twine &Dir,
127                                     std::error_code &EC) override {
128     return vfs::directory_iterator(
129         std::make_shared<DirIterImpl>(FilesAndDirs, Dir));
130   }
131 
132   void addEntry(StringRef Path, const vfs::Status &Status) {
133     FilesAndDirs[Path] = Status;
134   }
135 
136   void addRegularFile(StringRef Path, sys::fs::perms Perms = sys::fs::all_all) {
137     vfs::Status S(Path, UniqueID(FSID, FileID++),
138                   std::chrono::system_clock::now(), 0, 0, 1024,
139                   sys::fs::file_type::regular_file, Perms);
140     addEntry(Path, S);
141   }
142 
143   void addDirectory(StringRef Path, sys::fs::perms Perms = sys::fs::all_all) {
144     vfs::Status S(Path, UniqueID(FSID, FileID++),
145                   std::chrono::system_clock::now(), 0, 0, 0,
146                   sys::fs::file_type::directory_file, Perms);
147     addEntry(Path, S);
148   }
149 
150   void addSymlink(StringRef Path) {
151     vfs::Status S(Path, UniqueID(FSID, FileID++),
152                   std::chrono::system_clock::now(), 0, 0, 0,
153                   sys::fs::file_type::symlink_file, sys::fs::all_all);
154     addEntry(Path, S);
155   }
156 };
157 } // namespace
158 
159 TEST(FileSystemTest, FileAndDirectoryComponents) {
160   using namespace std::chrono;
161 
162   const bool resolve = true;
163 #ifdef _WIN32
164   FileSpec fs1("C:\\FILE\\THAT\\DOES\\NOT\\EXIST.TXT", !resolve);
165 #else
166   FileSpec fs1("/file/that/does/not/exist.txt", !resolve);
167 #endif
168   FileSpec fs2(TestMainArgv0, resolve);
169 
170   FileSystem fs;
171 
172   EXPECT_EQ(system_clock::time_point(), fs.GetModificationTime(fs1));
173   EXPECT_LT(system_clock::time_point() + hours(24 * 365 * 20),
174             fs.GetModificationTime(fs2));
175 }
176 
177 static IntrusiveRefCntPtr<DummyFileSystem> GetSimpleDummyFS() {
178   IntrusiveRefCntPtr<DummyFileSystem> D(new DummyFileSystem());
179   D->addRegularFile("/foo");
180   D->addDirectory("/bar");
181   D->addSymlink("/baz");
182   D->addRegularFile("/qux", ~sys::fs::perms::all_read);
183   D->setCurrentWorkingDirectory("/");
184   return D;
185 }
186 
187 TEST(FileSystemTest, Exists) {
188   FileSystem fs(GetSimpleDummyFS());
189 
190   EXPECT_TRUE(fs.Exists("/foo"));
191   EXPECT_TRUE(fs.Exists(FileSpec("/foo", false, FileSpec::Style::posix)));
192 }
193 
194 TEST(FileSystemTest, Readable) {
195   FileSystem fs(GetSimpleDummyFS());
196 
197   EXPECT_TRUE(fs.Readable("/foo"));
198   EXPECT_TRUE(fs.Readable(FileSpec("/foo", false, FileSpec::Style::posix)));
199 
200   EXPECT_FALSE(fs.Readable("/qux"));
201   EXPECT_FALSE(fs.Readable(FileSpec("/qux", false, FileSpec::Style::posix)));
202 }
203 
204 TEST(FileSystemTest, GetByteSize) {
205   FileSystem fs(GetSimpleDummyFS());
206 
207   EXPECT_EQ((uint64_t)1024, fs.GetByteSize("/foo"));
208   EXPECT_EQ((uint64_t)1024,
209             fs.GetByteSize(FileSpec("/foo", false, FileSpec::Style::posix)));
210 }
211 
212 TEST(FileSystemTest, GetPermissions) {
213   FileSystem fs(GetSimpleDummyFS());
214 
215   EXPECT_EQ(sys::fs::all_all, fs.GetPermissions("/foo"));
216   EXPECT_EQ(sys::fs::all_all,
217             fs.GetPermissions(FileSpec("/foo", false, FileSpec::Style::posix)));
218 }
219 
220 TEST(FileSystemTest, MakeAbsolute) {
221   FileSystem fs(GetSimpleDummyFS());
222 
223   {
224     StringRef foo_relative = "foo";
225     SmallString<16> foo(foo_relative);
226     auto EC = fs.MakeAbsolute(foo);
227     EXPECT_FALSE(EC);
228     EXPECT_TRUE(foo.equals("/foo"));
229   }
230 
231   {
232     FileSpec file_spec("foo", false);
233     auto EC = fs.MakeAbsolute(file_spec);
234     EXPECT_FALSE(EC);
235     EXPECT_EQ("/foo", file_spec.GetPath());
236   }
237 }
238 
239 TEST(FileSystemTest, Resolve) {
240   FileSystem fs(GetSimpleDummyFS());
241 
242   {
243     StringRef foo_relative = "foo";
244     SmallString<16> foo(foo_relative);
245     fs.Resolve(foo);
246     EXPECT_TRUE(foo.equals("/foo"));
247   }
248 
249   {
250     FileSpec file_spec("foo", false);
251     fs.Resolve(file_spec);
252     EXPECT_EQ("/foo", file_spec.GetPath());
253   }
254 
255   {
256     StringRef foo_relative = "bogus";
257     SmallString<16> foo(foo_relative);
258     fs.Resolve(foo);
259     EXPECT_TRUE(foo.equals("bogus"));
260   }
261 
262   {
263     FileSpec file_spec("bogus", false);
264     fs.Resolve(file_spec);
265     EXPECT_EQ("bogus", file_spec.GetPath());
266   }
267 }
268 
269 FileSystem::EnumerateDirectoryResult
270 VFSCallback(void *baton, llvm::sys::fs::file_type file_type,
271             llvm::StringRef path) {
272   auto visited = static_cast<std::vector<std::string> *>(baton);
273   visited->push_back(path.str());
274   return FileSystem::eEnumerateDirectoryResultNext;
275 }
276 
277 TEST(FileSystemTest, EnumerateDirectory) {
278   FileSystem fs(GetSimpleDummyFS());
279 
280   std::vector<std::string> visited;
281 
282   constexpr bool find_directories = true;
283   constexpr bool find_files = true;
284   constexpr bool find_other = true;
285 
286   fs.EnumerateDirectory("/", find_directories, find_files, find_other,
287                         VFSCallback, &visited);
288 
289   EXPECT_THAT(visited,
290               testing::UnorderedElementsAre("/foo", "/bar", "/baz", "/qux"));
291 }
292