/*
* Copyright (C) 2011 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.hash;
import static com.google.common.io.BaseEncoding.base16;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.io.BaseEncoding;
import com.google.common.testing.ClassSanityTester;
import junit.framework.TestCase;
import java.util.Arrays;
/**
* Unit tests for {@link HashCode}.
*
* @author Dimitris Andreou
* @author Kurt Alfred Kluever
*/
public class HashCodeTest extends TestCase {
// note: asInt(), asLong() are in little endian
private static final ImmutableList<ExpectedHashCode> expectedHashCodes = ImmutableList.of(
new ExpectedHashCode(new byte[] {
(byte) 0xef, (byte) 0xcd, (byte) 0xab, (byte) 0x89,
(byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01},
0x89abcdef, 0x0123456789abcdefL, "efcdab8967452301"),
new ExpectedHashCode(new byte[] {
(byte) 0xef, (byte) 0xcd, (byte) 0xab, (byte) 0x89,
(byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01, // up to here, same bytes as above
(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
(byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08},
0x89abcdef, 0x0123456789abcdefL, // asInt/asLong as above, due to equal eight first bytes
"efcdab89674523010102030405060708"),
new ExpectedHashCode(new byte[] { (byte) 0xdf, (byte) 0x9b, (byte) 0x57, (byte) 0x13 },
0x13579bdf, null, "df9b5713"),
new ExpectedHashCode(new byte[] {
(byte) 0xcd, (byte) 0xab, (byte) 0x00, (byte) 0x00},
0x0000abcd, null, "cdab0000"),
new ExpectedHashCode(new byte[] {
(byte) 0xef, (byte) 0xcd, (byte) 0xab, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00},
0x00abcdef, 0x0000000000abcdefL, "efcdab0000000000")
);
// expectedHashCodes must contain at least one hash code with 4 bytes
public void testFromInt() {
for (ExpectedHashCode expected : expectedHashCodes) {
if (expected.bytes.length == 4) {
HashCode fromInt = HashCode.fromInt(expected.asInt);
assertExpectedHashCode(expected, fromInt);
}
}
}
// expectedHashCodes must contain at least one hash code with 8 bytes
public void testFromLong() {
for (ExpectedHashCode expected : expectedHashCodes) {
if (expected.bytes.length == 8) {
HashCode fromLong = HashCode.fromLong(expected.asLong);
assertExpectedHashCode(expected, fromLong);
}
}
}
public void testFromBytes() {
for (ExpectedHashCode expected : expectedHashCodes) {
HashCode fromBytes = HashCode.fromBytes(expected.bytes);
assertExpectedHashCode(expected, fromBytes);
}
}
public void testFromBytes_copyOccurs() {
byte[] bytes = new byte[] { (byte) 0xcd, (byte) 0xab, (byte) 0x00, (byte) 0x00 };
HashCode hashCode = HashCode.fromBytes(bytes);
int expectedInt = 0x0000abcd;
String expectedToString = "cdab0000";
assertEquals(expectedInt, hashCode.asInt());
assertEquals(expectedToString, hashCode.toString());
bytes[0] = (byte) 0x00;
assertEquals(expectedInt, hashCode.asInt());
assertEquals(expectedToString, hashCode.toString());
}
public void testFromBytesNoCopy_noCopyOccurs() {
byte[] bytes = new byte[] { (byte) 0xcd, (byte) 0xab, (byte) 0x00, (byte) 0x00 };
HashCode hashCode = HashCode.fromBytesNoCopy(bytes);
assertEquals(0x0000abcd, hashCode.asInt());
assertEquals("cdab0000", hashCode.toString());
bytes[0] = (byte) 0x00;
assertEquals(0x0000ab00, hashCode.asInt());
assertEquals("00ab0000", hashCode.toString());
}
public void testGetBytesInternal_noCloneOccurs() {
byte[] bytes = new byte[] { (byte) 0xcd, (byte) 0xab, (byte) 0x00, (byte) 0x00 };
HashCode hashCode = HashCode.fromBytes(bytes);
assertEquals(0x0000abcd, hashCode.asInt());
assertEquals("cdab0000", hashCode.toString());
hashCode.getBytesInternal()[0] = (byte) 0x00;
assertEquals(0x0000ab00, hashCode.asInt());
assertEquals("00ab0000", hashCode.toString());
}
public void testPadToLong() {
assertEquals(0x1111111111111111L, HashCode.fromLong(0x1111111111111111L).padToLong());
assertEquals(0x9999999999999999L, HashCode.fromLong(0x9999999999999999L).padToLong());
assertEquals(0x0000000011111111L, HashCode.fromInt(0x11111111).padToLong());
assertEquals(0x0000000099999999L, HashCode.fromInt(0x99999999).padToLong());
}
public void testPadToLongWith4Bytes() {
assertEquals(0x0000000099999999L, HashCode.fromBytesNoCopy(byteArrayWith9s(4)).padToLong());
}
public void testPadToLongWith6Bytes() {
assertEquals(0x0000999999999999L, HashCode.fromBytesNoCopy(byteArrayWith9s(6)).padToLong());
}
public void testPadToLongWith8Bytes() {
assertEquals(0x9999999999999999L, HashCode.fromBytesNoCopy(byteArrayWith9s(8)).padToLong());
}
private static byte[] byteArrayWith9s(int size) {
byte[] bytez = new byte[size];
Arrays.fill(bytez, (byte) 0x99);
return bytez;
}
public void testToString() {
byte[] data = new byte[] { 127, -128, 5, -1, 14 };
assertEquals("7f8005ff0e", HashCode.fromBytes(data).toString());
assertEquals("7f8005ff0e", base16().lowerCase().encode(data));
}
public void testHashCode_nulls() throws Exception {
sanityTester().testNulls();
}
public void testHashCode_equalsAndSerializable() throws Exception {
sanityTester().testEqualsAndSerializable();
}
public void testRoundTripHashCodeUsingBaseEncoding() {
HashCode hash1 = Hashing.sha1().hashString("foo", Charsets.US_ASCII);
HashCode hash2 =
HashCode.fromBytes(BaseEncoding.base16().lowerCase().decode(hash1.toString()));
assertEquals(hash1, hash2);
}
public void testObjectHashCode() {
HashCode hashCode42 = HashCode.fromInt(42);
assertEquals(42, hashCode42.hashCode());
}
// See https://code.google.com/p/guava-libraries/issues/detail?id=1494
public void testObjectHashCodeWithSameLowOrderBytes() {
// These will have the same first 4 bytes (all 0).
byte[] bytesA = new byte[5];
byte[] bytesB = new byte[5];
// Change only the last (5th) byte
bytesA[4] = (byte) 0xbe;
bytesB[4] = (byte) 0xef;
HashCode hashCodeA = HashCode.fromBytes(bytesA);
HashCode hashCodeB = HashCode.fromBytes(bytesB);
// They aren't equal...
assertFalse(hashCodeA.equals(hashCodeB));
// But they still have the same Object#hashCode() value.
// Technically not a violation of the equals/hashCode contract, but...?
assertEquals(hashCodeA.hashCode(), hashCodeB.hashCode());
}
public void testRoundTripHashCodeUsingFromString() {
HashCode hash1 = Hashing.sha1().hashString("foo", Charsets.US_ASCII);
HashCode hash2 = HashCode.fromString(hash1.toString());
assertEquals(hash1, hash2);
}
public void testRoundTrip() {
for (ExpectedHashCode expected : expectedHashCodes) {
String string = HashCode.fromBytes(expected.bytes).toString();
assertEquals(expected.toString, string);
assertEquals(
expected.toString,
HashCode.fromBytes(
BaseEncoding.base16().lowerCase().decode(string)).toString());
}
}
public void testFromStringFailsWithInvalidHexChar() {
try {
HashCode.fromString("7f8005ff0z");
fail();
} catch (IllegalArgumentException expected) {
}
}
public void testFromStringFailsWithUpperCaseString() {
String string = Hashing.sha1().hashString("foo", Charsets.US_ASCII).toString().toUpperCase();
try {
HashCode.fromString(string);
fail();
} catch (IllegalArgumentException expected) {
}
}
public void testFromStringFailsWithShortInputs() {
try {
HashCode.fromString("");
fail();
} catch (IllegalArgumentException expected) {
}
try {
HashCode.fromString("7");
fail();
} catch (IllegalArgumentException expected) {
}
HashCode.fromString("7f");
}
public void testFromStringFailsWithOddLengthInput() {
try {
HashCode.fromString("7f8");
fail();
} catch (IllegalArgumentException expected) {
}
}
public void testIntWriteBytesTo() {
byte[] dest = new byte[4];
HashCode.fromInt(42).writeBytesTo(dest, 0, 4);
assertTrue(Arrays.equals(
HashCode.fromInt(42).asBytes(),
dest));
}
public void testLongWriteBytesTo() {
byte[] dest = new byte[8];
HashCode.fromLong(42).writeBytesTo(dest, 0, 8);
assertTrue(Arrays.equals(
HashCode.fromLong(42).asBytes(),
dest));
}
private static final HashCode HASH_ABCD =
HashCode.fromBytes(new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd });
public void testWriteBytesTo() {
byte[] dest = new byte[4];
HASH_ABCD.writeBytesTo(dest, 0, 4);
assertTrue(Arrays.equals(
new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd },
dest));
}
public void testWriteBytesToOversizedArray() {
byte[] dest = new byte[5];
HASH_ABCD.writeBytesTo(dest, 0, 4);
assertTrue(Arrays.equals(
new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0x00 },
dest));
}
public void testWriteBytesToOversizedArrayLongMaxLength() {
byte[] dest = new byte[5];
HASH_ABCD.writeBytesTo(dest, 0, 5);
assertTrue(Arrays.equals(
new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd, (byte) 0x00 },
dest));
}
public void testWriteBytesToOversizedArrayShortMaxLength() {
byte[] dest = new byte[5];
HASH_ABCD.writeBytesTo(dest, 0, 3);
assertTrue(Arrays.equals(
new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0x00, (byte) 0x00 },
dest));
}
public void testWriteBytesToUndersizedArray() {
byte[] dest = new byte[3];
try {
HASH_ABCD.writeBytesTo(dest, 0, 4);
fail();
} catch (IndexOutOfBoundsException expected) {
}
}
public void testWriteBytesToUndersizedArrayLongMaxLength() {
byte[] dest = new byte[3];
try {
HASH_ABCD.writeBytesTo(dest, 0, 5);
fail();
} catch (IndexOutOfBoundsException expected) {
}
}
public void testWriteBytesToUndersizedArrayShortMaxLength() {
byte[] dest = new byte[3];
HASH_ABCD.writeBytesTo(dest, 0, 2);
assertTrue(Arrays.equals(
new byte[] { (byte) 0xaa, (byte) 0xbb, (byte) 0x00 },
dest));
}
private static ClassSanityTester.FactoryMethodReturnValueTester sanityTester() {
return new ClassSanityTester()
.setDefault(byte[].class, new byte[] {1, 2, 3, 4})
.setDistinctValues(byte[].class, new byte[] {1, 2, 3, 4}, new byte[] {5, 6, 7, 8})
.setDistinctValues(String.class, "7f8005ff0e", "7f8005ff0f")
.forAllPublicStaticMethods(HashCode.class);
}
private static void assertExpectedHashCode(ExpectedHashCode expectedHashCode, HashCode hash) {
assertTrue(Arrays.equals(expectedHashCode.bytes, hash.asBytes()));
byte[] bb = new byte[hash.bits() / 8];
hash.writeBytesTo(bb, 0, bb.length);
assertTrue(Arrays.equals(expectedHashCode.bytes, bb));
assertEquals(expectedHashCode.asInt, hash.asInt());
if (expectedHashCode.asLong == null) {
try {
hash.asLong();
fail();
} catch (IllegalStateException expected) {}
} else {
assertEquals(expectedHashCode.asLong.longValue(), hash.asLong());
}
assertEquals(expectedHashCode.toString, hash.toString());
assertSideEffectFree(hash);
assertReadableBytes(hash);
}
private static void assertSideEffectFree(HashCode hash) {
byte[] original = hash.asBytes();
byte[] mutated = hash.asBytes();
mutated[0]++;
assertTrue(Arrays.equals(original, hash.asBytes()));
}
private static void assertReadableBytes(HashCode hashCode) {
assertTrue(hashCode.bits() >= 32); // sanity
byte[] hashBytes = hashCode.asBytes();
int totalBytes = hashCode.bits() / 8;
for (int bytes = 0; bytes < totalBytes; bytes++) {
byte[] bb = new byte[bytes];
hashCode.writeBytesTo(bb, 0, bb.length);
assertTrue(Arrays.equals(Arrays.copyOf(hashBytes, bytes), bb));
}
}
private static class ExpectedHashCode {
final byte[] bytes;
final int asInt;
final Long asLong; // null means that asLong should throw an exception
final String toString;
ExpectedHashCode(byte[] bytes, int asInt, Long asLong, String toString) {
this.bytes = bytes;
this.asInt = asInt;
this.asLong = asLong;
this.toString = toString;
}
}
}