Properly flush remaining buffered output at the end of the build.

Previously, this didn't happen in all cases, in particular if `aquery` output
included a newline character, e.g. if the length of a string is 10 bytes it
would be cut off.

A better, but much more involved fix would be to circumvent `UiEventHandler` for `{,a,c}query` output to stdout.

PiperOrigin-RevId: 660332325
Change-Id: I1a816028660da6598a001740bd7e3c95f9a1a098
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/UiEventHandler.java b/src/main/java/com/google/devtools/build/lib/runtime/UiEventHandler.java
index 7cf6b2e..54309b0 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/UiEventHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/UiEventHandler.java
@@ -675,6 +675,7 @@
     }
     completeBuild();
     try {
+      flushStdOutStdErrBuffers();
       terminal.resetTerminal();
       terminal.flush();
     } catch (IOException e) {
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/AqueryBuildToolTest.java b/src/test/java/com/google/devtools/build/lib/buildtool/AqueryBuildToolTest.java
index 621758b..33af63a 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/AqueryBuildToolTest.java
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/AqueryBuildToolTest.java
@@ -17,9 +17,11 @@
 import static org.junit.Assert.assertThrows;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.analysis.AnalysisProtosV2.ActionGraphContainer;
 import com.google.devtools.build.lib.buildtool.AqueryProcessor.AqueryActionFilterException;
 import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase;
 import com.google.devtools.build.lib.cmdline.TargetPattern;
+import com.google.devtools.build.lib.events.EventKind;
 import com.google.devtools.build.lib.query2.aquery.ActionGraphQueryEnvironment;
 import com.google.devtools.build.lib.query2.aquery.AqueryOptions;
 import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryFunction;
@@ -29,6 +31,9 @@
 import com.google.devtools.build.lib.runtime.CommandEnvironment;
 import com.google.devtools.build.lib.runtime.commands.AqueryCommand;
 import com.google.devtools.build.lib.server.FailureDetails.ActionQuery.Code;
+import com.google.protobuf.ExtensionRegistry;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -86,4 +91,49 @@
     assertThat(result.getDetailedExitCode().getFailureDetail().getActionQuery().getCode())
         .isEqualTo(Code.SKYFRAME_STATE_PREREQ_UNMET);
   }
+
+  @Test
+  public void testAquerySkyframeStateProtoNotCutoff() throws Exception {
+    // First, prepare and run the build.
+    write(
+        "x/BUILD",
+        """
+        genrule(
+            name = "x",
+            srcs = ["in"],
+            # This has the length 10, so it will include a 0x0a / newline character
+            # that triggers the cutoff.
+            outs = ["1234567890"],
+            cmd = "touch $(OUTS)",
+        )
+        """);
+    write("x/in", "");
+    buildTarget("//x");
+
+    // Then, run aquery and dump the action graph as of the previous skyframe state.
+    addOptions("--output=proto", "--skyframe_state");
+    CommandEnvironment env = runtimeWrapper.newCommand(AqueryCommand.class);
+    ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+    env.getReporter()
+        .addHandler(
+            event -> {
+              if (event.getKind().equals(EventKind.STDOUT)) {
+                try {
+                  stdout.write(event.getMessageBytes());
+                } catch (IOException e) {
+                  throw new IllegalStateException(e);
+                }
+              }
+            });
+
+    AqueryProcessor aqueryProcessor = new AqueryProcessor(null, TargetPattern.defaultParser());
+    BlazeCommandResult result = aqueryProcessor.dumpActionGraphFromSkyframe(env);
+    assertThat(result.isSuccess()).isTrue();
+
+    // Test whether stdout is a valid proto.
+    assertThat(stdout.size()).isGreaterThan(0);
+    ActionGraphContainer actionGraphContainer =
+        ActionGraphContainer.parseFrom(stdout.toByteArray(), ExtensionRegistry.getEmptyRegistry());
+    assertThat(actionGraphContainer.getActionsList()).isNotEmpty();
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/BUILD b/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
index 01247ce..d17b529 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/BUILD
@@ -586,14 +586,17 @@
         "//src/main/java/com/google/devtools/build/lib:runtime",
         "//src/main/java/com/google/devtools/build/lib:runtime/blaze_command_result",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
+        "//src/main/java/com/google/devtools/build/lib/events",
         "//src/main/java/com/google/devtools/build/lib/query2",
         "//src/main/java/com/google/devtools/build/lib/query2/engine",
         "//src/main/java/com/google/devtools/build/lib/runtime/commands",
+        "//src/main/protobuf:analysis_v2_java_proto",
         "//src/main/protobuf:failure_details_java_proto",
         "//src/test/java/com/google/devtools/build/lib/buildtool/util",
         "//third_party:guava",
         "//third_party:junit4",
         "//third_party:truth",
+        "//third_party/protobuf:protobuf_java",
     ],
 )
 
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/UiEventHandlerStdOutAndStdErrTest.java b/src/test/java/com/google/devtools/build/lib/runtime/UiEventHandlerStdOutAndStdErrTest.java
index 1272825..62c5860 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/UiEventHandlerStdOutAndStdErrTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/UiEventHandlerStdOutAndStdErrTest.java
@@ -241,6 +241,15 @@
     assertThat(output.flushed.get(4)).doesNotContain("\033[1A\033[K");
   }
 
+  @Test
+  public void handleOutputEvent_flushesRemainingLines() {
+    Assume.assumeTrue(testedOutput == TestedOutput.STDOUT);
+    uiEventHandler.handle(output("hello\nto\neveryone"));
+    output.assertFlushed("hello\nto\n");
+    uiEventHandler.afterCommand(new AfterCommandEvent());
+    output.assertFlushed("hello\nto\n", "everyone");
+  }
+
   private Event output(String message) {
     return Event.of(eventKind, message);
   }