Windows,test-wrapper: use UTF-16 strings and API

See https://github.com/bazelbuild/bazel/issues/5508

Change-Id: I667f5503ebf22b91e114f13241aa5b3f29c5146b

Closes #6086.

Change-Id: I667f5503ebf22b91e114f13241aa5b3f29c5146b
PiperOrigin-RevId: 211797041
diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc
index 941f6c2..99b213b 100644
--- a/src/main/cpp/util/file_windows.cc
+++ b/src/main/cpp/util/file_windows.cc
@@ -951,17 +951,36 @@
   return MakeDirectoriesW(wpath, mode);
 }
 
+static inline void ToLowerW(WCHAR* p) {
+  while (*p) {
+    *p++ = towlower(*p);
+  }
+}
+
 std::wstring GetCwdW() {
-  DWORD len = ::GetCurrentDirectoryW(0, nullptr);
-  unique_ptr<WCHAR[]> cwd(new WCHAR[len]);
-  if (!::GetCurrentDirectoryW(len, cwd.get())) {
+  static constexpr size_t kBufSmall = MAX_PATH;
+  WCHAR buf[kBufSmall];
+  DWORD len = GetCurrentDirectoryW(kBufSmall, buf);
+  if (len == 0) {
+    DWORD err = GetLastError();
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
-        << "GetCurrentDirectoryW failed: " << GetLastErrorString();
+        << "GetCurrentDirectoryW failed (error " << err << ")";
   }
-  for (WCHAR* p = cwd.get(); *p != 0; ++p) {
-    *p = towlower(*p);
+
+  if (len < kBufSmall) {
+    ToLowerW(buf);
+    return std::wstring(buf);
   }
-  return std::wstring(cwd.get());
+
+  unique_ptr<WCHAR[]> buf_big(new WCHAR[len]);
+  len = GetCurrentDirectoryW(len, buf_big.get());
+  if (len == 0) {
+    DWORD err = GetLastError();
+    BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
+        << "GetCurrentDirectoryW failed (error " << err << ")";
+  }
+  ToLowerW(buf_big.get());
+  return std::wstring(buf_big.get());
 }
 
 string GetCwd() {
diff --git a/tools/test/BUILD b/tools/test/BUILD
index a8972e1..f97325e 100644
--- a/tools/test/BUILD
+++ b/tools/test/BUILD
@@ -48,6 +48,7 @@
     deps = select({
         "@bazel_tools//src/conditions:windows": [
             "//src/main/cpp/util:filesystem",
+            "//src/main/cpp/util:strings",
             "@bazel_tools//tools/cpp/runfiles",
         ],
         "//conditions:default": [],
diff --git a/tools/test/windows/test_wrapper.cc b/tools/test/windows/test_wrapper.cc
index 0f7ee65..42d757f1 100644
--- a/tools/test/windows/test_wrapper.cc
+++ b/tools/test/windows/test_wrapper.cc
@@ -27,7 +27,9 @@
 #include <memory>
 #include <string>
 
+#include "src/main/cpp/util/file_platform.h"
 #include "src/main/cpp/util/path_platform.h"
+#include "src/main/cpp/util/strings.h"
 #include "tools/cpp/runfiles/runfiles.h"
 
 namespace {
@@ -57,10 +59,17 @@
           line, error_code, error_code, arg, msg);
 }
 
-bool GetEnv(const char* name, std::string* result) {
+void LogErrorWithArgAndValue(const int line, const char* msg,
+                             const wchar_t* arg, DWORD error_code) {
+  fprintf(stderr,
+          "ERROR(" __FILE__ ":%d) error code: %d (0x%08x), argument: %ls: %s\n",
+          line, error_code, error_code, arg, msg);
+}
+
+bool GetEnv(const wchar_t* name, std::wstring* result) {
   static constexpr size_t kSmallBuf = MAX_PATH;
-  char value[kSmallBuf];
-  DWORD size = GetEnvironmentVariableA(name, value, kSmallBuf);
+  WCHAR value[kSmallBuf];
+  DWORD size = GetEnvironmentVariableW(name, value, kSmallBuf);
   DWORD err = GetLastError();
   if (size == 0 && err == ERROR_ENVVAR_NOT_FOUND) {
     result->clear();
@@ -69,8 +78,8 @@
     *result = value;
     return true;
   } else if (size >= kSmallBuf) {
-    std::unique_ptr<char[]> value_big(new char[size]);
-    GetEnvironmentVariableA(name, value_big.get(), size);
+    std::unique_ptr<WCHAR[]> value_big(new WCHAR[size]);
+    GetEnvironmentVariableW(name, value_big.get(), size);
     *result = value_big.get();
     return true;
   } else {
@@ -86,35 +95,56 @@
       "-----\n");
 }
 
-inline bool GetWorkspaceName(std::string* result) {
-  return GetEnv("TEST_WORKSPACE", result) && !result->empty();
+inline bool GetWorkspaceName(std::wstring* result) {
+  return GetEnv(L"TEST_WORKSPACE", result) && !result->empty();
 }
 
-inline void StripLeadingDotSlash(std::string* s) {
-  if (s->size() >= 2 && (*s)[0] == '.' && (*s)[1] == '/') {
-    *s = s->substr(2);
+inline void StripLeadingDotSlash(std::wstring* s) {
+  if (s->size() >= 2 && (*s)[0] == L'.' && (*s)[1] == L'/') {
+    s->erase(0, 2);
   }
 }
 
-bool FindTestBinary(const std::string& argv0, std::string test_path,
+bool FindTestBinary(const std::wstring& argv0, std::wstring test_path,
                     std::wstring* result) {
   if (!blaze_util::IsAbsolute(test_path)) {
+    std::string argv0_acp;
+    uint32_t err;
+    if (!blaze_util::WcsToAcp(argv0, &argv0_acp, &err)) {
+      LogErrorWithArgAndValue(__LINE__, "Failed to convert string",
+                              argv0.c_str(), err);
+      return false;
+    }
+
     std::string error;
     std::unique_ptr<bazel::tools::cpp::runfiles::Runfiles> runfiles(
-        bazel::tools::cpp::runfiles::Runfiles::Create(argv0, &error));
+        bazel::tools::cpp::runfiles::Runfiles::Create(argv0_acp, &error));
     if (runfiles == nullptr) {
       LogError(__LINE__, "Failed to load runfiles");
       return false;
     }
 
-    std::string workspace;
+    std::wstring workspace;
     if (!GetWorkspaceName(&workspace)) {
       LogError(__LINE__, "Failed to read %TEST_WORKSPACE%");
       return false;
     }
 
     StripLeadingDotSlash(&test_path);
-    test_path = runfiles->Rlocation(workspace + "/" + test_path);
+    test_path = workspace + L"/" + test_path;
+
+    std::string utf8_test_path;
+    if (!blaze_util::WcsToUtf8(test_path, &utf8_test_path, &err)) {
+      LogErrorWithArgAndValue(__LINE__, "Failed to convert string to UTF-8",
+                              test_path.c_str(), err);
+      return false;
+    }
+
+    std::string rloc = runfiles->Rlocation(utf8_test_path);
+    if (!blaze_util::Utf8ToWcs(rloc, &test_path, &err)) {
+      LogErrorWithArgAndValue(__LINE__, "Failed to convert string",
+                              utf8_test_path.c_str(), err);
+    }
   }
 
   std::string error;
@@ -171,26 +201,26 @@
 
 }  // namespace
 
-int main(int argc, char** argv) {
+int wmain(int argc, wchar_t** argv) {
   // TODO(laszlocsomor): Implement the functionality described in
   // https://github.com/laszlocsomor/proposals/blob/win-test-runner/designs/2018-07-18-windows-native-test-runner.md
 
-  const char* argv0 = argv[0];
+  const wchar_t* argv0 = argv[0];
   argc--;
   argv++;
   bool suppress_output = false;
-  if (argc > 0 && strcmp(argv[0], "--no_echo") == 0) {
+  if (argc > 0 && wcscmp(argv[0], L"--no_echo") == 0) {
     // Don't print anything to stdout in this special case.
     // Currently needed for persistent test runner.
     suppress_output = true;
     argc--;
     argv++;
   } else {
-    std::string test_target;
-    if (!GetEnv("TEST_TARGET", &test_target)) {
+    std::wstring test_target;
+    if (!GetEnv(L"TEST_TARGET", &test_target)) {
       return 1;
     }
-    printf("Executing tests from %s\n", test_target.c_str());
+    printf("Executing tests from %ls\n", test_target.c_str());
   }
 
   if (argc < 1) {
@@ -202,7 +232,7 @@
     PrintTestLogStartMarker();
   }
 
-  const char* test_path_arg = argv[0];
+  const wchar_t* test_path_arg = argv[0];
   std::wstring test_path;
   if (!FindTestBinary(argv0, test_path_arg, &test_path)) {
     return 1;