Mercurial > hg > CbC > CbC_llvm
diff unittests/Support/Path.cpp @ 148:63bd29f05246
merged
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Wed, 14 Aug 2019 19:46:37 +0900 |
parents | c2174574ed3a |
children |
line wrap: on
line diff
--- a/unittests/Support/Path.cpp Sun Dec 23 19:23:36 2018 +0900 +++ b/unittests/Support/Path.cpp Wed Aug 14 19:46:37 2019 +0900 @@ -1,9 +1,8 @@ //===- llvm/unittest/Support/Path.cpp - Path tests ------------------------===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// @@ -12,6 +11,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 +21,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 +49,22 @@ } 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 +74,7 @@ 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 +94,7 @@ 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 +125,30 @@ 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. @@ -165,121 +185,92 @@ path::native(*i, temp_store); } - SmallString<32> Relative("foo.cpp"); - ASSERT_NO_ERROR(sys::fs::make_absolute("/root", Relative)); - Relative[5] = '/'; // Fix up windows paths. - 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; - - StringRef(Path).split(ExpectedPathComponents, '/'); - - for (path::const_iterator I = path::begin(Path), E = path::end(Path); I != E; - ++I) { - ActualPathComponents.push_back(*I); + { + SmallString<32> Relative("foo.cpp"); + sys::fs::make_absolute("/root", Relative); + Relative[5] = '/'; // Fix up windows paths. + ASSERT_EQ("/root/foo.cpp", Relative); } - 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, RelativePathDotIterator) { - SmallString<64> Path(StringRef(".c/.d/../.")); - typedef SmallVector<StringRef, 4> PathComponents; - PathComponents ExpectedPathComponents; - PathComponents ActualPathComponents; - - StringRef(Path).split(ExpectedPathComponents, '/'); - - 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()); + { + SmallString<32> Relative("foo.cpp"); + sys::fs::make_absolute("//root", Relative); + Relative[6] = '/'; // Fix up windows paths. + ASSERT_EQ("//root/foo.cpp", Relative); } } -TEST(Support, AbsolutePathIterator) { - 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("/")); + + EXPECT_EQ("\\", path::filename("c:\\", path::Style::windows)); + EXPECT_EQ("c:", path::parent_path("c:\\", path::Style::windows)); + + 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)); + + EXPECT_EQ("bar", path::filename("/foo/bar")); + EXPECT_EQ("/foo", path::parent_path("/foo/bar")); - // The root path will also be a component when iterating - ExpectedPathComponents[0] = "/"; + EXPECT_EQ("foo", path::filename("/foo")); + EXPECT_EQ("/", path::parent_path("/foo")); + + EXPECT_EQ("foo", path::filename("foo")); + EXPECT_EQ("", path::parent_path("foo")); - for (path::const_iterator I = path::begin(Path), E = path::end(Path); I != E; - ++I) { - ActualPathComponents.push_back(*I); - } + EXPECT_EQ(".", path::filename("foo/")); + EXPECT_EQ("foo", path::parent_path("foo/")); + + EXPECT_EQ("//net", path::filename("//net")); + EXPECT_EQ("", path::parent_path("//net")); + + EXPECT_EQ("/", path::filename("//net/")); + EXPECT_EQ("//net", path::parent_path("//net/")); + + EXPECT_EQ("foo", path::filename("//net/foo")); + EXPECT_EQ("//net/", path::parent_path("//net/foo")); - ASSERT_EQ(ExpectedPathComponents.size(), ActualPathComponents.size()); + // 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("//")); - 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)); + + 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 +278,11 @@ 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 +291,11 @@ 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 +304,7 @@ 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), @@ -360,35 +355,6 @@ } #endif -TEST(Support, UserCacheDirectory) { - SmallString<13> CacheDir; - SmallString<20> CacheDir2; - auto Status = path::user_cache_directory(CacheDir, ""); - EXPECT_TRUE(Status ^ CacheDir.empty()); - - if (Status) { - EXPECT_TRUE(path::user_cache_directory(CacheDir2, "")); // should succeed - EXPECT_EQ(CacheDir, CacheDir2); // and return same paths - - EXPECT_TRUE(path::user_cache_directory(CacheDir, "A", "B", "file.c")); - auto It = path::rbegin(CacheDir); - EXPECT_EQ("file.c", *It); - EXPECT_EQ("B", *++It); - EXPECT_EQ("A", *++It); - auto ParentDir = *++It; - - // Test Unicode: "<user_cache_dir>/(pi)r^2/aleth.0" - EXPECT_TRUE(path::user_cache_directory(CacheDir2, "\xCF\x80r\xC2\xB2", - "\xE2\x84\xB5.0")); - auto It2 = path::rbegin(CacheDir2); - EXPECT_EQ("\xE2\x84\xB5.0", *It2); - EXPECT_EQ("\xCF\x80r\xC2\xB2", *++It2); - auto ParentDir2 = *++It2; - - EXPECT_EQ(ParentDir, ParentDir2); - } -} - TEST(Support, TempDirectory) { SmallString<32> TempDir; path::system_temp_directory(false, TempDir); @@ -398,7 +364,7 @@ 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 +430,7 @@ /// 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 +438,11 @@ // 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())); } @@ -552,6 +524,8 @@ EXPECT_EQ(Expected, Actual); SmallString<64> HomeDir; + + // This can fail if $HOME is not set and getpwuid fails. bool Result = llvm::sys::path::home_directory(HomeDir); if (Result) { ASSERT_NO_ERROR(fs::real_path(HomeDir, Expected)); @@ -564,6 +538,31 @@ ASSERT_NO_ERROR(fs::remove_directories(Twine(TestDirectory) + "/test1")); } +TEST_F(FileSystemTest, ExpandTilde) { + SmallString<64> Expected; + SmallString<64> Actual; + SmallString<64> HomeDir; + + // This can fail if $HOME is not set and getpwuid fails. + bool Result = llvm::sys::path::home_directory(HomeDir); + if (Result) { + fs::expand_tilde(HomeDir, Expected); + + fs::expand_tilde("~", Actual); + EXPECT_EQ(Expected, Actual); + +#ifdef _WIN32 + Expected += "\\foo"; + fs::expand_tilde("~\\foo", Actual); +#else + Expected += "/foo"; + fs::expand_tilde("~/foo", Actual); +#endif + + EXPECT_EQ(Expected, Actual); + } +} + #ifdef LLVM_ON_UNIX TEST_F(FileSystemTest, RealPathNoReadPerm) { SmallString<64> Expanded; @@ -588,6 +587,7 @@ auto TempFileOrError = fs::TempFile::create(TestDirectory + "/test-%%%%"); ASSERT_TRUE((bool)TempFileOrError); fs::TempFile File = std::move(*TempFileOrError); + ASSERT_EQ(-1, TempFileOrError->FD); ASSERT_FALSE((bool)File.keep(TestDirectory + "/keep")); ASSERT_FALSE((bool)File.discard()); ASSERT_TRUE(fs::exists(TestDirectory + "/keep")); @@ -599,6 +599,7 @@ auto TempFileOrError = fs::TempFile::create(TestDirectory + "/test-%%%%"); ASSERT_TRUE((bool)TempFileOrError); fs::TempFile File = std::move(*TempFileOrError); + ASSERT_EQ(-1, TempFileOrError->FD); ASSERT_FALSE((bool)File.discard()); ASSERT_FALSE((bool)File.discard()); ASSERT_FALSE(fs::exists(TestDirectory + "/keep")); @@ -667,7 +668,7 @@ 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" @@ -688,6 +689,45 @@ #endif } +TEST_F(FileSystemTest, TempFileCollisions) { + SmallString<128> TestDirectory; + ASSERT_NO_ERROR( + fs::createUniqueDirectory("CreateUniqueFileTest", TestDirectory)); + FileRemover Cleanup(TestDirectory); + SmallString<128> Model = TestDirectory; + path::append(Model, "%.tmp"); + SmallString<128> Path; + std::vector<fs::TempFile> TempFiles; + + auto TryCreateTempFile = [&]() { + Expected<fs::TempFile> T = fs::TempFile::create(Model); + if (T) { + TempFiles.push_back(std::move(*T)); + return true; + } else { + logAllUnhandledErrors(T.takeError(), errs(), + "Failed to create temporary file: "); + return false; + } + }; + + // Our single-character template allows for 16 unique names. Check that + // calling TryCreateTempFile repeatedly results in 16 successes. + // Because the test depends on random numbers, it could theoretically fail. + // However, the probability of this happening is tiny: with 32 calls, each + // of which will retry up to 128 times, to not get a given digit we would + // have to fail at least 15 + 17 * 128 = 2191 attempts. The probability of + // 2191 attempts not producing a given hexadecimal digit is + // (1 - 1/16) ** 2191 or 3.88e-62. + int Successes = 0; + for (int i = 0; i < 32; ++i) + if (TryCreateTempFile()) ++Successes; + EXPECT_EQ(Successes, 16); + + for (fs::TempFile &T : TempFiles) + cantFail(T.discard()); +} + TEST_F(FileSystemTest, CreateDir) { ASSERT_NO_ERROR(fs::create_directory(Twine(TestDirectory) + "foo")); ASSERT_NO_ERROR(fs::create_directory(Twine(TestDirectory) + "foo")); @@ -715,7 +755,7 @@ ::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.) @@ -885,58 +925,66 @@ fs::create_link("no_such_file", Twine(TestDirectory) + "/symlink/e")); typedef std::vector<std::string> v_t; - v_t visited; + v_t VisitedNonBrokenSymlinks; + v_t VisitedBrokenSymlinks; + std::error_code ec; + using testing::UnorderedElementsAre; + using testing::UnorderedElementsAreArray; - // The directory iterator doesn't stat the file, so we should be able to - // iterate over the whole directory. - 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)) { ASSERT_NO_ERROR(ec); - visited.push_back(path::filename(i->path())); + if (i->status().getError() == + std::make_error_code(std::errc::no_such_file_or_directory)) { + VisitedBrokenSymlinks.push_back(path::filename(i->path())); + continue; + } + 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(); + EXPECT_THAT(VisitedNonBrokenSymlinks, UnorderedElementsAre("b", "d")); + VisitedNonBrokenSymlinks.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)) { + EXPECT_THAT(VisitedBrokenSymlinks, UnorderedElementsAre("a", "c", "e")); + 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)) { ASSERT_NO_ERROR(ec); - - ErrorOr<fs::basic_file_status> status = i->status(); - if (status.getError() == + if (i->status().getError() == std::make_error_code(std::errc::no_such_file_or_directory)) { - i.no_push(); + VisitedBrokenSymlinks.push_back(path::filename(i->path())); continue; } - - visited.push_back(path::filename(i->path())); + 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(); + EXPECT_THAT(VisitedNonBrokenSymlinks, + UnorderedElementsAre("b", "bb", "d", "da", "dd", "ddd", "ddd")); + VisitedNonBrokenSymlinks.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; + EXPECT_THAT(VisitedBrokenSymlinks, + UnorderedElementsAre("a", "ba", "bc", "c", "e")); + VisitedBrokenSymlinks.clear(); + + for (fs::recursive_directory_iterator i( + Twine(TestDirectory) + "/symlink", ec, /*follow_symlinks=*/false), e; i != e; i.increment(ec)) { ASSERT_NO_ERROR(ec); - visited.push_back(path::filename(i->path())); + if (i->status().getError() == + std::make_error_code(std::errc::no_such_file_or_directory)) { + VisitedBrokenSymlinks.push_back(path::filename(i->path())); + continue; + } + 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())); + EXPECT_THAT(VisitedNonBrokenSymlinks, + UnorderedElementsAreArray({"a", "b", "ba", "bb", "bc", "c", "d", + "da", "dd", "ddd", "e"})); + VisitedNonBrokenSymlinks.clear(); + + EXPECT_THAT(VisitedBrokenSymlinks, UnorderedElementsAre()); + VisitedBrokenSymlinks.clear(); ASSERT_NO_ERROR(fs::remove_directories(Twine(TestDirectory) + "/symlink")); } @@ -975,14 +1023,14 @@ ASSERT_FALSE(fs::exists(BaseDir)); } -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 TEST_F(FileSystemTest, CarriageReturn) { SmallString<128> FilePathname(TestDirectory); std::error_code EC; path::append(FilePathname, "test"); { - raw_fd_ostream File(FilePathname, EC, sys::fs::F_Text); + raw_fd_ostream File(FilePathname, EC, sys::fs::OF_Text); ASSERT_NO_ERROR(EC); File << '\n'; } @@ -993,7 +1041,7 @@ } { - raw_fd_ostream File(FilePathname, EC, sys::fs::F_None); + raw_fd_ostream File(FilePathname, EC, sys::fs::OF_None); ASSERT_NO_ERROR(EC); File << '\n'; } @@ -1045,7 +1093,7 @@ std::error_code EC; StringRef Val("hello there"); { - fs::mapped_file_region mfr(FileDescriptor, + fs::mapped_file_region mfr(fs::convertFDToNativeFile(FileDescriptor), fs::mapped_file_region::readwrite, Size, 0, EC); ASSERT_NO_ERROR(EC); std::copy(Val.begin(), Val.end(), mfr.data()); @@ -1060,14 +1108,16 @@ int FD; EC = fs::openFileForRead(Twine(TempPath), FD); ASSERT_NO_ERROR(EC); - fs::mapped_file_region mfr(FD, fs::mapped_file_region::readonly, Size, 0, EC); + fs::mapped_file_region mfr(fs::convertFDToNativeFile(FD), + fs::mapped_file_region::readonly, Size, 0, EC); ASSERT_NO_ERROR(EC); // Verify content EXPECT_EQ(StringRef(mfr.const_data()), Val); // Unmap temp file - fs::mapped_file_region m(FD, fs::mapped_file_region::readonly, Size, 0, EC); + fs::mapped_file_region m(fs::convertFDToNativeFile(FD), + fs::mapped_file_region::readonly, Size, 0, EC); ASSERT_NO_ERROR(EC); ASSERT_EQ(close(FD), 0); } @@ -1093,7 +1143,7 @@ EXPECT_EQ(std::get<2>(T), Posix); } -#if defined(LLVM_ON_WIN32) +#if defined(_WIN32) SmallString<64> PathHome; path::home_directory(PathHome); @@ -1214,8 +1264,8 @@ // 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()) { @@ -1226,8 +1276,320 @@ 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::setLastAccessAndModificationTime(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. OF_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, is_local) { + bool TestDirectoryIsLocal; + ASSERT_NO_ERROR(fs::is_local(TestDirectory, TestDirectoryIsLocal)); + EXPECT_EQ(TestDirectoryIsLocal, fs::is_local(TestDirectory)); + + int FD; + SmallString<128> TempPath; + ASSERT_NO_ERROR( + fs::createUniqueFile(Twine(TestDirectory) + "/temp", FD, TempPath)); + FileRemover Cleanup(TempPath); + + // Make sure it exists. + ASSERT_TRUE(sys::fs::exists(Twine(TempPath))); + + bool TempFileIsLocal; + ASSERT_NO_ERROR(fs::is_local(FD, TempFileIsLocal)); + EXPECT_EQ(TempFileIsLocal, fs::is_local(FD)); + ::close(FD); + + // Expect that the file and its parent directory are equally local or equally + // remote. + EXPECT_EQ(TestDirectoryIsLocal, TempFileIsLocal); +} + +TEST_F(FileSystemTest, getUmask) { +#ifdef _WIN32 + EXPECT_EQ(fs::getUmask(), 0U) << "Should always be 0 on Windows."; +#else + unsigned OldMask = ::umask(0022); + unsigned CurrentMask = fs::getUmask(); + EXPECT_EQ(CurrentMask, 0022U) + << "getUmask() didn't return previously set umask()"; + EXPECT_EQ(::umask(OldMask), 0022U) << "getUmask() may have changed umask()"; +#endif +} + +TEST_F(FileSystemTest, RespectUmask) { +#ifndef _WIN32 + unsigned OldMask = ::umask(0022); + + int FD; + SmallString<128> TempPath; + ASSERT_NO_ERROR(fs::createTemporaryFile("prefix", "temp", FD, TempPath)); + + fs::perms AllRWE = static_cast<fs::perms>(0777); + + ASSERT_NO_ERROR(fs::setPermissions(TempPath, AllRWE)); + + ErrorOr<fs::perms> Perms = fs::getPermissions(TempPath); + ASSERT_TRUE(!!Perms); + EXPECT_EQ(Perms.get(), AllRWE) << "Should have ignored umask by default"; + + ASSERT_NO_ERROR(fs::setPermissions(TempPath, AllRWE)); + + Perms = fs::getPermissions(TempPath); + ASSERT_TRUE(!!Perms); + EXPECT_EQ(Perms.get(), AllRWE) << "Should have ignored umask"; + + ASSERT_NO_ERROR( + fs::setPermissions(FD, static_cast<fs::perms>(AllRWE & ~fs::getUmask()))); + Perms = fs::getPermissions(TempPath); + ASSERT_TRUE(!!Perms); + EXPECT_EQ(Perms.get(), static_cast<fs::perms>(0755)) + << "Did not respect umask"; + + (void)::umask(0057); + + ASSERT_NO_ERROR( + fs::setPermissions(FD, static_cast<fs::perms>(AllRWE & ~fs::getUmask()))); + Perms = fs::getPermissions(TempPath); + ASSERT_TRUE(!!Perms); + EXPECT_EQ(Perms.get(), static_cast<fs::perms>(0720)) + << "Did not respect umask"; + + (void)::umask(OldMask); + (void)::close(FD); +#endif } TEST_F(FileSystemTest, set_current_path) { @@ -1273,7 +1635,7 @@ 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)); @@ -1402,7 +1764,10 @@ EXPECT_TRUE(CheckPermissions(fs::set_gid_on_exe)); // Modern BSDs require root to set the sticky bit on files. -#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) + // AIX and Solaris without root will mask off (i.e., lose) the sticky bit + // on files. +#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && \ + !defined(_AIX) && !(defined(__sun__) && defined(__svr4__)) EXPECT_EQ(fs::setPermissions(TempPath, fs::sticky_bit), NoError); EXPECT_TRUE(CheckPermissions(fs::sticky_bit)); @@ -1422,7 +1787,7 @@ EXPECT_EQ(fs::setPermissions(TempPath, fs::all_perms), NoError); EXPECT_TRUE(CheckPermissions(fs::all_perms)); -#endif // !FreeBSD && !NetBSD && !OpenBSD +#endif // !FreeBSD && !NetBSD && !OpenBSD && !AIX EXPECT_EQ(fs::setPermissions(TempPath, fs::all_perms & ~fs::sticky_bit), NoError);