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