/* * Copyright 2014 Ben Manes. All Rights Reserved. * * 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.github.benmanes.caffeine.cache; import static com.github.benmanes.caffeine.cache.testing.CacheWriterVerifier.verifyWriter; import static com.github.benmanes.caffeine.cache.testing.HasRemovalNotifications.hasRemovalNotifications; import static com.github.benmanes.caffeine.cache.testing.HasStats.hasEvictionCount; import static com.github.benmanes.caffeine.cache.testing.HasStats.hasEvictionWeight; import static com.github.benmanes.caffeine.testing.Awaits.await; import static com.github.benmanes.caffeine.testing.IsEmptyMap.emptyMap; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.verify; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.OptionalInt; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import org.mockito.Mockito; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import com.github.benmanes.caffeine.cache.Policy.Eviction; import com.github.benmanes.caffeine.cache.testing.CacheContext; import com.github.benmanes.caffeine.cache.testing.CacheProvider; import com.github.benmanes.caffeine.cache.testing.CacheSpec; import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheWeigher; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Compute; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Implementation; import com.github.benmanes.caffeine.cache.testing.CacheSpec.InitialCapacity; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Listener; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Maximum; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Population; import com.github.benmanes.caffeine.cache.testing.CacheSpec.ReferenceType; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Writer; import com.github.benmanes.caffeine.cache.testing.CacheValidationListener; import com.github.benmanes.caffeine.cache.testing.CheckNoStats; import com.github.benmanes.caffeine.cache.testing.CheckNoWriter; import com.github.benmanes.caffeine.cache.testing.RejectingCacheWriter.DeleteException; import com.github.benmanes.caffeine.cache.testing.RemovalListeners.RejectingRemovalListener; import com.github.benmanes.caffeine.cache.testing.RemovalNotification; import com.github.benmanes.caffeine.testing.Awaits; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; /** * The test cases for caches with a page replacement algorithm. * * @author ben.manes@gmail.com (Ben Manes) */ @Listeners(CacheValidationListener.class) @Test(dataProviderClass = CacheProvider.class) public final class EvictionTest { /* ---------------- RemovalListener -------------- */ @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, maximumSize = Maximum.FULL, weigher = {CacheWeigher.DEFAULT, CacheWeigher.TEN}, removalListener = Listener.REJECTING) public void removalListener_fails(Cache<Integer, Integer> cache, CacheContext context) { RejectingRemovalListener<Integer, Integer> removalListener = (RejectingRemovalListener<Integer, Integer>) context.removalListener(); // Guava-style caches reject before the max size is reached & are unpredictable removalListener.rejected = 0; long size = cache.estimatedSize(); for (Integer key : context.absentKeys()) { cache.put(key, -key); if (cache.estimatedSize() != ++size) { break; } } assertThat(removalListener.rejected, is(1)); } /* ---------------- Evict (size/weight) -------------- */ @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, maximumSize = { Maximum.ZERO, Maximum.ONE, Maximum.FULL }, weigher = {CacheWeigher.DEFAULT, CacheWeigher.TEN}) public void evict(Cache<Integer, Integer> cache, CacheContext context, Eviction<Integer, Integer> eviction) { cache.putAll(context.absent()); if (eviction.isWeighted()) { assertThat(eviction.weightedSize().getAsLong(), is(context.maximumWeight())); } else { assertThat(cache.estimatedSize(), is(context.maximumSize())); } int count = context.absentKeys().size(); assertThat(context, hasEvictionCount(count)); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.SIZE)); verifyWriter(context, (verifier, writer) -> { Map<Integer, Integer> all = new HashMap<>(context.original()); all.putAll(context.absent()); MapDifference<Integer, Integer> diff = Maps.difference(all, cache.asMap()); verifier.deletedAll(diff.entriesOnlyOnLeft(), RemovalCause.SIZE); }); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.TEN, weigher = CacheWeigher.COLLECTION, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void evict_weighted(Cache<Integer, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { @SuppressWarnings({"unchecked", "rawtypes"}) CacheWriter<Integer, List<Integer>> writer = (CacheWriter) context.cacheWriter(); // Enforce full initialization of internal structures for (int i = 0; i < context.maximumSize(); i++) { cache.put(i, Collections.emptyList()); } cache.invalidateAll(); Mockito.<Object>reset(writer); List<Integer> value1 = asList(8, 9, 10); List<Integer> value2 = asList(3, 4, 5, 6, 7); List<Integer> value3 = asList(1, 2); List<Integer> value4 = asList(11); List<Integer> value5 = asList(12, 13, 14, 15, 16, 17, 18, 19, 20); // Never evicted cache.put(0, asList()); cache.put(1, value1); cache.put(2, value2); cache.put(3, value3); await().until(cache::estimatedSize, is(4L)); assertThat(eviction.weightedSize().getAsLong(), is(10L)); // [0 | 1, 2, 3] -> [0, 4 | 2, 3] cache.put(4, value4); await().until(cache::estimatedSize, is(4L)); assertThat(cache.asMap().containsKey(1), is(false)); assertThat(eviction.weightedSize().getAsLong(), is(8L)); verifyWriter(context, (verifier, ignored) -> { verify(writer).delete(1, value1, RemovalCause.SIZE); verifier.deletions(1, RemovalCause.SIZE); }); // [0, 4 | 2, 3] remains (5 exceeds window and has the same usage history, so evicted) cache.put(5, value5); await().until(cache::estimatedSize, is(4L)); assertThat(eviction.weightedSize().getAsLong(), is(8L)); verifyWriter(context, (verifier, ignored) -> { verify(writer).delete(5, value5, RemovalCause.SIZE); verifier.deletions(2); }); assertThat(context, hasEvictionCount(2L)); assertThat(context, hasEvictionWeight(12L)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.EMPTY, removalListener = Listener.CONSUMING, keys = ReferenceType.STRONG, values = ReferenceType.STRONG, maximumSize = Maximum.TEN, weigher = CacheWeigher.VALUE) public void evict_weighted_entryTooBig(Cache<Integer, Integer> cache, CacheContext context) { cache.put(1, 1); cache.put(9, 9); assertThat(cache.estimatedSize(), is(2L)); cache.policy().eviction().ifPresent(eviction -> { assertThat(eviction.weightedSize().getAsLong(), is(10L)); }); cache.put(20, 20); assertThat(cache.estimatedSize(), is(2L)); cache.policy().eviction().ifPresent(eviction -> { assertThat(eviction.weightedSize().getAsLong(), is(10L)); }); assertThat(context.consumedNotifications(), is(equalTo(ImmutableList.of( new RemovalNotification<>(20, 20, RemovalCause.SIZE))))); if (context.isCaffeine()) { assertThat(context, hasEvictionWeight(20L)); } } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.TEN, weigher = CacheWeigher.VALUE, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG, removalListener = Listener.CONSUMING) @SuppressWarnings("FutureReturnValueIgnored") public void evict_weighted_async(AsyncLoadingCache<Integer, Integer> cache, CacheContext context, Eviction<?, ?> eviction) { AtomicBoolean ready = new AtomicBoolean(); AtomicBoolean done = new AtomicBoolean(); CompletableFuture<Integer> valueFuture = CompletableFuture.supplyAsync(() -> { Awaits.await().untilTrue(ready); return 6; }); valueFuture.whenComplete((r, e) -> done.set(true)); cache.put(5, CompletableFuture.completedFuture(5)); cache.put(4, CompletableFuture.completedFuture(4)); cache.put(6, valueFuture); assertThat(eviction.weightedSize().getAsLong(), is(9L)); assertThat(cache.synchronous().estimatedSize(), is(3L)); ready.set(true); Awaits.await().untilTrue(done); Awaits.await().until(context::consumedNotifications, hasSize(1)); Awaits.await().until(() -> cache.synchronous().estimatedSize(), is(2L)); Awaits.await().until(() -> eviction.weightedSize().getAsLong(), is(10L)); assertThat(context, hasEvictionWeight(5L)); assertThat(context, hasRemovalNotifications(context, 1, RemovalCause.SIZE)); verifyWriter(context, (verifier, writer) -> verifier.deletions(1, RemovalCause.SIZE)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.ZERO, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) @SuppressWarnings("FutureReturnValueIgnored") public void evict_zero_async(AsyncLoadingCache<Integer, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { AtomicBoolean ready = new AtomicBoolean(); AtomicBoolean done = new AtomicBoolean(); CompletableFuture<List<Integer>> valueFuture = CompletableFuture.supplyAsync(() -> { Awaits.await().untilTrue(ready); return ImmutableList.of(1, 2, 3, 4, 5); }); valueFuture.whenComplete((r, e) -> done.set(true)); cache.put(context.absentKey(), valueFuture); assertThat(eviction.weightedSize().getAsLong(), is(0L)); assertThat(cache.synchronous().estimatedSize(), is(1L)); ready.set(true); Awaits.await().untilTrue(done); Awaits.await().until(() -> eviction.weightedSize().getAsLong(), is(0L)); Awaits.await().until(() -> cache.synchronous().estimatedSize(), is(0L)); assertThat(context, hasRemovalNotifications(context, 1, RemovalCause.SIZE)); verifyWriter(context, (verifier, writer) -> verifier.deletions(1, RemovalCause.SIZE)); } @CheckNoStats @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, maximumSize = Maximum.FULL, weigher = {CacheWeigher.DEFAULT, CacheWeigher.TEN}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void evict_writerFails(Cache<Integer, Integer> cache, CacheContext context) { try { cache.policy().eviction().ifPresent(policy -> policy.setMaximum(0)); } finally { context.disableRejectingCacheWriter(); assertThat(cache.asMap(), equalTo(context.original())); } } /* ---------------- Weighted -------------- */ @CheckNoWriter @CacheSpec(maximumSize = Maximum.FULL, weigher = CacheWeigher.NEGATIVE, population = Population.EMPTY) @Test(dataProvider = "caches", expectedExceptions = { IllegalArgumentException.class, IllegalStateException.class }) public void put_negativeWeight(Cache<Integer, Integer> cache, CacheContext context) { cache.put(context.absentKey(), context.absentValue()); } @CacheSpec(maximumSize = Maximum.FULL, weigher = CacheWeigher.ZERO, population = Population.EMPTY) @Test(dataProvider = "caches") public void put_zeroWeight(Cache<Integer, Integer> cache, CacheContext context) { cache.put(context.absentKey(), context.absentValue()); verifyWriter(context, (verifier, writer) -> { verifier.wrote(context.absentKey(), context.absentValue()); }); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void put(Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { cache.put("a", asList(1, 2, 3)); assertThat(cache.estimatedSize(), is(1L)); assertThat(eviction.weightedSize().getAsLong(), is(3L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void put_sameWeight(Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); cache.put("a", asList(-1, -2, -3)); assertThat(cache.estimatedSize(), is(2L)); assertThat(eviction.weightedSize().getAsLong(), is(4L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void put_changeWeight(Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { @SuppressWarnings({"unchecked", "rawtypes"}) CacheWriter<String, List<Integer>> writer = (CacheWriter) context.cacheWriter(); cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); cache.put("a", asList(-1, -2, -3, -4)); assertThat(cache.estimatedSize(), is(2L)); assertThat(eviction.weightedSize().getAsLong(), is(5L)); verifyWriter(context, (verifier, ignored) -> { verify(writer).write("a", asList(1, 2, 3)); verify(writer).write("b", asList(1)); verify(writer).write("a", asList(-1, -2, -3, -4)); }); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) @SuppressWarnings("FutureReturnValueIgnored") public void put_asyncWeight(AsyncLoadingCache<Integer, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { AtomicBoolean ready = new AtomicBoolean(); AtomicBoolean done = new AtomicBoolean(); CompletableFuture<List<Integer>> valueFuture = CompletableFuture.supplyAsync(() -> { Awaits.await().untilTrue(ready); return ImmutableList.of(1, 2, 3, 4, 5); }); valueFuture.whenComplete((r, e) -> done.set(true)); cache.put(context.absentKey(), valueFuture); assertThat(eviction.weightedSize().getAsLong(), is(0L)); assertThat(cache.synchronous().estimatedSize(), is(1L)); ready.set(true); Awaits.await().untilTrue(done); Awaits.await().until(() -> eviction.weightedSize().getAsLong(), is(5L)); Awaits.await().until(() -> cache.synchronous().estimatedSize(), is(1L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void replace_sameWeight(Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); cache.asMap().replace("a", asList(-1, -2, -3)); assertThat(cache.estimatedSize(), is(2L)); assertThat(eviction.weightedSize().getAsLong(), is(4L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void replace_changeWeight(Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); cache.asMap().replace("a", asList(-1, -2, -3, -4)); assertThat(cache.estimatedSize(), is(2L)); assertThat(eviction.weightedSize().getAsLong(), is(5L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void replaceConditionally_sameWeight( Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); assertThat(cache.asMap().replace("a", asList(1, 2, 3), asList(4, 5, 6)), is(true)); assertThat(cache.estimatedSize(), is(2L)); assertThat(eviction.weightedSize().getAsLong(), is(4L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void replaceConditionally_changeWeight( Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); cache.asMap().replace("a", asList(1, 2, 3), asList(-1, -2, -3, -4)); assertThat(cache.estimatedSize(), is(2L)); assertThat(eviction.weightedSize().getAsLong(), is(5L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void replaceConditionally_fails( Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); assertThat(cache.asMap().replace("a", asList(1), asList(4, 5)), is(false)); assertThat(cache.estimatedSize(), is(2L)); assertThat(eviction.weightedSize().getAsLong(), is(4L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void remove(Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); assertThat(cache.asMap().remove("a"), is(asList(1, 2, 3))); assertThat(cache.estimatedSize(), is(1L)); assertThat(eviction.weightedSize().getAsLong(), is(1L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void removeConditionally(Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); assertThat(cache.asMap().remove("a", asList(1, 2, 3)), is(true)); assertThat(cache.estimatedSize(), is(1L)); assertThat(eviction.weightedSize().getAsLong(), is(1L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void removeConditionally_fails( Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); assertThat(cache.asMap().remove("a", asList(-1, -2, -3)), is(false)); assertThat(cache.estimatedSize(), is(2L)); assertThat(eviction.weightedSize().getAsLong(), is(4L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, population = Population.EMPTY, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) public void invalidateAll(Cache<String, List<Integer>> cache, CacheContext context, Eviction<?, ?> eviction) { cache.putAll(ImmutableMap.of("a", asList(1, 2, 3), "b", asList(1))); cache.invalidateAll(); assertThat(cache.estimatedSize(), is(0L)); assertThat(eviction.weightedSize().getAsLong(), is(0L)); } /* ---------------- Policy: IsWeighted -------------- */ @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, population = Population.EMPTY) public void isWeighted(CacheContext context, Eviction<Integer, Integer> eviction) { assertThat(eviction.isWeighted(), is(context.isWeighted())); } /* ---------------- Policy: WeightOf -------------- */ @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.VALUE) public void weightOf(Cache<Integer, Integer> cache, CacheContext context, Eviction<Integer, Integer> eviction) { Integer key = 1; cache.put(key, 1); assertThat(eviction.weightOf(key).getAsInt(), is(1)); cache.put(key, 2); assertThat(eviction.weightOf(key).getAsInt(), is(2)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL) public void weightOf_absent(Cache<Integer, Integer> cache, CacheContext context, Eviction<Integer, Integer> eviction) { assertThat(eviction.weightOf(context.absentKey()), is(OptionalInt.empty())); } /* ---------------- Policy: WeightedSize -------------- */ @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = CacheWeigher.TEN) public void weightedSize(Cache<Integer, Integer> cache, CacheContext context, Eviction<Integer, Integer> eviction) { long weightedSize = 0; for (Integer key : cache.asMap().keySet()) { weightedSize += eviction.weightOf(key).getAsInt(); } assertThat(weightedSize, is(eviction.weightedSize().getAsLong())); assertThat(eviction.weightedSize().getAsLong(), is(10 * cache.estimatedSize())); } /* ---------------- Policy: MaximumSize -------------- */ @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, removalListener = { Listener.DEFAULT, Listener.CONSUMING }) public void maximumSize_decrease(Cache<Integer, Integer> cache, CacheContext context, Eviction<Integer, Integer> eviction) { long newSize = context.maximumWeightOrSize() / 2; eviction.setMaximum(newSize); assertThat(eviction.getMaximum(), is(newSize)); if (context.initialSize() > newSize) { if (context.isZeroWeighted()) { assertThat(cache.estimatedSize(), is(context.initialSize())); assertThat(cache, hasRemovalNotifications(context, 0, RemovalCause.SIZE)); } else { assertThat(cache.estimatedSize(), is(newSize)); assertThat(cache, hasRemovalNotifications(context, newSize, RemovalCause.SIZE)); } } } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, weigher = { CacheWeigher.DEFAULT, CacheWeigher.TEN }, removalListener = { Listener.DEFAULT, Listener.CONSUMING }) public void maximumSize_decrease_min(Cache<Integer, Integer> cache, CacheContext context, Eviction<Integer, Integer> eviction) { eviction.setMaximum(0); assertThat(eviction.getMaximum(), is(0L)); if (context.initialSize() > 0) { long expect = context.isZeroWeighted() ? context.initialSize() : 0; assertThat(cache.estimatedSize(), is(expect)); } assertThat(cache, hasRemovalNotifications(context, context.initialSize(), RemovalCause.SIZE)); } @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, removalListener = { Listener.DEFAULT, Listener.CONSUMING }) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) public void maximumSize_decrease_negative(Cache<Integer, Integer> cache, CacheContext context, Eviction<Integer, Integer> eviction) { try { eviction.setMaximum(-1); } finally { assertThat(eviction.getMaximum(), is(context.maximumWeightOrSize())); assertThat(cache, hasRemovalNotifications(context, 0, RemovalCause.SIZE)); } } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, removalListener = { Listener.DEFAULT, Listener.REJECTING }) public void maximumSize_increase(Cache<Integer, Integer> cache, CacheContext context, Eviction<Integer, Integer> eviction) { eviction.setMaximum(2 * context.maximumWeightOrSize()); assertThat(cache.estimatedSize(), is(context.initialSize())); assertThat(eviction.getMaximum(), is(2 * context.maximumWeightOrSize())); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL, removalListener = Listener.REJECTING) public void maximumSize_increase_max(Cache<Integer, Integer> cache, CacheContext context, Eviction<Integer, Integer> eviction) { eviction.setMaximum(Long.MAX_VALUE); assertThat(cache.estimatedSize(), is(context.initialSize())); assertThat(eviction.getMaximum(), is(Long.MAX_VALUE - Integer.MAX_VALUE)); // impl detail } /* ---------------- Policy: Coldest -------------- */ @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) public void coldest_unmodifiable(CacheContext context, Eviction<Integer, Integer> eviction) { eviction.coldest(Integer.MAX_VALUE).clear();; } @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) public void coldest_negative(CacheContext context, Eviction<Integer, Integer> eviction) { eviction.coldest(-1); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL) public void coldest_zero(CacheContext context, Eviction<Integer, Integer> eviction) { assertThat(eviction.coldest(0), is(emptyMap())); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) public void coldest_partial(CacheContext context, Eviction<Integer, Integer> eviction) { int count = (int) context.initialSize() / 2; assertThat(eviction.coldest(count).size(), is(count)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL, weigher = { CacheWeigher.DEFAULT, CacheWeigher.TEN }, removalListener = { Listener.DEFAULT, Listener.REJECTING }) public void coldest_order(CacheContext context, Eviction<Integer, Integer> eviction) { Set<Integer> keys = new LinkedHashSet<>(context.original().keySet()); Set<Integer> coldest = new LinkedHashSet<>(eviction.coldest(Integer.MAX_VALUE).keySet()); // Ignore the last key; hard to predict with W-TinyLFU keys.remove(context.lastKey()); coldest.remove(context.lastKey()); assertThat(coldest, contains(keys.toArray())); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) public void coldest_snapshot(Cache<Integer, Integer> cache, CacheContext context, Eviction<Integer, Integer> eviction) { Map<Integer, Integer> coldest = eviction.coldest(Integer.MAX_VALUE); cache.invalidateAll(); assertThat(coldest, is(equalTo(context.original()))); } /* ---------------- Policy: Hottest -------------- */ @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL) @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) public void hottest_unmodifiable(CacheContext context, Eviction<Integer, Integer> eviction) { eviction.hottest(Integer.MAX_VALUE).clear(); } @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL) @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) public void hottest_negative(CacheContext context, Eviction<Integer, Integer> eviction) { eviction.hottest(-1); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, maximumSize = Maximum.FULL) public void hottest_zero(CacheContext context, Eviction<Integer, Integer> eviction) { assertThat(eviction.hottest(0), is(emptyMap())); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) public void hottest_partial(CacheContext context, Eviction<Integer, Integer> eviction) { int count = (int) context.initialSize() / 2; assertThat(eviction.hottest(count).size(), is(count)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL, removalListener = { Listener.DEFAULT, Listener.REJECTING }) public void hottest_order(CacheContext context, Eviction<Integer, Integer> eviction) { Set<Integer> keys = new LinkedHashSet<>(context.original().keySet()); Set<Integer> hottest = eviction.hottest(Integer.MAX_VALUE).keySet(); Set<Integer> coldest = new LinkedHashSet<>(ImmutableList.copyOf(hottest).reverse()); // Ignore the last key; hard to predict with W-TinyLFU keys.remove(context.lastKey()); coldest.remove(context.lastKey()); assertThat(coldest, contains(keys.toArray())); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) public void hottest_snapshot(Cache<Integer, Integer> cache, CacheContext context, Eviction<Integer, Integer> eviction) { Map<Integer, Integer> hottest = eviction.hottest(Integer.MAX_VALUE); cache.invalidateAll(); assertThat(hottest, is(equalTo(context.original()))); } }