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
15extern const char *TestMainArgv0;
16
17using namespace lldb_private;
18using namespace llvm;
19using llvm::sys::fs::UniqueID;
20
21// Modified from llvm/unittests/Support/VirtualFileSystemTest.cpp
22namespace {
23struct 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
35class 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
41 static int getNextFSID() {
42 static int Count = 0;
43 return Count++;
44 }
45
46public:
47 DummyFileSystem() : FSID(getNextFSID()) {}
48
49 ErrorOr<vfs::Status> status(const Twine &Path) override {
50 std::map<std::string, vfs::Status>::iterator I =
51 FilesAndDirs.find(x: Path.str());
52 if (I == FilesAndDirs.end())
53 return make_error_code(E: 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>>
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 }
68 llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
69 return cwd;
70 }
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".
76 std::error_code getRealPath(const Twine &Path,
77 SmallVectorImpl<char> &Output) override {
78 auto I = FilesAndDirs.find(x: Path.str());
79 if (I == FilesAndDirs.end())
80 return make_error_code(E: llvm::errc::no_such_file_or_directory);
81 if (I->second.isSymlink()) {
82 Output.clear();
83 Twine("/symlink").toVector(Out&: Output);
84 return std::error_code();
85 }
86 Output.clear();
87 Path.toVector(Out&: 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;
95 bool isInPath(StringRef S) {
96 if (Path.size() < S.size() && S.starts_with(Prefix: Path)) {
97 auto LastSep = S.find_last_of(C: '/');
98 if (LastSep == Path.size() || LastSep == Path.size() - 1)
99 return true;
100 }
101 return false;
102 }
103 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(S: I->first)) {
109 CurrentEntry = vfs::directory_entry(std::string(I->second.getName()),
110 I->second.getType());
111 break;
112 }
113 }
114 }
115 std::error_code increment() override {
116 ++I;
117 for (; I != FilesAndDirs.end(); ++I) {
118 if (isInPath(S: 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
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>(args&: FilesAndDirs, args: Dir));
134 }
135
136 void addEntry(StringRef Path, const vfs::Status &Status) {
137 FilesAndDirs[std::string(Path)] = Status;
138 }
139
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, Status: S);
145 }
146
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, Status: S);
152 }
153
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, Status: S);
159 }
160
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, Status: S);
166 }
167};
168} // namespace
169
170TEST(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(file_spec&: 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
188static IntrusiveRefCntPtr<DummyFileSystem> GetSimpleDummyFS() {
189 IntrusiveRefCntPtr<DummyFileSystem> D(new DummyFileSystem());
190 D->addRegularFile(Path: "/foo");
191 D->addDirectory(Path: "/bar");
192 D->addSymlink(Path: "/baz");
193 D->addBrokenSymlink(Path: "/lux");
194 D->addRegularFile(Path: "/qux", Perms: ~sys::fs::perms::all_read);
195 D->setCurrentWorkingDirectory("/");
196 return D;
197}
198
199TEST(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
206TEST(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
216TEST(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
224TEST(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
232TEST(FileSystemTest, MakeAbsolute) {
233 FileSystem fs(GetSimpleDummyFS());
234
235 {
236 StringRef foo_relative = "foo";
237 SmallString<16> foo(foo_relative);
238 auto EC = fs.MakeAbsolute(path&: 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
251TEST(FileSystemTest, Resolve) {
252 FileSystem fs(GetSimpleDummyFS());
253
254 {
255 StringRef foo_relative = "foo";
256 SmallString<16> foo(foo_relative);
257 fs.Resolve(path&: 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(path&: 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
281FileSystem::EnumerateDirectoryResult
282VFSCallback(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(x: path.str());
286 return FileSystem::eEnumerateDirectoryResultNext;
287}
288
289TEST(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(path: "/", find_directories, find_files, find_other,
299 callback: VFSCallback, callback_baton: &visited);
300
301 EXPECT_THAT(visited,
302 testing::UnorderedElementsAre("/foo", "/bar", "/baz", "/qux"));
303}
304
305TEST(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(file_spec: spec, options: File::eOpenOptionReadOnly, permissions: 0, should_close_fd: true);
313 ASSERT_FALSE(file);
314 std::error_code code = errorToErrorCode(Err: file.takeError());
315 EXPECT_EQ(code.category(), std::system_category());
316 EXPECT_EQ(code.value(), ENOENT);
317}
318
319TEST(FileSystemTest, EmptyTest) {
320 FileSpec spec;
321 FileSystem fs;
322
323 {
324 std::error_code ec;
325 fs.DirBegin(file_spec: 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(file_spec: 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

source code of lldb/unittests/Host/FileSystemTest.cpp