diff options
Diffstat (limited to 'unittests/Support/Path.cpp')
-rw-r--r-- | unittests/Support/Path.cpp | 578 |
1 files changed, 428 insertions, 150 deletions
diff --git a/unittests/Support/Path.cpp b/unittests/Support/Path.cpp index f624626f5e53..dc5bfb8c7bd5 100644 --- a/unittests/Support/Path.cpp +++ b/unittests/Support/Path.cpp @@ -12,6 +12,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Triple.h" #include "llvm/BinaryFormat/Magic.h" +#include "llvm/Config/llvm-config.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Errc.h" #include "llvm/Support/ErrorHandling.h" @@ -21,9 +22,11 @@ #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/raw_ostream.h" #include "gtest/gtest.h" +#include "gmock/gmock.h" -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 #include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/Chrono.h" #include <windows.h> #include <winerror.h> #endif @@ -47,8 +50,22 @@ using namespace llvm::sys; } else { \ } +#define ASSERT_ERROR(x) \ + if (!x) { \ + SmallString<128> MessageStorage; \ + raw_svector_ostream Message(MessageStorage); \ + Message << #x ": did not return a failure error code.\n"; \ + GTEST_FATAL_FAILURE_(MessageStorage.c_str()); \ + } + namespace { +struct FileDescriptorCloser { + explicit FileDescriptorCloser(int FD) : FD(FD) {} + ~FileDescriptorCloser() { ::close(FD); } + int FD; +}; + TEST(is_separator, Works) { EXPECT_TRUE(path::is_separator('/')); EXPECT_FALSE(path::is_separator('\0')); @@ -58,7 +75,7 @@ TEST(is_separator, Works) { EXPECT_TRUE(path::is_separator('\\', path::Style::windows)); EXPECT_FALSE(path::is_separator('\\', path::Style::posix)); -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 EXPECT_TRUE(path::is_separator('\\')); #else EXPECT_FALSE(path::is_separator('\\')); @@ -78,6 +95,7 @@ TEST(Support, Path) { paths.push_back("foo/bar"); paths.push_back("/foo/bar"); paths.push_back("//net"); + paths.push_back("//net/"); paths.push_back("//net/foo"); paths.push_back("///foo///"); paths.push_back("///foo///bar"); @@ -108,27 +126,30 @@ TEST(Support, Path) { paths.push_back("c:\\foo/"); paths.push_back("c:/foo\\bar"); - SmallVector<StringRef, 5> ComponentStack; for (SmallVector<StringRef, 40>::const_iterator i = paths.begin(), e = paths.end(); i != e; ++i) { + SCOPED_TRACE(*i); + SmallVector<StringRef, 5> ComponentStack; for (sys::path::const_iterator ci = sys::path::begin(*i), ce = sys::path::end(*i); ci != ce; ++ci) { - ASSERT_FALSE(ci->empty()); + EXPECT_FALSE(ci->empty()); ComponentStack.push_back(*ci); } + SmallVector<StringRef, 5> ReverseComponentStack; for (sys::path::reverse_iterator ci = sys::path::rbegin(*i), ce = sys::path::rend(*i); ci != ce; ++ci) { - ASSERT_TRUE(*ci == ComponentStack.back()); - ComponentStack.pop_back(); + EXPECT_FALSE(ci->empty()); + ReverseComponentStack.push_back(*ci); } - ASSERT_TRUE(ComponentStack.empty()); + std::reverse(ReverseComponentStack.begin(), ReverseComponentStack.end()); + EXPECT_THAT(ComponentStack, testing::ContainerEq(ReverseComponentStack)); // Crash test most of the API - since we're iterating over all of our paths // here there isn't really anything reasonable to assert on in the results. @@ -171,115 +192,77 @@ TEST(Support, Path) { ASSERT_EQ("/root/foo.cpp", Relative); } -TEST(Support, RelativePathIterator) { - SmallString<64> Path(StringRef("c/d/e/foo.txt")); - typedef SmallVector<StringRef, 4> PathComponents; - PathComponents ExpectedPathComponents; - PathComponents ActualPathComponents; +TEST(Support, FilenameParent) { + EXPECT_EQ("/", path::filename("/")); + EXPECT_EQ("", path::parent_path("/")); - StringRef(Path).split(ExpectedPathComponents, '/'); + EXPECT_EQ("\\", path::filename("c:\\", path::Style::windows)); + EXPECT_EQ("c:", path::parent_path("c:\\", path::Style::windows)); - for (path::const_iterator I = path::begin(Path), E = path::end(Path); I != E; - ++I) { - ActualPathComponents.push_back(*I); - } + EXPECT_EQ("/", path::filename("///")); + EXPECT_EQ("", path::parent_path("///")); - ASSERT_EQ(ExpectedPathComponents.size(), ActualPathComponents.size()); + EXPECT_EQ("\\", path::filename("c:\\\\", path::Style::windows)); + EXPECT_EQ("c:", path::parent_path("c:\\\\", path::Style::windows)); - for (size_t i = 0; i <ExpectedPathComponents.size(); ++i) { - EXPECT_EQ(ExpectedPathComponents[i].str(), ActualPathComponents[i].str()); - } -} - -TEST(Support, RelativePathDotIterator) { - SmallString<64> Path(StringRef(".c/.d/../.")); - typedef SmallVector<StringRef, 4> PathComponents; - PathComponents ExpectedPathComponents; - PathComponents ActualPathComponents; - - StringRef(Path).split(ExpectedPathComponents, '/'); + EXPECT_EQ("bar", path::filename("/foo/bar")); + EXPECT_EQ("/foo", path::parent_path("/foo/bar")); - for (path::const_iterator I = path::begin(Path), E = path::end(Path); I != E; - ++I) { - ActualPathComponents.push_back(*I); - } + EXPECT_EQ("foo", path::filename("/foo")); + EXPECT_EQ("/", path::parent_path("/foo")); - ASSERT_EQ(ExpectedPathComponents.size(), ActualPathComponents.size()); + EXPECT_EQ("foo", path::filename("foo")); + EXPECT_EQ("", path::parent_path("foo")); - for (size_t i = 0; i <ExpectedPathComponents.size(); ++i) { - EXPECT_EQ(ExpectedPathComponents[i].str(), ActualPathComponents[i].str()); - } -} + EXPECT_EQ(".", path::filename("foo/")); + EXPECT_EQ("foo", path::parent_path("foo/")); -TEST(Support, AbsolutePathIterator) { - SmallString<64> Path(StringRef("/c/d/e/foo.txt")); - typedef SmallVector<StringRef, 4> PathComponents; - PathComponents ExpectedPathComponents; - PathComponents ActualPathComponents; + EXPECT_EQ("//net", path::filename("//net")); + EXPECT_EQ("", path::parent_path("//net")); - StringRef(Path).split(ExpectedPathComponents, '/'); + EXPECT_EQ("/", path::filename("//net/")); + EXPECT_EQ("//net", path::parent_path("//net/")); - // The root path will also be a component when iterating - ExpectedPathComponents[0] = "/"; + EXPECT_EQ("foo", path::filename("//net/foo")); + EXPECT_EQ("//net/", path::parent_path("//net/foo")); - for (path::const_iterator I = path::begin(Path), E = path::end(Path); I != E; - ++I) { - ActualPathComponents.push_back(*I); - } + // These checks are just to make sure we do something reasonable with the + // paths below. They are not meant to prescribe the one true interpretation of + // these paths. Other decompositions (e.g. "//" -> "" + "//") are also + // possible. + EXPECT_EQ("/", path::filename("//")); + EXPECT_EQ("", path::parent_path("//")); - ASSERT_EQ(ExpectedPathComponents.size(), ActualPathComponents.size()); + EXPECT_EQ("\\", path::filename("\\\\", path::Style::windows)); + EXPECT_EQ("", path::parent_path("\\\\", path::Style::windows)); - for (size_t i = 0; i <ExpectedPathComponents.size(); ++i) { - EXPECT_EQ(ExpectedPathComponents[i].str(), ActualPathComponents[i].str()); - } + EXPECT_EQ("\\", path::filename("\\\\\\", path::Style::windows)); + EXPECT_EQ("", path::parent_path("\\\\\\", path::Style::windows)); } -TEST(Support, AbsolutePathDotIterator) { - SmallString<64> Path(StringRef("/.c/.d/../.")); - typedef SmallVector<StringRef, 4> PathComponents; - PathComponents ExpectedPathComponents; - PathComponents ActualPathComponents; - - StringRef(Path).split(ExpectedPathComponents, '/'); - - // The root path will also be a component when iterating - ExpectedPathComponents[0] = "/"; - - for (path::const_iterator I = path::begin(Path), E = path::end(Path); I != E; - ++I) { - ActualPathComponents.push_back(*I); - } - - ASSERT_EQ(ExpectedPathComponents.size(), ActualPathComponents.size()); - - for (size_t i = 0; i <ExpectedPathComponents.size(); ++i) { - EXPECT_EQ(ExpectedPathComponents[i].str(), ActualPathComponents[i].str()); - } +static std::vector<StringRef> +GetComponents(StringRef Path, path::Style S = path::Style::native) { + return {path::begin(Path, S), path::end(Path)}; } -TEST(Support, AbsolutePathIteratorWin32) { - SmallString<64> Path(StringRef("c:\\c\\e\\foo.txt")); - typedef SmallVector<StringRef, 4> PathComponents; - PathComponents ExpectedPathComponents; - PathComponents ActualPathComponents; - - StringRef(Path).split(ExpectedPathComponents, "\\"); - - // The root path (which comes after the drive name) will also be a component - // when iterating. - ExpectedPathComponents.insert(ExpectedPathComponents.begin()+1, "\\"); - - for (path::const_iterator I = path::begin(Path, path::Style::windows), - E = path::end(Path); - I != E; ++I) { - ActualPathComponents.push_back(*I); - } - - ASSERT_EQ(ExpectedPathComponents.size(), ActualPathComponents.size()); - - for (size_t i = 0; i <ExpectedPathComponents.size(); ++i) { - EXPECT_EQ(ExpectedPathComponents[i].str(), ActualPathComponents[i].str()); - } +TEST(Support, PathIterator) { + EXPECT_THAT(GetComponents("/foo"), testing::ElementsAre("/", "foo")); + EXPECT_THAT(GetComponents("/"), testing::ElementsAre("/")); + EXPECT_THAT(GetComponents("//"), testing::ElementsAre("/")); + EXPECT_THAT(GetComponents("///"), testing::ElementsAre("/")); + EXPECT_THAT(GetComponents("c/d/e/foo.txt"), + testing::ElementsAre("c", "d", "e", "foo.txt")); + EXPECT_THAT(GetComponents(".c/.d/../."), + testing::ElementsAre(".c", ".d", "..", ".")); + EXPECT_THAT(GetComponents("/c/d/e/foo.txt"), + testing::ElementsAre("/", "c", "d", "e", "foo.txt")); + EXPECT_THAT(GetComponents("/.c/.d/../."), + testing::ElementsAre("/", ".c", ".d", "..", ".")); + EXPECT_THAT(GetComponents("c:\\c\\e\\foo.txt", path::Style::windows), + testing::ElementsAre("c:", "\\", "c", "e", "foo.txt")); + EXPECT_THAT(GetComponents("//net/"), testing::ElementsAre("//net", "/")); + EXPECT_THAT(GetComponents("//net/c/foo.txt"), + testing::ElementsAre("//net", "/", "c", "foo.txt")); } TEST(Support, AbsolutePathIteratorEnd) { @@ -287,10 +270,11 @@ TEST(Support, AbsolutePathIteratorEnd) { SmallVector<std::pair<StringRef, path::Style>, 4> Paths; Paths.emplace_back("/foo/", path::Style::native); Paths.emplace_back("/foo//", path::Style::native); - Paths.emplace_back("//net//", path::Style::native); - Paths.emplace_back("c:\\\\", path::Style::windows); + Paths.emplace_back("//net/foo/", path::Style::native); + Paths.emplace_back("c:\\foo\\", path::Style::windows); for (auto &Path : Paths) { + SCOPED_TRACE(Path.first); StringRef LastComponent = *path::rbegin(Path.first, Path.second); EXPECT_EQ(".", LastComponent); } @@ -299,8 +283,11 @@ TEST(Support, AbsolutePathIteratorEnd) { RootPaths.emplace_back("/", path::Style::native); RootPaths.emplace_back("//net/", path::Style::native); RootPaths.emplace_back("c:\\", path::Style::windows); + RootPaths.emplace_back("//net//", path::Style::native); + RootPaths.emplace_back("c:\\\\", path::Style::windows); for (auto &Path : RootPaths) { + SCOPED_TRACE(Path.first); StringRef LastComponent = *path::rbegin(Path.first, Path.second); EXPECT_EQ(1u, LastComponent.size()); EXPECT_TRUE(path::is_separator(LastComponent[0], Path.second)); @@ -309,7 +296,7 @@ TEST(Support, AbsolutePathIteratorEnd) { TEST(Support, HomeDirectory) { std::string expected; -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 if (wchar_t const *path = ::_wgetenv(L"USERPROFILE")) { auto pathLen = ::wcslen(path); ArrayRef<char> ref{reinterpret_cast<char const *>(path), @@ -398,7 +385,7 @@ TEST(Support, TempDirectory) { EXPECT_TRUE(!TempDir.empty()); } -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 static std::string path2regex(std::string Path) { size_t Pos = 0; while ((Pos = Path.find('\\', Pos)) != std::string::npos) { @@ -464,6 +451,7 @@ protected: /// Unique temporary directory in which all created filesystem entities must /// be placed. It is removed at the end of each test (must be empty). SmallString<128> TestDirectory; + SmallString<128> NonExistantFile; void SetUp() override { ASSERT_NO_ERROR( @@ -471,6 +459,11 @@ protected: // We don't care about this specific file. errs() << "Test Directory: " << TestDirectory << '\n'; errs().flush(); + NonExistantFile = TestDirectory; + + // Even though this value is hardcoded, is a 128-bit GUID, so we should be + // guaranteed that this file will never exist. + sys::path::append(NonExistantFile, "1B28B495C16344CB9822E588CD4C3EF0"); } void TearDown() override { ASSERT_NO_ERROR(fs::remove(TestDirectory.str())); } @@ -564,6 +557,25 @@ TEST_F(FileSystemTest, RealPath) { ASSERT_NO_ERROR(fs::remove_directories(Twine(TestDirectory) + "/test1")); } +#ifdef LLVM_ON_UNIX +TEST_F(FileSystemTest, RealPathNoReadPerm) { + SmallString<64> Expanded; + + ASSERT_NO_ERROR( + fs::create_directories(Twine(TestDirectory) + "/noreadperm")); + ASSERT_TRUE(fs::exists(Twine(TestDirectory) + "/noreadperm")); + + fs::setPermissions(Twine(TestDirectory) + "/noreadperm", fs::no_perms); + fs::setPermissions(Twine(TestDirectory) + "/noreadperm", fs::all_exe); + + ASSERT_NO_ERROR(fs::real_path(Twine(TestDirectory) + "/noreadperm", Expanded, + false)); + + ASSERT_NO_ERROR(fs::remove_directories(Twine(TestDirectory) + "/noreadperm")); +} +#endif + + TEST_F(FileSystemTest, TempFileKeepDiscard) { // We can keep then discard. auto TempFileOrError = fs::TempFile::create(TestDirectory + "/test-%%%%"); @@ -648,7 +660,7 @@ TEST_F(FileSystemTest, TempFiles) { ASSERT_EQ(fs::access(Twine(TempPath), sys::fs::AccessMode::Exist), errc::no_such_file_or_directory); -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 // Path name > 260 chars should get an error. const char *Path270 = "abcdefghijklmnopqrstuvwxyz9abcdefghijklmnopqrstuvwxyz8" @@ -696,7 +708,7 @@ TEST_F(FileSystemTest, CreateDir) { ::umask(OldUmask); #endif -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 // Prove that create_directories() can handle a pathname > 248 characters, // which is the documented limit for CreateDirectory(). // (248 is MAX_PATH subtracting room for an 8.3 filename.) @@ -866,58 +878,91 @@ TEST_F(FileSystemTest, BrokenSymlinkDirectoryIteration) { fs::create_link("no_such_file", Twine(TestDirectory) + "/symlink/e")); typedef std::vector<std::string> v_t; - v_t visited; - - // The directory iterator doesn't stat the file, so we should be able to - // iterate over the whole directory. + v_t VisitedNonBrokenSymlinks; + v_t VisitedBrokenSymlinks; std::error_code ec; + + // Broken symbol links are expected to throw an error. for (fs::directory_iterator i(Twine(TestDirectory) + "/symlink", ec), e; i != e; i.increment(ec)) { + if (ec == std::make_error_code(std::errc::no_such_file_or_directory)) { + VisitedBrokenSymlinks.push_back(path::filename(i->path())); + continue; + } + ASSERT_NO_ERROR(ec); - visited.push_back(path::filename(i->path())); + VisitedNonBrokenSymlinks.push_back(path::filename(i->path())); } - std::sort(visited.begin(), visited.end()); - v_t expected = {"a", "b", "c", "d", "e"}; - ASSERT_TRUE(visited.size() == expected.size()); - ASSERT_TRUE(std::equal(visited.begin(), visited.end(), expected.begin())); - visited.clear(); - - // The recursive directory iterator has to stat the file, so we need to skip - // the broken symlinks. - for (fs::recursive_directory_iterator - i(Twine(TestDirectory) + "/symlink", ec), - e; - i != e; i.increment(ec)) { - ASSERT_NO_ERROR(ec); - - ErrorOr<fs::basic_file_status> status = i->status(); - if (status.getError() == - std::make_error_code(std::errc::no_such_file_or_directory)) { - i.no_push(); + llvm::sort(VisitedNonBrokenSymlinks.begin(), VisitedNonBrokenSymlinks.end()); + llvm::sort(VisitedBrokenSymlinks.begin(), VisitedBrokenSymlinks.end()); + v_t ExpectedNonBrokenSymlinks = {"b", "d"}; + ASSERT_EQ(ExpectedNonBrokenSymlinks.size(), VisitedNonBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedNonBrokenSymlinks.begin(), + VisitedNonBrokenSymlinks.end(), + ExpectedNonBrokenSymlinks.begin())); + VisitedNonBrokenSymlinks.clear(); + + v_t ExpectedBrokenSymlinks = {"a", "c", "e"}; + ASSERT_EQ(ExpectedBrokenSymlinks.size(), VisitedBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedBrokenSymlinks.begin(), + VisitedBrokenSymlinks.end(), + ExpectedBrokenSymlinks.begin())); + VisitedBrokenSymlinks.clear(); + + // Broken symbol links are expected to throw an error. + for (fs::recursive_directory_iterator i( + Twine(TestDirectory) + "/symlink", ec), e; i != e; i.increment(ec)) { + if (ec == std::make_error_code(std::errc::no_such_file_or_directory)) { + VisitedBrokenSymlinks.push_back(path::filename(i->path())); continue; } - visited.push_back(path::filename(i->path())); + ASSERT_NO_ERROR(ec); + VisitedNonBrokenSymlinks.push_back(path::filename(i->path())); } - std::sort(visited.begin(), visited.end()); - expected = {"b", "bb", "d", "da", "dd", "ddd", "ddd"}; - ASSERT_TRUE(visited.size() == expected.size()); - ASSERT_TRUE(std::equal(visited.begin(), visited.end(), expected.begin())); - visited.clear(); - - // This recursive directory iterator doesn't follow symlinks, so we don't need - // to skip them. - for (fs::recursive_directory_iterator - i(Twine(TestDirectory) + "/symlink", ec, /*follow_symlinks=*/false), - e; + llvm::sort(VisitedNonBrokenSymlinks.begin(), VisitedNonBrokenSymlinks.end()); + llvm::sort(VisitedBrokenSymlinks.begin(), VisitedBrokenSymlinks.end()); + ExpectedNonBrokenSymlinks = {"b", "bb", "d", "da", "dd", "ddd", "ddd"}; + ASSERT_EQ(ExpectedNonBrokenSymlinks.size(), VisitedNonBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedNonBrokenSymlinks.begin(), + VisitedNonBrokenSymlinks.end(), + ExpectedNonBrokenSymlinks.begin())); + VisitedNonBrokenSymlinks.clear(); + + ExpectedBrokenSymlinks = {"a", "ba", "bc", "c", "e"}; + ASSERT_EQ(ExpectedBrokenSymlinks.size(), VisitedBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedBrokenSymlinks.begin(), + VisitedBrokenSymlinks.end(), + ExpectedBrokenSymlinks.begin())); + VisitedBrokenSymlinks.clear(); + + for (fs::recursive_directory_iterator i( + Twine(TestDirectory) + "/symlink", ec, /*follow_symlinks=*/false), e; i != e; i.increment(ec)) { + if (ec == std::make_error_code(std::errc::no_such_file_or_directory)) { + VisitedBrokenSymlinks.push_back(path::filename(i->path())); + continue; + } + ASSERT_NO_ERROR(ec); - visited.push_back(path::filename(i->path())); + VisitedNonBrokenSymlinks.push_back(path::filename(i->path())); } - std::sort(visited.begin(), visited.end()); - expected = {"a", "b", "ba", "bb", "bc", "c", "d", "da", "dd", "ddd", "e"}; - ASSERT_TRUE(visited.size() == expected.size()); - ASSERT_TRUE(std::equal(visited.begin(), visited.end(), expected.begin())); + llvm::sort(VisitedNonBrokenSymlinks.begin(), VisitedNonBrokenSymlinks.end()); + llvm::sort(VisitedBrokenSymlinks.begin(), VisitedBrokenSymlinks.end()); + ExpectedNonBrokenSymlinks = {"a", "b", "ba", "bb", "bc", "c", "d", "da", "dd", + "ddd", "e"}; + ASSERT_EQ(ExpectedNonBrokenSymlinks.size(), VisitedNonBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedNonBrokenSymlinks.begin(), + VisitedNonBrokenSymlinks.end(), + ExpectedNonBrokenSymlinks.begin())); + VisitedNonBrokenSymlinks.clear(); + + ExpectedBrokenSymlinks = {}; + ASSERT_EQ(ExpectedBrokenSymlinks.size(), VisitedBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedBrokenSymlinks.begin(), + VisitedBrokenSymlinks.end(), + ExpectedBrokenSymlinks.begin())); + VisitedBrokenSymlinks.clear(); ASSERT_NO_ERROR(fs::remove_directories(Twine(TestDirectory) + "/symlink")); } @@ -956,7 +1001,7 @@ TEST_F(FileSystemTest, Remove) { ASSERT_FALSE(fs::exists(BaseDir)); } -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 TEST_F(FileSystemTest, CarriageReturn) { SmallString<128> FilePathname(TestDirectory); std::error_code EC; @@ -1074,7 +1119,7 @@ TEST(Support, NormalizePath) { EXPECT_EQ(std::get<2>(T), Posix); } -#if defined(LLVM_ON_WIN32) +#if defined(_WIN32) SmallString<64> PathHome; path::home_directory(PathHome); @@ -1195,8 +1240,8 @@ TEST_F(FileSystemTest, OpenFileForRead) { // Open the file for read int FileDescriptor2; SmallString<64> ResultPath; - ASSERT_NO_ERROR( - fs::openFileForRead(Twine(TempPath), FileDescriptor2, &ResultPath)) + ASSERT_NO_ERROR(fs::openFileForRead(Twine(TempPath), FileDescriptor2, + fs::OF_None, &ResultPath)) // If we succeeded, check that the paths are the same (modulo case): if (!ResultPath.empty()) { @@ -1207,8 +1252,241 @@ TEST_F(FileSystemTest, OpenFileForRead) { ASSERT_NO_ERROR(fs::getUniqueID(Twine(ResultPath), D2)); ASSERT_EQ(D1, D2); } + ::close(FileDescriptor); + ::close(FileDescriptor2); + +#ifdef _WIN32 + // Since Windows Vista, file access time is not updated by default. + // This is instead updated manually by openFileForRead. + // https://blogs.technet.microsoft.com/filecab/2006/11/07/disabling-last-access-time-in-windows-vista-to-improve-ntfs-performance/ + // This part of the unit test is Windows specific as the updating of + // access times can be disabled on Linux using /etc/fstab. + + // Set access time to UNIX epoch. + ASSERT_NO_ERROR(sys::fs::openFileForWrite(Twine(TempPath), FileDescriptor, + fs::CD_OpenExisting)); + TimePoint<> Epoch(std::chrono::milliseconds(0)); + ASSERT_NO_ERROR(fs::setLastModificationAndAccessTime(FileDescriptor, Epoch)); + ::close(FileDescriptor); + + // Open the file and ensure access time is updated, when forced. + ASSERT_NO_ERROR(fs::openFileForRead(Twine(TempPath), FileDescriptor, + fs::OF_UpdateAtime, &ResultPath)); + + sys::fs::file_status Status; + ASSERT_NO_ERROR(sys::fs::status(FileDescriptor, Status)); + auto FileAccessTime = Status.getLastAccessedTime(); + ASSERT_NE(Epoch, FileAccessTime); ::close(FileDescriptor); + + // Ideally this test would include a case when ATime is not forced to update, + // however the expected behaviour will differ depending on the configuration + // of the Windows file system. +#endif +} + +static void createFileWithData(const Twine &Path, bool ShouldExistBefore, + fs::CreationDisposition Disp, StringRef Data) { + int FD; + ASSERT_EQ(ShouldExistBefore, fs::exists(Path)); + ASSERT_NO_ERROR(fs::openFileForWrite(Path, FD, Disp)); + FileDescriptorCloser Closer(FD); + ASSERT_TRUE(fs::exists(Path)); + + ASSERT_EQ(Data.size(), (size_t)write(FD, Data.data(), Data.size())); +} + +static void verifyFileContents(const Twine &Path, StringRef Contents) { + auto Buffer = MemoryBuffer::getFile(Path); + ASSERT_TRUE((bool)Buffer); + StringRef Data = Buffer.get()->getBuffer(); + ASSERT_EQ(Data, Contents); +} + +TEST_F(FileSystemTest, CreateNew) { + int FD; + Optional<FileDescriptorCloser> Closer; + + // Succeeds if the file does not exist. + ASSERT_FALSE(fs::exists(NonExistantFile)); + ASSERT_NO_ERROR(fs::openFileForWrite(NonExistantFile, FD, fs::CD_CreateNew)); + ASSERT_TRUE(fs::exists(NonExistantFile)); + + FileRemover Cleanup(NonExistantFile); + Closer.emplace(FD); + + // And creates a file of size 0. + sys::fs::file_status Status; + ASSERT_NO_ERROR(sys::fs::status(FD, Status)); + EXPECT_EQ(0ULL, Status.getSize()); + + // Close this first, before trying to re-open the file. + Closer.reset(); + + // But fails if the file does exist. + ASSERT_ERROR(fs::openFileForWrite(NonExistantFile, FD, fs::CD_CreateNew)); +} + +TEST_F(FileSystemTest, CreateAlways) { + int FD; + Optional<FileDescriptorCloser> Closer; + + // Succeeds if the file does not exist. + ASSERT_FALSE(fs::exists(NonExistantFile)); + ASSERT_NO_ERROR( + fs::openFileForWrite(NonExistantFile, FD, fs::CD_CreateAlways)); + + Closer.emplace(FD); + + ASSERT_TRUE(fs::exists(NonExistantFile)); + + FileRemover Cleanup(NonExistantFile); + + // And creates a file of size 0. + uint64_t FileSize; + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(0ULL, FileSize); + + // If we write some data to it re-create it with CreateAlways, it succeeds and + // truncates to 0 bytes. + ASSERT_EQ(4, write(FD, "Test", 4)); + + Closer.reset(); + + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(4ULL, FileSize); + + ASSERT_NO_ERROR( + fs::openFileForWrite(NonExistantFile, FD, fs::CD_CreateAlways)); + Closer.emplace(FD); + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(0ULL, FileSize); +} + +TEST_F(FileSystemTest, OpenExisting) { + int FD; + + // Fails if the file does not exist. + ASSERT_FALSE(fs::exists(NonExistantFile)); + ASSERT_ERROR(fs::openFileForWrite(NonExistantFile, FD, fs::CD_OpenExisting)); + ASSERT_FALSE(fs::exists(NonExistantFile)); + + // Make a dummy file now so that we can try again when the file does exist. + createFileWithData(NonExistantFile, false, fs::CD_CreateNew, "Fizz"); + FileRemover Cleanup(NonExistantFile); + uint64_t FileSize; + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(4ULL, FileSize); + + // If we re-create it with different data, it overwrites rather than + // appending. + createFileWithData(NonExistantFile, true, fs::CD_OpenExisting, "Buzz"); + verifyFileContents(NonExistantFile, "Buzz"); +} + +TEST_F(FileSystemTest, OpenAlways) { + // Succeeds if the file does not exist. + createFileWithData(NonExistantFile, false, fs::CD_OpenAlways, "Fizz"); + FileRemover Cleanup(NonExistantFile); + uint64_t FileSize; + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(4ULL, FileSize); + + // Now re-open it and write again, verifying the contents get over-written. + createFileWithData(NonExistantFile, true, fs::CD_OpenAlways, "Bu"); + verifyFileContents(NonExistantFile, "Buzz"); +} + +TEST_F(FileSystemTest, AppendSetsCorrectFileOffset) { + fs::CreationDisposition Disps[] = {fs::CD_CreateAlways, fs::CD_OpenAlways, + fs::CD_OpenExisting}; + + // Write some data and re-open it with every possible disposition (this is a + // hack that shouldn't work, but is left for compatibility. F_Append + // overrides + // the specified disposition. + for (fs::CreationDisposition Disp : Disps) { + int FD; + Optional<FileDescriptorCloser> Closer; + + createFileWithData(NonExistantFile, false, fs::CD_CreateNew, "Fizz"); + + FileRemover Cleanup(NonExistantFile); + + uint64_t FileSize; + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(4ULL, FileSize); + ASSERT_NO_ERROR( + fs::openFileForWrite(NonExistantFile, FD, Disp, fs::OF_Append)); + Closer.emplace(FD); + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(4ULL, FileSize); + + ASSERT_EQ(4, write(FD, "Buzz", 4)); + Closer.reset(); + + verifyFileContents(NonExistantFile, "FizzBuzz"); + } +} + +static void verifyRead(int FD, StringRef Data, bool ShouldSucceed) { + std::vector<char> Buffer; + Buffer.resize(Data.size()); + int Result = ::read(FD, Buffer.data(), Buffer.size()); + if (ShouldSucceed) { + ASSERT_EQ((size_t)Result, Data.size()); + ASSERT_EQ(Data, StringRef(Buffer.data(), Buffer.size())); + } else { + ASSERT_EQ(-1, Result); + ASSERT_EQ(EBADF, errno); + } +} + +static void verifyWrite(int FD, StringRef Data, bool ShouldSucceed) { + int Result = ::write(FD, Data.data(), Data.size()); + if (ShouldSucceed) + ASSERT_EQ((size_t)Result, Data.size()); + else { + ASSERT_EQ(-1, Result); + ASSERT_EQ(EBADF, errno); + } +} + +TEST_F(FileSystemTest, ReadOnlyFileCantWrite) { + createFileWithData(NonExistantFile, false, fs::CD_CreateNew, "Fizz"); + FileRemover Cleanup(NonExistantFile); + + int FD; + ASSERT_NO_ERROR(fs::openFileForRead(NonExistantFile, FD)); + FileDescriptorCloser Closer(FD); + + verifyWrite(FD, "Buzz", false); + verifyRead(FD, "Fizz", true); +} + +TEST_F(FileSystemTest, WriteOnlyFileCantRead) { + createFileWithData(NonExistantFile, false, fs::CD_CreateNew, "Fizz"); + FileRemover Cleanup(NonExistantFile); + + int FD; + ASSERT_NO_ERROR( + fs::openFileForWrite(NonExistantFile, FD, fs::CD_OpenExisting)); + FileDescriptorCloser Closer(FD); + verifyRead(FD, "Fizz", false); + verifyWrite(FD, "Buzz", true); +} + +TEST_F(FileSystemTest, ReadWriteFileCanReadOrWrite) { + createFileWithData(NonExistantFile, false, fs::CD_CreateNew, "Fizz"); + FileRemover Cleanup(NonExistantFile); + + int FD; + ASSERT_NO_ERROR(fs::openFileForReadWrite(NonExistantFile, FD, + fs::CD_OpenExisting, fs::OF_None)); + FileDescriptorCloser Closer(FD); + verifyRead(FD, "Fizz", true); + verifyWrite(FD, "Buzz", true); } TEST_F(FileSystemTest, set_current_path) { @@ -1254,7 +1532,7 @@ TEST_F(FileSystemTest, permissions) { EXPECT_EQ(fs::setPermissions(TempPath, fs::all_read | fs::all_exe), NoError); EXPECT_TRUE(CheckPermissions(fs::all_read | fs::all_exe)); -#if defined(LLVM_ON_WIN32) +#if defined(_WIN32) fs::perms ReadOnly = fs::all_read | fs::all_exe; EXPECT_EQ(fs::setPermissions(TempPath, fs::no_perms), NoError); EXPECT_TRUE(CheckPermissions(ReadOnly)); |