345 lines · cpp
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-exception6//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.cpp22namespace {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 UniqueIDs37 int FileID = 0; // used to produce UniqueIDs38 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(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 that55 // 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(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.starts_with(Path)) {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} // namespace169 170TEST(FileSystemTest, FileAndDirectoryComponents) {171 using namespace std::chrono;172 FileSystem fs;173 174#ifdef _WIN32175 FileSpec fs1("C:\\FILE\\THAT\\DOES\\NOT\\EXIST.TXT");176#else177 FileSpec fs1("/file/that/does/not/exist.txt");178#endif179 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 188static IntrusiveRefCntPtr<DummyFileSystem> GetSimpleDummyFS() {189 auto D = makeIntrusiveRefCnt<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 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(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(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 281FileSystem::EnumerateDirectoryResult282VFSCallback(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 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("/", find_directories, find_files, find_other,299 VFSCallback, &visited);300 301 EXPECT_THAT(visited,302 testing::UnorderedElementsAre("/foo", "/bar", "/baz", "/qux"));303}304 305TEST(FileSystemTest, OpenErrno) {306#ifdef _WIN32307 FileSpec spec("C:\\FILE\\THAT\\DOES\\NOT\\EXIST.TXT");308#else309 FileSpec spec("/file/that/does/not/exist.txt");310#endif311 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 319TEST(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