402 lines
13 KiB
Java
402 lines
13 KiB
Java
/*
|
|
* 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 java.util.Arrays;
|
|
import junit.framework.TestCase;
|
|
|
|
/**
|
|
* 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 unused = 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;
|
|
}
|
|
}
|
|
}
|