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