Home | History | Annotate | Download | only in hash
      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