blob: e8f378fcac85b1eb26ddf7e2c18bf02f1bc9bde6 [file] [log] [blame]
Yu Shan10812112018-09-07 16:45:52 -07001// 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 Lainefbc68102022-11-06 19:03:46 +000032// Based on original Protocol Buffers design by
33// Sanjay Ghemawat, Jeff Dean, and others.
34
Yu Shan10812112018-09-07 16:45:52 -070035// 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 Lainefbc68102022-11-06 19:03:46 +000043#include <google/protobuf/io/io_win32.h>
44
Yu Shan10812112018-09-07 16:45:52 -070045#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 Shan10812112018-09-07 16:45:52 -070054#include <memory>
55#include <sstream>
56#include <string>
Armelle Lainefbc68102022-11-06 19:03:46 +000057#include <vector>
58
59#include <gtest/gtest.h>
Yu Shan10812112018-09-07 16:45:52 -070060
61namespace google {
62namespace protobuf {
Armelle Lainefbc68102022-11-06 19:03:46 +000063namespace io {
Yu Shan10812112018-09-07 16:45:52 -070064namespace win32 {
65namespace {
66
67const char kUtf8Text[] = {
68 'h', 'i', ' ',
69 // utf-8: 11010000 10011111, utf-16: 100 0001 1111 = 0x041F
Armelle Lainefbc68102022-11-06 19:03:46 +000070 static_cast<char>(0xd0), static_cast<char>(0x9f),
Yu Shan10812112018-09-07 16:45:52 -070071 // utf-8: 11010001 10000000, utf-16: 100 0100 0000 = 0x0440
Armelle Lainefbc68102022-11-06 19:03:46 +000072 static_cast<char>(0xd1), static_cast<char>(0x80),
Yu Shan10812112018-09-07 16:45:52 -070073 // utf-8: 11010000 10111000, utf-16: 100 0011 1000 = 0x0438
Armelle Lainefbc68102022-11-06 19:03:46 +000074 static_cast<char>(0xd0), static_cast<char>(0xb8),
Yu Shan10812112018-09-07 16:45:52 -070075 // utf-8: 11010000 10110010, utf-16: 100 0011 0010 = 0x0432
Armelle Lainefbc68102022-11-06 19:03:46 +000076 static_cast<char>(0xd0), static_cast<char>(0xb2),
Yu Shan10812112018-09-07 16:45:52 -070077 // utf-8: 11010000 10110101, utf-16: 100 0011 0101 = 0x0435
Armelle Lainefbc68102022-11-06 19:03:46 +000078 static_cast<char>(0xd0), static_cast<char>(0xb5),
Yu Shan10812112018-09-07 16:45:52 -070079 // utf-8: 11010001 10000010, utf-16: 100 0100 0010 = 0x0442
Armelle Lainefbc68102022-11-06 19:03:46 +000080 static_cast<char>(0xd1), static_cast<char>(0x82), 0
Yu Shan10812112018-09-07 16:45:52 -070081};
82
83const 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
88using std::string;
Armelle Lainefbc68102022-11-06 19:03:46 +000089using std::vector;
Yu Shan10812112018-09-07 16:45:52 -070090using std::wstring;
91
92class 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
112namespace {
113void 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
119bool GetEnvVarAsUtf8(const WCHAR* name, string* result) {
Armelle Lainefbc68102022-11-06 19:03:46 +0000120 DWORD size = ::GetEnvironmentVariableW(name, nullptr, 0);
Yu Shan10812112018-09-07 16:45:52 -0700121 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
135bool GetCwdAsUtf8(string* result) {
Armelle Lainefbc68102022-11-06 19:03:46 +0000136 DWORD size = ::GetCurrentDirectoryW(0, nullptr);
Yu Shan10812112018-09-07 16:45:52 -0700137 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 Lainefbc68102022-11-06 19:03:46 +0000151bool 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 Shan10812112018-09-07 16:45:52 -0700161} // namespace
162
163void IoWin32Test::SetUp() {
164 test_tmpdir.clear();
165 wtest_tmpdir.clear();
Armelle Lainefbc68102022-11-06 19:03:46 +0000166 DWORD size = ::GetCurrentDirectoryW(MAX_PATH, working_directory);
167 EXPECT_GT(size, 0U);
168 EXPECT_LT(size, static_cast<DWORD>(MAX_PATH));
Yu Shan10812112018-09-07 16:45:52 -0700169
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
207void IoWin32Test::TearDown() {
208 if (!wtest_tmpdir.empty()) {
209 DeleteAllUnder(wtest_tmpdir);
210 }
211 ::SetCurrentDirectoryW(working_directory);
212}
213
214bool 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 Lainefbc68102022-11-06 19:03:46 +0000221 if (::CreateDirectoryW(path.c_str(), nullptr) ||
Yu Shan10812112018-09-07 16:45:52 -0700222 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 Lainefbc68102022-11-06 19:03:46 +0000230 if (CreateAllUnder(parent) && CreateDirectoryW(path.c_str(), nullptr)) {
Yu Shan10812112018-09-07 16:45:52 -0700231 return true;
232 }
233 }
234 }
235 return false;
236}
237
238bool 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
285TEST_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
325TEST_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
350TEST_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
367TEST_F(IoWin32Test, MkdirTestNonAscii) {
368 ASSERT_INITIALIZED;
369
370 // Create a non-ASCII path.
Armelle Lainefbc68102022-11-06 19:03:46 +0000371 // 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 Shan10812112018-09-07 16:45:52 -0700374 // Ensure that we can create a very similarly named directory using mkdir.
Armelle Lainefbc68102022-11-06 19:03:46 +0000375 // We don't attempt to delete and recreate the same directory, because on
Yu Shan10812112018-09-07 16:45:52 -0700376 // 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
381TEST_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
399TEST_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 Lainefbc68102022-11-06 19:03:46 +0000406 EXPECT_TRUE(CreateDirectoryW(wNonAscii.c_str(), nullptr));
Yu Shan10812112018-09-07 16:45:52 -0700407 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 Lainefbc68102022-11-06 19:03:46 +0000419TEST_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
469TEST_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
522TEST_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
557TEST_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 Shan10812112018-09-07 16:45:52 -0700581TEST_F(IoWin32Test, AsWindowsPathTest) {
Armelle Lainefbc68102022-11-06 19:03:46 +0000582 DWORD size = GetCurrentDirectoryW(0, nullptr);
Yu Shan10812112018-09-07 16:45:52 -0700583 std::unique_ptr<wchar_t[]> cwd_str(new wchar_t[size]);
Armelle Lainefbc68102022-11-06 19:03:46 +0000584 EXPECT_GT(GetCurrentDirectoryW(size, cwd_str.get()), 0U);
Yu Shan10812112018-09-07 16:45:52 -0700585 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
615TEST_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 Lainefbc68102022-11-06 19:03:46 +0000626} // namespace io
Yu Shan10812112018-09-07 16:45:52 -0700627} // namespace protobuf
628} // namespace google
629
630#endif // defined(_WIN32)
631