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