/* * 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.io.SourceSinkFactory.ByteSourceFactory; import static com.google.common.io.SourceSinkFactory.CharSourceFactory; import static org.junit.Assert.assertArrayEquals; import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.hash.HashCode; import com.google.common.hash.Hashing; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.util.Map; import java.util.Random; import junit.framework.TestSuite; /** * A generator of {@code TestSuite} instances for testing {@code ByteSource} implementations. * Generates tests of a all methods on a {@code ByteSource} given various inputs the source is * expected to contain as well as as sub-suites for testing the {@code CharSource} view and * {@code slice()} views in the same way. * * @author Colin Decker */ @AndroidIncompatible // Android doesn't understand tests that lack default constructors. public class ByteSourceTester extends SourceSinkTester<ByteSource, byte[], ByteSourceFactory> { private static final ImmutableList<Method> testMethods = getTestMethods(ByteSourceTester.class); static TestSuite tests(String name, ByteSourceFactory factory, boolean testAsCharSource) { TestSuite suite = new TestSuite(name); for (Map.Entry<String, String> entry : TEST_STRINGS.entrySet()) { if (testAsCharSource) { suite.addTest(suiteForString(factory, entry.getValue(), name, entry.getKey())); } else { suite.addTest(suiteForBytes(factory, entry.getValue().getBytes(Charsets.UTF_8), name, entry.getKey(), true)); } } return suite; } static TestSuite suiteForString(ByteSourceFactory factory, String string, String name, String desc) { TestSuite suite = suiteForBytes( factory, string.getBytes(Charsets.UTF_8), name, desc, true); CharSourceFactory charSourceFactory = SourceSinkFactories.asCharSourceFactory(factory); suite.addTest(CharSourceTester.suiteForString(charSourceFactory, string, name + ".asCharSource[Charset]", desc)); return suite; } static TestSuite suiteForBytes(ByteSourceFactory factory, byte[] bytes, String name, String desc, boolean slice) { TestSuite suite = new TestSuite(name + " [" + desc + "]"); for (Method method : testMethods) { suite.addTest(new ByteSourceTester(factory, bytes, name, desc, method)); } if (slice && bytes.length > 0) { // test a random slice() of the ByteSource Random random = new Random(); byte[] expected = factory.getExpected(bytes); // if expected.length == 0, off has to be 0 but length doesn't matter--result will be empty int off = expected.length == 0 ? 0 : random.nextInt(expected.length); int len = expected.length == 0 ? 4 : random.nextInt(expected.length - off); ByteSourceFactory sliced = SourceSinkFactories.asSlicedByteSourceFactory(factory, off, len); suite.addTest(suiteForBytes(sliced, bytes, name + ".slice[long, long]", desc, false)); // test a slice() of the ByteSource starting at a random offset with a length of // Long.MAX_VALUE ByteSourceFactory slicedLongMaxValue = SourceSinkFactories.asSlicedByteSourceFactory( factory, off, Long.MAX_VALUE); suite.addTest(suiteForBytes(slicedLongMaxValue, bytes, name + ".slice[long, Long.MAX_VALUE]", desc, false)); // test a slice() of the ByteSource starting at an offset greater than its size ByteSourceFactory slicedOffsetPastEnd = SourceSinkFactories.asSlicedByteSourceFactory( factory, expected.length + 2, expected.length + 10); suite.addTest(suiteForBytes(slicedOffsetPastEnd, bytes, name + ".slice[size + 2, long]", desc, false)); } return suite; } private ByteSource source; public ByteSourceTester(ByteSourceFactory factory, byte[] bytes, String suiteName, String caseDesc, Method method) { super(factory, bytes, suiteName, caseDesc, method); } @Override public void setUp() throws IOException { source = factory.createSource(data); } public void testOpenStream() throws IOException { InputStream in = source.openStream(); try { byte[] readBytes = ByteStreams.toByteArray(in); assertExpectedBytes(readBytes); } finally { in.close(); } } public void testOpenBufferedStream() throws IOException { InputStream in = source.openBufferedStream(); try { byte[] readBytes = ByteStreams.toByteArray(in); assertExpectedBytes(readBytes); } finally { in.close(); } } public void testRead() throws IOException { byte[] readBytes = source.read(); assertExpectedBytes(readBytes); } public void testCopyTo_outputStream() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); source.copyTo(out); assertExpectedBytes(out.toByteArray()); } public void testCopyTo_byteSink() throws IOException { final ByteArrayOutputStream out = new ByteArrayOutputStream(); // HERESY! but it's ok just for this I guess source.copyTo(new ByteSink() { @Override public OutputStream openStream() throws IOException { return out; } }); assertExpectedBytes(out.toByteArray()); } public void testIsEmpty() throws IOException { assertEquals(expected.length == 0, source.isEmpty()); } public void testSize() throws IOException { assertEquals(expected.length, source.size()); } public void testSizeIfKnown() throws IOException { Optional<Long> sizeIfKnown = source.sizeIfKnown(); if (sizeIfKnown.isPresent()) { assertEquals(expected.length, (long) sizeIfKnown.get()); } } public void testContentEquals() throws IOException { assertTrue(source.contentEquals(new ByteSource() { @Override public InputStream openStream() throws IOException { return new RandomAmountInputStream( new ByteArrayInputStream(expected), new Random()); } })); } public void testRead_usingByteProcessor() throws IOException { byte[] readBytes = source.read(new ByteProcessor<byte[]>() { final ByteArrayOutputStream out = new ByteArrayOutputStream(); @Override public boolean processBytes(byte[] buf, int off, int len) throws IOException { out.write(buf, off, len); return true; } @Override public byte[] getResult() { return out.toByteArray(); } }); assertExpectedBytes(readBytes); } public void testHash() throws IOException { HashCode expectedHash = Hashing.md5().hashBytes(expected); assertEquals(expectedHash, source.hash(Hashing.md5())); } public void testSlice_illegalArguments() { try { source.slice(-1, 0); fail("expected IllegalArgumentException for call to slice with offset -1: " + source); } catch (IllegalArgumentException expected) { } try { source.slice(0, -1); fail("expected IllegalArgumentException for call to slice with length -1: " + source); } catch (IllegalArgumentException expected) { } } // Test that you can not expand the readable data in a previously sliced ByteSource. public void testSlice_constrainedRange() throws IOException { long size = source.read().length; if (size >= 2) { ByteSource sliced = source.slice(1, size - 2); assertEquals(size - 2, sliced.read().length); ByteSource resliced = sliced.slice(0, size - 1); assertTrue(sliced.contentEquals(resliced)); } } private void assertExpectedBytes(byte[] readBytes) { assertArrayEquals(expected, readBytes); } }