Make --verbose_failures work again

--verbose_failures is broken since Bazel 0.28.0, Bazel won't print the
failing command for local execution even --verbose_failures is enabled.

This change fixes the problem and adds a test for it.

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

Closes #10581.

PiperOrigin-RevId: 289854746
diff --git a/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java b/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java
index 29a8875..b1c42c0 100644
--- a/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategyTest.java
@@ -24,6 +24,7 @@
 import com.google.common.eventbus.EventBus;
 import com.google.devtools.build.lib.actions.ActionExecutionContext;
 import com.google.devtools.build.lib.actions.ActionExecutionContext.LostInputsCheck;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
 import com.google.devtools.build.lib.actions.ActionInputPrefetcher;
 import com.google.devtools.build.lib.actions.ActionKeyContext;
 import com.google.devtools.build.lib.actions.Artifact;
@@ -64,6 +65,7 @@
 import com.google.devtools.common.options.OptionsParser;
 import java.io.IOException;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import org.junit.Before;
@@ -84,6 +86,8 @@
           output.add(artifact);
         }
       };
+  private static final String WINDOWS_SYSTEM_DRIVE = "C:";
+  private static final String CMD_EXE = getWinSystemBinary("cmd.exe");
 
   private Reporter reporter =
       new Reporter(new EventBus(), PrintingEventHandler.ERRORS_AND_WARNINGS_TO_STDERR);
@@ -103,6 +107,13 @@
     return testRoot;
   }
 
+  /**
+   * We assume Windows is installed on C: and all system binaries exist under C:\Windows\System32\
+   */
+  private static String getWinSystemBinary(String binary) {
+    return WINDOWS_SYSTEM_DRIVE + "\\Windows\\System32\\" + binary;
+  }
+
   @Before
   public final void setUp() throws Exception {
     Path testRoot = createTestRoot();
@@ -188,7 +199,9 @@
     Spawn spawn = createSpawn(getTrueCommand());
     executor.getContext(SpawnActionContext.class).exec(spawn, createContext());
 
-    assertThat(out()).isEmpty();
+    if (OS.getCurrent() != OS.WINDOWS) {
+      assertThat(out()).isEmpty();
+    }
     assertThat(err()).isEmpty();
   }
 
@@ -215,62 +228,108 @@
   }
 
   @Test
-  public void testBinFalseYieldsException() throws Exception {
+  public void testBinFalseYieldsException() {
     ExecException e = assertThrows(ExecException.class, () -> run(createSpawn(getFalseCommand())));
     assertWithMessage("got: " + e.getMessage())
-        .that(e.getMessage().startsWith("false failed: error executing command"))
+        .that(e.getMessage().contains("failed: error executing command"))
         .isTrue();
   }
 
   private static String getFalseCommand() {
+    if (OS.getCurrent() == OS.WINDOWS) {
+      // No false command on Windows, we use help.exe as an alternative,
+      // the caveat is that the command will have some output to stdout.
+      // Default exit code of help is 1
+      return getWinSystemBinary("help.exe");
+    }
     return OS.getCurrent() == OS.DARWIN ? "/usr/bin/false" : "/bin/false";
   }
 
   private static String getTrueCommand() {
+    if (OS.getCurrent() == OS.WINDOWS) {
+      // No true command on Windows, we use whoami.exe as an alternative,
+      // the caveat is that the command will have some output to stdout.
+      // Default exit code of help is 0
+      return getWinSystemBinary("whoami.exe");
+    }
     return OS.getCurrent() == OS.DARWIN ? "/usr/bin/true" : "/bin/true";
   }
 
   @Test
   public void testBinEchoPrintsArguments() throws Exception {
-    Spawn spawn = createSpawn("/bin/echo", "Hello,", "world.");
+    Spawn spawn;
+    if (OS.getCurrent() == OS.WINDOWS) {
+      spawn = createSpawn(CMD_EXE, "/c", "echo", "Hello,", "world.");
+    } else {
+      spawn = createSpawn("/bin/echo", "Hello,", "world.");
+    }
     run(spawn);
-    assertThat(out()).isEqualTo("Hello, world.\n");
+    assertThat(out()).isEqualTo("Hello, world." + System.lineSeparator());
     assertThat(err()).isEmpty();
   }
 
   @Test
   public void testCommandRunsInWorkingDir() throws Exception {
-    Spawn spawn = createSpawn("/bin/pwd");
+    Spawn spawn;
+    if (OS.getCurrent() == OS.WINDOWS) {
+      spawn = createSpawn(CMD_EXE, "/c", "cd");
+    } else {
+      spawn = createSpawn("/bin/pwd");
+    }
     run(spawn);
-    assertThat(out()).isEqualTo(executor.getExecRoot() + "\n");
+    assertThat(out().replace('\\', '/')).isEqualTo(executor.getExecRoot() + System.lineSeparator());
   }
 
   @Test
   public void testCommandHonorsEnvironment() throws Exception {
-    if (OS.getCurrent() == OS.DARWIN) {
-      // // TODO(#)3795: For some reason, we get __CF_USER_TEXT_ENCODING into the env in some
-      // configurations of MacOS machines. I have been unable to reproduce on my Mac, or to track
-      // down where that env var is coming from.
-      return;
-    }
     Spawn spawn =
         new SimpleSpawn(
             new ActionsTestUtil.NullAction(),
-            ImmutableList.of("/usr/bin/env"),
+            OS.getCurrent() == OS.WINDOWS
+                ? ImmutableList.of(CMD_EXE, "/c", "set")
+                : ImmutableList.of("/usr/bin/env"),
             /*environment=*/ ImmutableMap.of("foo", "bar", "baz", "boo"),
             /*executionInfo=*/ ImmutableMap.of(),
             /*inputs=*/ NestedSetBuilder.emptySet(Order.STABLE_ORDER),
             /*outputs=*/ ImmutableSet.of(),
             ResourceSet.ZERO);
     run(spawn);
-    assertThat(Sets.newHashSet(out().split("\n"))).isEqualTo(Sets.newHashSet("foo=bar", "baz=boo"));
+    HashSet<String> environment = Sets.newHashSet(out().split(System.lineSeparator()));
+    if (OS.getCurrent() == OS.WINDOWS || OS.getCurrent() == OS.DARWIN) {
+      // On Windows and macOS, we may have some other env vars
+      // (eg. SystemRoot or __CF_USER_TEXT_ENCODING).
+      assertThat(environment).contains("foo=bar");
+      assertThat(environment).contains("baz=boo");
+    } else {
+      assertThat(environment).isEqualTo(Sets.newHashSet("foo=bar", "baz=boo"));
+    }
   }
 
   @Test
   public void testStandardError() throws Exception {
-    Spawn spawn = createSpawn("/bin/sh", "-c", "echo Oops! >&2");
+    Spawn spawn;
+    if (OS.getCurrent() == OS.WINDOWS) {
+      spawn = createSpawn(CMD_EXE, "/c", "echo Oops!>&2");
+    } else {
+      spawn = createSpawn("/bin/sh", "-c", "echo Oops! >&2");
+    }
     run(spawn);
-    assertThat(err()).isEqualTo("Oops!\n");
+    assertThat(err()).isEqualTo("Oops!" + System.lineSeparator());
     assertThat(out()).isEmpty();
   }
+
+  /**
+   * Regression test for https://github.com/bazelbuild/bazel/issues/10572 Make sure we do have the
+   * command line executed in the error message of ActionExecutionException when --verbose_failures
+   * is enabled.
+   */
+  @Test
+  public void testVerboseFailures() {
+    ExecException e = assertThrows(ExecException.class, () -> run(createSpawn(getFalseCommand())));
+    ActionExecutionException actionExecutionException =
+        e.toActionExecutionException("", /* verboseFailures= */ true, null);
+    assertWithMessage("got: " + actionExecutionException.getMessage())
+        .that(actionExecutionException.getMessage().contains("failed: error executing command"))
+        .isTrue();
+  }
 }