/* * Copyright 2015 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.CacheSpec.Expiration.AFTER_ACCESS; import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.AFTER_WRITE; import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.VARIABLE; 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.testing.IsFutureValue.futureOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; import org.testng.annotations.Listeners; import org.testng.annotations.Test; 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.CacheExpiry; 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.ExecutorFailure; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Expire; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Implementation; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Listener; import com.github.benmanes.caffeine.cache.testing.CacheSpec.Loader; 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.RejectingCacheWriter.DeleteException; import com.google.common.base.Functions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterators; import com.google.common.collect.Maps; /** * The test cases for caches that support an expiration policy. * * @author ben.manes@gmail.com (Ben Manes) */ @Listeners(CacheValidationListener.class) @Test(dataProviderClass = CacheProvider.class) public final class ExpirationTest { @Test(dataProvider = "caches") @CacheSpec(mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.IMMEDIATELY}, expireAfterWrite = {Expire.DISABLED, Expire.IMMEDIATELY}, expiryTime = Expire.IMMEDIATELY, population = Population.EMPTY) public void expire_zero(Cache<Integer, Integer> cache, CacheContext context) { cache.put(context.absentKey(), context.absentValue()); if (context.isZeroWeighted() && context.isGuava()) { // Guava translates to maximumSize=0, which won't evict assertThat(cache.estimatedSize(), is(1L)); assertThat(cache, hasRemovalNotifications(context, 0, RemovalCause.EXPIRED)); } else { runVariableExpiration(context); assertThat(cache.estimatedSize(), is(0L)); assertThat(cache, hasRemovalNotifications(context, 1, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> { verifier.deleted(context.absentKey(), context.absentValue(), RemovalCause.EXPIRED); }); } } /* ---------------- Cache -------------- */ @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, writer = Writer.EXCEPTIONAL, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, executorFailure = ExecutorFailure.EXPECTED, removalListener = Listener.REJECTING) public void getIfPresent_writerFails(Cache<Integer, Integer> cache, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); cache.getIfPresent(context.firstKey()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(cache.asMap(), equalTo(context.original())); } } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void get_writerFails(Cache<Integer, Integer> cache, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); cache.get(context.firstKey(), Function.identity()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(cache.asMap(), equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_insert(Cache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); cache.put(context.firstKey(), context.absentValue()); runVariableExpiration(context); long count = context.initialSize(); assertThat(cache.estimatedSize(), is(1L)); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_replace(Cache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); cache.put(context.firstKey(), context.absentValue()); cache.put(context.absentKey(), context.absentValue()); context.consumedNotifications().clear(); // Ignore replacement notification context.ticker().advance(45, TimeUnit.SECONDS); assertThat(cache.getIfPresent(context.firstKey()), is(context.absentValue())); assertThat(cache.getIfPresent(context.absentKey()), is(context.absentValue())); assertThat(cache.getIfPresent(context.middleKey()), is(nullValue())); assertThat(cache.estimatedSize(), is(2L)); if (context.isGuava()) { cache.cleanUp(); } long count = context.initialSize() - 1; assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void put_writerFails(Cache<Integer, Integer> cache, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); cache.put(context.firstKey(), context.absentValue()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(cache.asMap(), equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void putAll_insert(Cache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); cache.putAll(ImmutableMap.of(context.firstKey(), context.absentValue(), context.middleKey(), context.absentValue(), context.lastKey(), context.absentValue())); long count = context.initialSize(); assertThat(cache.estimatedSize(), is(3L)); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void putAll_replace(Cache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); cache.putAll(ImmutableMap.of( context.firstKey(), context.absentValue(), context.absentKey(), context.absentValue())); context.consumedNotifications().clear(); // Ignore replacement notification context.ticker().advance(45, TimeUnit.SECONDS); assertThat(cache.getIfPresent(context.firstKey()), is(context.absentValue())); assertThat(cache.getIfPresent(context.absentKey()), is(context.absentValue())); assertThat(cache.getIfPresent(context.middleKey()), is(nullValue())); assertThat(cache.estimatedSize(), is(2L)); if (context.isGuava()) { cache.cleanUp(); } long count = context.initialSize() - 1; assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void putAll_writerFails(Cache<Integer, Integer> cache, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); cache.putAll(ImmutableMap.of(context.firstKey(), context.absentValue())); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(cache.asMap(), equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void invalidate(Cache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); cache.invalidate(context.firstKey()); long count = context.initialSize(); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void invalidate_writerFails(Cache<Integer, Integer> cache, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); cache.invalidate(context.firstKey()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(cache.asMap(), equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void invalidateAll(Cache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); cache.invalidateAll(context.firstMiddleLastKeys()); long count = context.initialSize(); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void invalidateAll_writerFails(Cache<Integer, Integer> cache, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); cache.invalidateAll(context.firstMiddleLastKeys()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(cache.asMap(), equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void invalidateAll_full(Cache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); cache.invalidateAll(); long count = context.initialSize(); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void invalidateAll_full_writerFails(Cache<Integer, Integer> cache, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); cache.invalidateAll(); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(cache.asMap(), equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL }, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) public void estimatedSize(Cache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); assertThat(cache.estimatedSize(), is(context.initialSize())); } @Test(dataProvider = "caches") @CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL }, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) public void cleanUp(Cache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); cache.cleanUp(); long count = context.initialSize(); assertThat(cache.estimatedSize(), is(0L)); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void cleanUp_writerFails(Cache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(1, TimeUnit.HOURS); cache.cleanUp(); context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(cache.asMap(), equalTo(context.original())); } /* ---------------- LoadingCache -------------- */ @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void get_writerFails(LoadingCache<Integer, Integer> cache, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); cache.get(context.firstKey()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(cache.asMap(), equalTo(context.original())); } } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void getAll_writerFails(LoadingCache<Integer, Integer> cache, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); cache.getAll(context.firstMiddleLastKeys()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(cache.asMap(), equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, loader = Loader.IDENTITY, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void refresh(LoadingCache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); Integer key = context.firstKey(); cache.refresh(key); long count = (cache.estimatedSize() == 1) ? context.initialSize() : 1; assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> { verifier.deleted(key, context.original().get(key), RemovalCause.EXPIRED); }); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void refresh_writerFails(LoadingCache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(1, TimeUnit.HOURS); cache.refresh(context.firstKey()); context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(cache.asMap(), equalTo(context.original())); } /* ---------------- AsyncLoadingCache -------------- */ @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, loader = Loader.IDENTITY, removalListener = Listener.CONSUMING, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) @SuppressWarnings("FutureReturnValueIgnored") public void get(AsyncLoadingCache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(2, TimeUnit.MINUTES); cache.get(context.firstKey()); cache.get(context.middleKey(), k -> context.absentValue()); cache.get(context.lastKey(), (k, executor) -> CompletableFuture.completedFuture(context.absentValue())); long count = context.initialSize(); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.EMPTY, removalListener = Listener.CONSUMING, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) @SuppressWarnings("FutureReturnValueIgnored") public void get_async(AsyncLoadingCache<Integer, Integer> cache, CacheContext context) { CompletableFuture<Integer> future = new CompletableFuture<Integer>(); cache.get(context.absentKey(), (k, e) -> future); context.ticker().advance(2, TimeUnit.MINUTES); cache.synchronous().cleanUp(); assertThat(cache, hasRemovalNotifications(context, 0, RemovalCause.EXPIRED)); future.complete(context.absentValue()); context.ticker().advance(30, TimeUnit.SECONDS); assertThat(cache.getIfPresent(context.absentKey()), is(future)); context.ticker().advance(1, TimeUnit.MINUTES); assertThat(cache.getIfPresent(context.absentKey()), is(nullValue())); cache.synchronous().cleanUp(); assertThat(cache, hasRemovalNotifications(context, 1, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(1, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, removalListener = Listener.CONSUMING, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, loader = {Loader.IDENTITY, Loader.BULK_IDENTITY}) @SuppressWarnings("FutureReturnValueIgnored") public void getAll(AsyncLoadingCache<Integer, Integer> cache, CacheContext context) { Set<Integer> keys = context.firstMiddleLastKeys(); context.ticker().advance(1, TimeUnit.MINUTES); cache.getAll(context.firstMiddleLastKeys()); assertThat(cache.getAll(keys).join(), is(Maps.uniqueIndex(keys, Functions.identity()))); long count = context.initialSize(); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_insert(AsyncLoadingCache<Integer, Integer> cache, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); cache.put(context.firstKey(), CompletableFuture.completedFuture(context.absentValue())); runVariableExpiration(context); long count = context.initialSize(); assertThat(cache.synchronous().estimatedSize(), is(1L)); assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_replace(AsyncLoadingCache<Integer, Integer> cache, CacheContext context) { CompletableFuture<Integer> future = CompletableFuture.completedFuture(context.absentValue()); context.ticker().advance(30, TimeUnit.SECONDS); cache.put(context.firstKey(), future); cache.put(context.absentKey(), future); context.consumedNotifications().clear(); // Ignore replacement notification context.ticker().advance(45, TimeUnit.SECONDS); assertThat(cache.getIfPresent(context.firstKey()), is(futureOf(context.absentValue()))); assertThat(cache.getIfPresent(context.absentKey()), is(futureOf(context.absentValue()))); assertThat(cache.getIfPresent(context.middleKey()), is(nullValue())); assertThat(cache.synchronous().estimatedSize(), is(2L)); long count = context.initialSize() - 1; assertThat(cache, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } /* ---------------- Map -------------- */ @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void isEmpty(Map<Integer, Integer> map, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); assertThat(map.isEmpty(), is(false)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void size(Map<Integer, Integer> map, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); assertThat(map.size(), is((int) context.initialSize())); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void containsKey(Map<Integer, Integer> map, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); assertThat(map.containsKey(context.firstKey()), is(false)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void containsValue(Map<Integer, Integer> map, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); assertThat(map.containsValue(context.original().get(context.firstKey())), is(false)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void clear(Map<Integer, Integer> map, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); map.clear(); long count = context.initialSize(); assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void clear_writerFails(Map<Integer, Integer> map, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); map.clear(); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(map, equalTo(context.original())); } } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void putIfAbsent_writerFails(Map<Integer, Integer> map, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); map.putIfAbsent(context.firstKey(), context.absentValue()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(map, equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_insert(Map<Integer, Integer> map, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); assertThat(map.put(context.firstKey(), context.absentValue()), is(nullValue())); long count = context.initialSize(); assertThat(map.size(), is(1)); assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_replace(Map<Integer, Integer> map, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); assertThat(map.put(context.firstKey(), context.absentValue()), is(not(nullValue()))); assertThat(map.put(context.absentKey(), context.absentValue()), is(nullValue())); context.consumedNotifications().clear(); // Ignore replacement notification context.ticker().advance(45, TimeUnit.SECONDS); assertThat(map.get(context.firstKey()), is(context.absentValue())); assertThat(map.get(context.absentKey()), is(context.absentValue())); assertThat(map.get(context.middleKey()), is(nullValue())); assertThat(map.size(), is(2)); if (context.isGuava()) { context.cleanUp(); } long count = context.initialSize() - 1; assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void put_writerFails(Map<Integer, Integer> map, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); map.put(context.firstKey(), context.absentValue()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(map, equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void replace(Map<Integer, Integer> map, CacheContext context) { context.ticker().advance(60, TimeUnit.SECONDS); assertThat(map.replace(context.firstKey(), context.absentValue()), is(nullValue())); if (!map.isEmpty()) { context.cleanUp(); } assertThat(map.size(), is(0)); long count = context.initialSize(); assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void replace_updated(Map<Integer, Integer> map, CacheContext context) { context.ticker().advance(30, TimeUnit.SECONDS); assertThat(map.replace(context.firstKey(), context.absentValue()), is(not(nullValue()))); context.ticker().advance(30, TimeUnit.SECONDS); context.cleanUp(); assertThat(map.size(), is(1)); long count = context.initialSize() - 1; verifyWriter(context, (verifier, writer) -> verifier.deletions(count)); } // replace_writerFail: Not needed due to exiting without side-effects @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void replaceConditionally(Map<Integer, Integer> map, CacheContext context) { Integer key = context.firstKey(); context.ticker().advance(60, TimeUnit.SECONDS); assertThat(map.replace(key, context.original().get(key), context.absentValue()), is(false)); if (!map.isEmpty()) { context.cleanUp(); } assertThat(map.size(), is(0)); long count = context.initialSize(); assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void replaceConditionally_updated(Map<Integer, Integer> map, CacheContext context) { Integer key = context.firstKey(); context.ticker().advance(30, TimeUnit.SECONDS); assertThat(map.replace(key, context.original().get(key), context.absentValue()), is(true)); context.ticker().advance(30, TimeUnit.SECONDS); context.cleanUp(); assertThat(map.size(), is(1)); long count = context.initialSize() - 1; verifyWriter(context, (verifier, writer) -> verifier.deletions(count)); } // replaceConditionally_writerFail: Not needed due to exiting without side-effects @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void remove(Map<Integer, Integer> map, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); assertThat(map.remove(context.firstKey()), is(nullValue())); long count = context.initialSize(); assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, compute = Compute.SYNC, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void remove_writerFails(Map<Integer, Integer> map, CacheContext context) { try { context.ticker().advance(1, TimeUnit.HOURS); map.remove(context.firstKey()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(map, equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void removeConditionally(Map<Integer, Integer> map, CacheContext context) { Integer key = context.firstKey(); context.ticker().advance(1, TimeUnit.MINUTES); assertThat(map.remove(key, context.original().get(key)), is(false)); long count = context.initialSize(); assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void removeConditionally_writerFails(Map<Integer, Integer> map, CacheContext context) { try { Integer key = context.firstKey(); context.ticker().advance(1, TimeUnit.HOURS); map.remove(key, context.original().get(key)); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(map, equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void computeIfAbsent(Map<Integer, Integer> map, CacheContext context) { Integer key = context.firstKey(); context.ticker().advance(1, TimeUnit.MINUTES); assertThat(map.computeIfAbsent(key, k -> context.absentValue()), is(context.absentValue())); assertThat(map.size(), is(1)); long count = context.initialSize(); assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void computeIfAbsent_writerFails(Map<Integer, Integer> map, CacheContext context) { try { Integer key = context.firstKey(); context.ticker().advance(1, TimeUnit.HOURS); map.computeIfAbsent(key, k -> context.absentValue()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(map, equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void computeIfPresent(Map<Integer, Integer> map, CacheContext context) { Integer key = context.firstKey(); Integer value = context.absentValue(); context.ticker().advance(1, TimeUnit.MINUTES); assertThat(map.computeIfPresent(key, (k, v) -> value), is(nullValue())); assertThat(map.size(), is(0)); if (context.isGuava()) { context.cleanUp(); } long count = context.initialSize(); assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, executorFailure = ExecutorFailure.EXPECTED, removalListener = Listener.REJECTING) public void computeIfPresent_writerFails(Map<Integer, Integer> map, CacheContext context) { try { Integer key = context.firstKey(); context.ticker().advance(1, TimeUnit.HOURS); map.computeIfPresent(key, (k, v) -> context.absentValue()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(map, equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void compute(Map<Integer, Integer> map, CacheContext context) { Integer key = context.firstKey(); Integer value = context.absentValue(); context.ticker().advance(1, TimeUnit.MINUTES); assertThat(map.compute(key, (k, v) -> { assertThat(v, is(nullValue())); return value; }), is(value)); long count = context.initialSize() - map.size() + 1; assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void compute_writerFails(Map<Integer, Integer> map, CacheContext context) { try { Integer key = context.firstKey(); context.ticker().advance(1, TimeUnit.HOURS); map.compute(key, (k, v) -> context.absentValue()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(map, equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void merge(Map<Integer, Integer> map, CacheContext context) { Integer key = context.firstKey(); Integer value = context.absentValue(); context.ticker().advance(1, TimeUnit.MINUTES); assertThat(map.merge(key, value, (oldValue, v) -> { throw new AssertionError("Should never be called"); }), is(value)); long count = context.initialSize() - map.size() + 1; assertThat(map, hasRemovalNotifications(context, count, RemovalCause.EXPIRED)); verifyWriter(context, (verifier, writer) -> verifier.deletions(count, RemovalCause.EXPIRED)); } @Test(dataProvider = "caches", expectedExceptions = DeleteException.class) @CacheSpec(implementation = Implementation.Caffeine, keys = ReferenceType.STRONG, population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, compute = Compute.SYNC, writer = Writer.EXCEPTIONAL, removalListener = Listener.REJECTING) public void merge_writerFails(Map<Integer, Integer> map, CacheContext context) { try { Integer key = context.firstKey(); Integer value = context.absentValue(); context.ticker().advance(1, TimeUnit.HOURS); map.merge(key, value, (oldValue, v) -> context.absentValue()); } finally { context.disableRejectingCacheWriter(); context.ticker().advance(-1, TimeUnit.HOURS); assertThat(map, equalTo(context.original())); } } @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void iterators(Map<Integer, Integer> map, CacheContext context) { context.ticker().advance(1, TimeUnit.MINUTES); assertThat(Iterators.size(map.keySet().iterator()), is(0)); assertThat(Iterators.size(map.values().iterator()), is(0)); assertThat(Iterators.size(map.entrySet().iterator()), is(0)); } /* ---------------- Weights -------------- */ @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void putIfAbsent_weighted(Cache<Integer, List<Integer>> cache, CacheContext context) { cache.put(1, ImmutableList.of(1)); context.ticker().advance(1, TimeUnit.MINUTES); cache.asMap().putIfAbsent(1, ImmutableList.of(1, 2, 3)); assertThat(cache.policy().eviction().get().weightedSize().getAsLong(), is(3L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_weighted(Cache<Integer, List<Integer>> cache, CacheContext context) { cache.put(1, ImmutableList.of(1)); context.ticker().advance(1, TimeUnit.MINUTES); cache.put(1, ImmutableList.of(1, 2, 3)); assertThat(cache.policy().eviction().get().weightedSize().getAsLong(), is(3L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void computeIfAbsent_weighted(Cache<Integer, List<Integer>> cache, CacheContext context) { cache.put(1, ImmutableList.of(1)); context.ticker().advance(1, TimeUnit.MINUTES); cache.asMap().computeIfAbsent(1, k -> ImmutableList.of(1, 2, 3)); assertThat(cache.policy().eviction().get().weightedSize().getAsLong(), is(3L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void compute_weighted(Cache<Integer, List<Integer>> cache, CacheContext context) { cache.put(1, ImmutableList.of(1)); context.ticker().advance(1, TimeUnit.MINUTES); cache.asMap().compute(1, (k, v) -> ImmutableList.of(1, 2, 3)); assertThat(cache.policy().eviction().get().weightedSize().getAsLong(), is(3L)); } @Test(dataProvider = "caches") @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, mustExpiresWithAnyOf = { AFTER_ACCESS, AFTER_WRITE, VARIABLE }, expiry = { CacheExpiry.DISABLED, CacheExpiry.WRITE, CacheExpiry.ACCESS }, expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void merge_weighted(Cache<Integer, List<Integer>> cache, CacheContext context) { cache.put(1, ImmutableList.of(1)); context.ticker().advance(1, TimeUnit.MINUTES); cache.asMap().merge(1, ImmutableList.of(1, 2, 3), (oldValue, v) -> { throw new AssertionError("Should never be called"); }); assertThat(cache.policy().eviction().get().weightedSize().getAsLong(), is(3L)); } /** * Ensures that variable expiration is run, as it may not have due to expiring in coarse batches. */ private static void runVariableExpiration(CacheContext context) { if (context.expiresVariably()) { // Variable expires in coarse buckets at a time context.ticker().advance(2, TimeUnit.SECONDS); context.cleanUp(); } } }