1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.cache;
18
19 import static com.google.common.cache.TestingCacheLoaders.constantLoader;
20 import static com.google.common.cache.TestingCacheLoaders.identityLoader;
21 import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener;
22 import static com.google.common.cache.TestingRemovalListeners.nullRemovalListener;
23 import static com.google.common.cache.TestingRemovalListeners.queuingRemovalListener;
24 import static com.google.common.cache.TestingWeighers.constantWeigher;
25 import static java.util.concurrent.TimeUnit.NANOSECONDS;
26 import static java.util.concurrent.TimeUnit.SECONDS;
27
28 import com.google.common.annotations.GwtCompatible;
29 import com.google.common.annotations.GwtIncompatible;
30 import com.google.common.base.Ticker;
31 import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener;
32 import com.google.common.cache.TestingRemovalListeners.QueuingRemovalListener;
33 import com.google.common.collect.Maps;
34 import com.google.common.collect.Sets;
35 import com.google.common.testing.NullPointerTester;
36
37 import junit.framework.TestCase;
38
39 import java.util.Map;
40 import java.util.Random;
41 import java.util.Set;
42 import java.util.concurrent.CountDownLatch;
43 import java.util.concurrent.ExecutorService;
44 import java.util.concurrent.Executors;
45 import java.util.concurrent.TimeUnit;
46 import java.util.concurrent.atomic.AtomicBoolean;
47 import java.util.concurrent.atomic.AtomicInteger;
48
49
50
51
52 @GwtCompatible(emulated = true)
53 public class CacheBuilderTest extends TestCase {
54
55 public void testNewBuilder() {
56 CacheLoader<Object, Integer> loader = constantLoader(1);
57
58 LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
59 .removalListener(countingRemovalListener())
60 .build(loader);
61
62 assertEquals(Integer.valueOf(1), cache.getUnchecked("one"));
63 assertEquals(1, cache.size());
64 }
65
66 public void testInitialCapacity_negative() {
67 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>();
68 try {
69 builder.initialCapacity(-1);
70 fail();
71 } catch (IllegalArgumentException expected) {}
72 }
73
74 public void testInitialCapacity_setTwice() {
75 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>().initialCapacity(16);
76 try {
77
78 builder.initialCapacity(16);
79 fail();
80 } catch (IllegalStateException expected) {}
81 }
82
83 @GwtIncompatible("CacheTesting")
84 public void testInitialCapacity_small() {
85 LoadingCache<?, ?> cache = CacheBuilder.newBuilder()
86 .initialCapacity(5)
87 .build(identityLoader());
88 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache);
89
90 assertEquals(4, map.segments.length);
91 assertEquals(2, map.segments[0].table.length());
92 assertEquals(2, map.segments[1].table.length());
93 assertEquals(2, map.segments[2].table.length());
94 assertEquals(2, map.segments[3].table.length());
95 }
96
97 @GwtIncompatible("CacheTesting")
98 public void testInitialCapacity_smallest() {
99 LoadingCache<?, ?> cache = CacheBuilder.newBuilder()
100 .initialCapacity(0)
101 .build(identityLoader());
102 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache);
103
104 assertEquals(4, map.segments.length);
105
106 assertEquals(1, map.segments[0].table.length());
107 assertEquals(1, map.segments[1].table.length());
108 assertEquals(1, map.segments[2].table.length());
109 assertEquals(1, map.segments[3].table.length());
110 }
111
112 public void testInitialCapacity_large() {
113 CacheBuilder.newBuilder().initialCapacity(Integer.MAX_VALUE);
114
115
116 }
117
118 public void testConcurrencyLevel_zero() {
119 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>();
120 try {
121 builder.concurrencyLevel(0);
122 fail();
123 } catch (IllegalArgumentException expected) {}
124 }
125
126 public void testConcurrencyLevel_setTwice() {
127 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>().concurrencyLevel(16);
128 try {
129
130 builder.concurrencyLevel(16);
131 fail();
132 } catch (IllegalStateException expected) {}
133 }
134
135 @GwtIncompatible("CacheTesting")
136 public void testConcurrencyLevel_small() {
137 LoadingCache<?, ?> cache = CacheBuilder.newBuilder()
138 .concurrencyLevel(1)
139 .build(identityLoader());
140 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache);
141 assertEquals(1, map.segments.length);
142 }
143
144 public void testConcurrencyLevel_large() {
145 CacheBuilder.newBuilder().concurrencyLevel(Integer.MAX_VALUE);
146
147 }
148
149 public void testMaximumSize_negative() {
150 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>();
151 try {
152 builder.maximumSize(-1);
153 fail();
154 } catch (IllegalArgumentException expected) {}
155 }
156
157 public void testMaximumSize_setTwice() {
158 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>().maximumSize(16);
159 try {
160
161 builder.maximumSize(16);
162 fail();
163 } catch (IllegalStateException expected) {}
164 }
165
166 @GwtIncompatible("maximumWeight")
167 public void testMaximumSize_andWeight() {
168 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>().maximumSize(16);
169 try {
170 builder.maximumWeight(16);
171 fail();
172 } catch (IllegalStateException expected) {}
173 }
174
175 @GwtIncompatible("maximumWeight")
176 public void testMaximumWeight_negative() {
177 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>();
178 try {
179 builder.maximumWeight(-1);
180 fail();
181 } catch (IllegalArgumentException expected) {}
182 }
183
184 @GwtIncompatible("maximumWeight")
185 public void testMaximumWeight_setTwice() {
186 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>().maximumWeight(16);
187 try {
188
189 builder.maximumWeight(16);
190 fail();
191 } catch (IllegalStateException expected) {}
192 try {
193 builder.maximumSize(16);
194 fail();
195 } catch (IllegalStateException expected) {}
196 }
197
198 @GwtIncompatible("maximumWeight")
199 public void testMaximumWeight_withoutWeigher() {
200 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>()
201 .maximumWeight(1);
202 try {
203 builder.build(identityLoader());
204 fail();
205 } catch (IllegalStateException expected) {}
206 }
207
208 @GwtIncompatible("weigher")
209 public void testWeigher_withoutMaximumWeight() {
210 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>()
211 .weigher(constantWeigher(42));
212 try {
213 builder.build(identityLoader());
214 fail();
215 } catch (IllegalStateException expected) {}
216 }
217
218 @GwtIncompatible("weigher")
219 public void testWeigher_withMaximumSize() {
220 try {
221 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>()
222 .weigher(constantWeigher(42))
223 .maximumSize(1);
224 fail();
225 } catch (IllegalStateException expected) {}
226 try {
227 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>()
228 .maximumSize(1)
229 .weigher(constantWeigher(42));
230 fail();
231 } catch (IllegalStateException expected) {}
232 }
233
234 @GwtIncompatible("weakKeys")
235 public void testKeyStrengthSetTwice() {
236 CacheBuilder<Object, Object> builder1 = new CacheBuilder<Object, Object>().weakKeys();
237 try {
238 builder1.weakKeys();
239 fail();
240 } catch (IllegalStateException expected) {}
241 }
242
243 @GwtIncompatible("weakValues")
244 public void testValueStrengthSetTwice() {
245 CacheBuilder<Object, Object> builder1 = new CacheBuilder<Object, Object>().weakValues();
246 try {
247 builder1.weakValues();
248 fail();
249 } catch (IllegalStateException expected) {}
250 try {
251 builder1.softValues();
252 fail();
253 } catch (IllegalStateException expected) {}
254
255 CacheBuilder<Object, Object> builder2 = new CacheBuilder<Object, Object>().softValues();
256 try {
257 builder2.softValues();
258 fail();
259 } catch (IllegalStateException expected) {}
260 try {
261 builder2.weakValues();
262 fail();
263 } catch (IllegalStateException expected) {}
264 }
265
266 public void testTimeToLive_negative() {
267 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>();
268 try {
269 builder.expireAfterWrite(-1, SECONDS);
270 fail();
271 } catch (IllegalArgumentException expected) {}
272 }
273
274 public void testTimeToLive_small() {
275 CacheBuilder.newBuilder()
276 .expireAfterWrite(1, NANOSECONDS)
277 .build(identityLoader());
278
279 }
280
281 public void testTimeToLive_setTwice() {
282 CacheBuilder<Object, Object> builder =
283 new CacheBuilder<Object, Object>().expireAfterWrite(3600, SECONDS);
284 try {
285
286 builder.expireAfterWrite(3600, SECONDS);
287 fail();
288 } catch (IllegalStateException expected) {}
289 }
290
291 public void testTimeToIdle_negative() {
292 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>();
293 try {
294 builder.expireAfterAccess(-1, SECONDS);
295 fail();
296 } catch (IllegalArgumentException expected) {}
297 }
298
299 public void testTimeToIdle_small() {
300 CacheBuilder.newBuilder()
301 .expireAfterAccess(1, NANOSECONDS)
302 .build(identityLoader());
303
304 }
305
306 public void testTimeToIdle_setTwice() {
307 CacheBuilder<Object, Object> builder =
308 new CacheBuilder<Object, Object>().expireAfterAccess(3600, SECONDS);
309 try {
310
311 builder.expireAfterAccess(3600, SECONDS);
312 fail();
313 } catch (IllegalStateException expected) {}
314 }
315
316 public void testTimeToIdleAndToLive() {
317 CacheBuilder.newBuilder()
318 .expireAfterWrite(1, NANOSECONDS)
319 .expireAfterAccess(1, NANOSECONDS)
320 .build(identityLoader());
321
322 }
323
324 @GwtIncompatible("refreshAfterWrite")
325 public void testRefresh_zero() {
326 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>();
327 try {
328 builder.refreshAfterWrite(0, SECONDS);
329 fail();
330 } catch (IllegalArgumentException expected) {}
331 }
332
333 @GwtIncompatible("refreshAfterWrite")
334 public void testRefresh_setTwice() {
335 CacheBuilder<Object, Object> builder =
336 new CacheBuilder<Object, Object>().refreshAfterWrite(3600, SECONDS);
337 try {
338
339 builder.refreshAfterWrite(3600, SECONDS);
340 fail();
341 } catch (IllegalStateException expected) {}
342 }
343
344 public void testTicker_setTwice() {
345 Ticker testTicker = Ticker.systemTicker();
346 CacheBuilder<Object, Object> builder =
347 new CacheBuilder<Object, Object>().ticker(testTicker);
348 try {
349
350 builder.ticker(testTicker);
351 fail();
352 } catch (IllegalStateException expected) {}
353 }
354
355 public void testRemovalListener_setTwice() {
356 RemovalListener<Object, Object> testListener = nullRemovalListener();
357 CacheBuilder<Object, Object> builder =
358 new CacheBuilder<Object, Object>().removalListener(testListener);
359 try {
360
361 builder = builder.removalListener(testListener);
362 fail();
363 } catch (IllegalStateException expected) {}
364 }
365
366 @GwtIncompatible("CacheTesting")
367 public void testNullCache() {
368 CountingRemovalListener<Object, Object> listener = countingRemovalListener();
369 LoadingCache<Object, Object> nullCache = new CacheBuilder<Object, Object>()
370 .maximumSize(0)
371 .removalListener(listener)
372 .build(identityLoader());
373 assertEquals(0, nullCache.size());
374 Object key = new Object();
375 assertSame(key, nullCache.getUnchecked(key));
376 assertEquals(1, listener.getCount());
377 assertEquals(0, nullCache.size());
378 CacheTesting.checkEmpty(nullCache.asMap());
379 }
380
381 @GwtIncompatible("QueuingRemovalListener")
382
383 public void testRemovalNotification_clear() throws InterruptedException {
384
385
386
387 final AtomicBoolean shouldWait = new AtomicBoolean(false);
388 final CountDownLatch computingLatch = new CountDownLatch(1);
389 CacheLoader<String, String> computingFunction = new CacheLoader<String, String>() {
390 @Override public String load(String key) throws InterruptedException {
391 if (shouldWait.get()) {
392 computingLatch.await();
393 }
394 return key;
395 }
396 };
397 QueuingRemovalListener<String, String> listener = queuingRemovalListener();
398
399 final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
400 .concurrencyLevel(1)
401 .removalListener(listener)
402 .build(computingFunction);
403
404
405 cache.getUnchecked("a");
406 shouldWait.set(true);
407
408 final CountDownLatch computationStarted = new CountDownLatch(1);
409 final CountDownLatch computationComplete = new CountDownLatch(1);
410 new Thread(new Runnable() {
411 @Override public void run() {
412 computationStarted.countDown();
413 cache.getUnchecked("b");
414 computationComplete.countDown();
415 }
416 }).start();
417
418
419 computationStarted.await();
420 cache.invalidateAll();
421
422 computingLatch.countDown();
423
424 computationComplete.await();
425
426
427
428
429 assertEquals(1, listener.size());
430 RemovalNotification<String, String> notification = listener.remove();
431 assertEquals("a", notification.getKey());
432 assertEquals("a", notification.getValue());
433 assertEquals(1, cache.size());
434 assertEquals("b", cache.getUnchecked("b"));
435 }
436
437
438
439
440
441
442
443
444
445
446 @GwtIncompatible("QueuingRemovalListener")
447
448 public void testRemovalNotification_clear_basher() throws InterruptedException {
449
450
451
452
453 AtomicBoolean computationShouldWait = new AtomicBoolean();
454 CountDownLatch computationLatch = new CountDownLatch(1);
455 QueuingRemovalListener<String, String> listener = queuingRemovalListener();
456 final LoadingCache <String, String> cache = CacheBuilder.newBuilder()
457 .removalListener(listener)
458 .concurrencyLevel(20)
459 .build(
460 new DelayingIdentityLoader<String>(computationShouldWait, computationLatch));
461
462 int nThreads = 100;
463 int nTasks = 1000;
464 int nSeededEntries = 100;
465 Set<String> expectedKeys = Sets.newHashSetWithExpectedSize(nTasks + nSeededEntries);
466
467
468 for (int i = 0; i < nSeededEntries; i++) {
469 String s = "b" + i;
470 cache.getUnchecked(s);
471 expectedKeys.add(s);
472 }
473 computationShouldWait.set(true);
474
475 final AtomicInteger computedCount = new AtomicInteger();
476 ExecutorService threadPool = Executors.newFixedThreadPool(nThreads);
477 final CountDownLatch tasksFinished = new CountDownLatch(nTasks);
478 for (int i = 0; i < nTasks; i++) {
479 final String s = "a" + i;
480 threadPool.submit(new Runnable() {
481 @Override public void run() {
482 cache.getUnchecked(s);
483 computedCount.incrementAndGet();
484 tasksFinished.countDown();
485 }
486 });
487 expectedKeys.add(s);
488 }
489
490 computationLatch.countDown();
491
492 while (computedCount.get() < nThreads) {
493 Thread.yield();
494 }
495 cache.invalidateAll();
496 tasksFinished.await();
497
498
499
500
501 Map<String, String> removalNotifications = Maps.newHashMap();
502 for (RemovalNotification<String, String> notification : listener) {
503 removalNotifications.put(notification.getKey(), notification.getValue());
504 assertEquals("Unexpected key/value pair passed to removalListener",
505 notification.getKey(), notification.getValue());
506 }
507
508
509
510 for (int i = 0; i < nSeededEntries; i++) {
511 assertEquals("b" + i, removalNotifications.get("b" + i));
512 }
513
514
515
516 assertEquals(expectedKeys, Sets.union(cache.asMap().keySet(), removalNotifications.keySet()));
517 assertTrue(Sets.intersection(cache.asMap().keySet(), removalNotifications.keySet()).isEmpty());
518 }
519
520
521
522
523
524 @GwtIncompatible("QueuingRemovalListener")
525
526 public void testRemovalNotification_get_basher() throws InterruptedException {
527 int nTasks = 1000;
528 int nThreads = 100;
529 final int getsPerTask = 1000;
530 final int nUniqueKeys = 10000;
531 final Random random = new Random();
532
533 QueuingRemovalListener<String, String> removalListener = queuingRemovalListener();
534 final AtomicInteger computeCount = new AtomicInteger();
535 final AtomicInteger exceptionCount = new AtomicInteger();
536 final AtomicInteger computeNullCount = new AtomicInteger();
537 CacheLoader<String, String> countingIdentityLoader =
538 new CacheLoader<String, String>() {
539 @Override public String load(String key) throws InterruptedException {
540 int behavior = random.nextInt(4);
541 if (behavior == 0) {
542 exceptionCount.incrementAndGet();
543 throw new RuntimeException("fake exception for test");
544 } else if (behavior == 1) {
545 computeNullCount.incrementAndGet();
546 return null;
547 } else if (behavior == 2) {
548 Thread.sleep(5);
549 computeCount.incrementAndGet();
550 return key;
551 } else {
552 computeCount.incrementAndGet();
553 return key;
554 }
555 }
556 };
557 final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
558 .recordStats()
559 .concurrencyLevel(2)
560 .expireAfterWrite(100, TimeUnit.MILLISECONDS)
561 .removalListener(removalListener)
562 .maximumSize(5000)
563 .build(countingIdentityLoader);
564
565 ExecutorService threadPool = Executors.newFixedThreadPool(nThreads);
566 for (int i = 0; i < nTasks; i++) {
567 threadPool.submit(new Runnable() {
568 @Override public void run() {
569 for (int j = 0; j < getsPerTask; j++) {
570 try {
571 cache.getUnchecked("key" + random.nextInt(nUniqueKeys));
572 } catch (RuntimeException e) {
573 }
574 }
575 }
576 });
577 }
578
579 threadPool.shutdown();
580 threadPool.awaitTermination(300, TimeUnit.SECONDS);
581
582
583
584
585
586 for (RemovalNotification<String, String> notification : removalListener) {
587 assertEquals("Invalid removal notification", notification.getKey(), notification.getValue());
588 }
589
590 CacheStats stats = cache.stats();
591 assertEquals(removalListener.size(), stats.evictionCount());
592 assertEquals(computeCount.get(), stats.loadSuccessCount());
593 assertEquals(exceptionCount.get() + computeNullCount.get(), stats.loadExceptionCount());
594
595 assertEquals(computeCount.get(), cache.size() + removalListener.size());
596 }
597
598 @GwtIncompatible("NullPointerTester")
599 public void testNullParameters() throws Exception {
600 NullPointerTester tester = new NullPointerTester();
601 CacheBuilder<Object, Object> builder = new CacheBuilder<Object, Object>();
602 tester.testAllPublicInstanceMethods(builder);
603 }
604
605 @GwtIncompatible("CacheTesting")
606 public void testSizingDefaults() {
607 LoadingCache<?, ?> cache = CacheBuilder.newBuilder().build(identityLoader());
608 LocalCache<?, ?> map = CacheTesting.toLocalCache(cache);
609 assertEquals(4, map.segments.length);
610 assertEquals(4, map.segments[0].table.length());
611 }
612
613 @GwtIncompatible("CountDownLatch")
614 static final class DelayingIdentityLoader<T> extends CacheLoader<T, T> {
615 private final AtomicBoolean shouldWait;
616 private final CountDownLatch delayLatch;
617
618 DelayingIdentityLoader(AtomicBoolean shouldWait, CountDownLatch delayLatch) {
619 this.shouldWait = shouldWait;
620 this.delayLatch = delayLatch;
621 }
622
623 @Override public T load(T key) throws InterruptedException {
624 if (shouldWait.get()) {
625 delayLatch.await();
626 }
627 return key;
628 }
629 }
630 }