1 package com.atlassian.cache;
2
3 import java.util.Objects;
4 import java.util.concurrent.TimeUnit;
5 import java.util.concurrent.atomic.AtomicInteger;
6
7 import javax.annotation.Nonnull;
8
9 import org.hamcrest.Matcher;
10 import org.hamcrest.Matchers;
11 import org.junit.Test;
12
13 import static org.hamcrest.Matchers.hasSize;
14 import static org.hamcrest.Matchers.is;
15 import static org.junit.Assert.assertThat;
16 import static org.junit.Assert.fail;
17
18 public abstract class AbstractCacheTest
19 {
20 public enum CacheType {LOCAL, REMOTE, HYBRID, DEFAULT}
21
22 private final CacheType cacheType;
23
24 public AbstractCacheTest()
25 {
26 this.cacheType = CacheType.DEFAULT;
27 }
28
29 public AbstractCacheTest(CacheType cacheType)
30 {
31 this.cacheType = Objects.requireNonNull(cacheType);
32 }
33
34 @Test
35 public void testWhenRemovingAKeyWhereTheLoaderReturnsNullTheExceptionIsSwallowed()
36 {
37 try
38 {
39 Cache<String, Long> cache = makeNullReturningCache();
40 cache.remove("I don't exist key");
41 }
42 catch (Exception e)
43 {
44 fail("Exception should not be thrown when a loader return null during a remove: " + e.getMessage());
45 }
46 }
47
48 @Test
49 public void testWhenRemovingAKeyWithValueWhereTheLoaderReturnsNullTheExceptionIsSwallowed()
50 {
51 try
52 {
53 Cache<String, Long> cache = makeNullReturningCache();
54 cache.remove("I don't exist key", 1L);
55 }
56 catch (Exception e)
57 {
58 fail("Exception should not be thrown when a loader return null during a remove: " + e.getMessage());
59 }
60 }
61
62 @Test
63 public void testWhenRemovingAllKeysWhereTheLoaderReturnsNullTheExceptionIsSwallowed()
64 {
65 try
66 {
67 Cache<String, Long> cache = makeNullReturningCache();
68 cache.removeAll();
69 }
70 catch (Exception e)
71 {
72 fail("Exception should not be thrown when a loader return null during a remove: " + e.getMessage());
73 }
74 }
75
76
77
78
79
80
81
82
83
84 protected static <T> void assertEventuallyThat(Supplier<T> actual, Matcher<? super T> matcher)
85 {
86 final int TIMEOUT_MILLIS = 200;
87 final int SLEEP_MILLIS = 5;
88 try
89 {
90 for (int millis = 0; !matcher.matches(actual.get()) && millis < TIMEOUT_MILLIS; millis += SLEEP_MILLIS)
91 {
92 Thread.sleep(SLEEP_MILLIS);
93 }
94 }
95 catch (InterruptedException e)
96 {
97 Thread.currentThread().interrupt();
98 }
99 assertThat(actual.get(), matcher);
100 }
101
102 protected CacheFactory factory;
103
104 protected Cache<String, Long> makeExceptionalCache()
105 {
106
107 CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
108 {
109 @Nonnull
110 @Override
111 public Long load(@Nonnull final String key)
112 {
113 return Long.valueOf(key);
114 }
115 };
116 final Cache<String, Long> cache = factory.getCache("mycache", loader, settingsBuilder().build());
117 assertEmpty(cache);
118 return cache;
119 }
120
121 protected Cache<String, Long> makeExpiringCache()
122 {
123
124 CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
125 {
126 @Nonnull
127 @Override
128 public Long load(@Nonnull final String key)
129 {
130 try
131 {
132 return Long.valueOf(key);
133 }
134 catch (NumberFormatException e)
135 {
136 return -21L;
137 }
138 }
139 };
140 CacheSettings settings = settingsBuilder().expireAfterAccess(10, TimeUnit.MILLISECONDS).build();
141 final Cache<String, Long> cache = factory.getCache("mycache", loader, settings);
142 assertEmpty(cache);
143 return cache;
144 }
145
146 protected Cache<String, Long> makeNullReturningCache()
147 {
148
149 CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
150 {
151 @Nonnull
152 @Override
153 public Long load(@Nonnull final String key)
154 {
155 try
156 {
157 return Long.valueOf(key);
158 }
159 catch (NumberFormatException e)
160 {
161 return null;
162 }
163 }
164 };
165 final Cache<String, Long> cache = factory.getCache("mycache", loader, settingsBuilder().build());
166 assertEmpty(cache);
167 return cache;
168 }
169
170 protected Cache<String, Long> makeSimpleCache()
171 {
172 return makeSimpleCache(false);
173 }
174
175 protected Cache<String, Long> makeSimpleCache(boolean statsEnabled)
176 {
177
178 final Cache<String, Long> cache = factory.getCache("mycache", null, settingsBuilder(statsEnabled).build());
179 assertEmpty(cache);
180 return cache;
181 }
182
183 protected Cache<String, Long> makeSizeLimitedCache(int maxEntries, String cacheName)
184 {
185 CacheSettings required = settingsBuilder().maxEntries(maxEntries).build();
186 final Cache<String, Long> cache = factory.getCache(cacheName, null, required);
187 assertEmpty(cache);
188 return cache;
189 }
190
191 protected Cache<String, Long> makeSizeLimitedCache(final int maxEntries, final AtomicInteger loadCounter)
192 {
193
194 CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
195 {
196 @Nonnull
197 @Override
198 public Long load(@Nonnull final String key)
199 {
200 loadCounter.incrementAndGet();
201 return Long.valueOf(key);
202 }
203 };
204 CacheSettings settings = settingsBuilder().maxEntries(maxEntries).build();
205 final Cache<String, Long> cache = factory.getCache("mycache", loader, settings);
206 assertEmpty(cache);
207 return cache;
208 }
209
210 protected Cache<String, Long> makeUnexpiringCache()
211 {
212
213 CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
214 {
215 @Nonnull
216 @Override
217 public Long load(@Nonnull final String key)
218 {
219 try
220 {
221 return Long.valueOf(key);
222 }
223 catch (NumberFormatException e)
224 {
225 return -21L;
226 }
227 }
228 };
229 final Cache<String, Long> cache = factory.getCache("mycache", loader, settingsBuilder().build());
230 assertEmpty(cache);
231 return cache;
232 }
233
234 protected Cache<String, Long> makeSlowCache()
235 {
236 CacheLoader<String, Long> loader = new CacheLoader<String, Long>()
237 {
238 @Nonnull
239 @Override
240 public Long load(@Nonnull final String key)
241 {
242 try
243 {
244 Thread.sleep(100);
245 return Long.valueOf(key);
246 }
247 catch (InterruptedException | NumberFormatException e)
248 {
249 return -21L;
250 }
251 }
252 };
253 final Cache<String, Long> cache = factory.getCache("mycache", loader, settingsBuilder().build());
254 assertEmpty(cache);
255 return cache;
256 }
257
258 protected CacheSettingsBuilder settingsBuilder()
259 {
260 return settingsBuilder(false);
261 }
262
263 protected CacheSettingsBuilder settingsBuilder(boolean statsEnabled)
264 {
265 final CacheSettingsBuilder bob = new CacheSettingsBuilder();
266
267 if (statsEnabled)
268 {
269 bob.statisticsEnabled();
270 } else {
271 bob.statisticsDisabled();
272 }
273
274 if (cacheType == CacheType.LOCAL)
275 {
276 bob.local();
277 }
278 else if (cacheType == CacheType.REMOTE)
279 {
280 bob.remote();
281 }
282 else if (cacheType == CacheType.HYBRID)
283 {
284 bob.remote().replicateViaInvalidation();
285 }
286
287 return bob;
288 }
289
290
291
292 protected <K, V> void assertEmpty(Cache<K, V> cache)
293 {
294 assertThat(cache.getKeys(), Matchers.<K>empty());
295 }
296
297 protected <K, V> void assertSize(Cache<K, V> cache, final int expectedSize)
298 {
299 assertThat(cache.getKeys(), hasSize(expectedSize));
300 }
301
302 @SafeVarargs
303 protected final <K, V> void assertContainsKeys(Cache<K, V> cache, K... keys)
304 {
305 for (K key : keys)
306 {
307 assertThat("Contains key: " + key, cache.containsKey(key), is(true));
308 }
309 }
310
311 @SafeVarargs
312 protected final <K, V> void assertMissingKeys(Cache<K, V> cache, K... keys)
313 {
314 for (K key : keys)
315 {
316 assertThat("Contains key: " + key, cache.containsKey(key), is(false));
317 }
318 }
319 }