Skip to content

Commit f372c76

Browse files
Abseil Teamcopybara-github
Abseil Team
authored andcommitted
Adds Win32 UNC path support to FilePath::IsAbsolutePath() and FilePath::IsRootDirectory() in GoogleTest
Fixes: #3025 PiperOrigin-RevId: 481932601 Change-Id: I90fcb5b3d189aea79a0fd18735bad038b3511270
1 parent 26d3ab5 commit f372c76

File tree

3 files changed

+85
-18
lines changed

3 files changed

+85
-18
lines changed

googletest/include/gtest/internal/gtest-filepath.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,16 @@ class GTEST_API_ FilePath {
199199
// separators. Returns NULL if no path separator was found.
200200
const char* FindLastPathSeparator() const;
201201

202+
// Returns the length of the path root, including the directory separator at
203+
// the end of the prefix. Returns zero by definition if the path is relative.
204+
// Examples:
205+
// - [Windows] "..\Sibling" => 0
206+
// - [Windows] "\Windows" => 1
207+
// - [Windows] "C:/Windows\Notepad.exe" => 3
208+
// - [Windows] "\\Host\Share\C$/Windows" => 13
209+
// - [UNIX] "/bin" => 1
210+
size_t CalculateRootLength() const;
211+
202212
std::string pathname_;
203213
}; // class FilePath
204214

googletest/src/gtest-filepath.cc

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,45 @@ const char* FilePath::FindLastPathSeparator() const {
145145
return last_sep;
146146
}
147147

148+
size_t FilePath::CalculateRootLength() const {
149+
const auto &path = pathname_;
150+
auto s = path.begin();
151+
auto end = path.end();
152+
#if GTEST_OS_WINDOWS
153+
if (end - s >= 2 && s[1] == ':' &&
154+
(end - s == 2 || IsPathSeparator(s[2])) &&
155+
(('A' <= s[0] && s[0] <= 'Z') || ('a' <= s[0] && s[0] <= 'z'))) {
156+
// A typical absolute path like "C:\Windows" or "D:"
157+
s += 2;
158+
if (s != end) {
159+
++s;
160+
}
161+
} else if (end - s >= 3 && IsPathSeparator(*s) && IsPathSeparator(*(s + 1))
162+
&& !IsPathSeparator(*(s + 2))) {
163+
// Move past the "\\" prefix in a UNC path like "\\Server\Share\Folder"
164+
s += 2;
165+
// Skip 2 components and their following separators ("Server\" and "Share\")
166+
for (int i = 0; i < 2; ++i) {
167+
while (s != end) {
168+
bool stop = IsPathSeparator(*s);
169+
++s;
170+
if (stop) {
171+
break;
172+
}
173+
}
174+
}
175+
} else if (s != end && IsPathSeparator(*s)) {
176+
// A drive-rooted path like "\Windows"
177+
++s;
178+
}
179+
#else
180+
if (s != end && IsPathSeparator(*s)) {
181+
++s;
182+
}
183+
#endif
184+
return static_cast<size_t>(s - path.begin());
185+
}
186+
148187
// Returns a copy of the FilePath with the directory part removed.
149188
// Example: FilePath("path/to/file").RemoveDirectoryName() returns
150189
// FilePath("file"). If there is no directory part ("just_a_file"), it returns
@@ -246,26 +285,16 @@ bool FilePath::DirectoryExists() const {
246285
}
247286

248287
// Returns true if pathname describes a root directory. (Windows has one
249-
// root directory per disk drive.)
288+
// root directory per disk drive. UNC share roots are also included.)
250289
bool FilePath::IsRootDirectory() const {
251-
#if GTEST_OS_WINDOWS
252-
return pathname_.length() == 3 && IsAbsolutePath();
253-
#else
254-
return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]);
255-
#endif
290+
size_t root_length = CalculateRootLength();
291+
return root_length > 0 && root_length == pathname_.size() &&
292+
IsPathSeparator(pathname_[root_length - 1]);
256293
}
257294

258295
// Returns true if pathname describes an absolute path.
259296
bool FilePath::IsAbsolutePath() const {
260-
const char* const name = pathname_.c_str();
261-
#if GTEST_OS_WINDOWS
262-
return pathname_.length() >= 3 &&
263-
((name[0] >= 'a' && name[0] <= 'z') ||
264-
(name[0] >= 'A' && name[0] <= 'Z')) &&
265-
name[1] == ':' && IsPathSeparator(name[2]);
266-
#else
267-
return IsPathSeparator(name[0]);
268-
#endif
297+
return CalculateRootLength() > 0;
269298
}
270299

271300
// Returns a pathname for a file that does not currently exist. The pathname
@@ -347,17 +376,27 @@ FilePath FilePath::RemoveTrailingPathSeparator() const {
347376
// Removes any redundant separators that might be in the pathname.
348377
// For example, "bar///foo" becomes "bar/foo". Does not eliminate other
349378
// redundancies that might be in a pathname involving "." or "..".
379+
// Note that "\\Host\Share" does not contain a redundancy on Windows!
350380
void FilePath::Normalize() {
351381
auto out = pathname_.begin();
352382

353-
for (const char character : pathname_) {
383+
auto i = pathname_.cbegin();
384+
#if GTEST_OS_WINDOWS
385+
// UNC paths are treated specially
386+
if (pathname_.end() - i >= 3 && IsPathSeparator(*i) &&
387+
IsPathSeparator(*(i + 1)) && !IsPathSeparator(*(i + 2))) {
388+
*(out++) = kPathSeparator;
389+
*(out++) = kPathSeparator;
390+
}
391+
#endif
392+
while (i != pathname_.end()) {
393+
const char character = *i;
354394
if (!IsPathSeparator(character)) {
355395
*(out++) = character;
356396
} else if (out == pathname_.begin() || *std::prev(out) != kPathSeparator) {
357397
*(out++) = kPathSeparator;
358-
} else {
359-
continue;
360398
}
399+
++i;
361400
}
362401

363402
pathname_.erase(out, pathname_.end());

googletest/test/googletest-filepath-test.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,13 @@ TEST(NormalizeTest, MultipleConsecutiveSeparatorsInMidstring) {
421421
// "/bar" == //bar" == "///bar"
422422
TEST(NormalizeTest, MultipleConsecutiveSeparatorsAtStringStart) {
423423
EXPECT_EQ(GTEST_PATH_SEP_ "bar", FilePath(GTEST_PATH_SEP_ "bar").string());
424+
#if GTEST_OS_WINDOWS
425+
EXPECT_EQ(GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar",
426+
FilePath(GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar").string());
427+
#else
424428
EXPECT_EQ(GTEST_PATH_SEP_ "bar",
425429
FilePath(GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar").string());
430+
#endif
426431
EXPECT_EQ(
427432
GTEST_PATH_SEP_ "bar",
428433
FilePath(GTEST_PATH_SEP_ GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar").string());
@@ -621,6 +626,9 @@ TEST(FilePathTest, IsAbsolutePath) {
621626
EXPECT_TRUE(
622627
FilePath("c:/" GTEST_PATH_SEP_ "is_not" GTEST_PATH_SEP_ "relative")
623628
.IsAbsolutePath());
629+
EXPECT_TRUE(FilePath("d:/Windows").IsAbsolutePath());
630+
EXPECT_TRUE(FilePath("\\\\Host\\Share").IsAbsolutePath());
631+
EXPECT_TRUE(FilePath("\\\\Host\\Share\\Folder").IsAbsolutePath());
624632
#else
625633
EXPECT_TRUE(FilePath(GTEST_PATH_SEP_ "is_not" GTEST_PATH_SEP_ "relative")
626634
.IsAbsolutePath());
@@ -637,6 +645,16 @@ TEST(FilePathTest, IsRootDirectory) {
637645
EXPECT_FALSE(FilePath("b:a").IsRootDirectory());
638646
EXPECT_FALSE(FilePath("8:/").IsRootDirectory());
639647
EXPECT_FALSE(FilePath("c|/").IsRootDirectory());
648+
EXPECT_TRUE(FilePath("c:/").IsRootDirectory());
649+
EXPECT_FALSE(FilePath("d:/Windows").IsRootDirectory());
650+
651+
// This is for backward compatibility, since callers (even in this library)
652+
// have assumed IsRootDirectory() implies a trailing directory separator.
653+
EXPECT_FALSE(FilePath("\\\\Host\\Share").IsRootDirectory());
654+
655+
EXPECT_TRUE(FilePath("\\\\Host\\Share\\").IsRootDirectory());
656+
EXPECT_FALSE(FilePath("\\\\Host\\Share\\.").IsRootDirectory());
657+
EXPECT_FALSE(FilePath("\\\\Host\\Share\\C$\\").IsRootDirectory());
640658
#else
641659
EXPECT_TRUE(FilePath("/").IsRootDirectory());
642660
EXPECT_TRUE(FilePath("//").IsRootDirectory());

0 commit comments

Comments
 (0)