/*
* 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 com.google.common.primitives.Ints;
import com.google.common.testing.EqualsTester;
import com.google.common.testing.NullPointerTester;
import com.google.common.testing.SerializableTester;
import junit.framework.TestCase;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;
import javax.annotation.Nullable;
/**
* Tests for SimpleGenericBloomFilter and derived BloomFilter views.
*
* @author Dimitris Andreou
*/
public class BloomFilterTest extends TestCase {
/**
* Sanity checking with many combinations of false positive rates and expected insertions
*/
public void testBasic() {
for (double fpr = 0.0000001; fpr < 0.1; fpr *= 10) {
for (int expectedInsertions = 1; expectedInsertions <= 10000; expectedInsertions *= 10) {
checkSanity(BloomFilter.create(HashTestUtils.BAD_FUNNEL, expectedInsertions, fpr));
}
}
}
public void testPreconditions() {
try {
BloomFilter.create(Funnels.stringFunnel(), -1);
fail();
} catch (IllegalArgumentException expected) {}
try {
BloomFilter.create(Funnels.stringFunnel(), -1, 0.03);
fail();
} catch (IllegalArgumentException expected) {}
try {
BloomFilter.create(Funnels.stringFunnel(), 1, 0.0);
fail();
} catch (IllegalArgumentException expected) {}
try {
BloomFilter.create(Funnels.stringFunnel(), 1, 1.0);
fail();
} catch (IllegalArgumentException expected) {}
}
public void testFailureWhenMoreThan255HashFunctionsAreNeeded() {
try {
int n = 1000;
double p = 0.00000000000000000000000000000000000000000000000000000000000000000000000000000001;
BloomFilter.create(Funnels.stringFunnel(), n, p);
fail();
} catch (IllegalArgumentException expected) {}
}
public void testNullPointers() {
NullPointerTester tester = new NullPointerTester();
tester.setDefault(Funnel.class, Funnels.stringFunnel());
tester.setDefault(OutputStream.class, new ByteArrayOutputStream());
tester.setDefault(InputStream.class,
new ByteArrayInputStream(new ByteArrayOutputStream().toByteArray()));
tester.testAllPublicInstanceMethods(BloomFilter.create(Funnels.stringFunnel(), 100));
tester.testAllPublicStaticMethods(BloomFilter.class);
}
/**
* Tests that we never get an optimal hashes number of zero.
*/
public void testOptimalHashes() {
for (int n = 1; n < 1000; n++) {
for (int m = 0; m < 1000; m++) {
assertTrue(BloomFilter.optimalNumOfHashFunctions(n, m) > 0);
}
}
}
/**
* Tests that we always get a non-negative optimal size.
*/
public void testOptimalSize() {
for (int n = 1; n < 1000; n++) {
for (double fpp = Double.MIN_VALUE; fpp < 1.0; fpp += 0.001) {
assertTrue(BloomFilter.optimalNumOfBits(n, fpp) >= 0);
}
}
// some random values
Random random = new Random(0);
for (int repeats = 0; repeats < 10000; repeats++) {
assertTrue(BloomFilter.optimalNumOfBits(random.nextInt(1 << 16), random.nextDouble()) >= 0);
}
// and some crazy values (this used to be capped to Integer.MAX_VALUE, now it can go bigger
assertEquals(3327428144502L, BloomFilter.optimalNumOfBits(
Integer.MAX_VALUE, Double.MIN_VALUE));
try {
BloomFilter.create(HashTestUtils.BAD_FUNNEL, Integer.MAX_VALUE, Double.MIN_VALUE);
fail("we can't represent such a large BF!");
} catch (IllegalArgumentException expected) {
assertEquals("Could not create BloomFilter of 3327428144502 bits", expected.getMessage());
}
}
private void checkSanity(BloomFilter<Object> bf) {
assertFalse(bf.mightContain(new Object()));
for (int i = 0; i < 100; i++) {
Object o = new Object();
bf.put(o);
assertTrue(bf.mightContain(o));
}
}
public void testCopy() {
BloomFilter<CharSequence> original = BloomFilter.create(Funnels.stringFunnel(), 100);
BloomFilter<CharSequence> copy = original.copy();
assertNotSame(original, copy);
assertEquals(original, copy);
}
public void testExpectedFpp() {
BloomFilter<Object> bf = BloomFilter.create(HashTestUtils.BAD_FUNNEL, 10, 0.03);
double fpp = bf.expectedFpp();
assertEquals(0.0, fpp);
// usually completed in less than 200 iterations
while (fpp != 1.0) {
boolean changed = bf.put(new Object());
double newFpp = bf.expectedFpp();
// if changed, the new fpp is strictly higher, otherwise it is the same
assertTrue(changed ? newFpp > fpp : newFpp == fpp);
fpp = newFpp;
}
}
public void testEquals_empty() {
new EqualsTester()
.addEqualityGroup(BloomFilter.create(Funnels.byteArrayFunnel(), 100, 0.01))
.addEqualityGroup(BloomFilter.create(Funnels.byteArrayFunnel(), 100, 0.02))
.addEqualityGroup(BloomFilter.create(Funnels.byteArrayFunnel(), 200, 0.01))
.addEqualityGroup(BloomFilter.create(Funnels.byteArrayFunnel(), 200, 0.02))
.addEqualityGroup(BloomFilter.create(Funnels.stringFunnel(), 100, 0.01))
.addEqualityGroup(BloomFilter.create(Funnels.stringFunnel(), 100, 0.02))
.addEqualityGroup(BloomFilter.create(Funnels.stringFunnel(), 200, 0.01))
.addEqualityGroup(BloomFilter.create(Funnels.stringFunnel(), 200, 0.02))
.testEquals();
}
public void testEquals() {
BloomFilter<CharSequence> bf1 = BloomFilter.create(Funnels.stringFunnel(), 100);
bf1.put("1");
bf1.put("2");
BloomFilter<CharSequence> bf2 = BloomFilter.create(Funnels.stringFunnel(), 100);
bf2.put("1");
bf2.put("2");
new EqualsTester()
.addEqualityGroup(bf1, bf2)
.testEquals();
bf2.put("3");
new EqualsTester()
.addEqualityGroup(bf1)
.addEqualityGroup(bf2)
.testEquals();
}
public void testEqualsWithCustomFunnel() {
BloomFilter<Long> bf1 = BloomFilter.create(new CustomFunnel(), 100);
BloomFilter<Long> bf2 = BloomFilter.create(new CustomFunnel(), 100);
assertEquals(bf1, bf2);
}
public void testSerializationWithCustomFunnel() {
SerializableTester.reserializeAndAssert(BloomFilter.create(new CustomFunnel(), 100));
}
private static final class CustomFunnel implements Funnel<Long> {
@Override
public void funnel(Long value, PrimitiveSink into) {
into.putLong(value);
}
@Override
public boolean equals(@Nullable Object object) {
return (object instanceof CustomFunnel);
}
@Override
public int hashCode() {
return 42;
}
}
public void testPutReturnValue() {
for (int i = 0; i < 10; i++) {
BloomFilter<CharSequence> bf = BloomFilter.create(Funnels.stringFunnel(), 100);
for (int j = 0; j < 10; j++) {
String value = new Object().toString();
boolean mightContain = bf.mightContain(value);
boolean put = bf.put(value);
assertTrue(mightContain != put);
}
}
}
public void testJavaSerialization() {
BloomFilter<byte[]> bf = BloomFilter.create(Funnels.byteArrayFunnel(), 100);
for (int i = 0; i < 10; i++) {
bf.put(Ints.toByteArray(i));
}
BloomFilter<byte[]> copy = SerializableTester.reserialize(bf);
for (int i = 0; i < 10; i++) {
assertTrue(copy.mightContain(Ints.toByteArray(i)));
}
assertEquals(bf.expectedFpp(), copy.expectedFpp());
SerializableTester.reserializeAndAssert(bf);
}
/**
* This test will fail whenever someone updates/reorders the BloomFilterStrategies constants.
* Only appending a new constant is allowed.
*/
public void testBloomFilterStrategies() {
assertEquals(1, BloomFilterStrategies.values().length);
assertEquals(BloomFilterStrategies.MURMUR128_MITZ_32, BloomFilterStrategies.values()[0]);
}
}