Allow Bazel in JUnit black box tests to fail.

This is needed to verify the failure in case we do not know the expected error code exactly.

Closes #7616.

PiperOrigin-RevId: 236831845
diff --git a/src/test/java/com/google/devtools/build/lib/blackbox/framework/BuilderRunner.java b/src/test/java/com/google/devtools/build/lib/blackbox/framework/BuilderRunner.java
index 6927658..1d69d92 100644
--- a/src/test/java/com/google/devtools/build/lib/blackbox/framework/BuilderRunner.java
+++ b/src/test/java/com/google/devtools/build/lib/blackbox/framework/BuilderRunner.java
@@ -50,6 +50,7 @@
   private boolean useDefaultRc = true;
   private int errorCode = 0;
   private List<String> flags;
+  private boolean shouldFail;
 
   /**
    * Creates the BuilderRunner
@@ -106,6 +107,16 @@
   }
 
   /**
+   * Expect Bazel to fail. This method is needed when the exact error code can not be specified.
+   *
+   * @return this BuildRunner instance
+   */
+  public BuilderRunner shouldFail() {
+    this.shouldFail = true;
+    return this;
+  }
+
+  /**
    * Sets timeout value for the Bazel process invocation. If not called, default value is used,
    * which is calculated from the test parameters. See {@link
    * BlackBoxTestContext#getTestTimeoutMillis()}. If the invocation time exceeds timeout, {@link
@@ -271,6 +282,7 @@
             // we need to allow the error output stream be not empty
             .setExpectedEmptyError(false)
             .setExpectedExitCode(errorCode)
+            .setExpectedToFail(shouldFail)
             .build();
     return new ProcessRunner(parameters, executorService).runSynchronously();
   }
diff --git a/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessParameters.java b/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessParameters.java
index cef17f4..0113b30 100644
--- a/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessParameters.java
+++ b/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessParameters.java
@@ -34,6 +34,8 @@
 
   abstract int expectedExitCode();
 
+  abstract boolean expectedToFail();
+
   abstract boolean expectedEmptyError();
 
   abstract Optional<ImmutableMap<String, String>> environment();
@@ -48,6 +50,7 @@
     return new AutoValue_ProcessParameters.Builder()
         .setExpectedExitCode(0)
         .setExpectedEmptyError(true)
+        .setExpectedToFail(false)
         .setTimeoutMillis(30 * 1000)
         .setArguments();
   }
@@ -70,6 +73,8 @@
 
     public abstract Builder setExpectedExitCode(int value);
 
+    public abstract Builder setExpectedToFail(boolean value);
+
     public abstract Builder setExpectedEmptyError(boolean value);
 
     public abstract Builder setEnvironment(ImmutableMap<String, String> map);
diff --git a/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessRunner.java b/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessRunner.java
index 2436c8f..bfff4d4 100644
--- a/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessRunner.java
+++ b/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessRunner.java
@@ -34,7 +34,6 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 import java.util.logging.Logger;
-import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 
 /**
@@ -70,7 +69,7 @@
     commandParts.add(parameters.name());
     commandParts.addAll(args);
 
-    logger.info("Running: " + commandParts.stream().collect(Collectors.joining(" ")));
+    logger.info("Running: " + String.join(" ", commandParts));
 
     ProcessBuilder processBuilder = new ProcessBuilder(commandParts);
     processBuilder.directory(parameters.workingDirectory());
@@ -107,12 +106,25 @@
               ? outReader.get()
               : Files.readAllLines(parameters.redirectOutput().get());
 
-      if (parameters.expectedExitCode() != process.exitValue()) {
+      int exitValue = process.exitValue();
+      boolean processFailed = exitValue != 0;
+      boolean expectedToFail = parameters.expectedToFail() || parameters.expectedExitCode() != 0;
+      if (processFailed != expectedToFail) {
+        // We want to check the exact exit code if it was explicitly set to something;
+        if (parameters.expectedExitCode() != 0 && parameters.expectedExitCode() != exitValue) {
+          throw new ProcessRunnerException(
+              String.format(
+                  "Expected exit code %d, but found %d.\nError: %s\nOutput: %s",
+                  parameters.expectedExitCode(),
+                  exitValue,
+                  StringUtilities.joinLines(err),
+                  StringUtilities.joinLines(out)));
+        }
         throw new ProcessRunnerException(
             String.format(
-                "Expected exit code %d, but found %d.\nError: %s\nOutput: %s",
-                parameters.expectedExitCode(),
-                process.exitValue(),
+                "Expected to %s, but %s.\nError: %s\nOutput: %s",
+                expectedToFail ? "fail" : "succeed",
+                processFailed ? "failed" : "succeeded",
                 StringUtilities.joinLines(err),
                 StringUtilities.joinLines(out)));
       }
@@ -123,7 +135,7 @@
               "Expected empty error stream, but found: " + StringUtilities.joinLines(err));
         }
       }
-      return ProcessResult.create(parameters.expectedExitCode(), out, err);
+      return ProcessResult.create(exitValue, out, err);
     } finally {
       process.destroy();
     }
diff --git a/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessRunnerTest.java b/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessRunnerTest.java
index ed4cb78..c1355ec 100644
--- a/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/blackbox/framework/ProcessRunnerTest.java
@@ -80,7 +80,7 @@
   }
 
   @Test
-  public void testFailure() throws Exception {
+  public void testFailureWithCode() throws Exception {
     Files.write(
         path, createScriptText(/* exit code */ 124, /* output */ null, /* error */ "Failure"));
 
@@ -94,6 +94,20 @@
   }
 
   @Test
+  public void testFailure() throws Exception {
+    Files.write(
+        path, createScriptText(/* exit code */ 124, /* output */ null, /* error */ "Failure"));
+
+    ProcessParameters parameters =
+        createBuilder().setExpectedToFail(true).setExpectedEmptyError(false).build();
+    ProcessResult result = new ProcessRunner(parameters, executorService).runSynchronously();
+
+    assertThat(result.exitCode()).isEqualTo(124);
+    assertThat(result.outString()).isEmpty();
+    assertThat(result.errString()).isEqualTo("Failure");
+  }
+
+  @Test
   public void testTimeout() throws Exception {
     // Windows script to sleep 5 seconds, so that we can test timeout.
     // This script finds PowerShell using %systemroot% variable, which we assume is always