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:
		
				
					committed by
					
						
						Copybara-Service
					
				
			
			
				
	
			
			
			
						parent
						
							26d3ab5442
						
					
				
				
					commit
					f372c76026
				
			@@ -199,6 +199,16 @@ class GTEST_API_ FilePath {
 | 
				
			|||||||
  // separators. Returns NULL if no path separator was found.
 | 
					  // separators. Returns NULL if no path separator was found.
 | 
				
			||||||
  const char* FindLastPathSeparator() const;
 | 
					  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_;
 | 
					  std::string pathname_;
 | 
				
			||||||
};  // class FilePath
 | 
					};  // class FilePath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -145,6 +145,45 @@ const char* FilePath::FindLastPathSeparator() const {
 | 
				
			|||||||
  return last_sep;
 | 
					  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.
 | 
					// Returns a copy of the FilePath with the directory part removed.
 | 
				
			||||||
// Example: FilePath("path/to/file").RemoveDirectoryName() returns
 | 
					// Example: FilePath("path/to/file").RemoveDirectoryName() returns
 | 
				
			||||||
// FilePath("file"). If there is no directory part ("just_a_file"), it 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
 | 
					// 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 {
 | 
					bool FilePath::IsRootDirectory() const {
 | 
				
			||||||
#if GTEST_OS_WINDOWS
 | 
					  size_t root_length = CalculateRootLength();
 | 
				
			||||||
  return pathname_.length() == 3 && IsAbsolutePath();
 | 
					  return root_length > 0 && root_length == pathname_.size() &&
 | 
				
			||||||
#else
 | 
					         IsPathSeparator(pathname_[root_length - 1]);
 | 
				
			||||||
  return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Returns true if pathname describes an absolute path.
 | 
					// Returns true if pathname describes an absolute path.
 | 
				
			||||||
bool FilePath::IsAbsolutePath() const {
 | 
					bool FilePath::IsAbsolutePath() const {
 | 
				
			||||||
  const char* const name = pathname_.c_str();
 | 
					  return CalculateRootLength() > 0;
 | 
				
			||||||
#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
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Returns a pathname for a file that does not currently exist. The pathname
 | 
					// 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.
 | 
					// Removes any redundant separators that might be in the pathname.
 | 
				
			||||||
// For example, "bar///foo" becomes "bar/foo". Does not eliminate other
 | 
					// For example, "bar///foo" becomes "bar/foo". Does not eliminate other
 | 
				
			||||||
// redundancies that might be in a pathname involving "." or "..".
 | 
					// redundancies that might be in a pathname involving "." or "..".
 | 
				
			||||||
 | 
					// Note that "\\Host\Share" does not contain a redundancy on Windows!
 | 
				
			||||||
void FilePath::Normalize() {
 | 
					void FilePath::Normalize() {
 | 
				
			||||||
  auto out = pathname_.begin();
 | 
					  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)) {
 | 
					    if (!IsPathSeparator(character)) {
 | 
				
			||||||
      *(out++) = character;
 | 
					      *(out++) = character;
 | 
				
			||||||
    } else if (out == pathname_.begin() || *std::prev(out) != kPathSeparator) {
 | 
					    } else if (out == pathname_.begin() || *std::prev(out) != kPathSeparator) {
 | 
				
			||||||
      *(out++) = kPathSeparator;
 | 
					      *(out++) = kPathSeparator;
 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      continue;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    ++i;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pathname_.erase(out, pathname_.end());
 | 
					  pathname_.erase(out, pathname_.end());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -421,8 +421,13 @@ TEST(NormalizeTest, MultipleConsecutiveSeparatorsInMidstring) {
 | 
				
			|||||||
// "/bar" == //bar" == "///bar"
 | 
					// "/bar" == //bar" == "///bar"
 | 
				
			||||||
TEST(NormalizeTest, MultipleConsecutiveSeparatorsAtStringStart) {
 | 
					TEST(NormalizeTest, MultipleConsecutiveSeparatorsAtStringStart) {
 | 
				
			||||||
  EXPECT_EQ(GTEST_PATH_SEP_ "bar", FilePath(GTEST_PATH_SEP_ "bar").string());
 | 
					  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",
 | 
					  EXPECT_EQ(GTEST_PATH_SEP_ "bar",
 | 
				
			||||||
            FilePath(GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar").string());
 | 
					            FilePath(GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar").string());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
  EXPECT_EQ(
 | 
					  EXPECT_EQ(
 | 
				
			||||||
      GTEST_PATH_SEP_ "bar",
 | 
					      GTEST_PATH_SEP_ "bar",
 | 
				
			||||||
      FilePath(GTEST_PATH_SEP_ GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar").string());
 | 
					      FilePath(GTEST_PATH_SEP_ GTEST_PATH_SEP_ GTEST_PATH_SEP_ "bar").string());
 | 
				
			||||||
@@ -621,6 +626,9 @@ TEST(FilePathTest, IsAbsolutePath) {
 | 
				
			|||||||
  EXPECT_TRUE(
 | 
					  EXPECT_TRUE(
 | 
				
			||||||
      FilePath("c:/" GTEST_PATH_SEP_ "is_not" GTEST_PATH_SEP_ "relative")
 | 
					      FilePath("c:/" GTEST_PATH_SEP_ "is_not" GTEST_PATH_SEP_ "relative")
 | 
				
			||||||
          .IsAbsolutePath());
 | 
					          .IsAbsolutePath());
 | 
				
			||||||
 | 
					  EXPECT_TRUE(FilePath("d:/Windows").IsAbsolutePath());
 | 
				
			||||||
 | 
					  EXPECT_TRUE(FilePath("\\\\Host\\Share").IsAbsolutePath());
 | 
				
			||||||
 | 
					  EXPECT_TRUE(FilePath("\\\\Host\\Share\\Folder").IsAbsolutePath());
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
  EXPECT_TRUE(FilePath(GTEST_PATH_SEP_ "is_not" GTEST_PATH_SEP_ "relative")
 | 
					  EXPECT_TRUE(FilePath(GTEST_PATH_SEP_ "is_not" GTEST_PATH_SEP_ "relative")
 | 
				
			||||||
                  .IsAbsolutePath());
 | 
					                  .IsAbsolutePath());
 | 
				
			||||||
@@ -637,6 +645,16 @@ TEST(FilePathTest, IsRootDirectory) {
 | 
				
			|||||||
  EXPECT_FALSE(FilePath("b:a").IsRootDirectory());
 | 
					  EXPECT_FALSE(FilePath("b:a").IsRootDirectory());
 | 
				
			||||||
  EXPECT_FALSE(FilePath("8:/").IsRootDirectory());
 | 
					  EXPECT_FALSE(FilePath("8:/").IsRootDirectory());
 | 
				
			||||||
  EXPECT_FALSE(FilePath("c|/").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
 | 
					#else
 | 
				
			||||||
  EXPECT_TRUE(FilePath("/").IsRootDirectory());
 | 
					  EXPECT_TRUE(FilePath("/").IsRootDirectory());
 | 
				
			||||||
  EXPECT_TRUE(FilePath("//").IsRootDirectory());
 | 
					  EXPECT_TRUE(FilePath("//").IsRootDirectory());
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user