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.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 }
    398