1 /* 2 * Copyright (C) 2011 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.hash; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 22 import com.google.common.base.Charsets; 23 import com.google.common.collect.ImmutableSet; 24 import com.google.common.collect.Sets; 25 import com.google.common.primitives.Ints; 26 import com.google.common.testing.EqualsTester; 27 import java.nio.ByteBuffer; 28 import java.nio.ByteOrder; 29 import java.nio.charset.Charset; 30 import java.util.Arrays; 31 import java.util.Random; 32 import java.util.Set; 33 import org.junit.Assert; 34 35 /** 36 * Various utilities for testing {@link HashFunction}s. 37 * 38 * @author Dimitris Andreou 39 * @author Kurt Alfred Kluever 40 */ 41 final class HashTestUtils { HashTestUtils()42 private HashTestUtils() {} 43 44 /** Converts a string, which should contain only ascii-representable characters, to a byte[]. */ ascii(String string)45 static byte[] ascii(String string) { 46 byte[] bytes = new byte[string.length()]; 47 for (int i = 0; i < string.length(); i++) { 48 bytes[i] = (byte) string.charAt(i); 49 } 50 return bytes; 51 } 52 53 interface HashFn { hash(byte[] input, int seed)54 byte[] hash(byte[] input, int seed); 55 } 56 verifyHashFunction(HashFn hashFunction, int hashbits, int expected)57 static void verifyHashFunction(HashFn hashFunction, int hashbits, int expected) { 58 int hashBytes = hashbits / 8; 59 60 byte[] key = new byte[256]; 61 byte[] hashes = new byte[hashBytes * 256]; 62 63 // Hash keys of the form {}, {0}, {0,1}, {0,1,2}... up to N=255,using 256-N as the seed 64 for (int i = 0; i < 256; i++) { 65 key[i] = (byte) i; 66 int seed = 256 - i; 67 byte[] hash = hashFunction.hash(Arrays.copyOf(key, i), seed); 68 System.arraycopy(hash, 0, hashes, i * hashBytes, hash.length); 69 } 70 71 // Then hash the result array 72 byte[] result = hashFunction.hash(hashes, 0); 73 74 // interpreted in little-endian order. 75 int verification = Integer.reverseBytes(Ints.fromByteArray(result)); 76 77 if (expected != verification) { 78 throw new AssertionError( 79 "Expected: " 80 + Integer.toHexString(expected) 81 + " got: " 82 + Integer.toHexString(verification)); 83 } 84 } 85 86 static final Funnel<Object> BAD_FUNNEL = 87 new Funnel<Object>() { 88 @Override 89 public void funnel(Object object, PrimitiveSink bytePrimitiveSink) { 90 bytePrimitiveSink.putInt(object.hashCode()); 91 } 92 }; 93 94 enum RandomHasherAction { PUT_BOOLEAN()95 PUT_BOOLEAN() { 96 @Override 97 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 98 boolean value = random.nextBoolean(); 99 for (PrimitiveSink sink : sinks) { 100 sink.putBoolean(value); 101 } 102 } 103 }, PUT_BYTE()104 PUT_BYTE() { 105 @Override 106 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 107 int value = random.nextInt(); 108 for (PrimitiveSink sink : sinks) { 109 sink.putByte((byte) value); 110 } 111 } 112 }, PUT_SHORT()113 PUT_SHORT() { 114 @Override 115 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 116 short value = (short) random.nextInt(); 117 for (PrimitiveSink sink : sinks) { 118 sink.putShort(value); 119 } 120 } 121 }, PUT_CHAR()122 PUT_CHAR() { 123 @Override 124 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 125 char value = (char) random.nextInt(); 126 for (PrimitiveSink sink : sinks) { 127 sink.putChar(value); 128 } 129 } 130 }, PUT_INT()131 PUT_INT() { 132 @Override 133 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 134 int value = random.nextInt(); 135 for (PrimitiveSink sink : sinks) { 136 sink.putInt(value); 137 } 138 } 139 }, PUT_LONG()140 PUT_LONG() { 141 @Override 142 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 143 long value = random.nextLong(); 144 for (PrimitiveSink sink : sinks) { 145 sink.putLong(value); 146 } 147 } 148 }, PUT_FLOAT()149 PUT_FLOAT() { 150 @Override 151 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 152 float value = random.nextFloat(); 153 for (PrimitiveSink sink : sinks) { 154 sink.putFloat(value); 155 } 156 } 157 }, PUT_DOUBLE()158 PUT_DOUBLE() { 159 @Override 160 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 161 double value = random.nextDouble(); 162 for (PrimitiveSink sink : sinks) { 163 sink.putDouble(value); 164 } 165 } 166 }, PUT_BYTES()167 PUT_BYTES() { 168 @Override 169 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 170 byte[] value = new byte[random.nextInt(128)]; 171 random.nextBytes(value); 172 for (PrimitiveSink sink : sinks) { 173 sink.putBytes(value); 174 } 175 } 176 }, PUT_BYTES_INT_INT()177 PUT_BYTES_INT_INT() { 178 @Override 179 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 180 byte[] value = new byte[random.nextInt(128)]; 181 random.nextBytes(value); 182 int off = random.nextInt(value.length + 1); 183 int len = random.nextInt(value.length - off + 1); 184 for (PrimitiveSink sink : sinks) { 185 sink.putBytes(value, off, len); 186 } 187 } 188 }, PUT_BYTE_BUFFER()189 PUT_BYTE_BUFFER() { 190 @Override 191 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 192 byte[] value = new byte[random.nextInt(128)]; 193 random.nextBytes(value); 194 int pos = random.nextInt(value.length + 1); 195 int limit = pos + random.nextInt(value.length - pos + 1); 196 for (PrimitiveSink sink : sinks) { 197 ByteBuffer buffer = ByteBuffer.wrap(value); 198 buffer.position(pos); 199 buffer.limit(limit); 200 sink.putBytes(buffer); 201 assertEquals(limit, buffer.limit()); 202 assertEquals(limit, buffer.position()); 203 } 204 } 205 }, PUT_STRING()206 PUT_STRING() { 207 @Override 208 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 209 char[] value = new char[random.nextInt(128)]; 210 for (int i = 0; i < value.length; i++) { 211 value[i] = (char) random.nextInt(); 212 } 213 String s = new String(value); 214 for (PrimitiveSink sink : sinks) { 215 sink.putUnencodedChars(s); 216 } 217 } 218 }, PUT_STRING_LOW_SURROGATE()219 PUT_STRING_LOW_SURROGATE() { 220 @Override 221 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 222 String s = new String(new char[] {randomLowSurrogate(random)}); 223 for (PrimitiveSink sink : sinks) { 224 sink.putUnencodedChars(s); 225 } 226 } 227 }, PUT_STRING_HIGH_SURROGATE()228 PUT_STRING_HIGH_SURROGATE() { 229 @Override 230 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 231 String s = new String(new char[] {randomHighSurrogate(random)}); 232 for (PrimitiveSink sink : sinks) { 233 sink.putUnencodedChars(s); 234 } 235 } 236 }, PUT_STRING_LOW_HIGH_SURROGATE()237 PUT_STRING_LOW_HIGH_SURROGATE() { 238 @Override 239 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 240 String s = new String(new char[] {randomLowSurrogate(random), randomHighSurrogate(random)}); 241 for (PrimitiveSink sink : sinks) { 242 sink.putUnencodedChars(s); 243 } 244 } 245 }, PUT_STRING_HIGH_LOW_SURROGATE()246 PUT_STRING_HIGH_LOW_SURROGATE() { 247 @Override 248 void performAction(Random random, Iterable<? extends PrimitiveSink> sinks) { 249 String s = new String(new char[] {randomHighSurrogate(random), randomLowSurrogate(random)}); 250 for (PrimitiveSink sink : sinks) { 251 sink.putUnencodedChars(s); 252 } 253 } 254 }; 255 performAction(Random random, Iterable<? extends PrimitiveSink> sinks)256 abstract void performAction(Random random, Iterable<? extends PrimitiveSink> sinks); 257 258 private static final RandomHasherAction[] actions = values(); 259 pickAtRandom(Random random)260 static RandomHasherAction pickAtRandom(Random random) { 261 return actions[random.nextInt(actions.length)]; 262 } 263 } 264 265 /** 266 * Test that the hash function contains no funnels. A funnel is a situation where a set of input 267 * (key) bits 'affects' a strictly smaller set of output bits. Funneling is bad because it can 268 * result in more-than-ideal collisions for a non-uniformly distributed key space. In practice, 269 * most key spaces are ANYTHING BUT uniformly distributed. A bit(i) in the input is said to 270 * 'affect' a bit(j) in the output if two inputs, identical but for bit(i), will differ at output 271 * bit(j) about half the time 272 * 273 * <p>Funneling is pretty simple to detect. The key idea is to find example keys which 274 * unequivocally demonstrate that funneling cannot be occurring. This is done bit-by-bit. For each 275 * input bit(i) and output bit(j), two pairs of keys must be found with all bits identical except 276 * bit(i). One pair must differ in output bit(j), and one pair must not. This proves that input 277 * bit(i) can alter output bit(j). 278 */ checkNoFunnels(HashFunction function)279 static void checkNoFunnels(HashFunction function) { 280 Random rand = new Random(0); 281 int keyBits = 32; 282 int hashBits = function.bits(); 283 284 // output loop tests input bit 285 for (int i = 0; i < keyBits; i++) { 286 int same = 0x0; // bitset for output bits with same values 287 int diff = 0x0; // bitset for output bits with different values 288 int count = 0; 289 // originally was 2 * Math.log(...), making it try more times to avoid flakiness issues 290 int maxCount = (int) (4 * Math.log(2 * keyBits * hashBits) + 1); 291 while (same != 0xffffffff || diff != 0xffffffff) { 292 int key1 = rand.nextInt(); 293 // flip input bit for key2 294 int key2 = key1 ^ (1 << i); 295 // get hashes 296 int hash1 = function.hashInt(key1).asInt(); 297 int hash2 = function.hashInt(key2).asInt(); 298 // test whether the hash values have same output bits 299 same |= ~(hash1 ^ hash2); 300 // test whether the hash values have different output bits 301 diff |= (hash1 ^ hash2); 302 303 count++; 304 // check whether we've exceeded the probabilistically 305 // likely number of trials to have proven no funneling 306 if (count > maxCount) { 307 Assert.fail( 308 "input bit(" 309 + i 310 + ") was found not to affect all " 311 + hashBits 312 + " output bits; The unaffected bits are " 313 + "as follows: " 314 + ~(same & diff) 315 + ". This was " 316 + "determined after " 317 + count 318 + " trials."); 319 } 320 } 321 } 322 } 323 324 /** 325 * Test for avalanche. Avalanche means that output bits differ with roughly 1/2 probability on 326 * different input keys. This test verifies that each possible 1-bit key delta achieves avalanche. 327 * 328 * <p>For more information: http://burtleburtle.net/bob/hash/avalanche.html 329 */ checkAvalanche(HashFunction function, int trials, double epsilon)330 static void checkAvalanche(HashFunction function, int trials, double epsilon) { 331 Random rand = new Random(0); 332 int keyBits = 32; 333 int hashBits = function.bits(); 334 for (int i = 0; i < keyBits; i++) { 335 int[] same = new int[hashBits]; 336 int[] diff = new int[hashBits]; 337 // go through trials to compute probability 338 for (int j = 0; j < trials; j++) { 339 int key1 = rand.nextInt(); 340 // flip input bit for key2 341 int key2 = key1 ^ (1 << i); 342 // compute hash values 343 int hash1 = function.hashInt(key1).asInt(); 344 int hash2 = function.hashInt(key2).asInt(); 345 for (int k = 0; k < hashBits; k++) { 346 if ((hash1 & (1 << k)) == (hash2 & (1 << k))) { 347 same[k] += 1; 348 } else { 349 diff[k] += 1; 350 } 351 } 352 } 353 // measure probability and assert it's within margin of error 354 for (int j = 0; j < hashBits; j++) { 355 double prob = (double) diff[j] / (double) (diff[j] + same[j]); 356 Assert.assertEquals(0.50d, prob, epsilon); 357 } 358 } 359 } 360 361 /** 362 * Test for 2-bit characteristics. A characteristic is a delta in the input which is repeated in 363 * the output. For example, if f() is a block cipher and c is a characteristic, then f(x^c) = 364 * f(x)^c with greater than expected probability. The test for funneling is merely a test for 365 * 1-bit characteristics. 366 * 367 * <p>There is more general code provided by Bob Jenkins to test arbitrarily sized characteristics 368 * using the magic of gaussian elimination: http://burtleburtle.net/bob/crypto/findingc.html. 369 */ checkNo2BitCharacteristics(HashFunction function)370 static void checkNo2BitCharacteristics(HashFunction function) { 371 Random rand = new Random(0); 372 int keyBits = 32; 373 374 // get every one of (keyBits choose 2) deltas: 375 for (int i = 0; i < keyBits; i++) { 376 for (int j = 0; j < keyBits; j++) { 377 if (j <= i) continue; 378 int count = 0; 379 int maxCount = 20; // the probability of error here is miniscule 380 boolean diff = false; 381 382 while (!diff) { 383 int delta = (1 << i) | (1 << j); 384 int key1 = rand.nextInt(); 385 // apply delta 386 int key2 = key1 ^ delta; 387 388 // get hashes 389 int hash1 = function.hashInt(key1).asInt(); 390 int hash2 = function.hashInt(key2).asInt(); 391 392 // this 2-bit candidate delta is not a characteristic 393 // if deltas are different 394 if ((hash1 ^ hash2) != delta) { 395 diff = true; 396 continue; 397 } 398 399 // check if we've exceeded the probabilistically 400 // likely number of trials to have proven 2-bit candidate 401 // is not a characteristic 402 count++; 403 if (count > maxCount) { 404 Assert.fail( 405 "2-bit delta (" 406 + i 407 + ", " 408 + j 409 + ") is likely a " 410 + "characteristic for this hash. This was " 411 + "determined after " 412 + count 413 + " trials"); 414 } 415 } 416 } 417 } 418 } 419 420 /** 421 * Test for avalanche with 2-bit deltas. Most probabilities of output bit(j) differing are well 422 * within 50%. 423 */ check2BitAvalanche(HashFunction function, int trials, double epsilon)424 static void check2BitAvalanche(HashFunction function, int trials, double epsilon) { 425 Random rand = new Random(0); 426 int keyBits = 32; 427 int hashBits = function.bits(); 428 for (int bit1 = 0; bit1 < keyBits; bit1++) { 429 for (int bit2 = 0; bit2 < keyBits; bit2++) { 430 if (bit2 <= bit1) continue; 431 int delta = (1 << bit1) | (1 << bit2); 432 int[] same = new int[hashBits]; 433 int[] diff = new int[hashBits]; 434 // go through trials to compute probability 435 for (int j = 0; j < trials; j++) { 436 int key1 = rand.nextInt(); 437 // flip input bit for key2 438 int key2 = key1 ^ delta; 439 // compute hash values 440 int hash1 = function.hashInt(key1).asInt(); 441 int hash2 = function.hashInt(key2).asInt(); 442 for (int k = 0; k < hashBits; k++) { 443 if ((hash1 & (1 << k)) == (hash2 & (1 << k))) { 444 same[k] += 1; 445 } else { 446 diff[k] += 1; 447 } 448 } 449 } 450 // measure probability and assert it's within margin of error 451 for (int j = 0; j < hashBits; j++) { 452 double prob = (double) diff[j] / (double) (diff[j] + same[j]); 453 Assert.assertEquals(0.50d, prob, epsilon); 454 } 455 } 456 } 457 } 458 459 /** 460 * Checks that a Hasher returns the same HashCode when given the same input, and also that the 461 * collision rate looks sane. 462 */ assertInvariants(HashFunction hashFunction)463 static void assertInvariants(HashFunction hashFunction) { 464 int objects = 100; 465 Set<HashCode> hashcodes = Sets.newHashSetWithExpectedSize(objects); 466 Random random = new Random(314159); 467 for (int i = 0; i < objects; i++) { 468 int value = random.nextInt(); 469 HashCode hashcode1 = hashFunction.hashInt(value); 470 HashCode hashcode2 = hashFunction.hashInt(value); 471 Assert.assertEquals(hashcode1, hashcode2); // idempotent 472 Assert.assertEquals(hashFunction.bits(), hashcode1.bits()); 473 Assert.assertEquals(hashFunction.bits(), hashcode1.asBytes().length * 8); 474 hashcodes.add(hashcode1); 475 } 476 Assert.assertTrue(hashcodes.size() > objects * 0.95); // quite relaxed test 477 478 assertHashBytesThrowsCorrectExceptions(hashFunction); 479 assertIndependentHashers(hashFunction); 480 assertShortcutsAreEquivalent(hashFunction, 512); 481 } 482 assertHashByteBufferInvariants(HashFunction hashFunction)483 static void assertHashByteBufferInvariants(HashFunction hashFunction) { 484 assertHashByteBufferMatchesBytes(hashFunction); 485 assertHashByteBufferExhaustsBuffer(hashFunction); 486 assertHashByteBufferPreservesByteOrder(hashFunction); 487 assertHasherByteBufferPreservesByteOrder(hashFunction); 488 } 489 assertHashByteBufferMatchesBytes(HashFunction hashFunction)490 static void assertHashByteBufferMatchesBytes(HashFunction hashFunction) { 491 Random rng = new Random(0L); 492 byte[] bytes = new byte[rng.nextInt(256) + 1]; 493 rng.nextBytes(bytes); 494 assertEquals(hashFunction.hashBytes(bytes), hashFunction.hashBytes(ByteBuffer.wrap(bytes))); 495 } 496 assertHashByteBufferExhaustsBuffer(HashFunction hashFunction)497 static void assertHashByteBufferExhaustsBuffer(HashFunction hashFunction) { 498 Random rng = new Random(0L); 499 byte[] bytes = new byte[rng.nextInt(256) + 1]; 500 rng.nextBytes(bytes); 501 ByteBuffer buffer = ByteBuffer.wrap(bytes); 502 HashCode unused = hashFunction.hashBytes(buffer); 503 assertFalse(buffer.hasRemaining()); 504 } 505 assertHashByteBufferPreservesByteOrder(HashFunction hashFunction)506 static void assertHashByteBufferPreservesByteOrder(HashFunction hashFunction) { 507 Random rng = new Random(0L); 508 byte[] bytes = new byte[rng.nextInt(256) + 1]; 509 rng.nextBytes(bytes); 510 ByteBuffer littleEndian = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); 511 ByteBuffer bigEndian = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); 512 assertEquals(hashFunction.hashBytes(littleEndian), hashFunction.hashBytes(bigEndian)); 513 assertEquals(ByteOrder.LITTLE_ENDIAN, littleEndian.order()); 514 assertEquals(ByteOrder.BIG_ENDIAN, bigEndian.order()); 515 } 516 assertHasherByteBufferPreservesByteOrder(HashFunction hashFunction)517 static void assertHasherByteBufferPreservesByteOrder(HashFunction hashFunction) { 518 Random rng = new Random(0L); 519 byte[] bytes = new byte[rng.nextInt(256) + 1]; 520 rng.nextBytes(bytes); 521 ByteBuffer littleEndian = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); 522 ByteBuffer bigEndian = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); 523 assertEquals( 524 hashFunction.newHasher().putBytes(littleEndian).hash(), 525 hashFunction.newHasher().putBytes(bigEndian).hash()); 526 assertEquals(ByteOrder.LITTLE_ENDIAN, littleEndian.order()); 527 assertEquals(ByteOrder.BIG_ENDIAN, bigEndian.order()); 528 } 529 assertHashBytesThrowsCorrectExceptions(HashFunction hashFunction)530 static void assertHashBytesThrowsCorrectExceptions(HashFunction hashFunction) { 531 { 532 HashCode unused = hashFunction.hashBytes(new byte[64], 0, 0); 533 } 534 535 try { 536 hashFunction.hashBytes(new byte[128], -1, 128); 537 Assert.fail(); 538 } catch (IndexOutOfBoundsException expected) { 539 } 540 try { 541 hashFunction.hashBytes(new byte[128], 64, 256 /* too long len */); 542 Assert.fail(); 543 } catch (IndexOutOfBoundsException expected) { 544 } 545 try { 546 hashFunction.hashBytes(new byte[64], 0, -1); 547 Assert.fail(); 548 } catch (IndexOutOfBoundsException expected) { 549 } 550 } 551 assertIndependentHashers(HashFunction hashFunction)552 static void assertIndependentHashers(HashFunction hashFunction) { 553 int numActions = 100; 554 // hashcodes from non-overlapping hash computations 555 HashCode expected1 = randomHash(hashFunction, new Random(1L), numActions); 556 HashCode expected2 = randomHash(hashFunction, new Random(2L), numActions); 557 558 // equivalent, but overlapping, computations (should produce the same results as above) 559 Random random1 = new Random(1L); 560 Random random2 = new Random(2L); 561 Hasher hasher1 = hashFunction.newHasher(); 562 Hasher hasher2 = hashFunction.newHasher(); 563 for (int i = 0; i < numActions; i++) { 564 RandomHasherAction.pickAtRandom(random1).performAction(random1, ImmutableSet.of(hasher1)); 565 RandomHasherAction.pickAtRandom(random2).performAction(random2, ImmutableSet.of(hasher2)); 566 } 567 568 Assert.assertEquals(expected1, hasher1.hash()); 569 Assert.assertEquals(expected2, hasher2.hash()); 570 } 571 randomHash(HashFunction hashFunction, Random random, int numActions)572 static HashCode randomHash(HashFunction hashFunction, Random random, int numActions) { 573 Hasher hasher = hashFunction.newHasher(); 574 for (int i = 0; i < numActions; i++) { 575 RandomHasherAction.pickAtRandom(random).performAction(random, ImmutableSet.of(hasher)); 576 } 577 return hasher.hash(); 578 } 579 assertShortcutsAreEquivalent(HashFunction hashFunction, int trials)580 private static void assertShortcutsAreEquivalent(HashFunction hashFunction, int trials) { 581 Random random = new Random(42085L); 582 for (int i = 0; i < trials; i++) { 583 assertHashBytesEquivalence(hashFunction, random); 584 assertHashByteBufferEquivalence(hashFunction, random); 585 assertHashIntEquivalence(hashFunction, random); 586 assertHashLongEquivalence(hashFunction, random); 587 assertHashStringEquivalence(hashFunction, random); 588 assertHashStringWithSurrogatesEquivalence(hashFunction, random); 589 } 590 } 591 assertHashBytesEquivalence(HashFunction hashFunction, Random random)592 private static void assertHashBytesEquivalence(HashFunction hashFunction, Random random) { 593 int size = random.nextInt(2048); 594 byte[] bytes = new byte[size]; 595 random.nextBytes(bytes); 596 assertEquals( 597 hashFunction.hashBytes(bytes), hashFunction.newHasher(size).putBytes(bytes).hash()); 598 int off = random.nextInt(size); 599 int len = random.nextInt(size - off); 600 assertEquals( 601 hashFunction.hashBytes(bytes, off, len), 602 hashFunction.newHasher(size).putBytes(bytes, off, len).hash()); 603 } 604 assertHashByteBufferEquivalence(HashFunction hashFunction, Random random)605 private static void assertHashByteBufferEquivalence(HashFunction hashFunction, Random random) { 606 int size = random.nextInt(2048); 607 byte[] bytes = new byte[size]; 608 random.nextBytes(bytes); 609 assertEquals( 610 hashFunction.hashBytes(ByteBuffer.wrap(bytes)), 611 hashFunction.newHasher(size).putBytes(ByteBuffer.wrap(bytes)).hash()); 612 int off = random.nextInt(size); 613 int len = random.nextInt(size - off); 614 assertEquals( 615 hashFunction.hashBytes(ByteBuffer.wrap(bytes, off, len)), 616 hashFunction.newHasher(size).putBytes(ByteBuffer.wrap(bytes, off, len)).hash()); 617 } 618 assertHashIntEquivalence(HashFunction hashFunction, Random random)619 private static void assertHashIntEquivalence(HashFunction hashFunction, Random random) { 620 int i = random.nextInt(); 621 assertEquals(hashFunction.hashInt(i), hashFunction.newHasher().putInt(i).hash()); 622 } 623 assertHashLongEquivalence(HashFunction hashFunction, Random random)624 private static void assertHashLongEquivalence(HashFunction hashFunction, Random random) { 625 long l = random.nextLong(); 626 assertEquals(hashFunction.hashLong(l), hashFunction.newHasher().putLong(l).hash()); 627 } 628 629 private static final ImmutableSet<Charset> CHARSETS = 630 ImmutableSet.of( 631 Charsets.ISO_8859_1, 632 Charsets.US_ASCII, 633 Charsets.UTF_16, 634 Charsets.UTF_16BE, 635 Charsets.UTF_16LE, 636 Charsets.UTF_8); 637 assertHashStringEquivalence(HashFunction hashFunction, Random random)638 private static void assertHashStringEquivalence(HashFunction hashFunction, Random random) { 639 // Test that only data and data-order is important, not the individual operations. 640 new EqualsTester() 641 .addEqualityGroup( 642 hashFunction.hashUnencodedChars("abc"), 643 hashFunction.newHasher().putUnencodedChars("abc").hash(), 644 hashFunction.newHasher().putUnencodedChars("ab").putUnencodedChars("c").hash(), 645 hashFunction.newHasher().putUnencodedChars("a").putUnencodedChars("bc").hash(), 646 hashFunction 647 .newHasher() 648 .putUnencodedChars("a") 649 .putUnencodedChars("b") 650 .putUnencodedChars("c") 651 .hash(), 652 hashFunction.newHasher().putChar('a').putUnencodedChars("bc").hash(), 653 hashFunction.newHasher().putUnencodedChars("ab").putChar('c').hash(), 654 hashFunction.newHasher().putChar('a').putChar('b').putChar('c').hash()) 655 .testEquals(); 656 657 int size = random.nextInt(2048); 658 byte[] bytes = new byte[size]; 659 random.nextBytes(bytes); 660 String string = new String(bytes, Charsets.US_ASCII); 661 assertEquals( 662 hashFunction.hashUnencodedChars(string), 663 hashFunction.newHasher().putUnencodedChars(string).hash()); 664 for (Charset charset : CHARSETS) { 665 assertEquals( 666 hashFunction.hashString(string, charset), 667 hashFunction.newHasher().putString(string, charset).hash()); 668 } 669 } 670 671 /** 672 * This verifies that putUnencodedChars(String) and hashUnencodedChars(String) are equivalent, 673 * even for funny strings composed by (possibly unmatched, and mostly illegal) surrogate 674 * characters. (But doesn't test that they do the right thing - just their consistency). 675 */ assertHashStringWithSurrogatesEquivalence( HashFunction hashFunction, Random random)676 private static void assertHashStringWithSurrogatesEquivalence( 677 HashFunction hashFunction, Random random) { 678 int size = random.nextInt(8) + 1; 679 char[] chars = new char[size]; 680 for (int i = 0; i < chars.length; i++) { 681 chars[i] = random.nextBoolean() ? randomLowSurrogate(random) : randomHighSurrogate(random); 682 } 683 String string = new String(chars); 684 assertEquals( 685 hashFunction.hashUnencodedChars(string), 686 hashFunction.newHasher().putUnencodedChars(string).hash()); 687 } 688 randomLowSurrogate(Random random)689 static char randomLowSurrogate(Random random) { 690 return (char) 691 (Character.MIN_LOW_SURROGATE 692 + random.nextInt(Character.MAX_LOW_SURROGATE - Character.MIN_LOW_SURROGATE + 1)); 693 } 694 randomHighSurrogate(Random random)695 static char randomHighSurrogate(Random random) { 696 return (char) 697 (Character.MIN_HIGH_SURROGATE 698 + random.nextInt(Character.MAX_HIGH_SURROGATE - Character.MIN_HIGH_SURROGATE + 1)); 699 } 700 } 701