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