• 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 
testTimeToLive_negative()279   public void testTimeToLive_negative() {
280     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
281     try {
282       builder.expireAfterWrite(-1, SECONDS);
283       fail();
284     } catch (IllegalArgumentException expected) {
285     }
286   }
287 
288   @SuppressWarnings("ReturnValueIgnored")
testTimeToLive_small()289   public void testTimeToLive_small() {
290     CacheBuilder.newBuilder().expireAfterWrite(1, NANOSECONDS).build(identityLoader());
291     // well, it didn't blow up.
292   }
293 
testTimeToLive_setTwice()294   public void testTimeToLive_setTwice() {
295     CacheBuilder<Object, Object> builder =
296         CacheBuilder.newBuilder().expireAfterWrite(3600, SECONDS);
297     try {
298       // even to the same value is not allowed
299       builder.expireAfterWrite(3600, SECONDS);
300       fail();
301     } catch (IllegalStateException expected) {
302     }
303   }
304 
testTimeToIdle_negative()305   public void testTimeToIdle_negative() {
306     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
307     try {
308       builder.expireAfterAccess(-1, SECONDS);
309       fail();
310     } catch (IllegalArgumentException expected) {
311     }
312   }
313 
314   @SuppressWarnings("ReturnValueIgnored")
testTimeToIdle_small()315   public void testTimeToIdle_small() {
316     CacheBuilder.newBuilder().expireAfterAccess(1, NANOSECONDS).build(identityLoader());
317     // well, it didn't blow up.
318   }
319 
testTimeToIdle_setTwice()320   public void testTimeToIdle_setTwice() {
321     CacheBuilder<Object, Object> builder =
322         CacheBuilder.newBuilder().expireAfterAccess(3600, SECONDS);
323     try {
324       // even to the same value is not allowed
325       builder.expireAfterAccess(3600, SECONDS);
326       fail();
327     } catch (IllegalStateException expected) {
328     }
329   }
330 
331   @SuppressWarnings("ReturnValueIgnored")
testTimeToIdleAndToLive()332   public void testTimeToIdleAndToLive() {
333     CacheBuilder.newBuilder()
334         .expireAfterWrite(1, NANOSECONDS)
335         .expireAfterAccess(1, NANOSECONDS)
336         .build(identityLoader());
337     // well, it didn't blow up.
338   }
339 
340   @GwtIncompatible // refreshAfterWrite
testRefresh_zero()341   public void testRefresh_zero() {
342     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
343     try {
344       builder.refreshAfterWrite(0, SECONDS);
345       fail();
346     } catch (IllegalArgumentException expected) {
347     }
348   }
349 
350   @GwtIncompatible // refreshAfterWrite
testRefresh_setTwice()351   public void testRefresh_setTwice() {
352     CacheBuilder<Object, Object> builder =
353         CacheBuilder.newBuilder().refreshAfterWrite(3600, SECONDS);
354     try {
355       // even to the same value is not allowed
356       builder.refreshAfterWrite(3600, SECONDS);
357       fail();
358     } catch (IllegalStateException expected) {
359     }
360   }
361 
testTicker_setTwice()362   public void testTicker_setTwice() {
363     Ticker testTicker = Ticker.systemTicker();
364     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().ticker(testTicker);
365     try {
366       // even to the same instance is not allowed
367       builder.ticker(testTicker);
368       fail();
369     } catch (IllegalStateException expected) {
370     }
371   }
372 
testRemovalListener_setTwice()373   public void testRemovalListener_setTwice() {
374     RemovalListener<Object, Object> testListener = nullRemovalListener();
375     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder().removalListener(testListener);
376     try {
377       // even to the same instance is not allowed
378       builder = builder.removalListener(testListener);
379       fail();
380     } catch (IllegalStateException expected) {
381     }
382   }
383 
testValuesIsNotASet()384   public void testValuesIsNotASet() {
385     assertFalse(CacheBuilder.newBuilder().build().asMap().values() instanceof Set);
386   }
387 
388   @GwtIncompatible // CacheTesting
testNullCache()389   public void testNullCache() {
390     CountingRemovalListener<Object, Object> listener = countingRemovalListener();
391     LoadingCache<Object, Object> nullCache =
392         CacheBuilder.newBuilder().maximumSize(0).removalListener(listener).build(identityLoader());
393     assertEquals(0, nullCache.size());
394     Object key = new Object();
395     assertSame(key, nullCache.getUnchecked(key));
396     assertEquals(1, listener.getCount());
397     assertEquals(0, nullCache.size());
398     CacheTesting.checkEmpty(nullCache.asMap());
399   }
400 
401   @GwtIncompatible // QueuingRemovalListener
402 
testRemovalNotification_clear()403   public void testRemovalNotification_clear() throws InterruptedException {
404     // If a clear() happens while a computation is pending, we should not get a removal
405     // notification.
406 
407     final AtomicBoolean shouldWait = new AtomicBoolean(false);
408     final CountDownLatch computingLatch = new CountDownLatch(1);
409     CacheLoader<String, String> computingFunction =
410         new CacheLoader<String, String>() {
411           @Override
412           public String load(String key) throws InterruptedException {
413             if (shouldWait.get()) {
414               computingLatch.await();
415             }
416             return key;
417           }
418         };
419     QueuingRemovalListener<String, String> listener = queuingRemovalListener();
420 
421     final LoadingCache<String, String> cache =
422         CacheBuilder.newBuilder()
423             .concurrencyLevel(1)
424             .removalListener(listener)
425             .build(computingFunction);
426 
427     // seed the map, so its segment's count > 0
428     cache.getUnchecked("a");
429     shouldWait.set(true);
430 
431     final CountDownLatch computationStarted = new CountDownLatch(1);
432     final CountDownLatch computationComplete = new CountDownLatch(1);
433     new Thread(
434             new Runnable() {
435               @Override
436               public void run() {
437                 computationStarted.countDown();
438                 cache.getUnchecked("b");
439                 computationComplete.countDown();
440               }
441             })
442         .start();
443 
444     // wait for the computingEntry to be created
445     computationStarted.await();
446     cache.invalidateAll();
447     // let the computation proceed
448     computingLatch.countDown();
449     // don't check cache.size() until we know the get("b") call is complete
450     computationComplete.await();
451 
452     // At this point, the listener should be holding the seed value (a -> a), and the map should
453     // contain the computed value (b -> b), since the clear() happened before the computation
454     // completed.
455     assertEquals(1, listener.size());
456     RemovalNotification<String, String> notification = listener.remove();
457     assertEquals("a", notification.getKey());
458     assertEquals("a", notification.getValue());
459     assertEquals(1, cache.size());
460     assertEquals("b", cache.getUnchecked("b"));
461   }
462 
463   // "Basher tests", where we throw a bunch of stuff at a LoadingCache and check basic invariants.
464 
465   /**
466    * This is a less carefully-controlled version of {@link #testRemovalNotification_clear} - this is
467    * a black-box test that tries to create lots of different thread-interleavings, and asserts that
468    * each computation is affected by a call to {@code clear()} (and therefore gets passed to the
469    * removal listener), or else is not affected by the {@code clear()} (and therefore exists in the
470    * cache afterward).
471    */
472   @GwtIncompatible // QueuingRemovalListener
473 
testRemovalNotification_clear_basher()474   public void testRemovalNotification_clear_basher() throws InterruptedException {
475     // If a clear() happens close to the end of computation, one of two things should happen:
476     // - computation ends first: the removal listener is called, and the cache does not contain the
477     //   key/value pair
478     // - clear() happens first: the removal listener is not called, and the cache contains the pair
479     AtomicBoolean computationShouldWait = new AtomicBoolean();
480     CountDownLatch computationLatch = new CountDownLatch(1);
481     QueuingRemovalListener<String, String> listener = queuingRemovalListener();
482     final LoadingCache<String, String> cache =
483         CacheBuilder.newBuilder()
484             .removalListener(listener)
485             .concurrencyLevel(20)
486             .build(new DelayingIdentityLoader<String>(computationShouldWait, computationLatch));
487 
488     int nThreads = 100;
489     int nTasks = 1000;
490     int nSeededEntries = 100;
491     Set<String> expectedKeys = Sets.newHashSetWithExpectedSize(nTasks + nSeededEntries);
492     // seed the map, so its segments have a count>0; otherwise, clear() won't visit the in-progress
493     // entries
494     for (int i = 0; i < nSeededEntries; i++) {
495       String s = "b" + i;
496       cache.getUnchecked(s);
497       expectedKeys.add(s);
498     }
499     computationShouldWait.set(true);
500 
501     final AtomicInteger computedCount = new AtomicInteger();
502     ExecutorService threadPool = Executors.newFixedThreadPool(nThreads);
503     final CountDownLatch tasksFinished = new CountDownLatch(nTasks);
504     for (int i = 0; i < nTasks; i++) {
505       final String s = "a" + i;
506       @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored
507       Future<?> possiblyIgnoredError =
508           threadPool.submit(
509               new Runnable() {
510                 @Override
511                 public void run() {
512                   cache.getUnchecked(s);
513                   computedCount.incrementAndGet();
514                   tasksFinished.countDown();
515                 }
516               });
517       expectedKeys.add(s);
518     }
519 
520     computationLatch.countDown();
521     // let some computations complete
522     while (computedCount.get() < nThreads) {
523       Thread.yield();
524     }
525     cache.invalidateAll();
526     tasksFinished.await();
527 
528     // Check all of the removal notifications we received: they should have had correctly-associated
529     // keys and values. (An earlier bug saw removal notifications for in-progress computations,
530     // which had real keys with null values.)
531     Map<String, String> removalNotifications = Maps.newHashMap();
532     for (RemovalNotification<String, String> notification : listener) {
533       removalNotifications.put(notification.getKey(), notification.getValue());
534       assertEquals(
535           "Unexpected key/value pair passed to removalListener",
536           notification.getKey(),
537           notification.getValue());
538     }
539 
540     // All of the seed values should have been visible, so we should have gotten removal
541     // notifications for all of them.
542     for (int i = 0; i < nSeededEntries; i++) {
543       assertEquals("b" + i, removalNotifications.get("b" + i));
544     }
545 
546     // Each of the values added to the map should either still be there, or have seen a removal
547     // notification.
548     assertEquals(expectedKeys, Sets.union(cache.asMap().keySet(), removalNotifications.keySet()));
549     assertTrue(Sets.intersection(cache.asMap().keySet(), removalNotifications.keySet()).isEmpty());
550   }
551 
552   /**
553    * Calls get() repeatedly from many different threads, and tests that all of the removed entries
554    * (removed because of size limits or expiration) trigger appropriate removal notifications.
555    */
556   @GwtIncompatible // QueuingRemovalListener
557 
testRemovalNotification_get_basher()558   public void testRemovalNotification_get_basher() throws InterruptedException {
559     int nTasks = 1000;
560     int nThreads = 100;
561     final int getsPerTask = 1000;
562     final int nUniqueKeys = 10000;
563     final Random random = new Random(); // Randoms.insecureRandom();
564 
565     QueuingRemovalListener<String, String> removalListener = queuingRemovalListener();
566     final AtomicInteger computeCount = new AtomicInteger();
567     final AtomicInteger exceptionCount = new AtomicInteger();
568     final AtomicInteger computeNullCount = new AtomicInteger();
569     CacheLoader<String, String> countingIdentityLoader =
570         new CacheLoader<String, String>() {
571           @Override
572           public String load(String key) throws InterruptedException {
573             int behavior = random.nextInt(4);
574             if (behavior == 0) { // throw an exception
575               exceptionCount.incrementAndGet();
576               throw new RuntimeException("fake exception for test");
577             } else if (behavior == 1) { // return null
578               computeNullCount.incrementAndGet();
579               return null;
580             } else if (behavior == 2) { // slight delay before returning
581               Thread.sleep(5);
582               computeCount.incrementAndGet();
583               return key;
584             } else {
585               computeCount.incrementAndGet();
586               return key;
587             }
588           }
589         };
590     final LoadingCache<String, String> cache =
591         CacheBuilder.newBuilder()
592             .recordStats()
593             .concurrencyLevel(2)
594             .expireAfterWrite(100, MILLISECONDS)
595             .removalListener(removalListener)
596             .maximumSize(5000)
597             .build(countingIdentityLoader);
598 
599     ExecutorService threadPool = Executors.newFixedThreadPool(nThreads);
600     for (int i = 0; i < nTasks; i++) {
601       @SuppressWarnings("unused") // https://errorprone.info/bugpattern/FutureReturnValueIgnored
602       Future<?> possiblyIgnoredError =
603           threadPool.submit(
604               new Runnable() {
605                 @Override
606                 public void run() {
607                   for (int j = 0; j < getsPerTask; j++) {
608                     try {
609                       cache.getUnchecked("key" + random.nextInt(nUniqueKeys));
610                     } catch (RuntimeException e) {
611                     }
612                   }
613                 }
614               });
615     }
616 
617     threadPool.shutdown();
618     threadPool.awaitTermination(300, SECONDS);
619 
620     // Since we're not doing any more cache operations, and the cache only expires/evicts when doing
621     // other operations, the cache and the removal queue won't change from this point on.
622 
623     // Verify that each received removal notification was valid
624     for (RemovalNotification<String, String> notification : removalListener) {
625       assertEquals("Invalid removal notification", notification.getKey(), notification.getValue());
626     }
627 
628     CacheStats stats = cache.stats();
629     assertEquals(removalListener.size(), stats.evictionCount());
630     assertEquals(computeCount.get(), stats.loadSuccessCount());
631     assertEquals(exceptionCount.get() + computeNullCount.get(), stats.loadExceptionCount());
632     // each computed value is still in the cache, or was passed to the removal listener
633     assertEquals(computeCount.get(), cache.size() + removalListener.size());
634   }
635 
636   @GwtIncompatible // NullPointerTester
testNullParameters()637   public void testNullParameters() throws Exception {
638     NullPointerTester tester = new NullPointerTester();
639     CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
640     tester.testAllPublicInstanceMethods(builder);
641   }
642 
643   @GwtIncompatible // CacheTesting
testSizingDefaults()644   public void testSizingDefaults() {
645     LoadingCache<?, ?> cache = CacheBuilder.newBuilder().build(identityLoader());
646     LocalCache<?, ?> map = CacheTesting.toLocalCache(cache);
647     assertThat(map.segments).hasLength(4); // concurrency level
648     assertEquals(4, map.segments[0].table.length()); // capacity / conc level
649   }
650 
651   @GwtIncompatible // CountDownLatch
652   static final class DelayingIdentityLoader<T> extends CacheLoader<T, T> {
653     private final AtomicBoolean shouldWait;
654     private final CountDownLatch delayLatch;
655 
DelayingIdentityLoader(AtomicBoolean shouldWait, CountDownLatch delayLatch)656     DelayingIdentityLoader(AtomicBoolean shouldWait, CountDownLatch delayLatch) {
657       this.shouldWait = shouldWait;
658       this.delayLatch = delayLatch;
659     }
660 
661     @Override
load(T key)662     public T load(T key) throws InterruptedException {
663       if (shouldWait.get()) {
664         delayLatch.await();
665       }
666       return key;
667     }
668   }
669 }
670