Add test for SynchronizedOutputStream
Restoring test from https://github.com/bazelbuild/bazel/commit/886d7d590fae2f860788a4c8ccb4624e59fc212d, updated to deal in strings, and including a
disabled test demonstrating how binary data should work (it doesn't now).
PiperOrigin-RevId: 307671104
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/SynchronizedOutputStream.java b/src/main/java/com/google/devtools/build/lib/runtime/SynchronizedOutputStream.java
index 2c4c124..ea0909b 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/SynchronizedOutputStream.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/SynchronizedOutputStream.java
@@ -19,6 +19,7 @@
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -46,7 +47,6 @@
private byte[] buf;
private long count;
- private boolean discardAll;
// The event streamer that is supposed to flush stdout/stderr.
private BuildEventStreamer streamer;
@@ -55,7 +55,6 @@
Preconditions.checkArgument(maxChunkSize > 0);
buf = new byte[64];
count = 0;
- discardAll = false;
this.maxBufferedLength = maxBufferedLength;
this.maxChunkSize = Math.max(maxChunkSize, maxBufferedLength);
}
@@ -79,9 +78,6 @@
@Override
public void write(int oneByte) throws IOException {
- if (discardAll) {
- return;
- }
// We change the dependency with respect to that of the super class: write(int)
// now calls write(int[], int, int) which is implemented without any dependencies.
write(new byte[] {(byte) oneByte}, 0, 1);
@@ -91,11 +87,6 @@
public void write(byte[] buffer, int offset, int count) throws IOException {
// As we base the less common write(int) on this method, we may not depend not call write(int)
// directly or indirectly (e.g., by calling super.write(int[], int, int)).
- synchronized (this) {
- if (discardAll) {
- return;
- }
- }
boolean shouldFlush = false;
// As we have to do the flushing outside the synchronized block, we have to expect
// other writes to come immediately after flushing, so we have to do the check inside
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/SynchronizedOutputStreamTest.java b/src/test/java/com/google/devtools/build/lib/runtime/SynchronizedOutputStreamTest.java
new file mode 100644
index 0000000..2a27123
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/runtime/SynchronizedOutputStreamTest.java
@@ -0,0 +1,107 @@
+// 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);
+
+ 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);
+
+ List<List<String>> writes = new ArrayList<>();
+ BuildEventStreamer mockStreamer = mock(BuildEventStreamer.class);
+ doAnswer(
+ inv -> {
+ writes.add(ImmutableList.copyOf(underTest.readAndReset()));
+ return null;
+ })
+ .when(mockStreamer)
+ .flush();
+ 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);
+
+ 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);
+
+ 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);
+ }
+}