/*
* Copyright (C) 2012 The Guava Authors
*
* 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.common.io;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.io.TestOption.AVAILABLE_ALWAYS_ZERO;
import static com.google.common.io.TestOption.CLOSE_THROWS;
import static com.google.common.io.TestOption.OPEN_THROWS;
import static com.google.common.io.TestOption.READ_THROWS;
import static com.google.common.io.TestOption.SKIP_THROWS;
import static com.google.common.io.TestOption.WRITE_THROWS;
import static org.junit.Assert.assertArrayEquals;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.hash.Hashing;
import com.google.common.testing.TestLogHandler;
import junit.framework.TestSuite;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.EnumSet;
/**
* Tests for the default implementations of {@code ByteSource} methods.
*
* @author Colin Decker
*/
public class ByteSourceTest extends IoTestCase {
public static TestSuite suite() {
TestSuite suite = new TestSuite();
suite.addTest(ByteSourceTester.tests("ByteSource.wrap[byte[]]",
SourceSinkFactories.byteArraySourceFactory(), true));
suite.addTest(ByteSourceTester.tests("ByteSource.empty[]",
SourceSinkFactories.emptyByteSourceFactory(), true));
suite.addTestSuite(ByteSourceTest.class);
return suite;
}
private static final byte[] bytes = newPreFilledByteArray(10000);
private TestByteSource source;
@Override
protected void setUp() throws Exception {
source = new TestByteSource(bytes);
}
public void testOpenBufferedStream() throws IOException {
InputStream in = source.openBufferedStream();
assertTrue(source.wasStreamOpened());
assertFalse(source.wasStreamClosed());
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteStreams.copy(in, out);
in.close();
out.close();
assertTrue(source.wasStreamClosed());
assertArrayEquals(bytes, out.toByteArray());
}
public void testSize() throws IOException {
assertEquals(bytes.length, source.size());
assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
// test that we can get the size even if skip() isn't supported
assertEquals(bytes.length, new TestByteSource(bytes, SKIP_THROWS).size());
// test that we can get the size even if available() always returns zero
assertEquals(bytes.length, new TestByteSource(bytes, AVAILABLE_ALWAYS_ZERO).size());
}
public void testCopyTo_outputStream() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
assertEquals(bytes.length, source.copyTo(out));
assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
assertArrayEquals(bytes, out.toByteArray());
}
public void testCopyTo_byteSink() throws IOException {
TestByteSink sink = new TestByteSink();
assertFalse(sink.wasStreamOpened() || sink.wasStreamClosed());
assertEquals(bytes.length, source.copyTo(sink));
assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
assertTrue(sink.wasStreamOpened() && sink.wasStreamClosed());
assertArrayEquals(bytes, sink.getBytes());
}
public void testRead_toArray() throws IOException {
assertArrayEquals(bytes, source.read());
assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
}
public void testRead_withProcessor() throws IOException {
final byte[] processedBytes = new byte[bytes.length];
ByteProcessor<byte[]> processor = new ByteProcessor<byte[]>() {
int pos;
@Override
public boolean processBytes(byte[] buf, int off, int len) throws IOException {
System.arraycopy(buf, off, processedBytes, pos, len);
pos += len;
return true;
}
@Override
public byte[] getResult() {
return processedBytes;
}
};
source.read(processor);
assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
assertArrayEquals(bytes, processedBytes);
}
public void testRead_withProcessor_stopsOnFalse() throws IOException {
ByteProcessor<Void> processor = new ByteProcessor<Void>() {
boolean firstCall = true;
@Override
public boolean processBytes(byte[] buf, int off, int len) throws IOException {
assertTrue("consume() called twice", firstCall);
firstCall = false;
return false;
}
@Override
public Void getResult() {
return null;
}
};
source.read(processor);
assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
}
public void testHash() throws IOException {
ByteSource byteSource = new TestByteSource("hamburger\n".getBytes(Charsets.US_ASCII));
// Pasted this expected string from `echo hamburger | md5sum`
assertEquals("cfa0c5002275c90508338a5cdb2a9781", byteSource.hash(Hashing.md5()).toString());
}
public void testContentEquals() throws IOException {
assertTrue(source.contentEquals(source));
assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
ByteSource equalSource = new TestByteSource(bytes);
assertTrue(source.contentEquals(equalSource));
assertTrue(new TestByteSource(bytes).contentEquals(source));
ByteSource fewerBytes = new TestByteSource(newPreFilledByteArray(bytes.length / 2));
assertFalse(source.contentEquals(fewerBytes));
byte[] copy = bytes.clone();
copy[9876] = 1;
ByteSource oneByteOff = new TestByteSource(copy);
assertFalse(source.contentEquals(oneByteOff));
}
public void testSlice() throws IOException {
// Test preconditions
try {
source.slice(-1, 10);
fail();
} catch (IllegalArgumentException expected) {
}
try {
source.slice(0, -1);
fail();
} catch (IllegalArgumentException expected) {
}
assertCorrectSlice(0, 0, 0, 0);
assertCorrectSlice(0, 0, 1, 0);
assertCorrectSlice(100, 0, 10, 10);
assertCorrectSlice(100, 0, 100, 100);
assertCorrectSlice(100, 5, 10, 10);
assertCorrectSlice(100, 5, 100, 95);
assertCorrectSlice(100, 100, 0, 0);
assertCorrectSlice(100, 100, 10, 0);
try {
assertCorrectSlice(100, 101, 10, 0);
fail();
} catch (EOFException expected) {
}
}
/**
* @param input the size of the input source
* @param offset the first argument to {@link ByteSource#slice}
* @param length the second argument to {@link ByteSource#slice}
* @param expectRead the number of bytes we expect to read
*/
private static void assertCorrectSlice(
int input, int offset, long length, int expectRead) throws IOException {
checkArgument(expectRead == (int) Math.max(0, Math.min(input, offset + length) - offset));
byte[] expected = newPreFilledByteArray(offset, expectRead);
ByteSource source = new TestByteSource(newPreFilledByteArray(input));
ByteSource slice = source.slice(offset, length);
assertArrayEquals(expected, slice.read());
}
public void testCopyToStream_doesNotCloseThatStream() throws IOException {
TestOutputStream out = new TestOutputStream(ByteStreams.nullOutputStream());
assertFalse(out.closed());
source.copyTo(out);
assertFalse(out.closed());
}
public void testClosesOnErrors_copyingToByteSinkThatThrows() {
for (TestOption option : EnumSet.of(OPEN_THROWS, WRITE_THROWS, CLOSE_THROWS)) {
TestByteSource okSource = new TestByteSource(bytes);
try {
okSource.copyTo(new TestByteSink(option));
fail();
} catch (IOException expected) {
}
// ensure stream was closed IF it was opened (depends on implementation whether or not it's
// opened at all if sink.newOutputStream() throws).
assertTrue("stream not closed when copying to sink with option: " + option,
!okSource.wasStreamOpened() || okSource.wasStreamClosed());
}
}
public void testClosesOnErrors_whenReadThrows() {
TestByteSource failSource = new TestByteSource(bytes, READ_THROWS);
try {
failSource.copyTo(new TestByteSink());
fail();
} catch (IOException expected) {
}
assertTrue(failSource.wasStreamClosed());
}
public void testClosesOnErrors_copyingToOutputStreamThatThrows() {
TestByteSource okSource = new TestByteSource(bytes);
try {
OutputStream out = new TestOutputStream(ByteStreams.nullOutputStream(), WRITE_THROWS);
okSource.copyTo(out);
fail();
} catch (IOException expected) {
}
assertTrue(okSource.wasStreamClosed());
}
public void testConcat() throws IOException {
ByteSource b1 = ByteSource.wrap(new byte[] {0, 1, 2, 3});
ByteSource b2 = ByteSource.wrap(new byte[0]);
ByteSource b3 = ByteSource.wrap(new byte[] {4, 5});
byte[] expected = {0, 1, 2, 3, 4, 5};
assertArrayEquals(expected,
ByteSource.concat(ImmutableList.of(b1, b2, b3)).read());
assertArrayEquals(expected,
ByteSource.concat(b1, b2, b3).read());
assertArrayEquals(expected,
ByteSource.concat(ImmutableList.of(b1, b2, b3).iterator()).read());
assertEquals(expected.length, ByteSource.concat(b1, b2, b3).size());
assertFalse(ByteSource.concat(b1, b2, b3).isEmpty());
ByteSource emptyConcat = ByteSource.concat(ByteSource.empty(), ByteSource.empty());
assertTrue(emptyConcat.isEmpty());
assertEquals(0, emptyConcat.size());
}
public void testConcat_infiniteIterable() throws IOException {
ByteSource source = ByteSource.wrap(new byte[] {0, 1, 2, 3});
Iterable<ByteSource> cycle = Iterables.cycle(ImmutableList.of(source));
ByteSource concatenated = ByteSource.concat(cycle);
byte[] expected = {0, 1, 2, 3, 0, 1, 2, 3};
assertArrayEquals(expected, concatenated.slice(0, 8).read());
}
private static final ByteSource BROKEN_CLOSE_SOURCE
= new TestByteSource(new byte[10], CLOSE_THROWS);
private static final ByteSource BROKEN_OPEN_SOURCE
= new TestByteSource(new byte[10], OPEN_THROWS);
private static final ByteSource BROKEN_READ_SOURCE
= new TestByteSource(new byte[10], READ_THROWS);
private static final ByteSink BROKEN_CLOSE_SINK
= new TestByteSink(CLOSE_THROWS);
private static final ByteSink BROKEN_OPEN_SINK
= new TestByteSink(OPEN_THROWS);
private static final ByteSink BROKEN_WRITE_SINK
= new TestByteSink(WRITE_THROWS);
private static final ImmutableSet<ByteSource> BROKEN_SOURCES
= ImmutableSet.of(BROKEN_CLOSE_SOURCE, BROKEN_OPEN_SOURCE, BROKEN_READ_SOURCE);
private static final ImmutableSet<ByteSink> BROKEN_SINKS
= ImmutableSet.of(BROKEN_CLOSE_SINK, BROKEN_OPEN_SINK, BROKEN_WRITE_SINK);
public void testCopyExceptions() {
if (!Closer.SuppressingSuppressor.isAvailable()) {
// test that exceptions are logged
TestLogHandler logHandler = new TestLogHandler();
Closeables.logger.addHandler(logHandler);
try {
for (ByteSource in : BROKEN_SOURCES) {
runFailureTest(in, newNormalByteSink());
assertTrue(logHandler.getStoredLogRecords().isEmpty());
runFailureTest(in, BROKEN_CLOSE_SINK);
assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, getAndResetRecords(logHandler));
}
for (ByteSink out : BROKEN_SINKS) {
runFailureTest(newNormalByteSource(), out);
assertTrue(logHandler.getStoredLogRecords().isEmpty());
runFailureTest(BROKEN_CLOSE_SOURCE, out);
assertEquals(1, getAndResetRecords(logHandler));
}
for (ByteSource in : BROKEN_SOURCES) {
for (ByteSink out : BROKEN_SINKS) {
runFailureTest(in, out);
assertTrue(getAndResetRecords(logHandler) <= 1);
}
}
} finally {
Closeables.logger.removeHandler(logHandler);
}
} else {
// test that exceptions are suppressed
for (ByteSource in : BROKEN_SOURCES) {
int suppressed = runSuppressionFailureTest(in, newNormalByteSink());
assertEquals(0, suppressed);
suppressed = runSuppressionFailureTest(in, BROKEN_CLOSE_SINK);
assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, suppressed);
}
for (ByteSink out : BROKEN_SINKS) {
int suppressed = runSuppressionFailureTest(newNormalByteSource(), out);
assertEquals(0, suppressed);
suppressed = runSuppressionFailureTest(BROKEN_CLOSE_SOURCE, out);
assertEquals(1, suppressed);
}
for (ByteSource in : BROKEN_SOURCES) {
for (ByteSink out : BROKEN_SINKS) {
int suppressed = runSuppressionFailureTest(in, out);
assertTrue(suppressed <= 1);
}
}
}
}
private static int getAndResetRecords(TestLogHandler logHandler) {
int records = logHandler.getStoredLogRecords().size();
logHandler.clear();
return records;
}
private static void runFailureTest(ByteSource in, ByteSink out) {
try {
in.copyTo(out);
fail();
} catch (IOException expected) {
}
}
/**
* @return the number of exceptions that were suppressed on the expected thrown exception
*/
private static int runSuppressionFailureTest(ByteSource in, ByteSink out) {
try {
in.copyTo(out);
fail();
} catch (IOException expected) {
return CloserTest.getSuppressed(expected).length;
}
throw new AssertionError(); // can't happen
}
private static ByteSource newNormalByteSource() {
return ByteSource.wrap(new byte[10]);
}
private static ByteSink newNormalByteSink() {
return new ByteSink() {
@Override public OutputStream openStream() {
return new ByteArrayOutputStream();
}
};
}
}