• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2011 Google Inc. All Rights Reserved.
2 
3 package com.google.common.hash;
4 
5 import com.google.common.collect.Iterables;
6 import com.google.common.collect.Lists;
7 import com.google.common.hash.AbstractStreamingHashFunction.AbstractStreamingHasher;
8 import com.google.common.hash.HashTestUtils.RandomHasherAction;
9 
10 import junit.framework.TestCase;
11 
12 import java.io.ByteArrayOutputStream;
13 import java.nio.ByteBuffer;
14 import java.nio.ByteOrder;
15 import java.nio.charset.Charset;
16 import java.util.Arrays;
17 import java.util.Collections;
18 import java.util.List;
19 import java.util.Random;
20 
21 /**
22  * Tests for AbstractHashSink.
23  *
24  * @author andreou@google.com (Dimitris Andreou)
25  */
26 public class AbstractStreamingHasherTest extends TestCase {
27   /** Test we get the HashCode that is created by the sink. Later we ignore the result */
testSanity()28   public void testSanity() {
29     Sink sink = new Sink(4);
30     assertEquals(0xDeadBeef, sink.makeHash().asInt());
31   }
32 
testBytes()33   public void testBytes() {
34     Sink sink = new Sink(4); // byte order insignificant here
35     byte[] expected = { 1, 2, 3, 4, 5, 6, 7, 8 };
36     sink.putByte((byte) 1);
37     sink.putBytes(new byte[] { 2, 3, 4, 5, 6 });
38     sink.putByte((byte) 7);
39     sink.putBytes(new byte[] { });
40     sink.putBytes(new byte[] { 8 });
41     sink.hash();
42     sink.assertInvariants(8);
43     sink.assertBytes(expected);
44   }
45 
testShort()46   public void testShort() {
47     Sink sink = new Sink(4);
48     sink.putShort((short) 0x0201);
49     sink.hash();
50     sink.assertInvariants(2);
51     sink.assertBytes(new byte[] { 1, 2, 0, 0 }); // padded with zeros
52   }
53 
testInt()54   public void testInt() {
55     Sink sink = new Sink(4);
56     sink.putInt(0x04030201);
57     sink.hash();
58     sink.assertInvariants(4);
59     sink.assertBytes(new byte[] { 1, 2, 3, 4 });
60   }
61 
testLong()62   public void testLong() {
63     Sink sink = new Sink(8);
64     sink.putLong(0x0807060504030201L);
65     sink.hash();
66     sink.assertInvariants(8);
67     sink.assertBytes(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
68   }
69 
testChar()70   public void testChar() {
71     Sink sink = new Sink(4);
72     sink.putChar((char) 0x0201);
73     sink.hash();
74     sink.assertInvariants(2);
75     sink.assertBytes(new byte[] { 1, 2, 0, 0  }); // padded with zeros
76   }
77 
testFloat()78   public void testFloat() {
79     Sink sink = new Sink(4);
80     sink.putFloat(Float.intBitsToFloat(0x04030201));
81     sink.hash();
82     sink.assertInvariants(4);
83     sink.assertBytes(new byte[] { 1, 2, 3, 4 });
84   }
85 
testDouble()86   public void testDouble() {
87     Sink sink = new Sink(8);
88     sink.putDouble(Double.longBitsToDouble(0x0807060504030201L));
89     sink.hash();
90     sink.assertInvariants(8);
91     sink.assertBytes(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
92   }
93 
testCorrectExceptions()94   public void testCorrectExceptions() {
95     Sink sink = new Sink(4);
96     try {
97       sink.putBytes(new byte[8], -1, 4);
98       fail();
99     } catch (IndexOutOfBoundsException ok) {}
100     try {
101       sink.putBytes(new byte[8], 0, 16);
102       fail();
103     } catch (IndexOutOfBoundsException ok) {}
104     try {
105       sink.putBytes(new byte[8], 0, -1);
106       fail();
107     } catch (IndexOutOfBoundsException ok) {}
108   }
109 
110   /**
111    * This test creates a long random sequence of inputs, then a lot of differently configured
112    * sinks process it; all should produce the same answer, the only difference should be the
113    * number of process()/processRemaining() invocations, due to alignment.
114    */
testExhaustive()115   public void testExhaustive() throws Exception {
116     Random random = new Random(0); // will iteratively make more debuggable, each time it breaks
117     for (int totalInsertions = 0; totalInsertions < 200; totalInsertions++) {
118 
119       List<Sink> sinks = Lists.newArrayList();
120       for (int chunkSize = 4; chunkSize <= 32; chunkSize++) {
121         for (int bufferSize = chunkSize; bufferSize <= chunkSize * 4; bufferSize += chunkSize) {
122           // yes, that's a lot of sinks!
123           sinks.add(new Sink(chunkSize, bufferSize));
124           // For convenience, testing only with big endianness, to match DataOutputStream.
125           // I regard highly unlikely that both the little endianness tests above and this one
126           // passes, and there is still a little endianness bug lurking around.
127         }
128       }
129 
130       Control control = new Control();
131       Hasher controlSink = control.newHasher(1024);
132 
133       Iterable<Hasher> sinksAndControl = Iterables.concat(
134           sinks, Collections.singleton(controlSink));
135       for (int insertion = 0; insertion < totalInsertions; insertion++) {
136         RandomHasherAction.pickAtRandom(random).performAction(random, sinksAndControl);
137       }
138       for (Sink sink : sinks) {
139         sink.hash();
140       }
141 
142       byte[] expected = controlSink.hash().asBytes();
143       for (Sink sink : sinks) {
144         sink.assertInvariants(expected.length);
145         sink.assertBytes(expected);
146       }
147     }
148   }
149 
150   private static class Sink extends AbstractStreamingHasher {
151     final int chunkSize;
152     final int bufferSize;
153     final ByteArrayOutputStream out = new ByteArrayOutputStream();
154 
155     int processCalled = 0;
156     boolean remainingCalled = false;
157 
Sink(int chunkSize, int bufferSize)158     Sink(int chunkSize, int bufferSize) {
159       super(chunkSize, bufferSize);
160       this.chunkSize = chunkSize;
161       this.bufferSize = bufferSize;
162     }
163 
Sink(int chunkSize)164     Sink(int chunkSize) {
165       super(chunkSize);
166       this.chunkSize = chunkSize;
167       this.bufferSize = chunkSize;
168     }
169 
makeHash()170     @Override HashCode makeHash() {
171       return HashCodes.fromInt(0xDeadBeef);
172     }
173 
process(ByteBuffer bb)174     @Override protected void process(ByteBuffer bb) {
175       processCalled++;
176       assertEquals(ByteOrder.LITTLE_ENDIAN, bb.order());
177       assertTrue(bb.remaining() >= chunkSize);
178       for (int i = 0; i < chunkSize; i++) {
179         out.write(bb.get());
180       }
181     }
182 
processRemaining(ByteBuffer bb)183     @Override protected void processRemaining(ByteBuffer bb) {
184       assertFalse(remainingCalled);
185       remainingCalled = true;
186       assertEquals(ByteOrder.LITTLE_ENDIAN, bb.order());
187       assertTrue(bb.remaining() > 0);
188       assertTrue(bb.remaining() < bufferSize);
189       int before = processCalled;
190       super.processRemaining(bb);
191       int after = processCalled;
192       assertEquals(before + 1, after); // default implementation pads and calls process()
193       processCalled--; // don't count the tail invocation (makes tests a bit more understandable)
194     }
195 
196     // ensures that the number of invocations looks sane
197     void assertInvariants(int expectedBytes) {
198       // we should have seen as many bytes as the next multiple of chunk after expectedBytes - 1
199       assertEquals(out.toByteArray().length, ceilToMultiple(expectedBytes, chunkSize));
200       assertEquals(expectedBytes / chunkSize, processCalled);
201       assertEquals(expectedBytes % chunkSize != 0, remainingCalled);
202     }
203 
204     // returns the minimum x such as x >= a && (x % b) == 0
205     private static int ceilToMultiple(int a, int b) {
206       int remainder = a % b;
207       return remainder == 0 ? a : a + b - remainder;
208     }
209 
210     void assertBytes(byte[] expected) {
211       byte[] got = out.toByteArray();
212       for (int i = 0; i < expected.length; i++) {
213         assertEquals(expected[i], got[i]);
214       }
215     }
216   }
217 
218   private static class Control extends AbstractNonStreamingHashFunction {
219     @Override
220     public HashCode hashBytes(byte[] input) {
221       return HashCodes.fromBytes(input);
222     }
223 
224     @Override
225     public HashCode hashBytes(byte[] input, int off, int len) {
226       return hashBytes(Arrays.copyOfRange(input, off, off + len));
227     }
228 
229     @Override
230     public int bits() {
231       throw new UnsupportedOperationException();
232     }
233 
234     @Override
235     public HashCode hashString(CharSequence input) {
236       throw new UnsupportedOperationException();
237     }
238 
239     @Override
240     public HashCode hashString(CharSequence input, Charset charset) {
241       throw new UnsupportedOperationException();
242     }
243 
244     @Override
245     public HashCode hashLong(long input) {
246       throw new UnsupportedOperationException();
247     }
248   }
249 }
250