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