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 15 package com.google.common.cache; 16 17 import static com.google.common.cache.CacheTesting.checkEmpty; 18 import static com.google.common.cache.CacheTesting.checkValidState; 19 import static com.google.common.cache.TestingCacheLoaders.identityLoader; 20 import static java.util.Arrays.asList; 21 import static java.util.concurrent.TimeUnit.DAYS; 22 import static java.util.concurrent.TimeUnit.MILLISECONDS; 23 import static java.util.concurrent.TimeUnit.SECONDS; 24 import static org.junit.contrib.truth.Truth.ASSERT; 25 26 import com.google.common.base.Function; 27 import com.google.common.cache.CacheBuilderFactory.DurationSpec; 28 import com.google.common.cache.LocalCache.Strength; 29 import com.google.common.collect.ImmutableMap; 30 import com.google.common.collect.ImmutableSet; 31 import com.google.common.collect.Iterables; 32 import com.google.common.collect.Iterators; 33 import com.google.common.collect.Lists; 34 import com.google.common.collect.Maps; 35 import com.google.common.testing.EqualsTester; 36 37 import junit.framework.TestCase; 38 39 import java.util.Collection; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Map.Entry; 43 import java.util.Set; 44 45 /** 46 * {@link LoadingCache} tests that deal with caches that actually contain some key-value mappings. 47 * 48 * @author mike nonemacher 49 */ 50 public class PopulatedCachesTest extends TestCase { 51 // we use integers as keys; make sure the range covers some values that ARE cached by 52 // Integer.valueOf(int), and some that are not cached. (127 is the highest cached value.) 53 static final int WARMUP_MIN = 120; 54 static final int WARMUP_MAX = 135; 55 static final int WARMUP_SIZE = WARMUP_MAX - WARMUP_MIN; 56 57 public void testSize_populated() { 58 for (LoadingCache<Object, Object> cache : caches()) { 59 // don't let the entries get GCed 60 List<Entry<Object, Object>> warmed = warmUp(cache); 61 assertEquals(WARMUP_SIZE, cache.size()); 62 assertMapSize(cache.asMap(), WARMUP_SIZE); 63 checkValidState(cache); 64 } 65 } 66 67 public void testContainsKey_found() { 68 for (LoadingCache<Object, Object> cache : caches()) { 69 // don't let the entries get GCed 70 List<Entry<Object, Object>> warmed = warmUp(cache); 71 for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { 72 Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN); 73 assertTrue(cache.asMap().containsKey(entry.getKey())); 74 assertTrue(cache.asMap().containsValue(entry.getValue())); 75 // this getUnchecked() call shouldn't be a cache miss; verified below 76 assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey())); 77 } 78 assertEquals(WARMUP_SIZE, cache.stats().missCount()); 79 checkValidState(cache); 80 } 81 } 82 83 public void testPut_populated() { 84 for (LoadingCache<Object, Object> cache : caches()) { 85 // don't let the entries get GCed 86 List<Entry<Object, Object>> warmed = warmUp(cache); 87 for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { 88 Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN); 89 Object newValue = new Object(); 90 assertSame(entry.getValue(), cache.asMap().put(entry.getKey(), newValue)); 91 // don't let the new entry get GCed 92 warmed.add(entryOf(entry.getKey(), newValue)); 93 Object newKey = new Object(); 94 assertNull(cache.asMap().put(newKey, entry.getValue())); 95 // this getUnchecked() call shouldn't be a cache miss; verified below 96 assertEquals(newValue, cache.getUnchecked(entry.getKey())); 97 assertEquals(entry.getValue(), cache.getUnchecked(newKey)); 98 // don't let the new entry get GCed 99 warmed.add(entryOf(newKey, entry.getValue())); 100 } 101 assertEquals(WARMUP_SIZE, cache.stats().missCount()); 102 checkValidState(cache); 103 } 104 } 105 106 public void testPutIfAbsent_populated() { 107 for (LoadingCache<Object, Object> cache : caches()) { 108 // don't let the entries get GCed 109 List<Entry<Object, Object>> warmed = warmUp(cache); 110 for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { 111 Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN); 112 Object newValue = new Object(); 113 assertSame(entry.getValue(), cache.asMap().putIfAbsent(entry.getKey(), newValue)); 114 Object newKey = new Object(); 115 assertNull(cache.asMap().putIfAbsent(newKey, entry.getValue())); 116 // this getUnchecked() call shouldn't be a cache miss; verified below 117 assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey())); 118 assertEquals(entry.getValue(), cache.getUnchecked(newKey)); 119 // don't let the new entry get GCed 120 warmed.add(entryOf(newKey, entry.getValue())); 121 } 122 assertEquals(WARMUP_SIZE, cache.stats().missCount()); 123 checkValidState(cache); 124 } 125 } 126 127 public void testPutAll_populated() { 128 for (LoadingCache<Object, Object> cache : caches()) { 129 // don't let the entries get GCed 130 List<Entry<Object, Object>> warmed = warmUp(cache); 131 Object newKey = new Object(); 132 Object newValue = new Object(); 133 cache.asMap().putAll(ImmutableMap.of(newKey, newValue)); 134 // this getUnchecked() call shouldn't be a cache miss; verified below 135 assertEquals(newValue, cache.getUnchecked(newKey)); 136 assertEquals(WARMUP_SIZE, cache.stats().missCount()); 137 checkValidState(cache); 138 } 139 } 140 141 public void testReplace_populated() { 142 for (LoadingCache<Object, Object> cache : caches()) { 143 // don't let the entries get GCed 144 List<Entry<Object, Object>> warmed = warmUp(cache); 145 for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { 146 Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN); 147 Object newValue = new Object(); 148 assertSame(entry.getValue(), cache.asMap().replace(entry.getKey(), newValue)); 149 assertTrue(cache.asMap().replace(entry.getKey(), newValue, entry.getValue())); 150 Object newKey = new Object(); 151 assertNull(cache.asMap().replace(newKey, entry.getValue())); 152 assertFalse(cache.asMap().replace(newKey, entry.getValue(), newValue)); 153 // this getUnchecked() call shouldn't be a cache miss; verified below 154 assertEquals(entry.getValue(), cache.getUnchecked(entry.getKey())); 155 assertFalse(cache.asMap().containsKey(newKey)); 156 } 157 assertEquals(WARMUP_SIZE, cache.stats().missCount()); 158 checkValidState(cache); 159 } 160 } 161 162 public void testRemove_byKey() { 163 for (LoadingCache<Object, Object> cache : caches()) { 164 // don't let the entries get GCed 165 List<Entry<Object, Object>> warmed = warmUp(cache); 166 for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { 167 Entry<Object, Object> entry = warmed.get(i - WARMUP_MIN); 168 Object key = entry.getKey(); 169 assertEquals(entry.getValue(), cache.asMap().remove(key)); 170 assertNull(cache.asMap().remove(key)); 171 assertFalse(cache.asMap().containsKey(key)); 172 } 173 checkEmpty(cache); 174 } 175 } 176 177 public void testRemove_byKeyAndValue() { 178 for (LoadingCache<Object, Object> cache : caches()) { 179 // don't let the entries get GCed 180 List<Entry<Object, Object>> warmed = warmUp(cache); 181 for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { 182 Object key = warmed.get(i - WARMUP_MIN).getKey(); 183 Object value = warmed.get(i - WARMUP_MIN).getValue(); 184 assertFalse(cache.asMap().remove(key, -1)); 185 assertTrue(cache.asMap().remove(key, value)); 186 assertFalse(cache.asMap().remove(key, -1)); 187 assertFalse(cache.asMap().containsKey(key)); 188 } 189 checkEmpty(cache); 190 } 191 } 192 193 public void testKeySet_populated() { 194 for (LoadingCache<Object, Object> cache : caches()) { 195 Set<Object> keys = cache.asMap().keySet(); 196 List<Entry<Object, Object>> warmed = warmUp(cache); 197 198 Object[] expectedArray = Maps.newHashMap(cache.asMap()).keySet().toArray(new Object[0]); 199 ASSERT.that(keys).hasContentsAnyOrder(expectedArray); 200 ASSERT.that(asList(keys.toArray())).hasContentsAnyOrder(expectedArray); 201 ASSERT.that(asList(keys.toArray(new Object[(int) cache.size()]))) 202 .hasContentsAnyOrder(expectedArray); 203 204 new EqualsTester() 205 .addEqualityGroup(cache.asMap().keySet(), keys) 206 .addEqualityGroup(ImmutableSet.of()) 207 .testEquals(); 208 assertEquals(WARMUP_SIZE, keys.size()); 209 for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { 210 Object key = warmed.get(i - WARMUP_MIN).getKey(); 211 assertTrue(keys.contains(key)); 212 assertTrue(keys.remove(key)); 213 assertFalse(keys.remove(key)); 214 assertFalse(keys.contains(key)); 215 } 216 checkEmpty(keys); 217 checkEmpty(cache); 218 } 219 } 220 221 public void testValues_populated() { 222 for (LoadingCache<Object, Object> cache : caches()) { 223 Collection<Object> values = cache.asMap().values(); 224 List<Entry<Object, Object>> warmed = warmUp(cache); 225 226 Object[] expectedArray = Maps.newHashMap(cache.asMap()).values().toArray(new Object[0]); 227 ASSERT.that(values).hasContentsAnyOrder(expectedArray); 228 ASSERT.that(asList(values.toArray())).hasContentsAnyOrder(expectedArray); 229 ASSERT.that(asList(values.toArray(new Object[(int) cache.size()]))) 230 .hasContentsAnyOrder(expectedArray); 231 232 assertEquals(WARMUP_SIZE, values.size()); 233 for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { 234 Object value = warmed.get(i - WARMUP_MIN).getValue(); 235 assertTrue(values.contains(value)); 236 assertTrue(values.remove(value)); 237 assertFalse(values.remove(value)); 238 assertFalse(values.contains(value)); 239 } 240 checkEmpty(values); 241 checkEmpty(cache); 242 } 243 } 244 245 @SuppressWarnings("unchecked") // generic array creation 246 public void testEntrySet_populated() { 247 for (LoadingCache<Object, Object> cache : caches()) { 248 Set<Entry<Object, Object>> entries = cache.asMap().entrySet(); 249 List<Entry<Object, Object>> warmed = warmUp(cache, WARMUP_MIN, WARMUP_MAX); 250 251 Set<Entry<Object, Object>> entrySet = Maps.newHashMap(cache.asMap()).entrySet(); 252 ASSERT.that(entries).is(entrySet); 253 ASSERT.that(entries.toArray()).hasContentsAnyOrder(entrySet.toArray()); 254 ASSERT.that(entries.toArray(new Entry[0])) 255 .hasContentsAnyOrder(entrySet.toArray(new Entry[0])); 256 257 new EqualsTester() 258 .addEqualityGroup(cache.asMap().entrySet(), entries) 259 .addEqualityGroup(ImmutableSet.of()) 260 .testEquals(); 261 assertEquals(WARMUP_SIZE, entries.size()); 262 for (int i = WARMUP_MIN; i < WARMUP_MAX; i++) { 263 Entry<Object, Object> newEntry = warmed.get(i - WARMUP_MIN); 264 assertTrue(entries.contains(newEntry)); 265 assertTrue(entries.remove(newEntry)); 266 assertFalse(entries.remove(newEntry)); 267 assertFalse(entries.contains(newEntry)); 268 } 269 checkEmpty(entries); 270 checkEmpty(cache); 271 } 272 } 273 274 public void testWriteThroughEntry() { 275 for (LoadingCache<Object, Object> cache : caches()) { 276 cache.getUnchecked(1); 277 Entry<Object, Object> entry = Iterables.getOnlyElement(cache.asMap().entrySet()); 278 try { 279 entry.setValue(3); 280 fail("expected entry.setValue to throw UnsupportedOperationException"); 281 } catch (UnsupportedOperationException expected) { 282 } 283 try { 284 entry.setValue(null); 285 fail("expected entry.setValue(null) to throw UnsupportedOperationException"); 286 } catch (UnsupportedOperationException expected) { 287 } 288 checkValidState(cache); 289 } 290 } 291 292 /* ---------------- Local utilities -------------- */ 293 294 /** 295 * Most of the tests in this class run against every one of these caches. 296 */ 297 private Iterable<LoadingCache<Object, Object>> caches() { 298 // lots of different ways to configure a LoadingCache 299 CacheBuilderFactory factory = cacheFactory(); 300 return Iterables.transform(factory.buildAllPermutations(), 301 new Function<CacheBuilder<Object, Object>, LoadingCache<Object, Object>>() { 302 @Override public LoadingCache<Object, Object> apply( 303 CacheBuilder<Object, Object> builder) { 304 return builder.build(identityLoader()); 305 } 306 }); 307 } 308 309 private CacheBuilderFactory cacheFactory() { 310 // This is trickier than expected. We plan to put 15 values in each of these (WARMUP_MIN to 311 // WARMUP_MAX), but the tests assume no values get evicted. Even with a maximumSize of 100, one 312 // of the values gets evicted. With weak keys, we use identity equality, which means using 313 // System.identityHashCode, which means the assignment of keys to segments is nondeterministic, 314 // so more than (maximumSize / #segments) keys could get assigned to the same segment, which 315 // would cause one to be evicted. 316 return new CacheBuilderFactory() 317 .withKeyStrengths(ImmutableSet.of(Strength.STRONG, Strength.WEAK)) 318 .withValueStrengths(ImmutableSet.copyOf(Strength.values())) 319 .withConcurrencyLevels(ImmutableSet.of(1, 4, 16, 64)) 320 .withMaximumSizes(ImmutableSet.of(400, 1000)) 321 .withInitialCapacities(ImmutableSet.of(0, 1, 10, 100, 1000)) 322 .withExpireAfterWrites(ImmutableSet.of( 323 // DurationSpec.of(500, MILLISECONDS), 324 DurationSpec.of(1, SECONDS), 325 DurationSpec.of(1, DAYS))) 326 .withExpireAfterAccesses(ImmutableSet.of( 327 // DurationSpec.of(500, MILLISECONDS), 328 DurationSpec.of(1, SECONDS), 329 DurationSpec.of(1, DAYS))) 330 .withRefreshes(ImmutableSet.of( 331 // DurationSpec.of(500, MILLISECONDS), 332 DurationSpec.of(1, SECONDS), 333 DurationSpec.of(1, DAYS))); 334 } 335 336 private List<Map.Entry<Object, Object>> warmUp(LoadingCache<Object, Object> cache) { 337 return warmUp(cache, WARMUP_MIN, WARMUP_MAX); 338 } 339 340 /** 341 * Returns the entries that were added to the map, so they won't fall out of a map with weak or 342 * soft references until the caller drops the reference to the returned entries. 343 */ 344 private List<Map.Entry<Object, Object>> warmUp( 345 LoadingCache<Object, Object> cache, int minimum, int maximum) { 346 347 List<Map.Entry<Object, Object>> entries = Lists.newArrayList(); 348 for (int i = minimum; i < maximum; i++) { 349 Object key = i; 350 Object value = cache.getUnchecked(key); 351 entries.add(entryOf(key, value)); 352 } 353 return entries; 354 } 355 356 private Entry<Object, Object> entryOf(Object key, Object value) { 357 return Maps.immutableEntry(key, value); 358 } 359 360 private void assertMapSize(Map<?, ?> map, int size) { 361 assertEquals(size, map.size()); 362 if (size > 0) { 363 assertFalse(map.isEmpty()); 364 } else { 365 assertTrue(map.isEmpty()); 366 } 367 assertCollectionSize(map.keySet(), size); 368 assertCollectionSize(map.entrySet(), size); 369 assertCollectionSize(map.values(), size); 370 } 371 372 private void assertCollectionSize(Collection<?> collection, int size) { 373 assertEquals(size, collection.size()); 374 if (size > 0) { 375 assertFalse(collection.isEmpty()); 376 } else { 377 assertTrue(collection.isEmpty()); 378 } 379 assertEquals(size, Iterables.size(collection)); 380 assertEquals(size, Iterators.size(collection.iterator())); 381 } 382 } 383