| // 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 org.mockito.Mockito.doAnswer; | |
| import static org.mockito.Mockito.mock; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.Iterables; | |
| import java.io.IOException; | |
| import java.nio.charset.StandardCharsets; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import org.junit.Ignore; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** | |
| * Tests for {@link SynchronizedOutputStream}. | |
| * | |
| * <p>Note that, at the time of writing, these tests serve to document the actual behavior of the | |
| * class, not necessarily the desired behavior. | |
| */ | |
| @RunWith(JUnit4.class) | |
| public class SynchronizedOutputStreamTest { | |
| @Test | |
| public void testReadAndResetReturnsChunkedWritesSinceLastCall() throws IOException { | |
| SynchronizedOutputStream underTest = | |
| new SynchronizedOutputStream( | |
| /* maxBufferedLength= */ 5, /* maxChunkSize= */ 5, /* isStderr= */ false); | |
| BuildEventStreamer mockStreamer = mock(BuildEventStreamer.class); | |
| doAnswer(inv -> true).when(mockStreamer).canBufferProgressWrite(false); | |
| underTest.registerStreamer(mockStreamer); | |
| underTest.write(new byte[] {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}); | |
| assertThat(underTest.readAndReset()).containsExactly("abcde", "fgh").inOrder(); | |
| assertThat(underTest.readAndReset()).isEmpty(); | |
| underTest.write(new byte[] {'i', 'j', 'k'}); | |
| assertThat(underTest.readAndReset()).containsExactly("ijk"); | |
| } | |
| @Test | |
| public void testWriteFlushesStreamerWhenMaxBufferedLengthReached() throws IOException { | |
| SynchronizedOutputStream underTest = | |
| new SynchronizedOutputStream( | |
| /* maxBufferedLength= */ 3, /* maxChunkSize= */ 3, /* isStderr= */ false); | |
| List<List<String>> writes = new ArrayList<>(); | |
| BuildEventStreamer mockStreamer = mock(BuildEventStreamer.class); | |
| doAnswer( | |
| inv -> { | |
| writes.add(ImmutableList.copyOf(underTest.readAndReset())); | |
| return null; | |
| }) | |
| .when(mockStreamer) | |
| .flush(); | |
| doAnswer(inv -> true).when(mockStreamer).canBufferProgressWrite(false); | |
| underTest.registerStreamer(mockStreamer); | |
| underTest.write(new byte[] {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}); | |
| underTest.write(new byte[] {'i', 'j'}); | |
| underTest.write(new byte[] {'k', 'l'}); | |
| assertThat(writes) | |
| .containsExactly( | |
| ImmutableList.of("abc", "def", "gh"), | |
| // The write of {'k', 'j'} would have put the buffer over size, so {'i', 'j'} was | |
| // flushed. | |
| ImmutableList.of("ij")) | |
| .inOrder(); | |
| } | |
| @Test | |
| public void testUsesMaxOfMaxBufferedSizeAndMaxChunkSizeForChunking() throws IOException { | |
| SynchronizedOutputStream underTest = | |
| new SynchronizedOutputStream( | |
| /* maxBufferedLength= */ 2, /* maxChunkSize= */ 1, /* isStderr= */ false); | |
| BuildEventStreamer mockStreamer = mock(BuildEventStreamer.class); | |
| doAnswer(inv -> true).when(mockStreamer).canBufferProgressWrite(false); | |
| underTest.registerStreamer(mockStreamer); | |
| underTest.write(new byte[] {'a', 'b', 'c', 'd'}); | |
| assertThat(underTest.readAndReset()).containsExactly("ab", "cd").inOrder(); | |
| } | |
| // TODO(b/154242266): Make BES handle binary data correctly. | |
| @Ignore("b/154242266 - BES doesn't handle binary stdout/err correctly") | |
| @Test | |
| public void testHandlesArbitraryBinaryDataCorrectly() throws IOException { | |
| SynchronizedOutputStream underTest = | |
| new SynchronizedOutputStream( | |
| /* maxBufferedLength= */ 1, /* maxChunkSize= */ 1, /* isStderr= */ false); | |
| byte[] input = new byte[] {(byte) 0xff}; | |
| underTest.write(input); | |
| String result = Iterables.getOnlyElement(underTest.readAndReset()); | |
| // In the real code the result eventually winds up in a protobuf, which treats strings as utf8. | |
| assertThat(result.getBytes(StandardCharsets.UTF_8)).isEqualTo(input); | |
| } | |
| } |