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__anonef9ab2f10111::DummyFile25 explicit DummyFile(vfs::Status S) : S(S) {}
status__anonef9ab2f10111::DummyFile26 llvm::ErrorOr<vfs::Status> status() override { return S; }
27 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
getBuffer__anonef9ab2f10111::DummyFile28 getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator,
29 bool IsVolatile) override {
30 llvm_unreachable("unimplemented");
31 }
close__anonef9ab2f10111::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 return I->second;
55 }
56 ErrorOr<std::unique_ptr<vfs::File>>
openFileForRead(const Twine & Path)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 }
getCurrentWorkingDirectory() const63 llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
64 return cwd;
65 }
setCurrentWorkingDirectory(const Twine & Path)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".
getRealPath(const Twine & Path,SmallVectorImpl<char> & Output) const71 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;
isInPath__anonef9ab2f10111::DummyFileSystem::DirIterImpl90 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 }
DirIterImpl__anonef9ab2f10111::DummyFileSystem::DirIterImpl98 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 = vfs::directory_entry(std::string(I->second.getName()),
105 I->second.getType());
106 break;
107 }
108 }
109 }
increment__anonef9ab2f10111::DummyFileSystem::DirIterImpl110 std::error_code increment() override {
111 ++I;
112 for (; I != FilesAndDirs.end(); ++I) {
113 if (isInPath(I->first)) {
114 CurrentEntry = vfs::directory_entry(std::string(I->second.getName()),
115 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
dir_begin(const Twine & Dir,std::error_code & EC)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
addEntry(StringRef Path,const vfs::Status & Status)131 void addEntry(StringRef Path, const vfs::Status &Status) {
132 FilesAndDirs[std::string(Path)] = Status;
133 }
134
addRegularFile(StringRef Path,sys::fs::perms Perms=sys::fs::all_all)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
addDirectory(StringRef Path,sys::fs::perms Perms=sys::fs::all_all)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
addSymlink(StringRef Path)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
TEST(FileSystemTest,FileAndDirectoryComponents)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
GetSimpleDummyFS()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
TEST(FileSystemTest,Exists)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
TEST(FileSystemTest,Readable)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
TEST(FileSystemTest,GetByteSize)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
TEST(FileSystemTest,GetPermissions)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
TEST(FileSystemTest,MakeAbsolute)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
TEST(FileSystemTest,Resolve)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
VFSCallback(void * baton,llvm::sys::fs::file_type file_type,llvm::StringRef path)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
TEST(FileSystemTest,EnumerateDirectory)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
TEST(FileSystemTest,OpenErrno)292 TEST(FileSystemTest, OpenErrno) {
293 #ifdef _WIN32
294 FileSpec spec("C:\\FILE\\THAT\\DOES\\NOT\\EXIST.TXT");
295 #else
296 FileSpec spec("/file/that/does/not/exist.txt");
297 #endif
298 FileSystem fs;
299 auto file = fs.Open(spec, File::eOpenOptionReadOnly, 0, true);
300 ASSERT_FALSE(file);
301 std::error_code code = errorToErrorCode(file.takeError());
302 EXPECT_EQ(code.category(), std::system_category());
303 EXPECT_EQ(code.value(), ENOENT);
304 }
305
TEST(FileSystemTest,EmptyTest)306 TEST(FileSystemTest, EmptyTest) {
307 FileSpec spec;
308 FileSystem fs;
309
310 {
311 std::error_code ec;
312 fs.DirBegin(spec, ec);
313 EXPECT_EQ(ec.category(), std::system_category());
314 EXPECT_EQ(ec.value(), ENOENT);
315 }
316
317 {
318 llvm::ErrorOr<vfs::Status> status = fs.GetStatus(spec);
319 ASSERT_FALSE(status);
320 EXPECT_EQ(status.getError().category(), std::system_category());
321 EXPECT_EQ(status.getError().value(), ENOENT);
322 }
323
324 EXPECT_EQ(sys::TimePoint<>(), fs.GetModificationTime(spec));
325 EXPECT_EQ(static_cast<uint64_t>(0), fs.GetByteSize(spec));
326 EXPECT_EQ(llvm::sys::fs::perms::perms_not_known, fs.GetPermissions(spec));
327 EXPECT_FALSE(fs.Exists(spec));
328 EXPECT_FALSE(fs.Readable(spec));
329 EXPECT_FALSE(fs.IsDirectory(spec));
330 EXPECT_FALSE(fs.IsLocal(spec));
331 }
332