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; 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 = 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 46 public: 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(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>> 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) const 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; 95 bool isInPath(StringRef S) { 96 if (Path.size() < S.size() && S.find(Path) == 0) { 97 auto LastSep = S.find_last_of('/'); 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(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(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>(FilesAndDirs, 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, 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, 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, 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, S); 166 } 167 }; 168 } // namespace 169 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 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 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 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 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 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 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 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 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 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 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 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