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 com.google.common.io.BaseEncoding.base16; 20 21 import com.google.common.collect.ImmutableList; 22 import com.google.common.io.BaseEncoding; 23 import com.google.common.jdk5backport.Arrays; 24 import com.google.common.testing.ClassSanityTester; 25 26 import junit.framework.TestCase; 27 28 /** 29 * Unit tests for {@link HashCode}. 30 * 31 * @author Dimitris Andreou 32 * @author Kurt Alfred Kluever 33 */ 34 public class HashCodeTest extends TestCase { 35 // note: asInt(), asLong() are in little endian 36 private static final ImmutableList<ExpectedHashCode> expectedHashCodes = ImmutableList.of( 37 new ExpectedHashCode(new byte[] { 38 (byte) 0xef, (byte) 0xcd, (byte) 0xab, (byte) 0x89, 39 (byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01}, 40 0x89abcdef, 0x0123456789abcdefL, "efcdab8967452301"), 41 42 new ExpectedHashCode(new byte[] { 43 (byte) 0xef, (byte) 0xcd, (byte) 0xab, (byte) 0x89, 44 (byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01, // up to here, same bytes as above 45 (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, 46 (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08}, 47 0x89abcdef, 0x0123456789abcdefL, // asInt/asLong as above, due to equal eight first bytes 48 "efcdab89674523010102030405060708"), 49 50 new ExpectedHashCode(new byte[] { (byte) 0xdf, (byte) 0x9b, (byte) 0x57, (byte) 0x13 }, 51 0x13579bdf, null, "df9b5713"), 52 53 new ExpectedHashCode(new byte[] { 54 (byte) 0xcd, (byte) 0xab, (byte) 0x00, (byte) 0x00}, 55 0x0000abcd, null, "cdab0000"), 56 57 new ExpectedHashCode(new byte[] { 58 (byte) 0xef, (byte) 0xcd, (byte) 0xab, (byte) 0x00, 59 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}, 60 0x00abcdef, 0x0000000000abcdefL, "efcdab0000000000") 61 ); 62 63 // expectedHashCodes must contain at least one hash code with 4 bytes 64 public void testFromInt() { 65 for (ExpectedHashCode expected : expectedHashCodes) { 66 if (expected.bytes.length == 4) { 67 HashCode fromInt = HashCode.fromInt(expected.asInt); 68 assertExpectedHashCode(expected, fromInt); 69 } 70 } 71 } 72 73 // expectedHashCodes must contain at least one hash code with 8 bytes 74 public void testFromLong() { 75 for (ExpectedHashCode expected : expectedHashCodes) { 76 if (expected.bytes.length == 8) { 77 HashCode fromLong = HashCode.fromLong(expected.asLong); 78 assertExpectedHashCode(expected, fromLong); 79 } 80 } 81 } 82 83 public void testFromBytes() { 84 for (ExpectedHashCode expected : expectedHashCodes) { 85 HashCode fromBytes = HashCode.fromBytes(expected.bytes); 86 assertExpectedHashCode(expected, fromBytes); 87 } 88 } 89 90 public void testFromBytes_copyOccurs() { 91 byte[] bytes = new byte[] { (byte) 0xcd, (byte) 0xab, (byte) 0x00, (byte) 0x00 }; 92 HashCode hashCode = HashCode.fromBytes(bytes); 93 int expectedInt = 0x0000abcd; 94 String expectedToString = "cdab0000"; 95 96 assertEquals(expectedInt, hashCode.asInt()); 97 assertEquals(expectedToString, hashCode.toString()); 98 99 bytes[0] = (byte) 0x00; 100 101 assertEquals(expectedInt, hashCode.asInt()); 102 assertEquals(expectedToString, hashCode.toString()); 103 } 104 105 public void testFromBytesNoCopy_noCopyOccurs() { 106 byte[] bytes = new byte[] { (byte) 0xcd, (byte) 0xab, (byte) 0x00, (byte) 0x00 }; 107 HashCode hashCode = HashCode.fromBytesNoCopy(bytes); 108 109 assertEquals(0x0000abcd, hashCode.asInt()); 110 assertEquals("cdab0000", hashCode.toString()); 111 112 bytes[0] = (byte) 0x00; 113 114 assertEquals(0x0000ab00, hashCode.asInt()); 115 assertEquals("00ab0000", hashCode.toString()); 116 } 117 118 public void testGetBytesInternal_noCloneOccurs() { 119 byte[] bytes = new byte[] { (byte) 0xcd, (byte) 0xab, (byte) 0x00, (byte) 0x00 }; 120 HashCode hashCode = HashCode.fromBytes(bytes); 121 122 assertEquals(0x0000abcd, hashCode.asInt()); 123 assertEquals("cdab0000", hashCode.toString()); 124 125 hashCode.getBytesInternal()[0] = (byte) 0x00; 126 127 assertEquals(0x0000ab00, hashCode.asInt()); 128 assertEquals("00ab0000", hashCode.toString()); 129 } 130 131 public void testPadToLong() { 132 assertEquals(0x1111111111111111L, HashCode.fromLong(0x1111111111111111L).padToLong()); 133 assertEquals(0x9999999999999999L, HashCode.fromLong(0x9999999999999999L).padToLong()); 134 assertEquals(0x0000000011111111L, HashCode.fromInt(0x11111111).padToLong()); 135 assertEquals(0x0000000099999999L, HashCode.fromInt(0x99999999).padToLong()); 136 } 137 138 public void testPadToLongWith4Bytes() { 139 assertEquals(0x0000000099999999L, HashCode.fromBytesNoCopy(byteArrayWith9s(4)).padToLong()); 140 } 141 142 public void testPadToLongWith6Bytes() { 143 assertEquals(0x0000999999999999L, HashCode.fromBytesNoCopy(byteArrayWith9s(6)).padToLong()); 144 } 145 146 public void testPadToLongWith8Bytes() { 147 assertEquals(0x9999999999999999L, HashCode.fromBytesNoCopy(byteArrayWith9s(8)).padToLong()); 148 } 149 150 private static byte[] byteArrayWith9s(int size) { 151 byte[] bytez = new byte[size]; 152 Arrays.fill(bytez, (byte) 0x99); 153 return bytez; 154 } 155 156 public void testToString() { 157 byte[] data = new byte[] { 127, -128, 5, -1, 14 }; 158 assertEquals("7f8005ff0e", HashCode.fromBytes(data).toString()); 159 assertEquals("7f8005ff0e", base16().lowerCase().encode(data)); 160 } 161 162 public void testHashCode_nulls() throws Exception { 163 sanityTester().testNulls(); 164 } 165 166 public void testHashCode_equalsAndSerializable() throws Exception { 167 sanityTester().testEqualsAndSerializable(); 168 } 169 170 public void testRoundTripHashCodeUsingBaseEncoding() { 171 HashCode hash1 = Hashing.sha1().hashString("foo"); 172 HashCode hash2 = 173 HashCode.fromBytes(BaseEncoding.base16().lowerCase().decode(hash1.toString())); 174 assertEquals(hash1, hash2); 175 } 176 177 public void testObjectHashCode() { 178 HashCode hashCode42 = HashCode.fromInt(42); 179 assertEquals(42, hashCode42.hashCode()); 180 } 181 182 // See https://code.google.com/p/guava-libraries/issues/detail?id=1494 183 public void testObjectHashCodeWithSameLowOrderBytes() { 184 // These will have the same first 4 bytes (all 0). 185 byte[] bytesA = new byte[5]; 186 byte[] bytesB = new byte[5]; 187 188 // Change only the last (5th) byte 189 bytesA[4] = (byte) 0xbe; 190 bytesB[4] = (byte) 0xef; 191 192 HashCode hashCodeA = HashCode.fromBytes(bytesA); 193 HashCode hashCodeB = HashCode.fromBytes(bytesB); 194 195 // They aren't equal... 196 assertFalse(hashCodeA.equals(hashCodeB)); 197 198 // But they still have the same Object#hashCode() value. 199 // Technically not a violation of the equals/hashCode contract, but...? 200 assertEquals(hashCodeA.hashCode(), hashCodeB.hashCode()); 201 } 202 203 public void testRoundTripHashCodeUsingFromString() { 204 HashCode hash1 = Hashing.sha1().hashString("foo"); 205 HashCode hash2 = HashCode.fromString(hash1.toString()); 206 assertEquals(hash1, hash2); 207 } 208 209 public void testRoundTrip() { 210 for (ExpectedHashCode expected : expectedHashCodes) { 211 String string = HashCodes.fromBytes(expected.bytes).toString(); 212 assertEquals(expected.toString, string); 213 assertEquals( 214 expected.toString, 215 HashCodes.fromBytes( 216 BaseEncoding.base16().lowerCase().decode(string)).toString()); 217 } 218 } 219 220 public void testFromStringFailsWithInvalidHexChar() { 221 try { 222 HashCode.fromString("7f8005ff0z"); 223 fail(); 224 } catch (IllegalArgumentException expected) { 225 } 226 } 227 228 public void testFromStringFailsWithUpperCaseString() { 229 String string = Hashing.sha1().hashString("foo").toString().toUpperCase(); 230 try { 231 HashCode.fromString(string); 232 fail(); 233 } catch (IllegalArgumentException expected) { 234 } 235 } 236 237 public void testFromStringFailsWithShortInputs() { 238 try { 239 HashCode.fromString(""); 240 fail(); 241 } catch (IllegalArgumentException expected) { 242 } 243 try { 244 HashCode.fromString("7"); 245 fail(); 246 } catch (IllegalArgumentException expected) { 247 } 248 HashCode.fromString("7f"); 249 } 250 251 public void testFromStringFailsWithOddLengthInput() { 252 try { 253 HashCode.fromString("7f8"); 254 fail(); 255 } catch (IllegalArgumentException expected) { 256 } 257 } 258 259 public void testIntWriteBytesTo() { 260 byte[] dest = new byte[4]; 261 HashCodes.fromInt(42).writeBytesTo(dest, 0, 4); 262 assertTrue(Arrays.equals( 263 HashCodes.fromInt(42).asBytes(), 264 dest)); 265 } 266 267 public void testLongWriteBytesTo() { 268 byte[] dest = new byte[8]; 269 HashCodes.fromLong(42).writeBytesTo(dest, 0, 8); 270 assertTrue(Arrays.equals( 271 HashCodes.fromLong(42).asBytes(), 272 dest)); 273 } 274 275 private static final HashCode HASH_ABCD = 276 HashCode.fromBytes(new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd }); 277 278 public void testWriteBytesTo() { 279 byte[] dest = new byte[4]; 280 HASH_ABCD.writeBytesTo(dest, 0, 4); 281 assertTrue(Arrays.equals( 282 new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd }, 283 dest)); 284 } 285 286 public void testWriteBytesToOversizedArray() { 287 byte[] dest = new byte[5]; 288 HASH_ABCD.writeBytesTo(dest, 0, 4); 289 assertTrue(Arrays.equals( 290 new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0x00 }, 291 dest)); 292 } 293 294 public void testWriteBytesToOversizedArrayLongMaxLength() { 295 byte[] dest = new byte[5]; 296 HASH_ABCD.writeBytesTo(dest, 0, 5); 297 assertTrue(Arrays.equals( 298 new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0x00 }, 299 dest)); 300 } 301 302 public void testWriteBytesToOversizedArrayShortMaxLength() { 303 byte[] dest = new byte[5]; 304 HASH_ABCD.writeBytesTo(dest, 0, 3); 305 assertTrue(Arrays.equals( 306 new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0x00, (byte) 0x00 }, 307 dest)); 308 } 309 310 public void testWriteBytesToUndersizedArray() { 311 byte[] dest = new byte[3]; 312 try { 313 HASH_ABCD.writeBytesTo(dest, 0, 4); 314 fail(); 315 } catch (IndexOutOfBoundsException expected) { 316 } 317 } 318 319 public void testWriteBytesToUndersizedArrayLongMaxLength() { 320 byte[] dest = new byte[3]; 321 try { 322 HASH_ABCD.writeBytesTo(dest, 0, 5); 323 fail(); 324 } catch (IndexOutOfBoundsException expected) { 325 } 326 } 327 328 public void testWriteBytesToUndersizedArrayShortMaxLength() { 329 byte[] dest = new byte[3]; 330 HASH_ABCD.writeBytesTo(dest, 0, 2); 331 assertTrue(Arrays.equals( 332 new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0x00 }, 333 dest)); 334 } 335 336 private static ClassSanityTester.FactoryMethodReturnValueTester sanityTester() { 337 return new ClassSanityTester() 338 .setDefault(byte[].class, new byte[] {1, 2, 3, 4}) 339 .setDistinctValues(byte[].class, new byte[] {1, 2, 3, 4}, new byte[] {5, 6, 7, 8}) 340 .setDistinctValues(String.class, "7f8005ff0e", "7f8005ff0f") 341 .forAllPublicStaticMethods(HashCode.class); 342 } 343 344 private static void assertExpectedHashCode(ExpectedHashCode expectedHashCode, HashCode hash) { 345 assertTrue(Arrays.equals(expectedHashCode.bytes, hash.asBytes())); 346 byte[] bb = new byte[hash.bits() / 8]; 347 hash.writeBytesTo(bb, 0, bb.length); 348 assertTrue(Arrays.equals(expectedHashCode.bytes, bb)); 349 assertEquals(expectedHashCode.asInt, hash.asInt()); 350 if (expectedHashCode.asLong == null) { 351 try { 352 hash.asLong(); 353 fail(); 354 } catch (IllegalStateException expected) {} 355 } else { 356 assertEquals(expectedHashCode.asLong.longValue(), hash.asLong()); 357 } 358 assertEquals(expectedHashCode.toString, hash.toString()); 359 assertSideEffectFree(hash); 360 assertReadableBytes(hash); 361 } 362 363 private static void assertSideEffectFree(HashCode hash) { 364 byte[] original = hash.asBytes(); 365 byte[] mutated = hash.asBytes(); 366 mutated[0]++; 367 assertTrue(Arrays.equals(original, hash.asBytes())); 368 } 369 370 private static void assertReadableBytes(HashCode hashCode) { 371 assertTrue(hashCode.bits() >= 32); // sanity 372 byte[] hashBytes = hashCode.asBytes(); 373 int totalBytes = hashCode.bits() / 8; 374 375 for (int bytes = 0; bytes < totalBytes; bytes++) { 376 byte[] bb = new byte[bytes]; 377 hashCode.writeBytesTo(bb, 0, bb.length); 378 379 assertTrue(Arrays.equals(Arrays.copyOf(hashBytes, bytes), bb)); 380 } 381 } 382 383 private static class ExpectedHashCode { 384 final byte[] bytes; 385 final int asInt; 386 final Long asLong; // null means that asLong should throw an exception 387 final String toString; 388 ExpectedHashCode(byte[] bytes, int asInt, Long asLong, String toString) { 389 this.bytes = bytes; 390 this.asInt = asInt; 391 this.asLong = asLong; 392 this.toString = toString; 393 } 394 } 395 } 396