BuildEventStreamer supports status INCOMPLETE (--nokeep_going) and INTERNAL (catastrophe)

  - Add and Report AbortReason.INCOMPLETE when the build is incomplete due to an earlier build failure (--nokeep_going).
  - Report AbortReason.INTERNAL when the build is incomplete due to a catastrophic failure.
  - If multiple BuildEventStreamer AbortReasons are reported then the last one wins (no change), but all reasons are reported the Aborted event description (new behavior).

RELNOTES: none

PiperOrigin-RevId: 248037389
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java b/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java
index 645f89e..6da3ca7 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java
@@ -48,6 +48,7 @@
 import com.google.devtools.build.lib.buildeventstream.BuildEventId;
 import com.google.devtools.build.lib.buildeventstream.BuildEventProtocolOptions;
 import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos;
+import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.Aborted.AbortReason;
 import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.NamedSetOfFilesId;
 import com.google.devtools.build.lib.buildeventstream.BuildEventTransport;
 import com.google.devtools.build.lib.buildeventstream.BuildEventTransportClosedEvent;
@@ -59,10 +60,12 @@
 import com.google.devtools.build.lib.buildeventstream.transports.BuildEventStreamOptions;
 import com.google.devtools.build.lib.buildtool.BuildResult;
 import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
+import com.google.devtools.build.lib.buildtool.buildevent.NoAnalyzeEvent;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetView;
 import com.google.devtools.build.lib.testutil.FoundationTestCase;
+import com.google.devtools.build.lib.util.ExitCode;
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -79,6 +82,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.LockSupport;
+import javax.annotation.Nullable;
 import org.apache.commons.lang.time.StopWatch;
 import org.junit.After;
 import org.junit.Before;
@@ -1178,4 +1182,141 @@
     assertThat(transportedEvents).contains(SUCCESSFUL_ACTION_EXECUTED_EVENT);
     assertThat(transportedEvents).contains(failedActionExecutedEvent);
   }
+
+  @Test
+  public void testBuildIncomplete() {
+    BuildEventId buildEventId = testId("abort_expected");
+    BuildEvent startEvent =
+        new GenericBuildEvent(
+            BuildEventId.buildStartedId(),
+            ImmutableSet.of(
+                buildEventId, ProgressEvent.INITIAL_PROGRESS_UPDATE, BuildEventId.buildFinished()));
+    BuildCompleteEvent buildCompleteEvent =
+        buildCompleteEvent(ExitCode.BUILD_FAILURE, true, null, false);
+
+    streamer.buildEvent(startEvent);
+    streamer.buildEvent(buildCompleteEvent);
+    streamer.close();
+
+    BuildEventStreamProtos.BuildEvent aborted = getBepEvent(buildEventId);
+    assertThat(aborted).isNotNull();
+    assertThat(aborted.hasAborted()).isNotNull();
+    assertThat(aborted.getAborted().getReason()).isEqualTo(AbortReason.INCOMPLETE);
+    assertThat(aborted.getAborted().getDescription()).isEmpty();
+  }
+
+  @Test
+  public void testBuildCrash() {
+    BuildEventId buildEventId = testId("abort_expected");
+    BuildEvent startEvent =
+        new GenericBuildEvent(
+            BuildEventId.buildStartedId(),
+            ImmutableSet.of(
+                buildEventId, ProgressEvent.INITIAL_PROGRESS_UPDATE, BuildEventId.buildFinished()));
+    BuildCompleteEvent buildCompleteEvent =
+        buildCompleteEvent(ExitCode.BUILD_FAILURE, true, new RuntimeException(), false);
+
+    streamer.buildEvent(startEvent);
+    streamer.buildEvent(buildCompleteEvent);
+    streamer.close();
+
+    BuildEventStreamProtos.BuildEvent aborted = getBepEvent(buildEventId);
+    assertThat(aborted).isNotNull();
+    assertThat(aborted.hasAborted()).isNotNull();
+    assertThat(aborted.getAborted().getReason()).isEqualTo(AbortReason.INTERNAL);
+    assertThat(aborted.getAborted().getDescription()).isEmpty();
+  }
+
+  @Test
+  public void testBuildCatastrophe() {
+    BuildEventId buildEventId = testId("abort_expected");
+    BuildEvent startEvent =
+        new GenericBuildEvent(
+            BuildEventId.buildStartedId(),
+            ImmutableSet.of(
+                buildEventId, ProgressEvent.INITIAL_PROGRESS_UPDATE, BuildEventId.buildFinished()));
+    BuildCompleteEvent buildCompleteEvent =
+        buildCompleteEvent(ExitCode.BUILD_FAILURE, true, null, true);
+
+    streamer.buildEvent(startEvent);
+    streamer.buildEvent(buildCompleteEvent);
+    streamer.close();
+
+    BuildEventStreamProtos.BuildEvent aborted = getBepEvent(buildEventId);
+    assertThat(aborted).isNotNull();
+    assertThat(aborted.hasAborted()).isNotNull();
+    assertThat(aborted.getAborted().getReason()).isEqualTo(AbortReason.INTERNAL);
+    assertThat(aborted.getAborted().getDescription()).isEmpty();
+  }
+
+  @Test
+  public void testStreamAbortedWithTimeout() {
+    BuildEventId buildEventId = testId("abort_expected");
+    BuildEvent startEvent =
+        new GenericBuildEvent(
+            BuildEventId.buildStartedId(),
+            ImmutableSet.of(
+                buildEventId, ProgressEvent.INITIAL_PROGRESS_UPDATE, BuildEventId.buildFinished()));
+
+    streamer.buildEvent(startEvent);
+    streamer.close(AbortReason.TIME_OUT);
+
+    BuildEventStreamProtos.BuildEvent aborted0 = getBepEvent(buildEventId);
+    assertThat(aborted0).isNotNull();
+    assertThat(aborted0.hasAborted()).isNotNull();
+    assertThat(aborted0.getAborted().getReason()).isEqualTo(AbortReason.TIME_OUT);
+    assertThat(aborted0.getAborted().getDescription()).isEmpty();
+
+    BuildEventStreamProtos.BuildEvent aborted1 = getBepEvent(BuildEventId.buildFinished());
+    assertThat(aborted1).isNotNull();
+    assertThat(aborted1.hasAborted()).isNotNull();
+    assertThat(aborted1.getAborted().getReason()).isEqualTo(AbortReason.TIME_OUT);
+    assertThat(aborted1.getAborted().getDescription()).isEmpty();
+  }
+
+  @Test
+  public void testBuildFailureMultipleReasons() {
+    BuildEventId buildEventId = testId("abort_expected");
+    BuildEvent startEvent =
+        new GenericBuildEvent(
+            BuildEventId.buildStartedId(),
+            ImmutableSet.of(
+                buildEventId, ProgressEvent.INITIAL_PROGRESS_UPDATE, BuildEventId.buildFinished()));
+    BuildCompleteEvent buildCompleteEvent =
+        buildCompleteEvent(ExitCode.BUILD_FAILURE, false, new RuntimeException(), false);
+
+    streamer.buildEvent(startEvent);
+    streamer.noAnalyze(new NoAnalyzeEvent());
+    streamer.buildEvent(buildCompleteEvent);
+    streamer.close();
+
+    BuildEventStreamProtos.BuildEvent aborted = getBepEvent(buildEventId);
+    assertThat(aborted).isNotNull();
+    assertThat(aborted.hasAborted()).isNotNull();
+    assertThat(aborted.getAborted().getReason()).isEqualTo(AbortReason.INTERNAL);
+    assertThat(aborted.getAborted().getDescription())
+        .isEqualTo("Multiple abort reasons reported: [NO_ANALYZE, INTERNAL]");
+  }
+
+  @Nullable
+  private BuildEventStreamProtos.BuildEvent getBepEvent(BuildEventId buildEventId) {
+    return transport.getEventProtos().stream()
+        .filter(e -> e.getId().equals(buildEventId.asStreamProto()))
+        .findFirst()
+        .orElse(null);
+  }
+
+  private BuildCompleteEvent buildCompleteEvent(
+      ExitCode exitCode, boolean stopOnFailure, Throwable crash, boolean catastrophe) {
+    BuildResult result = new BuildResult(0);
+    result.setExitCondition(exitCode);
+    result.setStopOnFirstFailure(stopOnFailure);
+    if (catastrophe) {
+      result.setCatastrophe();
+    }
+    if (crash != null) {
+      result.setUnhandledThrowable(crash);
+    }
+    return new BuildCompleteEvent(result);
+  }
 }