• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.io;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.io.TestOption.AVAILABLE_ALWAYS_ZERO;
21 import static com.google.common.io.TestOption.CLOSE_THROWS;
22 import static com.google.common.io.TestOption.OPEN_THROWS;
23 import static com.google.common.io.TestOption.READ_THROWS;
24 import static com.google.common.io.TestOption.SKIP_THROWS;
25 import static com.google.common.io.TestOption.WRITE_THROWS;
26 import static org.junit.Assert.assertArrayEquals;
27 import static org.junit.Assert.assertThrows;
28 
29 import com.google.common.base.Charsets;
30 import com.google.common.collect.ImmutableList;
31 import com.google.common.collect.ImmutableSet;
32 import com.google.common.collect.Iterables;
33 import com.google.common.hash.Hashing;
34 import com.google.common.io.Closer.LoggingSuppressor;
35 import com.google.common.primitives.UnsignedBytes;
36 import com.google.common.testing.TestLogHandler;
37 import java.io.ByteArrayOutputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.OutputStream;
41 import java.util.Arrays;
42 import java.util.EnumSet;
43 import junit.framework.TestSuite;
44 import org.checkerframework.checker.nullness.qual.Nullable;
45 
46 /**
47  * Tests for the default implementations of {@code ByteSource} methods.
48  *
49  * @author Colin Decker
50  */
51 public class ByteSourceTest extends IoTestCase {
52 
53   @AndroidIncompatible // Android doesn't understand suites whose tests lack default constructors.
suite()54   public static TestSuite suite() {
55     TestSuite suite = new TestSuite();
56     for (boolean asCharSource : new boolean[] {false, true}) {
57       suite.addTest(
58           ByteSourceTester.tests(
59               "ByteSource.wrap[byte[]]",
60               SourceSinkFactories.byteArraySourceFactory(),
61               asCharSource));
62       suite.addTest(
63           ByteSourceTester.tests(
64               "ByteSource.empty[]", SourceSinkFactories.emptyByteSourceFactory(), asCharSource));
65     }
66     suite.addTestSuite(ByteSourceTest.class);
67     return suite;
68   }
69 
70   private static final byte[] bytes = newPreFilledByteArray(10000);
71 
72   private TestByteSource source;
73 
74   @Override
setUp()75   protected void setUp() throws Exception {
76     source = new TestByteSource(bytes);
77   }
78 
testOpenBufferedStream()79   public void testOpenBufferedStream() throws IOException {
80     InputStream in = source.openBufferedStream();
81     assertTrue(source.wasStreamOpened());
82     assertFalse(source.wasStreamClosed());
83 
84     ByteArrayOutputStream out = new ByteArrayOutputStream();
85     ByteStreams.copy(in, out);
86     in.close();
87     out.close();
88 
89     assertTrue(source.wasStreamClosed());
90     assertArrayEquals(bytes, out.toByteArray());
91   }
92 
testSize()93   public void testSize() throws IOException {
94     assertEquals(bytes.length, source.size());
95     assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
96 
97     // test that we can get the size even if skip() isn't supported
98     assertEquals(bytes.length, new TestByteSource(bytes, SKIP_THROWS).size());
99 
100     // test that we can get the size even if available() always returns zero
101     assertEquals(bytes.length, new TestByteSource(bytes, AVAILABLE_ALWAYS_ZERO).size());
102   }
103 
testCopyTo_outputStream()104   public void testCopyTo_outputStream() throws IOException {
105     ByteArrayOutputStream out = new ByteArrayOutputStream();
106 
107     assertEquals(bytes.length, source.copyTo(out));
108     assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
109 
110     assertArrayEquals(bytes, out.toByteArray());
111   }
112 
testCopyTo_byteSink()113   public void testCopyTo_byteSink() throws IOException {
114     TestByteSink sink = new TestByteSink();
115 
116     assertFalse(sink.wasStreamOpened() || sink.wasStreamClosed());
117 
118     assertEquals(bytes.length, source.copyTo(sink));
119     assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
120     assertTrue(sink.wasStreamOpened() && sink.wasStreamClosed());
121 
122     assertArrayEquals(bytes, sink.getBytes());
123   }
124 
testRead_toArray()125   public void testRead_toArray() throws IOException {
126     assertArrayEquals(bytes, source.read());
127     assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
128   }
129 
testRead_withProcessor()130   public void testRead_withProcessor() throws IOException {
131     final byte[] processedBytes = new byte[bytes.length];
132     ByteProcessor<byte[]> processor =
133         new ByteProcessor<byte[]>() {
134           int pos;
135 
136           @Override
137           public boolean processBytes(byte[] buf, int off, int len) throws IOException {
138             System.arraycopy(buf, off, processedBytes, pos, len);
139             pos += len;
140             return true;
141           }
142 
143           @Override
144           public byte[] getResult() {
145             return processedBytes;
146           }
147         };
148 
149     source.read(processor);
150     assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
151 
152     assertArrayEquals(bytes, processedBytes);
153   }
154 
testRead_withProcessor_stopsOnFalse()155   public void testRead_withProcessor_stopsOnFalse() throws IOException {
156     ByteProcessor<@Nullable Void> processor =
157         new ByteProcessor<@Nullable Void>() {
158           boolean firstCall = true;
159 
160           @Override
161           public boolean processBytes(byte[] buf, int off, int len) throws IOException {
162             assertTrue("consume() called twice", firstCall);
163             firstCall = false;
164             return false;
165           }
166 
167           @Override
168           public @Nullable Void getResult() {
169             return null;
170           }
171         };
172 
173     source.read(processor);
174     assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
175   }
176 
testHash()177   public void testHash() throws IOException {
178     ByteSource byteSource = new TestByteSource("hamburger\n".getBytes(Charsets.US_ASCII));
179 
180     // Pasted this expected string from `echo hamburger | md5sum`
181     assertEquals("cfa0c5002275c90508338a5cdb2a9781", byteSource.hash(Hashing.md5()).toString());
182   }
183 
testContentEquals()184   public void testContentEquals() throws IOException {
185     assertTrue(source.contentEquals(source));
186     assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
187 
188     ByteSource equalSource = new TestByteSource(bytes);
189     assertTrue(source.contentEquals(equalSource));
190     assertTrue(new TestByteSource(bytes).contentEquals(source));
191 
192     ByteSource fewerBytes = new TestByteSource(newPreFilledByteArray(bytes.length / 2));
193     assertFalse(source.contentEquals(fewerBytes));
194 
195     byte[] copy = bytes.clone();
196     copy[9876] = 1;
197     ByteSource oneByteOff = new TestByteSource(copy);
198     assertFalse(source.contentEquals(oneByteOff));
199   }
200 
testSlice()201   public void testSlice() throws IOException {
202     // Test preconditions
203     assertThrows(IllegalArgumentException.class, () -> source.slice(-1, 10));
204 
205     assertThrows(IllegalArgumentException.class, () -> source.slice(0, -1));
206 
207     assertCorrectSlice(0, 0, 0, 0);
208     assertCorrectSlice(0, 0, 1, 0);
209     assertCorrectSlice(100, 0, 10, 10);
210     assertCorrectSlice(100, 0, 100, 100);
211     assertCorrectSlice(100, 5, 10, 10);
212     assertCorrectSlice(100, 5, 100, 95);
213     assertCorrectSlice(100, 100, 0, 0);
214     assertCorrectSlice(100, 100, 10, 0);
215     assertCorrectSlice(100, 101, 10, 0);
216   }
217 
218   /**
219    * Tests that the default slice() behavior is correct when the source is sliced starting at an
220    * offset that is greater than the current length of the source, a stream is then opened to that
221    * source, and finally additional bytes are appended to the source before the stream is read.
222    *
223    * <p>Without special handling, it's possible to have reads of the open stream start <i>before</i>
224    * the offset at which the slice is supposed to start.
225    */
226   // TODO(cgdecker): Maybe add a test for this to ByteSourceTester
testSlice_appendingAfterSlicing()227   public void testSlice_appendingAfterSlicing() throws IOException {
228     // Source of length 5
229     AppendableByteSource source = new AppendableByteSource(newPreFilledByteArray(5));
230 
231     // Slice it starting at offset 10.
232     ByteSource slice = source.slice(10, 5);
233 
234     // Open a stream to the slice.
235     InputStream in = slice.openStream();
236 
237     // Append 10 more bytes to the source.
238     source.append(newPreFilledByteArray(5, 10));
239 
240     // The stream reports no bytes... importantly, it doesn't read the byte at index 5 when it
241     // should be reading the byte at index 10.
242     // We could use a custom InputStream instead to make the read start at index 10, but since this
243     // is a racy situation anyway, this behavior seems reasonable.
244     assertEquals(-1, in.read());
245   }
246 
247   private static class AppendableByteSource extends ByteSource {
248     private byte[] bytes;
249 
AppendableByteSource(byte[] initialBytes)250     public AppendableByteSource(byte[] initialBytes) {
251       this.bytes = initialBytes.clone();
252     }
253 
254     @Override
openStream()255     public InputStream openStream() {
256       return new In();
257     }
258 
append(byte[] b)259     public void append(byte[] b) {
260       byte[] newBytes = Arrays.copyOf(bytes, bytes.length + b.length);
261       System.arraycopy(b, 0, newBytes, bytes.length, b.length);
262       bytes = newBytes;
263     }
264 
265     private class In extends InputStream {
266       private int pos;
267 
268       @Override
read()269       public int read() throws IOException {
270         byte[] b = new byte[1];
271         return read(b) == -1 ? -1 : UnsignedBytes.toInt(b[0]);
272       }
273 
274       @Override
read(byte[] b, int off, int len)275       public int read(byte[] b, int off, int len) {
276         if (pos >= bytes.length) {
277           return -1;
278         }
279 
280         int lenToRead = Math.min(len, bytes.length - pos);
281         System.arraycopy(bytes, pos, b, off, lenToRead);
282         pos += lenToRead;
283         return lenToRead;
284       }
285     }
286   }
287 
288   /**
289    * @param input the size of the input source
290    * @param offset the first argument to {@link ByteSource#slice}
291    * @param length the second argument to {@link ByteSource#slice}
292    * @param expectRead the number of bytes we expect to read
293    */
assertCorrectSlice(int input, int offset, long length, int expectRead)294   private static void assertCorrectSlice(int input, int offset, long length, int expectRead)
295       throws IOException {
296     checkArgument(expectRead == (int) Math.max(0, Math.min(input, offset + length) - offset));
297 
298     byte[] expected = newPreFilledByteArray(offset, expectRead);
299 
300     ByteSource source = new TestByteSource(newPreFilledByteArray(input));
301     ByteSource slice = source.slice(offset, length);
302 
303     assertArrayEquals(expected, slice.read());
304   }
305 
testCopyToStream_doesNotCloseThatStream()306   public void testCopyToStream_doesNotCloseThatStream() throws IOException {
307     TestOutputStream out = new TestOutputStream(ByteStreams.nullOutputStream());
308     assertFalse(out.closed());
309     source.copyTo(out);
310     assertFalse(out.closed());
311   }
312 
testClosesOnErrors_copyingToByteSinkThatThrows()313   public void testClosesOnErrors_copyingToByteSinkThatThrows() {
314     for (TestOption option : EnumSet.of(OPEN_THROWS, WRITE_THROWS, CLOSE_THROWS)) {
315       TestByteSource okSource = new TestByteSource(bytes);
316       assertThrows(IOException.class, () -> okSource.copyTo(new TestByteSink(option)));
317       // ensure stream was closed IF it was opened (depends on implementation whether or not it's
318       // opened at all if sink.newOutputStream() throws).
319       assertTrue(
320           "stream not closed when copying to sink with option: " + option,
321           !okSource.wasStreamOpened() || okSource.wasStreamClosed());
322     }
323   }
324 
testClosesOnErrors_whenReadThrows()325   public void testClosesOnErrors_whenReadThrows() {
326     TestByteSource failSource = new TestByteSource(bytes, READ_THROWS);
327     assertThrows(IOException.class, () -> failSource.copyTo(new TestByteSink()));
328     assertTrue(failSource.wasStreamClosed());
329   }
330 
testClosesOnErrors_copyingToOutputStreamThatThrows()331   public void testClosesOnErrors_copyingToOutputStreamThatThrows() throws IOException {
332     TestByteSource okSource = new TestByteSource(bytes);
333     OutputStream out = new TestOutputStream(ByteStreams.nullOutputStream(), WRITE_THROWS);
334     assertThrows(IOException.class, () -> okSource.copyTo(out));
335     assertTrue(okSource.wasStreamClosed());
336   }
337 
testConcat()338   public void testConcat() throws IOException {
339     ByteSource b1 = ByteSource.wrap(new byte[] {0, 1, 2, 3});
340     ByteSource b2 = ByteSource.wrap(new byte[0]);
341     ByteSource b3 = ByteSource.wrap(new byte[] {4, 5});
342 
343     byte[] expected = {0, 1, 2, 3, 4, 5};
344 
345     assertArrayEquals(expected, ByteSource.concat(ImmutableList.of(b1, b2, b3)).read());
346     assertArrayEquals(expected, ByteSource.concat(b1, b2, b3).read());
347     assertArrayEquals(expected, ByteSource.concat(ImmutableList.of(b1, b2, b3).iterator()).read());
348     assertEquals(expected.length, ByteSource.concat(b1, b2, b3).size());
349     assertFalse(ByteSource.concat(b1, b2, b3).isEmpty());
350 
351     ByteSource emptyConcat = ByteSource.concat(ByteSource.empty(), ByteSource.empty());
352     assertTrue(emptyConcat.isEmpty());
353     assertEquals(0, emptyConcat.size());
354   }
355 
testConcat_infiniteIterable()356   public void testConcat_infiniteIterable() throws IOException {
357     ByteSource source = ByteSource.wrap(new byte[] {0, 1, 2, 3});
358     Iterable<ByteSource> cycle = Iterables.cycle(ImmutableList.of(source));
359     ByteSource concatenated = ByteSource.concat(cycle);
360 
361     byte[] expected = {0, 1, 2, 3, 0, 1, 2, 3};
362     assertArrayEquals(expected, concatenated.slice(0, 8).read());
363   }
364 
365   private static final ByteSource BROKEN_CLOSE_SOURCE =
366       new TestByteSource(new byte[10], CLOSE_THROWS);
367   private static final ByteSource BROKEN_OPEN_SOURCE =
368       new TestByteSource(new byte[10], OPEN_THROWS);
369   private static final ByteSource BROKEN_READ_SOURCE =
370       new TestByteSource(new byte[10], READ_THROWS);
371   private static final ByteSink BROKEN_CLOSE_SINK = new TestByteSink(CLOSE_THROWS);
372   private static final ByteSink BROKEN_OPEN_SINK = new TestByteSink(OPEN_THROWS);
373   private static final ByteSink BROKEN_WRITE_SINK = new TestByteSink(WRITE_THROWS);
374 
375   private static final ImmutableSet<ByteSource> BROKEN_SOURCES =
376       ImmutableSet.of(BROKEN_CLOSE_SOURCE, BROKEN_OPEN_SOURCE, BROKEN_READ_SOURCE);
377   private static final ImmutableSet<ByteSink> BROKEN_SINKS =
378       ImmutableSet.of(BROKEN_CLOSE_SINK, BROKEN_OPEN_SINK, BROKEN_WRITE_SINK);
379 
testCopyExceptions()380   public void testCopyExceptions() {
381     if (Closer.create().suppressor instanceof LoggingSuppressor) {
382       // test that exceptions are logged
383 
384       TestLogHandler logHandler = new TestLogHandler();
385       Closeables.logger.addHandler(logHandler);
386       try {
387         for (ByteSource in : BROKEN_SOURCES) {
388           runFailureTest(in, newNormalByteSink());
389           assertTrue(logHandler.getStoredLogRecords().isEmpty());
390 
391           runFailureTest(in, BROKEN_CLOSE_SINK);
392           assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, getAndResetRecords(logHandler));
393         }
394 
395         for (ByteSink out : BROKEN_SINKS) {
396           runFailureTest(newNormalByteSource(), out);
397           assertTrue(logHandler.getStoredLogRecords().isEmpty());
398 
399           runFailureTest(BROKEN_CLOSE_SOURCE, out);
400           assertEquals(1, getAndResetRecords(logHandler));
401         }
402 
403         for (ByteSource in : BROKEN_SOURCES) {
404           for (ByteSink out : BROKEN_SINKS) {
405             runFailureTest(in, out);
406             assertTrue(getAndResetRecords(logHandler) <= 1);
407           }
408         }
409       } finally {
410         Closeables.logger.removeHandler(logHandler);
411       }
412     } else {
413       // test that exceptions are suppressed
414 
415       for (ByteSource in : BROKEN_SOURCES) {
416         int suppressed = runSuppressionFailureTest(in, newNormalByteSink());
417         assertEquals(0, suppressed);
418 
419         suppressed = runSuppressionFailureTest(in, BROKEN_CLOSE_SINK);
420         assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, suppressed);
421       }
422 
423       for (ByteSink out : BROKEN_SINKS) {
424         int suppressed = runSuppressionFailureTest(newNormalByteSource(), out);
425         assertEquals(0, suppressed);
426 
427         suppressed = runSuppressionFailureTest(BROKEN_CLOSE_SOURCE, out);
428         assertEquals(1, suppressed);
429       }
430 
431       for (ByteSource in : BROKEN_SOURCES) {
432         for (ByteSink out : BROKEN_SINKS) {
433           int suppressed = runSuppressionFailureTest(in, out);
434           assertTrue(suppressed <= 1);
435         }
436       }
437     }
438   }
439 
testSlice_returnEmptySource()440   public void testSlice_returnEmptySource() {
441     assertEquals(ByteSource.empty(), source.slice(0, 3).slice(4, 3));
442   }
443 
getAndResetRecords(TestLogHandler logHandler)444   private static int getAndResetRecords(TestLogHandler logHandler) {
445     int records = logHandler.getStoredLogRecords().size();
446     logHandler.clear();
447     return records;
448   }
449 
runFailureTest(ByteSource in, ByteSink out)450   private static void runFailureTest(ByteSource in, ByteSink out) {
451     try {
452       in.copyTo(out);
453       fail();
454     } catch (IOException expected) {
455     }
456   }
457 
458   /**
459    * @return the number of exceptions that were suppressed on the expected thrown exception
460    */
runSuppressionFailureTest(ByteSource in, ByteSink out)461   private static int runSuppressionFailureTest(ByteSource in, ByteSink out) {
462     try {
463       in.copyTo(out);
464       fail();
465     } catch (IOException expected) {
466       return CloserTest.getSuppressed(expected).length;
467     }
468     throw new AssertionError(); // can't happen
469   }
470 
newNormalByteSource()471   private static ByteSource newNormalByteSource() {
472     return ByteSource.wrap(new byte[10]);
473   }
474 
newNormalByteSink()475   private static ByteSink newNormalByteSink() {
476     return new ByteSink() {
477       @Override
478       public OutputStream openStream() {
479         return new ByteArrayOutputStream();
480       }
481     };
482   }
483 }
484