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