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