diff options
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java | 44 | ||||
-rw-r--r-- | src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java | 93 |
2 files changed, 130 insertions, 7 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java b/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java index 66aaa7fe36..e10444b325 100644 --- a/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java +++ b/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java @@ -56,6 +56,7 @@ import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.events.Reporter; import com.google.devtools.build.lib.rules.extra.ExtraAction; +import com.google.devtools.build.lib.util.Pair; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -86,6 +87,7 @@ public class BuildEventStreamer implements EventHandler { private Set<BuildEventId> announcedEvents; private final Set<BuildEventId> postedEvents = new HashSet<>(); private final Set<BuildEventId> configurationsPosted = new HashSet<>(); + private List<Pair<String, String>> bufferedStdoutStderrPairs = new ArrayList<>(); private final Multimap<BuildEventId, BuildEvent> pendingEvents = HashMultimap.create(); private int progressCount; private final CountingArtifactGroupNamer artifactGroupNamer = new CountingArtifactGroupNamer(); @@ -168,6 +170,7 @@ public class BuildEventStreamer implements EventHandler { private void post(BuildEvent event) { BuildEvent linkEvent = null; BuildEventId id = event.getEventId(); + List<BuildEvent> flushEvents = null; synchronized (this) { if (announcedEvents == null) { @@ -182,6 +185,14 @@ public class BuildEventStreamer implements EventHandler { if (reporter != null) { reporter.post(new AnnounceBuildEventTransportsEvent(transports)); } + + if (!bufferedStdoutStderrPairs.isEmpty()) { + flushEvents = new ArrayList<>(bufferedStdoutStderrPairs.size()); + for (Pair<String, String> outErrPair : bufferedStdoutStderrPairs) { + flushEvents.add(flushStdoutStderrEvent(outErrPair.getFirst(), outErrPair.getSecond())); + } + } + bufferedStdoutStderrPairs = null; } else { if (!announcedEvents.contains(id)) { String out = null; @@ -215,6 +226,14 @@ public class BuildEventStreamer implements EventHandler { } transport.sendBuildEvent(event, artifactGroupNamer); } + + if (flushEvents != null) { + for (BuildEvent flushEvent : flushEvents) { + for (BuildEventTransport transport : transports) { + transport.sendBuildEvent(flushEvent, artifactGroupNamer); + } + } + } } /** Clear pending events by generating aborted events for all their requisits. */ @@ -383,8 +402,16 @@ public class BuildEventStreamer implements EventHandler { } } + private synchronized BuildEvent flushStdoutStderrEvent(String out, String err) { + BuildEvent updateEvent = ProgressEvent.progressUpdate(progressCount, out, err); + progressCount++; + announcedEvents.addAll(updateEvent.getChildrenEvents()); + postedEvents.add(updateEvent.getEventId()); + return updateEvent; + } + void flush() { - BuildEvent updateEvent; + BuildEvent updateEvent = null; synchronized (this) { String out = null; String err = null; @@ -392,13 +419,16 @@ public class BuildEventStreamer implements EventHandler { out = outErrProvider.getOut(); err = outErrProvider.getErr(); } - updateEvent = ProgressEvent.progressUpdate(progressCount, out, err); - progressCount++; - announcedEvents.addAll(updateEvent.getChildrenEvents()); - postedEvents.add(updateEvent.getEventId()); + if (announcedEvents != null) { + updateEvent = flushStdoutStderrEvent(out, err); + } else { + bufferedStdoutStderrPairs.add(Pair.of(out, err)); + } } - for (BuildEventTransport transport : transports) { - transport.sendBuildEvent(updateEvent, artifactGroupNamer); + if (updateEvent != null) { + for (BuildEventTransport transport : transports) { + transport.sendBuildEvent(updateEvent, artifactGroupNamer); + } } } 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 76272294fa..9fae49273a 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 @@ -628,4 +628,97 @@ public class BuildEventStreamerTest extends FoundationTestCase { assertThat(allEventsSeen.get(5).getEventId()).isEqualTo(BuildEventId.progressId(2)); assertThat(allEventsSeen.get(6)).isEqualTo(secondWithConfiguration); } + + @Test + public void testEarlyFlush() throws Exception { + // Verify that the streamer can handle early calls to flush() and still correctly + // reports stdout and stderr in the build-event stream. + RecordingBuildEventTransport transport = new RecordingBuildEventTransport(); + BuildEventStreamer streamer = + new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter); + BuildEventStreamer.OutErrProvider outErr = + Mockito.mock(BuildEventStreamer.OutErrProvider.class); + String firstStdoutMsg = "Some text that was written to stdout."; + String firstStderrMsg = "The UI text that bazel wrote to stderr."; + String secondStdoutMsg = "More text that was written to stdout, still before the start event."; + String secondStderrMsg = "More text written to stderr, still before the start event."; + when(outErr.getOut()).thenReturn(firstStdoutMsg).thenReturn(secondStdoutMsg); + when(outErr.getErr()).thenReturn(firstStderrMsg).thenReturn(secondStderrMsg); + BuildEvent startEvent = + new GenericBuildEvent( + testId("Initial"), + ImmutableSet.<BuildEventId>of(ProgressEvent.INITIAL_PROGRESS_UPDATE)); + + streamer.registerOutErrProvider(outErr); + streamer.flush(); + streamer.flush(); + streamer.buildEvent(startEvent); + + List<BuildEvent> eventsSeen = transport.getEvents(); + assertThat(eventsSeen).hasSize(3); + assertThat(eventsSeen.get(0).getEventId()).isEqualTo(startEvent.getEventId()); + BuildEvent progressEvent = eventsSeen.get(1); + assertThat(progressEvent.getEventId()).isEqualTo(ProgressEvent.INITIAL_PROGRESS_UPDATE); + BuildEventStreamProtos.BuildEvent progressEventProto = transport.getEventProtos().get(1); + assertThat(progressEventProto.getProgress().getStdout()).isEqualTo(firstStdoutMsg); + assertThat(progressEventProto.getProgress().getStderr()).isEqualTo(firstStderrMsg); + BuildEventStreamProtos.BuildEvent secondProgressEventProto = transport.getEventProtos().get(2); + assertThat(secondProgressEventProto.getProgress().getStdout()).isEqualTo(secondStdoutMsg); + assertThat(secondProgressEventProto.getProgress().getStderr()).isEqualTo(secondStderrMsg); + + // As there is only one progress event, the OutErrProvider should be queried + // only once per flush() for stdout and stderr. + verify(outErr, times(2)).getOut(); + verify(outErr, times(2)).getErr(); + } + + @Test + public void testEarlyFlushBadInitialEvent() throws Exception { + // Verify that an early flush works correctly with an unusual start event. + // In this case, we expect 3 events in the stream, in that order: + // - an artifical progress event as initial event, to properly link in + // all events + // - the unusal first event we have seen, and + // - a progress event reporting the flushed messages. + RecordingBuildEventTransport transport = new RecordingBuildEventTransport(); + BuildEventStreamer streamer = + new BuildEventStreamer(ImmutableSet.<BuildEventTransport>of(transport), reporter); + BuildEventStreamer.OutErrProvider outErr = + Mockito.mock(BuildEventStreamer.OutErrProvider.class); + String stdoutMsg = "Some text that was written to stdout."; + String stderrMsg = "The UI text that bazel wrote to stderr."; + when(outErr.getOut()).thenReturn(stdoutMsg); + when(outErr.getErr()).thenReturn(stderrMsg); + + BuildEvent unexpectedStartEvent = + new GenericBuildEvent(testId("unexpected start"), ImmutableSet.<BuildEventId>of()); + + streamer.registerOutErrProvider(outErr); + streamer.flush(); + streamer.buildEvent(unexpectedStartEvent); + + List<BuildEvent> eventsSeen = transport.getEvents(); + assertThat(eventsSeen).hasSize(3); + + BuildEvent initial = eventsSeen.get(0); + assertThat(initial.getEventId()).isEqualTo(ProgressEvent.INITIAL_PROGRESS_UPDATE); + BuildEventStreamProtos.BuildEvent initialProto = transport.getEventProtos().get(0); + assertThat(initialProto.getProgress().getStdout()).isEmpty(); + assertThat(initialProto.getProgress().getStderr()).isEmpty(); + + assertThat(eventsSeen.get(1).getEventId()).isEqualTo(unexpectedStartEvent.getEventId()); + assertWithMessage("Unexpected event should be linked") + .that(initial.getChildrenEvents().contains(unexpectedStartEvent.getEventId())) + .isTrue(); + + BuildEventStreamProtos.BuildEvent progressProto = transport.getEventProtos().get(2); + assertThat(progressProto.getProgress().getStdout()).isEqualTo(stdoutMsg); + assertThat(progressProto.getProgress().getStderr()).isEqualTo(stderrMsg); + assertWithMessage("flushed progress should be linked") + .that(initial.getChildrenEvents().contains(eventsSeen.get(2).getEventId())) + .isTrue(); + + verify(outErr, times(1)).getOut(); + verify(outErr, times(1)).getErr(); + } } |