View Javadoc
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.base.Charsets;
22  import com.google.common.collect.ImmutableList;
23  import com.google.common.io.BaseEncoding;
24  import com.google.common.testing.ClassSanityTester;
25  
26  import junit.framework.TestCase;
27  
28  import java.util.Arrays;
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 = 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 }