summaryrefslogtreecommitdiff
path: root/unittests/Support/Path.cpp
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2018-07-28 10:51:19 +0000
committerDimitry Andric <dim@FreeBSD.org>2018-07-28 10:51:19 +0000
commiteb11fae6d08f479c0799db45860a98af528fa6e7 (patch)
tree44d492a50c8c1a7eb8e2d17ea3360ec4d066f042 /unittests/Support/Path.cpp
parentb8a2042aa938069e862750553db0e4d82d25822c (diff)
Notes
Diffstat (limited to 'unittests/Support/Path.cpp')
-rw-r--r--unittests/Support/Path.cpp578
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));