• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
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 com.google.common.truth.Truth.assertThat;
26 import static java.util.concurrent.TimeUnit.MILLISECONDS;
27 import static java.util.concurrent.TimeUnit.NANOSECONDS;
28 import static java.util.concurrent.TimeUnit.SECONDS;
29 
30 import com.google.common.annotations.GwtCompatible;
31 import com.google.common.annotations.GwtIncompatible;
32 import com.google.common.base.Ticker;
33 import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener;
34 import com.google.common.cache.TestingRemovalListeners.QueuingRemovalListener;
35 import com.google.common.collect.Maps;
36 import com.google.common.collect.Sets;
37 import com.google.common.testing.NullPointerTester;
38 import java.util.Map;
39 import java.util.Random;
40 import java.util.Set;
41 import java.util.concurrent.CountDownLatch;
42 import java.util.concurrent.ExecutorService;
43 import java.util.concurrent.Executors;
44 import java.util.concurrent.Future;
45 import java.util.concurrent.atomic.AtomicBoolean;
46 import java.util.concurrent.atomic.AtomicInteger;
47 import junit.framework.TestCase;
48 
49 /** Unit tests for CacheBuilder. */
50 @GwtCompatible(emulated = true)
51 public class CacheBuilderTest extends TestCase {
52 
testNewBuilder()53   public void testNewBuilder() {
54     CacheLoader<Object, Integer> loader = constantLoader(1);
55 
56     LoadingCache<String, Integer> cache =
57         CacheBuilder.newBuilder().removalListener(countingRemovalListener()).build(loader);
58 
59     assertEquals(Integer.valueOf(1), cache.getUnchecked("one"));
60     assertEquals(1, cache.size());
61   }
62 
testInitialCapacity_negative()63   public void testInitialCapacity_negative() {
64     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
65     try {
66       builder.initialCapacity(-1);
67       fail();
68     } catch (IllegalArgumentException expected) {
69     }
70   }
71 
testInitialCapacity_setTwice()72   public void testInitialCapacity_setTwice() {
73     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().initialCapacity(16);
74     try {
75       // even to the same value is not allowed
76       builder.initialCapacity(16);
77       fail();
78     } catch (IllegalStateException expected) {
79     }
80   }
81 
82   @GwtIncompatible // CacheTesting
testInitialCapacity_small()83   public void testInitialCapacity_small() {
84     LoadingCache<?, ?> cache = CacheBuilder.newBuilder().initialCapacity(5).build(identityLoader());
85     LocalCache<?, ?> map = CacheTesting.toLocalCache(cache);
86 
87     assertThat(map.segments).hasLength(4);
88     assertEquals(2, map.segments[0].table.length());
89     assertEquals(2, map.segments[1].table.length());
90     assertEquals(2, map.segments[2].table.length());
91     assertEquals(2, map.segments[3].table.length());
92   }
93 
94   @GwtIncompatible // CacheTesting
testInitialCapacity_smallest()95   public void testInitialCapacity_smallest() {
96     LoadingCache<?, ?> cache = CacheBuilder.newBuilder().initialCapacity(0).build(identityLoader());
97     LocalCache<?, ?> map = CacheTesting.toLocalCache(cache);
98 
99     assertThat(map.segments).hasLength(4);
100     // 1 is as low as it goes, not 0. it feels dirty to know this/test this.
101     assertEquals(1, map.segments[0].table.length());
102     assertEquals(1, map.segments[1].table.length());
103     assertEquals(1, map.segments[2].table.length());
104     assertEquals(1, map.segments[3].table.length());
105   }
106 
testInitialCapacity_large()107   public void testInitialCapacity_large() {
108     CacheBuilder.newBuilder().initialCapacity(Integer.MAX_VALUE);
109     // that the builder didn't blow up is enough;
110     // don't actually create this monster!
111   }
112 
testConcurrencyLevel_zero()113   public void testConcurrencyLevel_zero() {
114     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
115     try {
116       builder.concurrencyLevel(0);
117       fail();
118     } catch (IllegalArgumentException expected) {
119     }
120   }
121 
testConcurrencyLevel_setTwice()122   public void testConcurrencyLevel_setTwice() {
123     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().concurrencyLevel(16);
124     try {
125       // even to the same value is not allowed
126       builder.concurrencyLevel(16);
127       fail();
128     } catch (IllegalStateException expected) {
129     }
130   }
131 
132   @GwtIncompatible // CacheTesting
testConcurrencyLevel_small()133   public void testConcurrencyLevel_small() {
134     LoadingCache<?, ?> cache =
135         CacheBuilder.newBuilder().concurrencyLevel(1).build(identityLoader());
136     LocalCache<?, ?> map = CacheTesting.toLocalCache(cache);
137     assertThat(map.segments).hasLength(1);
138   }
139 
testConcurrencyLevel_large()140   public void testConcurrencyLevel_large() {
141     CacheBuilder.newBuilder().concurrencyLevel(Integer.MAX_VALUE);
142     // don't actually build this beast
143   }
144 
testMaximumSize_negative()145   public void testMaximumSize_negative() {
146     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
147     try {
148       builder.maximumSize(-1);
149       fail();
150     } catch (IllegalArgumentException expected) {
151     }
152   }
153 
testMaximumSize_setTwice()154   public void testMaximumSize_setTwice() {
155     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumSize(16);
156     try {
157       // even to the same value is not allowed
158       builder.maximumSize(16);
159       fail();
160     } catch (IllegalStateException expected) {
161     }
162   }
163 
164   @GwtIncompatible // maximumWeight
testMaximumSize_andWeight()165   public void testMaximumSize_andWeight() {
166     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumSize(16);
167     try {
168       builder.maximumWeight(16);
169       fail();
170     } catch (IllegalStateException expected) {
171     }
172   }
173 
174   @GwtIncompatible // digs into internals of the non-GWT implementation
testMaximumSize_largerThanInt()175   public void testMaximumSize_largerThanInt() {
176     CacheBuilder<Object, Object> builder =
177         CacheBuilder.newBuilder().initialCapacity(512).maximumSize(Long.MAX_VALUE);
178     LocalCache<?, ?> cache = ((LocalCache.LocalManualCache<?, ?>) builder.build()).localCache;
179     assertThat(cache.segments.length * cache.segments[0].table.length()).isEqualTo(512);
180   }
181 
182   @GwtIncompatible // maximumWeight
testMaximumWeight_negative()183   public void testMaximumWeight_negative() {
184     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
185     try {
186       builder.maximumWeight(-1);
187       fail();
188     } catch (IllegalArgumentException expected) {
189     }
190   }
191 
192   @GwtIncompatible // maximumWeight
testMaximumWeight_setTwice()193   public void testMaximumWeight_setTwice() {
194     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumWeight(16);
195     try {
196       // even to the same value is not allowed
197       builder.maximumWeight(16);
198       fail();
199     } catch (IllegalStateException expected) {
200     }
201     try {
202       builder.maximumSize(16);
203       fail();
204     } catch (IllegalStateException expected) {
205     }
206   }
207 
208   @GwtIncompatible // maximumWeight
testMaximumWeight_withoutWeigher()209   public void testMaximumWeight_withoutWeigher() {
210     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().maximumWeight(1);
211     try {
212       builder.build(identityLoader());
213       fail();
214     } catch (IllegalStateException expected) {
215     }
216   }
217 
218   @GwtIncompatible // weigher
testWeigher_withoutMaximumWeight()219   public void testWeigher_withoutMaximumWeight() {
220     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().weigher(constantWeigher(42));
221     try {
222       builder.build(identityLoader());
223       fail();
224     } catch (IllegalStateException expected) {
225     }
226   }
227 
228   @GwtIncompatible // weigher
testWeigher_withMaximumSize()229   public void testWeigher_withMaximumSize() {
230     try {
231       CacheBuilder.newBuilder().weigher(constantWeigher(42)).maximumSize(1);
232       fail();
233     } catch (IllegalStateException expected) {
234     }
235     try {
236       CacheBuilder.newBuilder().maximumSize(1).weigher(constantWeigher(42));
237       fail();
238     } catch (IllegalStateException expected) {
239     }
240   }
241 
242   @GwtIncompatible // weakKeys
testKeyStrengthSetTwice()243   public void testKeyStrengthSetTwice() {
244     CacheBuilder<Object, Object> builder1 = CacheBuilder.newBuilder().weakKeys();
245     try {
246       builder1.weakKeys();
247       fail();
248     } catch (IllegalStateException expected) {
249     }
250   }
251 
252   @GwtIncompatible // weakValues
testValueStrengthSetTwice()253   public void testValueStrengthSetTwice() {
254     CacheBuilder<Object, Object> builder1 = CacheBuilder.newBuilder().weakValues();
255     try {
256       builder1.weakValues();
257       fail();
258     } catch (IllegalStateException expected) {
259     }
260     try {
261       builder1.softValues();
262       fail();
263     } catch (IllegalStateException expected) {
264     }
265 
266     CacheBuilder<Object, Object> builder2 = CacheBuilder.newBuilder().softValues();
267     try {
268       builder2.softValues();
269       fail();
270     } catch (IllegalStateException expected) {
271     }
272     try {
273       builder2.weakValues();
274       fail();
275     } catch (IllegalStateException expected) {
276     }
277   }
278 
279   @GwtIncompatible // java.time.Duration
testLargeDurations()280   public void testLargeDurations() {
281     java.time.Duration threeHundredYears = java.time.Duration.ofDays(365 * 300);
282     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
283     try {
284       builder.expireAfterWrite(threeHundredYears);
285       fail();
286     } catch (ArithmeticException expected) {
287     }
288     try {
289       builder.expireAfterAccess(threeHundredYears);
290       fail();
291     } catch (ArithmeticException expected) {
292     }
293     try {
294       builder.refreshAfterWrite(threeHundredYears);
295       fail();
296     } catch (ArithmeticException expected) {
297     }
298   }
299 
testTimeToLive_negative()300   public void testTimeToLive_negative() {
301     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
302     try {
303       builder.expireAfterWrite(-1, SECONDS);
304       fail();
305     } catch (IllegalArgumentException expected) {
306     }
307   }
308 
309   @GwtIncompatible // java.time.Duration
testTimeToLive_negative_duration()310   public void testTimeToLive_negative_duration() {
311     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
312     try {
313       builder.expireAfterWrite(java.time.Duration.ofSeconds(-1));
314       fail();
315     } catch (IllegalArgumentException expected) {
316     }
317   }
318 
testTimeToLive_small()319   public void testTimeToLive_small() {
320     CacheBuilder.newBuilder().expireAfterWrite(1, NANOSECONDS).build(identityLoader());
321     // well, it didn't blow up.
322   }
323 
testTimeToLive_setTwice()324   public void testTimeToLive_setTwice() {
325     CacheBuilder<Object, Object> builder =
326         CacheBuilder.newBuilder().expireAfterWrite(3600, SECONDS);
327     try {
328       // even to the same value is not allowed
329       builder.expireAfterWrite(3600, SECONDS);
330       fail();
331     } catch (IllegalStateException expected) {
332     }
333   }
334 
335   @GwtIncompatible // java.time.Duration
testTimeToLive_setTwice_duration()336   public void testTimeToLive_setTwice_duration() {
337     CacheBuilder<Object, Object> builder =
338         CacheBuilder.newBuilder().expireAfterWrite(java.time.Duration.ofSeconds(3600));
339     try {
340       // even to the same value is not allowed
341       builder.expireAfterWrite(java.time.Duration.ofSeconds(3600));
342       fail();
343     } catch (IllegalStateException expected) {
344     }
345   }
346 
testTimeToIdle_negative()347   public void testTimeToIdle_negative() {
348     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
349     try {
350       builder.expireAfterAccess(-1, SECONDS);
351       fail();
352     } catch (IllegalArgumentException expected) {
353     }
354   }
355 
356   @GwtIncompatible // java.time.Duration
testTimeToIdle_negative_duration()357   public void testTimeToIdle_negative_duration() {
358     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
359     try {
360       builder.expireAfterAccess(java.time.Duration.ofSeconds(-1));
361       fail();
362     } catch (IllegalArgumentException expected) {
363     }
364   }
365 
testTimeToIdle_small()366   public void testTimeToIdle_small() {
367     CacheBuilder.newBuilder().expireAfterAccess(1, NANOSECONDS).build(identityLoader());
368     // well, it didn't blow up.
369   }
370 
testTimeToIdle_setTwice()371   public void testTimeToIdle_setTwice() {
372     CacheBuilder<Object, Object> builder =
373         CacheBuilder.newBuilder().expireAfterAccess(3600, SECONDS);
374     try {
375       // even to the same value is not allowed
376       builder.expireAfterAccess(3600, SECONDS);
377       fail();
378     } catch (IllegalStateException expected) {
379     }
380   }
381 
382   @GwtIncompatible // java.time.Duration
testTimeToIdle_setTwice_duration()383   public void testTimeToIdle_setTwice_duration() {
384     CacheBuilder<Object, Object> builder =
385         CacheBuilder.newBuilder().expireAfterAccess(java.time.Duration.ofSeconds(3600));
386     try {
387       // even to the same value is not allowed
388       builder.expireAfterAccess(java.time.Duration.ofSeconds(3600));
389       fail();
390     } catch (IllegalStateException expected) {
391     }
392   }
393 
testTimeToIdleAndToLive()394   public void testTimeToIdleAndToLive() {
395     CacheBuilder.newBuilder()
396         .expireAfterWrite(1, NANOSECONDS)
397         .expireAfterAccess(1, NANOSECONDS)
398         .build(identityLoader());
399     // well, it didn't blow up.
400   }
401 
402   @GwtIncompatible // refreshAfterWrite
testRefresh_zero()403   public void testRefresh_zero() {
404     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
405     try {
406       builder.refreshAfterWrite(0, SECONDS);
407       fail();
408     } catch (IllegalArgumentException expected) {
409     }
410   }
411 
412   @GwtIncompatible // java.time.Duration
testRefresh_zero_duration()413   public void testRefresh_zero_duration() {
414     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
415     try {
416       builder.refreshAfterWrite(java.time.Duration.ZERO);
417       fail();
418     } catch (IllegalArgumentException expected) {
419     }
420   }
421 
422   @GwtIncompatible // refreshAfterWrite
testRefresh_setTwice()423   public void testRefresh_setTwice() {
424     CacheBuilder<Object, Object> builder =
425         CacheBuilder.newBuilder().refreshAfterWrite(3600, SECONDS);
426     try {
427       // even to the same value is not allowed
428       builder.refreshAfterWrite(3600, SECONDS);
429       fail();
430     } catch (IllegalStateException expected) {
431     }
432   }
433 
434   @GwtIncompatible // java.time.Duration
testRefresh_setTwice_duration()435   public void testRefresh_setTwice_duration() {
436     CacheBuilder<Object, Object> builder =
437         CacheBuilder.newBuilder().refreshAfterWrite(java.time.Duration.ofSeconds(3600));
438     try {
439       // even to the same value is not allowed
440       builder.refreshAfterWrite(java.time.Duration.ofSeconds(3600));
441       fail();
442     } catch (IllegalStateException expected) {
443     }
444   }
445 
testTicker_setTwice()446   public void testTicker_setTwice() {
447     Ticker testTicker = Ticker.systemTicker();
448     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().ticker(testTicker);
449     try {
450       // even to the same instance is not allowed
451       builder.ticker(testTicker);
452       fail();
453     } catch (IllegalStateException expected) {
454     }
455   }
456 
testRemovalListener_setTwice()457   public void testRemovalListener_setTwice() {
458     RemovalListener<Object, Object> testListener = nullRemovalListener();
459     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().removalListener(testListener);
460     try {
461       // even to the same instance is not allowed
462       builder = builder.removalListener(testListener);
463       fail();
464     } catch (IllegalStateException expected) {
465     }
466   }
467 
testValuesIsNotASet()468   public void testValuesIsNotASet() {
469     assertFalse(CacheBuilder.newBuilder().build().asMap().values() instanceof Set);
470   }
471 
472   @GwtIncompatible // CacheTesting
testNullCache()473   public void testNullCache() {
474     CountingRemovalListener<Object, Object> listener = countingRemovalListener();
475     LoadingCache<Object, Object> nullCache =
476         CacheBuilder.newBuilder().maximumSize(0).removalListener(listener).build(identityLoader());
477     assertEquals(0, nullCache.size());
478     Object key = new Object();
479     assertSame(key, nullCache.getUnchecked(key));
480     assertEquals(1, listener.getCount());
481     assertEquals(0, nullCache.size());
482     CacheTesting.checkEmpty(nullCache.asMap());
483   }
484 
485   @GwtIncompatible // QueuingRemovalListener
486 
testRemovalNotification_clear()487   public void testRemovalNotification_clear() throws InterruptedException {
488     // If a clear() happens while a computation is pending, we should not get a removal
489     // notification.
490 
491     final AtomicBoolean shouldWait = new AtomicBoolean(false);
492     final CountDownLatch computingLatch = new CountDownLatch(1);
493     CacheLoader<String, String> computingFunction =
494         new CacheLoader<String, String>() {
495           @Override
496           public String load(String key) throws InterruptedException {
497             if (shouldWait.get()) {
498               computingLatch.await();
499             }
500             return key;
501           }
502         };
503     QueuingRemovalListener<String, String> listener = queuingRemovalListener();
504 
505     final LoadingCache<String, String> cache =
506         CacheBuilder.newBuilder()
507             .concurrencyLevel(1)
508             .removalListener(listener)
509             .build(computingFunction);
510 
511     // seed the map, so its segment's count > 0
512     cache.getUnchecked("a");
513     shouldWait.set(true);
514 
515     final CountDownLatch computationStarted = new CountDownLatch(1);
516     final CountDownLatch computationComplete = new CountDownLatch(1);
517     new Thread(
518             new Runnable() {
519               @Override
520               public void run() {
521                 computationStarted.countDown();
522                 cache.getUnchecked("b");
523                 computationComplete.countDown();
524               }
525             })
526         .start();
527 
528     // wait for the computingEntry to be created
529     computationStarted.await();
530     cache.invalidateAll();
531     // let the computation proceed
532     computingLatch.countDown();
533     // don't check cache.size() until we know the get("b") call is complete
534     computationComplete.await();
535 
536     // At this point, the listener should be holding the seed value (a -> a), and the map should
537     // contain the computed value (b -> b), since the clear() happened before the computation
538     // completed.
539     assertEquals(1, listener.size());
540     RemovalNotification<String, String> notification = listener.remove();
541     assertEquals("a", notification.getKey());
542     assertEquals("a", notification.getValue());
543     assertEquals(1, cache.size());
544     assertEquals("b", cache.getUnchecked("b"));
545   }
546 
547   // "Basher tests", where we throw a bunch of stuff at a LoadingCache and check basic invariants.
548 
549   /**
550    * This is a less carefully-controlled version of {@link #testRemovalNotification_clear} - this is
551    * a black-box test that tries to create lots of different thread-interleavings, and asserts that
552    * each computation is affected by a call to {@code clear()} (and therefore gets passed to the
553    * removal listener), or else is not affected by the {@code clear()} (and therefore exists in the
554    * cache afterward).
555    */
556   @GwtIncompatible // QueuingRemovalListener
557 
testRemovalNotification_clear_basher()558   public void testRemovalNotification_clear_basher() throws InterruptedException {
559     // If a clear() happens close to the end of computation, one of two things should happen:
560     // - computation ends first: the removal listener is called, and the cache does not contain the
561     //   key/value pair
562     // - clear() happens first: the removal listener is not called, and the cache contains the pair
563     AtomicBoolean computationShouldWait = new AtomicBoolean();
564     CountDownLatch computationLatch = new CountDownLatch(1);
565     QueuingRemovalListener<String, String> listener = queuingRemovalListener();
566     final LoadingCache<String, String> cache =
567         CacheBuilder.newBuilder()
568             .removalListener(listener)
569             .concurrencyLevel(20)
570             .build(new DelayingIdentityLoader<String>(computationShouldWait, computationLatch));
571 
572     int nThreads = 100;
573     int nTasks = 1000;
574     int nSeededEntries = 100;
575     Set<String> expectedKeys = Sets.newHashSetWithExpectedSize(nTasks + nSeededEntries);
576     // seed the map, so its segments have a count>0; otherwise, clear() won't visit the in-progress
577     // entries
578     for (int i = 0; i < nSeededEntries; i++) {
579       String s = "b" + i;
580       cache.getUnchecked(s);
581       expectedKeys.add(s);
582     }
583     computationShouldWait.set(true);
584 
585     final AtomicInteger computedCount = new AtomicInteger();
586     ExecutorService threadPool = Executors.newFixedThreadPool(nThreads);
587     final CountDownLatch tasksFinished = new CountDownLatch(nTasks);
588     for (int i = 0; i < nTasks; i++) {
589       final String s = "a" + i;
590       @SuppressWarnings("unused") // go/futurereturn-lsc
591       Future<?> possiblyIgnoredError =
592           threadPool.submit(
593               new Runnable() {
594                 @Override
595                 public void run() {
596                   cache.getUnchecked(s);
597                   computedCount.incrementAndGet();
598                   tasksFinished.countDown();
599                 }
600               });
601       expectedKeys.add(s);
602     }
603 
604     computationLatch.countDown();
605     // let some computations complete
606     while (computedCount.get() < nThreads) {
607       Thread.yield();
608     }
609     cache.invalidateAll();
610     tasksFinished.await();
611 
612     // Check all of the removal notifications we received: they should have had correctly-associated
613     // keys and values. (An earlier bug saw removal notifications for in-progress computations,
614     // which had real keys with null values.)
615     Map<String, String> removalNotifications = Maps.newHashMap();
616     for (RemovalNotification<String, String> notification : listener) {
617       removalNotifications.put(notification.getKey(), notification.getValue());
618       assertEquals(
619           "Unexpected key/value pair passed to removalListener",
620           notification.getKey(),
621           notification.getValue());
622     }
623 
624     // All of the seed values should have been visible, so we should have gotten removal
625     // notifications for all of them.
626     for (int i = 0; i < nSeededEntries; i++) {
627       assertEquals("b" + i, removalNotifications.get("b" + i));
628     }
629 
630     // Each of the values added to the map should either still be there, or have seen a removal
631     // notification.
632     assertEquals(expectedKeys, Sets.union(cache.asMap().keySet(), removalNotifications.keySet()));
633     assertTrue(Sets.intersection(cache.asMap().keySet(), removalNotifications.keySet()).isEmpty());
634   }
635 
636   /**
637    * Calls get() repeatedly from many different threads, and tests that all of the removed entries
638    * (removed because of size limits or expiration) trigger appropriate removal notifications.
639    */
640   @GwtIncompatible // QueuingRemovalListener
641 
testRemovalNotification_get_basher()642   public void testRemovalNotification_get_basher() throws InterruptedException {
643     int nTasks = 1000;
644     int nThreads = 100;
645     final int getsPerTask = 1000;
646     final int nUniqueKeys = 10000;
647     final Random random = new Random(); // Randoms.insecureRandom();
648 
649     QueuingRemovalListener<String, String> removalListener = queuingRemovalListener();
650     final AtomicInteger computeCount = new AtomicInteger();
651     final AtomicInteger exceptionCount = new AtomicInteger();
652     final AtomicInteger computeNullCount = new AtomicInteger();
653     CacheLoader<String, String> countingIdentityLoader =
654         new CacheLoader<String, String>() {
655           @Override
656           public String load(String key) throws InterruptedException {
657             int behavior = random.nextInt(4);
658             if (behavior == 0) { // throw an exception
659               exceptionCount.incrementAndGet();
660               throw new RuntimeException("fake exception for test");
661             } else if (behavior == 1) { // return null
662               computeNullCount.incrementAndGet();
663               return null;
664             } else if (behavior == 2) { // slight delay before returning
665               Thread.sleep(5);
666               computeCount.incrementAndGet();
667               return key;
668             } else {
669               computeCount.incrementAndGet();
670               return key;
671             }
672           }
673         };
674     final LoadingCache<String, String> cache =
675         CacheBuilder.newBuilder()
676             .recordStats()
677             .concurrencyLevel(2)
678             .expireAfterWrite(100, MILLISECONDS)
679             .removalListener(removalListener)
680             .maximumSize(5000)
681             .build(countingIdentityLoader);
682 
683     ExecutorService threadPool = Executors.newFixedThreadPool(nThreads);
684     for (int i = 0; i < nTasks; i++) {
685       @SuppressWarnings("unused") // go/futurereturn-lsc
686       Future<?> possiblyIgnoredError =
687           threadPool.submit(
688               new Runnable() {
689                 @Override
690                 public void run() {
691                   for (int j = 0; j < getsPerTask; j++) {
692                     try {
693                       cache.getUnchecked("key" + random.nextInt(nUniqueKeys));
694                     } catch (RuntimeException e) {
695                     }
696                   }
697                 }
698               });
699     }
700 
701     threadPool.shutdown();
702     threadPool.awaitTermination(300, SECONDS);
703 
704     // Since we're not doing any more cache operations, and the cache only expires/evicts when doing
705     // other operations, the cache and the removal queue won't change from this point on.
706 
707     // Verify that each received removal notification was valid
708     for (RemovalNotification<String, String> notification : removalListener) {
709       assertEquals("Invalid removal notification", notification.getKey(), notification.getValue());
710     }
711 
712     CacheStats stats = cache.stats();
713     assertEquals(removalListener.size(), stats.evictionCount());
714     assertEquals(computeCount.get(), stats.loadSuccessCount());
715     assertEquals(exceptionCount.get() + computeNullCount.get(), stats.loadExceptionCount());
716     // each computed value is still in the cache, or was passed to the removal listener
717     assertEquals(computeCount.get(), cache.size() + removalListener.size());
718   }
719 
720   @GwtIncompatible // NullPointerTester
testNullParameters()721   public void testNullParameters() throws Exception {
722     NullPointerTester tester = new NullPointerTester();
723     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
724     tester.testAllPublicInstanceMethods(builder);
725   }
726 
727   @GwtIncompatible // CacheTesting
testSizingDefaults()728   public void testSizingDefaults() {
729     LoadingCache<?, ?> cache = CacheBuilder.newBuilder().build(identityLoader());
730     LocalCache<?, ?> map = CacheTesting.toLocalCache(cache);
731     assertThat(map.segments).hasLength(4); // concurrency level
732     assertEquals(4, map.segments[0].table.length()); // capacity / conc level
733   }
734 
735   @GwtIncompatible // CountDownLatch
736   static final class DelayingIdentityLoader<T> extends CacheLoader<T, T> {
737     private final AtomicBoolean shouldWait;
738     private final CountDownLatch delayLatch;
739 
DelayingIdentityLoader(AtomicBoolean shouldWait, CountDownLatch delayLatch)740     DelayingIdentityLoader(AtomicBoolean shouldWait, CountDownLatch delayLatch) {
741       this.shouldWait = shouldWait;
742       this.delayLatch = delayLatch;
743     }
744 
745     @Override
load(T key)746     public T load(T key) throws InterruptedException {
747       if (shouldWait.get()) {
748         delayLatch.await();
749       }
750       return key;
751     }
752   }
753 }
754