Windows, test wrapper: works with external tests

Fixes https://github.com/bazelbuild/bazel/issues/8088 for the native test wrapper.

Closes #8090.

PiperOrigin-RevId: 244811707
diff --git a/src/test/py/bazel/test_wrapper_test.py b/src/test/py/bazel/test_wrapper_test.py
index 1460763..340996e 100644
--- a/src/test/py/bazel/test_wrapper_test.py
+++ b/src/test/py/bazel/test_wrapper_test.py
@@ -368,7 +368,7 @@
     if not good or bad:
       self._FailWithOutput(stderr + stdout)
 
-  def _AssertTestArgs(self, flag, expected):
+  def _AssertTestArgs(self, flag):
     exit_code, bazel_bin, stderr = self.RunBazel(['info', 'bazel-bin'])
     self.AssertExitCode(exit_code, 0, stderr)
     bazel_bin = bazel_bin[0]
@@ -394,7 +394,26 @@
     for line in stderr + stdout:
       if line.startswith('arg='):
         actual.append(str(line[len('arg='):]))
-    self.assertListEqual(expected, actual)
+    self.assertListEqual(
+        [
+            '(foo)',
+            # TODO(laszlocsomor): assert that "a b" is passed as one argument,
+            # not two, after https://github.com/bazelbuild/bazel/issues/6277
+            # is fixed.
+            '(a)',
+            '(b)',
+            # TODO(laszlocsomor): assert that the empty string argument is
+            # passed, after https://github.com/bazelbuild/bazel/issues/6276
+            # is fixed.
+            '(c d)',
+            '()',
+            '(bar)',
+            '(baz)',
+            '("x y")',
+            '("")',
+            '(qux)',
+        ],
+        actual)
 
   def _AssertUndeclaredOutputs(self, flag):
     exit_code, bazel_testlogs, stderr = self.RunBazel(
@@ -550,6 +569,34 @@
       xml_contents = [line.strip() for line in f.readlines()]
     self.assertListEqual(xml_contents, ['leave this'])
 
+  # Test that the native test wrapper can run tests from external repositories.
+  # See https://github.com/bazelbuild/bazel/issues/8088
+  # Unfortunately as of 2019-04-18 the legacy test wrapper (test-setup.sh) also
+  # has this bug, but I (@laszlocsomor) work on enabling the native test wrapper
+  # by default so fixing the legacy one seems to make little sense.
+  def testRunningTestFromExternalRepo(self):
+    self.ScratchFile('WORKSPACE', ['local_repository(name = "a", path = "a")'])
+    self.ScratchFile('a/WORKSPACE')
+    self.ScratchFile('BUILD', ['py_test(name = "x", srcs = ["x.py"])'])
+    self.ScratchFile('a/BUILD', ['py_test(name = "x", srcs = ["x.py"])'])
+    self.ScratchFile('x.py')
+    self.ScratchFile('a/x.py')
+
+    for flag in ['--legacy_external_runfiles', '--nolegacy_external_runfiles']:
+      for target in ['//:x', '@a//:x']:
+        exit_code, _, stderr = self.RunBazel([
+            'test',
+            '-t-',
+            '--incompatible_windows_native_test_wrapper',
+            '--test_output=errors',
+            '--verbose_failures',
+            flag,
+            target,
+        ])
+        self.AssertExitCode(
+            exit_code, 0,
+            ['flag=%s' % flag, 'target=%s' % target] + stderr)
+
   def testTestExecutionWithTestSetupSh(self):
     self._CreateMockWorkspace()
     flag = '--noincompatible_windows_native_test_wrapper'
@@ -559,24 +606,7 @@
     self._AssertRunfiles(flag)
     self._AssertShardedTest(flag)
     self._AssertUnexportsEnvvars(flag)
-    self._AssertTestArgs(
-        flag,
-        [
-            '(foo)',
-            # If https://github.com/bazelbuild/bazel/issues/6277 is ever fixed,
-            # then assert that (a b) is one argument.
-            '(a)',
-            '(b)',
-            # If https://github.com/bazelbuild/bazel/issues/6276 is ever fixed,
-            # then assert that there's an empty argument before (c d).
-            '(c d)',
-            '()',
-            '(bar)',
-            '(baz)',
-            '("x y")',
-            '("")',
-            '(qux)',
-        ])
+    self._AssertTestArgs(flag)
     self._AssertUndeclaredOutputs(flag)
     self._AssertUndeclaredOutputsAnnotations(flag)
     self._AssertXmlGeneration(flag, split_xml=False)
@@ -593,26 +623,7 @@
     self._AssertRunfiles(flag)
     self._AssertShardedTest(flag)
     self._AssertUnexportsEnvvars(flag)
-    self._AssertTestArgs(
-        flag,
-        [
-            '(foo)',
-            # TODO(laszlocsomor): assert that "a b" is passed as one argument,
-            # not two, after https://github.com/bazelbuild/bazel/issues/6277
-            # is fixed.
-            '(a)',
-            '(b)',
-            # TODO(laszlocsomor): assert that the empty string argument is
-            # passed, after https://github.com/bazelbuild/bazel/issues/6276
-            # is fixed.
-            '(c d)',
-            '()',
-            '(bar)',
-            '(baz)',
-            '("x y")',
-            '("")',
-            '(qux)',
-        ])
+    self._AssertTestArgs(flag)
     self._AssertUndeclaredOutputs(flag)
     self._AssertUndeclaredOutputsAnnotations(flag)
     self._AssertXmlGeneration(flag, split_xml=False)
diff --git a/tools/test/windows/tw.cc b/tools/test/windows/tw.cc
index 57f3787..bb577c5 100644
--- a/tools/test/windows/tw.cc
+++ b/tools/test/windows/tw.cc
@@ -1029,10 +1029,16 @@
   return GetEnv(L"TEST_WORKSPACE", result) && !result->empty();
 }
 
-inline void StripLeadingDotSlash(std::wstring* s) {
+inline void ComputeRunfilePath(const std::wstring& test_workspace,
+                               std::wstring* s) {
   if (s->size() >= 2 && (*s)[0] == L'.' && (*s)[1] == L'/') {
     s->erase(0, 2);
   }
+  if (s->find(L"external/") == 0) {
+    s->erase(0, 9);
+  } else {
+    *s = test_workspace + L"/" + *s;
+  }
 }
 
 bool FindTestBinary(const Path& argv0, std::wstring test_path, Path* result) {
@@ -1058,8 +1064,7 @@
       return false;
     }
 
-    StripLeadingDotSlash(&test_path);
-    test_path = workspace + L"/" + test_path;
+    ComputeRunfilePath(workspace, &test_path);
 
     std::string utf8_test_path;
     uint32_t err;