• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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