1// Copyright 2011 Google Inc. All Rights Reserved. 2 3package com.google.common.hash; 4 5import com.google.common.collect.Iterables; 6import com.google.common.collect.Lists; 7import com.google.common.hash.AbstractStreamingHashFunction.AbstractStreamingHasher; 8import com.google.common.hash.HashTestUtils.RandomHasherAction; 9 10import junit.framework.TestCase; 11 12import java.io.ByteArrayOutputStream; 13import java.nio.ByteBuffer; 14import java.nio.ByteOrder; 15import java.nio.charset.Charset; 16import java.util.Arrays; 17import java.util.Collections; 18import java.util.List; 19import java.util.Random; 20 21/** 22 * Tests for AbstractHashSink. 23 * 24 * @author andreou@google.com (Dimitris Andreou) 25 */ 26public class AbstractStreamingHasherTest extends TestCase { 27 /** Test we get the HashCode that is created by the sink. Later we ignore the result */ 28 public void testSanity() { 29 Sink sink = new Sink(4); 30 assertEquals(0xDeadBeef, sink.makeHash().asInt()); 31 } 32 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 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 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 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 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 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 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 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 */ 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 158 Sink(int chunkSize, int bufferSize) { 159 super(chunkSize, bufferSize); 160 this.chunkSize = chunkSize; 161 this.bufferSize = bufferSize; 162 } 163 164 Sink(int chunkSize) { 165 super(chunkSize); 166 this.chunkSize = chunkSize; 167 this.bufferSize = chunkSize; 168 } 169 170 @Override HashCode makeHash() { 171 return HashCodes.fromInt(0xDeadBeef); 172 } 173 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 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