/*
* Copyright (C) 2011 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.common.cache;
import static com.google.common.cache.TestingCacheLoaders.identityLoader;
import static com.google.common.cache.TestingRemovalListeners.countingRemovalListener;
import static com.google.common.cache.TestingWeighers.constantWeigher;
import static com.google.common.cache.TestingWeighers.intKeyWeigher;
import static java.util.Arrays.asList;
import static org.junit.contrib.truth.Truth.ASSERT;
import com.google.common.cache.CacheTesting.Receiver;
import com.google.common.cache.LocalCache.ReferenceEntry;
import com.google.common.cache.TestingCacheLoaders.IdentityLoader;
import com.google.common.cache.TestingRemovalListeners.CountingRemovalListener;
import junit.framework.TestCase;
import java.util.List;
import java.util.Set;
/**
* Tests relating to cache eviction: what does and doesn't count toward maximumSize, what happens
* when maximumSize is reached, etc.
*
* @author mike nonemacher
*/
public class CacheEvictionTest extends TestCase {
static final int MAX_SIZE = 100;
public void testEviction_setMaxSegmentSize() {
IdentityLoader<Object> loader = identityLoader();
for (int i = 1; i < 1000; i++) {
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
.maximumSize(i)
.build(loader);
assertEquals(i, CacheTesting.getTotalSegmentSize(cache));
}
}
public void testEviction_setMaxSegmentWeight() {
IdentityLoader<Object> loader = identityLoader();
for (int i = 1; i < 1000; i++) {
LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
.maximumWeight(i)
.weigher(constantWeigher(1))
.build(loader);
assertEquals(i, CacheTesting.getTotalSegmentSize(cache));
}
}
public void testEviction_maxSizeOneSegment() {
IdentityLoader<Integer> loader = identityLoader();
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.maximumSize(MAX_SIZE)
.build(loader);
for (int i = 0; i < 2 * MAX_SIZE; i++) {
cache.getUnchecked(i);
assertEquals(Math.min(i + 1, MAX_SIZE), cache.size());
}
assertEquals(MAX_SIZE, cache.size());
CacheTesting.checkValidState(cache);
}
public void testEviction_maxWeightOneSegment() {
IdentityLoader<Integer> loader = identityLoader();
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.maximumWeight(2 * MAX_SIZE)
.weigher(constantWeigher(2))
.build(loader);
for (int i = 0; i < 2 * MAX_SIZE; i++) {
cache.getUnchecked(i);
assertEquals(Math.min(i + 1, MAX_SIZE), cache.size());
}
assertEquals(MAX_SIZE, cache.size());
CacheTesting.checkValidState(cache);
}
public void testEviction_maxSize() {
CountingRemovalListener<Integer, Integer> removalListener = countingRemovalListener();
IdentityLoader<Integer> loader = identityLoader();
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
.maximumSize(MAX_SIZE)
.removalListener(removalListener)
.build(loader);
for (int i = 0; i < 2 * MAX_SIZE; i++) {
cache.getUnchecked(i);
assertTrue(cache.size() <= MAX_SIZE);
}
assertEquals(MAX_SIZE, CacheTesting.accessQueueSize(cache));
assertEquals(MAX_SIZE, cache.size());
CacheTesting.processPendingNotifications(cache);
assertEquals(MAX_SIZE, removalListener.getCount());
CacheTesting.checkValidState(cache);
}
public void testEviction_maxWeight() {
CountingRemovalListener<Integer, Integer> removalListener = countingRemovalListener();
IdentityLoader<Integer> loader = identityLoader();
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
.maximumWeight(2 * MAX_SIZE)
.weigher(constantWeigher(2))
.removalListener(removalListener)
.build(loader);
for (int i = 0; i < 2 * MAX_SIZE; i++) {
cache.getUnchecked(i);
assertTrue(cache.size() <= MAX_SIZE);
}
assertEquals(MAX_SIZE, CacheTesting.accessQueueSize(cache));
assertEquals(MAX_SIZE, cache.size());
CacheTesting.processPendingNotifications(cache);
assertEquals(MAX_SIZE, removalListener.getCount());
CacheTesting.checkValidState(cache);
}
public void testUpdateRecency_onGet() {
IdentityLoader<Integer> loader = identityLoader();
final LoadingCache<Integer, Integer> cache =
CacheBuilder.newBuilder().maximumSize(MAX_SIZE).build(loader);
CacheTesting.checkRecency(cache, MAX_SIZE,
new Receiver<ReferenceEntry<Integer, Integer>>() {
@Override
public void accept(ReferenceEntry<Integer, Integer> entry) {
cache.getUnchecked(entry.getKey());
}
});
}
public void testUpdateRecency_onInvalidate() {
IdentityLoader<Integer> loader = identityLoader();
final LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
.maximumSize(MAX_SIZE)
.concurrencyLevel(1)
.build(loader);
CacheTesting.checkRecency(cache, MAX_SIZE,
new Receiver<ReferenceEntry<Integer, Integer>>() {
@Override
public void accept(ReferenceEntry<Integer, Integer> entry) {
Integer key = entry.getKey();
cache.invalidate(key);
}
});
}
public void testEviction_lru() {
// test lru within a single segment
IdentityLoader<Integer> loader = identityLoader();
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.maximumSize(10)
.build(loader);
CacheTesting.warmUp(cache, 0, 10);
Set<Integer> keySet = cache.asMap().keySet();
ASSERT.that(keySet).hasContentsAnyOrder(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
// re-order
getAll(cache, asList(0, 1, 2));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(3, 4, 5, 6, 7, 8, 9, 0, 1, 2);
// evict 3, 4, 5
getAll(cache, asList(10, 11, 12));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(6, 7, 8, 9, 0, 1, 2, 10, 11, 12);
// re-order
getAll(cache, asList(6, 7, 8));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(9, 0, 1, 2, 10, 11, 12, 6, 7, 8);
// evict 9, 0, 1
getAll(cache, asList(13, 14, 15));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(2, 10, 11, 12, 6, 7, 8, 13, 14, 15);
}
public void testEviction_weightedLru() {
// test weighted lru within a single segment
IdentityLoader<Integer> loader = identityLoader();
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.maximumWeight(45)
.weigher(intKeyWeigher())
.build(loader);
CacheTesting.warmUp(cache, 0, 10);
Set<Integer> keySet = cache.asMap().keySet();
ASSERT.that(keySet).hasContentsAnyOrder(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
// re-order
getAll(cache, asList(0, 1, 2));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(3, 4, 5, 6, 7, 8, 9, 0, 1, 2);
// evict 3, 4, 5
getAll(cache, asList(10));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(6, 7, 8, 9, 0, 1, 2, 10);
// re-order
getAll(cache, asList(6, 7, 8));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(9, 0, 1, 2, 10, 6, 7, 8);
// evict 9, 1, 2, 10
getAll(cache, asList(15));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(0, 6, 7, 8, 15);
// fill empty space
getAll(cache, asList(9));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(0, 6, 7, 8, 15, 9);
// evict 6
getAll(cache, asList(1));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(0, 7, 8, 15, 9, 1);
}
public void testEviction_overweight() {
// test weighted lru within a single segment
IdentityLoader<Integer> loader = identityLoader();
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.maximumWeight(45)
.weigher(intKeyWeigher())
.build(loader);
CacheTesting.warmUp(cache, 0, 10);
Set<Integer> keySet = cache.asMap().keySet();
ASSERT.that(keySet).hasContentsAnyOrder(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
// add an at-the-maximum-weight entry
getAll(cache, asList(45));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(0, 45);
// add an over-the-maximum-weight entry
getAll(cache, asList(46));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(0);
}
public void testEviction_invalidateAll() {
// test that .invalidateAll() resets total weight state correctly
IdentityLoader<Integer> loader = identityLoader();
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder()
.concurrencyLevel(1)
.maximumSize(10)
.build(loader);
Set<Integer> keySet = cache.asMap().keySet();
ASSERT.that(keySet).isEmpty();
// add 0, 1, 2, 3, 4
getAll(cache, asList(0, 1, 2, 3, 4));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(0, 1, 2, 3, 4);
// invalidate all
cache.invalidateAll();
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).isEmpty();
// add 5, 6, 7, 8, 9, 10, 11, 12
getAll(cache, asList(5, 6, 7, 8, 9, 10, 11, 12));
CacheTesting.drainRecencyQueues(cache);
ASSERT.that(keySet).hasContentsAnyOrder(5, 6, 7, 8, 9, 10, 11, 12);
}
private void getAll(LoadingCache<Integer, Integer> cache, List<Integer> keys) {
for (int i : keys) {
cache.getUnchecked(i);
}
}
}