blob: 8d41a6324e26d81e462325b0b2b62d71d28ebe33 [file] [log] [blame]
// Copyright 2020 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.runtime;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.Mockito.mock;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.buildtool.BuildRequest;
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.BuildStartingEvent;
import com.google.devtools.build.lib.buildtool.buildevent.MainRepoMappingComputationStartingEvent;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventKind;
import com.google.devtools.build.lib.testutil.ManualClock;
import com.google.devtools.build.lib.util.io.OutErr;
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for output generated by {@link UiEventHandler}. */
@RunWith(TestParameterInjector.class)
public final class UiEventHandlerStdOutAndStdErrTest {
private static final BuildCompleteEvent BUILD_COMPLETE_EVENT =
new BuildCompleteEvent(new BuildResult(/*startTimeMillis=*/ 0));
@TestParameter private TestedOutput testedOutput;
private UiEventHandler uiEventHandler;
private FlushCollectingOutputStream output;
private EventKind eventKind;
private enum TestedOutput {
STDOUT,
STDERR;
}
@Before
public void createUiEventHandler() {
UiOptions uiOptions = new UiOptions();
uiOptions.eventFilters = ImmutableList.of();
createUiEventHandler(uiOptions);
}
private void createUiEventHandler(UiOptions uiOptions) {
output = new FlushCollectingOutputStream();
OutErr outErr = null;
switch (testedOutput) {
case STDOUT:
outErr = OutErr.create(/* out= */ output, /* err= */ mock(OutputStream.class));
eventKind = EventKind.STDOUT;
break;
case STDERR:
outErr = OutErr.create(/* out= */ mock(OutputStream.class), /* err= */ output);
eventKind = EventKind.STDERR;
break;
}
uiEventHandler =
new UiEventHandler(outErr, uiOptions, new ManualClock(), /* workspacePathFragment= */ null);
uiEventHandler.mainRepoMappingComputationStarted(new MainRepoMappingComputationStartingEvent());
uiEventHandler.buildStarted(
BuildStartingEvent.create(
"outputFileSystemType",
/* usesInMemoryFileSystem= */ false,
mock(BuildRequest.class),
/* workspace= */ null,
"/pwd"));
}
@Test
public void buildComplete_outputsNothing() {
uiEventHandler.buildComplete(BUILD_COMPLETE_EVENT);
output.assertFlushed();
}
@Test
public void buildComplete_flushesBufferedMessage() {
uiEventHandler.handle(output("hello"));
uiEventHandler.buildComplete(BUILD_COMPLETE_EVENT);
output.assertFlushed("hello");
}
@Test
public void buildComplete_emptyBuffer_outputsNothing() {
uiEventHandler.handle(output(""));
uiEventHandler.buildComplete(BUILD_COMPLETE_EVENT);
output.assertFlushed();
}
@Test
public void handleOutputEvent_buffersWithoutNewline() {
uiEventHandler.handle(output("hello"));
output.assertFlushed();
}
@Test
public void handleOutputEvent_concatenatesInBuffer() {
uiEventHandler.handle(output("hello "));
uiEventHandler.handle(output("there"));
uiEventHandler.buildComplete(BUILD_COMPLETE_EVENT);
output.assertFlushed("hello there");
}
@Test
public void handleOutputEvent_flushesOnNewline() {
uiEventHandler.handle(output("hello\n"));
output.assertFlushed("hello\n");
}
@Test
public void handleOutputEvent_flushesOnlyUntilNewline() {
uiEventHandler.handle(output("hello\nworld"));
output.assertFlushed("hello\n");
}
@Test
public void handleOutputEvent_flushesUntilLastNewline() {
uiEventHandler.handle(output("hello\nto\neveryone"));
output.assertFlushed("hello\nto\n");
}
@Test
public void handleOutputEvent_flushesMultiLineMessageAtOnce() {
uiEventHandler.handle(output("hello\neveryone\n"));
output.assertFlushed("hello\neveryone\n");
}
@Test
public void handleOutputEvent_concatenatesBufferBeforeFlushingOnNewline() {
uiEventHandler.handle(output("hello"));
uiEventHandler.handle(output(" there!\nmore text"));
output.assertFlushed("hello there!\n");
}
// This test only exercises progress bar code when testing stderr output, since we don't make
// any assertions on stderr (where the progress bar is written) when testing stdout.
@Test
public void noChangeOnUnflushedWrite() {
UiOptions uiOptions = new UiOptions();
uiOptions.showProgress = true;
uiOptions.useCursesEnum = UiOptions.UseCurses.YES;
uiOptions.eventFilters = ImmutableList.of();
createUiEventHandler(uiOptions);
if (testedOutput == TestedOutput.STDERR) {
assertThat(output.flushed).hasSize(2);
output.flushed.clear();
}
// Unterminated strings are saved in memory and not pushed out at all.
assertThat(output.flushed).isEmpty();
assertThat(output.writtenSinceFlush).isEmpty();
}
@Test
public void buildCompleteMessageDoesntOverrideError() {
Assume.assumeTrue(testedOutput == TestedOutput.STDERR);
UiOptions uiOptions = new UiOptions();
uiOptions.showProgress = true;
uiOptions.useCursesEnum = UiOptions.UseCurses.YES;
uiOptions.eventFilters = ImmutableList.of();
createUiEventHandler(uiOptions);
uiEventHandler.buildComplete(BUILD_COMPLETE_EVENT);
uiEventHandler.handle(Event.error("Show me this!"));
uiEventHandler.afterCommand(new AfterCommandEvent());
assertThat(output.flushed.size()).isEqualTo(5);
assertThat(output.flushed.get(3)).contains("Show me this!");
assertThat(output.flushed.get(4)).doesNotContain("\033[1A\033[K");
}
private Event output(String message) {
return Event.of(eventKind, message);
}
private static class FlushCollectingOutputStream extends OutputStream {
private final List<String> flushed = new ArrayList<>();
private String writtenSinceFlush = "";
@Override
public void write(int b) throws IOException {
write(new byte[] {(byte) b});
}
@Override
public void write(byte[] bytes, int offset, int len) {
writtenSinceFlush += new String(Arrays.copyOfRange(bytes, offset, offset + len), UTF_8);
}
@Override
public void flush() {
// Ignore inconsequential extra flushes.
if (!writtenSinceFlush.isEmpty()) {
flushed.add(writtenSinceFlush);
}
writtenSinceFlush = "";
}
private void assertFlushed(String... messages) {
assertThat(writtenSinceFlush).isEmpty();
assertThat(flushed).containsExactlyElementsIn(messages);
}
}
}