1/*
2 * Copyright (C) 2011 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14
15package com.google.common.cache;
16
17import static com.google.common.base.Preconditions.checkNotNull;
18import static junit.framework.Assert.assertEquals;
19import static junit.framework.Assert.assertFalse;
20import static junit.framework.Assert.assertNotNull;
21import static junit.framework.Assert.assertNotSame;
22import static junit.framework.Assert.assertNull;
23import static junit.framework.Assert.assertSame;
24import static junit.framework.Assert.assertTrue;
25
26import com.google.common.base.Preconditions;
27import com.google.common.cache.LocalCache.LocalLoadingCache;
28import com.google.common.cache.LocalCache.ReferenceEntry;
29import com.google.common.cache.LocalCache.Segment;
30import com.google.common.cache.LocalCache.ValueReference;
31import com.google.common.collect.ImmutableList;
32import com.google.common.collect.ImmutableMap;
33import com.google.common.collect.ImmutableSet;
34import com.google.common.collect.Maps;
35import com.google.common.collect.Sets;
36import com.google.common.testing.EqualsTester;
37import com.google.common.testing.FakeTicker;
38
39import java.lang.ref.Reference;
40import java.util.Collection;
41import java.util.List;
42import java.util.Map;
43import java.util.Map.Entry;
44import java.util.Set;
45import java.util.concurrent.ConcurrentMap;
46import java.util.concurrent.TimeUnit;
47import java.util.concurrent.atomic.AtomicReferenceArray;
48
49import javax.annotation.Nullable;
50
51/**
52 * A collection of utilities for {@link Cache} testing.
53 *
54 * @author mike nonemacher
55 */
56class CacheTesting {
57
58  /**
59   * Poke into the Cache internals to simulate garbage collection of the value associated with the
60   * given key. This assumes that the associated entry is a WeakValueReference or a
61   * SoftValueReference (and not a LoadingValueReference), and throws an IllegalStateException
62   * if that assumption does not hold.
63   */
64  @SuppressWarnings("unchecked")  // the instanceof check and the cast generate this warning
65  static <K, V> void simulateValueReclamation(Cache<K, V> cache, K key) {
66    ReferenceEntry<K, V> entry = getReferenceEntry(cache, key);
67    if (entry != null) {
68      ValueReference<K, V> valueRef = entry.getValueReference();
69      // fail on strong/computing refs
70      Preconditions.checkState(valueRef instanceof Reference);
71      Reference<V> ref = (Reference<V>) valueRef;
72      if (ref != null) {
73        ref.clear();
74      }
75    }
76  }
77
78  /**
79   * Poke into the Cache internals to simulate garbage collection of the given key. This assumes
80   * that the given entry is a weak or soft reference, and throws an IllegalStateException if that
81   * assumption does not hold.
82   */
83  @SuppressWarnings("unchecked")  // the instanceof check and the cast generate this warning
84  static <K, V> void simulateKeyReclamation(Cache<K, V> cache, K key) {
85    ReferenceEntry<K, V> entry = getReferenceEntry(cache, key);
86
87    Preconditions.checkState(entry instanceof Reference);
88    Reference<?> ref = (Reference<?>) entry;
89    if (ref != null) {
90      ref.clear();
91    }
92  }
93
94  static <K, V> ReferenceEntry<K, V> getReferenceEntry(Cache<K, V> cache, K key) {
95    checkNotNull(cache);
96    checkNotNull(key);
97    LocalCache<K, V> map = toLocalCache(cache);
98    return map.getEntry(key);
99  }
100
101  /**
102   * Forces the segment containing the given {@code key} to expand (see
103   * {@link Segment#expand()}.
104   */
105  static <K, V> void forceExpandSegment(Cache<K, V> cache, K key) {
106    checkNotNull(cache);
107    checkNotNull(key);
108    LocalCache<K, V> map = toLocalCache(cache);
109    int hash = map.hash(key);
110    Segment<K, V> segment = map.segmentFor(hash);
111    segment.expand();
112  }
113
114  /**
115   * Gets the {@link LocalCache} used by the given {@link Cache}, if any, or throws an
116   * IllegalArgumentException if this is a Cache type that doesn't have a LocalCache.
117   */
118  static <K, V> LocalCache<K, V> toLocalCache(Cache<K, V> cache) {
119    if (cache instanceof LocalLoadingCache) {
120      return ((LocalLoadingCache<K, V>) cache).localCache;
121    }
122    throw new IllegalArgumentException("Cache of type " + cache.getClass()
123        + " doesn't have a LocalCache.");
124  }
125
126  /**
127   * Determines whether the given cache can be converted to a LocalCache by
128   * {@link #toLocalCache} without throwing an exception.
129   */
130  static boolean hasLocalCache(Cache<?, ?> cache) {
131    return (checkNotNull(cache) instanceof LocalLoadingCache);
132  }
133
134  static void drainRecencyQueues(Cache<?, ?> cache) {
135    if (hasLocalCache(cache)) {
136      LocalCache<?, ?> map = toLocalCache(cache);
137      for (Segment<?, ?> segment : map.segments) {
138        drainRecencyQueue(segment);
139      }
140    }
141  }
142
143  static void drainRecencyQueue(Segment<?, ?> segment) {
144    segment.lock();
145    try {
146      segment.cleanUp();
147    } finally {
148      segment.unlock();
149    }
150  }
151
152  static void drainReferenceQueues(Cache<?, ?> cache) {
153    if (hasLocalCache(cache)) {
154      drainReferenceQueues(toLocalCache(cache));
155    }
156  }
157
158  static void drainReferenceQueues(LocalCache<?, ?> cchm) {
159    for (LocalCache.Segment<?, ?> segment : cchm.segments) {
160      drainReferenceQueue(segment);
161    }
162  }
163
164  static void drainReferenceQueue(LocalCache.Segment<?, ?> segment) {
165    segment.lock();
166    try {
167      segment.drainReferenceQueues();
168    } finally {
169      segment.unlock();
170    }
171  }
172
173  static int getTotalSegmentSize(Cache<?, ?> cache) {
174    LocalCache<?, ?> map = toLocalCache(cache);
175    int totalSize = 0;
176    for (Segment<?, ?> segment : map.segments) {
177      totalSize += segment.maxSegmentWeight;
178    }
179    return totalSize;
180  }
181
182  /**
183   * Peeks into the cache's internals to check its internal consistency. Verifies that each
184   * segment's count matches its #elements (after cleanup), each segment is unlocked, each entry
185   * contains a non-null key and value, and the eviction and expiration queues are consistent
186   * (see {@link #checkEviction}, {@link #checkExpiration}).
187   */
188  static void checkValidState(Cache<?, ?> cache) {
189    if (hasLocalCache(cache)) {
190      checkValidState(toLocalCache(cache));
191    }
192  }
193
194  static void checkValidState(LocalCache<?, ?> cchm) {
195    for (Segment<?, ?> segment : cchm.segments) {
196      segment.cleanUp();
197      assertFalse(segment.isLocked());
198      Map<?, ?> table = segmentTable(segment);
199      // cleanup and then check count after we have a strong reference to all entries
200      segment.cleanUp();
201      // under high memory pressure keys/values may be nulled out but not yet enqueued
202      assertTrue(table.size() <= segment.count);
203      for (Entry<?, ?> entry : table.entrySet()) {
204        assertNotNull(entry.getKey());
205        assertNotNull(entry.getValue());
206        assertSame(entry.getValue(), cchm.get(entry.getKey()));
207      }
208    }
209    checkEviction(cchm);
210    checkExpiration(cchm);
211  }
212
213  /**
214   * Peeks into the cache's internals to verify that its expiration queue is consistent. Verifies
215   * that the next/prev links in the expiration queue are correct, and that the queue is ordered
216   * by expiration time.
217   */
218  static void checkExpiration(Cache<?, ?> cache) {
219    if (hasLocalCache(cache)) {
220      checkExpiration(toLocalCache(cache));
221    }
222  }
223
224  static void checkExpiration(LocalCache<?, ?> cchm) {
225    for (Segment<?, ?> segment : cchm.segments) {
226      if (cchm.usesWriteQueue()) {
227        Set<ReferenceEntry<?, ?>> entries = Sets.newIdentityHashSet();
228
229        ReferenceEntry<?, ?> prev = null;
230        for (ReferenceEntry<?, ?> current : segment.writeQueue) {
231          assertTrue(entries.add(current));
232          if (prev != null) {
233            assertSame(prev, current.getPreviousInWriteQueue());
234            assertSame(prev.getNextInWriteQueue(), current);
235            assertTrue(prev.getWriteTime() <= current.getWriteTime());
236          }
237          Object key = current.getKey();
238          if (key != null) {
239            assertSame(current, segment.getEntry(key, current.getHash()));
240          }
241          prev = current;
242        }
243        assertEquals(segment.count, entries.size());
244      } else {
245        assertTrue(segment.writeQueue.isEmpty());
246      }
247
248      if (cchm.usesAccessQueue()) {
249        Set<ReferenceEntry<?, ?>> entries = Sets.newIdentityHashSet();
250
251        ReferenceEntry<?, ?> prev = null;
252        for (ReferenceEntry<?, ?> current : segment.accessQueue) {
253          assertTrue(entries.add(current));
254          if (prev != null) {
255            assertSame(prev, current.getPreviousInAccessQueue());
256            assertSame(prev.getNextInAccessQueue(), current);
257            // read accesses may be slightly misordered
258            assertTrue(prev.getAccessTime() <= current.getAccessTime()
259                || prev.getAccessTime() - current.getAccessTime() < 1000);
260          }
261          Object key = current.getKey();
262          if (key != null) {
263            assertSame(current, segment.getEntry(key, current.getHash()));
264          }
265          prev = current;
266        }
267        assertEquals(segment.count, entries.size());
268      } else {
269        assertTrue(segment.accessQueue.isEmpty());
270      }
271    }
272  }
273
274  /**
275   * Peeks into the cache's internals to verify that its eviction queue is consistent. Verifies
276   * that the prev/next links are correct, and that all items in each segment are also in that
277   * segment's eviction (recency) queue.
278   */
279  static void checkEviction(Cache<?, ?> cache) {
280    if (hasLocalCache(cache)) {
281      checkEviction(toLocalCache(cache));
282    }
283  }
284
285  static void checkEviction(LocalCache<?, ?> map) {
286    if (map.evictsBySize()) {
287      for (Segment<?, ?> segment : map.segments) {
288        drainRecencyQueue(segment);
289        assertEquals(0, segment.recencyQueue.size());
290        assertEquals(0, segment.readCount.get());
291
292        ReferenceEntry<?, ?> prev = null;
293        for (ReferenceEntry<?, ?> current : segment.accessQueue) {
294          if (prev != null) {
295            assertSame(prev, current.getPreviousInAccessQueue());
296            assertSame(prev.getNextInAccessQueue(), current);
297          }
298          Object key = current.getKey();
299          if (key != null) {
300            assertSame(current, segment.getEntry(key, current.getHash()));
301          }
302          prev = current;
303        }
304      }
305    } else {
306      for (Segment<?, ?> segment : map.segments) {
307        assertEquals(0, segment.recencyQueue.size());
308      }
309    }
310  }
311
312  static int segmentSize(Segment<?, ?> segment) {
313    Map<?, ?> map = segmentTable(segment);
314    return map.size();
315  }
316
317  static <K, V> Map<K, V> segmentTable(Segment<K, V> segment) {
318    AtomicReferenceArray<? extends ReferenceEntry<K, V>> table = segment.table;
319    Map<K, V> map = Maps.newLinkedHashMap();
320    for (int i = 0; i < table.length(); i++) {
321      for (ReferenceEntry<K, V> entry = table.get(i); entry != null; entry = entry.getNext()) {
322        K key = entry.getKey();
323        V value = entry.getValueReference().get();
324        if (key != null && value != null) {
325          assertNull(map.put(key, value));
326        }
327      }
328    }
329    return map;
330  }
331
332  static int writeQueueSize(Cache<?, ?> cache) {
333    LocalCache<?, ?> cchm = toLocalCache(cache);
334
335    int size = 0;
336    for (Segment<?, ?> segment : cchm.segments) {
337      size += writeQueueSize(segment);
338    }
339    return size;
340  }
341
342  static int writeQueueSize(Segment<?, ?> segment) {
343    return segment.writeQueue.size();
344  }
345
346  static int accessQueueSize(Cache<?, ?> cache) {
347    LocalCache<?, ?> cchm = toLocalCache(cache);
348    int size = 0;
349    for (Segment<?, ?> segment : cchm.segments) {
350      size += accessQueueSize(segment);
351    }
352    return size;
353  }
354
355  static int accessQueueSize(Segment<?, ?> segment) {
356    return segment.accessQueue.size();
357  }
358
359  static int expirationQueueSize(Cache<?, ?> cache) {
360    return Math.max(accessQueueSize(cache), writeQueueSize(cache));
361  }
362
363  static void processPendingNotifications(Cache<?, ?> cache) {
364    if (hasLocalCache(cache)) {
365      LocalCache<?, ?> cchm = toLocalCache(cache);
366      cchm.processPendingNotifications();
367    }
368  }
369
370  interface Receiver<T> {
371    void accept(@Nullable T object);
372  }
373
374  /**
375   * Assuming the given cache has maximum size {@code maxSize}, this method populates the cache (by
376   * getting a bunch of different keys), then makes sure all the items in the cache are also in the
377   * eviction queue. It will invoke the given {@code operation} on the first element in the
378   * eviction queue, and then reverify that all items in the cache are in the eviction queue, and
379   * verify that the head of the eviction queue has changed as a result of the operation.
380   */
381  static void checkRecency(LoadingCache<Integer, Integer> cache, int maxSize,
382      Receiver<ReferenceEntry<Integer, Integer>> operation) {
383    checkNotNull(operation);
384    if (hasLocalCache(cache)) {
385      warmUp(cache, 0, 2 * maxSize);
386
387      LocalCache<Integer, Integer> cchm = toLocalCache(cache);
388      Segment<?, ?> segment = cchm.segments[0];
389      drainRecencyQueue(segment);
390      assertEquals(maxSize, accessQueueSize(cache));
391      assertEquals(maxSize, cache.size());
392
393      ReferenceEntry<?, ?> originalHead = segment.accessQueue.peek();
394      @SuppressWarnings("unchecked")
395      ReferenceEntry<Integer, Integer> entry = (ReferenceEntry) originalHead;
396      operation.accept(entry);
397      drainRecencyQueue(segment);
398
399      assertNotSame(originalHead, segment.accessQueue.peek());
400      assertEquals(cache.size(), accessQueueSize(cache));
401    }
402  }
403
404  /**
405   * Warms the given cache by getting all values in {@code [start, end)}, in order.
406   */
407  static void warmUp(LoadingCache<Integer, Integer> map, int start, int end) {
408    checkNotNull(map);
409    for (int i = start; i < end; i++) {
410      map.getUnchecked(i);
411    }
412  }
413
414  static void expireEntries(Cache<?, ?> cache, long expiringTime, FakeTicker ticker) {
415    checkNotNull(ticker);
416    expireEntries(toLocalCache(cache), expiringTime, ticker);
417  }
418
419  static void expireEntries(
420      LocalCache<?, ?> cchm, long expiringTime, FakeTicker ticker) {
421
422    for (Segment<?, ?> segment : cchm.segments) {
423      drainRecencyQueue(segment);
424    }
425
426    ticker.advance(2 * expiringTime, TimeUnit.MILLISECONDS);
427
428    long now = ticker.read();
429    for (Segment<?, ?> segment : cchm.segments) {
430      expireEntries(segment, now);
431      assertEquals("Expiration queue must be empty by now", 0, writeQueueSize(segment));
432      assertEquals("Expiration queue must be empty by now", 0, accessQueueSize(segment));
433      assertEquals("Segments must be empty by now", 0, segmentSize(segment));
434    }
435    cchm.processPendingNotifications();
436  }
437
438  static void expireEntries(Segment<?, ?> segment, long now) {
439    segment.lock();
440    try {
441      segment.expireEntries(now);
442      segment.cleanUp();
443    } finally {
444      segment.unlock();
445    }
446  }
447  static void checkEmpty(Cache<?, ?> cache) {
448    assertEquals(0, cache.size());
449    assertFalse(cache.asMap().containsKey(null));
450    assertFalse(cache.asMap().containsKey(6));
451    assertFalse(cache.asMap().containsValue(null));
452    assertFalse(cache.asMap().containsValue(6));
453    checkEmpty(cache.asMap());
454  }
455
456  static void checkEmpty(ConcurrentMap<?, ?> map) {
457    checkEmpty(map.keySet());
458    checkEmpty(map.values());
459    checkEmpty(map.entrySet());
460    assertEquals(ImmutableMap.of(), map);
461    assertEquals(ImmutableMap.of().hashCode(), map.hashCode());
462    assertEquals(ImmutableMap.of().toString(), map.toString());
463
464    if (map instanceof LocalCache) {
465      LocalCache<?, ?> cchm = (LocalCache<?, ?>) map;
466
467      checkValidState(cchm);
468      assertTrue(cchm.isEmpty());
469      assertEquals(0, cchm.size());
470      for (LocalCache.Segment<?, ?> segment : cchm.segments) {
471        assertEquals(0, segment.count);
472        assertEquals(0, segmentSize(segment));
473        assertTrue(segment.writeQueue.isEmpty());
474        assertTrue(segment.accessQueue.isEmpty());
475      }
476    }
477  }
478
479  static void checkEmpty(Collection<?> collection) {
480    assertTrue(collection.isEmpty());
481    assertEquals(0, collection.size());
482    assertFalse(collection.iterator().hasNext());
483    assertEquals(0, collection.toArray().length);
484    assertEquals(0, collection.toArray(new Object[0]).length);
485    if (collection instanceof Set) {
486      new EqualsTester()
487          .addEqualityGroup(ImmutableSet.of(), collection)
488          .addEqualityGroup(ImmutableSet.of(""))
489          .testEquals();
490    } else if (collection instanceof List) {
491      new EqualsTester()
492          .addEqualityGroup(ImmutableList.of(), collection)
493          .addEqualityGroup(ImmutableList.of(""))
494          .testEquals();
495    }
496  }
497}
498