View Javadoc
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); //magic one null will always be returned
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       * Version of {@link org.hamcrest.MatcherAssert#assertThat} that retries for a brief duration before giving up.
78       * This allows reasonable testing of caches with relaxed consistency semantics.
79       * @param actual a supplier that, when evaluated via {@link Supplier#get()}, returns the actual value to be checked.
80       *               This supplier <strong>must</strong> be idempotent, as it will be called repeatedly until either
81       *               the matcher matches or the timeout is exceeded.
82       * @param matcher a matcher that checks the actual value
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         // Build a Cache using the builder
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         // Build a Cache using the builder
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         // Build a Cache using the builder
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         // Build a Cache using the builder
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         // Build a Cache using the builder
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         // Build a Cache using the builder
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     // Hiding broken type inferences
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 }