| /* |
| * 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.CacheTesting.checkEmpty; |
| import static com.google.common.cache.CacheTesting.checkValidState; |
| import static com.google.common.cache.TestingCacheLoaders.identityLoader; |
| import static com.google.common.truth.Truth.assertThat; |
| import static java.util.concurrent.TimeUnit.DAYS; |
| import static java.util.concurrent.TimeUnit.SECONDS; |
| |
| import com.google.common.base.Function; |
| import com.google.common.cache.CacheBuilderFactory.DurationSpec; |
| import com.google.common.cache.LocalCache.Strength; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Iterators; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.testing.EqualsTester; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import junit.framework.TestCase; |
| |
| /** |
| * {@link LoadingCache} tests that deal with caches that actually contain some key-value mappings. |
| * |
| * @author mike nonemacher |
| */ |
| |
| public class PopulatedCachesTest extends TestCase { |
| // we use integers as keys; make sure the range covers some values that ARE cached by |
| // Integer.valueOf(int), and some that are not cached. (127 is the highest cached value.) |
| static final int WARMUP_MIN = 120; |
| static final int WARMUP_MAX = 135; |
| static final int WARMUP_SIZE = WARMUP_MAX - WARMUP_MIN; |
| |
| public void testSize_populated() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| // don't let the entries get GCed |
| List<Entry<Object, Object>> warmed = warmUp(cache); |
| assertEquals(WARMUP_SIZE, cache.size()); |
| assertMapSize(cache.asMap(), WARMUP_SIZE); |
| checkValidState(cache); |
| } |
| } |
| |
| public void testContainsKey_found() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| // don't let the entries get GCed |
| List<Entry<Object, Object>> warmed = warmUp(cache); |
| for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { |
| Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN); |
| assertTrue(cache.asMap().containsKey(entry.getKey())); |
| assertTrue(cache.asMap().containsValue(entry.getValue())); |
| // this getUnchecked() call shouldn't be a cache miss; verified below |
| assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey())); |
| } |
| assertEquals(WARMUP_SIZE, cache.stats().missCount()); |
| checkValidState(cache); |
| } |
| } |
| |
| public void testPut_populated() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| // don't let the entries get GCed |
| List<Entry<Object, Object>> warmed = warmUp(cache); |
| for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { |
| Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN); |
| Object newValue = new Object(); |
| assertSame(entry.getValue(), cache.asMap().put(entry.getKey(), newValue)); |
| // don't let the new entry get GCed |
| warmed.add(entryOf(entry.getKey(), newValue)); |
| Object newKey = new Object(); |
| assertNull(cache.asMap().put(newKey, entry.getValue())); |
| // this getUnchecked() call shouldn't be a cache miss; verified below |
| assertEquals(newValue, cache.getUnchecked(entry.getKey())); |
| assertEquals(entry.getValue(), cache.getUnchecked(newKey)); |
| // don't let the new entry get GCed |
| warmed.add(entryOf(newKey, entry.getValue())); |
| } |
| assertEquals(WARMUP_SIZE, cache.stats().missCount()); |
| checkValidState(cache); |
| } |
| } |
| |
| public void testPutIfAbsent_populated() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| // don't let the entries get GCed |
| List<Entry<Object, Object>> warmed = warmUp(cache); |
| for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { |
| Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN); |
| Object newValue = new Object(); |
| assertSame(entry.getValue(), cache.asMap().putIfAbsent(entry.getKey(), newValue)); |
| Object newKey = new Object(); |
| assertNull(cache.asMap().putIfAbsent(newKey, entry.getValue())); |
| // this getUnchecked() call shouldn't be a cache miss; verified below |
| assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey())); |
| assertEquals(entry.getValue(), cache.getUnchecked(newKey)); |
| // don't let the new entry get GCed |
| warmed.add(entryOf(newKey, entry.getValue())); |
| } |
| assertEquals(WARMUP_SIZE, cache.stats().missCount()); |
| checkValidState(cache); |
| } |
| } |
| |
| public void testPutAll_populated() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| // don't let the entries get GCed |
| List<Entry<Object, Object>> warmed = warmUp(cache); |
| Object newKey = new Object(); |
| Object newValue = new Object(); |
| cache.asMap().putAll(ImmutableMap.of(newKey, newValue)); |
| // this getUnchecked() call shouldn't be a cache miss; verified below |
| assertEquals(newValue, cache.getUnchecked(newKey)); |
| assertEquals(WARMUP_SIZE, cache.stats().missCount()); |
| checkValidState(cache); |
| } |
| } |
| |
| public void testReplace_populated() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| // don't let the entries get GCed |
| List<Entry<Object, Object>> warmed = warmUp(cache); |
| for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { |
| Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN); |
| Object newValue = new Object(); |
| assertSame(entry.getValue(), cache.asMap().replace(entry.getKey(), newValue)); |
| assertTrue(cache.asMap().replace(entry.getKey(), newValue, entry.getValue())); |
| Object newKey = new Object(); |
| assertNull(cache.asMap().replace(newKey, entry.getValue())); |
| assertFalse(cache.asMap().replace(newKey, entry.getValue(), newValue)); |
| // this getUnchecked() call shouldn't be a cache miss; verified below |
| assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey())); |
| assertFalse(cache.asMap().containsKey(newKey)); |
| } |
| assertEquals(WARMUP_SIZE, cache.stats().missCount()); |
| checkValidState(cache); |
| } |
| } |
| |
| public void testRemove_byKey() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| // don't let the entries get GCed |
| List<Entry<Object, Object>> warmed = warmUp(cache); |
| for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { |
| Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN); |
| Object key = entry.getKey(); |
| assertEquals(entry.getValue(), cache.asMap().remove(key)); |
| assertNull(cache.asMap().remove(key)); |
| assertFalse(cache.asMap().containsKey(key)); |
| } |
| checkEmpty(cache); |
| } |
| } |
| |
| public void testRemove_byKeyAndValue() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| // don't let the entries get GCed |
| List<Entry<Object, Object>> warmed = warmUp(cache); |
| for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { |
| Object key = warmed.get(i - WARMUP_MIN).getKey(); |
| Object value = warmed.get(i - WARMUP_MIN).getValue(); |
| assertFalse(cache.asMap().remove(key, -1)); |
| assertTrue(cache.asMap().remove(key, value)); |
| assertFalse(cache.asMap().remove(key, -1)); |
| assertFalse(cache.asMap().containsKey(key)); |
| } |
| checkEmpty(cache); |
| } |
| } |
| |
| public void testKeySet_populated() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| Set<Object> keys = cache.asMap().keySet(); |
| List<Entry<Object, Object>> warmed = warmUp(cache); |
| |
| Set<Object> expected = Maps.newHashMap(cache.asMap()).keySet(); |
| assertThat(keys).containsExactlyElementsIn(expected); |
| assertThat(keys.toArray()).asList().containsExactlyElementsIn(expected); |
| assertThat(keys.toArray(new Object[0])).asList().containsExactlyElementsIn(expected); |
| |
| new EqualsTester() |
| .addEqualityGroup(cache.asMap().keySet(), keys) |
| .addEqualityGroup(ImmutableSet.of()) |
| .testEquals(); |
| assertEquals(WARMUP_SIZE, keys.size()); |
| for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { |
| Object key = warmed.get(i - WARMUP_MIN).getKey(); |
| assertTrue(keys.contains(key)); |
| assertTrue(keys.remove(key)); |
| assertFalse(keys.remove(key)); |
| assertFalse(keys.contains(key)); |
| } |
| checkEmpty(keys); |
| checkEmpty(cache); |
| } |
| } |
| |
| public void testValues_populated() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| Collection<Object> values = cache.asMap().values(); |
| List<Entry<Object, Object>> warmed = warmUp(cache); |
| |
| Collection<Object> expected = Maps.newHashMap(cache.asMap()).values(); |
| assertThat(values).containsExactlyElementsIn(expected); |
| assertThat(values.toArray()).asList().containsExactlyElementsIn(expected); |
| assertThat(values.toArray(new Object[0])).asList().containsExactlyElementsIn(expected); |
| |
| assertEquals(WARMUP_SIZE, values.size()); |
| for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { |
| Object value = warmed.get(i - WARMUP_MIN).getValue(); |
| assertTrue(values.contains(value)); |
| assertTrue(values.remove(value)); |
| assertFalse(values.remove(value)); |
| assertFalse(values.contains(value)); |
| } |
| checkEmpty(values); |
| checkEmpty(cache); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") // generic array creation |
| |
| public void testEntrySet_populated() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| Set<Entry<Object, Object>> entries = cache.asMap().entrySet(); |
| List<Entry<Object, Object>> warmed = warmUp(cache, WARMUP_MIN, WARMUP_MAX); |
| |
| Set<?> expected = Maps.newHashMap(cache.asMap()).entrySet(); |
| assertThat(entries).containsExactlyElementsIn((Collection<Entry<Object, Object>>) expected); |
| assertThat(entries.toArray()) |
| .asList() |
| .containsExactlyElementsIn((Collection<Object>) expected); |
| assertThat(entries.toArray(new Entry[0])) |
| .asList() |
| .containsExactlyElementsIn((Collection<Entry>) expected); |
| |
| new EqualsTester() |
| .addEqualityGroup(cache.asMap().entrySet(), entries) |
| .addEqualityGroup(ImmutableSet.of()) |
| .testEquals(); |
| assertEquals(WARMUP_SIZE, entries.size()); |
| for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { |
| Entry<Object, Object> newEntry = warmed.get(i - WARMUP_MIN); |
| assertTrue(entries.contains(newEntry)); |
| assertTrue(entries.remove(newEntry)); |
| assertFalse(entries.remove(newEntry)); |
| assertFalse(entries.contains(newEntry)); |
| } |
| checkEmpty(entries); |
| checkEmpty(cache); |
| } |
| } |
| |
| public void testWriteThroughEntry() { |
| for (LoadingCache<Object, Object> cache : caches()) { |
| cache.getUnchecked(1); |
| Entry<Object, Object> entry = Iterables.getOnlyElement(cache.asMap().entrySet()); |
| |
| cache.invalidate(1); |
| assertEquals(0, cache.size()); |
| |
| entry.setValue(3); |
| assertEquals(1, cache.size()); |
| assertEquals(3, cache.getIfPresent(1)); |
| checkValidState(cache); |
| |
| try { |
| entry.setValue(null); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| checkValidState(cache); |
| } |
| } |
| |
| /* ---------------- Local utilities -------------- */ |
| |
| /** Most of the tests in this class run against every one of these caches. */ |
| private Iterable<LoadingCache<Object, Object>> caches() { |
| // lots of different ways to configure a LoadingCache |
| CacheBuilderFactory factory = cacheFactory(); |
| return Iterables.transform( |
| factory.buildAllPermutations(), |
| new Function<CacheBuilder<Object, Object>, LoadingCache<Object, Object>>() { |
| @Override |
| public LoadingCache<Object, Object> apply(CacheBuilder<Object, Object> builder) { |
| return builder.recordStats().build(identityLoader()); |
| } |
| }); |
| } |
| |
| private CacheBuilderFactory cacheFactory() { |
| // This is trickier than expected. We plan to put 15 values in each of these (WARMUP_MIN to |
| // WARMUP_MAX), but the tests assume no values get evicted. Even with a maximumSize of 100, one |
| // of the values gets evicted. With weak keys, we use identity equality, which means using |
| // System.identityHashCode, which means the assignment of keys to segments is nondeterministic, |
| // so more than (maximumSize / #segments) keys could get assigned to the same segment, which |
| // would cause one to be evicted. |
| return new CacheBuilderFactory() |
| .withKeyStrengths(ImmutableSet.of(Strength.STRONG, Strength.WEAK)) |
| .withValueStrengths(ImmutableSet.copyOf(Strength.values())) |
| .withConcurrencyLevels(ImmutableSet.of(1, 4, 16, 64)) |
| .withMaximumSizes(ImmutableSet.of(400, 1000)) |
| .withInitialCapacities(ImmutableSet.of(0, 1, 10, 100, 1000)) |
| .withExpireAfterWrites( |
| ImmutableSet.of( |
| // DurationSpec.of(500, MILLISECONDS), |
| DurationSpec.of(1, SECONDS), DurationSpec.of(1, DAYS))) |
| .withExpireAfterAccesses( |
| ImmutableSet.of( |
| // DurationSpec.of(500, MILLISECONDS), |
| DurationSpec.of(1, SECONDS), DurationSpec.of(1, DAYS))) |
| .withRefreshes( |
| ImmutableSet.of( |
| // DurationSpec.of(500, MILLISECONDS), |
| DurationSpec.of(1, SECONDS), DurationSpec.of(1, DAYS))); |
| } |
| |
| private List<Entry<Object, Object>> warmUp(LoadingCache<Object, Object> cache) { |
| return warmUp(cache, WARMUP_MIN, WARMUP_MAX); |
| } |
| |
| /** |
| * Returns the entries that were added to the map, so they won't fall out of a map with weak or |
| * soft references until the caller drops the reference to the returned entries. |
| */ |
| private List<Entry<Object, Object>> warmUp( |
| LoadingCache<Object, Object> cache, int minimum, int maximum) { |
| |
| List<Entry<Object, Object>> entries = Lists.newArrayList(); |
| for (int i = minimum; i < maximum; i++) { |
| Object key = i; |
| Object value = cache.getUnchecked(key); |
| entries.add(entryOf(key, value)); |
| } |
| return entries; |
| } |
| |
| private Entry<Object, Object> entryOf(Object key, Object value) { |
| return Maps.immutableEntry(key, value); |
| } |
| |
| private void assertMapSize(Map<?, ?> map, int size) { |
| assertEquals(size, map.size()); |
| if (size > 0) { |
| assertFalse(map.isEmpty()); |
| } else { |
| assertTrue(map.isEmpty()); |
| } |
| assertCollectionSize(map.keySet(), size); |
| assertCollectionSize(map.entrySet(), size); |
| assertCollectionSize(map.values(), size); |
| } |
| |
| private void assertCollectionSize(Collection<?> collection, int size) { |
| assertEquals(size, collection.size()); |
| if (size > 0) { |
| assertFalse(collection.isEmpty()); |
| } else { |
| assertTrue(collection.isEmpty()); |
| } |
| assertEquals(size, Iterables.size(collection)); |
| assertEquals(size, Iterators.size(collection.iterator())); |
| } |
| } |