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; // 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 = vfs::directory_entry(std::string(I->second.getName()), 105 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 = 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 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[std::string(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 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::eOpenOptionRead, 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 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