Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 1 | // Protocol Buffers - Google's data interchange format |
| 2 | // Copyright 2008 Google Inc. All rights reserved. |
| 3 | // https://developers.google.com/protocol-buffers/ |
| 4 | // |
| 5 | // Redistribution and use in source and binary forms, with or without |
| 6 | // modification, are permitted provided that the following conditions are |
| 7 | // met: |
| 8 | // |
| 9 | // * Redistributions of source code must retain the above copyright |
| 10 | // notice, this list of conditions and the following disclaimer. |
| 11 | // * Redistributions in binary form must reproduce the above |
| 12 | // copyright notice, this list of conditions and the following disclaimer |
| 13 | // in the documentation and/or other materials provided with the |
| 14 | // distribution. |
| 15 | // * Neither the name of Google Inc. nor the names of its |
| 16 | // contributors may be used to endorse or promote products derived from |
| 17 | // this software without specific prior written permission. |
| 18 | // |
| 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 30 | |
| 31 | // Author: laszlocsomor@google.com (Laszlo Csomor) |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 32 | // Based on original Protocol Buffers design by |
| 33 | // Sanjay Ghemawat, Jeff Dean, and others. |
| 34 | |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 35 | // Unit tests for long-path-aware open/mkdir/access/etc. on Windows, as well as |
| 36 | // for the supporting utility functions. |
| 37 | // |
| 38 | // This file is only used on Windows, it's empty on other platforms. |
| 39 | |
| 40 | #if defined(_WIN32) |
| 41 | |
| 42 | #define WIN32_LEAN_AND_MEAN |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 43 | #include <google/protobuf/io/io_win32.h> |
| 44 | |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 45 | #include <errno.h> |
| 46 | #include <fcntl.h> |
| 47 | #include <stdlib.h> |
| 48 | #include <string.h> |
| 49 | #include <sys/stat.h> |
| 50 | #include <sys/types.h> |
| 51 | #include <wchar.h> |
| 52 | #include <windows.h> |
| 53 | |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 54 | #include <memory> |
| 55 | #include <sstream> |
| 56 | #include <string> |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 57 | #include <vector> |
| 58 | |
| 59 | #include <gtest/gtest.h> |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 60 | |
| 61 | namespace google { |
| 62 | namespace protobuf { |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 63 | namespace io { |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 64 | namespace win32 { |
| 65 | namespace { |
| 66 | |
| 67 | const char kUtf8Text[] = { |
| 68 | 'h', 'i', ' ', |
| 69 | // utf-8: 11010000 10011111, utf-16: 100 0001 1111 = 0x041F |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 70 | static_cast<char>(0xd0), static_cast<char>(0x9f), |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 71 | // utf-8: 11010001 10000000, utf-16: 100 0100 0000 = 0x0440 |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 72 | static_cast<char>(0xd1), static_cast<char>(0x80), |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 73 | // utf-8: 11010000 10111000, utf-16: 100 0011 1000 = 0x0438 |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 74 | static_cast<char>(0xd0), static_cast<char>(0xb8), |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 75 | // utf-8: 11010000 10110010, utf-16: 100 0011 0010 = 0x0432 |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 76 | static_cast<char>(0xd0), static_cast<char>(0xb2), |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 77 | // utf-8: 11010000 10110101, utf-16: 100 0011 0101 = 0x0435 |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 78 | static_cast<char>(0xd0), static_cast<char>(0xb5), |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 79 | // utf-8: 11010001 10000010, utf-16: 100 0100 0010 = 0x0442 |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 80 | static_cast<char>(0xd1), static_cast<char>(0x82), 0 |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 81 | }; |
| 82 | |
| 83 | const wchar_t kUtf16Text[] = { |
| 84 | L'h', L'i', L' ', |
| 85 | L'\x41f', L'\x440', L'\x438', L'\x432', L'\x435', L'\x442', 0 |
| 86 | }; |
| 87 | |
| 88 | using std::string; |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 89 | using std::vector; |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 90 | using std::wstring; |
| 91 | |
| 92 | class IoWin32Test : public ::testing::Test { |
| 93 | public: |
| 94 | void SetUp(); |
| 95 | void TearDown(); |
| 96 | |
| 97 | protected: |
| 98 | bool CreateAllUnder(wstring path); |
| 99 | bool DeleteAllUnder(wstring path); |
| 100 | |
| 101 | WCHAR working_directory[MAX_PATH]; |
| 102 | string test_tmpdir; |
| 103 | wstring wtest_tmpdir; |
| 104 | }; |
| 105 | |
| 106 | #define ASSERT_INITIALIZED \ |
| 107 | { \ |
| 108 | EXPECT_FALSE(test_tmpdir.empty()); \ |
| 109 | EXPECT_FALSE(wtest_tmpdir.empty()); \ |
| 110 | } |
| 111 | |
| 112 | namespace { |
| 113 | void StripTrailingSlashes(string* str) { |
| 114 | int i = str->size() - 1; |
| 115 | for (; i >= 0 && ((*str)[i] == '/' || (*str)[i] == '\\'); --i) {} |
| 116 | str->resize(i+1); |
| 117 | } |
| 118 | |
| 119 | bool GetEnvVarAsUtf8(const WCHAR* name, string* result) { |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 120 | DWORD size = ::GetEnvironmentVariableW(name, nullptr, 0); |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 121 | if (size > 0 && GetLastError() != ERROR_ENVVAR_NOT_FOUND) { |
| 122 | std::unique_ptr<WCHAR[]> wcs(new WCHAR[size]); |
| 123 | ::GetEnvironmentVariableW(name, wcs.get(), size); |
| 124 | // GetEnvironmentVariableA retrieves an Active-Code-Page-encoded text which |
| 125 | // we'd first need to convert to UTF-16 then to UTF-8, because there seems |
| 126 | // to be no API function to do that conversion directly. |
| 127 | // GetEnvironmentVariableW retrieves an UTF-16-encoded text, which we need |
| 128 | // to convert to UTF-8. |
| 129 | return strings::wcs_to_utf8(wcs.get(), result); |
| 130 | } else { |
| 131 | return false; |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | bool GetCwdAsUtf8(string* result) { |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 136 | DWORD size = ::GetCurrentDirectoryW(0, nullptr); |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 137 | if (size > 0) { |
| 138 | std::unique_ptr<WCHAR[]> wcs(new WCHAR[size]); |
| 139 | ::GetCurrentDirectoryW(size, wcs.get()); |
| 140 | // GetCurrentDirectoryA retrieves an Active-Code-Page-encoded text which |
| 141 | // we'd first need to convert to UTF-16 then to UTF-8, because there seems |
| 142 | // to be no API function to do that conversion directly. |
| 143 | // GetCurrentDirectoryW retrieves an UTF-16-encoded text, which we need |
| 144 | // to convert to UTF-8. |
| 145 | return strings::wcs_to_utf8(wcs.get(), result); |
| 146 | } else { |
| 147 | return false; |
| 148 | } |
| 149 | } |
| 150 | |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 151 | bool CreateEmptyFile(const wstring& path) { |
| 152 | HANDLE h = CreateFileW(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, |
| 153 | FILE_ATTRIBUTE_NORMAL, NULL); |
| 154 | if (h == INVALID_HANDLE_VALUE) { |
| 155 | return false; |
| 156 | } |
| 157 | CloseHandle(h); |
| 158 | return true; |
| 159 | } |
| 160 | |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 161 | } // namespace |
| 162 | |
| 163 | void IoWin32Test::SetUp() { |
| 164 | test_tmpdir.clear(); |
| 165 | wtest_tmpdir.clear(); |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 166 | DWORD size = ::GetCurrentDirectoryW(MAX_PATH, working_directory); |
| 167 | EXPECT_GT(size, 0U); |
| 168 | EXPECT_LT(size, static_cast<DWORD>(MAX_PATH)); |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 169 | |
| 170 | string tmp; |
| 171 | bool ok = false; |
| 172 | if (!ok) { |
| 173 | // Bazel sets this environment variable when it runs tests. |
| 174 | ok = GetEnvVarAsUtf8(L"TEST_TMPDIR", &tmp); |
| 175 | } |
| 176 | if (!ok) { |
| 177 | // Bazel 0.8.0 sets this environment for every build and test action. |
| 178 | ok = GetEnvVarAsUtf8(L"TEMP", &tmp); |
| 179 | } |
| 180 | if (!ok) { |
| 181 | // Bazel 0.8.0 sets this environment for every build and test action. |
| 182 | ok = GetEnvVarAsUtf8(L"TMP", &tmp); |
| 183 | } |
| 184 | if (!ok) { |
| 185 | // Fall back to using the current directory. |
| 186 | ok = GetCwdAsUtf8(&tmp); |
| 187 | } |
| 188 | if (!ok || tmp.empty()) { |
| 189 | FAIL() << "Cannot find a temp directory."; |
| 190 | } |
| 191 | |
| 192 | StripTrailingSlashes(&tmp); |
| 193 | std::stringstream result; |
| 194 | // Deleting files and directories is asynchronous on Windows, and if TearDown |
| 195 | // just deleted the previous temp directory, sometimes we cannot recreate the |
| 196 | // same directory. |
| 197 | // Use a counter so every test method gets its own temp directory. |
| 198 | static unsigned int counter = 0; |
| 199 | result << tmp << "\\w32tst" << counter++ << ".tmp"; |
| 200 | test_tmpdir = result.str(); |
| 201 | wtest_tmpdir = testonly_utf8_to_winpath(test_tmpdir.c_str()); |
| 202 | ASSERT_FALSE(wtest_tmpdir.empty()); |
| 203 | ASSERT_TRUE(DeleteAllUnder(wtest_tmpdir)); |
| 204 | ASSERT_TRUE(CreateAllUnder(wtest_tmpdir)); |
| 205 | } |
| 206 | |
| 207 | void IoWin32Test::TearDown() { |
| 208 | if (!wtest_tmpdir.empty()) { |
| 209 | DeleteAllUnder(wtest_tmpdir); |
| 210 | } |
| 211 | ::SetCurrentDirectoryW(working_directory); |
| 212 | } |
| 213 | |
| 214 | bool IoWin32Test::CreateAllUnder(wstring path) { |
| 215 | // Prepend UNC prefix if the path doesn't have it already. Don't bother |
| 216 | // checking if the path is shorter than MAX_PATH, let's just do it |
| 217 | // unconditionally. |
| 218 | if (path.find(L"\\\\?\\") != 0) { |
| 219 | path = wstring(L"\\\\?\\") + path; |
| 220 | } |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 221 | if (::CreateDirectoryW(path.c_str(), nullptr) || |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 222 | GetLastError() == ERROR_ALREADY_EXISTS || |
| 223 | GetLastError() == ERROR_ACCESS_DENIED) { |
| 224 | return true; |
| 225 | } |
| 226 | if (GetLastError() == ERROR_PATH_NOT_FOUND) { |
| 227 | size_t pos = path.find_last_of(L'\\'); |
| 228 | if (pos != wstring::npos) { |
| 229 | wstring parent(path, 0, pos); |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 230 | if (CreateAllUnder(parent) && CreateDirectoryW(path.c_str(), nullptr)) { |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 231 | return true; |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | return false; |
| 236 | } |
| 237 | |
| 238 | bool IoWin32Test::DeleteAllUnder(wstring path) { |
| 239 | static const wstring kDot(L"."); |
| 240 | static const wstring kDotDot(L".."); |
| 241 | |
| 242 | // Prepend UNC prefix if the path doesn't have it already. Don't bother |
| 243 | // checking if the path is shorter than MAX_PATH, let's just do it |
| 244 | // unconditionally. |
| 245 | if (path.find(L"\\\\?\\") != 0) { |
| 246 | path = wstring(L"\\\\?\\") + path; |
| 247 | } |
| 248 | // Append "\" if necessary. |
| 249 | if (path[path.size() - 1] != L'\\') { |
| 250 | path.push_back(L'\\'); |
| 251 | } |
| 252 | |
| 253 | WIN32_FIND_DATAW metadata; |
| 254 | HANDLE handle = ::FindFirstFileW((path + L"*").c_str(), &metadata); |
| 255 | if (handle == INVALID_HANDLE_VALUE) { |
| 256 | return true; // directory doesn't exist |
| 257 | } |
| 258 | |
| 259 | bool result = true; |
| 260 | do { |
| 261 | wstring childname = metadata.cFileName; |
| 262 | if (kDot != childname && kDotDot != childname) { |
| 263 | wstring childpath = path + childname; |
| 264 | if ((metadata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { |
| 265 | // If this is not a junction, delete its contents recursively. |
| 266 | // Finally delete this directory/junction too. |
| 267 | if (((metadata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0 && |
| 268 | !DeleteAllUnder(childpath)) || |
| 269 | !::RemoveDirectoryW(childpath.c_str())) { |
| 270 | result = false; |
| 271 | break; |
| 272 | } |
| 273 | } else { |
| 274 | if (!::DeleteFileW(childpath.c_str())) { |
| 275 | result = false; |
| 276 | break; |
| 277 | } |
| 278 | } |
| 279 | } |
| 280 | } while (::FindNextFileW(handle, &metadata)); |
| 281 | ::FindClose(handle); |
| 282 | return result; |
| 283 | } |
| 284 | |
| 285 | TEST_F(IoWin32Test, AccessTest) { |
| 286 | ASSERT_INITIALIZED; |
| 287 | |
| 288 | string path = test_tmpdir; |
| 289 | while (path.size() < MAX_PATH - 30) { |
| 290 | path += "\\accesstest"; |
| 291 | EXPECT_EQ(mkdir(path.c_str(), 0644), 0); |
| 292 | } |
| 293 | string file = path + "\\file.txt"; |
| 294 | int fd = open(file.c_str(), O_CREAT | O_WRONLY, 0644); |
| 295 | if (fd > 0) { |
| 296 | EXPECT_EQ(close(fd), 0); |
| 297 | } else { |
| 298 | EXPECT_TRUE(false); |
| 299 | } |
| 300 | |
| 301 | EXPECT_EQ(access(test_tmpdir.c_str(), F_OK), 0); |
| 302 | EXPECT_EQ(access(path.c_str(), F_OK), 0); |
| 303 | EXPECT_EQ(access(path.c_str(), W_OK), 0); |
| 304 | EXPECT_EQ(access(file.c_str(), F_OK | W_OK), 0); |
| 305 | EXPECT_NE(access((file + ".blah").c_str(), F_OK), 0); |
| 306 | EXPECT_NE(access((file + ".blah").c_str(), W_OK), 0); |
| 307 | |
| 308 | EXPECT_EQ(access(".", F_OK), 0); |
| 309 | EXPECT_EQ(access(".", W_OK), 0); |
| 310 | EXPECT_EQ(access((test_tmpdir + "/accesstest").c_str(), F_OK | W_OK), 0); |
| 311 | ASSERT_EQ(access((test_tmpdir + "/./normalize_me/.././accesstest").c_str(), |
| 312 | F_OK | W_OK), |
| 313 | 0); |
| 314 | EXPECT_NE(access("io_win32_unittest.AccessTest.nonexistent", F_OK), 0); |
| 315 | EXPECT_NE(access("io_win32_unittest.AccessTest.nonexistent", W_OK), 0); |
| 316 | |
| 317 | ASSERT_EQ(access("c:bad", F_OK), -1); |
| 318 | ASSERT_EQ(errno, ENOENT); |
| 319 | ASSERT_EQ(access("/tmp/bad", F_OK), -1); |
| 320 | ASSERT_EQ(errno, ENOENT); |
| 321 | ASSERT_EQ(access("\\bad", F_OK), -1); |
| 322 | ASSERT_EQ(errno, ENOENT); |
| 323 | } |
| 324 | |
| 325 | TEST_F(IoWin32Test, OpenTest) { |
| 326 | ASSERT_INITIALIZED; |
| 327 | |
| 328 | string path = test_tmpdir; |
| 329 | while (path.size() < MAX_PATH) { |
| 330 | path += "\\opentest"; |
| 331 | EXPECT_EQ(mkdir(path.c_str(), 0644), 0); |
| 332 | } |
| 333 | string file = path + "\\file.txt"; |
| 334 | int fd = open(file.c_str(), O_CREAT | O_WRONLY, 0644); |
| 335 | if (fd > 0) { |
| 336 | EXPECT_EQ(write(fd, "hello", 5), 5); |
| 337 | EXPECT_EQ(close(fd), 0); |
| 338 | } else { |
| 339 | EXPECT_TRUE(false); |
| 340 | } |
| 341 | |
| 342 | ASSERT_EQ(open("c:bad.txt", O_CREAT | O_WRONLY, 0644), -1); |
| 343 | ASSERT_EQ(errno, ENOENT); |
| 344 | ASSERT_EQ(open("/tmp/bad.txt", O_CREAT | O_WRONLY, 0644), -1); |
| 345 | ASSERT_EQ(errno, ENOENT); |
| 346 | ASSERT_EQ(open("\\bad.txt", O_CREAT | O_WRONLY, 0644), -1); |
| 347 | ASSERT_EQ(errno, ENOENT); |
| 348 | } |
| 349 | |
| 350 | TEST_F(IoWin32Test, MkdirTest) { |
| 351 | ASSERT_INITIALIZED; |
| 352 | |
| 353 | string path = test_tmpdir; |
| 354 | do { |
| 355 | path += "\\mkdirtest"; |
| 356 | ASSERT_EQ(mkdir(path.c_str(), 0644), 0); |
| 357 | } while (path.size() <= MAX_PATH); |
| 358 | |
| 359 | ASSERT_EQ(mkdir("c:bad", 0644), -1); |
| 360 | ASSERT_EQ(errno, ENOENT); |
| 361 | ASSERT_EQ(mkdir("/tmp/bad", 0644), -1); |
| 362 | ASSERT_EQ(errno, ENOENT); |
| 363 | ASSERT_EQ(mkdir("\\bad", 0644), -1); |
| 364 | ASSERT_EQ(errno, ENOENT); |
| 365 | } |
| 366 | |
| 367 | TEST_F(IoWin32Test, MkdirTestNonAscii) { |
| 368 | ASSERT_INITIALIZED; |
| 369 | |
| 370 | // Create a non-ASCII path. |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 371 | // Ensure that we can create the directory using CreateDirectoryW. |
| 372 | EXPECT_TRUE(CreateDirectoryW((wtest_tmpdir + L"\\1").c_str(), nullptr)); |
| 373 | EXPECT_TRUE(CreateDirectoryW((wtest_tmpdir + L"\\1\\" + kUtf16Text).c_str(), nullptr)); |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 374 | // Ensure that we can create a very similarly named directory using mkdir. |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 375 | // We don't attempt to delete and recreate the same directory, because on |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 376 | // Windows, deleting files and directories seems to be asynchronous. |
| 377 | EXPECT_EQ(mkdir((test_tmpdir + "\\2").c_str(), 0644), 0); |
| 378 | EXPECT_EQ(mkdir((test_tmpdir + "\\2\\" + kUtf8Text).c_str(), 0644), 0); |
| 379 | } |
| 380 | |
| 381 | TEST_F(IoWin32Test, ChdirTest) { |
| 382 | string path("C:\\"); |
| 383 | EXPECT_EQ(access(path.c_str(), F_OK), 0); |
| 384 | ASSERT_EQ(chdir(path.c_str()), 0); |
| 385 | |
| 386 | // Do not try to chdir into the test_tmpdir, it may already contain directory |
| 387 | // names with trailing dots. |
| 388 | // Instead test here with an obviously dot-trailed path. If the win32_chdir |
| 389 | // function would not convert the path to absolute and prefix with "\\?\" then |
| 390 | // the Win32 API would ignore the trailing dot, but because of the prefixing |
| 391 | // there'll be no path processing done, so we'll actually attempt to chdir |
| 392 | // into "C:\some\path\foo." |
| 393 | path = test_tmpdir + "/foo."; |
| 394 | EXPECT_EQ(mkdir(path.c_str(), 644), 0); |
| 395 | EXPECT_EQ(access(path.c_str(), F_OK), 0); |
| 396 | ASSERT_NE(chdir(path.c_str()), 0); |
| 397 | } |
| 398 | |
| 399 | TEST_F(IoWin32Test, ChdirTestNonAscii) { |
| 400 | ASSERT_INITIALIZED; |
| 401 | |
| 402 | // Create a directory with a non-ASCII path and ensure we can cd into it. |
| 403 | wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text); |
| 404 | string nonAscii; |
| 405 | EXPECT_TRUE(strings::wcs_to_utf8(wNonAscii.c_str(), &nonAscii)); |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 406 | EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr)); |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 407 | WCHAR cwd[MAX_PATH]; |
| 408 | EXPECT_TRUE(GetCurrentDirectoryW(MAX_PATH, cwd)); |
| 409 | // Ensure that we can cd into the path using SetCurrentDirectoryW. |
| 410 | EXPECT_TRUE(SetCurrentDirectoryW(wNonAscii.c_str())); |
| 411 | EXPECT_TRUE(SetCurrentDirectoryW(cwd)); |
| 412 | // Ensure that we can cd into the path using chdir. |
| 413 | ASSERT_EQ(chdir(nonAscii.c_str()), 0); |
| 414 | // Ensure that the GetCurrentDirectoryW returns the desired path. |
| 415 | EXPECT_TRUE(GetCurrentDirectoryW(MAX_PATH, cwd)); |
| 416 | ASSERT_EQ(wNonAscii, cwd); |
| 417 | } |
| 418 | |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 419 | TEST_F(IoWin32Test, ExpandWildcardsInRelativePathTest) { |
| 420 | wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text); |
| 421 | EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr)); |
| 422 | // Create mock files we will test pattern matching on. |
| 423 | EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto")); |
| 424 | EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_b.proto")); |
| 425 | EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\bar.proto")); |
| 426 | // `cd` into `wtest_tmpdir`. |
| 427 | EXPECT_TRUE(SetCurrentDirectoryW(wtest_tmpdir.c_str())); |
| 428 | |
| 429 | int found_a = 0; |
| 430 | int found_b = 0; |
| 431 | vector<string> found_bad; |
| 432 | // Assert matching a relative path pattern. Results should also be relative. |
| 433 | ExpandWildcardsResult result = |
| 434 | ExpandWildcards(string(kUtf8Text) + "\\foo*.proto", |
| 435 | [&found_a, &found_b, &found_bad](const string& p) { |
| 436 | if (p == string(kUtf8Text) + "\\foo_a.proto") { |
| 437 | found_a++; |
| 438 | } else if (p == string(kUtf8Text) + "\\foo_b.proto") { |
| 439 | found_b++; |
| 440 | } else { |
| 441 | found_bad.push_back(p); |
| 442 | } |
| 443 | }); |
| 444 | EXPECT_EQ(result, ExpandWildcardsResult::kSuccess); |
| 445 | EXPECT_EQ(found_a, 1); |
| 446 | EXPECT_EQ(found_b, 1); |
| 447 | if (!found_bad.empty()) { |
| 448 | FAIL() << found_bad[0]; |
| 449 | } |
| 450 | |
| 451 | // Assert matching the exact filename. |
| 452 | found_a = 0; |
| 453 | found_bad.clear(); |
| 454 | result = ExpandWildcards(string(kUtf8Text) + "\\foo_a.proto", |
| 455 | [&found_a, &found_bad](const string& p) { |
| 456 | if (p == string(kUtf8Text) + "\\foo_a.proto") { |
| 457 | found_a++; |
| 458 | } else { |
| 459 | found_bad.push_back(p); |
| 460 | } |
| 461 | }); |
| 462 | EXPECT_EQ(result, ExpandWildcardsResult::kSuccess); |
| 463 | EXPECT_EQ(found_a, 1); |
| 464 | if (!found_bad.empty()) { |
| 465 | FAIL() << found_bad[0]; |
| 466 | } |
| 467 | } |
| 468 | |
| 469 | TEST_F(IoWin32Test, ExpandWildcardsInAbsolutePathTest) { |
| 470 | wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text); |
| 471 | EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr)); |
| 472 | // Create mock files we will test pattern matching on. |
| 473 | EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto")); |
| 474 | EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_b.proto")); |
| 475 | EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\bar.proto")); |
| 476 | |
| 477 | int found_a = 0; |
| 478 | int found_b = 0; |
| 479 | vector<string> found_bad; |
| 480 | // Assert matching an absolute path. The results should also use absolute |
| 481 | // path. |
| 482 | ExpandWildcardsResult result = |
| 483 | ExpandWildcards(string(test_tmpdir) + "\\" + kUtf8Text + "\\foo*.proto", |
| 484 | [this, &found_a, &found_b, &found_bad](const string& p) { |
| 485 | if (p == string(this->test_tmpdir) + "\\" + kUtf8Text + |
| 486 | "\\foo_a.proto") { |
| 487 | found_a++; |
| 488 | } else if (p == string(this->test_tmpdir) + "\\" + |
| 489 | kUtf8Text + "\\foo_b.proto") { |
| 490 | found_b++; |
| 491 | } else { |
| 492 | found_bad.push_back(p); |
| 493 | } |
| 494 | }); |
| 495 | EXPECT_EQ(result, ExpandWildcardsResult::kSuccess); |
| 496 | EXPECT_EQ(found_a, 1); |
| 497 | EXPECT_EQ(found_b, 1); |
| 498 | if (!found_bad.empty()) { |
| 499 | FAIL() << found_bad[0]; |
| 500 | } |
| 501 | |
| 502 | // Assert matching the exact filename. |
| 503 | found_a = 0; |
| 504 | found_bad.clear(); |
| 505 | result = |
| 506 | ExpandWildcards(string(test_tmpdir) + "\\" + kUtf8Text + "\\foo_a.proto", |
| 507 | [this, &found_a, &found_bad](const string& p) { |
| 508 | if (p == string(this->test_tmpdir) + "\\" + kUtf8Text + |
| 509 | "\\foo_a.proto") { |
| 510 | found_a++; |
| 511 | } else { |
| 512 | found_bad.push_back(p); |
| 513 | } |
| 514 | }); |
| 515 | EXPECT_EQ(result, ExpandWildcardsResult::kSuccess); |
| 516 | EXPECT_EQ(found_a, 1); |
| 517 | if (!found_bad.empty()) { |
| 518 | FAIL() << found_bad[0]; |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | TEST_F(IoWin32Test, ExpandWildcardsIgnoresDirectoriesTest) { |
| 523 | wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text); |
| 524 | EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr)); |
| 525 | // Create mock files we will test pattern matching on. |
| 526 | EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto")); |
| 527 | EXPECT_TRUE( |
| 528 | CreateDirectoryW((wNonAscii + L"\\foo_b.proto").c_str(), nullptr)); |
| 529 | EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_c.proto")); |
| 530 | // `cd` into `wtest_tmpdir`. |
| 531 | EXPECT_TRUE(SetCurrentDirectoryW(wtest_tmpdir.c_str())); |
| 532 | |
| 533 | int found_a = 0; |
| 534 | int found_c = 0; |
| 535 | vector<string> found_bad; |
| 536 | // Assert that the pattern matches exactly the expected files, and using the |
| 537 | // absolute path as did the input pattern. |
| 538 | ExpandWildcardsResult result = |
| 539 | ExpandWildcards(string(kUtf8Text) + "\\foo*.proto", |
| 540 | [&found_a, &found_c, &found_bad](const string& p) { |
| 541 | if (p == string(kUtf8Text) + "\\foo_a.proto") { |
| 542 | found_a++; |
| 543 | } else if (p == string(kUtf8Text) + "\\foo_c.proto") { |
| 544 | found_c++; |
| 545 | } else { |
| 546 | found_bad.push_back(p); |
| 547 | } |
| 548 | }); |
| 549 | EXPECT_EQ(result, ExpandWildcardsResult::kSuccess); |
| 550 | EXPECT_EQ(found_a, 1); |
| 551 | EXPECT_EQ(found_c, 1); |
| 552 | if (!found_bad.empty()) { |
| 553 | FAIL() << found_bad[0]; |
| 554 | } |
| 555 | } |
| 556 | |
| 557 | TEST_F(IoWin32Test, ExpandWildcardsFailsIfNoFileMatchesTest) { |
| 558 | wstring wNonAscii(wtest_tmpdir + L"\\" + kUtf16Text); |
| 559 | EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr)); |
| 560 | // Create mock files we will test pattern matching on. |
| 561 | EXPECT_TRUE(CreateEmptyFile(wNonAscii + L"\\foo_a.proto")); |
| 562 | // `cd` into `wtest_tmpdir`. |
| 563 | EXPECT_TRUE(SetCurrentDirectoryW(wtest_tmpdir.c_str())); |
| 564 | |
| 565 | // Control test: should match foo*.proto |
| 566 | ExpandWildcardsResult result = |
| 567 | ExpandWildcards(string(kUtf8Text) + "\\foo*.proto", [](const string&) {}); |
| 568 | EXPECT_EQ(result, ExpandWildcardsResult::kSuccess); |
| 569 | |
| 570 | // Control test: should match foo_a.proto |
| 571 | result = ExpandWildcards(string(kUtf8Text) + "\\foo_a.proto", |
| 572 | [](const string&) {}); |
| 573 | EXPECT_EQ(result, ExpandWildcardsResult::kSuccess); |
| 574 | |
| 575 | // Actual test: should not match anything. |
| 576 | result = |
| 577 | ExpandWildcards(string(kUtf8Text) + "\\bar*.proto", [](const string&) {}); |
| 578 | ASSERT_EQ(result, ExpandWildcardsResult::kErrorNoMatchingFile); |
| 579 | } |
| 580 | |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 581 | TEST_F(IoWin32Test, AsWindowsPathTest) { |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 582 | DWORD size = GetCurrentDirectoryW(0, nullptr); |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 583 | std::unique_ptr<wchar_t[]> cwd_str(new wchar_t[size]); |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 584 | EXPECT_GT(GetCurrentDirectoryW(size, cwd_str.get()), 0U); |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 585 | wstring cwd = wstring(L"\\\\?\\") + cwd_str.get(); |
| 586 | |
| 587 | ASSERT_EQ(testonly_utf8_to_winpath("relative_mkdirtest"), |
| 588 | cwd + L"\\relative_mkdirtest"); |
| 589 | ASSERT_EQ(testonly_utf8_to_winpath("preserve//\\trailing///"), |
| 590 | cwd + L"\\preserve\\trailing\\"); |
| 591 | ASSERT_EQ(testonly_utf8_to_winpath("./normalize_me\\/../blah"), |
| 592 | cwd + L"\\blah"); |
| 593 | std::ostringstream relpath; |
| 594 | for (wchar_t* p = cwd_str.get(); *p; ++p) { |
| 595 | if (*p == '/' || *p == '\\') { |
| 596 | relpath << "../"; |
| 597 | } |
| 598 | } |
| 599 | relpath << ".\\/../\\./beyond-toplevel"; |
| 600 | ASSERT_EQ(testonly_utf8_to_winpath(relpath.str().c_str()), |
| 601 | wstring(L"\\\\?\\") + cwd_str.get()[0] + L":\\beyond-toplevel"); |
| 602 | |
| 603 | // Absolute unix paths lack drive letters, driveless absolute windows paths |
| 604 | // do too. Neither can be converted to a drive-specifying absolute Windows |
| 605 | // path. |
| 606 | ASSERT_EQ(testonly_utf8_to_winpath("/absolute/unix/path"), L""); |
| 607 | // Though valid on Windows, we also don't support UNC paths (\\UNC\\blah). |
| 608 | ASSERT_EQ(testonly_utf8_to_winpath("\\driveless\\absolute"), L""); |
| 609 | // Though valid in cmd.exe, drive-relative paths are not supported. |
| 610 | ASSERT_EQ(testonly_utf8_to_winpath("c:foo"), L""); |
| 611 | ASSERT_EQ(testonly_utf8_to_winpath("c:/foo"), L"\\\\?\\c:\\foo"); |
| 612 | ASSERT_EQ(testonly_utf8_to_winpath("\\\\?\\C:\\foo"), L"\\\\?\\C:\\foo"); |
| 613 | } |
| 614 | |
| 615 | TEST_F(IoWin32Test, Utf8Utf16ConversionTest) { |
| 616 | string mbs; |
| 617 | wstring wcs; |
| 618 | ASSERT_TRUE(strings::utf8_to_wcs(kUtf8Text, &wcs)); |
| 619 | ASSERT_TRUE(strings::wcs_to_utf8(kUtf16Text, &mbs)); |
| 620 | ASSERT_EQ(wcs, kUtf16Text); |
| 621 | ASSERT_EQ(mbs, kUtf8Text); |
| 622 | } |
| 623 | |
| 624 | } // namespace |
| 625 | } // namespace win32 |
Armelle Laine | fbc6810 | 2022-11-06 19:03:46 +0000 | [diff] [blame] | 626 | } // namespace io |
Yu Shan | 1081211 | 2018-09-07 16:45:52 -0700 | [diff] [blame] | 627 | } // namespace protobuf |
| 628 | } // namespace google |
| 629 | |
| 630 | #endif // defined(_WIN32) |
| 631 | |