Windows: Use test binary in runfiles tree when it's enabled

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

RELNOTES: None
PiperOrigin-RevId: 302857287
diff --git a/src/test/py/bazel/test_wrapper_test.py b/src/test/py/bazel/test_wrapper_test.py
index e93c7a9..f2f740e 100644
--- a/src/test/py/bazel/test_wrapper_test.py
+++ b/src/test/py/bazel/test_wrapper_test.py
@@ -82,6 +82,12 @@
         '        "@echo BAD=%TEST_UNDECLARED_OUTPUTS_MANIFEST%",',
         '    ],',
         ')',
+        'bat_test(',
+        '    name = "print_arg0_test",',
+        '    content = [',
+        '        "@echo ARG0=%0",',
+        '    ],',
+        ')',
         'exe_test(',
         '    name = "testargs_test",',
         '    src = "testargs.exe",',
@@ -385,6 +391,50 @@
     if not good or bad:
       self._FailWithOutput(stderr + stdout)
 
+  def _AssertTestBinaryLocation(self, flags):
+    exit_code, bazel_bin, stderr = self.RunBazel(['info', 'bazel-bin'])
+    self.AssertExitCode(exit_code, 0, stderr)
+    bazel_bin = bazel_bin[0].replace('/', '\\')
+
+    exit_code, stdout, stderr = self.RunBazel([
+        'test',
+        '//foo:print_arg0_test',
+        '--test_output=all',
+    ] + flags)
+    self.AssertExitCode(exit_code, 0, stderr)
+
+    arg0 = None
+    for line in stderr + stdout:
+      if line.startswith('ARG0='):
+        arg0 = line[len('ARG0='):]
+    # Get rid of the quotes if there is any
+    if arg0[0] == '"' and arg0[-1] == '"':
+      arg0 = arg0[1:-1]
+    # The test binary should located at the bazel bin folder
+    self.assertEqual(arg0, os.path.join(bazel_bin, 'foo\\print_arg0_test.bat'))
+
+    exit_code, stdout, stderr = self.RunBazel([
+        'test',
+        '//foo:print_arg0_test',
+        '--test_output=all',
+        '--enable_runfiles',
+    ] + flags)
+    self.AssertExitCode(exit_code, 0, stderr)
+
+    arg0 = None
+    for line in stderr + stdout:
+      if line.startswith('ARG0='):
+        arg0 = line[len('ARG0='):]
+    # Get rid of the quotes if there is any
+    if arg0[0] == '"' and arg0[-1] == '"':
+      arg0 = arg0[1:-1]
+    self.assertEqual(
+        arg0,
+        os.path.join(bazel_bin,
+                     'foo\\print_arg0_test.bat.runfiles\\'
+                     '__main__\\foo\\print_arg0_test.bat')
+    )
+
   def _AssertTestArgs(self, flags):
     exit_code, bazel_bin, stderr = self.RunBazel(['info', 'bazel-bin'])
     self.AssertExitCode(exit_code, 0, stderr)
@@ -617,6 +667,7 @@
     self._AssertRunfilesSymlinks(flags)
     self._AssertShardedTest(flags)
     self._AssertUnexportsEnvvars(flags)
+    self._AssertTestBinaryLocation(flags)
     self._AssertTestArgs(flags)
     self._AssertUndeclaredOutputs(flags)
     self._AssertUndeclaredOutputsAnnotations(flags)
diff --git a/tools/test/windows/tw.cc b/tools/test/windows/tw.cc
index 0a7862c..ac2ce57 100644
--- a/tools/test/windows/tw.cc
+++ b/tools/test/windows/tw.cc
@@ -1118,7 +1118,7 @@
 }
 
 bool FindTestBinary(const Path& argv0, const Path& cwd, std::wstring test_path,
-                    Path* result) {
+                    const Path& abs_test_srcdir, Path* result) {
   if (!blaze_util::IsAbsolute(test_path)) {
     std::string argv0_acp;
     if (!WcsToAcp(argv0.Get(), &argv0_acp)) {
@@ -1142,18 +1142,39 @@
 
     ComputeRunfilePath(workspace, &test_path);
 
-    std::string utf8_test_path;
-    uint32_t err;
-    if (!blaze_util::WcsToUtf8(test_path, &utf8_test_path, &err)) {
-      LogErrorWithArgAndValue(__LINE__, "Failed to convert string to UTF-8",
-                              test_path, err);
+    Path test_bin_in_runfiles;
+    if (!test_bin_in_runfiles.Set(abs_test_srcdir.Get() + L"\\" + test_path)) {
+      LogErrorWithArg2(__LINE__, "Could not join paths", abs_test_srcdir.Get(),
+                       test_path);
       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, err);
+    std::wstring mf_only_str;
+    int mf_only_value = 0;
+    if (!GetIntEnv(L"RUNFILES_MANIFEST_ONLY", &mf_only_str, &mf_only_value)) {
+      return false;
+    }
+
+    // If runfiles is enabled on Windows, we use the test binary in the runfiles
+    // tree, which is consistent with the behavior on Linux and macOS.
+    // Otherwise, we use Rlocation function to find the actual test binary
+    // location.
+    if (mf_only_value != 1 && IsReadableFile(test_bin_in_runfiles)) {
+      test_path = test_bin_in_runfiles.Get();
+    } else {
+      std::string utf8_test_path;
+      uint32_t err;
+      if (!blaze_util::WcsToUtf8(test_path, &utf8_test_path, &err)) {
+        LogErrorWithArgAndValue(__LINE__, "Failed to convert string to UTF-8",
+                                test_path, 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, err);
+      }
     }
   }
 
@@ -1864,8 +1885,8 @@
   std::wstring args;
   if (!ParseArgs(argc, argv, &argv0, &test_path_arg, &args) ||
       !PrintTestLogStartMarker() || !GetCwd(&exec_root) ||
-      !FindTestBinary(argv0, exec_root, test_path_arg, &test_path) ||
       !ExportUserName() || !ExportSrcPath(exec_root, &srcdir) ||
+      !FindTestBinary(argv0, exec_root, test_path_arg, srcdir, &test_path) ||
       !ChdirToRunfiles(exec_root, srcdir) ||
       !ExportTmpPath(exec_root, &tmpdir) || !ExportHome(tmpdir) ||
       !ExportRunfiles(exec_root, srcdir) || !ExportShardStatusFile(exec_root) ||