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