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