• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //    https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.google.setfilters.cuckoofilter;
16 
17 import static com.google.common.truth.Truth.assertThat;
18 import static com.google.common.truth.Truth8.assertThat;
19 import static org.junit.Assert.assertThrows;
20 import static org.mockito.Mockito.mock;
21 import static org.mockito.Mockito.when;
22 
23 import java.util.Arrays;
24 import java.util.List;
25 import java.util.Optional;
26 import java.util.Random;
27 import org.junit.Before;
28 import org.junit.Test;
29 import org.junit.runner.RunWith;
30 import org.junit.runners.Parameterized;
31 import org.junit.runners.Parameterized.Parameter;
32 import org.junit.runners.Parameterized.Parameters;
33 
34 @RunWith(Parameterized.class)
35 public final class CuckooFilterTableTest {
36   private static final int BUCKET_COUNT = 10000;
37   private static final int BUCKET_CAPACITY = 4;
38   private static final int FINGERPRINT_LENGTH = 16;
39 
40   private Random random;
41   private CuckooFilterTable table;
42 
43   private interface CuckooFilterTableFactory {
create(CuckooFilterConfig.Size size, Random random)44     public CuckooFilterTable create(CuckooFilterConfig.Size size, Random random);
45 
createExisting( SerializedCuckooFilterTable serializedTable, Random random)46     public default CuckooFilterTable createExisting(
47         SerializedCuckooFilterTable serializedTable, Random random) {
48       return CuckooFilterTable.createFromSerialization(serializedTable, random);
49     }
50   }
51 
52   private static class SemiSortedCuckooFilterTableFactory implements CuckooFilterTableFactory {
53     @Override
create(CuckooFilterConfig.Size size, Random random)54     public CuckooFilterTable create(CuckooFilterConfig.Size size, Random random) {
55       return new SemiSortedCuckooFilterTable(size, random);
56     }
57   }
58 
59   private static class UncompressedCuckooFilterTableFactory implements CuckooFilterTableFactory {
60     @Override
create(CuckooFilterConfig.Size size, Random random)61     public CuckooFilterTable create(CuckooFilterConfig.Size size, Random random) {
62       return new UncompressedCuckooFilterTable(size, random);
63     }
64   }
65 
66   @Parameters
data()67   public static List<? extends Object> data() {
68     return Arrays.asList(
69         new SemiSortedCuckooFilterTableFactory(), new UncompressedCuckooFilterTableFactory());
70   }
71 
72   @Parameter public CuckooFilterTableFactory tableFactory;
73 
74   @Before
setUp()75   public void setUp() {
76     random = mock(Random.class);
77     table =
78         tableFactory.create(
79             CuckooFilterConfig.Size.newBuilder()
80                 .setBucketCount(BUCKET_COUNT)
81                 .setBucketCapacity(BUCKET_CAPACITY)
82                 .setFingerprintLength(FINGERPRINT_LENGTH)
83                 .build(),
84             random);
85   }
86 
87   @Test
insertWithReplacement()88   public void insertWithReplacement() {
89     for (int i = 0; i < BUCKET_COUNT; i++) {
90       long offset = (long) i * BUCKET_CAPACITY;
91       for (int j = 0; j < BUCKET_CAPACITY; j++) {
92         assertThat(table.insertWithReplacement(i, offset + j + 1)).isEmpty();
93       }
94       when(random.nextInt(BUCKET_CAPACITY)).thenReturn(0);
95 
96       Optional<Long> replaced = table.insertWithReplacement(i, offset + BUCKET_CAPACITY + 1);
97 
98       boolean anyOf = false;
99       for (int j = 0; j < BUCKET_CAPACITY; j++) {
100         anyOf = anyOf || (replaced.get() == offset + j + 1);
101       }
102       assertThat(anyOf).isTrue();
103       assertThat(table.contains(i, replaced.get())).isFalse();
104       for (long fingerprint = offset + 1;
105           fingerprint < offset + BUCKET_CAPACITY + 2;
106           fingerprint++) {
107         if (fingerprint != replaced.get()) {
108           assertThat(table.contains(i, fingerprint)).isTrue();
109         }
110       }
111     }
112   }
113 
114   @Test
contains_containsFingerprint()115   public void contains_containsFingerprint() {
116     assertThat(table.insertWithReplacement(0, 1L)).isEmpty();
117 
118     assertThat(table.contains(0, 1L)).isTrue();
119   }
120 
121   @Test
contains_doesNotContainFingerprint()122   public void contains_doesNotContainFingerprint() {
123     assertThat(table.contains(0, 1L)).isFalse();
124   }
125 
126   @Test
delete_deletesExistingFingerprint()127   public void delete_deletesExistingFingerprint() {
128     assertThat(table.insertWithReplacement(0, 1L)).isEmpty();
129     assertThat(table.contains(0, 1L)).isTrue();
130 
131     assertThat(table.delete(0, 1L)).isTrue();
132     assertThat(table.contains(0, 1L)).isFalse();
133   }
134 
135   @Test
delete_deletesOneFingerprintAtATime()136   public void delete_deletesOneFingerprintAtATime() {
137     assertThat(table.insertWithReplacement(0, 1L)).isEmpty();
138     assertThat(table.insertWithReplacement(0, 1L)).isEmpty();
139     assertThat(table.contains(0, 1L)).isTrue();
140 
141     assertThat(table.delete(0, 1L)).isTrue();
142     assertThat(table.contains(0, 1L)).isTrue();
143     assertThat(table.delete(0, 1L)).isTrue();
144     assertThat(table.contains(0, 1L)).isFalse();
145   }
146 
147   @Test
delete_deletesNonExistingFingerprint()148   public void delete_deletesNonExistingFingerprint() {
149     assertThat(table.delete(0, 1L)).isFalse();
150   }
151 
152   @Test
isFull()153   public void isFull() {
154     for (int j = 0; j < BUCKET_CAPACITY; j++) {
155       assertThat(table.isFull(0)).isFalse();
156       assertThat(table.insertWithReplacement(0, j + 1)).isEmpty();
157     }
158     assertThat(table.isFull(0)).isTrue();
159   }
160 
161   @Test
size()162   public void size() {
163     CuckooFilterConfig.Size size = table.size();
164 
165     assertThat(size.bucketCount()).isEqualTo(BUCKET_COUNT);
166     assertThat(size.bucketCapacity()).isEqualTo(BUCKET_CAPACITY);
167     assertThat(size.fingerprintLength()).isEqualTo(FINGERPRINT_LENGTH);
168   }
169 
170   @Test
serializeAndDeserialize()171   public void serializeAndDeserialize() {
172     for (int i = 0; i < BUCKET_CAPACITY; i++) {
173       long offset = (long) i * BUCKET_CAPACITY;
174       for (int j = 0; j < BUCKET_CAPACITY; j++) {
175         assertThat(table.insertWithReplacement(i, offset + j + 1)).isEmpty();
176       }
177     }
178 
179     SerializedCuckooFilterTable serializedTable = table.serialize();
180     CuckooFilterTable existingTable = tableFactory.createExisting(serializedTable, new Random());
181 
182     for (int i = 0; i < BUCKET_CAPACITY; i++) {
183       long offset = (long) i * BUCKET_CAPACITY;
184       for (int j = 0; j < BUCKET_CAPACITY; j++) {
185         assertThat(existingTable.contains(i, offset + j + 1)).isTrue();
186       }
187     }
188   }
189 
190   @Test
deserialize_failsWithInvalidSerialization()191   public void deserialize_failsWithInvalidSerialization() {
192     SerializedCuckooFilterTable serializedTable =
193         SerializedCuckooFilterTable.createFromByteArray(new byte[12]);
194 
195     String message =
196         assertThrows(
197                 IllegalArgumentException.class,
198                 () -> tableFactory.createExisting(serializedTable, new Random()))
199             .getMessage();
200     assertThat(message).isEqualTo("Unable to parse the SerializedCuckooFilterTable.");
201   }
202 }
203