Adds Win32 UNC path support to FilePath::IsAbsolutePath() and FilePath::IsRootDirectory() in GoogleTest

Fixes: #3025
PiperOrigin-RevId: 481932601
Change-Id: I90fcb5b3d189aea79a0fd18735bad038b3511270
This commit is contained in:
Abseil Team 2022-10-18 08:51:57 -07:00 committed by Copybara-Service
parent 26d3ab5442
commit f372c76026
3 changed files with 85 additions and 18 deletions

View File

@ -199,6 +199,16 @@ class GTEST_API_ FilePath {
// separators. Returns NULL if no path separator was found.
const char* FindLastPathSeparator() const;
// Returns the length of the path root, including the directory separator at
// the end of the prefix. Returns zero by definition if the path is relative.
// Examples:
// - [Windows] "..\Sibling" => 0
// - [Windows] "\Windows" => 1
// - [Windows] "C:/Windows\Notepad.exe" => 3
// - [Windows] "\\Host\Share\C$/Windows" => 13
// - [UNIX] "/bin" => 1
size_t CalculateRootLength() const;
std::string pathname_;
}; // class FilePath

View File

@ -145,6 +145,45 @@ const char* FilePath::FindLastPathSeparator() const {
return last_sep;
}
size_t FilePath::CalculateRootLength() const {
const auto &path = pathname_;
auto s = path.begin();
auto end = path.end();
#if GTEST_OS_WINDOWS
if (end - s >= 2 && s[1] == ':' &&
(end - s == 2 || IsPathSeparator(s[2])) &&
(('A' <= s[0] && s[0] <= 'Z') || ('a' <= s[0] && s[0] <= 'z'))) {
// A typical absolute path like "C:\Windows" or "D:"
s += 2;
if (s != end) {
++s;
}
} else if (end - s >= 3 && IsPathSeparator(*s) && IsPathSeparator(*(s + 1))
&& !IsPathSeparator(*(s + 2))) {
// Move past the "\\" prefix in a UNC path like "\\Server\Share\Folder"
s += 2;
// Skip 2 components and their following separators ("Server\" and "Share\")
for (int i = 0; i < 2; ++i) {
while (s != end) {
bool stop = IsPathSeparator(*s);
++s;
if (stop) {
break;
}
}
}
} else if (s != end && IsPathSeparator(*s)) {
// A drive-rooted path like "\Windows"
++s;
}
#else
if (s != end && IsPathSeparator(*s)) {
++s;
}
#endif
return static_cast<size_t>(s - path.begin());
}
// Returns a copy of the FilePath with the directory part removed.
// Example: FilePath("path/to/file").RemoveDirectoryName() returns
// FilePath("file"). If there is no directory part ("just_a_file"), it returns
@ -246,26 +285,16 @@ bool FilePath::DirectoryExists() const {
}
// Returns true if pathname describes a root directory. (Windows has one
// root directory per disk drive.)
// root directory per disk drive. UNC share roots are also included.)
bool FilePath::IsRootDirectory() const {
#if GTEST_OS_WINDOWS
return pathname_.length() == 3 && IsAbsolutePath();
#else
return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]);
#endif
size_t root_length = CalculateRootLength();
return root_length > 0 && root_length == pathname_.size() &&
IsPathSeparator(pathname_[root_length - 1]);
}
// Returns true if pathname describes an absolute path.
bool FilePath::IsAbsolutePath() const {
const char* const name = pathname_.c_str();
#if GTEST_OS_WINDOWS
return pathname_.length() >= 3 &&
((name[0] >= 'a' && name[0] <= 'z') ||
(name[0] >= 'A' && name[0] <= 'Z')) &&
name[1] == ':' && IsPathSeparator(name[2]);
#else
return IsPathSeparator(name[0]);
#endif
return CalculateRootLength() > 0;
}
// Returns a pathname for a file that does not currently exist. The pathname
@ -347,17 +376,27 @@ FilePath FilePath::RemoveTrailingPathSeparator() const {
// Removes any redundant separators that might be in the pathname.
// For example, "bar///foo" becomes "bar/foo". Does not eliminate other
// redundancies that might be in a pathname involving "." or "..".
// Note that "\\Host\Share" does not contain a redundancy on Windows!
void FilePath::Normalize() {
auto out = pathname_.begin();
for (const char character : pathname_) {
auto i = pathname_.cbegin();
#if GTEST_OS_WINDOWS
// UNC paths are treated specially
if (pathname_.end() - i >= 3 && IsPathSeparator(*i) &&
IsPathSeparator(*(i + 1)) && !IsPathSeparator(*(i + 2))) {
*(out++) = kPathSeparator;
*(out++) = kPathSeparator;
}
#endif
while (i != pathname_.end()) {
const char character = *i;
if (!IsPathSeparator(character)) {
*(out++) = character;
} else if (out == pathname_.begin() || *std::prev(out) != kPathSeparator) {
*(out++) = kPathSeparator;
} else {
continue;
}
++i;
}
pathname_.erase(out, pathname_.end());

View File

@ -421,8 +421,13 @@ TEST(NormalizeTest, MultipleConsecutiveSeparatorsInMidstring) {
// "/bar" == //bar" == "///bar"
TEST(NormalizeTest, MultipleConsecutiveSeparatorsAtStringStart) {
EXPECT_EQ(GTEST_PATH_SEP_ "bar", FilePath(GTEST_PATH_SEP_ "bar").string());
#if GTEST_OS_WINDOWS
EXPECT_EQ(GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar",
FilePath(GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar").string());
#else
EXPECT_EQ(GTEST_PATH_SEP_ "bar",
FilePath(GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar").string());
#endif
EXPECT_EQ(
GTEST_PATH_SEP_ "bar",
FilePath(GTEST_PATH_SEP_ GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar").string());
@ -621,6 +626,9 @@ TEST(FilePathTest, IsAbsolutePath) {
EXPECT_TRUE(
FilePath("c:/" GTEST_PATH_SEP_ "is_not" GTEST_PATH_SEP_ "relative")
.IsAbsolutePath());
EXPECT_TRUE(FilePath("d:/Windows").IsAbsolutePath());
EXPECT_TRUE(FilePath("\\\\Host\\Share").IsAbsolutePath());
EXPECT_TRUE(FilePath("\\\\Host\\Share\\Folder").IsAbsolutePath());
#else
EXPECT_TRUE(FilePath(GTEST_PATH_SEP_ "is_not" GTEST_PATH_SEP_ "relative")
.IsAbsolutePath());
@ -637,6 +645,16 @@ TEST(FilePathTest, IsRootDirectory) {
EXPECT_FALSE(FilePath("b:a").IsRootDirectory());
EXPECT_FALSE(FilePath("8:/").IsRootDirectory());
EXPECT_FALSE(FilePath("c|/").IsRootDirectory());
EXPECT_TRUE(FilePath("c:/").IsRootDirectory());
EXPECT_FALSE(FilePath("d:/Windows").IsRootDirectory());
// This is for backward compatibility, since callers (even in this library)
// have assumed IsRootDirectory() implies a trailing directory separator.
EXPECT_FALSE(FilePath("\\\\Host\\Share").IsRootDirectory());
EXPECT_TRUE(FilePath("\\\\Host\\Share\\").IsRootDirectory());
EXPECT_FALSE(FilePath("\\\\Host\\Share\\.").IsRootDirectory());
EXPECT_FALSE(FilePath("\\\\Host\\Share\\C$\\").IsRootDirectory());
#else
EXPECT_TRUE(FilePath("/").IsRootDirectory());
EXPECT_TRUE(FilePath("//").IsRootDirectory());