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