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