| // 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/blaze_util_platform.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(_WIN32) && !defined(__CYGWIN__) |
| #error("This test should only be run on Windows") |
| #endif // !defined(_WIN32) && !defined(__CYGWIN__) |
| |
| namespace blaze_util { |
| |
| using std::string; |
| using std::unique_ptr; |
| using std::wstring; |
| |
| TEST(PathWindowsTest, TestNormalizeWindowsPath) { |
| #define ASSERT_NORMALIZE(x, y) \ |
| { \ |
| std::string result; \ |
| EXPECT_TRUE( \ |
| blaze_util::testing::TestOnly_NormalizeWindowsPath(x, &result)); \ |
| EXPECT_EQ(result, y); \ |
| } |
| |
| ASSERT_NORMALIZE("", ""); |
| ASSERT_NORMALIZE("a", "a"); |
| ASSERT_NORMALIZE("foo/bar", "foo\\bar"); |
| ASSERT_NORMALIZE("foo/../bar", "bar"); |
| ASSERT_NORMALIZE("a/", "a"); |
| ASSERT_NORMALIZE("foo", "foo"); |
| ASSERT_NORMALIZE("foo/", "foo"); |
| ASSERT_NORMALIZE(".", "."); |
| ASSERT_NORMALIZE("./", "."); |
| ASSERT_NORMALIZE("..", ".."); |
| ASSERT_NORMALIZE("../", ".."); |
| ASSERT_NORMALIZE("./..", ".."); |
| ASSERT_NORMALIZE("./../", ".."); |
| ASSERT_NORMALIZE("../.", ".."); |
| ASSERT_NORMALIZE(".././", ".."); |
| ASSERT_NORMALIZE("...", "..."); |
| ASSERT_NORMALIZE(".../", "..."); |
| ASSERT_NORMALIZE("a/", "a"); |
| ASSERT_NORMALIZE(".a", ".a"); |
| ASSERT_NORMALIZE("..a", "..a"); |
| ASSERT_NORMALIZE("...a", "...a"); |
| ASSERT_NORMALIZE("./a", "a"); |
| ASSERT_NORMALIZE("././a", "a"); |
| ASSERT_NORMALIZE("./../a", "..\\a"); |
| ASSERT_NORMALIZE(".././a", "..\\a"); |
| ASSERT_NORMALIZE("../../a", "..\\..\\a"); |
| ASSERT_NORMALIZE("../.../a", "..\\...\\a"); |
| ASSERT_NORMALIZE(".../../a", "a"); |
| ASSERT_NORMALIZE("a/..", ""); |
| ASSERT_NORMALIZE("a/../", ""); |
| ASSERT_NORMALIZE("a/./../", ""); |
| |
| ASSERT_NORMALIZE("c:/", "c:\\"); |
| ASSERT_NORMALIZE("c:/a", "c:\\a"); |
| ASSERT_NORMALIZE("c:/foo/bar", "c:\\foo\\bar"); |
| ASSERT_NORMALIZE("c:/foo/../bar", "c:\\bar"); |
| ASSERT_NORMALIZE("d:/a/", "d:\\a"); |
| ASSERT_NORMALIZE("D:/foo", "D:\\foo"); |
| ASSERT_NORMALIZE("c:/foo/", "c:\\foo"); |
| ASSERT_NORMALIZE("c:/.", "c:\\"); |
| ASSERT_NORMALIZE("c:/./", "c:\\"); |
| ASSERT_NORMALIZE("c:/..", "c:\\"); |
| ASSERT_NORMALIZE("c:/../", "c:\\"); |
| ASSERT_NORMALIZE("c:/./..", "c:\\"); |
| ASSERT_NORMALIZE("c:/./../", "c:\\"); |
| ASSERT_NORMALIZE("c:/../.", "c:\\"); |
| ASSERT_NORMALIZE("c:/.././", "c:\\"); |
| ASSERT_NORMALIZE("c:/...", "c:\\..."); |
| ASSERT_NORMALIZE("c:/.../", "c:\\..."); |
| ASSERT_NORMALIZE("c:/.a", "c:\\.a"); |
| ASSERT_NORMALIZE("c:/..a", "c:\\..a"); |
| ASSERT_NORMALIZE("c:/...a", "c:\\...a"); |
| ASSERT_NORMALIZE("c:/./a", "c:\\a"); |
| ASSERT_NORMALIZE("c:/././a", "c:\\a"); |
| ASSERT_NORMALIZE("c:/./../a", "c:\\a"); |
| ASSERT_NORMALIZE("c:/.././a", "c:\\a"); |
| ASSERT_NORMALIZE("c:/../../a", "c:\\a"); |
| ASSERT_NORMALIZE("c:/../.../a", "c:\\...\\a"); |
| ASSERT_NORMALIZE("c:/.../../a", "c:\\a"); |
| ASSERT_NORMALIZE("c:/a/..", "c:\\"); |
| ASSERT_NORMALIZE("c:/a/../", "c:\\"); |
| ASSERT_NORMALIZE("c:/a/./../", "c:\\"); |
| |
| ASSERT_NORMALIZE("foo", "foo"); |
| ASSERT_NORMALIZE("foo/", "foo"); |
| ASSERT_NORMALIZE("foo//bar", "foo\\bar"); |
| ASSERT_NORMALIZE("../..//foo/./bar", "..\\..\\foo\\bar"); |
| ASSERT_NORMALIZE("../foo/baz/../bar", "..\\foo\\bar"); |
| ASSERT_NORMALIZE("c:", "c:\\"); |
| ASSERT_NORMALIZE("c:/", "c:\\"); |
| ASSERT_NORMALIZE("c:\\", "c:\\"); |
| ASSERT_NORMALIZE("c:\\..//foo/./bar/", "c:\\foo\\bar"); |
| ASSERT_NORMALIZE("../foo", "..\\foo"); |
| #undef ASSERT_NORMALIZE |
| } |
| |
| 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(L"c:/", &actual, nullptr)); |
| ASSERT_EQ(L"\\\\?\\c:\\", actual); |
| |
| ASSERT_TRUE(AsAbsoluteWindowsPath("c:/..\\non-existent//", &actual, nullptr)); |
| ASSERT_EQ(L"\\\\?\\c:\\non-existent", actual); |
| |
| ASSERT_TRUE( |
| AsAbsoluteWindowsPath(L"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); |
| |
| ASSERT_TRUE(AsAbsoluteWindowsPath(L"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((char *) nullptr)); |
| 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\\.\\")); |
| } |
| |
| TEST(PathWindowsTest, MakeAbsolute) { |
| 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("nul", MakeAbsolute("/dev/null")); |
| |
| EXPECT_EQ("", MakeAbsolute("")); |
| } |
| |
| TEST(PathWindowsTest, MakeAbsoluteAndResolveWindowsEnvvars_WithTmpdir) { |
| // We cannot test the system-default paths like %ProgramData% because these |
| // are wiped from the test environment. TestTmpdir is set by Bazel though, |
| // so serves as a fine substitute. |
| char buf[MAX_PATH] = {0}; |
| DWORD len = ::GetEnvironmentVariableA("TEST_TMPDIR", buf, MAX_PATH); |
| const std::string tmpdir = buf; |
| const std::string expected_tmpdir_bar = ConvertPath(tmpdir + "\\bar"); |
| |
| EXPECT_EQ(expected_tmpdir_bar, |
| MakeAbsoluteAndResolveWindowsEnvvars("%TEST_TMPDIR%\\bar")); |
| EXPECT_EQ(expected_tmpdir_bar, |
| MakeAbsoluteAndResolveWindowsEnvvars("%Test_Tmpdir%\\bar")); |
| EXPECT_EQ(expected_tmpdir_bar, |
| MakeAbsoluteAndResolveWindowsEnvvars("%test_tmpdir%\\bar")); |
| EXPECT_EQ(expected_tmpdir_bar, |
| MakeAbsoluteAndResolveWindowsEnvvars("%test_tmpdir%/bar")); |
| } |
| |
| TEST(PathWindowsTest, MakeAbsoluteAndResolveWindowsEnvvars_LongPaths) { |
| const std::string long_path = "c:\\" + std::string(MAX_PATH, 'a'); |
| blaze::SetEnv("long", long_path); |
| |
| EXPECT_EQ(long_path, MakeAbsoluteAndResolveWindowsEnvvars("%long%")); |
| } |
| |
| } // namespace blaze_util |