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