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.io.SourceSinkFactory.ByteSourceFactory; 20 import static com.google.common.io.SourceSinkFactory.CharSourceFactory; 21 import static org.junit.Assert.assertArrayEquals; 22 import static org.junit.Assert.assertThrows; 23 24 import com.google.common.base.Charsets; 25 import com.google.common.base.Optional; 26 import com.google.common.collect.ImmutableList; 27 import com.google.common.hash.HashCode; 28 import com.google.common.hash.Hashing; 29 import java.io.ByteArrayInputStream; 30 import java.io.ByteArrayOutputStream; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.OutputStream; 34 import java.lang.reflect.Method; 35 import java.util.Map.Entry; 36 import java.util.Random; 37 import junit.framework.TestSuite; 38 39 /** 40 * A generator of {@code TestSuite} instances for testing {@code ByteSource} implementations. 41 * Generates tests of all methods on a {@code ByteSource} given various inputs the source is 42 * expected to contain as well as sub-suites for testing the {@code CharSource} view and {@code 43 * slice()} views in the same way. 44 * 45 * @author Colin Decker 46 */ 47 @AndroidIncompatible // TODO(b/230620681): Make this available (even though we won't run it). 48 public class ByteSourceTester extends SourceSinkTester<ByteSource, byte[], ByteSourceFactory> { 49 50 private static final ImmutableList<Method> testMethods = getTestMethods(ByteSourceTester.class); 51 tests(String name, ByteSourceFactory factory, boolean testAsCharSource)52 static TestSuite tests(String name, ByteSourceFactory factory, boolean testAsCharSource) { 53 TestSuite suite = new TestSuite(name); 54 for (Entry<String, String> entry : TEST_STRINGS.entrySet()) { 55 if (testAsCharSource) { 56 suite.addTest(suiteForString(factory, entry.getValue(), name, entry.getKey())); 57 } else { 58 suite.addTest( 59 suiteForBytes( 60 factory, entry.getValue().getBytes(Charsets.UTF_8), name, entry.getKey(), true)); 61 } 62 } 63 return suite; 64 } 65 suiteForString( ByteSourceFactory factory, String string, String name, String desc)66 static TestSuite suiteForString( 67 ByteSourceFactory factory, String string, String name, String desc) { 68 TestSuite suite = suiteForBytes(factory, string.getBytes(Charsets.UTF_8), name, desc, true); 69 CharSourceFactory charSourceFactory = SourceSinkFactories.asCharSourceFactory(factory); 70 suite.addTest( 71 CharSourceTester.suiteForString( 72 charSourceFactory, string, name + ".asCharSource[Charset]", desc)); 73 return suite; 74 } 75 suiteForBytes( ByteSourceFactory factory, byte[] bytes, String name, String desc, boolean slice)76 static TestSuite suiteForBytes( 77 ByteSourceFactory factory, byte[] bytes, String name, String desc, boolean slice) { 78 TestSuite suite = new TestSuite(name + " [" + desc + "]"); 79 for (Method method : testMethods) { 80 suite.addTest(new ByteSourceTester(factory, bytes, name, desc, method)); 81 } 82 83 if (slice && bytes.length > 0) { 84 // test a random slice() of the ByteSource 85 Random random = new Random(); 86 byte[] expected = factory.getExpected(bytes); 87 // if expected.length == 0, off has to be 0 but length doesn't matter--result will be empty 88 int off = expected.length == 0 ? 0 : random.nextInt(expected.length); 89 int len = expected.length == 0 ? 4 : random.nextInt(expected.length - off); 90 91 ByteSourceFactory sliced = SourceSinkFactories.asSlicedByteSourceFactory(factory, off, len); 92 suite.addTest(suiteForBytes(sliced, bytes, name + ".slice[long, long]", desc, false)); 93 94 // test a slice() of the ByteSource starting at a random offset with a length of 95 // Long.MAX_VALUE 96 ByteSourceFactory slicedLongMaxValue = 97 SourceSinkFactories.asSlicedByteSourceFactory(factory, off, Long.MAX_VALUE); 98 suite.addTest( 99 suiteForBytes( 100 slicedLongMaxValue, bytes, name + ".slice[long, Long.MAX_VALUE]", desc, false)); 101 102 // test a slice() of the ByteSource starting at an offset greater than its size 103 ByteSourceFactory slicedOffsetPastEnd = 104 SourceSinkFactories.asSlicedByteSourceFactory( 105 factory, expected.length + 2, expected.length + 10); 106 suite.addTest( 107 suiteForBytes(slicedOffsetPastEnd, bytes, name + ".slice[size + 2, long]", desc, false)); 108 } 109 110 return suite; 111 } 112 113 private ByteSource source; 114 ByteSourceTester( ByteSourceFactory factory, byte[] bytes, String suiteName, String caseDesc, Method method)115 public ByteSourceTester( 116 ByteSourceFactory factory, byte[] bytes, String suiteName, String caseDesc, Method method) { 117 super(factory, bytes, suiteName, caseDesc, method); 118 } 119 120 @Override setUp()121 public void setUp() throws IOException { 122 source = factory.createSource(data); 123 } 124 testOpenStream()125 public void testOpenStream() throws IOException { 126 InputStream in = source.openStream(); 127 try { 128 byte[] readBytes = ByteStreams.toByteArray(in); 129 assertExpectedBytes(readBytes); 130 } finally { 131 in.close(); 132 } 133 } 134 testOpenBufferedStream()135 public void testOpenBufferedStream() throws IOException { 136 InputStream in = source.openBufferedStream(); 137 try { 138 byte[] readBytes = ByteStreams.toByteArray(in); 139 assertExpectedBytes(readBytes); 140 } finally { 141 in.close(); 142 } 143 } 144 testRead()145 public void testRead() throws IOException { 146 byte[] readBytes = source.read(); 147 assertExpectedBytes(readBytes); 148 } 149 testCopyTo_outputStream()150 public void testCopyTo_outputStream() throws IOException { 151 ByteArrayOutputStream out = new ByteArrayOutputStream(); 152 source.copyTo(out); 153 assertExpectedBytes(out.toByteArray()); 154 } 155 testCopyTo_byteSink()156 public void testCopyTo_byteSink() throws IOException { 157 final ByteArrayOutputStream out = new ByteArrayOutputStream(); 158 // HERESY! but it's ok just for this I guess 159 source.copyTo( 160 new ByteSink() { 161 @Override 162 public OutputStream openStream() throws IOException { 163 return out; 164 } 165 }); 166 assertExpectedBytes(out.toByteArray()); 167 } 168 testIsEmpty()169 public void testIsEmpty() throws IOException { 170 assertEquals(expected.length == 0, source.isEmpty()); 171 } 172 testSize()173 public void testSize() throws IOException { 174 assertEquals(expected.length, source.size()); 175 } 176 testSizeIfKnown()177 public void testSizeIfKnown() throws IOException { 178 Optional<Long> sizeIfKnown = source.sizeIfKnown(); 179 if (sizeIfKnown.isPresent()) { 180 assertEquals(expected.length, (long) sizeIfKnown.get()); 181 } 182 } 183 testContentEquals()184 public void testContentEquals() throws IOException { 185 assertTrue( 186 source.contentEquals( 187 new ByteSource() { 188 @Override 189 public InputStream openStream() throws IOException { 190 return new RandomAmountInputStream( 191 new ByteArrayInputStream(expected), new Random()); 192 } 193 })); 194 } 195 testRead_usingByteProcessor()196 public void testRead_usingByteProcessor() throws IOException { 197 byte[] readBytes = 198 source.read( 199 new ByteProcessor<byte[]>() { 200 final ByteArrayOutputStream out = new ByteArrayOutputStream(); 201 202 @Override 203 public boolean processBytes(byte[] buf, int off, int len) throws IOException { 204 out.write(buf, off, len); 205 return true; 206 } 207 208 @Override 209 public byte[] getResult() { 210 return out.toByteArray(); 211 } 212 }); 213 214 assertExpectedBytes(readBytes); 215 } 216 testHash()217 public void testHash() throws IOException { 218 HashCode expectedHash = Hashing.md5().hashBytes(expected); 219 assertEquals(expectedHash, source.hash(Hashing.md5())); 220 } 221 testSlice_illegalArguments()222 public void testSlice_illegalArguments() { 223 assertThrows( 224 "expected IllegalArgumentException for call to slice with offset -1: " + source, 225 IllegalArgumentException.class, 226 () -> source.slice(-1, 0)); 227 228 assertThrows( 229 "expected IllegalArgumentException for call to slice with length -1: " + source, 230 IllegalArgumentException.class, 231 () -> source.slice(0, -1)); 232 } 233 234 // Test that you can not expand the readable data in a previously sliced ByteSource. testSlice_constrainedRange()235 public void testSlice_constrainedRange() throws IOException { 236 long size = source.read().length; 237 if (size >= 2) { 238 ByteSource sliced = source.slice(1, size - 2); 239 assertEquals(size - 2, sliced.read().length); 240 ByteSource resliced = sliced.slice(0, size - 1); 241 assertTrue(sliced.contentEquals(resliced)); 242 } 243 } 244 assertExpectedBytes(byte[] readBytes)245 private void assertExpectedBytes(byte[] readBytes) { 246 assertArrayEquals(expected, readBytes); 247 } 248 } 249