Windows, test wrapper: use WaitableProcess

The Windows-native test wrapper now uses the JNI
library's WaitableProcess to create, wait for, and
terminate the subprocess.

This allows killing the whole subprocess tree and
not waiting for sub-subprocesses.

Fixes https://github.com/bazelbuild/bazel/issues/8005

Closes #8075.

PiperOrigin-RevId: 243984578
diff --git a/src/main/native/windows/process.cc b/src/main/native/windows/process.cc
index 82fec19..48e54bc 100644
--- a/src/main/native/windows/process.cc
+++ b/src/main/native/windows/process.cc
@@ -33,6 +33,7 @@
                              const std::wstring& argv_rest, void* env,
                              const std::wstring& wcwd, HANDLE stdin_process,
                              HANDLE stdout_process, HANDLE stderr_process,
+                             LARGE_INTEGER* opt_out_start_time,
                              std::wstring* error) {
   std::wstring cwd;
   std::wstring error_msg(AsShortPath(wcwd, &cwd));
@@ -177,11 +178,26 @@
     return false;
   }
 
+  if (opt_out_start_time) {
+    QueryPerformanceCounter(opt_out_start_time);
+  }
   *error = L"";
   return true;
 }
 
-int WaitableProcess::WaitFor(int64_t timeout_msec, std::wstring* error) {
+int WaitableProcess::WaitFor(int64_t timeout_msec,
+                             LARGE_INTEGER* opt_out_end_time,
+                             std::wstring* error) {
+  struct Defer {
+    LARGE_INTEGER* t;
+    Defer(LARGE_INTEGER* cnt) : t(cnt) {}
+    ~Defer() {
+      if (t) {
+        QueryPerformanceCounter(t);
+      }
+    }
+  } defer_query_end_time(opt_out_end_time);
+
   DWORD win32_timeout = timeout_msec < 0 ? INFINITE : timeout_msec;
   int result;
   switch (WaitForSingleObject(process_, win32_timeout)) {
diff --git a/src/main/native/windows/process.h b/src/main/native/windows/process.h
index e750f46..de0e1e4 100644
--- a/src/main/native/windows/process.h
+++ b/src/main/native/windows/process.h
@@ -42,9 +42,10 @@
   bool Create(const std::wstring& argv0, const std::wstring& argv_rest,
               void* env, const std::wstring& wcwd, HANDLE stdin_process,
               HANDLE stdout_process, HANDLE stderr_process,
-              std::wstring* error);
+              LARGE_INTEGER* opt_out_start_time, std::wstring* error);
 
-  int WaitFor(int64_t timeout_msec, std::wstring* error);
+  int WaitFor(int64_t timeout_msec, LARGE_INTEGER* opt_out_end_time,
+              std::wstring* error);
 
   int GetExitCode(std::wstring* error);
 
diff --git a/src/main/native/windows/processes-jni.cc b/src/main/native/windows/processes-jni.cc
index e3bb480..f58397e 100644
--- a/src/main/native/windows/processes-jni.cc
+++ b/src/main/native/windows/processes-jni.cc
@@ -314,7 +314,7 @@
     return proc_.Create(
         wpath, bazel::windows::GetJavaWstring(env, java_argv_rest),
         env_map.ptr(), bazel::windows::GetJavaWpath(env, java_cwd),
-        stdin_process, stdout_process, stderr_process, &error_);
+        stdin_process, stdout_process, stderr_process, nullptr, &error_);
   }
 
   void CloseStdin() {
@@ -325,7 +325,7 @@
 
   // Wait for this process to exit (or timeout).
   int WaitFor(int64_t timeout_msec) {
-    return proc_.WaitFor(timeout_msec, &error_);
+    return proc_.WaitFor(timeout_msec, nullptr, &error_);
   }
 
   // Returns the exit code of the process if it has already exited. If the
diff --git a/src/test/shell/bazel/testdata/embedded_tools_srcs_deps b/src/test/shell/bazel/testdata/embedded_tools_srcs_deps
index 4caa1c9..1f15403 100644
--- a/src/test/shell/bazel/testdata/embedded_tools_srcs_deps
+++ b/src/test/shell/bazel/testdata/embedded_tools_srcs_deps
@@ -21,6 +21,7 @@
 //src/tools/launcher/util:util
 //src/main/cpp/util:filesystem
 //src/main/native/windows:lib-file
+//src/main/native/windows:lib-process
 //src/main/native/windows:lib-util
 //src/main/cpp/util:strings
 //src/main/cpp/util:errors
diff --git a/tools/test/BUILD b/tools/test/BUILD
index 6deb7dd..d259336 100644
--- a/tools/test/BUILD
+++ b/tools/test/BUILD
@@ -79,6 +79,7 @@
             "//src/main/cpp/util:filesystem",
             "//src/main/cpp/util:strings",
             "//src/main/native/windows:lib-file",
+            "//src/main/native/windows:lib-process",
             "//src/main/native/windows:lib-util",
             "//src/tools/launcher/util",
             "//third_party/ijar:zip",
diff --git a/tools/test/windows/tw.cc b/tools/test/windows/tw.cc
index d65312b..57f3787 100644
--- a/tools/test/windows/tw.cc
+++ b/tools/test/windows/tw.cc
@@ -18,12 +18,14 @@
 
 #include "tools/test/windows/tw.h"
 
+#ifndef WIN32_LEAN_AND_MEAN
 #define WIN32_LEAN_AND_MEAN
-#include <lmcons.h>  // UNLEN
+#endif
 #include <windows.h>
 
 #include <errno.h>
 #include <limits.h>  // INT_MAX
+#include <lmcons.h>  // UNLEN
 #include <string.h>
 #include <sys/types.h>
 #include <wchar.h>
@@ -42,6 +44,7 @@
 #include "src/main/cpp/util/path_platform.h"
 #include "src/main/cpp/util/strings.h"
 #include "src/main/native/windows/file.h"
+#include "src/main/native/windows/process.h"
 #include "src/main/native/windows/util.h"
 #include "src/tools/launcher/util/launcher_util.h"
 #include "third_party/ijar/common.h"
@@ -1098,7 +1101,7 @@
 bool StartSubprocess(const Path& path, const std::wstring& args,
                      const Path& outerr, std::unique_ptr<Tee>* tee,
                      LARGE_INTEGER* start_time,
-                     bazel::windows::AutoHandle* process) {
+                     bazel::windows::WaitableProcess* process) {
   SECURITY_ATTRIBUTES inheritable_handle_sa = {sizeof(SECURITY_ATTRIBUTES),
                                                NULL, TRUE};
 
@@ -1139,16 +1142,6 @@
     return false;
   }
 
-  // Create an attribute object that specifies which particular handles shall
-  // the subprocess inherit. We pass this object to CreateProcessW.
-  std::unique_ptr<bazel::windows::AutoAttributeList> attr_list;
-  std::wstring werror;
-  if (!bazel::windows::AutoAttributeList::Create(
-          devnull_read, pipe_write, pipe_write_dup, &attr_list, &werror)) {
-    LogError(__LINE__, werror);
-    return false;
-  }
-
   // Open a handle to the test log file. The "tee" thread will write everything
   // into it that the subprocess writes to the pipe.
   bazel::windows::AutoHandle test_outerr;
@@ -1177,57 +1170,13 @@
     return false;
   }
 
-  PROCESS_INFORMATION process_info;
-  STARTUPINFOEXW startup_info;
-  attr_list->InitStartupInfoExW(&startup_info);
-
-  std::unique_ptr<WCHAR[]> cmdline;
-  if (!CreateCommandLine(path, args, &cmdline)) {
+  std::wstring werror;
+  if (!process->Create(path.Get(), args, nullptr, L"", devnull_read, pipe_write,
+                       pipe_write_dup, start_time, &werror)) {
+    LogError(__LINE__, werror);
     return false;
   }
-
-  QueryPerformanceCounter(start_time);
-  if (CreateProcessW(NULL, cmdline.get(), NULL, NULL, TRUE,
-                     CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT,
-                     NULL, NULL, &startup_info.StartupInfo,
-                     &process_info) != 0) {
-    CloseHandle(process_info.hThread);
-    *process = process_info.hProcess;
-    return true;
-  } else {
-    DWORD err = GetLastError();
-    LogErrorWithValue(
-        __LINE__,
-        (std::wstring(L"CreateProcessW failed (") + cmdline.get() + L")")
-            .c_str(),
-        err);
-    return false;
-  }
-}
-
-int WaitForSubprocess(HANDLE process, LARGE_INTEGER* end_time) {
-  DWORD result = WaitForSingleObject(process, INFINITE);
-  QueryPerformanceCounter(end_time);
-  switch (result) {
-    case WAIT_OBJECT_0: {
-      DWORD exit_code;
-      if (!GetExitCodeProcess(process, &exit_code)) {
-        DWORD err = GetLastError();
-        LogErrorWithValue(__LINE__, "GetExitCodeProcess failed", err);
-        return 1;
-      }
-      return exit_code;
-    }
-    case WAIT_FAILED: {
-      DWORD err = GetLastError();
-      LogErrorWithValue(__LINE__, "WaitForSingleObject failed", err);
-      return 1;
-    }
-    default:
-      LogErrorWithValue(
-          __LINE__, "WaitForSingleObject returned unexpected result", result);
-      return 1;
-  }
+  return true;
 }
 
 bool ArchiveUndeclaredOutputs(const UndeclaredOutputs& undecl) {
@@ -1385,14 +1334,27 @@
 int RunSubprocess(const Path& test_path, const std::wstring& args,
                   const Path& test_outerr, Duration* test_duration) {
   std::unique_ptr<Tee> tee;
-  bazel::windows::AutoHandle process;
+  bazel::windows::WaitableProcess process;
   LARGE_INTEGER start, end, freq;
   if (!StartSubprocess(test_path, args, test_outerr, &tee, &start, &process)) {
     LogError(__LINE__, std::wstring(L"Failed to start test process \"") +
                            test_path.Get() + L"\"");
     return 1;
   }
-  int result = WaitForSubprocess(process, &end);
+
+  std::wstring werror;
+  int wait_res = process.WaitFor(-1, &end, &werror);
+  if (wait_res != bazel::windows::WaitableProcess::kWaitSuccess) {
+    LogErrorWithValue(__LINE__, werror, wait_res);
+    return 1;
+  }
+
+  werror.clear();
+  int result = process.GetExitCode(&werror);
+  if (!werror.empty()) {
+    LogError(__LINE__, werror);
+    return 1;
+  }
 
   QueryPerformanceFrequency(&freq);
   end.QuadPart -= start.QuadPart;