Move path-manipulation functions to own library file.

Leave functions that make file accesses in the file library, and general blaze utilities in the blaze_util file, but move the functions that boil down to string manipulation and path formatting to their own file. (With the exception of getCWD, since absolute path syntax is relevant here.)

Doing this largely to consolidate all Windows path control into a single place, so that it's easier to notice inconsistencies. For instance, ConvertPath currently makes Windows paths absolute, but not Posix paths, and MakeAbsolute relies on this behavior. In addition, JoinPath assumes Posix path syntax, which leads to some odd looking paths. These will be fixed in a followup change.

(Found these issues while working on #4502, trying to fix the windows-specific system bazelrc.)

RELNOTES: None.
PiperOrigin-RevId: 199368226
diff --git a/src/test/cpp/blaze_util_test.cc b/src/test/cpp/blaze_util_test.cc
index 15963b6..03b43c0 100644
--- a/src/test/cpp/blaze_util_test.cc
+++ b/src/test/cpp/blaze_util_test.cc
@@ -211,20 +211,4 @@
                    "--flag"));
 }
 
-TEST_F(BlazeUtilTest, MakeAbsolute) {
-#if defined(WIN32)
-  EXPECT_EQ(MakeAbsolute("C:\\foo\\bar"), "C:\\foo\\bar");
-  EXPECT_EQ(MakeAbsolute("C:/foo/bar"), "C:\\foo\\bar");
-  EXPECT_EQ(MakeAbsolute("C:\\foo\\bar\\"), "C:\\foo\\bar\\");
-  EXPECT_EQ(MakeAbsolute("C:/foo/bar/"), "C:\\foo\\bar\\");
-  EXPECT_EQ(MakeAbsolute("foo"), blaze_util::GetCwd() + "\\foo");
-#else
-  EXPECT_EQ(MakeAbsolute("/foo/bar"), "/foo/bar");
-  EXPECT_EQ(MakeAbsolute("/foo/bar/"), "/foo/bar/");
-  EXPECT_EQ(MakeAbsolute("foo"), blaze_util::GetCwd() + "/foo");
-#endif
-  EXPECT_EQ(MakeAbsolute(std::string()), blaze_util::GetCwd());
-  EXPECT_EQ(MakeAbsolute("/dev/null"), "/dev/null");
-}
-
 }  // namespace blaze
diff --git a/src/test/cpp/blaze_util_windows_test.cc b/src/test/cpp/blaze_util_windows_test.cc
index 2ac5969..f577b48 100644
--- a/src/test/cpp/blaze_util_windows_test.cc
+++ b/src/test/cpp/blaze_util_windows_test.cc
@@ -155,27 +155,4 @@
   ASSERT_ENVVAR_UNSET(long_key.c_str());
 }
 
-TEST(BlazeUtilWindowsTest, ConvertPathTest) {
-  EXPECT_EQ("c:\\foo", ConvertPath("C:\\FOO"));
-  EXPECT_EQ("c:\\", ConvertPath("c:/"));
-  EXPECT_EQ("c:\\foo\\bar", ConvertPath("c:/../foo\\BAR\\.\\"));
-  EXPECT_EQ("nul", MakeAbsolute("NUL"));
-  EXPECT_EQ("nul", MakeAbsolute("nul"));
-  EXPECT_EQ("nul", MakeAbsolute("/dev/null"));
-}
-
-TEST(BlazeUtilWindowsTest, TestMakeAbsolute) {
-  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:\\foo\\BAR"));
-  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:/foo/bar"));
-  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:\\foo\\bar\\"));
-  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:/foo/bar/"));
-  EXPECT_EQ(blaze_util::AsLower(blaze_util::GetCwd()) + "\\foo",
-            MakeAbsolute("foo"));
-  EXPECT_EQ("nul", MakeAbsolute("NUL"));
-  EXPECT_EQ("nul", MakeAbsolute("Nul"));
-  EXPECT_EQ("nul", MakeAbsolute("nul"));
-  EXPECT_EQ(blaze_util::AsLower(blaze_util::GetCwd()), MakeAbsolute(""));
-  EXPECT_EQ("nul", MakeAbsolute("/dev/null"));
-}
-
 }  // namespace blaze
diff --git a/src/test/cpp/option_processor_test.cc b/src/test/cpp/option_processor_test.cc
index 01e2805..10e55cf 100644
--- a/src/test/cpp/option_processor_test.cc
+++ b/src/test/cpp/option_processor_test.cc
@@ -20,6 +20,7 @@
 #include "src/main/cpp/option_processor-internal.h"
 #include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/file_platform.h"
+#include "src/main/cpp/util/path.h"
 #include "src/main/cpp/workspace_layout.h"
 #include "googletest/include/gtest/gtest.h"
 
diff --git a/src/test/cpp/rc_file_test.cc b/src/test/cpp/rc_file_test.cc
index 5900db1..a534778 100644
--- a/src/test/cpp/rc_file_test.cc
+++ b/src/test/cpp/rc_file_test.cc
@@ -21,6 +21,7 @@
 #include "src/main/cpp/rc_file.h"
 #include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/file_platform.h"
+#include "src/main/cpp/util/path.h"
 #include "src/main/cpp/workspace_layout.h"
 #include "googlemock/include/gmock/gmock.h"
 #include "googletest/include/gtest/gtest.h"
diff --git a/src/test/cpp/rc_options_test.cc b/src/test/cpp/rc_options_test.cc
index 3a3ebf3..58251af 100644
--- a/src/test/cpp/rc_options_test.cc
+++ b/src/test/cpp/rc_options_test.cc
@@ -19,6 +19,7 @@
 #include "src/main/cpp/option_processor.h"
 #include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/file_platform.h"
+#include "src/main/cpp/util/path.h"
 #include "src/main/cpp/util/strings.h"
 #include "src/main/cpp/workspace_layout.h"
 #include "googletest/include/gtest/gtest.h"
diff --git a/src/test/cpp/util/BUILD b/src/test/cpp/util/BUILD
index 5dcb04a..455994f 100644
--- a/src/test/cpp/util/BUILD
+++ b/src/test/cpp/util/BUILD
@@ -31,7 +31,31 @@
     }),
     deps = [
         ":test_util",
-        "//src/main/cpp/util:file",
+        "//src/main/cpp/util:filesystem",
+        "@com_google_googletest//:gtest_main",
+    ] + select({
+        "//src/conditions:windows": [
+            ":windows_test_util",
+            "//src/main/native/windows:lib-file",
+        ],
+        "//conditions:default": [],
+    }),
+)
+
+cc_test(
+    name = "path_test",
+    size = "small",
+    srcs = select({
+        "//src/conditions:windows": [
+            "path_windows_test.cc",
+        ],
+        "//conditions:default": [
+            "path_posix_test.cc",
+        ],
+    }),
+    deps = [
+        ":test_util",
+        "//src/main/cpp/util:filesystem",
         "@com_google_googletest//:gtest_main",
     ] + select({
         "//src/conditions:windows": [
@@ -48,7 +72,7 @@
     deps = [
         "//src/main/cpp:blaze_util",
         "//src/main/cpp/util:bazel_log_handler",
-        "//src/main/cpp/util:file",
+        "//src/main/cpp/util:filesystem",
         "//src/main/cpp/util:logging",
         "@com_google_googletest//:gtest_main",
     ],
diff --git a/src/test/cpp/util/file_posix_test.cc b/src/test/cpp/util/file_posix_test.cc
index 688042c..75026af 100644
--- a/src/test/cpp/util/file_posix_test.cc
+++ b/src/test/cpp/util/file_posix_test.cc
@@ -19,6 +19,8 @@
 
 #include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/file_platform.h"
+#include "src/main/cpp/util/path.h"
+#include "src/main/cpp/util/path_platform.h"
 #include "src/test/cpp/util/test_util.h"
 #include "googletest/include/gtest/gtest.h"
 
@@ -46,73 +48,6 @@
   return close(fd) == 0;
 }
 
-TEST(FilePosixTest, TestDirname) {
-  // The Posix version of SplitPath (thus Dirname too, which is implemented on
-  // top of it) is not aware of Windows paths.
-  ASSERT_EQ("", Dirname(""));
-  ASSERT_EQ("/", Dirname("/"));
-  ASSERT_EQ("", Dirname("foo"));
-  ASSERT_EQ("/", Dirname("/foo"));
-  ASSERT_EQ("/foo", Dirname("/foo/"));
-  ASSERT_EQ("foo", Dirname("foo/bar"));
-  ASSERT_EQ("foo/bar", Dirname("foo/bar/baz"));
-  ASSERT_EQ("", Dirname("\\foo"));
-  ASSERT_EQ("", Dirname("\\foo\\"));
-  ASSERT_EQ("", Dirname("foo\\bar"));
-  ASSERT_EQ("", Dirname("foo\\bar\\baz"));
-  ASSERT_EQ("foo\\bar", Dirname("foo\\bar/baz\\qux"));
-  ASSERT_EQ("c:", Dirname("c:/"));
-  ASSERT_EQ("", Dirname("c:\\"));
-  ASSERT_EQ("c:", Dirname("c:/foo"));
-  ASSERT_EQ("", Dirname("c:\\foo"));
-  ASSERT_EQ("", Dirname("\\\\?\\c:\\"));
-  ASSERT_EQ("", Dirname("\\\\?\\c:\\foo"));
-}
-
-TEST(FilePosixTest, TestBasename) {
-  // The Posix version of SplitPath (thus Basename too, which is implemented on
-  // top of it) is not aware of Windows paths.
-  ASSERT_EQ("", Basename(""));
-  ASSERT_EQ("", Basename("/"));
-  ASSERT_EQ("foo", Basename("foo"));
-  ASSERT_EQ("foo", Basename("/foo"));
-  ASSERT_EQ("", Basename("/foo/"));
-  ASSERT_EQ("bar", Basename("foo/bar"));
-  ASSERT_EQ("baz", Basename("foo/bar/baz"));
-  ASSERT_EQ("\\foo", Basename("\\foo"));
-  ASSERT_EQ("\\foo\\", Basename("\\foo\\"));
-  ASSERT_EQ("foo\\bar", Basename("foo\\bar"));
-  ASSERT_EQ("foo\\bar\\baz", Basename("foo\\bar\\baz"));
-  ASSERT_EQ("baz\\qux", Basename("foo\\bar/baz\\qux"));
-  ASSERT_EQ("qux", Basename("qux"));
-  ASSERT_EQ("", Basename("c:/"));
-  ASSERT_EQ("c:\\", Basename("c:\\"));
-  ASSERT_EQ("foo", Basename("c:/foo"));
-  ASSERT_EQ("c:\\foo", Basename("c:\\foo"));
-  ASSERT_EQ("\\\\?\\c:\\", Basename("\\\\?\\c:\\"));
-  ASSERT_EQ("\\\\?\\c:\\foo", Basename("\\\\?\\c:\\foo"));
-}
-
-TEST(FilePosixTest, JoinPath) {
-  std::string path = JoinPath("", "");
-  ASSERT_EQ("", path);
-
-  path = JoinPath("a", "b");
-  ASSERT_EQ("a/b", path);
-
-  path = JoinPath("a/", "b");
-  ASSERT_EQ("a/b", path);
-
-  path = JoinPath("a", "/b");
-  ASSERT_EQ("a/b", path);
-
-  path = JoinPath("a/", "/b");
-  ASSERT_EQ("a/b", path);
-
-  path = JoinPath("/", "/");
-  ASSERT_EQ("/", path);
-}
-
 void MockDirectoryListingFunction(const string& path,
                                   DirectoryEntryConsumer* consume) {
   if (path == "root") {
@@ -272,24 +207,6 @@
   ASSERT_EQ(0, rmdir(dir.c_str()));
 }
 
-TEST(FilePosixTest, GetCwd) {
-  char cwdbuf[PATH_MAX];
-  ASSERT_EQ(cwdbuf, getcwd(cwdbuf, PATH_MAX));
-
-  // Assert that GetCwd() and getcwd() return the same value.
-  string cwd(cwdbuf);
-  ASSERT_EQ(cwd, blaze_util::GetCwd());
-
-  // Change to a different directory.
-  ASSERT_EQ(0, chdir("/usr"));
-
-  // Assert that GetCwd() returns the new CWD.
-  ASSERT_EQ(string("/usr"), blaze_util::GetCwd());
-
-  ASSERT_EQ(0, chdir(cwd.c_str()));
-  ASSERT_EQ(cwd, blaze_util::GetCwd());
-}
-
 TEST(FilePosixTest, ChangeDirectory) {
   // Retrieve the current working directory.
   char old_wd[PATH_MAX];
@@ -405,32 +322,4 @@
   rmdir(root.c_str());
 }
 
-TEST(FilePosixTest, IsAbsolute) {
-  ASSERT_FALSE(IsAbsolute(""));
-  ASSERT_TRUE(IsAbsolute("/"));
-  ASSERT_TRUE(IsAbsolute("/foo"));
-  ASSERT_FALSE(IsAbsolute("\\"));
-  ASSERT_FALSE(IsAbsolute("\\foo"));
-  ASSERT_FALSE(IsAbsolute("c:"));
-  ASSERT_FALSE(IsAbsolute("c:/"));
-  ASSERT_FALSE(IsAbsolute("c:\\"));
-  ASSERT_FALSE(IsAbsolute("c:\\foo"));
-  ASSERT_FALSE(IsAbsolute("\\\\?\\c:\\"));
-  ASSERT_FALSE(IsAbsolute("\\\\?\\c:\\foo"));
-}
-
-TEST(FilePosixTest, IsRootDirectory) {
-  ASSERT_FALSE(IsRootDirectory(""));
-  ASSERT_TRUE(IsRootDirectory("/"));
-  ASSERT_FALSE(IsRootDirectory("/foo"));
-  ASSERT_FALSE(IsRootDirectory("\\"));
-  ASSERT_FALSE(IsRootDirectory("\\foo"));
-  ASSERT_FALSE(IsRootDirectory("c:"));
-  ASSERT_FALSE(IsRootDirectory("c:/"));
-  ASSERT_FALSE(IsRootDirectory("c:\\"));
-  ASSERT_FALSE(IsRootDirectory("c:\\foo"));
-  ASSERT_FALSE(IsRootDirectory("\\\\?\\c:\\"));
-  ASSERT_FALSE(IsRootDirectory("\\\\?\\c:\\foo"));
-}
-
 }  // namespace blaze_util
diff --git a/src/test/cpp/util/file_test.cc b/src/test/cpp/util/file_test.cc
index c05b3b9..5f6f6ea 100644
--- a/src/test/cpp/util/file_test.cc
+++ b/src/test/cpp/util/file_test.cc
@@ -21,6 +21,8 @@
 #include <vector>
 
 #include "src/main/cpp/util/file.h"
+#include "src/main/cpp/util/path.h"
+#include "src/main/cpp/util/path_platform.h"
 #include "src/test/cpp/util/test_util.h"
 #include "googletest/include/gtest/gtest.h"
 
diff --git a/src/test/cpp/util/file_windows_test.cc b/src/test/cpp/util/file_windows_test.cc
index 87987b4..1844736 100644
--- a/src/test/cpp/util/file_windows_test.cc
+++ b/src/test/cpp/util/file_windows_test.cc
@@ -22,6 +22,8 @@
 #include "gtest/gtest.h"
 #include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/file_platform.h"
+#include "src/main/cpp/util/path.h"
+#include "src/main/cpp/util/path_platform.h"
 #include "src/main/cpp/util/strings.h"
 #include "src/main/native/windows/file.h"
 #include "src/main/native/windows/util.h"
@@ -40,7 +42,6 @@
 using std::wstring;
 
 // Methods defined in file_windows.cc that are only visible for testing.
-bool AsWindowsPath(const string& path, wstring* result, string* error);
 string NormalizeWindowsPath(string path);
 
 class FileWindowsTest : public ::testing::Test {
@@ -90,223 +91,6 @@
   AssertTearDown(L"test.teardown.b", L"test.teardown.a");
 }
 
-TEST_F(FileWindowsTest, TestNormalizeWindowsPath) {
-  ASSERT_EQ(string(""), NormalizeWindowsPath(""));
-  ASSERT_EQ(string(""), NormalizeWindowsPath("."));
-  ASSERT_EQ(string("foo"), NormalizeWindowsPath("foo"));
-  ASSERT_EQ(string("foo"), NormalizeWindowsPath("foo/"));
-  ASSERT_EQ(string("foo\\bar"), NormalizeWindowsPath("foo//bar"));
-  ASSERT_EQ(string("foo\\bar"), NormalizeWindowsPath("../..//foo/./bar"));
-  ASSERT_EQ(string("foo\\bar"), NormalizeWindowsPath("../foo/baz/../bar"));
-  ASSERT_EQ(string("c:\\"), NormalizeWindowsPath("c:"));
-  ASSERT_EQ(string("c:\\"), NormalizeWindowsPath("c:/"));
-  ASSERT_EQ(string("c:\\"), NormalizeWindowsPath("c:\\"));
-  ASSERT_EQ(string("c:\\foo\\bar"), NormalizeWindowsPath("c:\\..//foo/./bar/"));
-}
-
-TEST_F(FileWindowsTest, TestDirname) {
-  ASSERT_EQ("", Dirname(""));
-  ASSERT_EQ("/", Dirname("/"));
-  ASSERT_EQ("", Dirname("foo"));
-  ASSERT_EQ("/", Dirname("/foo"));
-  ASSERT_EQ("/foo", Dirname("/foo/"));
-  ASSERT_EQ("foo", Dirname("foo/bar"));
-  ASSERT_EQ("foo/bar", Dirname("foo/bar/baz"));
-  ASSERT_EQ("\\", Dirname("\\foo"));
-  ASSERT_EQ("\\foo", Dirname("\\foo\\"));
-  ASSERT_EQ("foo", Dirname("foo\\bar"));
-  ASSERT_EQ("foo\\bar", Dirname("foo\\bar\\baz"));
-  ASSERT_EQ("foo\\bar/baz", Dirname("foo\\bar/baz\\qux"));
-  ASSERT_EQ("c:/", Dirname("c:/"));
-  ASSERT_EQ("c:\\", Dirname("c:\\"));
-  ASSERT_EQ("c:/", Dirname("c:/foo"));
-  ASSERT_EQ("c:\\", Dirname("c:\\foo"));
-  ASSERT_EQ("\\\\?\\c:\\", Dirname("\\\\?\\c:\\"));
-  ASSERT_EQ("\\\\?\\c:\\", Dirname("\\\\?\\c:\\foo"));
-}
-
-TEST_F(FileWindowsTest, TestBasename) {
-  ASSERT_EQ("", Basename(""));
-  ASSERT_EQ("", Basename("/"));
-  ASSERT_EQ("foo", Basename("foo"));
-  ASSERT_EQ("foo", Basename("/foo"));
-  ASSERT_EQ("", Basename("/foo/"));
-  ASSERT_EQ("bar", Basename("foo/bar"));
-  ASSERT_EQ("baz", Basename("foo/bar/baz"));
-  ASSERT_EQ("foo", Basename("\\foo"));
-  ASSERT_EQ("", Basename("\\foo\\"));
-  ASSERT_EQ("bar", Basename("foo\\bar"));
-  ASSERT_EQ("baz", Basename("foo\\bar\\baz"));
-  ASSERT_EQ("qux", Basename("foo\\bar/baz\\qux"));
-  ASSERT_EQ("", Basename("c:/"));
-  ASSERT_EQ("", Basename("c:\\"));
-  ASSERT_EQ("foo", Basename("c:/foo"));
-  ASSERT_EQ("foo", Basename("c:\\foo"));
-  ASSERT_EQ("", Basename("\\\\?\\c:\\"));
-  ASSERT_EQ("foo", Basename("\\\\?\\c:\\foo"));
-}
-
-TEST_F(FileWindowsTest, TestIsAbsolute) {
-  ASSERT_FALSE(IsAbsolute(""));
-  ASSERT_TRUE(IsAbsolute("/"));
-  ASSERT_TRUE(IsAbsolute("/foo"));
-  ASSERT_TRUE(IsAbsolute("\\"));
-  ASSERT_TRUE(IsAbsolute("\\foo"));
-  ASSERT_FALSE(IsAbsolute("c:"));
-  ASSERT_TRUE(IsAbsolute("c:/"));
-  ASSERT_TRUE(IsAbsolute("c:\\"));
-  ASSERT_TRUE(IsAbsolute("c:\\foo"));
-  ASSERT_TRUE(IsAbsolute("\\\\?\\c:\\"));
-  ASSERT_TRUE(IsAbsolute("\\\\?\\c:\\foo"));
-}
-
-TEST_F(FileWindowsTest, TestIsRootDirectory) {
-  ASSERT_FALSE(IsRootDirectory(""));
-  ASSERT_TRUE(IsRootDirectory("/"));
-  ASSERT_FALSE(IsRootDirectory("/foo"));
-  ASSERT_TRUE(IsRootDirectory("\\"));
-  ASSERT_FALSE(IsRootDirectory("\\foo"));
-  ASSERT_FALSE(IsRootDirectory("c:"));
-  ASSERT_TRUE(IsRootDirectory("c:/"));
-  ASSERT_TRUE(IsRootDirectory("c:\\"));
-  ASSERT_FALSE(IsRootDirectory("c:\\foo"));
-  ASSERT_TRUE(IsRootDirectory("\\\\?\\c:\\"));
-  ASSERT_FALSE(IsRootDirectory("\\\\?\\c:\\foo"));
-}
-
-TEST_F(FileWindowsTest, TestAsWindowsPath) {
-  SetEnvironmentVariableA("BAZEL_SH", "c:\\some\\long/path\\bin\\bash.exe");
-  wstring actual;
-
-  // Null and empty input produces empty result.
-  ASSERT_TRUE(AsWindowsPath("", &actual, nullptr));
-  ASSERT_EQ(wstring(L""), actual);
-
-  // If the path has a "\\?\" prefix, AsWindowsPath assumes it's a correct
-  // Windows path. If it's not, the Windows API function that we pass the path
-  // to will fail anyway.
-  ASSERT_TRUE(AsWindowsPath("\\\\?\\anything/..", &actual, nullptr));
-  ASSERT_EQ(wstring(L"\\\\?\\anything/.."), actual);
-
-  // Trailing slash or backslash is removed.
-  ASSERT_TRUE(AsWindowsPath("foo/", &actual, nullptr));
-  ASSERT_EQ(wstring(L"foo"), actual);
-  ASSERT_TRUE(AsWindowsPath("foo\\", &actual, nullptr));
-  ASSERT_EQ(wstring(L"foo"), actual);
-
-  // Slashes are converted to backslash.
-  ASSERT_TRUE(AsWindowsPath("foo/bar", &actual, nullptr));
-  ASSERT_EQ(wstring(L"foo\\bar"), actual);
-  ASSERT_TRUE(AsWindowsPath("c:/", &actual, nullptr));
-  ASSERT_EQ(wstring(L"c:\\"), actual);
-  ASSERT_TRUE(AsWindowsPath("c:\\", &actual, nullptr));
-  ASSERT_EQ(wstring(L"c:\\"), actual);
-
-  // Invalid paths
-  string error;
-  ASSERT_FALSE(AsWindowsPath("c:", &actual, &error));
-  EXPECT_TRUE(error.find("working-directory relative paths") != string::npos);
-  ASSERT_FALSE(AsWindowsPath("c:foo", &actual, &error));
-  EXPECT_TRUE(error.find("working-directory relative paths") != string::npos);
-  ASSERT_FALSE(AsWindowsPath("\\\\foo", &actual, &error));
-  EXPECT_TRUE(error.find("network paths") != string::npos);
-
-  // /dev/null and NUL produce NUL.
-  ASSERT_TRUE(AsWindowsPath("/dev/null", &actual, nullptr));
-  ASSERT_EQ(wstring(L"NUL"), actual);
-  ASSERT_TRUE(AsWindowsPath("Nul", &actual, nullptr));
-  ASSERT_EQ(wstring(L"NUL"), actual);
-
-  // MSYS path with drive letter.
-  ASSERT_FALSE(AsWindowsPath("/c", &actual, &error));
-  EXPECT_TRUE(error.find("Unix-style") != string::npos);
-  ASSERT_FALSE(AsWindowsPath("/c/", &actual, &error));
-  EXPECT_TRUE(error.find("Unix-style") != string::npos);
-
-  // Absolute-on-current-drive path gets a drive letter.
-  ASSERT_TRUE(AsWindowsPath("\\foo", &actual, nullptr));
-  ASSERT_EQ(wstring(1, GetCwd()[0]) + L":\\foo", actual);
-
-  // Even for long paths, AsWindowsPath doesn't add a "\\?\" prefix (it's the
-  // caller's duty to do so).
-  wstring wlongpath(L"dummy_long_path\\");
-  string longpath("dummy_long_path/");
-  while (longpath.size() <= MAX_PATH) {
-    wlongpath += wlongpath;
-    longpath += longpath;
-  }
-  wlongpath.pop_back();  // remove trailing "\"
-  ASSERT_TRUE(AsWindowsPath(longpath, &actual, nullptr));
-  ASSERT_EQ(wlongpath, actual);
-}
-
-TEST_F(FileWindowsTest, TestAsAbsoluteWindowsPath) {
-  SetEnvironmentVariableA("BAZEL_SH", "c:\\some\\long/path\\bin\\bash.exe");
-  wstring actual;
-
-  ASSERT_TRUE(AsAbsoluteWindowsPath("c:/", &actual, nullptr));
-  ASSERT_EQ(L"\\\\?\\c:\\", actual);
-
-  ASSERT_TRUE(AsAbsoluteWindowsPath("c:/..\\non-existent//", &actual, nullptr));
-  ASSERT_EQ(L"\\\\?\\c:\\non-existent", actual);
-
-  WCHAR cwd[MAX_PATH];
-  wstring cwdw(CstringToWstring(GetCwd().c_str()).get());
-  wstring expected =
-      wstring(L"\\\\?\\") + cwdw +
-      ((cwdw.back() == L'\\') ? L"non-existent" : L"\\non-existent");
-  ASSERT_TRUE(AsAbsoluteWindowsPath("non-existent", &actual, nullptr));
-  ASSERT_EQ(actual, expected);
-}
-
-TEST_F(FileWindowsTest, TestAsShortWindowsPath) {
-  string actual;
-  ASSERT_TRUE(AsShortWindowsPath("/dev/null", &actual, nullptr));
-  ASSERT_EQ(string("NUL"), actual);
-
-  ASSERT_TRUE(AsShortWindowsPath("nul", &actual, nullptr));
-  ASSERT_EQ(string("NUL"), actual);
-
-  ASSERT_TRUE(AsShortWindowsPath("C://", &actual, nullptr));
-  ASSERT_EQ(string("c:\\"), actual);
-
-  string error;
-  ASSERT_FALSE(AsShortWindowsPath("/C//", &actual, &error));
-  EXPECT_TRUE(error.find("Unix-style") != string::npos);
-
-  // The A drive usually doesn't exist but AsShortWindowsPath should still work.
-  // Here we even have multiple trailing slashes, that should be handled too.
-  ASSERT_TRUE(AsShortWindowsPath("A://", &actual, nullptr));
-  ASSERT_EQ(string("a:\\"), actual);
-
-  // Assert that we can shorten the TEST_TMPDIR.
-  string tmpdir;
-  GET_TEST_TMPDIR(tmpdir);
-  string short_tmpdir;
-  ASSERT_TRUE(AsShortWindowsPath(tmpdir, &short_tmpdir, nullptr));
-  ASSERT_LT(0, short_tmpdir.size());
-  ASSERT_TRUE(PathExists(short_tmpdir));
-
-  // Assert that a trailing "/" doesn't change the shortening logic and it will
-  // be stripped from the result.
-  ASSERT_TRUE(AsShortWindowsPath(tmpdir + "/", &actual, nullptr));
-  ASSERT_EQ(actual, short_tmpdir);
-  ASSERT_NE(actual.back(), '/');
-  ASSERT_NE(actual.back(), '\\');
-
-  // Assert shortening another long path, and that the result is lowercased.
-  string dirname(JoinPath(short_tmpdir, "LONGpathNAME"));
-  ASSERT_EQ(0, mkdir(dirname.c_str()));
-  ASSERT_TRUE(PathExists(dirname));
-  ASSERT_TRUE(AsShortWindowsPath(dirname, &actual, nullptr));
-  ASSERT_EQ(short_tmpdir + "\\longpa~1", actual);
-
-  // Assert shortening non-existent paths.
-  ASSERT_TRUE(AsShortWindowsPath(JoinPath(tmpdir, "NonExistent/FOO"), &actual,
-                                 nullptr));
-  ASSERT_EQ(short_tmpdir + "\\nonexistent\\foo", actual);
-}
-
 TEST_F(FileWindowsTest, TestMsysRootRetrieval) {
   wstring actual;
 
@@ -513,18 +297,4 @@
   ASSERT_EQ(dircanon, symcanon);
 }
 
-TEST(FileTest, IsWindowsDevNullTest) {
-  ASSERT_TRUE(IsDevNull("nul"));
-  ASSERT_TRUE(IsDevNull("NUL"));
-  ASSERT_TRUE(IsDevNull("nuL"));
-  ASSERT_TRUE(IsDevNull("/dev/null"));
-  ASSERT_FALSE(IsDevNull("/Dev/Null"));
-  ASSERT_FALSE(IsDevNull("dev/null"));
-  ASSERT_FALSE(IsDevNull("/dev/nul"));
-  ASSERT_FALSE(IsDevNull("/dev/nulll"));
-  ASSERT_FALSE(IsDevNull("nu"));
-  ASSERT_FALSE(IsDevNull(NULL));
-  ASSERT_FALSE(IsDevNull(""));
-}
-
 }  // namespace blaze_util
diff --git a/src/test/cpp/util/logging_test.cc b/src/test/cpp/util/logging_test.cc
index f3ced04..d48c177 100644
--- a/src/test/cpp/util/logging_test.cc
+++ b/src/test/cpp/util/logging_test.cc
@@ -20,6 +20,7 @@
 #include "src/main/cpp/util/bazel_log_handler.h"
 #include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/logging.h"
+#include "src/main/cpp/util/path.h"
 #include "googlemock/include/gmock/gmock.h"
 #include "googletest/include/gtest/gtest.h"
 
diff --git a/src/test/cpp/util/path_posix_test.cc b/src/test/cpp/util/path_posix_test.cc
new file mode 100644
index 0000000..7d0e497
--- /dev/null
+++ b/src/test/cpp/util/path_posix_test.cc
@@ -0,0 +1,162 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "src/main/cpp/util/file_platform.h"
+#include "src/main/cpp/util/path.h"
+#include "src/main/cpp/util/path_platform.h"
+#include "src/test/cpp/util/test_util.h"
+#include "googletest/include/gtest/gtest.h"
+
+namespace blaze_util {
+
+using std::pair;
+using std::string;
+using std::vector;
+
+TEST(PathPosixTest, TestDirname) {
+  // The Posix version of SplitPath (thus Dirname too, which is implemented on
+  // top of it) is not aware of Windows paths.
+  ASSERT_EQ("", Dirname(""));
+  ASSERT_EQ("/", Dirname("/"));
+  ASSERT_EQ("", Dirname("foo"));
+  ASSERT_EQ("/", Dirname("/foo"));
+  ASSERT_EQ("/foo", Dirname("/foo/"));
+  ASSERT_EQ("foo", Dirname("foo/bar"));
+  ASSERT_EQ("foo/bar", Dirname("foo/bar/baz"));
+  ASSERT_EQ("", Dirname("\\foo"));
+  ASSERT_EQ("", Dirname("\\foo\\"));
+  ASSERT_EQ("", Dirname("foo\\bar"));
+  ASSERT_EQ("", Dirname("foo\\bar\\baz"));
+  ASSERT_EQ("foo\\bar", Dirname("foo\\bar/baz\\qux"));
+  ASSERT_EQ("c:", Dirname("c:/"));
+  ASSERT_EQ("", Dirname("c:\\"));
+  ASSERT_EQ("c:", Dirname("c:/foo"));
+  ASSERT_EQ("", Dirname("c:\\foo"));
+  ASSERT_EQ("", Dirname("\\\\?\\c:\\"));
+  ASSERT_EQ("", Dirname("\\\\?\\c:\\foo"));
+}
+
+TEST(PathPosixTest, TestBasename) {
+  // The Posix version of SplitPath (thus Basename too, which is implemented on
+  // top of it) is not aware of Windows paths.
+  ASSERT_EQ("", Basename(""));
+  ASSERT_EQ("", Basename("/"));
+  ASSERT_EQ("foo", Basename("foo"));
+  ASSERT_EQ("foo", Basename("/foo"));
+  ASSERT_EQ("", Basename("/foo/"));
+  ASSERT_EQ("bar", Basename("foo/bar"));
+  ASSERT_EQ("baz", Basename("foo/bar/baz"));
+  ASSERT_EQ("\\foo", Basename("\\foo"));
+  ASSERT_EQ("\\foo\\", Basename("\\foo\\"));
+  ASSERT_EQ("foo\\bar", Basename("foo\\bar"));
+  ASSERT_EQ("foo\\bar\\baz", Basename("foo\\bar\\baz"));
+  ASSERT_EQ("baz\\qux", Basename("foo\\bar/baz\\qux"));
+  ASSERT_EQ("qux", Basename("qux"));
+  ASSERT_EQ("", Basename("c:/"));
+  ASSERT_EQ("c:\\", Basename("c:\\"));
+  ASSERT_EQ("foo", Basename("c:/foo"));
+  ASSERT_EQ("c:\\foo", Basename("c:\\foo"));
+  ASSERT_EQ("\\\\?\\c:\\", Basename("\\\\?\\c:\\"));
+  ASSERT_EQ("\\\\?\\c:\\foo", Basename("\\\\?\\c:\\foo"));
+}
+
+TEST(PathPosixTest, JoinPath) {
+  std::string path = JoinPath("", "");
+  ASSERT_EQ("", path);
+
+  path = JoinPath("a", "b");
+  ASSERT_EQ("a/b", path);
+
+  path = JoinPath("a/", "b");
+  ASSERT_EQ("a/b", path);
+
+  path = JoinPath("a", "/b");
+  ASSERT_EQ("a/b", path);
+
+  path = JoinPath("a/", "/b");
+  ASSERT_EQ("a/b", path);
+
+  path = JoinPath("/", "/");
+  ASSERT_EQ("/", path);
+}
+
+TEST(PathPosixTest, GetCwd) {
+  char cwdbuf[PATH_MAX];
+  ASSERT_EQ(cwdbuf, getcwd(cwdbuf, PATH_MAX));
+
+  // Assert that GetCwd() and getcwd() return the same value.
+  string cwd(cwdbuf);
+  ASSERT_EQ(cwd, blaze_util::GetCwd());
+
+  // Change to a different directory.
+  ASSERT_EQ(0, chdir("/usr"));
+
+  // Assert that GetCwd() returns the new CWD.
+  ASSERT_EQ(string("/usr"), blaze_util::GetCwd());
+
+  ASSERT_EQ(0, chdir(cwd.c_str()));
+  ASSERT_EQ(cwd, blaze_util::GetCwd());
+}
+
+TEST(PathPosixTest, IsAbsolute) {
+  ASSERT_FALSE(IsAbsolute(""));
+  ASSERT_TRUE(IsAbsolute("/"));
+  ASSERT_TRUE(IsAbsolute("/foo"));
+  ASSERT_FALSE(IsAbsolute("\\"));
+  ASSERT_FALSE(IsAbsolute("\\foo"));
+  ASSERT_FALSE(IsAbsolute("c:"));
+  ASSERT_FALSE(IsAbsolute("c:/"));
+  ASSERT_FALSE(IsAbsolute("c:\\"));
+  ASSERT_FALSE(IsAbsolute("c:\\foo"));
+  ASSERT_FALSE(IsAbsolute("\\\\?\\c:\\"));
+  ASSERT_FALSE(IsAbsolute("\\\\?\\c:\\foo"));
+}
+
+TEST(PathPosixTest, IsRootDirectory) {
+  ASSERT_FALSE(IsRootDirectory(""));
+  ASSERT_TRUE(IsRootDirectory("/"));
+  ASSERT_FALSE(IsRootDirectory("/foo"));
+  ASSERT_FALSE(IsRootDirectory("\\"));
+  ASSERT_FALSE(IsRootDirectory("\\foo"));
+  ASSERT_FALSE(IsRootDirectory("c:"));
+  ASSERT_FALSE(IsRootDirectory("c:/"));
+  ASSERT_FALSE(IsRootDirectory("c:\\"));
+  ASSERT_FALSE(IsRootDirectory("c:\\foo"));
+  ASSERT_FALSE(IsRootDirectory("\\\\?\\c:\\"));
+  ASSERT_FALSE(IsRootDirectory("\\\\?\\c:\\foo"));
+}
+
+TEST(PathPosixTest, IsDevNullTest) {
+  ASSERT_TRUE(IsDevNull("/dev/null"));
+  ASSERT_FALSE(IsDevNull("dev/null"));
+  ASSERT_FALSE(IsDevNull("/dev/nul"));
+  ASSERT_FALSE(IsDevNull("/dev/nulll"));
+  ASSERT_FALSE(IsDevNull(NULL));
+  ASSERT_FALSE(IsDevNull(""));
+}
+
+TEST(PathPosixTest, MakeAbsolute) {
+  EXPECT_EQ(MakeAbsolute("/foo/bar"), "/foo/bar");
+  EXPECT_EQ(MakeAbsolute("/foo/bar/"), "/foo/bar/");
+  EXPECT_EQ(MakeAbsolute("foo"), blaze_util::GetCwd() + "/foo");
+  EXPECT_EQ(MakeAbsolute(std::string()), blaze_util::GetCwd());
+  EXPECT_EQ(MakeAbsolute("/dev/null"), "/dev/null");
+}
+
+}  // namespace blaze_util
diff --git a/src/test/cpp/util/path_windows_test.cc b/src/test/cpp/util/path_windows_test.cc
new file mode 100644
index 0000000..789f67c
--- /dev/null
+++ b/src/test/cpp/util/path_windows_test.cc
@@ -0,0 +1,318 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include <stdio.h>
+#include <string.h>
+#include <windows.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "src/main/cpp/util/file_platform.h"
+#include "src/main/cpp/util/path.h"
+#include "src/main/cpp/util/path_platform.h"
+#include "src/main/cpp/util/strings.h"
+#include "src/main/native/windows/file.h"
+#include "src/main/native/windows/util.h"
+#include "src/test/cpp/util/test_util.h"
+#include "src/test/cpp/util/windows_test_util.h"
+
+#if !defined(COMPILER_MSVC) && !defined(__CYGWIN__)
+#error("This test should only be run on Windows")
+#endif  // !defined(COMPILER_MSVC) && !defined(__CYGWIN__)
+
+namespace blaze_util {
+
+using bazel::windows::CreateJunction;
+using std::string;
+using std::unique_ptr;
+using std::wstring;
+
+// Methods defined in path_windows.cc that are only visible for testing.
+string NormalizeWindowsPath(string path);
+
+TEST(PathWindowsTest, TestNormalizeWindowsPath) {
+  ASSERT_EQ(string(""), NormalizeWindowsPath(""));
+  ASSERT_EQ(string(""), NormalizeWindowsPath("."));
+  ASSERT_EQ(string("foo"), NormalizeWindowsPath("foo"));
+  ASSERT_EQ(string("foo"), NormalizeWindowsPath("foo/"));
+  ASSERT_EQ(string("foo\\bar"), NormalizeWindowsPath("foo//bar"));
+  ASSERT_EQ(string("foo\\bar"), NormalizeWindowsPath("../..//foo/./bar"));
+  ASSERT_EQ(string("foo\\bar"), NormalizeWindowsPath("../foo/baz/../bar"));
+  ASSERT_EQ(string("c:\\"), NormalizeWindowsPath("c:"));
+  ASSERT_EQ(string("c:\\"), NormalizeWindowsPath("c:/"));
+  ASSERT_EQ(string("c:\\"), NormalizeWindowsPath("c:\\"));
+  ASSERT_EQ(string("c:\\foo\\bar"), NormalizeWindowsPath("c:\\..//foo/./bar/"));
+}
+
+TEST(PathWindowsTest, TestDirname) {
+  ASSERT_EQ("", Dirname(""));
+  ASSERT_EQ("/", Dirname("/"));
+  ASSERT_EQ("", Dirname("foo"));
+  ASSERT_EQ("/", Dirname("/foo"));
+  ASSERT_EQ("/foo", Dirname("/foo/"));
+  ASSERT_EQ("foo", Dirname("foo/bar"));
+  ASSERT_EQ("foo/bar", Dirname("foo/bar/baz"));
+  ASSERT_EQ("\\", Dirname("\\foo"));
+  ASSERT_EQ("\\foo", Dirname("\\foo\\"));
+  ASSERT_EQ("foo", Dirname("foo\\bar"));
+  ASSERT_EQ("foo\\bar", Dirname("foo\\bar\\baz"));
+  ASSERT_EQ("foo\\bar/baz", Dirname("foo\\bar/baz\\qux"));
+  ASSERT_EQ("c:/", Dirname("c:/"));
+  ASSERT_EQ("c:\\", Dirname("c:\\"));
+  ASSERT_EQ("c:/", Dirname("c:/foo"));
+  ASSERT_EQ("c:\\", Dirname("c:\\foo"));
+  ASSERT_EQ("\\\\?\\c:\\", Dirname("\\\\?\\c:\\"));
+  ASSERT_EQ("\\\\?\\c:\\", Dirname("\\\\?\\c:\\foo"));
+}
+
+TEST(PathWindowsTest, TestBasename) {
+  ASSERT_EQ("", Basename(""));
+  ASSERT_EQ("", Basename("/"));
+  ASSERT_EQ("foo", Basename("foo"));
+  ASSERT_EQ("foo", Basename("/foo"));
+  ASSERT_EQ("", Basename("/foo/"));
+  ASSERT_EQ("bar", Basename("foo/bar"));
+  ASSERT_EQ("baz", Basename("foo/bar/baz"));
+  ASSERT_EQ("foo", Basename("\\foo"));
+  ASSERT_EQ("", Basename("\\foo\\"));
+  ASSERT_EQ("bar", Basename("foo\\bar"));
+  ASSERT_EQ("baz", Basename("foo\\bar\\baz"));
+  ASSERT_EQ("qux", Basename("foo\\bar/baz\\qux"));
+  ASSERT_EQ("", Basename("c:/"));
+  ASSERT_EQ("", Basename("c:\\"));
+  ASSERT_EQ("foo", Basename("c:/foo"));
+  ASSERT_EQ("foo", Basename("c:\\foo"));
+  ASSERT_EQ("", Basename("\\\\?\\c:\\"));
+  ASSERT_EQ("foo", Basename("\\\\?\\c:\\foo"));
+}
+
+TEST(PathWindowsTest, TestIsAbsolute) {
+  ASSERT_FALSE(IsAbsolute(""));
+  ASSERT_TRUE(IsAbsolute("/"));
+  ASSERT_TRUE(IsAbsolute("/foo"));
+  ASSERT_TRUE(IsAbsolute("\\"));
+  ASSERT_TRUE(IsAbsolute("\\foo"));
+  ASSERT_FALSE(IsAbsolute("c:"));
+  ASSERT_TRUE(IsAbsolute("c:/"));
+  ASSERT_TRUE(IsAbsolute("c:\\"));
+  ASSERT_TRUE(IsAbsolute("c:\\foo"));
+  ASSERT_TRUE(IsAbsolute("\\\\?\\c:\\"));
+  ASSERT_TRUE(IsAbsolute("\\\\?\\c:\\foo"));
+}
+
+TEST(PathWindowsTest, TestIsRootDirectory) {
+  ASSERT_FALSE(IsRootDirectory(""));
+  ASSERT_TRUE(IsRootDirectory("/"));
+  ASSERT_FALSE(IsRootDirectory("/foo"));
+  ASSERT_TRUE(IsRootDirectory("\\"));
+  ASSERT_FALSE(IsRootDirectory("\\foo"));
+  ASSERT_FALSE(IsRootDirectory("c:"));
+  ASSERT_TRUE(IsRootDirectory("c:/"));
+  ASSERT_TRUE(IsRootDirectory("c:\\"));
+  ASSERT_FALSE(IsRootDirectory("c:\\foo"));
+  ASSERT_TRUE(IsRootDirectory("\\\\?\\c:\\"));
+  ASSERT_FALSE(IsRootDirectory("\\\\?\\c:\\foo"));
+}
+
+TEST(PathWindowsTest, TestAsWindowsPath) {
+  SetEnvironmentVariableA("BAZEL_SH", "c:\\some\\long/path\\bin\\bash.exe");
+  wstring actual;
+
+  // Null and empty input produces empty result.
+  ASSERT_TRUE(AsWindowsPath("", &actual, nullptr));
+  ASSERT_EQ(wstring(L""), actual);
+
+  // If the path has a "\\?\" prefix, AsWindowsPath assumes it's a correct
+  // Windows path. If it's not, the Windows API function that we pass the path
+  // to will fail anyway.
+  ASSERT_TRUE(AsWindowsPath("\\\\?\\anything/..", &actual, nullptr));
+  ASSERT_EQ(wstring(L"\\\\?\\anything/.."), actual);
+
+  // Trailing slash or backslash is removed.
+  ASSERT_TRUE(AsWindowsPath("foo/", &actual, nullptr));
+  ASSERT_EQ(wstring(L"foo"), actual);
+  ASSERT_TRUE(AsWindowsPath("foo\\", &actual, nullptr));
+  ASSERT_EQ(wstring(L"foo"), actual);
+
+  // Slashes are converted to backslash.
+  ASSERT_TRUE(AsWindowsPath("foo/bar", &actual, nullptr));
+  ASSERT_EQ(wstring(L"foo\\bar"), actual);
+  ASSERT_TRUE(AsWindowsPath("c:/", &actual, nullptr));
+  ASSERT_EQ(wstring(L"c:\\"), actual);
+  ASSERT_TRUE(AsWindowsPath("c:\\", &actual, nullptr));
+  ASSERT_EQ(wstring(L"c:\\"), actual);
+
+  // Invalid paths
+  string error;
+  ASSERT_FALSE(AsWindowsPath("c:", &actual, &error));
+  EXPECT_TRUE(error.find("working-directory relative paths") != string::npos);
+  ASSERT_FALSE(AsWindowsPath("c:foo", &actual, &error));
+  EXPECT_TRUE(error.find("working-directory relative paths") != string::npos);
+  ASSERT_FALSE(AsWindowsPath("\\\\foo", &actual, &error));
+  EXPECT_TRUE(error.find("network paths") != string::npos);
+
+  // /dev/null and NUL produce NUL.
+  ASSERT_TRUE(AsWindowsPath("/dev/null", &actual, nullptr));
+  ASSERT_EQ(wstring(L"NUL"), actual);
+  ASSERT_TRUE(AsWindowsPath("Nul", &actual, nullptr));
+  ASSERT_EQ(wstring(L"NUL"), actual);
+
+  // MSYS path with drive letter.
+  ASSERT_FALSE(AsWindowsPath("/c", &actual, &error));
+  EXPECT_TRUE(error.find("Unix-style") != string::npos);
+  ASSERT_FALSE(AsWindowsPath("/c/", &actual, &error));
+  EXPECT_TRUE(error.find("Unix-style") != string::npos);
+
+  // Absolute-on-current-drive path gets a drive letter.
+  ASSERT_TRUE(AsWindowsPath("\\foo", &actual, nullptr));
+  ASSERT_EQ(wstring(1, GetCwd()[0]) + L":\\foo", actual);
+
+  // Even for long paths, AsWindowsPath doesn't add a "\\?\" prefix (it's the
+  // caller's duty to do so).
+  wstring wlongpath(L"dummy_long_path\\");
+  string longpath("dummy_long_path/");
+  while (longpath.size() <= MAX_PATH) {
+    wlongpath += wlongpath;
+    longpath += longpath;
+  }
+  wlongpath.pop_back();  // remove trailing "\"
+  ASSERT_TRUE(AsWindowsPath(longpath, &actual, nullptr));
+  ASSERT_EQ(wlongpath, actual);
+}
+
+TEST(PathWindowsTest, TestAsAbsoluteWindowsPath) {
+  SetEnvironmentVariableA("BAZEL_SH", "c:\\some\\long/path\\bin\\bash.exe");
+  wstring actual;
+
+  ASSERT_TRUE(AsAbsoluteWindowsPath("c:/", &actual, nullptr));
+  ASSERT_EQ(L"\\\\?\\c:\\", actual);
+
+  ASSERT_TRUE(AsAbsoluteWindowsPath("c:/..\\non-existent//", &actual, nullptr));
+  ASSERT_EQ(L"\\\\?\\c:\\non-existent", actual);
+
+  WCHAR cwd[MAX_PATH];
+  wstring cwdw(CstringToWstring(GetCwd().c_str()).get());
+  wstring expected =
+      wstring(L"\\\\?\\") + cwdw +
+      ((cwdw.back() == L'\\') ? L"non-existent" : L"\\non-existent");
+  ASSERT_TRUE(AsAbsoluteWindowsPath("non-existent", &actual, nullptr));
+  ASSERT_EQ(actual, expected);
+}
+
+TEST(PathWindowsTest, TestAsShortWindowsPath) {
+  string actual;
+  ASSERT_TRUE(AsShortWindowsPath("/dev/null", &actual, nullptr));
+  ASSERT_EQ(string("NUL"), actual);
+
+  ASSERT_TRUE(AsShortWindowsPath("nul", &actual, nullptr));
+  ASSERT_EQ(string("NUL"), actual);
+
+  ASSERT_TRUE(AsShortWindowsPath("C://", &actual, nullptr));
+  ASSERT_EQ(string("c:\\"), actual);
+
+  string error;
+  ASSERT_FALSE(AsShortWindowsPath("/C//", &actual, &error));
+  EXPECT_TRUE(error.find("Unix-style") != string::npos);
+
+  // The A drive usually doesn't exist but AsShortWindowsPath should still work.
+  // Here we even have multiple trailing slashes, that should be handled too.
+  ASSERT_TRUE(AsShortWindowsPath("A://", &actual, nullptr));
+  ASSERT_EQ(string("a:\\"), actual);
+
+  // Assert that we can shorten the TEST_TMPDIR.
+  char buf[MAX_PATH] = {0};
+  DWORD len = ::GetEnvironmentVariableA("TEST_TMPDIR", buf, MAX_PATH);
+  string tmpdir = buf;
+  ASSERT_GT(tmpdir.size(), 0);
+  string short_tmpdir;
+  ASSERT_TRUE(AsShortWindowsPath(tmpdir, &short_tmpdir, nullptr));
+  ASSERT_LT(0, short_tmpdir.size());
+  ASSERT_TRUE(PathExists(short_tmpdir));
+
+  // Assert that a trailing "/" doesn't change the shortening logic and it will
+  // be stripped from the result.
+  ASSERT_TRUE(AsShortWindowsPath(tmpdir + "/", &actual, nullptr));
+  ASSERT_EQ(actual, short_tmpdir);
+  ASSERT_NE(actual.back(), '/');
+  ASSERT_NE(actual.back(), '\\');
+
+  // Assert shortening another long path, and that the result is lowercased.
+  string dirname(JoinPath(short_tmpdir, "LONGpathNAME"));
+  ASSERT_EQ(0, mkdir(dirname.c_str()));
+  ASSERT_TRUE(PathExists(dirname));
+  ASSERT_TRUE(AsShortWindowsPath(dirname, &actual, nullptr));
+  ASSERT_EQ(short_tmpdir + "\\longpa~1", actual);
+
+  // Assert shortening non-existent paths.
+  ASSERT_TRUE(AsShortWindowsPath(JoinPath(tmpdir, "NonExistent/FOO"), &actual,
+                                 nullptr));
+  ASSERT_EQ(short_tmpdir + "\\nonexistent\\foo", actual);
+}
+
+TEST(PathWindowsTest, TestMsysRootRetrieval) {
+  wstring actual;
+
+  // We just need "bin/<something>" or "usr/bin/<something>".
+  // Forward slashes are converted to backslashes.
+  SetEnvironmentVariableA("BAZEL_SH", "c:/foo\\bin/some_bash.exe");
+
+  string error;
+  ASSERT_FALSE(AsWindowsPath("/blah", &actual, &error));
+  EXPECT_TRUE(error.find("Unix-style") != string::npos);
+
+  SetEnvironmentVariableA("BAZEL_SH", "c:/tools/msys64/usr/bin/bash.exe");
+  ASSERT_FALSE(AsWindowsPath("/blah", &actual, &error));
+  EXPECT_TRUE(error.find("Unix-style") != string::npos);
+}
+
+TEST(PathWindowsTest, IsWindowsDevNullTest) {
+  ASSERT_TRUE(IsDevNull("nul"));
+  ASSERT_TRUE(IsDevNull("NUL"));
+  ASSERT_TRUE(IsDevNull("nuL"));
+  ASSERT_TRUE(IsDevNull("/dev/null"));
+  ASSERT_FALSE(IsDevNull("/Dev/Null"));
+  ASSERT_FALSE(IsDevNull("dev/null"));
+  ASSERT_FALSE(IsDevNull("/dev/nul"));
+  ASSERT_FALSE(IsDevNull("/dev/nulll"));
+  ASSERT_FALSE(IsDevNull("nu"));
+  ASSERT_FALSE(IsDevNull(NULL));
+  ASSERT_FALSE(IsDevNull(""));
+}
+
+TEST(PathWindowsTest, ConvertPathTest) {
+  EXPECT_EQ("c:\\foo", ConvertPath("C:\\FOO"));
+  EXPECT_EQ("c:\\", ConvertPath("c:/"));
+  EXPECT_EQ("c:\\foo\\bar", ConvertPath("c:/../foo\\BAR\\.\\"));
+  EXPECT_EQ("nul", MakeAbsolute("NUL"));
+  EXPECT_EQ("nul", MakeAbsolute("nul"));
+  EXPECT_EQ("nul", MakeAbsolute("/dev/null"));
+}
+
+TEST(PathWindowsTest, TestMakeAbsolute) {
+  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:\\foo\\BAR"));
+  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:/foo/bar"));
+  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:\\foo\\bar\\"));
+  EXPECT_EQ("c:\\foo\\bar", MakeAbsolute("C:/foo/bar/"));
+  EXPECT_EQ(blaze_util::AsLower(blaze_util::GetCwd()) + "\\foo",
+            MakeAbsolute("foo"));
+  EXPECT_EQ("nul", MakeAbsolute("NUL"));
+  EXPECT_EQ("nul", MakeAbsolute("Nul"));
+  EXPECT_EQ("nul", MakeAbsolute("nul"));
+  EXPECT_EQ(blaze_util::AsLower(blaze_util::GetCwd()), MakeAbsolute(""));
+  EXPECT_EQ("nul", MakeAbsolute("/dev/null"));
+}
+
+}  // namespace blaze_util
diff --git a/src/test/cpp/workspace_layout_test.cc b/src/test/cpp/workspace_layout_test.cc
index 8ac4fcb..4e17d64 100644
--- a/src/test/cpp/workspace_layout_test.cc
+++ b/src/test/cpp/workspace_layout_test.cc
@@ -20,6 +20,7 @@
 
 #include "src/main/cpp/blaze_util_platform.h"
 #include "src/main/cpp/util/file.h"
+#include "src/main/cpp/util/path.h"
 #include "googletest/include/gtest/gtest.h"
 
 namespace blaze {