diff --git a/caffeine/build.gradle b/caffeine/build.gradle index 58a9aa8..2cd15f2 100644 --- a/caffeine/build.gradle +++ b/caffeine/build.gradle @@ -14,25 +14,10 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2' testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.2' - testImplementation 'org.mockito:mockito-core:5.0.0' - testImplementation 'org.mockito:mockito-junit-jupiter:5.0.0' - testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation 'org.testng:testng:7.7.1' - testImplementation 'com.google.truth:truth:1.1.3' + testImplementation 'org.assertj:assertj-core:3.23.1' + testImplementation 'io.reactivex.rxjava2:rxjava:2.2.21' testImplementation 'org.awaitility:awaitility:4.2.0' - testImplementation 'uk.org.lidalia:lidalia-slf4j-ext:1.0.0' - testImplementation 'com.google.truth.extensions:truth-java8-extension:1.1.3' - testImplementation 'it.unimi.dsi:fastutil-core:8.5.11' - testImplementation 'com.google.guava:guava:31.1-jre' testImplementation 'com.google.guava:guava-testlib:31.1-jre' - testImplementation 'org.jctools:jctools-core:4.0.1' - testImplementation 'site.ycsb:core:0.17.0' - testImplementation 'com.github.valfirst:slf4j-test:2.8.1' - testImplementation 'org.apache.commons:commons-lang3:3.12.0' - testImplementation 'org.jooq:joor-java-8:0.9.14' - testImplementation 'org.eclipse.collections:eclipse-collections:11.1.0' - testImplementation 'org.jetbrains.kotlinx:lincheck:2.16' - testImplementation 'info.picocli:picocli:4.7.0' } test { diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java deleted file mode 100644 index 2be2fe6..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java +++ /dev/null @@ -1,2825 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.testing.*; -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.Population; -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.base.Splitter; -import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; -import com.google.common.testing.SerializableTester; -import org.eclipse.collections.impl.factory.Sets; -import org.mockito.Mockito; -import org.testng.Assert; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.IntStream; - -import static com.github.benmanes.caffeine.cache.RemovalCause.EXPLICIT; -import static com.github.benmanes.caffeine.cache.RemovalCause.REPLACED; -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContext.intern; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.CollectionSubject.assertThat; -import static com.github.benmanes.caffeine.testing.IntSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.truth.Truth.assertThat; -import static java.util.function.Function.identity; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; -import static uk.org.lidalia.slf4jext.Level.ERROR; -import static uk.org.lidalia.slf4jext.Level.WARN; - -@CheckNoEvictions -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@Test(dataProviderClass = CacheProvider.class) -public final class AsMapTest { - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void isEmpty(Map map, CacheContext context) { - if (context.original().isEmpty()) { - assertThat(map).isExhaustivelyEmpty(); - } else { - assertThat(map.isEmpty()).isFalse(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void size(Map map, CacheContext context) { - assertThat(map.size()).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void clear(Map map, CacheContext context) { - map.clear(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @SuppressWarnings("ReturnValueIgnored") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void containsKey_null(Map map, CacheContext context) { - map.containsKey(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void containsKey_present(Map map, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> assertThat(map.containsKey(key)).isTrue()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void containsKey_absent(Map map, CacheContext context) { - assertThat(map.containsKey(context.absentKey())).isFalse(); - } - - @CheckNoStats - @SuppressWarnings("ReturnValueIgnored") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void containsValue_null(Map map, CacheContext context) { - map.containsValue(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void containsValue_present(Map map, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> assertThat(map.containsValue(context.original().get(key))).isTrue()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void containsValue_absent(Map map, CacheContext context) { - assertThat(map.containsValue(context.absentValue())).isFalse(); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void get_null(Map map, CacheContext context) { - map.get(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void get_absent(Map map, CacheContext context) { - assertThat(map.get(context.absentKey())).isNull(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void get_present(Map map, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> assertThat(map.get(key)).isEqualTo(context.original().get(key))); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getOrDefault_nullKey(Map map, CacheContext context) { - map.getOrDefault(null, Int.valueOf(1)); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getOrDefault_absent(Map map, CacheContext context) { - Int key = context.absentKey(); - assertThat(map.getOrDefault(key, null)).isNull(); - assertThat(map.getOrDefault(key, key.negate())).isEqualTo(key.negate()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getOrDefault_present(Map map, CacheContext context) { - for (Int key : context.firstMiddleLastKeys()) { - assertThat(map.getOrDefault(key, context.absentKey())).isEqualTo(context.original().get(key)); - } - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void forEach_null(Map map, CacheContext context) { - map.forEach(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void forEach_scan(Map map, CacheContext context) { - var remaining = new HashMap<>(context.original()); - map.forEach((key, value) -> { - assertThat(key).isNotNull(); - assertThat(value).isNotNull(); - assertThat(remaining.remove(key, value)).isTrue(); - }); - assertThat(remaining).isExhaustivelyEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void forEach_modify(Map map, CacheContext context) { - map.forEach((key, value) -> { - Int newKey = intern(context.lastKey().add(key)); - map.put(newKey, key); - }); - } - - /* --------------- put --------------- */ - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_nullKey(Map map, CacheContext context) { - map.put(null, Int.valueOf(1)); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_nullValue(Map map, CacheContext context) { - map.put(Int.valueOf(1), null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_nullKeyAndValue(Map map, CacheContext context) { - map.put(null, null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_insert(Map map, CacheContext context) { - assertThat(map.put(context.absentKey(), context.absentValue())).isNull(); - assertThat(map).containsEntry(context.absentKey(), context.absentValue()); - assertThat(map).hasSize(context.initialSize() + 1); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace_sameValue(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = intern(new Int(context.original().get(key))); - assertThat(map.put(key, value)).isSameInstanceAs(context.original().get(key)); - assertThat(map).containsEntry(key, value); - replaced.put(key, value); - }); - assertThat(map).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace_sameInstance(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map.put(key, value)).isSameInstanceAs(context.original().get(key)); - assertThat(map).containsEntry(key, value); - replaced.put(key, value); - }); - assertThat(map).hasSize(context.initialSize()); - if (context.isGuava()) { - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } else { - assertThat(context).removalNotifications().isEmpty(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace_differentValue(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map.put(key, context.absentValue())).isEqualTo(value); - assertThat(map).containsEntry(key, context.absentValue()); - replaced.put(key, value); - }); - assertThat(map).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY) - public void put_recursiveUpdate(Map map, CacheContext context) { - map.put(context.absentKey(), context.absentValue()); - var result = map.compute(context.absentKey(), (key, value) -> { - var oldValue = map.put(key, intern(value.add(1))); - assertThat(oldValue).isEqualTo(value); - return key; - }); - assertThat(result).isEqualTo(context.absentKey()); - assertThat(map).containsExactly(context.absentKey(), context.absentKey()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_async_null(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - Int newValue = context.absentValue(); - var future = new CompletableFuture(); - cache.put(key, future); - var start = new AtomicBoolean(); - var done = new AtomicBoolean(); - ConcurrentTestHarness.execute(() -> { - start.set(true); - Int result = cache.synchronous().asMap().put(key, newValue); - assertThat(result).isNull(); - done.set(true); - }); - await().untilTrue(start); - future.complete(null); - await().untilTrue(done); - assertThat(cache).containsEntry(key, newValue); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putAll_null(Map map, CacheContext context) { - map.putAll(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putAll_empty(Map map, CacheContext context) { - map.putAll(Map.of()); - assertThat(map).hasSize(context.initialSize()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putAll_insert(Map map, CacheContext context) { - int startKey = context.original().size() + 1; - var entries = IntStream - .range(startKey, 100 + startKey) - .mapToObj(Int::valueOf) - .collect(toImmutableMap(identity(), Int::negate)); - map.putAll(entries); - assertThat(map).hasSize(100 + context.initialSize()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void putAll_replace(Map map, CacheContext context) { - var entries = new LinkedHashMap<>(context.original()); - entries.replaceAll((key, value) -> key); - map.putAll(entries); - assertThat(map).isEqualTo(entries); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}) - public void putAll_mixed(Map map, CacheContext context) { - var entries = new HashMap(); - var replaced = new HashMap(); - context.original().forEach((key, value) -> { - if ((key.intValue() % 2) == 0) { - value = intern(value.add(1)); - replaced.put(key, value); - } - entries.put(key, value); - }); - map.putAll(entries); - assertThat(map).isEqualTo(entries); - var expect = context.isGuava() ? entries : replaced; - for (var entry : expect.entrySet()) { - entry.setValue(context.original().get(entry.getKey())); - } - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(expect).exclusively(); - } - - /* --------------- putIfAbsent --------------- */ - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putIfAbsent_nullKey(Map map, CacheContext context) { - map.putIfAbsent(null, Int.valueOf(2)); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putIfAbsent_nullValue(Map map, CacheContext context) { - map.putIfAbsent(Int.valueOf(1), null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putIfAbsent_nullKeyAndValue(Map map, CacheContext context) { - map.putIfAbsent(null, null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putIfAbsent_present(Map map, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map.putIfAbsent(key, key)).isEqualTo(value); - assertThat(map).containsEntry(key, value); - }); - assertThat(map).hasSize(context.initialSize()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putIfAbsent_insert(Map map, CacheContext context) { - assertThat(map.putIfAbsent(context.absentKey(), context.absentValue())).isNull(); - assertThat(map).containsEntry(context.absentKey(), context.absentValue()); - assertThat(map).hasSize(context.initialSize() + 1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putIfAbsent_async_null(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - Int newValue = context.absentValue(); - var future = new CompletableFuture(); - - cache.put(key, future); - var start = new AtomicBoolean(); - var done = new AtomicBoolean(); - ConcurrentTestHarness.execute(() -> { - start.set(true); - Int result = cache.synchronous().asMap().putIfAbsent(key, newValue); - assertThat(result).isNull(); - done.set(true); - }); - await().untilTrue(start); - future.complete(null); - await().untilTrue(done); - assertThat(cache).containsEntry(key, newValue); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void remove_nullKey(Map map, CacheContext context) { - map.remove(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void remove_absent(Map map, CacheContext context) { - assertThat(map.remove(context.absentKey())).isNull(); - assertThat(map).hasSize(context.initialSize()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void remove_present(Map map, CacheContext context) { - var removed = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = map.remove(key); - removed.put(key, value); - }); - int expectedSize = context.original().size() - context.firstMiddleLastKeys().size(); - assertThat(map).hasSize(expectedSize); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(removed).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void remove_async_null(AsyncCache cache, CacheContext context) { - var future = new CompletableFuture(); - Int key = context.absentKey(); - cache.put(key, future); - var start = new AtomicBoolean(); - var done = new AtomicBoolean(); - ConcurrentTestHarness.execute(() -> { - start.set(true); - Int result = cache.synchronous().asMap().remove(key); - assertThat(result).isNull(); - done.set(true); - }); - await().untilTrue(start); - future.complete(null); - await().untilTrue(done); - assertThat(cache).doesNotContainKey(key); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void removeConditionally_nullKey(Map map, CacheContext context) { - map.remove(null, context.absentValue()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void removeConditionally_nullValue(Map map, CacheContext context) { - assertThat(map.remove(context.absentKey(), null)).isFalse(); // see ConcurrentHashMap - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void removeConditionally_nullKeyAndValue(Map map, CacheContext context) { - map.remove(null, null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void removeConditionally_absent(Map map, CacheContext context) { - assertThat(map.remove(context.absentKey(), context.absentValue())).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void removeConditionally_presentKey(Map map, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> assertThat(map.remove(key, key)).isFalse()); - assertThat(map).hasSize(context.initialSize()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void removeConditionally_presentKeyAndValue(Map map, CacheContext context) { - var removed = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map.remove(key, value)).isTrue(); - removed.put(key, value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(map).hasSize(context.initialSize() - count); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(removed).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void removeConditionally_async_null( - AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - Int newValue = context.absentValue(); - var future = new CompletableFuture(); - cache.put(key, future); - var start = new AtomicBoolean(); - var done = new AtomicBoolean(); - ConcurrentTestHarness.execute(() -> { - start.set(true); - boolean result = cache.synchronous().asMap().remove(key, newValue); - assertThat(result).isFalse(); - done.set(true); - }); - await().untilTrue(start); - future.complete(null); - await().untilTrue(done); - assertThat(cache).doesNotContainKey(key); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replace_null(Map map, CacheContext context) { - map.replace(null, Int.valueOf(1)); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replace_nullValue(Map map, CacheContext context) { - map.replace(Int.valueOf(1), null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replace_nullKeyAndValue(Map map, CacheContext context) { - map.replace(null, null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replace_absent(Map map, CacheContext context) { - assertThat(map.replace(context.absentKey(), context.absentValue())).isNull(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replace_sameValue(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = intern(new Int(context.original().get(key))); - assertThat(map.replace(key, value)).isSameInstanceAs(context.original().get(key)); - assertThat(map).containsEntry(key, value); - replaced.put(key, value); - }); - assertThat(map).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replace_sameInstance(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map.replace(key, value)).isSameInstanceAs(value); - assertThat(map).containsEntry(key, value); - replaced.put(key, value); - }); - assertThat(map).hasSize(context.initialSize()); - if (context.isGuava()) { - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } else { - assertThat(context).removalNotifications().isEmpty(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replace_differentValue(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int oldValue = context.original().get(key); - assertThat(map.replace(key, context.absentValue())).isEqualTo(oldValue); - assertThat(map).containsEntry(key, context.absentValue()); - replaced.put(key, oldValue); - }); - assertThat(map).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replace_async_null(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - Int newValue = context.absentValue(); - var future = new CompletableFuture(); - cache.put(key, future); - var start = new AtomicBoolean(); - var done = new AtomicBoolean(); - ConcurrentTestHarness.execute(() -> { - start.set(true); - Int result = cache.synchronous().asMap().replace(key, newValue); - assertThat(result).isNull(); - done.set(true); - }); - await().untilTrue(start); - future.complete(null); - await().untilTrue(done); - assertThat(cache).doesNotContainKey(key); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullKey(Map map, CacheContext context) { - map.replace(null, Int.valueOf(1), Int.valueOf(1)); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullOldValue(Map map, CacheContext context) { - map.replace(Int.valueOf(1), null, Int.valueOf(1)); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullNewValue(Map map, CacheContext context) { - map.replace(Int.valueOf(1), Int.valueOf(1), null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullKeyAndOldValue(Map map, CacheContext context) { - map.replace(null, null, Int.valueOf(1)); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullKeyAndNewValue(Map map, CacheContext context) { - map.replace(null, Int.valueOf(1), null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullOldAndNewValue(Map map, CacheContext context) { - map.replace(Int.valueOf(1), null, null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullKeyAndValues(Map map, CacheContext context) { - map.replace(null, null, null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_absent(Map map, CacheContext context) { - Int key = context.absentKey(); - assertThat(map.replace(key, key, key)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replaceConditionally_wrongOldValue(Map map, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map.replace(key, key, context.absentKey())).isFalse(); - assertThat(map).containsEntry(key, value); - }); - assertThat(map).hasSize(context.initialSize()); - assertThat(context).removalNotifications().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replaceConditionally_sameValue(Cache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var oldValue = cache.asMap().get(key); - var newValue = intern(new Int(cache.asMap().get(key))); - assertThat(cache.asMap().replace(key, oldValue, newValue)).isTrue(); - assertThat(cache).containsEntry(key, newValue); - replaced.put(key, oldValue); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replaceConditionally_sameInstance(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map.replace(key, value, value)).isTrue(); - assertThat(map).containsEntry(key, value); - replaced.put(key, value); - }); - assertThat(map).hasSize(context.initialSize()); - if (context.isGuava()) { - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } else { - assertThat(context).removalNotifications().isEmpty(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replaceConditionally_differentValue(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map.replace(key, value, context.absentValue())).isTrue(); - assertThat(map).containsEntry(key, context.absentValue()); - replaced.put(key, value); - }); - assertThat(map).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_async_null( - AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - Int newValue = context.absentValue(); - var future = new CompletableFuture(); - cache.put(key, future); - var start = new AtomicBoolean(); - var done = new AtomicBoolean(); - ConcurrentTestHarness.execute(() -> { - start.set(true); - boolean replaced = cache.synchronous().asMap().replace(key, key, newValue); - assertThat(replaced).isFalse(); - done.set(true); - }); - await().untilTrue(start); - future.complete(null); - await().untilTrue(done); - assertThat(cache).doesNotContainKey(key); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void replaceAll_null(Map map, CacheContext context) { - map.replaceAll(null); - } - - @CheckNoStats - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void replaceAll_nullValue(Map map, CacheContext context) { - map.replaceAll((key, value) -> null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void replaceAll_sameValue(Map map, CacheContext context) { - map.replaceAll((key, value) -> value); - assertThat(map).containsExactlyEntriesIn(context.original()); - if (context.isGuava()) { - assertThat(context).removalNotifications().withCause(REPLACED).contains(context.original()).exclusively(); - } else { - assertThat(context).removalNotifications().isEmpty(); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void replaceAll_differentValue(Map map, CacheContext context) { - map.replaceAll((key, value) -> key); - map.forEach((key, value) -> assertThat(value).isEqualTo(key)); - assertThat(context).removalNotifications().withCause(REPLACED).contains(context.original()).exclusively(); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void computeIfAbsent_nullKey(Map map, CacheContext context) { - map.computeIfAbsent(null, Int::negate); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void computeIfAbsent_nullMappingFunction(Map map, CacheContext context) { - map.computeIfAbsent(context.absentKey(), null); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfAbsent_nullValue(Map map, CacheContext context) { - assertThat(map.computeIfAbsent(context.absentKey(), key -> null)).isNull(); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - assertThat(map).hasSize(context.initialSize()); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void computeIfAbsent_recursive(Map map, CacheContext context) { - var mappingFunction = new Function() { - @Override - public Int apply(Int key) { - return map.computeIfAbsent(key, this); - } - }; - try { - map.computeIfAbsent(context.absentKey(), mappingFunction); - Assert.fail(); - } catch (StackOverflowError | IllegalStateException ignored) { - } - } - - @CacheSpec - @Test(dataProvider = "caches") - public void computeIfAbsent_pingpong(Map map, CacheContext context) { - var mappingFunction = new Function() { - @Override - public Int apply(Int key) { - return map.computeIfAbsent(key.negate(), this); - } - }; - try { - map.computeIfAbsent(context.absentKey(), mappingFunction); - Assert.fail(); - } catch (StackOverflowError | IllegalStateException ignored) { - } - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfAbsent_error(Map map, CacheContext context) { - try { - map.computeIfAbsent(context.absentKey(), key -> { - throw new ExpectedError(); - }); - } catch (ExpectedError expected) { - } - assertThat(map).containsExactlyEntriesIn(context.original()); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - assertThat(map.computeIfAbsent(context.absentKey(), key -> key)).isEqualTo(context.absentKey()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfAbsent_present(Map map, CacheContext context) { - for (Int key : context.firstMiddleLastKeys()) { - Int value = context.original().get(key); - assertThat(map.computeIfAbsent(key, k -> { - throw new AssertionError(); - })).isEqualTo(value); - } - assertThat(map).hasSize(context.initialSize()); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(count).misses(0).success(0).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfAbsent_absent(Map map, CacheContext context) { - assertThat(map.computeIfAbsent(context.absentKey(), key -> context.absentValue())) - .isEqualTo(context.absentValue()); - assertThat(context).stats().hits(0).misses(1).success(1).failures(0); - assertThat(map).containsEntry(context.absentKey(), context.absentValue()); - assertThat(map).hasSize(context.initialSize() + 1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfAbsent_async_null(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - Int newValue = context.absentValue(); - var future = new CompletableFuture(); - cache.put(key, future); - var start = new AtomicBoolean(); - var done = new AtomicBoolean(); - ConcurrentTestHarness.execute(() -> { - start.set(true); - Int result = cache.synchronous().asMap().computeIfAbsent(key, k -> newValue); - assertThat(result).isEqualTo(newValue); - done.set(true); - }); - - await().untilTrue(start); - future.complete(null); - - await().untilTrue(done); - assertThat(cache).containsEntry(key, newValue); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void computeIfPresent_nullKey(Map map, CacheContext context) { - map.computeIfPresent(null, (key, value) -> key.negate()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void computeIfPresent_nullMappingFunction(Map map, CacheContext context) { - map.computeIfPresent(Int.valueOf(1), null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void computeIfPresent_nullValue(Map map, CacheContext context) { - var removed = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - map.computeIfPresent(key, (k, v) -> null); - removed.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(map).hasSize(context.initialSize() - count); - assertThat(context).stats().hits(0).misses(0).success(0).failures(count); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(removed).exclusively(); - } - - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = StackOverflowError.class) - public void computeIfPresent_recursive(Map map, CacheContext context) { - var mappingFunction = new BiFunction() { - boolean recursed; - - @Override - public Int apply(Int key, Int value) { - if (recursed) { - throw new StackOverflowError(); - } - recursed = true; - return map.computeIfPresent(key, this); - } - }; - map.computeIfPresent(context.firstKey(), mappingFunction); - } - - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = StackOverflowError.class) - public void computeIfPresent_pingpong(Map map, CacheContext context) { - var mappingFunction = new BiFunction() { - int recursed; - - @Override - public Int apply(Int key, Int value) { - if (++recursed == 2) { - throw new StackOverflowError(); - } - return map.computeIfPresent(context.lastKey(), this); - } - }; - map.computeIfPresent(context.firstKey(), mappingFunction); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void computeIfPresent_error(Map map, CacheContext context) { - try { - map.computeIfPresent(context.firstKey(), (key, value) -> { - throw new ExpectedError(); - }); - } catch (ExpectedError expected) { - } - assertThat(map).isEqualTo(context.original()); - assertThat(context).stats().hits(0).misses(0).success(0).failures(1); - assertThat(map.computeIfPresent(context.firstKey(), (k, v) -> k.negate())) - .isEqualTo(context.firstKey().negate()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfPresent_absent(Map map, CacheContext context) { - assertThat(map.computeIfPresent(context.absentKey(), (key, value) -> value)).isNull(); - assertThat(map).doesNotContainKey(context.absentKey()); - assertThat(map).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void computeIfPresent_present_sameValue(Map map, CacheContext context) { - var replaced = new HashMap(); - for (Int key : context.firstMiddleLastKeys()) { - var value = intern(new Int(context.original().get(key))); - assertThat(map.computeIfPresent(key, (k, v) -> value)).isSameInstanceAs(value); - replaced.put(key, value); - } - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - - assertThat(map).hasSize(context.initialSize()); - assertThat(map).containsAtLeastEntriesIn(replaced); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void computeIfPresent_present_sameInstance(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - assertThat(map.computeIfPresent(key, (k, v) -> v)) - .isSameInstanceAs(context.original().get(key)); - replaced.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> assertThat(map).containsEntry(key, context.original().get(key))); - assertThat(map).hasSize(context.initialSize()); - if (context.isGuava()) { - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } else { - assertThat(context).removalNotifications().isEmpty(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void computeIfPresent_present_differentValue(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - assertThat(map.computeIfPresent(key, (k, v) -> k)).isEqualTo(key); - replaced.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> assertThat(map).containsEntry(key, key)); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfPresent_async_null(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - Int newValue = context.absentValue(); - var future = new CompletableFuture(); - cache.put(key, future); - var start = new AtomicBoolean(); - var done = new AtomicBoolean(); - ConcurrentTestHarness.execute(() -> { - start.set(true); - Int result = cache.synchronous().asMap().computeIfPresent(key, (k, oldValue) -> newValue); - assertThat(result).isNull(); - done.set(true); - }); - await().untilTrue(start); - future.complete(null); - await().untilTrue(done); - assertThat(cache).doesNotContainKey(key); - } - - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void compute_nullKey(Map map, CacheContext context) { - map.compute(null, (key, value) -> key.negate()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void compute_nullMappingFunction(Map map, CacheContext context) { - map.compute(Int.valueOf(1), null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_remove(Map map, CacheContext context) { - var removed = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - assertThat(map.compute(key, (k, v) -> null)).isNull(); - removed.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(map).hasSize(context.initialSize() - count); - assertThat(context).stats().hits(0).misses(0).success(0).failures(count); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(removed).exclusively(); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void compute_recursive(Map map, CacheContext context) { - var mappingFunction = new BiFunction() { - @Override - public Int apply(Int key, Int value) { - return map.compute(key, this); - } - }; - try { - map.compute(context.absentKey(), mappingFunction); - Assert.fail(); - } catch (StackOverflowError | IllegalStateException e) { /* ignored */ } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY) - public void compute_pingpong(Map map, CacheContext context) { - var key1 = Int.valueOf(1); - var key2 = Int.valueOf(2); - var mappingFunction = new BiFunction() { - @Override - public Int apply(Int key, Int value) { - return map.compute(key.equals(key1) ? key2 : key1, this); - } - }; - try { - map.compute(key1, mappingFunction); - Assert.fail(); - } catch (StackOverflowError | IllegalStateException ignored) { - } - } - - @CacheSpec - @Test(dataProvider = "caches") - public void compute_error(Map map, CacheContext context) { - try { - map.compute(context.absentKey(), (key, value) -> { - throw new IllegalStateException(); - }); - Assert.fail(); - } catch (IllegalStateException ignored) { - } - assertThat(map).isEqualTo(context.original()); - assertThat(context).stats().hits(0).misses(0).success(0).failures(1); - assertThat(map.compute(context.absentKey(), (k, v) -> intern(k.negate()))).isEqualTo(context.absentKey().negate()); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void compute_absent_nullValue(Map map, CacheContext context) { - assertThat(map.compute(context.absentKey(), (key, value) -> null)).isNull(); - assertThat(context).stats().hits(0).misses(0).success(0).failures(1); - assertThat(map.get(context.absentKey())).isNull(); - assertThat(map).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void compute_absent(Map map, CacheContext context) { - assertThat(map.compute(context.absentKey(), (key, value) -> context.absentValue())).isEqualTo(context.absentValue()); - assertThat(context).stats().hits(0).misses(0).success(1).failures(0); - assertThat(map).containsEntry(context.absentKey(), context.absentValue()); - assertThat(map).hasSize(1 + context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_sameValue(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = intern(new Int(context.original().get(key))); - assertThat(map.compute(key, (k, v) -> value)).isSameInstanceAs(value); - replaced.put(key, value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - assertThat(map).hasSize(context.initialSize()); - assertThat(map).containsAtLeastEntriesIn(replaced); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_sameInstance(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map.compute(key, (k, v) -> value)).isSameInstanceAs(value); - replaced.put(key, value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map).containsEntry(key, value); - }); - assertThat(map).hasSize(context.initialSize()); - if (context.isGuava()) { - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } else { - assertThat(context).removalNotifications().isEmpty(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_differentValue(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - assertThat(map.compute(key, (k, v) -> k)).isEqualTo(key); - replaced.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> assertThat(map).containsEntry(key, key)); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void compute_async_null( - AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - Int newValue = context.absentValue(); - var future = new CompletableFuture(); - cache.put(key, future); - var start = new AtomicBoolean(); - var done = new AtomicBoolean(); - ConcurrentTestHarness.execute(() -> { - start.set(true); - Int result = cache.synchronous().asMap().compute(key, (k, oldValue) -> newValue); - assertThat(result).isEqualTo(newValue); - done.set(true); - }); - await().untilTrue(start); - future.complete(null); - await().untilTrue(done); - assertThat(cache).containsEntry(key, newValue); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void merge_nullKey(Map map, CacheContext context) { - map.merge(null, Int.valueOf(1), (oldValue, value) -> oldValue.negate()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void merge_nullValue(Map map, CacheContext context) { - map.merge(Int.valueOf(1), null, (oldValue, value) -> oldValue.negate()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void merge_nullMappingFunction(Map map, CacheContext context) { - map.merge(Int.valueOf(1), Int.valueOf(1), null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void merge_remove(Map map, CacheContext context) { - var removed = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map.merge(key, value, (oldValue, v) -> null)).isNull(); - removed.put(key, value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(map).hasSize(context.initialSize() - count); - assertThat(context).stats().hits(0).misses(0).success(0).failures(count); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(removed).exclusively(); - } - - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches") - public void merge_recursive(Map map, CacheContext context) { - var mappingFunction = new BiFunction() { - @Override - public Int apply(Int oldValue, Int value) { - return map.merge(oldValue, oldValue.negate(), this); - } - }; - Int firstValue = context.original().get(context.firstKey()); - Int value = map.merge(context.absentKey(), firstValue, mappingFunction); - assertThat(value).isEqualTo(firstValue); - } - - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = StackOverflowError.class) - public void merge_pingpong(Map map, CacheContext context) { - var mappingFunction = new BiFunction() { - int recursed; - - @Override - public Int apply(Int oldValue, Int value) { - if (++recursed == 2) { - throw new StackOverflowError(); - } - return map.merge(context.lastKey(), context.original().get(context.lastKey()), this); - } - }; - map.merge(context.firstKey(), context.original().get(context.firstKey()), mappingFunction); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void merge_error(Map map, CacheContext context) { - try { - map.merge(context.firstKey(), context.original().get(context.firstKey()), (oldValue, value) -> { - throw new ExpectedError(); - }); - } catch (ExpectedError expected) { - } - assertThat(map).containsExactlyEntriesIn(context.original()); - assertThat(context).stats().hits(0).misses(0).success(0).failures(1); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void merge_absent(Map map, CacheContext context) { - Int result = map.merge(context.absentKey(), context.absentValue(), (oldValue, value) -> value); - assertThat(result).isEqualTo(context.absentValue()); - assertThat(map).containsEntry(context.absentKey(), context.absentValue()); - assertThat(map).hasSize(1 + context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void merge_sameValue(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = intern(new Int(context.original().get(key))); - assertThat(map.merge(key, key.negate(), (oldValue, v) -> value)).isSameInstanceAs(value); - replaced.put(key, value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - assertThat(map).hasSize(context.initialSize()); - assertThat(map).containsAtLeastEntriesIn(replaced); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void merge_sameInstance(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map.merge(key, key.negate(), (oldValue, v) -> value)).isSameInstanceAs(value); - replaced.put(key, value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(map).containsEntry(key, value); - }); - assertThat(map).hasSize(context.initialSize()); - if (context.isGuava()) { - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } else { - assertThat(context).removalNotifications().isEmpty(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void merge_differentValue(Map map, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - assertThat(map.merge(key, key, (oldValue, v) -> intern(oldValue.add(v)))).isEqualTo(0); - replaced.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> assertThat(map).containsEntry(key, Int.valueOf(0))); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void merge_async_null(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - Int newValue = context.absentValue(); - var future = new CompletableFuture(); - cache.put(key, future); - var start = new AtomicBoolean(); - var done = new AtomicBoolean(); - ConcurrentTestHarness.execute(() -> { - start.set(true); - Int result = cache.synchronous().asMap() - .merge(key, newValue, (k, oldValue) -> newValue.add(1)); - assertThat(result).isEqualTo(newValue); - done.set(true); - }); - await().untilTrue(start); - future.complete(null); - await().untilTrue(done); - assertThat(cache).containsEntry(key, newValue); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void equals_null(Map map, CacheContext context) { - assertThat(map.equals(null)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @SuppressWarnings("SelfEquals") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void equals_self(Map map, CacheContext context) { - assertThat(map.equals(map)).isTrue(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void equals(Map map, CacheContext context) { - assertThat(map.equals(context.original())).isTrue(); - assertThat(context.original().equals(map)).isTrue(); - assertThat(map.equals(context.absent())).isFalse(); - assertThat(context.absent().equals(map)).isFalse(); - if (!map.isEmpty()) { - var other = map.entrySet().stream().collect(toImmutableMap(Map.Entry::getKey, entry -> entry.getValue().negate())); - assertThat(map.equals(other)).isFalse(); - assertThat(other.equals(map)).isFalse(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void hashCode(Map map, CacheContext context) { - assertThat(map.hashCode()).isEqualTo(context.original().hashCode()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void hashCode_self(Map map, CacheContext context) { - assertThat(map.hashCode()).isEqualTo(map.hashCode()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void equalsAndHashCodeFail_empty(Map map, CacheContext context) { - var other = Int.mapOf(1, -1, 2, -2, 3, -3); - assertThat(map.equals(other)).isFalse(); - assertThat(other.equals(map)).isFalse(); - assertThat(map.hashCode()).isNotEqualTo(other.hashCode()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void equalsAndHashCodeFail_present(Map map, CacheContext context) { - var other = Int.mapOf(1, -1, 2, -2, 3, -3); - assertThat(map.equals(other)).isFalse(); - assertThat(other.equals(map)).isFalse(); - assertThat(map.hashCode()).isNotEqualTo(other.hashCode()); - Map empty = Map.of(); - assertThat(map.equals(empty)).isFalse(); - assertThat(empty.equals(map)).isFalse(); - assertThat(map.hashCode()).isNotEqualTo(empty.hashCode()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void toString(Map map, CacheContext context) { - assertThat(parseToString(map)).containsExactlyEntriesIn(parseToString(context.original())); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine) - public void toString_self(Map map, CacheContext context) { - map.put(context.absentKey(), map); - assertThat(map.toString()).contains(context.absentKey() + "=(this Map)"); - } - - private static Map parseToString(Map map) { - return Splitter.on(',').trimResults().omitEmptyStrings().withKeyValueSeparator("=") - .split(map.toString().replaceAll("\\{|\\}", "")); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySet_toArray_null(Map map, CacheContext context) { - map.keySet().toArray((Int[]) null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void keySet_toArray(Map map, CacheContext context) { - var array = map.keySet().toArray(); - assertThat(array).asList().containsExactlyElementsIn(context.original().keySet()); - var ints = map.keySet().toArray(new Int[0]); - assertThat(ints).asList().containsExactlyElementsIn(context.original().keySet()); - var func = map.keySet().toArray(Int[]::new); - assertThat(func).asList().containsExactlyElementsIn(context.original().keySet()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void keySet_contains_absent(Map map, CacheContext context) { - assertThat(map.containsKey(context.absentKey())).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void keySet_contains_present(Map map, CacheContext context) { - assertThat(map.containsKey(context.firstKey())).isTrue(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void keySet_whenEmpty(Map map, CacheContext context) { - assertThat(map.keySet()).isExhaustivelyEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void keySet_addNotSupported(Map map, CacheContext context) { - map.keySet().add(Int.valueOf(1)); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_clear(Map map, CacheContext context) { - map.keySet().clear(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySet_removeAll_null(Map map, CacheContext context) { - map.keySet().removeAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeAll_nullKey(Map map, CacheContext context) { - map.keySet().removeAll(Arrays.asList((Object) null)); - assertThat(map).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeAll_none_empty(Map map, CacheContext context) { - assertThat(map.keySet().removeAll(Set.of())).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeAll_none_populated(Map map, CacheContext context) { - assertThat(map.keySet().removeAll(Set.of(context.absentKey()))).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_removeAll_partial(Map map, CacheContext context) { - var expected = new HashMap<>(context.original()); - expected.keySet().removeAll(context.firstMiddleLastKeys()); - assertThat(map.keySet().removeAll(context.firstMiddleLastKeys())).isTrue(); - assertThat(map).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_removeAll_all(Map map, CacheContext context) { - assertThat(map.keySet().removeAll(context.original().keySet())).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_removeAll_self(Map map, CacheContext context) { - assertThat(map.keySet().removeAll(map.keySet())).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, implementation = Implementation.Caffeine) - public void keySet_removeAll_byCollection(Map map, CacheContext context) { - var delegate = Sets.union(context.original().keySet(), context.absentKeys()); - var keys = Mockito.mock(Collection.class); - when(keys.iterator()).thenReturn(delegate.iterator()); - assertThat(map.keySet().removeAll(keys)).isTrue(); - verify(keys).iterator(); - verifyNoMoreInteractions(keys); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, implementation = Implementation.Caffeine) - public void keySet_removeAll_bySet(Map map, CacheContext context) { - var delegate = Sets.union(context.original().keySet(), context.absentKeys()); - var keys = Mockito.mock(Set.class); - when(keys.size()).thenReturn(delegate.size()); - when(keys.contains(any())).thenAnswer(invocation -> delegate.contains(invocation.getArgument(0))); - assertThat(map.keySet().removeAll(keys)).isTrue(); - verify(keys).size(); - verify(keys, times(context.original().size())).contains(any()); - verifyNoMoreInteractions(keys); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySet_remove_null(Map map, CacheContext context) { - map.keySet().remove(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_remove_none(Map map, CacheContext context) { - assertThat(map.keySet().remove(context.absentKey())).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_remove(Map map, CacheContext context) { - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(map.keySet().remove(context.firstKey())).isTrue(); - assertThat(map).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.firstKey(), context.original().get(context.firstKey())).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySet_removeIf_null(Map map, CacheContext context) { - map.keySet().removeIf(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeIf_none(Map map, CacheContext context) { - assertThat(map.keySet().removeIf(v -> false)).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeIf_partial(Map map, CacheContext context) { - Predicate isEven = key -> (key.intValue() % 2) == 0; - boolean hasEven = map.keySet().stream().anyMatch(isEven); - boolean removedIfEven = map.keySet().removeIf(isEven); - assertThat(map.keySet().stream().anyMatch(isEven)).isFalse(); - assertThat(removedIfEven).isEqualTo(hasEven); - if (removedIfEven) { - assertThat(map).hasSizeLessThan(context.initialSize()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeIf_all(Map map, CacheContext context) { - if (context.population() == Population.EMPTY) { - assertThat(map.keySet().removeIf(v -> true)).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(map.keySet().removeIf(v -> true)).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySet_retainAll_null(Map map, CacheContext context) { - map.keySet().retainAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_retainAll_nullKey(Map map, CacheContext context) { - map.keySet().retainAll(Arrays.asList((Object) null)); - assertThat(map).isExhaustivelyEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_retainAll_none_empty(Map map, CacheContext context) { - boolean modified = map.keySet().retainAll(Set.of()); - assertThat(map).isExhaustivelyEmpty(); - if (context.original().isEmpty()) { - assertThat(modified).isFalse(); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(modified).isTrue(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_retainAll_none_populated(Map map, CacheContext context) { - assertThat(map.keySet().retainAll(Set.of(context.absentKey()))).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_retainAll_partial(Map map, CacheContext context) { - var expected = new HashMap<>(context.original()); - expected.keySet().removeAll(context.firstMiddleLastKeys()); - assertThat(map.keySet().retainAll(expected.keySet())).isTrue(); - assertThat(map).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_retainAll_all(Map map, CacheContext context) { - assertThat(map.keySet().retainAll(context.original().keySet())).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_retainAll_self(Map map, CacheContext context) { - assertThat(map.keySet().retainAll(map.keySet())).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet(Map map, CacheContext context) { - var keys = map.keySet(); - assertThat(keys).doesNotContain(new Object()); - assertThat(keys.remove(new Object())).isFalse(); - assertThat(keys).hasSize(context.initialSize()); - Set.copyOf(keys).forEach(key -> { - assertThat(keys).contains(key); - assertThat(keys.remove(key)).isTrue(); - assertThat(keys.remove(key)).isFalse(); - assertThat(keys).doesNotContain(key); - }); - assertThat(map).isExhaustivelyEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_iterator(Map map, CacheContext context) { - int iterations = 0; - for (var i = map.keySet().iterator(); i.hasNext(); ) { - assertThat(map).containsKey(i.next()); - iterations++; - i.remove(); - } - int count = iterations; - assertThat(map).isExhaustivelyEmpty(); - assertThat(count).isEqualTo(context.initialSize()); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void keyIterator_noElement(Map map, CacheContext context) { - map.keySet().iterator().remove(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NoSuchElementException.class) - public void keyIterator_noMoreElements(Map map, CacheContext context) { - map.keySet().iterator().next(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySpliterator_forEachRemaining_null(Map map, CacheContext context) { - map.keySet().spliterator().forEachRemaining(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySpliterator_forEachRemaining(Map map, CacheContext context) { - int[] count = new int[1]; - map.keySet().spliterator().forEachRemaining(key -> count[0]++); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySpliterator_tryAdvance_null(Map map, CacheContext context) { - map.keySet().spliterator().tryAdvance(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySpliterator_tryAdvance(Map map, CacheContext context) { - var spliterator = map.keySet().spliterator(); - int[] count = new int[1]; - boolean advanced; - do { - advanced = spliterator.tryAdvance(key -> count[0]++); - } while (advanced); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySpliterator_trySplit(Map map, CacheContext context) { - var spliterator = map.keySet().spliterator(); - var other = firstNonNull(spliterator.trySplit(), Spliterators.emptySpliterator()); - int[] count = new int[1]; - spliterator.forEachRemaining(key -> count[0]++); - other.forEachRemaining(key -> count[0]++); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySpliterator_estimateSize(Map map, CacheContext context) { - var spliterator = map.keySet().spliterator(); - assertThat(spliterator.estimateSize()).isEqualTo(context.initialSize()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void values_toArray_null(Map map, CacheContext context) { - map.values().toArray((Int[]) null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void values_toArray(Map map, CacheContext context) { - var array = map.values().toArray(); - assertThat(array).asList().containsExactlyElementsIn(context.original().values()); - var ints = map.values().toArray(new Int[0]); - assertThat(ints).asList().containsExactlyElementsIn(context.original().values()); - var func = map.values().toArray(Int[]::new); - assertThat(func).asList().containsExactlyElementsIn(context.original().values()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void values_contains_absent(Map map, CacheContext context) { - assertThat(map.containsValue(context.absentValue())).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void values_contains_present(Map map, CacheContext context) { - assertThat(map.containsValue(context.original().get(context.firstKey()))).isTrue(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void values_empty(Map map, CacheContext context) { - assertThat(map.values()).isExhaustivelyEmpty(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void values_addNotSupported(Map map, CacheContext context) { - map.values().add(Int.valueOf(1)); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_clear(Map map, CacheContext context) { - map.values().clear(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void values_removeAll_null(Map map, CacheContext context) { - map.values().removeAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeAll_nullValue(Map map, CacheContext context) { - map.values().removeAll(Arrays.asList((Object) null)); - assertThat(map).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeAll_none_empty(Map map, CacheContext context) { - assertThat(map.values().removeAll(Set.of())).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeAll_none_populated(Map map, CacheContext context) { - assertThat(map.values().removeAll(Set.of(context.absentValue()))).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_removeAll_partial(Map map, CacheContext context) { - var expected = new HashMap<>(context.original()); - expected.keySet().removeAll(context.firstMiddleLastKeys()); - var removed = Maps.asMap(context.firstMiddleLastKeys(), context.original()::get); - assertThat(map.values().removeAll(removed.values())).isTrue(); - assertThat(map).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(removed).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_removeAll_all(Map map, CacheContext context) { - assertThat(map.values().removeAll(context.original().values())).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_removeAll_self(Map map, CacheContext context) { - assertThat(map.values().removeAll(map.values())).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_remove_null(Map map, CacheContext context) { - assertThat(map.values().remove(null)).isFalse(); - assertThat(map).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_remove_none(Map map, CacheContext context) { - assertThat(map.values().remove(context.absentValue())).isFalse(); - assertThat(map).isEqualTo(context.original()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_remove(Map map, CacheContext context) { - var value = context.original().get(context.firstKey()); - assertThat(map.values().remove(value)).isTrue(); - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(map).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.firstKey(), value).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_remove_once(Map map, CacheContext context) { - var expected = new HashMap<>(context.original()); - context.firstMiddleLastKeys().forEach(key -> { - expected.put(key, context.absentValue()); - map.put(key, context.absentValue()); - }); - context.clearRemovalNotifications(); - assertThat(map.values().remove(context.absentValue())).isTrue(); - var removedKey = context.firstMiddleLastKeys().stream().filter(key -> !map.containsKey(key)).findAny().orElseThrow(); - expected.remove(removedKey); - assertThat(map).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(removedKey, context.absentValue()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void values_removeIf_null(Map map, CacheContext context) { - map.values().removeIf(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeIf_none(Map map, CacheContext context) { - assertThat(map.values().removeIf(v -> false)).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeIf_partial(Map map, CacheContext context) { - Predicate isEven = value -> (value.intValue() % 2) == 0; - boolean hasEven = map.values().stream().anyMatch(isEven); - boolean removedIfEven = map.values().removeIf(isEven); - assertThat(map.values().stream().anyMatch(isEven)).isFalse(); - assertThat(removedIfEven).isEqualTo(hasEven); - if (removedIfEven) { - assertThat(map.size()).isLessThan(context.original().size()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeIf_all(Map map, CacheContext context) { - if (context.population() == Population.EMPTY) { - assertThat(map.values().removeIf(v -> true)).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(map.values().removeIf(v -> true)).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void values_retainAll_null(Map map, CacheContext context) { - map.values().retainAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_retainAll_nullValue(Map map, CacheContext context) { - map.values().retainAll(Arrays.asList((Object) null)); - assertThat(map).isExhaustivelyEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_retainAll_none_empty(Map map, CacheContext context) { - boolean modified = map.values().retainAll(Set.of()); - assertThat(map).isExhaustivelyEmpty(); - if (context.original().isEmpty()) { - assertThat(modified).isFalse(); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(modified).isTrue(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_retainAll_none_populated(Map map, CacheContext context) { - assertThat(map.values().retainAll(Set.of(context.absentValue()))).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_retainAll_partial(Map map, CacheContext context) { - var expected = new HashMap<>(context.original()); - expected.keySet().removeAll(context.firstMiddleLastKeys()); - var removed = Maps.asMap(context.firstMiddleLastKeys(), context.original()::get); - assertThat(map.values().retainAll(expected.values())).isTrue(); - assertThat(map).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(removed).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_retainAll_all(Map map, CacheContext context) { - assertThat(map.values().retainAll(context.original().values())).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_retainAll_self(Map map, CacheContext context) { - assertThat(map.values().retainAll(map.values())).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values(Map map, CacheContext context) { - var values = map.values(); - assertThat(values).doesNotContain(new Object()); - assertThat(values.remove(new Object())).isFalse(); - assertThat(values).hasSize(context.initialSize()); - List.copyOf(values).forEach(value -> { - assertThat(values).contains(value); - assertThat(values.remove(value)).isTrue(); - assertThat(values.remove(value)).isFalse(); - assertThat(values).doesNotContain(value); - }); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void valueIterator(Map map, CacheContext context) { - int iterations = 0; - for (var i = map.values().iterator(); i.hasNext(); ) { - assertThat(map.values()).contains(i.next()); - iterations++; - i.remove(); - } - int count = iterations; - assertThat(map).isExhaustivelyEmpty(); - assertThat(count).isEqualTo(context.initialSize()); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void valueIterator_noElement(Map map, CacheContext context) { - map.values().iterator().remove(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NoSuchElementException.class) - public void valueIterator_noMoreElements(Map map, CacheContext context) { - map.values().iterator().next(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void valueSpliterator_forEachRemaining_null(Map map, CacheContext context) { - map.values().spliterator().forEachRemaining(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void valueSpliterator_forEachRemaining(Map map, CacheContext context) { - int[] count = new int[1]; - map.values().spliterator().forEachRemaining(value -> count[0]++); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void valueSpliterator_tryAdvance_null(Map map, CacheContext context) { - map.values().spliterator().tryAdvance(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void valueSpliterator_tryAdvance(Map map, CacheContext context) { - var spliterator = map.values().spliterator(); - int[] count = new int[1]; - boolean advanced; - do { - advanced = spliterator.tryAdvance(value -> count[0]++); - } while (advanced); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void valueSpliterator_trySplit(Map map, CacheContext context) { - var spliterator = map.values().spliterator(); - var other = firstNonNull(spliterator.trySplit(), Spliterators.emptySpliterator()); - int[] count = new int[1]; - spliterator.forEachRemaining(value -> count[0]++); - other.forEachRemaining(value -> count[0]++); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void valueSpliterator_estimateSize(Map map, CacheContext context) { - var spliterator = map.values().spliterator(); - assertThat(spliterator.estimateSize()).isEqualTo(context.initialSize()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySet_toArray_null(Map map, CacheContext context) { - map.entrySet().toArray((Map.Entry[]) null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_toArray(Map map, CacheContext context) { - var array = map.entrySet().toArray(); - assertThat(array).asList().containsExactlyElementsIn(context.original().entrySet()); - var ints = map.entrySet().toArray(new Map.Entry[0]); - assertThat(ints).asList().containsExactlyElementsIn(context.original().entrySet()); - var func = map.entrySet().toArray(Map.Entry[]::new); - assertThat(func).asList().containsExactlyElementsIn(context.original().entrySet()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_contains_nullKey(Map map, CacheContext context) { - var entry = new AbstractMap.SimpleEntry<>(null, context.original().get(context.firstKey())); - assertThat(map.entrySet().contains(entry)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_contains_nullValue(Map map, CacheContext context) { - var entry = new AbstractMap.SimpleEntry<>(context.firstKey(), null); - assertThat(map.entrySet().contains(entry)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_contains_absent(Map map, CacheContext context) { - var entry = Map.entry(context.absentKey(), context.absentValue()); - assertThat(map.entrySet().contains(entry)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_contains_present(Map map, CacheContext context) { - var entry = Map.entry(context.firstKey(), context.original().get(context.firstKey())); - assertThat(map.entrySet().contains(entry)).isTrue(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_empty(Map map, CacheContext context) { - assertThat(map.entrySet()).isExhaustivelyEmpty(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = Listener.DISABLED) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void entrySet_addIsNotSupported(Map map, CacheContext context) { - try { - map.entrySet().add(Map.entry(Int.valueOf(1), Int.valueOf(2))); - } finally { - assertThat(map).isExhaustivelyEmpty(); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_clear(Map map, CacheContext context) { - map.entrySet().clear(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySet_removeAll_null(Map map, CacheContext context) { - map.entrySet().removeAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeAll_nullEntry(Map map, CacheContext context) { - map.entrySet().removeAll(Arrays.asList((Object) null)); - assertThat(map).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeAll_none_empty(Map map, CacheContext context) { - assertThat(map.entrySet().removeAll(Set.of())).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeAll_none_populated(Map map, CacheContext context) { - assertThat(map.entrySet().removeAll(Set.of(Map.entry(context.absentKey(), context.absentKey())))).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_removeAll_partial(Map map, CacheContext context) { - var removed = Maps.asMap(context.firstMiddleLastKeys(), context.original()::get); - var expected = new HashMap<>(context.original()); - expected.entrySet().removeAll(removed.entrySet()); - assertThat(map.entrySet().removeAll(removed.entrySet())).isTrue(); - assertThat(map).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(removed).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_removeAll_all(Map map, CacheContext context) { - assertThat(map.entrySet().removeAll(context.original().entrySet())).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_removeAll_self(Map map, CacheContext context) { - assertThat(map.entrySet().removeAll(map.entrySet())).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, implementation = Implementation.Caffeine) - public void entrySet_removeAll_byCollection(Map map, CacheContext context) { - var delegate = Sets.union(context.original().entrySet(), context.absent().entrySet()); - var entries = Mockito.mock(Collection.class); - when(entries.iterator()).thenReturn(delegate.iterator()); - assertThat(map.entrySet().removeAll(entries)).isTrue(); - verify(entries).iterator(); - verifyNoMoreInteractions(entries); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, implementation = Implementation.Caffeine) - public void entrySet_removeAll_bySet(Map map, CacheContext context) { - var delegate = Sets.union(context.original().entrySet(), context.absent().entrySet()); - var entries = Mockito.mock(Set.class); - when(entries.size()).thenReturn(delegate.size()); - when(entries.contains(any())).thenAnswer(invocation -> delegate.contains(invocation.getArgument(0))); - assertThat(map.entrySet().removeAll(entries)).isTrue(); - verify(entries).size(); - verify(entries, times(context.original().size())).contains(any()); - verifyNoMoreInteractions(entries); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_remove_null(Map map, CacheContext context) { - assertThat(map.values().remove(null)).isFalse(); - assertThat(map).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_remove_nullKey(Map map, CacheContext context) { - var value = Iterables.getFirst(context.original().values(), context.absentValue()); - assertThat(map.entrySet().remove(Maps.immutableEntry(null, value))).isFalse(); - assertThat(map).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_remove_nullValue(Map map, CacheContext context) { - var key = Iterables.getFirst(context.original().keySet(), context.absentKey()); - assertThat(map.entrySet().remove(Maps.immutableEntry(key, null))).isFalse(); - assertThat(map).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_remove_nullKeyValue(Map map, CacheContext context) { - assertThat(map.entrySet().remove(Maps.immutableEntry(null, null))).isFalse(); - assertThat(map).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_remove_none(Map map, CacheContext context) { - var entry = Map.entry(context.absentKey(), context.absentValue()); - assertThat(map.entrySet().remove(entry)).isFalse(); - assertThat(map).isEqualTo(context.original()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_remove(Map map, CacheContext context) { - var entry = Map.entry(context.firstKey(), context.original().get(context.firstKey())); - assertThat(map.entrySet().remove(entry)).isTrue(); - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(map).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(entry).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySet_removeIf_null(Map map, CacheContext context) { - map.entrySet().removeIf(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeIf_none(Map map, CacheContext context) { - assertThat(map.entrySet().removeIf(v -> false)).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeIf_partial(Map map, CacheContext context) { - Predicate> isEven = entry -> (entry.getValue().intValue() % 2) == 0; - boolean hasEven = map.entrySet().stream().anyMatch(isEven); - boolean removedIfEven = map.entrySet().removeIf(isEven); - assertThat(map.entrySet().stream().anyMatch(isEven)).isFalse(); - assertThat(removedIfEven).isEqualTo(hasEven); - if (removedIfEven) { - assertThat(map).hasSizeLessThan(context.initialSize()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeIf_all(Map map, CacheContext context) { - if (context.population() == Population.EMPTY) { - assertThat(map.entrySet().removeIf(v -> true)).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(map.entrySet().removeIf(v -> true)).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySet_retainAll_null(Map map, CacheContext context) { - map.entrySet().retainAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_retainAll_nullEntry(Map map, CacheContext context) { - map.entrySet().retainAll(Arrays.asList((Object) null)); - assertThat(map).isExhaustivelyEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_retainAll_none_empty(Map map, CacheContext context) { - boolean modified = map.entrySet().retainAll(Set.of()); - assertThat(map).isExhaustivelyEmpty(); - if (context.original().isEmpty()) { - assertThat(modified).isFalse(); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(modified).isTrue(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_retainAll_none_populated(Map map, CacheContext context) { - assertThat(map.entrySet().retainAll(Set.of(Map.entry(context.absentKey(), context.absentValue())))).isTrue(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_retainAll_partial(Map map, CacheContext context) { - var removed = Maps.asMap(context.firstMiddleLastKeys(), context.original()::get); - var expected = new HashMap<>(context.original()); - expected.entrySet().removeAll(removed.entrySet()); - assertThat(map.entrySet().retainAll(expected.entrySet())).isTrue(); - assertThat(map).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(removed).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_retainAll_all(Map map, CacheContext context) { - assertThat(map.entrySet().retainAll(context.original().entrySet())).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_retainAll_self(Map map, CacheContext context) { - assertThat(map.entrySet().retainAll(map.entrySet())).isFalse(); - assertThat(map).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet(Map map, CacheContext context) { - var entries = map.entrySet(); - assertThat(entries).doesNotContain(new Object()); - assertThat(entries.remove(new Object())).isFalse(); - assertThat(entries).hasSize(context.initialSize()); - entries.forEach(entry -> { - assertThat(entries).contains(entry); - assertThat(entries.remove(entry)).isTrue(); - assertThat(entries.remove(entry)).isFalse(); - assertThat(entries).doesNotContain(entry); - }); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entryIterator(Map map, CacheContext context) { - int iterations = 0; - for (var i = map.entrySet().iterator(); i.hasNext(); ) { - var entry = i.next(); - assertThat(map).containsEntry(entry.getKey(), entry.getValue()); - iterations++; - i.remove(); - } - int count = iterations; - assertThat(map).isExhaustivelyEmpty(); - assertThat(count).isEqualTo(context.initialSize()); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void entryIterator_noElement(Map map, CacheContext context) { - map.entrySet().iterator().remove(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NoSuchElementException.class) - public void entryIterator_noMoreElements(Map map, CacheContext context) { - map.entrySet().iterator().next(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySpliterator_forEachRemaining_null(Map map, CacheContext context) { - map.entrySet().spliterator().forEachRemaining(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySpliterator_forEachRemaining(Map map, CacheContext context) { - int[] count = new int[1]; - map.entrySet().spliterator().forEachRemaining(entry -> { - if (context.isCaffeine()) { - assertThat(entry).isInstanceOf(WriteThroughEntry.class); - } - count[0]++; - assertThat(context.original()).containsEntry(entry.getKey(), entry.getValue()); - }); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySpliterator_tryAdvance_null(Map map, CacheContext context) { - map.entrySet().spliterator().tryAdvance(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySpliterator_tryAdvance(Map map, CacheContext context) { - var spliterator = map.entrySet().spliterator(); - int[] count = new int[1]; - boolean advanced; - do { - advanced = spliterator.tryAdvance(entry -> { - if (context.isCaffeine()) { - assertThat(entry).isInstanceOf(WriteThroughEntry.class); - } - count[0]++; - }); - } while (advanced); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySpliterator_trySplit(Map map, CacheContext context) { - var spliterator = map.entrySet().spliterator(); - var other = firstNonNull(spliterator.trySplit(), Spliterators.emptySpliterator()); - int[] count = new int[1]; - spliterator.forEachRemaining(entry -> count[0]++); - other.forEachRemaining(entry -> count[0]++); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySpliterator_estimateSize(Map map, CacheContext context) { - var spliterator = map.entrySet().spliterator(); - assertThat(spliterator.estimateSize()).isEqualTo(context.initialSize()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void writeThroughEntry(Map map, CacheContext context) { - var entry = map.entrySet().iterator().next(); - var oldValue = entry.getValue(); - entry.setValue(Int.valueOf(3)); - assertThat(map).hasSize(context.initialSize()); - assertThat(map).containsEntry(entry.getKey(), Int.valueOf(3)); - assertThat(context).removalNotifications().withCause(REPLACED).contains(entry.getKey(), oldValue).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void writeThroughEntry_null(Map map, CacheContext context) { - map.entrySet().iterator().next().setValue(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void writeThroughEntry_serialize(Map map, CacheContext context) { - var entry = map.entrySet().iterator().next(); - var copy = SerializableTester.reserialize(entry); - assertThat(entry).isEqualTo(copy); - } - - @Test - public void writeThroughEntry_equals_hashCode_toString() { - var map = new ConcurrentHashMap<>(); - var entry = new WriteThroughEntry<>(map, 1, 2); - assertThat(entry.equals(Map.entry(1, 2))).isTrue(); - assertThat(entry.hashCode()).isEqualTo(Map.entry(1, 2).hashCode()); - assertThat(entry.toString()).isEqualTo(Map.entry(1, 2).toString()); - var other = new WriteThroughEntry<>(map, 3, 4); - assertThat(entry.equals(other)).isFalse(); - assertThat(entry.hashCode()).isNotEqualTo(other.hashCode()); - assertThat(entry.toString()).isNotEqualTo(other.toString()); - } - - static final class ExpectedError extends Error { - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncAsMapTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncAsMapTest.java deleted file mode 100644 index d12a25c..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncAsMapTest.java +++ /dev/null @@ -1,2619 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.testing.*; -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.Population; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.base.Splitter; -import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; -import org.eclipse.collections.impl.factory.Sets; -import org.mockito.Mockito; -import org.testng.Assert; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import static com.github.benmanes.caffeine.cache.RemovalCause.EXPLICIT; -import static com.github.benmanes.caffeine.cache.RemovalCause.REPLACED; -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContext.intern; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.testing.CollectionSubject.assertThat; -import static com.github.benmanes.caffeine.testing.FutureSubject.assertThat; -import static com.github.benmanes.caffeine.testing.IntSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.truth.Truth.assertThat; -import static java.util.function.Function.identity; -import static org.mockito.Mockito.*; -import static uk.org.lidalia.slf4jext.Level.ERROR; -import static uk.org.lidalia.slf4jext.Level.WARN; - -@CheckNoEvictions -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@Test(dataProviderClass = CacheProvider.class) -public final class AsyncAsMapTest { - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void isEmpty(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().isEmpty()).isEqualTo(context.original().isEmpty()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void size(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().size()).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void clear(AsyncCache cache, CacheContext context) { - cache.asMap().clear(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @SuppressWarnings("ReturnValueIgnored") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void containsKey_null(AsyncCache cache, CacheContext context) { - cache.asMap().containsKey(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void containsKey_present(AsyncCache cache, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> assertThat(cache.asMap().containsKey(key)).isTrue()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void containsKey_absent(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().containsKey(context.absentKey())).isFalse(); - } - - @CheckNoStats - @SuppressWarnings("ReturnValueIgnored") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void containsValue_null(AsyncCache cache, CacheContext context) { - cache.asMap().containsValue(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void containsValue_present(AsyncCache cache, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> assertThat(cache.asMap().containsValue(cache.asMap().get(key))).isTrue()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void containsValue_absent(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().containsValue(context.absentValue().asFuture())).isFalse(); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void get_null(AsyncCache cache, CacheContext context) { - cache.asMap().get(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void get_absent(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().get(context.absentKey())).isNull(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void get_present(AsyncCache cache, CacheContext context) { - for (Int key : context.firstMiddleLastKeys()) { - assertThat(cache.asMap().get(key)).succeedsWith(context.original().get(key)); - } - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getOrDefault_nullKey(AsyncCache cache, CacheContext context) { - cache.asMap().getOrDefault(null, Int.valueOf(1).asFuture()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getOrDefault_absent(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); - assertThat(cache.asMap().getOrDefault(context.absentKey(), null)).isNull(); - assertThat(cache.asMap().getOrDefault(context.absentKey(), value)).isEqualTo(value); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getOrDefault_present(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); - context.firstMiddleLastKeys().forEach(key -> assertThat(cache.asMap().getOrDefault(key, value)).succeedsWith(context.original().get(key))); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void forEach_null(AsyncCache cache, CacheContext context) { - cache.asMap().forEach(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void forEach_scan(AsyncCache cache, CacheContext context) { - var remaining = new HashMap<>(context.original()); - cache.asMap().forEach((key, future) -> { - assertThat(key).isNotNull(); - assertThat(future).isNotNull(); - assertThat(future.join()).isNotNull(); - assertThat(remaining.remove(key, future.join())).isTrue(); - }); - assertThat(remaining).isExhaustivelyEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void forEach_modify(AsyncCache cache, CacheContext context) { - cache.asMap().forEach((key, value) -> { - Int newKey = intern(context.lastKey().add(key)); - cache.synchronous().put(newKey, key); - }); - } - - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_nullKey(AsyncCache cache, CacheContext context) { - cache.asMap().put(null, Int.valueOf(1).asFuture()); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_nullValue(AsyncCache cache, CacheContext context) { - cache.asMap().put(Int.valueOf(1), null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_nullKeyAndValue(AsyncCache cache, CacheContext context) { - cache.asMap().put(null, null); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_insert(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); - assertThat(cache.asMap().put(context.absentKey(), value)).isNull(); - assertThat(cache).containsEntry(context.absentKey(), value); - assertThat(cache).hasSize(context.initialSize() + 1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace_sameValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var oldValue = cache.asMap().get(key); - var value = cache.asMap().get(key).thenApply(val -> intern(new Int(val))); - assertThat(cache.asMap().put(key, value)).isSameInstanceAs(oldValue); - assertThat(cache).containsEntry(key, value); - replaced.put(key, context.original().get(key)); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace_sameInstance(AsyncCache cache, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> { - var value = cache.asMap().get(key); - assertThat(cache.asMap().put(key, value)).isSameInstanceAs(value); - assertThat(cache).containsEntry(key, value); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace_differentValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var newValue = context.absentValue().asFuture(); - assertThat(cache.asMap().put(key, newValue)).succeedsWith(context.original().get(key)); - assertThat(cache).containsEntry(key, newValue); - replaced.put(key, context.original().get(key)); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY) - public void put_recursiveUpdate(AsyncCache cache, CacheContext context) { - cache.synchronous().put(context.absentKey(), context.absentValue()); - var result = cache.asMap().compute(context.absentKey(), (key, future) -> { - var oldValue = cache.synchronous().asMap().put(key, intern(future.join().add(1))); - assertThat(oldValue).isEqualTo(future.join()); - return key.asFuture(); - }); - assertThat(result).succeedsWith(context.absentKey()); - assertThat(cache).containsEntry(context.absentKey(), context.absentKey()); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putAll_null(AsyncCache cache, CacheContext context) { - cache.asMap().putAll(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putAll_empty(AsyncCache cache, CacheContext context) { - cache.asMap().putAll(Map.of()); - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putAll_insert(AsyncCache cache, CacheContext context) { - int startKey = context.original().size() + 1; - var entries = IntStream.range(startKey, 100 + startKey).mapToObj(Int::valueOf).collect(toImmutableMap(identity(), key -> key.negate().asFuture())); - cache.asMap().putAll(entries); - assertThat(cache).hasSize(100 + context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void putAll_replace(AsyncCache cache, CacheContext context) { - var entries = context.original().keySet().stream().collect(toImmutableMap(identity(), Int::asFuture)); - cache.asMap().putAll(entries); - assertThat(cache).containsExactlyEntriesIn(entries); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}) - public void putAll_mixed(AsyncCache cache, CacheContext context) { - var entries = new HashMap>(); - var replaced = new HashMap(); - context.original().forEach((key, value) -> { - if ((key.intValue() % 2) == 0) { - replaced.put(key, value); - entries.put(key, value.add(1).asFuture()); - } else { - entries.put(key, cache.asMap().get(key)); - } - }); - cache.asMap().putAll(entries); - assertThat(cache).containsExactlyEntriesIn(entries); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putIfAbsent_nullKey(AsyncCache cache, CacheContext context) { - cache.asMap().putIfAbsent(null, Int.valueOf(2).asFuture()); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putIfAbsent_nullValue(AsyncCache cache, CacheContext context) { - cache.asMap().putIfAbsent(Int.valueOf(1), null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putIfAbsent_nullKeyAndValue(AsyncCache cache, CacheContext context) { - cache.asMap().putIfAbsent(null, null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putIfAbsent_present(AsyncCache cache, CacheContext context) { - for (Int key : context.firstMiddleLastKeys()) { - var value = cache.asMap().get(key); - assertThat(cache.asMap().putIfAbsent(key, key.asFuture())).isEqualTo(value); - assertThat(cache).containsEntry(key, value); - } - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putIfAbsent_insert(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); - assertThat(cache.asMap().putIfAbsent(context.absentKey(), value)).isNull(); - assertThat(cache).containsEntry(context.absentKey(), value); - assertThat(cache).hasSize(context.initialSize() + 1); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void remove_nullKey(AsyncCache cache, CacheContext context) { - cache.asMap().remove(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void remove_absent(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().remove(context.absentKey())).isNull(); - assertThat(cache).hasSize(context.initialSize()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void remove_present(AsyncCache cache, CacheContext context) { - var removed = new HashMap(); - for (Int key : context.firstMiddleLastKeys()) { - cache.asMap().remove(key); - removed.put(key, context.original().get(key)); - } - assertThat(cache).hasSize(context.initialSize() - context.firstMiddleLastKeys().size()); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(removed).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void removeConditionally_nullKey(AsyncCache cache, CacheContext context) { - cache.asMap().remove(null, context.absentValue().asFuture()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void removeConditionally_nullValue(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().remove(context.absentKey(), null)).isFalse(); // see ConcurrentHashMap - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void removeConditionally_nullKeyAndValue( - AsyncCache cache, CacheContext context) { - cache.asMap().remove(null, null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void removeConditionally_absent(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); - assertThat(cache.asMap().remove(context.absentKey(), value)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void removeConditionally_presentKey(AsyncCache cache, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> assertThat(cache.asMap().remove(key, key.asFuture())).isFalse()); - assertThat(cache).hasSize(context.initialSize()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void removeConditionally_presentKeyAndValue(AsyncCache cache, CacheContext context) { - var removed = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var value = cache.asMap().get(key); - removed.put(key, context.original().get(key)); - assertThat(cache.asMap().remove(key, value)).isTrue(); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(cache).hasSize(context.initialSize() - count); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(removed).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replace_null(AsyncCache cache, CacheContext context) { - cache.asMap().replace(null, Int.valueOf(1).asFuture()); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replace_nullValue(AsyncCache cache, CacheContext context) { - cache.asMap().replace(Int.valueOf(1), null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replace_nullKeyAndValue(AsyncCache cache, CacheContext context) { - cache.asMap().replace(null, null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replace_absent(AsyncCache cache, CacheContext context) { - var result = cache.asMap().replace(context.absentKey(), context.absentValue().asFuture()); - assertThat(result).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.SINGLETON, removalListener = Listener.CONSUMING) - public void replace_failure(AsyncCache cache, CacheContext context) { - cache.asMap().replace(context.firstKey(), CompletableFuture.failedFuture(new Exception())); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.firstKey(), context.original().get(context.firstKey())) - .exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replace_sameValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var oldValue = cache.asMap().get(key); - var newValue = cache.asMap().get(key).thenApply(val -> intern(new Int(val))); - assertThat(cache.asMap().replace(key, newValue)).isSameInstanceAs(oldValue); - assertThat(cache).containsEntry(key, newValue); - replaced.put(key, context.original().get(key)); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replace_sameInstance(AsyncCache cache, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> { - var value = cache.asMap().get(key); - assertThat(cache.asMap().replace(key, value)).isSameInstanceAs(value); - assertThat(cache).containsEntry(key, value); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replace_differentValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var oldValue = cache.asMap().get(key); - var value = context.absentValue().asFuture(); - replaced.put(key, context.original().get(key)); - assertThat(cache.asMap().replace(key, value)).isEqualTo(oldValue); - assertThat(cache).containsEntry(key, value); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullKey(AsyncCache cache, CacheContext context) { - var value = Int.valueOf(1).asFuture(); - cache.asMap().replace(null, value, value); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullOldValue(AsyncCache cache, CacheContext context) { - var value = Int.valueOf(1).asFuture(); - cache.asMap().replace(Int.valueOf(1), null, value); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullNewValue(AsyncCache cache, CacheContext context) { - var value = Int.futureOf(1); - cache.asMap().replace(Int.valueOf(1), value, null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullKeyAndOldValue(AsyncCache cache, CacheContext context) { - var value = Int.futureOf(1); - cache.asMap().replace(null, null, value); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullKeyAndNewValue(AsyncCache cache, CacheContext context) { - var value = Int.futureOf(1); - cache.asMap().replace(null, value, null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullOldAndNewValue(AsyncCache cache, CacheContext context) { - cache.asMap().replace(Int.valueOf(1), null, null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_nullKeyAndValues(AsyncCache cache, CacheContext context) { - cache.asMap().replace(null, null, null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void replaceConditionally_absent(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); - assertThat(cache.asMap().replace(context.absentKey(), value, value)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replaceConditionally_wrongOldValue(AsyncCache cache, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> { - var oldValue = cache.asMap().get(key); - var value = context.absentValue().asFuture(); - assertThat(cache.asMap().replace(key, value, value)).isFalse(); - assertThat(cache).containsEntry(key, oldValue); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replaceConditionally_sameValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var oldValue = cache.asMap().get(key); - var newValue = cache.asMap().get(key).thenApply(val -> intern(new Int(val))); - assertThat(cache.asMap().replace(key, oldValue, newValue)).isTrue(); - assertThat(cache).containsEntry(key, newValue); - replaced.put(key, context.original().get(key)); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replaceConditionally_sameInstance(AsyncCache cache, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> { - var value = cache.asMap().get(key); - assertThat(cache.asMap().replace(key, value, value)).isTrue(); - assertThat(cache).containsEntry(key, value); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void replaceConditionally_differentValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var oldValue = cache.asMap().get(key); - var value = context.absentValue().asFuture(); - assertThat(cache.asMap().replace(key, oldValue, value)).isTrue(); - assertThat(cache).containsEntry(key, value); - replaced.put(key, context.original().get(key)); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void replaceAll_null(AsyncCache cache, CacheContext context) { - cache.asMap().replaceAll(null); - } - - @CheckNoStats - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void replaceAll_nullValue(AsyncCache cache, CacheContext context) { - cache.asMap().replaceAll((key, value) -> null); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void replaceAll_sameValue(AsyncCache cache, CacheContext context) { - cache.asMap().replaceAll((key, value) -> value); - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void replaceAll_differentValue(AsyncCache cache, CacheContext context) { - cache.asMap().replaceAll((key, value) -> key.asFuture()); - cache.asMap().forEach((key, value) -> assertThat(value).succeedsWith(key)); - assertThat(context).removalNotifications().withCause(REPLACED).contains(context.original()).exclusively(); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void computeIfAbsent_nullKey(AsyncCache cache, CacheContext context) { - cache.asMap().computeIfAbsent(null, key -> key.negate().asFuture()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void computeIfAbsent_nullMappingFunction( - AsyncCache cache, CacheContext context) { - cache.asMap().computeIfAbsent(context.absentKey(), null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfAbsent_nullValue(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().computeIfAbsent(context.absentKey(), key -> null)).isNull(); - assertThat(cache).hasSize(context.initialSize()); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void computeIfAbsent_recursive(AsyncCache cache, CacheContext context) { - var mappingFunction = new Function>() { - @Override - public CompletableFuture apply(Int key) { - return cache.asMap().computeIfAbsent(key, this); - } - }; - try { - cache.asMap().computeIfAbsent(context.absentKey(), mappingFunction); - Assert.fail(); - } catch (StackOverflowError | IllegalStateException ignored) { - } - } - - @CacheSpec - @Test(dataProvider = "caches") - public void computeIfAbsent_pingpong(AsyncCache cache, CacheContext context) { - var mappingFunction = new Function>() { - @Override - public CompletableFuture apply(Int key) { - return cache.asMap().computeIfAbsent(key.negate(), this); - } - }; - try { - cache.asMap().computeIfAbsent(context.absentKey(), mappingFunction); - Assert.fail(); - } catch (StackOverflowError | IllegalStateException ignored) { - } - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfAbsent_error(AsyncCache cache, CacheContext context) { - try { - cache.asMap().computeIfAbsent(context.absentKey(), key -> { - throw new IllegalStateException(); - }); - Assert.fail(); - } catch (IllegalStateException expected) { - } - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(context).stats().hits(0).misses(0).success(0).failures(0); - var future = cache.asMap().computeIfAbsent(context.absentKey(), Int::asFuture); - assertThat(future).succeedsWith(context.absentKey()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfAbsent_present(AsyncCache cache, CacheContext context) { - context.firstMiddleLastKeys().stream().map(key -> cache.asMap().computeIfAbsent(key, k -> { - throw new AssertionError(); - })).forEach(result -> assertThat(result).isNotNull()); - int count = context.firstMiddleLastKeys().size(); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).stats().hits(count).misses(0).success(0).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(stats = CacheSpec.Stats.ENABLED, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfAbsent_absent(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); - assertThat(cache.asMap().computeIfAbsent(context.absentKey(), key -> value)).isEqualTo(value); - assertThat(context).stats().hits(0).misses(1).success(1).failures(0); - assertThat(cache).containsEntry(context.absentKey(), value); - assertThat(cache).hasSize(1 + context.original().size()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void computeIfPresent_nullKey(AsyncCache cache, CacheContext context) { - cache.asMap().computeIfPresent(null, (key, value) -> key.negate().asFuture()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void computeIfPresent_nullMappingFunction( - AsyncCache cache, CacheContext context) { - cache.asMap().computeIfPresent(Int.valueOf(1), null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void computeIfPresent_nullValue(AsyncCache cache, CacheContext context) { - var removed = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - cache.asMap().computeIfPresent(key, (k, v) -> null); - removed.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(cache).hasSize(context.initialSize() - count); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(removed).exclusively(); - } - - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = StackOverflowError.class) - public void computeIfPresent_recursive(AsyncCache cache, CacheContext context) { - var mappingFunction = new BiFunction, CompletableFuture>() { - boolean recursed; - - @Override - public CompletableFuture apply(Int key, CompletableFuture value) { - if (recursed) { - throw new StackOverflowError(); - } - recursed = true; - return cache.asMap().computeIfPresent(key, this); - } - }; - cache.asMap().computeIfPresent(context.firstKey(), mappingFunction); - } - - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = StackOverflowError.class) - public void computeIfPresent_pingpong(AsyncCache cache, CacheContext context) { - var mappingFunction = new BiFunction, CompletableFuture>() { - int recursed; - - @Override - public CompletableFuture apply(Int key, CompletableFuture value) { - if (++recursed == 2) { - throw new StackOverflowError(); - } - return cache.asMap().computeIfPresent(context.lastKey(), this); - } - }; - cache.asMap().computeIfPresent(context.firstKey(), mappingFunction); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void computeIfPresent_error(AsyncCache cache, CacheContext context) { - try { - cache.asMap().computeIfPresent(context.firstKey(), - (key, value) -> { - throw new IllegalStateException(); - }); - Assert.fail(); - } catch (IllegalStateException expected) { - } - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(context).stats().hits(0).misses(0).success(0).failures(0); - var future = cache.asMap().computeIfPresent( - context.firstKey(), (k, v) -> k.negate().asFuture()); - assertThat(future).succeedsWith(context.firstKey().negate()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void computeIfPresent_absent(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().computeIfPresent(context.absentKey(), (key, value) -> value)).isNull(); - assertThat(cache).doesNotContainKey(context.absentKey()); - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void computeIfPresent_present_sameValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap>(); - context.firstMiddleLastKeys().forEach(key -> { - var value = intern(new Int(context.original().get(key))).asFuture(); - assertThat(cache.asMap().computeIfPresent(key, (k, v) -> value)).isSameInstanceAs(value); - replaced.put(key, value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - - assertThat(cache).hasSize(context.initialSize()); - assertThat(cache.asMap()).containsAtLeastEntriesIn(replaced); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(Maps.transformValues(replaced, CompletableFuture::join)) - .exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void computeIfPresent_present_sameInstance(AsyncCache cache, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> { - var value = cache.asMap().get(key); - assertThat(cache.asMap().computeIfPresent(key, (k, v) -> value)).isSameInstanceAs(value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - assertThat(context).removalNotifications().isEmpty(); - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void computeIfPresent_present_differentValue( - AsyncCache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var value = key.asFuture(); - replaced.put(key, context.original().get(key)); - assertThat(cache.asMap().computeIfPresent(key, (k, v) -> value)).isEqualTo(value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> assertThat(cache).containsEntry(key, key)); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void compute_nullKey(AsyncCache cache, CacheContext context) { - cache.asMap().compute(null, (key, value) -> key.negate().asFuture()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void compute_nullMappingFunction(AsyncCache cache, CacheContext context) { - cache.asMap().compute(Int.valueOf(1), null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_remove(AsyncCache cache, CacheContext context) { - var removed = new HashMap(); - for (Int key : context.firstMiddleLastKeys()) { - assertThat(cache.asMap().compute(key, (k, v) -> null)).isNull(); - removed.put(key, context.original().get(key)); - } - int count = context.firstMiddleLastKeys().size(); - assertThat(cache).hasSize(context.initialSize() - count); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(removed).exclusively(); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void compute_recursive(AsyncCache cache, CacheContext context) { - var mappingFunction = new BiFunction, CompletableFuture>() { - @Override - public CompletableFuture apply(Int key, CompletableFuture value) { - return cache.asMap().compute(key, this); - } - }; - try { - cache.asMap().compute(context.absentKey(), mappingFunction); - Assert.fail(); - } catch (StackOverflowError | IllegalStateException e) { /* ignored */ } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY) - public void compute_pingpong(AsyncCache cache, CacheContext context) { - var key1 = Int.valueOf(1); - var key2 = Int.valueOf(2); - var mappingFunction = new BiFunction, CompletableFuture>() { - @Override - public CompletableFuture apply(Int key, CompletableFuture value) { - return cache.asMap().compute(key.equals(key1) ? key2 : key1, this); - } - }; - try { - cache.asMap().compute(key1, mappingFunction); - Assert.fail(); - } catch (StackOverflowError | IllegalStateException ignored) { - } - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void compute_error(AsyncCache cache, CacheContext context) { - try { - cache.asMap().compute(context.absentKey(), (key, value) -> { - throw new IllegalStateException(); - }); - Assert.fail(); - } catch (IllegalStateException expected) { - } - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(context).stats().hits(0).misses(0).success(0).failures(0); - var future = context.absentKey().negate().asFuture(); - assertThat(cache.asMap().compute(context.absentKey(), (k, v) -> future)).isEqualTo(future); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void compute_absent_nullValue(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().compute(context.absentKey(), (key, value) -> null)).isNull(); - assertThat(cache).doesNotContainKey(context.absentKey()); - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void compute_absent(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); - assertThat(cache.asMap().compute(context.absentKey(), (k, v) -> value)).isEqualTo(value); - assertThat(context).stats().hits(0).misses(0).success(1).failures(0); - assertThat(cache).containsEntry(context.absentKey(), value); - assertThat(cache).hasSize(1 + context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_sameValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap>(); - context.firstMiddleLastKeys().forEach(key -> { - var value = intern(new Int(context.original().get(key))).asFuture(); - assertThat(cache.asMap().compute(key, (k, v) -> value)).isSameInstanceAs(value); - replaced.put(key, value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - assertThat(cache).hasSize(context.initialSize()); - assertThat(cache.asMap()).containsAtLeastEntriesIn(replaced); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(Maps.transformValues(replaced, CompletableFuture::join)) - .exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_sameInstance(AsyncCache cache, CacheContext context) { - for (Int key : context.firstMiddleLastKeys()) { - var value = cache.asMap().get(key); - assertThat(cache.asMap().compute(key, (k, v) -> value)).isSameInstanceAs(value); - } - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(cache).containsEntry(key, value); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_differentValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var value = key.asFuture(); - assertThat(cache.asMap().compute(key, (k, v) -> value)).isEqualTo(value); - replaced.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> assertThat(cache).containsEntry(key, key)); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void merge_nullKey(AsyncCache cache, CacheContext context) { - cache.asMap().merge(null, Int.valueOf(-1).asFuture(), (oldValue, value) -> value); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void merge_nullValue(AsyncCache cache, CacheContext context) { - cache.asMap().merge(Int.valueOf(1), null, (oldValue, value) -> null); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void merge_nullMappingFunction(AsyncCache cache, CacheContext context) { - cache.asMap().merge(Int.valueOf(1), Int.valueOf(1).asFuture(), null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void merge_remove(AsyncCache cache, CacheContext context) { - var removed = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var value = cache.asMap().get(key); - assertThat(cache.asMap().merge(key, value, (oldValue, v) -> null)).isNull(); - removed.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(cache).hasSize(context.initialSize() - count); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(removed).exclusively(); - } - - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches") - public void merge_recursive(AsyncCache cache, CacheContext context) { - var mappingFunction = new BiFunction, CompletableFuture, CompletableFuture>() { - @Override - public CompletableFuture apply( - CompletableFuture oldValue, CompletableFuture value) { - return cache.asMap().merge(context.absentKey(), oldValue, this); - } - }; - var firstValue = cache.asMap().get(context.firstKey()); - var value = cache.asMap().merge(context.absentKey(), firstValue, mappingFunction); - assertThat(value).isEqualTo(firstValue); - } - - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = StackOverflowError.class) - public void merge_pingpong(AsyncCache cache, CacheContext context) { - var mappingFunction = new BiFunction, CompletableFuture, CompletableFuture>() { - int recursed; - - @Override - public CompletableFuture apply(CompletableFuture oldValue, CompletableFuture value) { - if (++recursed == 2) { - throw new StackOverflowError(); - } - var lastValue = cache.asMap().get(context.lastKey()); - return cache.asMap().merge(context.lastKey(), lastValue, this); - } - }; - cache.asMap().merge(context.firstKey(), cache.asMap().get(context.firstKey()), mappingFunction); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void merge_error(AsyncCache cache, CacheContext context) { - try { - cache.asMap().merge(context.firstKey(), cache.asMap().get(context.firstKey()), (oldValue, value) -> { - throw new IllegalStateException(); - }); - Assert.fail(); - } catch (IllegalStateException expected) { - } - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(context).stats().hits(0).misses(0).success(0).failures(0); - var result = cache.asMap().merge(context.firstKey(), cache.asMap().get(context.firstKey()), (oldValue, value) -> context.absentValue().asFuture()); - assertThat(result).succeedsWith(context.absentValue()); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void merge_absent(AsyncCache cache, CacheContext context) { - var absent = context.absentValue().asFuture(); - var result = cache.asMap().merge(context.absentKey(), absent, (oldValue, value) -> value); - assertThat(result).isEqualTo(absent); - assertThat(cache).containsEntry(context.absentKey(), absent); - assertThat(cache).hasSize(1 + context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void merge_sameValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap>(); - context.firstMiddleLastKeys().forEach(key -> { - var value = cache.asMap().get(key).thenApply(Int::new); - var result = cache.asMap().merge(key, key.negate().asFuture(), (oldValue, v) -> value); - assertThat(result).isSameInstanceAs(value); - replaced.put(key, value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - assertThat(cache).hasSize(context.initialSize()); - assertThat(cache.asMap()).containsAtLeastEntriesIn(replaced); - assertThat(context).removalNotifications().withCause(REPLACED).contains(Maps.transformValues(replaced, CompletableFuture::join)) - .exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void merge_sameInstance(AsyncCache cache, CacheContext context) { - context.firstMiddleLastKeys().forEach(key -> { - var value = cache.asMap().get(key); - var result = cache.asMap().merge(key, key.negate().asFuture(), (oldValue, v) -> value); - assertThat(result).isSameInstanceAs(value); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(cache).containsEntry(key, value); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void merge_differentValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap(); - Int mergedValue = context.absentValue(); - context.firstMiddleLastKeys().forEach(key -> { - var result = cache.asMap().merge(key, key.asFuture(), - (oldValue, v) -> mergedValue.asFuture()); - assertThat(result).succeedsWith(mergedValue); - replaced.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> assertThat(cache).containsEntry(key, mergedValue)); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void equals_null(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().equals(null)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @SuppressWarnings("SelfEquals") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void equals_self(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().equals(cache.asMap())).isTrue(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void equals(AsyncCache cache, CacheContext context) { - var map = Map.copyOf(cache.asMap()); - assertThat(cache.asMap().equals(map)).isTrue(); - assertThat(map.equals(cache.asMap())).isTrue(); - var absent = Maps.asMap(context.absentKeys(), CompletableFuture::completedFuture); - assertThat(cache.asMap().equals(absent)).isFalse(); - assertThat(absent.equals(cache.asMap())).isFalse(); - if (!cache.asMap().isEmpty()) { - var other = Maps.asMap(cache.asMap().keySet(), CompletableFuture::completedFuture); - assertThat(cache.asMap().equals(other)).isFalse(); - assertThat(other.equals(cache.asMap())).isFalse(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void hashCode(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().hashCode()).isEqualTo(Map.copyOf(cache.asMap()).hashCode()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void hashCode_self(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().hashCode()).isEqualTo(cache.asMap().hashCode()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void equalsAndHashCodeFail_empty(AsyncCache cache, CacheContext context) { - var other = Map.of(Int.valueOf(1), Int.futureOf(-1), - Int.valueOf(2), Int.futureOf(-2), Int.valueOf(3), Int.futureOf(-3)); - assertThat(cache.asMap().equals(other)).isFalse(); - assertThat(other.equals(cache.asMap())).isFalse(); - assertThat(cache.asMap().hashCode()).isNotEqualTo(other.hashCode()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void equalsAndHashCodeFail_present(AsyncCache cache, CacheContext context) { - var other = Map.of(Int.valueOf(1), Int.futureOf(-1), Int.valueOf(2), Int.futureOf(-2), Int.valueOf(3), Int.futureOf(-3)); - assertThat(cache.asMap().equals(other)).isFalse(); - assertThat(other.equals(cache.asMap())).isFalse(); - assertThat(cache.asMap().hashCode()).isNotEqualTo(other.hashCode()); - var empty = Map.of(); - assertThat(cache.asMap().equals(empty)).isFalse(); - assertThat(empty.equals(cache.asMap())).isFalse(); - assertThat(cache.asMap().hashCode()).isNotEqualTo(empty.hashCode()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void toString(AsyncCache cache, CacheContext context) { - assertThat(parseToString(cache.asMap())).containsExactlyEntriesIn(parseToString(Map.copyOf(cache.asMap()))); - } - - private static Map parseToString(Map> map) { - return Splitter.on(',').trimResults().omitEmptyStrings().withKeyValueSeparator("=") - .split(map.toString().replaceAll("\\{|\\}", "")); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySet_toArray_null(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().toArray((Int[]) null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void keySet_toArray(AsyncCache cache, CacheContext context) { - var ints = cache.asMap().keySet().toArray(new Int[0]); - assertThat(ints).asList().containsExactlyElementsIn(context.original().keySet()); - var array = cache.asMap().keySet().toArray(); - assertThat(array).asList().containsExactlyElementsIn(context.original().keySet()); - var func = cache.asMap().keySet().toArray(Int[]::new); - assertThat(func).asList().containsExactlyElementsIn(context.original().keySet()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void keySet_contains_absent(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().containsKey(context.absentKey())).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void keySet_contains_present(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().containsKey(context.firstKey())).isTrue(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void keySet_whenEmpty(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().keySet()).isExhaustivelyEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void keySet_addNotSupported(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().add(Int.valueOf(1)); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_clear(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().clear(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySet_removeAll_null(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().removeAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeAll_nullKey(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().removeAll(Arrays.asList((Object) null)); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeAll_none_empty(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().keySet().removeAll(Set.of())).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeAll_none_populated(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().keySet().removeAll(Set.of(context.absentKey()))).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_removeAll_partial(AsyncCache cache, CacheContext context) { - var expected = new HashMap<>(context.original()); - expected.keySet().removeAll(context.firstMiddleLastKeys()); - assertThat(cache.asMap().keySet().removeAll(context.firstMiddleLastKeys())).isTrue(); - assertThat(cache.synchronous().asMap()).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_removeAll_all(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().keySet().removeAll(context.original().keySet())).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_removeAll_self(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().keySet().removeAll(cache.asMap().keySet())).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, implementation = Implementation.Caffeine) - public void keySet_removeAll_byCollection(AsyncCache cache, CacheContext context) { - var delegate = Sets.union(context.original().keySet(), context.absentKeys()); - var keys = Mockito.mock(Collection.class); - when(keys.iterator()).thenReturn(delegate.iterator()); - - assertThat(cache.asMap().keySet().removeAll(keys)).isTrue(); - verify(keys).iterator(); - verifyNoMoreInteractions(keys); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, implementation = Implementation.Caffeine) - public void keySet_removeAll_bySet(AsyncCache cache, CacheContext context) { - var delegate = Sets.union(context.original().keySet(), context.absentKeys()); - var keys = Mockito.mock(Set.class); - when(keys.size()).thenReturn(delegate.size()); - when(keys.contains(any())).thenAnswer(invocation -> - delegate.contains(invocation.getArgument(0))); - assertThat(cache.asMap().keySet().removeAll(keys)).isTrue(); - verify(keys).size(); - verify(keys, times(context.original().size())).contains(any()); - verifyNoMoreInteractions(keys); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySet_remove_null(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().remove(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_remove_none(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().keySet().remove(context.absentKey())).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_remove(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().keySet().remove(context.firstKey())).isTrue(); - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(cache.synchronous().asMap()).isEqualTo(expected); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySet_removeIf_null(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().removeIf(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeIf_none(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().keySet().removeIf(v -> false)).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeIf_partial(AsyncCache cache, CacheContext context) { - Predicate isEven = key -> (key.intValue() % 2) == 0; - boolean hasEven = cache.asMap().keySet().stream().anyMatch(isEven); - - boolean removedIfEven = cache.asMap().keySet().removeIf(isEven); - assertThat(cache.asMap().keySet().stream().anyMatch(isEven)).isFalse(); - assertThat(removedIfEven).isEqualTo(hasEven); - if (removedIfEven) { - assertThat(cache).hasSizeLessThan(context.initialSize()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_removeIf_all(AsyncCache cache, CacheContext context) { - if (context.population() == Population.EMPTY) { - assertThat(cache.asMap().keySet().removeIf(v -> true)).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(cache.asMap().keySet().removeIf(v -> true)).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySet_retainAll_null(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().retainAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_retainAll_nullKey(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().retainAll(Arrays.asList((Object) null)); - assertThat(cache).isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_retainAll_none_empty(AsyncCache cache, CacheContext context) { - boolean modified = cache.asMap().keySet().retainAll(Set.of()); - assertThat(cache).isEmpty(); - if (context.original().isEmpty()) { - assertThat(modified).isFalse(); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(modified).isTrue(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_retainAll_none_populated(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().keySet().retainAll(Set.of(context.absentKey()))).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_retainAll_partial(AsyncCache cache, CacheContext context) { - var expected = new HashMap<>(cache.asMap()); - expected.keySet().removeAll(context.firstMiddleLastKeys()); - - assertThat(cache.asMap().keySet().retainAll(expected.keySet())).isTrue(); - assertThat(cache.asMap()).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_retainAll_all(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().keySet().retainAll(context.original().keySet())).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void keySet_retainAll_self(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().keySet().retainAll(cache.asMap().keySet())).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet(AsyncCache cache, CacheContext context) { - var keys = cache.asMap().keySet(); - assertThat(keys).doesNotContain(new Object()); - assertThat(keys.remove(new Object())).isFalse(); - assertThat(keys).hasSize(context.initialSize()); - for (Int key : Set.copyOf(keys)) { - assertThat(keys).contains(key); - assertThat(keys.remove(key)).isTrue(); - assertThat(keys.remove(key)).isFalse(); - assertThat(keys).doesNotContain(key); - } - assertThat(cache).isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySet_iterator(AsyncCache cache, CacheContext context) { - int count = 0; - for (var i = cache.asMap().keySet().iterator(); i.hasNext(); ) { - assertThat(cache).containsKey(i.next()); - count++; - i.remove(); - } - assertThat(cache).isEmpty(); - assertThat(count).isEqualTo(context.initialSize()); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void keyIterator_noElement(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().iterator().remove(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NoSuchElementException.class) - public void keyIterator_noMoreElements(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().iterator().next(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySpliterator_forEachRemaining_null( - AsyncCache cache, CacheContext context) { - cache.asMap().keySet().spliterator().forEachRemaining(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySpliterator_forEachRemaining(AsyncCache cache, CacheContext context) { - int[] count = new int[1]; - cache.asMap().keySet().spliterator().forEachRemaining(key -> count[0]++); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void keySpliterator_tryAdvance_null(AsyncCache cache, CacheContext context) { - cache.asMap().keySet().spliterator().tryAdvance(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySpliterator_tryAdvance(AsyncCache cache, CacheContext context) { - var spliterator = cache.asMap().keySet().spliterator(); - int[] count = new int[1]; - boolean advanced; - do { - advanced = spliterator.tryAdvance(key -> count[0]++); - } while (advanced); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySpliterator_trySplit(AsyncCache cache, CacheContext context) { - var spliterator = cache.asMap().keySet().spliterator(); - var other = firstNonNull(spliterator.trySplit(), Spliterators.emptySpliterator()); - - int[] count = new int[1]; - spliterator.forEachRemaining(key -> count[0]++); - other.forEachRemaining(key -> count[0]++); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void keySpliterator_estimateSize(AsyncCache cache, CacheContext context) { - var spliterator = cache.asMap().keySet().spliterator(); - assertThat(spliterator.estimateSize()).isEqualTo(context.initialSize()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void values_toArray_null(AsyncCache cache, CacheContext context) { - cache.asMap().values().toArray((CompletableFuture[]) null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void values_toArray(AsyncCache cache, CacheContext context) { - var futures = cache.asMap().values().toArray(new CompletableFuture[0]); - var values1 = Stream.of(futures).map(CompletableFuture::join).collect(toImmutableList()); - assertThat(values1).containsExactlyElementsIn(context.original().values()); - var array = cache.asMap().values().toArray(new CompletableFuture[0]); - var values2 = Stream.of(array).map(CompletableFuture::join).collect(toImmutableList()); - assertThat(values2).containsExactlyElementsIn(context.original().values()); - var func = cache.asMap().values().toArray(CompletableFuture[]::new); - var values3 = Stream.of(func).map(CompletableFuture::join).collect(toImmutableList()); - assertThat(values3).containsExactlyElementsIn(context.original().values()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void values_contains_absent(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().containsValue(context.absentValue().asFuture())).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void values_contains_present(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().containsValue(cache.asMap().get(context.firstKey()))).isTrue(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void values_empty(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values()).isExhaustivelyEmpty(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void values_addNotSupported(AsyncCache cache, CacheContext context) { - cache.asMap().values().add(Int.valueOf(1).asFuture()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_clear(AsyncCache cache, CacheContext context) { - cache.asMap().values().clear(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void values_removeAll_null(AsyncCache cache, CacheContext context) { - cache.asMap().values().removeAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeAll_nullValue(AsyncCache cache, CacheContext context) { - cache.asMap().values().removeAll(Collections.singletonList(null)); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeAll_none_empty(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().removeAll(Set.of())).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeAll_none_populated(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().removeAll(Set.of(context.absentValue().asFuture()))).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_removeAll_partial(AsyncCache cache, CacheContext context) { - var expected = new HashMap<>(context.original()); - expected.keySet().removeAll(context.firstMiddleLastKeys()); - var removed = Maps.asMap(context.firstMiddleLastKeys(), cache.asMap()::get); - assertThat(cache.asMap().values().removeAll(removed.values())).isTrue(); - assertThat(cache.synchronous().asMap()).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_removeAll_all(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().removeAll(List.copyOf(cache.asMap().values()))).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_removeAll_self(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().removeAll(cache.asMap().values())).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_remove_null(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().remove(null)).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_remove_none(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().remove(context.absentValue().asFuture())).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_remove(AsyncCache cache, CacheContext context) { - var future = cache.asMap().get(context.firstKey()); - assertThat(cache.asMap().values().remove(future)).isTrue(); - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(cache.synchronous().asMap()).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.firstKey(), future.join()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_remove_once(AsyncCache cache, CacheContext context) { - var expected = new HashMap<>(context.original()); - var future = context.absentValue().asFuture(); - context.firstMiddleLastKeys().forEach(key -> { - expected.put(key, context.absentValue()); - cache.put(key, future); - }); - context.clearRemovalNotifications(); - assertThat(cache.asMap().values().remove(future)).isTrue(); - var removedKey = context.firstMiddleLastKeys().stream().filter(key -> !cache.asMap().containsKey(key)).findAny().orElseThrow(); - expected.remove(removedKey); - assertThat(cache.synchronous().asMap()).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(removedKey, context.absentValue()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void values_removeIf_null(AsyncCache cache, CacheContext context) { - cache.asMap().values().removeIf(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeIf_none(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().removeIf(v -> false)).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeIf_partial(AsyncCache cache, CacheContext context) { - Predicate> isEven = value -> (value.join().intValue() % 2) == 0; - boolean hasEven = cache.asMap().values().stream().anyMatch(isEven); - boolean removedIfEven = cache.asMap().values().removeIf(isEven); - assertThat(cache.asMap().values().stream().anyMatch(isEven)).isFalse(); - assertThat(removedIfEven).isEqualTo(hasEven); - if (removedIfEven) { - assertThat(cache).hasSizeLessThan(context.original().size()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeIf_all(AsyncCache cache, CacheContext context) { - if (context.population() == Population.EMPTY) { - assertThat(cache.asMap().values().removeIf(v -> true)).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(cache.asMap().values().removeIf(v -> true)).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_removeIf(AsyncCache cache, CacheContext context) { - Predicate> isEven = value -> (value.join().intValue() % 2) == 0; - boolean hasEven = cache.asMap().values().stream().anyMatch(isEven); - boolean removedIfEven = cache.asMap().values().removeIf(isEven); - assertThat(cache.asMap().values().stream().anyMatch(isEven)).isFalse(); - assertThat(removedIfEven).isEqualTo(hasEven); - if (removedIfEven) { - assertThat(cache).hasSizeLessThan(context.initialSize()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void values_retainAll_null(AsyncCache cache, CacheContext context) { - cache.asMap().values().retainAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_retainAll_nullValue(AsyncCache cache, CacheContext context) { - cache.asMap().values().retainAll(Arrays.asList((Object) null)); - assertThat(cache).isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values_retainAll_none_empty(AsyncCache cache, CacheContext context) { - boolean modified = cache.asMap().values().retainAll(Set.of()); - assertThat(cache).isEmpty(); - if (context.original().isEmpty()) { - assertThat(modified).isFalse(); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(modified).isTrue(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_retainAll_none_populated(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().retainAll(Set.of(context.absentValue().asFuture()))).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_retainAll_partial(AsyncCache cache, CacheContext context) { - var expected = new HashMap<>(cache.asMap()); - expected.keySet().removeAll(context.firstMiddleLastKeys()); - assertThat(cache.asMap().values().retainAll(expected.values())).isTrue(); - assertThat(cache.asMap()).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_retainAll_all(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().retainAll(List.copyOf(cache.asMap().values()))).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void values_retainAll_self(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().retainAll(cache.asMap().values())).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void values(AsyncCache cache, CacheContext context) { - var values = cache.asMap().values(); - assertThat(values).doesNotContain(new Object()); - assertThat(values.remove(new Object())).isFalse(); - assertThat(values).hasSize(context.initialSize()); - List.copyOf(values).forEach(value -> { - assertThat(values).contains(value); - assertThat(values.remove(value)).isTrue(); - assertThat(values.remove(value)).isFalse(); - assertThat(values).doesNotContain(value); - }); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void valueIterator(AsyncCache cache, CacheContext context) { - int count = 0; - for (var i = cache.asMap().values().iterator(); i.hasNext(); ) { - assertThat(cache).containsValue(i.next()); - count++; - i.remove(); - } - assertThat(cache).isEmpty(); - assertThat(count).isEqualTo(context.initialSize()); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void valueIterator_noElement(AsyncCache cache, CacheContext context) { - cache.asMap().values().iterator().remove(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NoSuchElementException.class) - public void valueIterator_noMoreElements(AsyncCache cache, CacheContext context) { - cache.asMap().values().iterator().next(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void valueSpliterator_forEachRemaining_null(AsyncCache cache, CacheContext context) { - cache.asMap().values().spliterator().forEachRemaining(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void valueSpliterator_forEachRemaining(AsyncCache cache, CacheContext context) { - int[] count = new int[1]; - cache.asMap().values().spliterator().forEachRemaining(value -> count[0]++); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void valueSpliterator_tryAdvance_null(AsyncCache cache, CacheContext context) { - cache.asMap().values().spliterator().tryAdvance(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void valueSpliterator_tryAdvance(AsyncCache cache, CacheContext context) { - var spliterator = cache.asMap().values().spliterator(); - int[] count = new int[1]; - boolean advanced; - do { - advanced = spliterator.tryAdvance(value -> count[0]++); - } while (advanced); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void valueSpliterator_trySplit(AsyncCache cache, CacheContext context) { - var spliterator = cache.asMap().values().spliterator(); - var other = firstNonNull(spliterator.trySplit(), Spliterators.emptySpliterator()); - int[] count = new int[1]; - spliterator.forEachRemaining(value -> count[0]++); - other.forEachRemaining(value -> count[0]++); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void valueSpliterator_estimateSize(AsyncCache cache, CacheContext context) { - var spliterator = cache.asMap().values().spliterator(); - assertThat(spliterator.estimateSize()).isEqualTo(context.initialSize()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySet_toArray_null(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().toArray((Map.Entry[]) null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_toArray(AsyncCache cache, CacheContext context) { - @SuppressWarnings("unchecked") - var entries = (Map.Entry>[]) - cache.asMap().entrySet().toArray(new Map.Entry[0]); - assertThat(entries).hasLength(context.original().size()); - Arrays.stream(entries).forEach(entry -> assertThat(entry.getValue()).succeedsWith(context.original().get(entry.getKey()))); - var array = cache.asMap().entrySet().toArray(); - assertThat(array).hasLength(context.original().size()); - Arrays.stream(array).map(item -> (Map.Entry>) item) - .forEach(entry -> assertThat(entry.getValue()).succeedsWith(context.original().get(entry.getKey()))); - @SuppressWarnings("unchecked") - var func = (Map.Entry>[]) cache.asMap().entrySet().toArray(Map.Entry[]::new); - assertThat(func).hasLength(context.original().size()); - Arrays.stream(entries) - .forEach(entry -> assertThat(entry.getValue()) - .succeedsWith(context.original().get(entry.getKey()))); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_empty(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().entrySet()).isExhaustivelyEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_contains_nullKey(AsyncCache cache, CacheContext context) { - var entry = new AbstractMap.SimpleEntry<>(null, cache.asMap().get(context.firstKey())); - assertThat(cache.asMap().entrySet().contains(entry)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_contains_nullValue(AsyncCache cache, CacheContext context) { - var entry = new AbstractMap.SimpleEntry<>(context.firstKey(), null); - assertThat(cache.asMap().entrySet().contains(entry)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_contains_absent(AsyncCache cache, CacheContext context) { - var entry = Map.entry(context.absentKey(), context.absentValue().asFuture()); - assertThat(cache.asMap().entrySet().contains(entry)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_contains_present(AsyncCache cache, CacheContext context) { - var entry = Map.entry(context.firstKey(), cache.asMap().get(context.firstKey())); - assertThat(cache.asMap().entrySet().contains(entry)).isTrue(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = Listener.DISABLED) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void entrySet_addIsNotSupported(AsyncCache cache, CacheContext context) { - try { - cache.asMap().entrySet().add(Map.entry(Int.valueOf(1), Int.valueOf(2).asFuture())); - } finally { - assertThat(cache).isEmpty(); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_clear(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().clear(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySet_removeAll_null(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().removeAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeAll_nullEntry(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().removeAll(Arrays.asList((Object) null)); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeAll_none_empty(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().entrySet().removeAll(Set.of())).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeAll_none_populated(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().entrySet().removeAll(Set.of(Map.entry(context.absentKey(), context.absentKey().asFuture())))).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_removeAll_partial(AsyncCache cache, CacheContext context) { - var removed = Maps.asMap(context.firstMiddleLastKeys(), cache.asMap()::get); - var expected = new HashMap<>(context.original()); - expected.keySet().removeAll(removed.keySet()); - assertThat(cache.asMap().entrySet().removeAll(removed.entrySet())).isTrue(); - assertThat(cache.synchronous().asMap()).isEqualTo(expected); - assertThat(context).removalNotifications() - .withCause(EXPLICIT).contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_removeAll_all(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().entrySet().removeAll(Map.copyOf(cache.asMap()).entrySet())).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_removeAll_self(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().entrySet().removeAll(cache.asMap().entrySet())).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, implementation = Implementation.Caffeine) - public void entrySet_removeAll_byCollection(AsyncCache cache, CacheContext context) { - var delegate = Sets.union(cache.asMap().entrySet(), Maps.transformValues(context.absent(), Int::asFuture).entrySet()); - var entries = Mockito.mock(Collection.class); - when(entries.iterator()).thenReturn(delegate.iterator()); - assertThat(cache.asMap().entrySet().removeAll(entries)).isTrue(); - verify(entries).iterator(); - verifyNoMoreInteractions(entries); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, implementation = Implementation.Caffeine) - public void entrySet_removeAll_bySet(AsyncCache cache, CacheContext context) { - var delegate = Sets.union(cache.asMap().entrySet(), - Maps.transformValues(context.absent(), Int::asFuture).entrySet()); - var entries = Mockito.mock(Set.class); - when(entries.size()).thenReturn(delegate.size()); - when(entries.contains(any())).thenAnswer(invocation -> delegate.contains(invocation.getArgument(0))); - assertThat(cache.asMap().entrySet().removeAll(entries)).isTrue(); - verify(entries).size(); - verify(entries, times(context.original().size())).contains(any()); - verifyNoMoreInteractions(entries); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_remove_null(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().remove(null)).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_remove_nullKey(AsyncCache cache, CacheContext context) { - var future = Iterables.getFirst(cache.asMap().values(), context.absentValue().asFuture()); - assertThat(cache.asMap().entrySet().remove(Maps.immutableEntry(null, future))).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_remove_nullValue(AsyncCache cache, CacheContext context) { - var key = Iterables.getFirst(context.original().keySet(), context.absentKey()); - assertThat(cache.asMap().entrySet().remove(Maps.immutableEntry(key, null))).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_remove_nullKeyValue(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().entrySet().remove(Maps.immutableEntry(null, null))).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_remove_none(AsyncCache cache, CacheContext context) { - var entry = Map.entry(context.absentKey(), context.absentValue().asFuture()); - assertThat(cache.asMap().entrySet().remove(entry)).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_remove(AsyncCache cache, CacheContext context) { - var entry = Map.entry(context.firstKey(), cache.asMap().get(context.firstKey())); - assertThat(cache.asMap().entrySet().remove(entry)).isTrue(); - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(cache.synchronous().asMap()).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.firstKey(), context.original().get(context.firstKey())).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySet_removeIf_null(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().removeIf(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeIf_none(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().entrySet().removeIf(v -> false)).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeIf_partial(AsyncCache cache, CacheContext context) { - Predicate>> isEven = entry -> (entry.getValue().join().intValue() % 2) == 0; - boolean hasEven = cache.asMap().entrySet().stream().anyMatch(isEven); - boolean removedIfEven = cache.asMap().entrySet().removeIf(isEven); - assertThat(cache.asMap().entrySet().stream().anyMatch(isEven)).isFalse(); - assertThat(removedIfEven).isEqualTo(hasEven); - if (removedIfEven) { - assertThat(cache).hasSizeLessThan(context.initialSize()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeIf_all(AsyncCache cache, CacheContext context) { - if (context.population() == Population.EMPTY) { - assertThat(cache.asMap().entrySet().removeIf(v -> true)).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(cache.asMap().entrySet().removeIf(v -> true)).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_removeIf(AsyncCache cache, CacheContext context) { - Predicate>> isEven = entry -> (entry.getValue().join().intValue() % 2) == 0; - boolean hasEven = cache.asMap().entrySet().stream().anyMatch(isEven); - boolean removedIfEven = cache.asMap().entrySet().removeIf(isEven); - assertThat(cache.asMap().entrySet().stream().anyMatch(isEven)).isFalse(); - assertThat(removedIfEven).isEqualTo(hasEven); - if (removedIfEven) { - assertThat(cache).hasSizeLessThan(context.initialSize()); - } - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySet_retainAll_null(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().retainAll(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_retainAll_nullEntry(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().retainAll(Arrays.asList((Object) null)); - assertThat(cache).isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet_retainAll_none_empty(AsyncCache cache, CacheContext context) { - boolean modified = cache.asMap().entrySet().retainAll(Set.of()); - assertThat(cache).isEmpty(); - if (context.original().isEmpty()) { - assertThat(modified).isFalse(); - assertThat(context).removalNotifications().isEmpty(); - } else { - assertThat(modified).isTrue(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_retainAll_none_populated(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().entrySet().retainAll(Set.of(Map.entry(context.absentKey(), context.absentValue().asFuture())))).isTrue(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_retainAll_partial(AsyncCache cache, CacheContext context) { - var expected = new HashMap<>(cache.asMap()); - expected.keySet().removeAll(context.firstMiddleLastKeys()); - assertThat(cache.asMap().entrySet().retainAll(expected.entrySet())).isTrue(); - assertThat(cache.asMap()).isEqualTo(expected); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(Maps.asMap(context.firstMiddleLastKeys(), context.original()::get)).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_retainAll_all(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().entrySet().retainAll(Map.copyOf(cache.asMap()).entrySet())).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL) - public void entrySet_retainAll_self(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().entrySet().retainAll(cache.asMap().entrySet())).isFalse(); - assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySet(AsyncCache cache, CacheContext context) { - var entries = cache.asMap().entrySet(); - assertThat(entries).doesNotContain(new Object()); - assertThat(entries.remove(new Object())).isFalse(); - assertThat(entries).hasSize(context.initialSize()); - entries.forEach(entry -> { - assertThat(entries).contains(entry); - assertThat(entries.remove(entry)).isTrue(); - assertThat(entries.remove(entry)).isFalse(); - assertThat(entries).doesNotContain(entry); - }); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entryIterator(AsyncCache cache, CacheContext context) { - var i = cache.asMap().entrySet().iterator(); - int iterations = 0; - while (i.hasNext()) { - var entry = i.next(); - assertThat(cache).containsEntry(entry.getKey(), entry.getValue()); - iterations++; - i.remove(); - } - int count = iterations; - assertThat(cache).isEmpty(); - assertThat(count).isEqualTo(context.initialSize()); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.original()).exclusively(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void entryIterator_noElement(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().iterator().remove(); - } - - @CheckNoStats - @CacheSpec(population = Population.EMPTY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NoSuchElementException.class) - public void entryIterator_noMoreElements(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().iterator().next(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySpliterator_forEachRemaining_null(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().spliterator().forEachRemaining(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySpliterator_forEachRemaining(AsyncCache cache, CacheContext context) { - int[] count = new int[1]; - cache.asMap().entrySet().spliterator().forEachRemaining(entry -> { - if (context.isCaffeine()) { - assertThat(entry).isInstanceOf(WriteThroughEntry.class); - } - count[0]++; - assertThat(entry.getValue()).succeedsWith(context.original().get(entry.getKey())); - }); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void entrySpliterator_tryAdvance_null(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().spliterator().tryAdvance(null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySpliterator_tryAdvance(AsyncCache cache, CacheContext context) { - var spliterator = cache.asMap().entrySet().spliterator(); - int[] count = new int[1]; - boolean advanced; - do { - advanced = spliterator.tryAdvance(entry -> { - if (context.isCaffeine()) { - assertThat(entry).isInstanceOf(WriteThroughEntry.class); - } - count[0]++; - }); - } while (advanced); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySpliterator_trySplit(AsyncCache cache, CacheContext context) { - var spliterator = cache.asMap().entrySet().spliterator(); - var other = firstNonNull(spliterator.trySplit(), Spliterators.emptySpliterator()); - int[] count = new int[1]; - spliterator.forEachRemaining(entry -> count[0]++); - other.forEachRemaining(entry -> count[0]++); - assertThat(count[0]).isEqualTo(context.initialSize()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void entrySpliterator_estimateSize(AsyncCache cache, CacheContext context) { - var spliterator = cache.asMap().entrySet().spliterator(); - assertThat(spliterator.estimateSize()).isEqualTo(context.initialSize()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void writeThroughEntry(AsyncCache cache, CacheContext context) { - var entry = cache.asMap().entrySet().iterator().next(); - var oldValue = entry.getValue().join(); - var value = Int.valueOf(3).asFuture(); - entry.setValue(value); - assertThat(cache).hasSize(context.initialSize()); - assertThat(cache).containsEntry(entry.getKey(), value); - assertThat(context).removalNotifications().withCause(REPLACED).contains(entry.getKey(), oldValue).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void writeThroughEntry_null(AsyncCache cache, CacheContext context) { - cache.asMap().entrySet().iterator().next().setValue(null); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncCacheTest.java deleted file mode 100644 index 2483c0d..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncCacheTest.java +++ /dev/null @@ -1,928 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.LocalAsyncCache.AsyncBulkCompleter.NullMapCompletionException; -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExecutor; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.ExecutorFailure; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.Listener; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.Population; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.primitives.Ints; -import org.testng.Assert; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.Executor; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.stream.IntStream; - -import static com.github.benmanes.caffeine.cache.RemovalCause.EXPLICIT; -import static com.github.benmanes.caffeine.cache.RemovalCause.REPLACED; -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.CollectionSubject.assertThat; -import static com.github.benmanes.caffeine.testing.FutureSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.truth.Truth.assertThat; -import static java.util.function.Function.identity; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static uk.org.lidalia.slf4jext.Level.WARN; - -@CheckNoEvictions -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@Test(dataProviderClass = CacheProvider.class) -@SuppressWarnings("FutureReturnValueIgnored") -public final class AsyncCacheTest { - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getIfPresent_nullKey(AsyncCache cache, CacheContext context) { - cache.getIfPresent(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getIfPresent_absent(AsyncCache cache, CacheContext context) { - assertThat(cache.getIfPresent(context.absentKey())).isNull(); - assertThat(context).stats().hits(0).misses(1).success(0).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void getIfPresent_present(AsyncCache cache, CacheContext context) { - assertThat(cache.getIfPresent(context.firstKey())).isNotNull(); - assertThat(cache.getIfPresent(context.middleKey())).isNotNull(); - assertThat(cache.getIfPresent(context.lastKey())).isNotNull(); - assertThat(context).stats().hits(3).misses(0).success(0).failures(0); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getFunc_nullKey(AsyncCache cache, CacheContext context) { - cache.get(null, key -> null); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getFunc_nullLoader(AsyncCache cache, CacheContext context) { - cache.get(context.absentKey(), (Function) null); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getFunc_nullKeyAndLoader(AsyncCache cache, CacheContext context) { - cache.get(null, (Function) null); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getFunc_absent_null(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - var valueFuture = cache.get(key, k -> null); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - assertThat(valueFuture).isDone(); - assertThat(cache).doesNotContainKey(key); - } - - @Test(dataProvider = "caches") - @CacheSpec(executor = CacheExecutor.THREADED, executorFailure = ExecutorFailure.IGNORED) - public void getFunc_absent_null_async(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - var ready = new AtomicBoolean(); - var done = new AtomicBoolean(); - var valueFuture = cache.get(key, k -> { - await().untilTrue(ready); - return null; - }); - valueFuture.whenComplete((r, e) -> done.set(true)); - ready.set(true); - await().untilTrue(done); - await().untilAsserted(() -> assertThat(cache).doesNotContainKey(context.absentKey())); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - - assertThat(valueFuture).isDone(); - assertThat(cache).doesNotContainKey(key); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getFunc_absent_failure(AsyncCache cache, CacheContext context) { - var valueFuture = cache.get(context.absentKey(), k -> { - throw new IllegalStateException(); - }); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - assertThat(valueFuture).hasCompletedExceptionally(); - assertThat(cache).doesNotContainKey(context.absentKey()); - } - - @Test(dataProvider = "caches") - @CacheSpec(executor = CacheExecutor.THREADED, executorFailure = ExecutorFailure.IGNORED) - public void getFunc_absent_failure_async(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - var done = new AtomicBoolean(); - var valueFuture = cache.get(key, k -> { - throw new IllegalStateException(); - }); - valueFuture.whenComplete((r, e) -> done.set(true)); - await().untilTrue(done); - await().untilAsserted(() -> assertThat(cache).doesNotContainKey(key)); - await().untilAsserted(() -> assertThat(cache).hasSize(context.initialSize())); - assertThat(valueFuture).hasCompletedExceptionally(); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(executor = CacheExecutor.THREADED, executorFailure = ExecutorFailure.IGNORED) - public void getFunc_absent_cancelled(AsyncCache cache, CacheContext context) { - var done = new AtomicBoolean(); - var valueFuture = cache.get(context.absentKey(), k -> { - await().until(done::get); - return null; - }); - valueFuture.whenComplete((r, e) -> done.set(true)); - valueFuture.cancel(true); - await().untilTrue(done); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - assertThat(valueFuture).isDone(); - await().untilAsserted(() -> assertThat(cache).doesNotContainKey(context.absentKey())); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getFunc_absent(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - var value = cache.get(key, k -> context.absentValue()); - assertThat(value).succeedsWith(context.absentValue()); - assertThat(context).stats().hits(0).misses(1).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void getFunc_present(AsyncCache cache, CacheContext context) { - Function loader = key -> { - throw new RuntimeException(); - }; - assertThat(cache.get(context.firstKey(), loader)) - .succeedsWith(context.original().get(context.firstKey())); - assertThat(cache.get(context.middleKey(), loader)) - .succeedsWith(context.original().get(context.middleKey())); - assertThat(cache.get(context.lastKey(), loader)) - .succeedsWith(context.original().get(context.lastKey())); - assertThat(context).stats().hits(3).misses(0).success(0).failures(0); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getBiFunc_nullKey(AsyncCache cache, CacheContext context) { - cache.get(null, (key, executor) -> CompletableFuture.completedFuture(null)); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getBiFunc_nullLoader(AsyncCache cache, CacheContext context) { - BiFunction> mappingFunction = null; - cache.get(context.absentKey(), mappingFunction); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getBiFunc_nullKeyAndLoader(AsyncCache cache, CacheContext context) { - BiFunction> mappingFunction = null; - cache.get(null, mappingFunction); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void getBiFunc_throwsException(AsyncCache cache, CacheContext context) { - try { - cache.get(context.absentKey(), (key, executor) -> { - throw new IllegalStateException(); - }); - } finally { - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - assertThat(cache).doesNotContainKey(context.absentKey()); - } - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = UnknownError.class) - public void getBiFunc_throwsError(AsyncCache cache, CacheContext context) { - try { - cache.get(context.absentKey(), (key, executor) -> { - throw new UnknownError(); - }); - } finally { - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - assertThat(cache).doesNotContainKey(context.absentKey()); - } - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getBiFunc_absent_null(AsyncCache cache, CacheContext context) { - cache.get(context.absentKey(), (k, executor) -> null); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getBiFunc_absent_failure_before(AsyncCache cache, CacheContext context) { - var failedFuture = new CompletableFuture(); - failedFuture.completeExceptionally(new IllegalStateException()); - - Int key = context.absentKey(); - var valueFuture = cache.get(key, (k, executor) -> failedFuture); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - - assertThat(valueFuture).hasCompletedExceptionally(); - assertThat(cache).doesNotContainKey(key); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getBiFunc_absent_failure_after(AsyncCache cache, CacheContext context) { - var failedFuture = new CompletableFuture(); - - Int key = context.absentKey(); - var valueFuture = cache.get(key, (k, executor) -> failedFuture); - failedFuture.completeExceptionally(new IllegalStateException()); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - - assertThat(valueFuture).hasCompletedExceptionally(); - assertThat(cache).doesNotContainKey(key); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getBiFunc_absent_cancelled(AsyncCache cache, CacheContext context) { - var cancelledFuture = new CompletableFuture(); - cache.get(context.absentKey(), (k, executor) -> cancelledFuture); - cancelledFuture.cancel(true); - - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - assertThat(cache).doesNotContainKey(context.absentKey()); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getBiFunc_absent(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - var value = cache.get(key, (k, executor) -> context.absentValue().asFuture()); - assertThat(value).succeedsWith(context.absentValue()); - assertThat(context).stats().hits(0).misses(1).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void getBiFunc_present(AsyncCache cache, CacheContext context) { - BiFunction> loader = - (key, executor) -> { - throw new RuntimeException(); - }; - assertThat(cache.get(context.firstKey(), loader)) - .succeedsWith(context.original().get(context.firstKey())); - assertThat(cache.get(context.middleKey(), loader)) - .succeedsWith(context.original().get(context.middleKey())); - assertThat(cache.get(context.lastKey(), loader)) - .succeedsWith(context.original().get(context.lastKey())); - assertThat(context).stats().hits(3).misses(0).success(0).failures(0); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_nullKeys(AsyncCache cache, CacheContext context) { - cache.getAll(null, keys -> { - throw new AssertionError(); - }); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_nullKeys_nullFunction( - AsyncCache cache, CacheContext context) { - cache.getAll(null, (Function, Map>) null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_nullFunction(AsyncCache cache, CacheContext context) { - cache.getAll(context.original().keySet(), (Function, Map>) null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_nullKey(AsyncCache cache, CacheContext context) { - cache.getAll(Collections.singletonList(null), keys -> { - throw new AssertionError(); - }); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_iterable_empty(AsyncCache cache, CacheContext context) { - var result = cache.getAll(List.of(), keys -> { - throw new AssertionError(); - }).join(); - assertThat(result).isExhaustivelyEmpty(); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getAllFunction_immutable_keys(AsyncCache cache, CacheContext context) { - var future = cache.getAll(context.absentKeys(), keys -> { - keys.clear(); - return Map.of(); - }); - assertThat(future).failsWith(CompletionException.class) - .hasCauseThat().isInstanceOf(UnsupportedOperationException.class); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void getAllFunction_immutable_result(AsyncCache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys(), - keys -> keys.stream().collect(toImmutableMap(identity(), identity()))).join(); - result.clear(); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getAllFunction_absent_null(AsyncCache cache, CacheContext context) { - assertThat(cache.getAll(context.absentKeys(), keys -> null)) - .failsWith(NullMapCompletionException.class); - assertThat(context).stats().hits(0).misses(context.absentKeys().size()).success(0).failures(1); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getAllFunction_absent_failure(AsyncCache cache, CacheContext context) { - assertThat(cache.getAll(context.absentKeys(), keys -> { - throw new IllegalStateException(); - })) - .failsWith(CompletionException.class) - .hasCauseThat().isInstanceOf(IllegalStateException.class); - int misses = context.absentKeys().size(); - assertThat(context).stats().hits(0).misses(misses).success(0).failures(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_absent(AsyncCache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys(), keys -> context.absent()).join(); - int count = context.absentKeys().size(); - assertThat(result).hasSize(count); - assertThat(context).stats().hits(0).misses(count).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_present_partial(AsyncCache cache, CacheContext context) { - var expect = new HashMap(); - expect.put(context.firstKey(), context.firstKey().negate()); - expect.put(context.middleKey(), context.middleKey().negate()); - expect.put(context.lastKey(), context.lastKey().negate()); - var result = cache.getAll(expect.keySet(), keys -> { - assertThat(keys).hasSizeLessThan(expect.size()); - return keys.stream().collect(toImmutableMap(identity(), Int::negate)); - }).join(); - - assertThat(result).isEqualTo(expect); - assertThat(context).stats().hits(expect.size()).misses(0).success(0).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_exceeds(AsyncCache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys(), keys -> { - var moreKeys = new ArrayList(keys); - for (int i = 0; i < 10; i++) { - moreKeys.add(Int.valueOf(ThreadLocalRandom.current().nextInt())); - } - return moreKeys.stream().collect(toImmutableMap(identity(), Int::negate)); - }).join(); - - assertThat(result).containsExactlyKeys(context.absentKeys()); - assertThat(cache).hasSizeGreaterThan(context.initialSize() + context.absentKeys().size()); - assertThat(context).stats().hits(0).misses(result.size()).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_different(AsyncCache cache, CacheContext context) { - var actual = context.absentKeys().stream().collect(toImmutableMap(Int::negate, identity())); - var result = cache.getAll(context.absentKeys(), keys -> actual).join(); - - assertThat(result).isEmpty(); - assertThat(cache).hasSize(context.initialSize() + actual.size()); - assertThat(cache.synchronous().asMap()).containsAtLeastEntriesIn(actual); - assertThat(context).stats().hits(0).misses(actual.size()).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_duplicates(AsyncCache cache, CacheContext context) { - var absentKeys = ImmutableSet.copyOf(Iterables.limit(context.absentKeys(), - Ints.saturatedCast(context.maximum().max() - context.initialSize()))); - var keys = Iterables.concat(absentKeys, absentKeys, - context.original().keySet(), context.original().keySet()); - var result = cache.getAll(keys, keysToLoad -> { - assertThat(keysToLoad).containsNoDuplicates(); - return keysToLoad.stream().collect(toImmutableMap(identity(), Int::negate)); - }).join(); - - assertThat(context).stats().hits(context.initialSize()) - .misses(absentKeys.size()).success(1).failures(0); - assertThat(result).containsExactlyKeys(keys); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_present_ordered_absent( - AsyncCache cache, CacheContext context) { - var keys = new ArrayList<>(context.absentKeys()); - Collections.shuffle(keys); - var result = cache.getAll(keys, keysToLoad -> keysToLoad.stream().collect(toImmutableMap(identity(), Int::negate))).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_present_ordered_partial( - AsyncCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - keys.addAll(context.absentKeys()); - Collections.shuffle(keys); - var result = cache.getAll(keys, keysToLoad -> keysToLoad.stream().collect(toImmutableMap(identity(), Int::negate))).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_present_ordered_present( - AsyncCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - Collections.shuffle(keys); - var result = cache.getAll(keys, keysToLoad -> { - throw new AssertionError(); - }).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_present_ordered_exceeds( - AsyncCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - keys.addAll(context.absentKeys()); - Collections.shuffle(keys); - var result = cache.getAll(keys, keysToLoad -> { - var moreKeys = new ArrayList(keysToLoad); - for (int i = 0; i < 10; i++) { - moreKeys.add(Int.valueOf(ThreadLocalRandom.current().nextInt())); - } - return moreKeys.stream().collect(toImmutableMap(identity(), Int::negate)); - }).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllFunction_badLoader(AsyncCache cache, CacheContext context) { - assertThat(cache.getAll(context.absentKeys(), keysToLoad -> { - throw new LoadAllException(); - })) - .failsWith(CompletionException.class).hasCauseThat().isInstanceOf(LoadAllException.class); - assertThat(cache).hasSize(context.initialSize()); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_nullKeys(AsyncCache cache, CacheContext context) { - cache.getAll(null, (keys, executor) -> { - throw new AssertionError(); - }); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_nullKeys_nullBifunction( - AsyncCache cache, CacheContext context) { - BiFunction, Executor, CompletableFuture>> f = null; - cache.getAll(null, f); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_nullBifunction(AsyncCache cache, CacheContext context) { - BiFunction, Executor, CompletableFuture>> f = null; - cache.getAll(context.original().keySet(), f); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_nullKey(AsyncCache cache, CacheContext context) { - cache.getAll(Collections.singletonList(null), - (keys, executor) -> { - throw new AssertionError(); - }); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_iterable_empty(AsyncCache cache, CacheContext context) { - var result = cache.getAll(List.of(), - (keys, executor) -> { - throw new AssertionError(); - }).join(); - assertThat(result).isExhaustivelyEmpty(); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void getAllBifunction_immutable_keys(AsyncCache cache, CacheContext context) { - cache.getAll(context.absentKeys(), (keys, executor) -> { - keys.clear(); - return CompletableFuture.completedFuture(Map.of()); - }); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void getAllBifunction_immutable_result(AsyncCache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys(), (keys, executor) -> CompletableFuture.completedFuture( - keys.stream().collect(toImmutableMap(identity(), identity())))).join(); - result.clear(); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getAllBifunction_absent_null(AsyncCache cache, CacheContext context) { - var future = CompletableFuture.completedFuture((Map) null); - assertThat(cache.getAll(context.absentKeys(), (keys, executor) -> future)) - .failsWith(NullMapCompletionException.class); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getAllBifunction_absent_nullValue(AsyncCache cache, CacheContext context) { - cache.getAll(context.absentKeys(), (keys, executor) -> null); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getAllBifunction_absent_failure(AsyncCache cache, CacheContext context) { - var future = cache.getAll(context.absentKeys(), - (keys, executor) -> CompletableFuture.failedFuture(new IllegalStateException())); - assertThat(future).failsWith(CompletionException.class) - .hasCauseThat().isInstanceOf(IllegalStateException.class); - assertThat(context).stats().hits(0).misses(context.absentKeys().size()).success(0).failures(1); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void getAllBifunction_absent_throwsException( - AsyncCache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys(), - (keys, executor) -> { - throw new IllegalStateException(); - }); - } finally { - int misses = context.absentKeys().size(); - assertThat(context).stats().hits(0).misses(misses).success(0).failures(1); - assertThat(cache).doesNotContainKey(context.absentKey()); - } - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = UnknownError.class) - public void getAllBifunction_absent_throwsError(AsyncCache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys(), (keys, executor) -> { - throw new UnknownError(); - }); - } finally { - int misses = context.absentKeys().size(); - assertThat(context).stats().hits(0).misses(misses).success(0).failures(1); - assertThat(cache).doesNotContainKey(context.absentKey()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_absent(AsyncCache cache, CacheContext context) { - var future = cache.getAll(context.absentKeys(), - (keys, executor) -> CompletableFuture.completedFuture(context.absent())); - assertThat(future).succeedsWith(context.absent()); - int count = context.absentKeys().size(); - assertThat(context).stats().hits(0).misses(count).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_present_partial(AsyncCache cache, CacheContext context) { - var expect = new HashMap(); - expect.put(context.firstKey(), context.firstKey().negate()); - expect.put(context.middleKey(), context.middleKey().negate()); - expect.put(context.lastKey(), context.lastKey().negate()); - var result = cache.getAll(expect.keySet(), (keys, executor) -> { - assertThat(keys.size()).isLessThan(expect.size()); - return CompletableFuture.completedFuture(keys.stream().collect(toImmutableMap(identity(), Int::negate))); - }).join(); - assertThat(result).containsExactlyEntriesIn(expect); - assertThat(context).stats().hits(expect.size()).misses(0).success(0).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_exceeds(AsyncCache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys(), (keys, executor) -> { - var moreKeys = new ArrayList(keys); - IntStream.range(0, 10).mapToObj(i -> Int.valueOf(ThreadLocalRandom.current().nextInt())).forEach(moreKeys::add); - return CompletableFuture.completedFuture( - moreKeys.stream().collect(toImmutableMap(identity(), Int::negate))); - }).join(); - assertThat(result).containsExactlyKeys(context.absentKeys()); - assertThat(cache).hasSizeGreaterThan(context.initialSize() + context.absentKeys().size()); - assertThat(context).stats().hits(0).misses(result.size()).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_different(AsyncCache cache, CacheContext context) { - var actual = context.absentKeys().stream().collect(toImmutableMap(Int::negate, identity())); - var result = cache.getAll(context.absentKeys(), (keys, executor) -> CompletableFuture.completedFuture(actual)).join(); - assertThat(result).isEmpty(); - assertThat(cache).hasSize(context.initialSize() + actual.size()); - assertThat(cache.synchronous().asMap()).containsAtLeastEntriesIn(actual); - assertThat(context).stats().hits(0).misses(actual.size()).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_duplicates(AsyncCache cache, CacheContext context) { - var absentKeys = ImmutableSet.copyOf(Iterables.limit(context.absentKeys(), Ints.saturatedCast(context.maximum().max() - context.initialSize()))); - var keys = Iterables.concat(absentKeys, absentKeys, context.original().keySet(), context.original().keySet()); - var result = cache.getAll(keys, (keysToLoad, executor) -> { - assertThat(keysToLoad).containsNoDuplicates(); - return CompletableFuture.completedFuture(keysToLoad.stream().collect(toImmutableMap(identity(), Int::negate))); - }).join(); - assertThat(result).containsExactlyKeys(keys); - assertThat(context).stats().hits(context.initialSize()).misses(absentKeys.size()).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_present_ordered_absent(AsyncCache cache, CacheContext context) { - var keys = new ArrayList<>(context.absentKeys()); - Collections.shuffle(keys); - var result = cache.getAll(keys, (keysToLoad, executor) -> { - assertThat(keysToLoad).containsExactlyElementsIn(context.absentKeys()); - return CompletableFuture.completedFuture(keysToLoad.stream().collect(toImmutableMap(identity(), Int::negate))); - }).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_present_ordered_partial( - AsyncCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - keys.addAll(context.absentKeys()); - Collections.shuffle(keys); - var result = cache.getAll(keys, (keysToLoad, executor) -> { - assertThat(keysToLoad).containsExactlyElementsIn(context.absentKeys()); - return CompletableFuture.completedFuture( - keysToLoad.stream().collect(toImmutableMap(identity(), Int::negate))); - }).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_present_ordered_present(AsyncCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - Collections.shuffle(keys); - var result = cache.getAll(keys, (keysToLoad, executor) -> { - throw new AssertionError(); - }).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_present_ordered_exceeds( - AsyncCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - keys.addAll(context.absentKeys()); - Collections.shuffle(keys); - var result = cache.getAll(keys, (keysToLoad, executor) -> { - var moreKeys = new ArrayList(keysToLoad); - IntStream.range(0, 10).mapToObj(i -> Int.valueOf(ThreadLocalRandom.current().nextInt())).forEach(moreKeys::add); - return CompletableFuture.completedFuture(moreKeys.stream().collect(toImmutableMap(identity(), Int::negate))); - }).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_badLoader(AsyncCache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys(), (keysToLoad, executor) -> { - throw new LoadAllException(); - }); - Assert.fail(); - } catch (LoadAllException e) { - assertThat(cache).hasSize(context.initialSize()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_early_success(AsyncCache cache, CacheContext context) { - var key = context.absentKeys().iterator().next(); - var value = Int.valueOf(Integer.MAX_VALUE); - var bulk = new CompletableFuture>(); - var result = cache.getAll(context.absentKeys(), (keysToLoad, executor) -> bulk); - var future = cache.asMap().get(key); - future.complete(value); - bulk.complete(context.absent()); - assertThat(future).succeedsWith(context.absent().get(key)); - assertThat(result.join()).containsExactlyEntriesIn(context.absent()); - assertThat(cache.synchronous().asMap()).containsAtLeastEntriesIn(context.absent()); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllBifunction_early_failure(AsyncCache cache, CacheContext context) { - var key = context.absentKeys().iterator().next(); - var error = new IllegalStateException(); - var bulk = new CompletableFuture>(); - var result = cache.getAll(context.absentKeys(), (keysToLoad, executor) -> bulk); - var future = cache.asMap().get(key); - future.completeExceptionally(error); - bulk.complete(context.absent()); - assertThat(future).succeedsWith(context.absent().get(key)); - assertThat(cache.synchronous().asMap()).containsAtLeastEntriesIn(context.absent()); - if (result.isCompletedExceptionally()) { - assertThat(result).failsWith(CompletionException.class).hasCauseThat().isSameInstanceAs(error); - } else { - assertThat(result.join()).containsExactlyEntriesIn(context.absent()); - } - } - - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void put_nullKey(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); - cache.put(null, value); - } - - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void put_nullValue(AsyncCache cache, CacheContext context) { - cache.put(context.absentKey(), null); - } - - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void put_nullKeyAndValue(AsyncCache cache, CacheContext context) { - cache.put(null, null); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_insert_failure_before(AsyncCache cache, CacheContext context) { - var failedFuture = CompletableFuture.completedFuture((Int) null); - failedFuture.completeExceptionally(new IllegalStateException()); - cache.put(context.absentKey(), failedFuture); - assertThat(cache).hasSize(context.initialSize()); - assertThat(cache).doesNotContainKey(context.absentKey()); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_insert_failure_after(AsyncCache cache, CacheContext context) { - var failedFuture = new CompletableFuture(); - - cache.put(context.absentKey(), failedFuture); - failedFuture.completeExceptionally(new IllegalStateException()); - assertThat(cache).doesNotContainKey(context.absentKey()); - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_insert(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); - cache.put(context.absentKey(), value); - assertThat(cache).hasSize(context.initialSize() + 1); - assertThat(context).stats().hits(0).misses(0).success(1).failures(0); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_replace_failure_before(AsyncCache cache, CacheContext context) { - var failedFuture = CompletableFuture.completedFuture((Int) null); - failedFuture.completeExceptionally(new IllegalStateException()); - - cache.put(context.middleKey(), failedFuture); - assertThat(cache).hasSize(context.initialSize() - 1); - assertThat(cache).doesNotContainKey(context.absentKey()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_replace_failure_after(AsyncCache cache, CacheContext context) { - var failedFuture = CompletableFuture.completedFuture((Int) null); - - cache.put(context.middleKey(), failedFuture); - failedFuture.completeExceptionally(new IllegalStateException()); - assertThat(cache).doesNotContainKey(context.absentKey()); - assertThat(cache).hasSize(context.initialSize() - 1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace_nullValue(AsyncCache cache, CacheContext context) { - var removed = new HashMap(); - var value = CompletableFuture.completedFuture((Int) null); - for (Int key : context.firstMiddleLastKeys()) { - cache.put(key, value); - assertThat(cache).doesNotContainKey(key); - removed.put(key, context.original().get(key)); - } - int count = context.firstMiddleLastKeys().size(); - assertThat(cache).hasSize(context.initialSize() - count); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(removed).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace_differentValue(AsyncCache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var newValue = context.absentValue().asFuture(); - cache.put(key, newValue); - assertThat(cache).containsEntry(key, newValue); - replaced.put(key, context.original().get(key)); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = Listener.MOCKITO) - public void removalListener_nullValue(AsyncCache cache, CacheContext context) { - var future = new CompletableFuture(); - cache.put(context.absentKey(), future); - future.complete(null); - verify(context.removalListener(), never()).onRemoval(any(Int.class), any(Int.class), any(RemovalCause.class)); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void serialize(AsyncCache cache, CacheContext context) { - assertThat(cache).isReserialize(); - } - - private static final class LoadAllException extends RuntimeException { - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java deleted file mode 100644 index e65e120..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java +++ /dev/null @@ -1,591 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.LocalAsyncCache.AsyncBulkCompleter.NullMapCompletionException; -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.primitives.Ints; -import org.testng.Assert; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.stream.IntStream; - -import static com.github.benmanes.caffeine.cache.RemovalCause.REPLACED; -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.CollectionSubject.assertThat; -import static com.github.benmanes.caffeine.testing.FutureSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.truth.Truth.assertThat; -import static java.util.function.Function.identity; -import static uk.org.lidalia.slf4jext.Level.WARN; - - -@CheckNoEvictions -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@Test(dataProviderClass = CacheProvider.class) -@SuppressWarnings({"FutureReturnValueIgnored", "PreferJavaTimeOverload"}) -public final class AsyncLoadingCacheTest { - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void get_null(AsyncLoadingCache cache, CacheContext context) { - cache.get(null); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void get_absent(AsyncLoadingCache cache, CacheContext context) { - assertThat(cache.get(context.absentKey())).succeedsWith(context.absentValue()); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.EXCEPTIONAL) - public void get_absent_failure(AsyncLoadingCache cache, CacheContext context) { - assertThat(cache.get(context.absentKey())).hasCompletedExceptionally(); - assertThat(cache).doesNotContainKey(context.absentKey()); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.EXCEPTIONAL, executor = CacheExecutor.THREADED, - executorFailure = ExecutorFailure.IGNORED) - public void get_absent_failure_async(AsyncLoadingCache cache, CacheContext context) { - Int key = context.absentKey(); - var done = new AtomicBoolean(); - var valueFuture = cache.get(key).whenComplete((r, e) -> done.set(true)); - await().untilTrue(done); - await().untilAsserted(() -> assertThat(cache).doesNotContainKey(key)); - await().untilAsserted(() -> assertThat(cache).hasSize(context.initialSize())); - assertThat(valueFuture).hasCompletedExceptionally(); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.ASYNC_EXCEPTIONAL) - public void get_absent_throwsException(AsyncLoadingCache cache, CacheContext context) { - try { - cache.get(context.absentKey()).join(); - Assert.fail(); - } catch (IllegalStateException e) { - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.ASYNC_CHECKED_EXCEPTIONAL) - public void get_absent_throwsCheckedException( - AsyncLoadingCache cache, CacheContext context) { - try { - cache.get(context.absentKey()).join(); - Assert.fail(); - } catch (CompletionException e) { - assertThat(e).hasCauseThat().isInstanceOf(ExecutionException.class); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.ASYNC_INTERRUPTED) - public void get_absent_interrupted(AsyncLoadingCache cache, CacheContext context) { - try { - cache.get(context.absentKey()).join(); - Assert.fail(); - } catch (CompletionException e) { - assertThat(Thread.interrupted()).isTrue(); - assertThat(e).hasCauseThat().isInstanceOf(InterruptedException.class); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void get_present(AsyncLoadingCache cache, CacheContext context) { - assertThat(cache.get(context.firstKey())).succeedsWith(context.firstKey().negate()); - assertThat(cache.get(context.middleKey())).succeedsWith(context.middleKey().negate()); - assertThat(cache.get(context.lastKey())).succeedsWith(context.lastKey().negate()); - assertThat(context).stats().hits(3).misses(0).success(0).failures(0); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_iterable_null(AsyncLoadingCache cache, CacheContext context) { - cache.getAll(null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_iterable_nullKey(AsyncLoadingCache cache, CacheContext context) { - cache.getAll(Collections.singletonList(null)); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_iterable_empty(AsyncLoadingCache cache, CacheContext context) { - assertThat(cache.getAll(List.of()).join()).isExhaustivelyEmpty(); - } - - @CacheSpec(loader = Loader.BULK_MODIFY_KEYS) - @Test(dataProvider = "caches") - public void getAll_immutable_keys_loader( - AsyncLoadingCache cache, CacheContext context) { - var future = cache.getAll(context.absentKeys()); - assertThat(future).failsWith(CompletionException.class) - .hasCauseThat().isInstanceOf(UnsupportedOperationException.class); - } - - @CacheSpec(loader = Loader.ASYNC_BULK_MODIFY_KEYS) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void getAll_immutable_keys_asyncLoader( - AsyncLoadingCache cache, CacheContext context) { - cache.getAll(context.absentKeys()); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void getAll_immutable_result(AsyncLoadingCache cache, CacheContext context) { - cache.getAll(context.absentKeys()).join().clear(); - } - - @CacheSpec(loader = Loader.BULK_NULL) - @Test(dataProvider = "caches") - public void getAll_absent_bulkNull(AsyncLoadingCache cache, CacheContext context) { - assertThat(cache.getAll(context.absentKeys())).failsWith(NullMapCompletionException.class); - assertThat(context).stats().hits(0).misses(context.absentKeys().size()).success(0).failures(1); - } - - @CacheSpec(loader = {Loader.EXCEPTIONAL, Loader.BULK_EXCEPTIONAL}) - @Test(dataProvider = "caches") - public void getAll_absent_failure(AsyncLoadingCache cache, CacheContext context) { - assertThat(cache.getAll(context.absentKeys())).failsWith(CompletionException.class); - int misses = context.absentKeys().size(); - int loadFailures = (context.loader().isBulk() || context.isSync()) ? 1 : misses; - assertThat(context).stats().hits(0).misses(misses).success(0).failures(loadFailures); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.ASYNC_EXCEPTIONAL, Loader.ASYNC_BULK_EXCEPTIONAL}) - public void getAll_absent_throwsException( - AsyncLoadingCache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys()).join(); - Assert.fail(); - } catch (IllegalStateException e) { - int misses = context.loader().isBulk() ? context.absentKeys().size() : 1; - assertThat(context).stats().hits(0).misses(misses).success(0).failures(1); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.ASYNC_CHECKED_EXCEPTIONAL, Loader.ASYNC_BULK_CHECKED_EXCEPTIONAL}) - public void getAll_absent_throwsCheckedException( - AsyncLoadingCache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys()).join(); - Assert.fail(); - } catch (CompletionException e) { - assertThat(e).hasCauseThat().isInstanceOf(ExecutionException.class); - int misses = context.loader().isBulk() ? context.absentKeys().size() : 1; - assertThat(context).stats().hits(0).misses(misses).success(0).failures(1); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.ASYNC_INTERRUPTED, Loader.ASYNC_BULK_INTERRUPTED}) - public void getAll_absent_interrupted(AsyncLoadingCache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys()); - Assert.fail(); - } catch (CompletionException e) { - assertThat(Thread.interrupted()).isTrue(); - assertThat(e).hasCauseThat().isInstanceOf(InterruptedException.class); - int misses = context.loader().isBulk() ? context.absentKeys().size() : 1; - assertThat(context).stats().hits(0).misses(misses).success(0).failures(1); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_absent(AsyncLoadingCache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys()).join(); - int count = context.absentKeys().size(); - int loads = context.loader().isBulk() ? 1 : count; - assertThat(result).hasSize(count); - assertThat(context).stats().hits(0).misses(count).success(loads).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_partial(AsyncLoadingCache cache, CacheContext context) { - var expect = new HashMap(); - expect.put(context.firstKey(), context.firstKey().negate()); - expect.put(context.middleKey(), context.middleKey().negate()); - expect.put(context.lastKey(), context.lastKey().negate()); - var result = cache.getAll(expect.keySet()).join(); - assertThat(result).containsExactlyEntriesIn(expect); - assertThat(context).stats().hits(expect.size()).misses(0).success(0).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.BULK_NEGATIVE_EXCEEDS, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_exceeds(AsyncLoadingCache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys()).join(); - assertThat(result.keySet()).containsExactlyElementsIn(context.absentKeys()); - assertThat(cache).hasSizeGreaterThan(context.initialSize() + context.absentKeys().size()); - assertThat(context).stats().hits(0).misses(result.size()).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.BULK_DIFFERENT, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_different(AsyncLoadingCache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys()).join(); - assertThat(result).isEmpty(); - assertThat(cache.asMap()).containsAtLeastEntriesIn(result); - assertThat(context).stats().hits(0).misses(context.absent().size()).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_duplicates(AsyncLoadingCache cache, CacheContext context) { - var absentKeys = ImmutableSet.copyOf(Iterables.limit(context.absentKeys(), - Ints.saturatedCast(context.maximum().max() - context.initialSize()))); - var keys = Iterables.concat(absentKeys, absentKeys, - context.original().keySet(), context.original().keySet()); - var result = cache.getAll(keys).join(); - assertThat(result).containsExactlyKeys(keys); - int loads = context.loader().isBulk() ? 1 : absentKeys.size(); - assertThat(context).stats().hits(context.initialSize()) - .misses(absentKeys.size()).success(loads).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllPresent_ordered_absent( - AsyncLoadingCache cache, CacheContext context) { - var keys = new ArrayList<>(context.absentKeys()); - Collections.shuffle(keys); - var result = cache.getAll(keys).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, - population = {Population.SINGLETON, Population.PARTIAL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllPresent_ordered_partial( - AsyncLoadingCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - keys.addAll(context.absentKeys()); - Collections.shuffle(keys); - var result = cache.getAll(keys).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.EXCEPTIONAL, Loader.BULK_NEGATIVE_EXCEEDS}, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllPresent_ordered_present( - AsyncLoadingCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - Collections.shuffle(keys); - var result = cache.getAll(keys).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.BULK_NEGATIVE_EXCEEDS, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllPresent_ordered_exceeds( - AsyncLoadingCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - keys.addAll(context.absentKeys()); - Collections.shuffle(keys); - var result = cache.getAll(keys).join(); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.ASYNC, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_badLoader(CacheContext context) { - var loader = new AsyncCacheLoader() { - @Override - public CompletableFuture asyncLoad(Int key, Executor executor) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture> asyncLoadAll( - Set keys, Executor executor) { - throw new LoadAllException(); - } - }; - var cache = context.buildAsync(loader); - try { - cache.getAll(context.absentKeys()); - Assert.fail(); - } catch (LoadAllException e) { - assertThat(cache).isEmpty(); - } - } - - @SuppressWarnings("serial") - private static final class LoadAllException extends RuntimeException { - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace(AsyncLoadingCache cache, CacheContext context) { - var replaced = new HashMap(); - var value = context.absentValue().asFuture(); - for (Int key : context.firstMiddleLastKeys()) { - cache.put(key, value); - assertThat(cache.get(key)).succeedsWith(context.absentValue()); - replaced.put(key, context.original().get(key)); - } - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - compute = Compute.ASYNC, executor = CacheExecutor.THREADED) - public void refresh(CacheContext context) { - var done = new AtomicBoolean(); - var cache = context.buildAsync((Int key) -> { - await().untilTrue(done); - return key.negate(); - }); - Int key = Int.valueOf(1); - cache.synchronous().put(key, key); - var original = cache.get(key); - IntStream.range(0, 10).forEach(i -> { - context.ticker().advance(1, TimeUnit.SECONDS); - cache.synchronous().refresh(key); - var next = cache.get(key); - assertThat(next).isSameInstanceAs(original); - }); - done.set(true); - await().untilAsserted(() -> assertThat(cache).containsEntry(key, key.negate())); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = Population.EMPTY, compute = Compute.ASYNC) - public void refresh_nullFuture_load(CacheContext context) { - var cache = context.buildAsync((Int key, Executor executor) -> null); - cache.synchronous().refresh(context.absentKey()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = Population.EMPTY, compute = Compute.ASYNC) - public void refresh_nullFuture_reload(CacheContext context) { - var cache = context.buildAsync(new AsyncCacheLoader() { - @Override - public CompletableFuture asyncLoad(Int key, Executor executor) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture asyncReload( - Int key, Int oldValue, Executor executor) { - return null; - } - }); - cache.synchronous().put(context.absentKey(), context.absentValue()); - cache.synchronous().refresh(context.absentKey()); - } - - @Test(dataProvider = "caches", timeOut = 5_000) - @CacheSpec(population = Population.EMPTY, - compute = Compute.ASYNC, executor = CacheExecutor.THREADED) - public void refresh_deadlock(CacheContext context) { - var future = new CompletableFuture(); - var cache = context.buildAsync((Int k, Executor e) -> future); - cache.synchronous().refresh(context.absentKey()); - var get = cache.get(context.absentKey()); - future.complete(context.absentValue()); - assertThat(get).succeedsWith(context.absentValue()); - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.REFRESH_EXCEPTIONAL) - public void refresh_throwsException(AsyncLoadingCache cache, CacheContext context) { - try { - var key = context.original().isEmpty() ? context.absentKey() : context.firstKey(); - cache.synchronous().refresh(key); - Assert.fail(); - } catch (IllegalStateException e) { - int failures = context.isGuava() ? 1 : 0; - assertThat(context).stats().hits(0).misses(0).success(0).failures(failures); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.REFRESH_CHECKED_EXCEPTIONAL) - public void refresh_throwsCheckedException( - AsyncLoadingCache cache, CacheContext context) { - try { - var key = context.original().isEmpty() ? context.absentKey() : context.firstKey(); - cache.synchronous().refresh(key); - Assert.fail(); - } catch (CompletionException e) { - assertThat(e).hasCauseThat().isInstanceOf(ExecutionException.class); - int failures = context.isGuava() ? 1 : 0; - assertThat(context).stats().hits(0).misses(0).success(0).failures(failures); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.REFRESH_INTERRUPTED) - public void refresh_interrupted(AsyncLoadingCache cache, CacheContext context) { - try { - var key = context.original().isEmpty() ? context.absentKey() : context.firstKey(); - cache.synchronous().refresh(key); - Assert.fail(); - } catch (CompletionException e) { - assertThat(Thread.interrupted()).isTrue(); - assertThat(e).hasCauseThat().isInstanceOf(InterruptedException.class); - int failures = context.isGuava() ? 1 : 0; - assertThat(context).stats().hits(0).misses(0).success(0).failures(failures); - } - } - - @CacheSpec - @Test(dataProvider = "caches") - public void refresh_current_inFlight(AsyncLoadingCache cache, CacheContext context) { - var future = new CompletableFuture(); - cache.put(context.absentKey(), future); - cache.synchronous().refresh(context.absentKey()); - assertThat(cache).containsEntry(context.absentKey(), future); - assertThat(cache.synchronous().policy().refreshes()).isEmpty(); - future.complete(context.absentValue()); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.ASYNC, removalListener = Listener.CONSUMING) - public void refresh_current_sameInstance(CacheContext context) { - var future = context.absentValue().asFuture(); - var cache = context.buildAsync((key, executor) -> future); - cache.put(context.absentKey(), future); - cache.synchronous().refresh(context.absentKey()); - assertThat(context).notifications().isEmpty(); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void refresh_current_failed(AsyncLoadingCache cache, CacheContext context) { - var future = context.absentValue().asFuture(); - cache.put(context.absentKey(), future); - future.obtrudeException(new Exception()); - assertThat(cache.asMap()).containsKey(context.absentKey()); - cache.synchronous().refresh(context.absentKey()); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.ASYNC, - removalListener = Listener.CONSUMING, executor = CacheExecutor.THREADED) - public void refresh_current_removed(CacheContext context) { - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - var cache = context.buildAsync((Int key) -> { - started.set(true); - await().untilTrue(done); - return key; - }); - cache.put(context.absentKey(), context.absentValue().asFuture()); - cache.synchronous().refresh(context.absentKey()); - await().untilTrue(started); - cache.synchronous().invalidate(context.absentKey()); - done.set(true); - await().untilAsserted(() -> assertThat(context).removalNotifications().containsExactlyValues( - context.absentKey(), context.absentValue())); - } - - @Test(expectedExceptions = UnsupportedOperationException.class) - public void asyncLoadAll() throws Exception { - AsyncCacheLoader loader = (key, executor) -> key.negate().asFuture(); - loader.asyncLoadAll(Set.of(), Runnable::run); - } - - @Test - public void asyncReload() throws Exception { - AsyncCacheLoader loader = (key, executor) -> key.negate().asFuture(); - var future = loader.asyncReload(Int.valueOf(1), Int.valueOf(2), Runnable::run); - assertThat(future).succeedsWith(-1); - } - - @SuppressWarnings("CheckReturnValue") - @Test(expectedExceptions = NullPointerException.class) - public void bulk_function_null() { - Function, Map> f = null; - AsyncCacheLoader.bulk(f); - } - - @Test - public void bulk_function_absent() throws Exception { - AsyncCacheLoader loader = AsyncCacheLoader.bulk(keys -> Map.of()); - assertThat(loader.asyncLoadAll(Set.of(), Runnable::run)).succeedsWith(Map.of()); - assertThat(loader.asyncLoad(Int.valueOf(1), Runnable::run)).succeedsWithNull(); - } - - @Test - public void bulk_function_present() throws Exception { - AsyncCacheLoader loader = AsyncCacheLoader.bulk(keys -> keys.stream().collect(toImmutableMap(identity(), identity()))); - assertThat(loader.asyncLoadAll(Int.setOf(1, 2), Runnable::run)).succeedsWith(Int.mapOf(1, 1, 2, 2)); - assertThat(loader.asyncLoad(Int.valueOf(1), Runnable::run)).succeedsWith(1); - } - - @SuppressWarnings("CheckReturnValue") - @Test(expectedExceptions = NullPointerException.class) - public void bulk_bifunction_null() { - BiFunction, Executor, CompletableFuture>> f = null; - AsyncCacheLoader.bulk(f); - } - - @Test - public void bulk_absent() throws Exception { - BiFunction, Executor, CompletableFuture>> f = (keys, executor) -> CompletableFuture.completedFuture(Map.of()); - var loader = AsyncCacheLoader.bulk(f); - assertThat(loader.asyncLoadAll(Set.of(), Runnable::run)).succeedsWith(Map.of()); - assertThat(loader.asyncLoad(Int.valueOf(1), Runnable::run)).succeedsWithNull(); - } - - @Test - public void bulk_present() throws Exception { - BiFunction, Executor, CompletableFuture>> f = (keys, executor) -> { - ImmutableMap results = keys.stream().collect(toImmutableMap(identity(), identity())); - return CompletableFuture.completedFuture(results); - }; - var loader = AsyncCacheLoader.bulk(f); - assertThat(loader.asyncLoadAll(Int.setOf(1, 2), Runnable::run)) - .succeedsWith(Int.mapOf(1, 1, 2, 2)); - assertThat(loader.asyncLoad(Int.valueOf(1), Runnable::run)).succeedsWith(1); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncTest.java deleted file mode 100644 index edb82ab..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncTest.java +++ /dev/null @@ -1,152 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.Async.AsyncExpiry; -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import org.mockito.Mockito; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import static com.github.benmanes.caffeine.cache.Async.ASYNC_EXPIRY; -import static com.github.benmanes.caffeine.cache.BoundedLocalCache.MAXIMUM_EXPIRY; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.google.common.truth.Truth.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.mockito.Mockito.*; - - -public final class AsyncTest { - private static final long ONE_MINUTE = TimeUnit.MINUTES.toNanos(1); - - @Test - public void reflectivelyConstruct() throws ReflectiveOperationException { - var constructor = Async.class.getDeclaredConstructor(); - constructor.setAccessible(true); - constructor.newInstance(); - } - - @Test(dataProvider = "successful") - public void isReady_success(CompletableFuture future) { - assertThat(Async.isReady(future)).isTrue(); - } - - @Test(dataProvider = "unsuccessful") - public void isReady_fails(CompletableFuture future) { - assertThat(Async.isReady(future)).isFalse(); - } - - @Test(dataProvider = "successful") - public void getIfReady_success(CompletableFuture future) { - assertThat(Async.getIfReady(future)).isEqualTo(1); - } - - @Test(dataProvider = "unsuccessful") - public void getIfReady_fails(CompletableFuture future) { - assertThat(Async.getIfReady(future)).isNull(); - } - - @Test(dataProvider = "successful") - public void getWhenSuccessful_success(CompletableFuture future) { - assertThat(Async.getWhenSuccessful(future)).isEqualTo(1); - } - - @Test - public void getWhenSuccessful_success_async() { - var future = new CompletableFuture(); - var result = new AtomicInteger(); - ConcurrentTestHarness.execute(() -> { - result.set(1); - result.set(Async.getWhenSuccessful(future)); - }); - await().untilAtomic(result, is(1)); - future.complete(2); - await().untilAtomic(result, is(2)); - } - - @Test(dataProvider = "unsuccessful") - public void getWhenSuccessful_fails(CompletableFuture future) { - if ((future != null) && !future.isDone()) { - var result = new AtomicInteger(); - ConcurrentTestHarness.execute(() -> { - result.set(1); - Object value = Async.getWhenSuccessful(future); - result.set((value == null) ? 2 : 3); - }); - await().untilAtomic(result, is(1)); - future.obtrudeException(new IllegalStateException()); - await().untilAtomic(result, is(not(1))); - assertThat(result.get()).isEqualTo(2); - } - assertThat(Async.getWhenSuccessful(future)).isNull(); - } - - @Test - public void asyncExpiry_pending() { - var expiry = makeAsyncExpiry(ONE_MINUTE, ONE_MINUTE, ONE_MINUTE); - var future = new CompletableFuture(); - assertThat(expiry.expireAfterCreate(0, future, 1)).isEqualTo(ASYNC_EXPIRY); - verify(expiry.delegate, never()).expireAfterCreate(any(), any(), anyLong()); - assertThat(expiry.expireAfterUpdate(0, future, 1, 2)).isEqualTo(ASYNC_EXPIRY); - verify(expiry.delegate, never()).expireAfterUpdate(any(), any(), anyLong(), anyLong()); - assertThat(expiry.expireAfterRead(0, future, 1, 2)).isEqualTo(ASYNC_EXPIRY); - verify(expiry.delegate, never()).expireAfterRead(any(), any(), anyLong(), anyLong()); - } - - @Test - public void asyncExpiry_completed() { - var expiry = makeAsyncExpiry(ONE_MINUTE, 2 * ONE_MINUTE, 3 * ONE_MINUTE); - var future = CompletableFuture.completedFuture(100); - assertThat(expiry.expireAfterCreate(0, future, 1)).isEqualTo(ONE_MINUTE); - verify(expiry.delegate).expireAfterCreate(0, 100, 1); - assertThat(expiry.expireAfterUpdate(0, future, 1, 2)).isEqualTo(2 * ONE_MINUTE); - verify(expiry.delegate).expireAfterUpdate(0, 100, 1, 2); - assertThat(expiry.expireAfterRead(0, future, 1, 2)).isEqualTo(3 * ONE_MINUTE); - verify(expiry.delegate).expireAfterRead(0, 100, 1, 2); - } - - @Test - public void asyncExpiry_bounded() { - var expiry = makeAsyncExpiry(Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE); - var future = CompletableFuture.completedFuture(100); - assertThat(expiry.expireAfterCreate(0, future, 1)).isEqualTo(MAXIMUM_EXPIRY); - assertThat(expiry.expireAfterUpdate(0, future, 1, 2)).isEqualTo(MAXIMUM_EXPIRY); - assertThat(expiry.expireAfterRead(0, future, 1, 2)).isEqualTo(MAXIMUM_EXPIRY); - } - - @DataProvider(name = "successful") - public Object[][] providesSuccessful() { - return new Object[][]{{CompletableFuture.completedFuture(1)}}; - } - - @DataProvider(name = "unsuccessful") - public Object[][] providesUnsuccessful() { - return new Object[][]{ - {null}, - {new CompletableFuture()}, - {CompletableFuture.completedFuture(null)}, - {newFailedFuture(new InterruptedException())}, - {newFailedFuture(new IllegalStateException())}, - }; - } - - private static AsyncExpiry makeAsyncExpiry( - long create, long update, long read) { - @SuppressWarnings("unchecked") - Expiry mock = Mockito.mock(Expiry.class); - when(mock.expireAfterCreate(any(), any(), anyLong())).thenReturn(create); - when(mock.expireAfterUpdate(any(), any(), anyLong(), anyLong())).thenReturn(update); - when(mock.expireAfterRead(any(), any(), anyLong(), anyLong())).thenReturn(read); - return new AsyncExpiry<>(mock); - } - - private static CompletableFuture newFailedFuture(Exception e) { - var future = new CompletableFuture(); - future.completeExceptionally(e); - return future; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedBufferTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedBufferTest.java deleted file mode 100644 index 2d6c144..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedBufferTest.java +++ /dev/null @@ -1,80 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; - -import static com.google.common.truth.Truth.assertThat; - -public final class BoundedBufferTest { - - @DataProvider - public Object[][] buffer() { - return new Object[][]{{new BoundedBuffer()}}; - } - - @Test(dataProvider = "buffer") - public void offer(BoundedBuffer buffer) { - ConcurrentTestHarness.timeTasks(10, () -> { - for (int i = 0; i < 100; i++) { - buffer.offer(Boolean.TRUE); - } - }); - assertThat(buffer.writes()).isGreaterThan(0); - assertThat(buffer.writes()).isEqualTo(buffer.size()); - } - - @Test(dataProvider = "buffer") - public void drain(BoundedBuffer buffer) { - for (int i = 0; i < BoundedBuffer.BUFFER_SIZE; i++) { - buffer.offer(Boolean.TRUE); - } - long[] read = new long[1]; - buffer.drainTo(e -> read[0]++); - assertThat(read[0]).isEqualTo(buffer.reads()); - assertThat(read[0]).isEqualTo(buffer.writes()); - } - - @Test(dataProvider = "buffer") - @SuppressWarnings("ThreadPriorityCheck") - public void offerAndDrain(BoundedBuffer buffer) { - var lock = new ReentrantLock(); - var reads = new AtomicInteger(); - ConcurrentTestHarness.timeTasks(10, () -> { - for (int i = 0; i < 1000; i++) { - boolean shouldDrain = (buffer.offer(Boolean.TRUE) == Buffer.FULL); - if (shouldDrain && lock.tryLock()) { - buffer.drainTo(e -> reads.incrementAndGet()); - lock.unlock(); - } - Thread.yield(); - } - }); - buffer.drainTo(e -> reads.incrementAndGet()); - assertThat(reads.longValue()).isEqualTo(buffer.reads()); - assertThat(reads.longValue()).isEqualTo(buffer.writes()); - } - - @Test - public void overflow() { - var buffer = new BoundedBuffer.RingBuffer(null); - buffer.writeCounter = Long.MAX_VALUE; - buffer.readCounter = Long.MAX_VALUE; - - buffer.offer(Boolean.TRUE); - var data = new ArrayList<>(); - buffer.drainTo(data::add); - - for (var e : buffer.buffer) { - assertThat(e).isNull(); - } - assertThat(data).containsExactly(Boolean.TRUE); - assertThat(buffer.readCounter).isEqualTo(Long.MIN_VALUE); - assertThat(buffer.writeCounter).isEqualTo(Long.MIN_VALUE); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java deleted file mode 100644 index 3546e20..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java +++ /dev/null @@ -1,1950 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.BoundedLocalCache.BoundedPolicy.FixedExpireAfterWrite; -import com.github.benmanes.caffeine.cache.Policy.Eviction; -import com.github.benmanes.caffeine.cache.Policy.FixedExpiration; -import com.github.benmanes.caffeine.cache.Policy.VarExpiration; -import com.github.benmanes.caffeine.cache.References.WeakKeyReference; -import com.github.benmanes.caffeine.cache.stats.StatsCounter; -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.cache.testing.RemovalListeners.ConsumingRemovalListener; -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import com.github.benmanes.caffeine.testing.Int; -import com.github.valfirst.slf4jtest.TestLogger; -import com.github.valfirst.slf4jtest.TestLoggerFactory; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; -import com.google.common.collect.Range; -import com.google.common.testing.GcFinalization; -import com.google.common.util.concurrent.Uninterruptibles; -import org.apache.commons.lang3.mutable.MutableInt; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.testng.Assert; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.lang.ref.Reference; -import java.time.Duration; -import java.util.AbstractMap.SimpleEntry; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.stream.IntStream; - -import static com.github.benmanes.caffeine.cache.BoundedLocalCache.*; -import static com.github.benmanes.caffeine.cache.Node.WINDOW; -import static com.github.benmanes.caffeine.cache.RemovalCause.*; -import static com.github.benmanes.caffeine.cache.testing.CacheContext.intern; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.*; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.FutureSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.truth.Truth.assertThat; -import static java.lang.Thread.State.BLOCKED; -import static java.lang.Thread.State.WAITING; -import static java.util.Locale.US; -import static java.util.function.Function.identity; -import static org.hamcrest.Matchers.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; -import static uk.org.lidalia.slf4jext.ConventionalLevelHierarchy.WARN_LEVELS; -import static uk.org.lidalia.slf4jext.Level.ERROR; -import static uk.org.lidalia.slf4jext.Level.WARN; - -@CheckMaxLogLevel(WARN) -@SuppressWarnings("GuardedBy") -@Listeners(CacheValidationListener.class) -@Test(dataProviderClass = CacheProvider.class) -public final class BoundedLocalCacheTest { - - /* --------------- Maintenance --------------- */ - - @Test - @SuppressWarnings("UnusedVariable") - public void cleanupTask_allowGc() { - var cache = new BoundedLocalCache<>(Caffeine.newBuilder(), null, false) { - }; - var task = cache.drainBuffersTask; - cache = null; - GcFinalization.awaitClear(task.reference); - task.run(); - } - - @Test - @CheckMaxLogLevel(ERROR) - public void cleanupTask_exception() { - var expected = new RuntimeException(); - var cache = mock(BoundedLocalCache.class); - doThrow(expected).when(cache).performCleanUp(any()); - var task = new PerformCleanupTask(cache); - assertThat(task.exec()).isFalse(); - var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()); - assertThat(event.getThrowable().orElseThrow()).isSameInstanceAs(expected); - assertThat(event.getLevel()).isEqualTo(ERROR); - } - - @Test - @CheckMaxLogLevel(ERROR) - public void cleanup_exception() { - var expected = new RuntimeException(); - var cache = mock(BoundedLocalCache.class); - doThrow(expected).when(cache).performCleanUp(any()); - doCallRealMethod().when(cache).cleanUp(); - cache.cleanUp(); - var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()); - assertThat(event.getThrowable().orElseThrow()).isSameInstanceAs(expected); - assertThat(event.getLevel()).isEqualTo(ERROR); - } - - @Test - public void scheduleAfterWrite() { - var cache = new BoundedLocalCache<>(Caffeine.newBuilder(), null, false) { - @Override - void scheduleDrainBuffers() { - } - }; - var transitions = Map.of( - IDLE, REQUIRED, - REQUIRED, REQUIRED, - PROCESSING_TO_IDLE, PROCESSING_TO_REQUIRED, - PROCESSING_TO_REQUIRED, PROCESSING_TO_REQUIRED); - transitions.forEach((start, end) -> { - cache.drainStatus = start; - cache.scheduleAfterWrite(); - assertThat(cache.drainStatus).isEqualTo(end); - }); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void scheduleAfterWrite_invalidDrainStatus() { - var cache = new BoundedLocalCache<>(Caffeine.newBuilder(), null, false) { - }; - var valid = Set.of(IDLE, REQUIRED, PROCESSING_TO_IDLE, PROCESSING_TO_REQUIRED); - for (; ; ) { - int state = ThreadLocalRandom.current().nextInt(); - if (!valid.contains(state)) { - cache.drainStatus = state; - cache.scheduleAfterWrite(); - Assert.fail("Should not be valid: " + state); - } - } - } - - @Test - public void scheduleDrainBuffers() { - var executor = mock(Executor.class); - var cache = new BoundedLocalCache<>(Caffeine.newBuilder().executor(executor), null, false) { - }; - var transitions = Map.of( - IDLE, PROCESSING_TO_IDLE, - REQUIRED, PROCESSING_TO_IDLE, - PROCESSING_TO_IDLE, PROCESSING_TO_IDLE, - PROCESSING_TO_REQUIRED, PROCESSING_TO_REQUIRED); - transitions.forEach((start, end) -> { - cache.drainStatus = start; - cache.scheduleDrainBuffers(); - assertThat(cache.drainStatus).isEqualTo(end); - if (!start.equals(end)) { - verify(executor).execute(any()); - reset(executor); - } - }); - } - - @Test - public void rescheduleDrainBuffers() { - var evicting = new AtomicBoolean(); - var done = new AtomicBoolean(); - var evictionListener = new RemovalListener() { - @Override - public void onRemoval(Int key, Int value, RemovalCause cause) { - evicting.set(true); - await().untilTrue(done); - } - }; - var cache = asBoundedLocalCache(Caffeine.newBuilder() - .executor(CacheExecutor.THREADED.create()) - .evictionListener(evictionListener) - .maximumSize(0) - .build()); - cache.put(Int.valueOf(1), Int.valueOf(1)); - await().untilTrue(evicting); - cache.put(Int.valueOf(2), Int.valueOf(2)); - assertThat(cache.drainStatus).isEqualTo(PROCESSING_TO_REQUIRED); - done.set(true); - await().untilAsserted(() -> assertThat(cache.drainStatus).isAnyOf(REQUIRED, IDLE)); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, maximumSize = Maximum.FULL, - executor = CacheExecutor.REJECTING, executorFailure = ExecutorFailure.EXPECTED, - removalListener = Listener.CONSUMING) - public void scheduleDrainBuffers_rejected( - BoundedLocalCache cache, CacheContext context) { - cache.put(context.absentKey(), context.absentValue()); - - assertThat(cache.drainStatus).isEqualTo(IDLE); - assertThat(cache.writeBuffer.isEmpty()).isTrue(); - assertThat(cache.evictionLock.isLocked()).isFalse(); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void shouldDrainBuffers_invalidDrainStatus() { - var cache = new BoundedLocalCache<>( - Caffeine.newBuilder(), /* loader */ null, /* async */ false) { - }; - var valid = Set.of(IDLE, REQUIRED, PROCESSING_TO_IDLE, PROCESSING_TO_REQUIRED); - for (; ; ) { - int state = ThreadLocalRandom.current().nextInt(); - if (!valid.contains(state)) { - cache.drainStatus = state; - cache.shouldDrainBuffers(true); - Assert.fail("Should not be valid: " + state); - } - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY) - public void afterWrite_drainFullWriteBuffer(BoundedLocalCache cache, CacheContext context) { - cache.drainStatus = PROCESSING_TO_IDLE; - int[] queued = {0}; - Runnable pendingTask = () -> queued[0]++; - IntStream.range(0, WRITE_BUFFER_MAX).mapToObj(i -> pendingTask).forEach(cache::afterWrite); - assertThat(cache.drainStatus).isEqualTo(PROCESSING_TO_REQUIRED); - int[] triggered = {0}; - Runnable triggerTask = () -> triggered[0] = WRITE_BUFFER_MAX + 1; - cache.afterWrite(triggerTask); - assertThat(cache.drainStatus).isEqualTo(IDLE); - assertThat(cache.evictionLock.isLocked()).isFalse(); - assertThat(queued[0]).isEqualTo(WRITE_BUFFER_MAX); - assertThat(triggered[0]).isEqualTo(WRITE_BUFFER_MAX + 1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, executor = CacheExecutor.DISCARDING) - public void afterWrite_drainFullWriteBuffer_discarded( - BoundedLocalCache cache, CacheContext context) { - cache.put(context.absentKey(), context.absentValue()); - assertThat(cache.drainStatus).isEqualTo(PROCESSING_TO_IDLE); - assertThat(cache.evictionLock.isLocked()).isFalse(); - int[] queued = {1}; - Runnable pendingTask = () -> queued[0]++; - for (int i = 0; i < (WRITE_BUFFER_MAX - 1); i++) { - cache.afterWrite(pendingTask); - } - assertThat(cache.drainStatus).isEqualTo(PROCESSING_TO_REQUIRED); - int[] triggered = {0}; - Runnable triggerTask = () -> triggered[0] = WRITE_BUFFER_MAX + 1; - cache.afterWrite(triggerTask); - assertThat(cache.drainStatus).isEqualTo(IDLE); - assertThat(cache.evictionLock.isLocked()).isFalse(); - assertThat(queued[0]).isEqualTo(WRITE_BUFFER_MAX); - assertThat(triggered[0]).isEqualTo(WRITE_BUFFER_MAX + 1); - } - - @Test - @CheckMaxLogLevel(ERROR) - public void afterWrite_exception() { - var expected = new RuntimeException(); - var cache = new BoundedLocalCache<>(Caffeine.newBuilder(), null, false) { - @Override - void maintenance(Runnable task) { - throw expected; - } - }; - Runnable pendingTask = () -> { - }; - IntStream.range(0, WRITE_BUFFER_MAX).mapToObj(i -> pendingTask).forEach(cache::afterWrite); - assertThat(cache.drainStatus).isEqualTo(PROCESSING_TO_REQUIRED); - cache.afterWrite(pendingTask); - var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()); - assertThat(event.getThrowable().orElseThrow()).isSameInstanceAs(expected); - assertThat(event.getLevel()).isEqualTo(ERROR); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.MAX_VALUE) - public void overflow_add_one(BoundedLocalCache map, CacheContext context) { - long actualWeight = map.weightedSize(); - map.setWeightedSize(MAXIMUM_CAPACITY); - map.put(context.absentKey(), context.absentValue()); - assertThat(map).hasSize(context.initialSize()); - assertThat(map.weightedSize()).isEqualTo(MAXIMUM_CAPACITY); - var removed = new HashMap<>(context.original()); - removed.put(context.absentKey(), context.absentValue()); - removed.keySet().removeAll(map.keySet()); - assertThat(context).notifications().hasSize(1); - assertThat(context).notifications().withCause(SIZE).contains(removed).exclusively(); - map.setWeightedSize(actualWeight); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.MAX_VALUE) - public void overflow_add_many(BoundedLocalCache map, CacheContext context) { - long actualWeight = map.weightedSize(); - map.setWeightedSize(MAXIMUM_CAPACITY); - map.evictionLock.lock(); - try { - map.putAll(context.absent()); - } finally { - map.evictionLock.unlock(); - } - map.cleanUp(); - assertThat(map).hasSize(context.initialSize()); - assertThat(map.weightedSize()).isEqualTo(MAXIMUM_CAPACITY); - var removed = new HashMap<>(context.original()); - removed.putAll(context.absent()); - removed.keySet().removeAll(map.keySet()); - assertThat(context).notifications().hasSize(context.absent().size()); - assertThat(context).notifications().withCause(SIZE).contains(removed).exclusively(); - map.setWeightedSize(actualWeight); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.VALUE) - public void overflow_update_one(BoundedLocalCache map, CacheContext context) { - map.setWeightedSize(MAXIMUM_CAPACITY); - map.put(context.firstKey(), Int.MAX_VALUE); - assertThat(map).hasSizeLessThan(1 + context.initialSize()); - assertThat(map.weightedSize()).isAtMost(MAXIMUM_CAPACITY); - var removed = new HashMap<>(context.original()); - removed.put(context.firstKey(), Int.MAX_VALUE); - removed.keySet().removeAll(map.keySet()); - assertThat(removed.size()).isAtLeast(1); - assertThat(context).notifications().withCause(SIZE).contains(removed); - map.setWeightedSize(map.data.values().stream().mapToLong(Node::getWeight).sum()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.VALUE) - public void overflow_update_many(BoundedLocalCache map, CacheContext context) { - var updated = Maps.asMap(context.firstMiddleLastKeys(), key -> Int.MAX_VALUE); - map.setWeightedSize(MAXIMUM_CAPACITY); - map.evictionLock.lock(); - try { - map.putAll(updated); - } finally { - map.evictionLock.unlock(); - } - map.cleanUp(); - assertThat(map).hasSizeLessThan(1 + context.initialSize()); - assertThat(map.weightedSize()).isAtMost(MAXIMUM_CAPACITY); - var removed = new HashMap<>(context.original()); - removed.putAll(updated); - removed.keySet().removeAll(map.keySet()); - assertThat(removed.size()).isAtLeast(1); - assertThat(context).notifications().withCause(SIZE).contains(removed); - map.setWeightedSize(map.data.values().stream().mapToLong(Node::getWeight).sum()); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, maximumSize = Maximum.ONE) - public void evict_alreadyRemoved(BoundedLocalCache cache, CacheContext context) { - var oldEntry = Iterables.get(context.absent().entrySet(), 0); - var newEntry = Iterables.get(context.absent().entrySet(), 1); - var removed = new AtomicBoolean(); - cache.put(oldEntry.getKey(), oldEntry.getValue()); - cache.evictionLock.lock(); - try { - var lookupKey = cache.nodeFactory.newLookupKey(oldEntry.getKey()); - var node = cache.data.get(lookupKey); - checkStatus(node, Status.ALIVE); - ConcurrentTestHarness.execute(() -> { - cache.put(newEntry.getKey(), newEntry.getValue()); - assertThat(cache.remove(oldEntry.getKey())).isEqualTo(oldEntry.getValue()); - removed.set(true); - }); - await().untilAsserted(() -> assertThat(cache).doesNotContainKey(oldEntry.getKey())); - await().untilTrue(removed); - await().until(() -> { - synchronized (node) { - return !node.isAlive(); - } - }); - checkStatus(node, Status.RETIRED); - cache.cleanUp(); - checkStatus(node, Status.DEAD); - assertThat(cache).containsKey(newEntry.getKey()); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(oldEntry).exclusively(); - } finally { - cache.evictionLock.unlock(); - } - } - - enum Status {ALIVE, RETIRED, DEAD} - - static void checkStatus(Node node, Status expected) { - synchronized (node) { - assertThat(node.isAlive()).isEqualTo(expected == Status.ALIVE); - assertThat(node.isRetired()).isEqualTo(expected == Status.RETIRED); - assertThat(node.isDead()).isEqualTo(expected == Status.DEAD); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, - population = Population.EMPTY, maximumSize = Maximum.TEN, weigher = CacheWeigher.DISABLED) - public void evict_wtinylfu(Cache cache, CacheContext context) { - asBoundedLocalCache(cache).frequencySketch().ensureCapacity(context.maximumSize()); - IntStream.range(0, 10).forEach(i -> cache.put(Int.valueOf(i), Int.valueOf(-i))); - checkContainsInOrder(cache, Int.listOf(9, 0, 1, 2, 3, 4, 5, 6, 7, 8)); - checkReorder(cache, Int.listOf(0, 1, 2), Int.listOf(9, 3, 4, 5, 6, 7, 8, 0, 1, 2)); - checkEvict(cache, Int.listOf(10, 11, 12), Int.listOf(12, 3, 4, 5, 6, 7, 8, 0, 1, 2)); - checkReorder(cache, Int.listOf(6, 7, 8), Int.listOf(12, 3, 4, 5, 0, 1, 2, 6, 7, 8)); - checkEvict(cache, Int.listOf(13, 14, 15), Int.listOf(15, 3, 4, 5, 0, 1, 2, 6, 7, 8)); - assertThat(context).stats().evictions(6); - } - - private void checkReorder(Cache cache, List keys, List expect) { - keys.forEach(cache::getIfPresent); - checkContainsInOrder(cache, expect); - } - - private void checkEvict(Cache cache, List keys, List expect) { - keys.forEach(i -> cache.put(i, i)); - checkContainsInOrder(cache, expect); - } - - private void checkContainsInOrder(Cache cache, List expect) { - var evictionOrder = cache.policy().eviction().orElseThrow().coldest(Integer.MAX_VALUE).keySet(); - assertThat(cache).containsExactlyKeys(expect); - assertThat(evictionOrder).containsExactlyElementsIn(expect).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED, - removalListener = Listener.CONSUMING) - public void evict_candidate_lru(BoundedLocalCache cache, CacheContext context) { - cache.setMainProtectedMaximum(0); - cache.setWindowMaximum(context.maximumSize()); - IntStream.iterate(0, i -> i < context.maximumSize(), i -> i + 1).forEach(i -> cache.put(Int.valueOf(i), Int.valueOf(i))); - var expected = cache.accessOrderWindowDeque().stream() - .map(Node::getKey).collect(toImmutableList()); - cache.setWindowMaximum(0L); - cache.evictFromWindow(); - var actual = cache.accessOrderProbationDeque().stream().map(Node::getKey).collect(toImmutableList()); - assertThat(actual).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED, - removalListener = Listener.CONSUMING) - public void evict_victim_lru(BoundedLocalCache cache, CacheContext context) { - cache.setWindowMaximum(0); - cache.evictFromWindow(); - var expected = FluentIterable - .from(cache.accessOrderProbationDeque()) - .append(cache.accessOrderProtectedDeque()) - .transform(Node::getKey).toList(); - cache.setMaximumSize(0L); - cache.cleanUp(); - var listener = (ConsumingRemovalListener) context.removalListener(); - var actual = listener.removed().stream().map(Entry::getKey).collect(toImmutableList()); - assertThat(actual).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED, - removalListener = Listener.CONSUMING) - public void evict_window_candidates(BoundedLocalCache cache, CacheContext context) { - cache.setWindowMaximum(context.maximumSize() / 2); - cache.setMainProtectedMaximum(0); - for (int i = 0; i < context.maximumSize(); i++) { - cache.put(Int.valueOf(i), Int.valueOf(i)); - } - Arrays.fill(cache.frequencySketch().table, 0L); - var expected = cache.accessOrderWindowDeque().stream() - .map(Node::getKey).collect(toImmutableList()); - cache.setMaximum(context.maximumSize() / 2); - cache.setWindowMaximum(0); - cache.evictEntries(); - var listener = (ConsumingRemovalListener) context.removalListener(); - var actual = listener.removed().stream().map(Entry::getKey).collect(toImmutableList()); - assertThat(actual).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED, - removalListener = Listener.CONSUMING) - public void evict_window_fallback(BoundedLocalCache cache, CacheContext context) { - cache.setWindowMaximum(context.maximumSize() / 2); - cache.setMainProtectedMaximum(0); - IntStream.iterate(0, i -> i < context.maximumSize(), i -> i + 1).forEach(i -> cache.put(Int.valueOf(i), Int.valueOf(i))); - Arrays.fill(cache.frequencySketch().table, 0L); - var expected = cache.accessOrderWindowDeque().stream() - .map(Node::getKey).collect(toImmutableList()); - cache.setMaximum(context.maximumSize() / 2); - cache.evictEntries(); - var listener = (ConsumingRemovalListener) context.removalListener(); - var actual = listener.removed().stream().map(Entry::getKey).collect(toImmutableList()); - assertThat(actual).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED, - removalListener = Listener.CONSUMING) - public void evict_candidateIsVictim(BoundedLocalCache cache, CacheContext context) { - cache.setMainProtectedMaximum(context.maximumSize() / 2); - cache.setWindowMaximum(context.maximumSize() / 2); - IntStream.iterate(0, i -> i < context.maximumSize(), i -> i + 1).forEach(i -> cache.put(Int.valueOf(i), Int.valueOf(i))); - while (!cache.accessOrderProbationDeque().isEmpty()) { - var node = cache.accessOrderProbationDeque().removeFirst(); - cache.accessOrderProtectedDeque().offerLast(node); - node.makeMainProtected(); - } - Arrays.fill(cache.frequencySketch().table, 0L); - cache.setMainProtectedWeightedSize(context.maximumSize() - cache.windowWeightedSize()); - var expected = FluentIterable.from(cache.accessOrderWindowDeque()) - .append(cache.accessOrderProbationDeque()) - .append(cache.accessOrderProtectedDeque()) - .transform(Node::getKey).toList(); - cache.setMainProtectedMaximum(0L); - cache.setWindowMaximum(0L); - cache.setMaximum(0L); - cache.evictEntries(); - - var listener = (ConsumingRemovalListener) context.removalListener(); - var actual = listener.removed().stream().map(Entry::getKey).collect(toImmutableList()); - assertThat(actual).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED, - removalListener = Listener.CONSUMING) - public void evict_toZero(BoundedLocalCache cache, CacheContext context) { - for (int i = 0; i < context.maximumSize(); i++) { - cache.put(Int.valueOf(i), Int.valueOf(i)); - } - Arrays.fill(cache.frequencySketch().table, 0L); - - var expected = FluentIterable - .from(cache.accessOrderWindowDeque()) - .append(cache.accessOrderProbationDeque()) - .append(cache.accessOrderProtectedDeque()) - .transform(Node::getKey).toList(); - cache.setMaximumSize(0); - cache.evictEntries(); - var listener = (ConsumingRemovalListener) context.removalListener(); - var actual = listener.removed().stream().map(Entry::getKey).collect(toImmutableList()); - assertThat(actual).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED) - public void evict_retired_candidate(BoundedLocalCache cache, CacheContext context) { - cache.evictionLock.lock(); - try { - var expected = cache.accessOrderWindowDeque().peekFirst(); - var key = expected.getKey(); - ConcurrentTestHarness.execute(() -> cache.remove(key)); - await().until(() -> !cache.containsKey(key)); - assertThat(expected.isRetired()).isTrue(); - cache.setWindowMaximum(cache.windowMaximum() - 1); - cache.setMaximum(context.maximumSize() - 1); - cache.evictEntries(); - assertThat(expected.isDead()).isTrue(); - await().untilAsserted(() -> assertThat(cache).hasSize(cache.maximum())); - } finally { - cache.evictionLock.unlock(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED) - public void evict_retired_victim(BoundedLocalCache cache, CacheContext context) { - cache.evictionLock.lock(); - try { - var expected = cache.accessOrderProbationDeque().peekFirst(); - var key = expected.getKey(); - ConcurrentTestHarness.execute(() -> cache.remove(key)); - await().until(() -> !cache.containsKey(key)); - assertThat(expected.isRetired()).isTrue(); - cache.setWindowMaximum(cache.windowMaximum() - 1); - cache.setMaximum(context.maximumSize() - 1); - cache.evictEntries(); - assertThat(expected.isDead()).isTrue(); - await().untilAsserted(() -> assertThat(cache).hasSize(cache.maximum())); - } finally { - cache.evictionLock.unlock(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.VALUE) - public void evict_zeroWeight(BoundedLocalCache cache, CacheContext context) { - for (int i = 0; i < context.maximumSize(); i++) { - cache.put(Int.valueOf(i), Int.valueOf(1)); - cache.get(Int.valueOf(i - 1)); - } - cache.put(cache.accessOrderWindowDeque().peekFirst().getKey(), Int.valueOf(0)); - cache.put(cache.accessOrderProbationDeque().peekFirst().getKey(), Int.valueOf(0)); - cache.setMaximumSize(0); - cache.evictEntries(); - assertThat(cache).hasSize(2); - assertThat(cache.weightedSize()).isEqualTo(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, maximumSize = Maximum.FULL) - public void evict_admit(BoundedLocalCache cache, CacheContext context) { - cache.frequencySketch().ensureCapacity(context.maximumSize()); - Int candidate = Int.valueOf(0); - Int victim = Int.valueOf(1); - assertThat(cache.admit(candidate, victim)).isFalse(); - cache.frequencySketch().increment(candidate); - assertThat(cache.admit(candidate, victim)).isTrue(); - IntStream.range(0, 15).forEach(i -> { - cache.frequencySketch().increment(victim); - assertThat(cache.admit(candidate, victim)).isFalse(); - }); - while (cache.frequencySketch().frequency(candidate) < ADMIT_HASHDOS_THRESHOLD) { - cache.frequencySketch().increment(candidate); - } - int allow = 0; - int reject = 0; - for (int i = 0; i < 1_000; i++) { - if (cache.admit(candidate, victim)) { - allow++; - } else { - reject++; - } - } - assertThat(allow).isGreaterThan(0); - assertThat(reject).isGreaterThan(allow); - assertThat(100.0 * allow / (allow + reject)).isIn(Range.open(0.2, 2.0)); - } - - @Test(groups = "isolated") - public void evict_update() { - Int key = Int.valueOf(0); - Int oldValue = Int.valueOf(1); - Int newValue = Int.valueOf(2); - var evictor = Thread.currentThread(); - var started = new AtomicBoolean(); - var writing = new AtomicBoolean(); - var evictedValue = new AtomicReference(); - var previousValue = new AtomicReference(); - var removedValues = new AtomicReference<>(Int.valueOf(0)); - RemovalListener evictionListener = (k, v, cause) -> evictedValue.set(v); - RemovalListener removalListener = (k, v, cause) -> removedValues.accumulateAndGet(v, Int::add); - var cache = Caffeine.newBuilder() - .executor(CacheExecutor.DIRECT.create()) - .evictionListener(evictionListener) - .removalListener(removalListener) - .maximumSize(100) - .build(); - var localCache = asBoundedLocalCache(cache); - cache.put(key, oldValue); - started.set(true); - ConcurrentTestHarness.execute(() -> localCache.compute(key, (k, v) -> { - if (started.get()) { - writing.set(true); - await().untilAsserted(() -> assertThat(evictor.getState()).isEqualTo(BLOCKED)); - } - previousValue.set(v); - return newValue; - })); - await().untilTrue(writing); - var node = localCache.data.values().iterator().next(); - localCache.evictEntry(node, SIZE, 0); - await().untilAtomic(evictedValue, is(newValue)); - await().untilAtomic(previousValue, is(oldValue)); - await().untilAtomic(removedValues, is(oldValue.add(newValue))); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.TEN, weigher = CacheWeigher.VALUE, - initialCapacity = InitialCapacity.EXCESSIVE, removalListener = Listener.CONSUMING) - public void evict_update_entryTooBig_window( - BoundedLocalCache cache, CacheContext context) { - cache.put(Int.valueOf(9), Int.valueOf(9)); - cache.put(Int.valueOf(1), Int.valueOf(1)); - var lookupKey = cache.nodeFactory.newLookupKey(Int.valueOf(1)); - assertThat(cache.data.get(lookupKey).inWindow()).isTrue(); - cache.put(Int.valueOf(1), Int.valueOf(20)); - assertThat(cache.weightedSize()).isAtMost(context.maximumSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(Int.valueOf(1), Int.valueOf(1)); - assertThat(context).removalNotifications().withCause(SIZE) - .contains(Int.valueOf(1), Int.valueOf(20)); - assertThat(context).removalNotifications().hasSize(2); - assertThat(context).evictionNotifications().withCause(SIZE) - .contains(Int.valueOf(1), Int.valueOf(20)).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.TEN, weigher = CacheWeigher.VALUE, - initialCapacity = InitialCapacity.EXCESSIVE, removalListener = Listener.CONSUMING) - public void evict_update_entryTooBig_probation( - BoundedLocalCache cache, CacheContext context) { - for (int i = 1; i <= 10; i++) { - cache.put(Int.valueOf(i), Int.valueOf(1)); - } - var lookupKey = cache.nodeFactory.newLookupKey(Int.valueOf(1)); - assertThat(cache.data.get(lookupKey).inMainProbation()).isTrue(); - cache.put(Int.valueOf(1), Int.valueOf(20)); - assertThat(cache.weightedSize()).isAtMost(context.maximumSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(Int.valueOf(1), Int.valueOf(1)); - assertThat(context).removalNotifications().withCause(SIZE) - .contains(Int.valueOf(1), Int.valueOf(20)); - assertThat(context).removalNotifications().hasSize(2); - assertThat(context).evictionNotifications().withCause(SIZE) - .contains(Int.valueOf(1), Int.valueOf(20)).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.TEN, weigher = CacheWeigher.VALUE, - initialCapacity = InitialCapacity.EXCESSIVE, removalListener = Listener.CONSUMING) - public void evict_update_entryTooBig_protected( - BoundedLocalCache cache, CacheContext context) { - IntStream.rangeClosed(1, 10).forEach(i -> { - cache.put(Int.valueOf(i), Int.valueOf(1)); - cache.get(Int.valueOf(1)); - }); - cache.cleanUp(); - var lookupKey = cache.nodeFactory.newLookupKey(Int.valueOf(1)); - assertThat(cache.data.get(lookupKey).inMainProtected()).isTrue(); - cache.put(Int.valueOf(1), Int.valueOf(20)); - assertThat(cache.weightedSize()).isAtMost(context.maximumSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(Int.valueOf(1), Int.valueOf(1)); - assertThat(context).removalNotifications().withCause(SIZE) - .contains(Int.valueOf(1), Int.valueOf(20)); - assertThat(context).removalNotifications().hasSize(2); - assertThat(context).evictionNotifications().withCause(SIZE) - .contains(Int.valueOf(1), Int.valueOf(20)).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - values = {ReferenceType.WEAK, ReferenceType.SOFT}, removalListener = Listener.CONSUMING) - public void evict_resurrect_collected(BoundedLocalCache cache, CacheContext context) { - Int key = Int.valueOf(1); - Int oldValue = Int.valueOf(2); - Int newValue = Int.valueOf(3); - cache.put(key, oldValue); - var node = cache.data.get(cache.referenceKey(key)); - @SuppressWarnings("unchecked") - var ref = (Reference) node.getValueReference(); - ref.enqueue(); - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - var evictor = new AtomicReference(); - cache.compute(key, (k, v) -> { - assertThat(v).isNull(); - ConcurrentTestHarness.execute(() -> { - evictor.set(Thread.currentThread()); - started.set(true); - cache.cleanUp(); - done.set(true); - }); - await().untilTrue(started); - var threadState = EnumSet.of(BLOCKED, WAITING); - await().until(() -> threadState.contains(evictor.get().getState())); - - return newValue; - }); - await().untilTrue(done); - assertThat(node.getValue()).isEqualTo(newValue); - assertThat(context).notifications().withCause(COLLECTED) - .contains(key, null).exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, - population = Population.EMPTY, maximumSize = Maximum.UNREACHABLE, - weigher = CacheWeigher.COLLECTION) - public void evict_resurrect_weight(Cache> cache, CacheContext context) { - Int key = Int.valueOf(1); - var value = intern(List.of(key)); - cache.put(key, value); - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - var evictor = new AtomicReference(); - cache.asMap().compute(key, (k, v) -> { - ConcurrentTestHarness.execute(() -> { - evictor.set(Thread.currentThread()); - started.set(true); - cache.policy().eviction().orElseThrow().setMaximum(0); - done.set(true); - }); - await().untilTrue(started); - var threadState = EnumSet.of(BLOCKED, WAITING); - await().until(() -> threadState.contains(evictor.get().getState())); - return List.of(); - }); - await().untilTrue(done); - assertThat(cache).containsEntry(key, List.of()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(Map.entry(key, value)).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, - population = Population.EMPTY, mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void evict_resurrect_expireAfter(Cache cache, CacheContext context) { - Int key = Int.valueOf(1); - cache.put(key, key); - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - var evictor = new AtomicReference(); - context.ticker().advance(Duration.ofHours(1)); - cache.asMap().compute(key, (k, v) -> { - ConcurrentTestHarness.execute(() -> { - evictor.set(Thread.currentThread()); - started.set(true); - cache.cleanUp(); - done.set(true); - }); - await().untilTrue(started); - var threadState = EnumSet.of(BLOCKED, WAITING); - await().until(() -> threadState.contains(evictor.get().getState())); - return key.negate(); - }); - await().untilTrue(done); - assertThat(cache).containsEntry(key, key.negate()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(key, key).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, - population = Population.EMPTY, expireAfterAccess = Expire.FOREVER) - public void evict_resurrect_expireAfterAccess(Cache cache, CacheContext context) { - Int key = Int.valueOf(1); - cache.put(key, key); - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - var evictor = new AtomicReference(); - context.ticker().advance(Duration.ofMinutes(1)); - cache.asMap().compute(key, (k, v) -> { - ConcurrentTestHarness.execute(() -> { - evictor.set(Thread.currentThread()); - started.set(true); - cache.policy().expireAfterAccess().orElseThrow().setExpiresAfter(Duration.ZERO); - done.set(true); - }); - await().untilTrue(started); - var threadState = EnumSet.of(BLOCKED, WAITING); - await().until(() -> threadState.contains(evictor.get().getState())); - cache.policy().expireAfterAccess().orElseThrow().setExpiresAfter(Duration.ofHours(1)); - return v; - }); - await().untilTrue(done); - assertThat(cache).containsEntry(key, key); - assertThat(context).notifications().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, - population = Population.EMPTY, expireAfterWrite = Expire.FOREVER) - public void evict_resurrect_expireAfterWrite(Cache cache, CacheContext context) { - Int key = Int.valueOf(1); - cache.put(key, key); - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - var evictor = new AtomicReference(); - context.ticker().advance(Duration.ofMinutes(1)); - cache.asMap().compute(key, (k, v) -> { - ConcurrentTestHarness.execute(() -> { - evictor.set(Thread.currentThread()); - started.set(true); - cache.policy().expireAfterWrite().orElseThrow().setExpiresAfter(Duration.ZERO); - done.set(true); - }); - await().untilTrue(started); - var threadState = EnumSet.of(BLOCKED, WAITING); - await().until(() -> threadState.contains(evictor.get().getState())); - cache.policy().expireAfterWrite().orElseThrow().setExpiresAfter(Duration.ofHours(1)); - return v; - }); - await().untilTrue(done); - assertThat(cache).containsEntry(key, key); - assertThat(context).notifications().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, - population = Population.EMPTY, expireAfterWrite = Expire.ONE_MINUTE) - public void evict_resurrect_expireAfterWrite_entry(Cache cache, CacheContext context) { - Int key = Int.valueOf(1); - cache.put(key, key); - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - var evictor = new AtomicReference(); - context.ticker().advance(Duration.ofHours(1)); - cache.asMap().compute(key, (k, v) -> { - ConcurrentTestHarness.execute(() -> { - evictor.set(Thread.currentThread()); - started.set(true); - cache.cleanUp(); - done.set(true); - }); - await().untilTrue(started); - var threadState = EnumSet.of(BLOCKED, WAITING); - await().until(() -> threadState.contains(evictor.get().getState())); - return key.negate(); - }); - await().untilTrue(done); - assertThat(cache).containsEntry(key, key.negate()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(key, key).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - expiry = CacheExpiry.CREATE, expiryTime = Expire.ONE_MINUTE) - public void evict_resurrect_expireAfterVar( - BoundedLocalCache cache, CacheContext context) { - Int key = Int.valueOf(1); - cache.put(key, key); - var node = cache.data.get(cache.referenceKey(key)); - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - var evictor = new AtomicReference(); - synchronized (node) { - context.ticker().advance(Duration.ofHours(1)); - ConcurrentTestHarness.execute(() -> { - evictor.set(Thread.currentThread()); - started.set(true); - cache.cleanUp(); - done.set(true); - }); - await().untilTrue(started); - var threadState = EnumSet.of(BLOCKED, WAITING); - await().until(() -> threadState.contains(evictor.get().getState())); - node.setVariableTime(context.ticker().read() + TimeUnit.DAYS.toNanos(1)); - } - await().untilTrue(done); - assertThat(cache).containsEntry(key, key); - assertThat(context).notifications().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED, - keys = ReferenceType.WEAK, removalListener = Listener.CONSUMING) - public void evict_collected_candidate(BoundedLocalCache cache, CacheContext context) { - var candidate = cache.accessOrderWindowDeque().getFirst(); - var value = candidate.getValue(); - @SuppressWarnings("unchecked") - var keyReference = (WeakKeyReference) candidate.getKeyReference(); - keyReference.clear(); - cache.put(context.absentKey(), context.absentValue()); - assertThat(context).notifications().withCause(COLLECTED) - .contains(null, value).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED, - keys = ReferenceType.WEAK, removalListener = Listener.CONSUMING) - public void evict_collected_victim(BoundedLocalCache cache, CacheContext context) { - var victim = cache.accessOrderProbationDeque().getFirst(); - var value = victim.getValue(); - @SuppressWarnings("unchecked") - var keyReference = (WeakKeyReference) victim.getKeyReference(); - keyReference.clear(); - cache.setMaximumSize(cache.size() - 1); - cache.cleanUp(); - assertThat(context).notifications().withCause(COLLECTED) - .contains(null, value).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, maximumSize = Maximum.FULL) - public void updateRecency_onGet(BoundedLocalCache cache, CacheContext context) { - var first = firstBeforeAccess(cache, context); - updateRecency(cache, context, () -> cache.get(first.getKey())); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, maximumSize = Maximum.FULL) - public void updateRecency_onPutIfAbsent(BoundedLocalCache cache, CacheContext context) { - var first = firstBeforeAccess(cache, context); - updateRecency(cache, context, () -> cache.putIfAbsent(first.getKey(), first.getKey())); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, maximumSize = Maximum.FULL) - public void updateRecency_onPut(BoundedLocalCache cache, CacheContext context) { - var first = firstBeforeAccess(cache, context); - updateRecency(cache, context, () -> cache.put(first.getKey(), first.getKey())); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, maximumSize = Maximum.FULL) - public void updateRecency_onReplace(BoundedLocalCache cache, CacheContext context) { - var first = firstBeforeAccess(cache, context); - updateRecency(cache, context, () -> cache.replace(first.getKey(), first.getKey())); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, maximumSize = Maximum.FULL) - public void updateRecency_onReplaceConditionally( - BoundedLocalCache cache, CacheContext context) { - var first = firstBeforeAccess(cache, context); - Int value = first.getValue(); - - updateRecency(cache, context, () -> cache.replace(first.getKey(), value, value)); - } - - private static Node firstBeforeAccess( - BoundedLocalCache cache, CacheContext context) { - return context.isZeroWeighted() - ? cache.accessOrderWindowDeque().peek() - : cache.accessOrderProbationDeque().peek(); - } - - private static void updateRecency(BoundedLocalCache cache, - CacheContext context, Runnable operation) { - var first = firstBeforeAccess(cache, context); - operation.run(); - cache.maintenance(null); - if (context.isZeroWeighted()) { - assertThat(cache.accessOrderWindowDeque().peekFirst()).isNotEqualTo(first); - assertThat(cache.accessOrderWindowDeque().peekLast()).isEqualTo(first); - } else { - assertThat(cache.accessOrderProbationDeque().peekFirst()).isNotEqualTo(first); - assertThat(cache.accessOrderProtectedDeque().peekLast()).isEqualTo(first); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, maximumSize = Maximum.FULL) - public void exceedsMaximumBufferSize_onRead( - BoundedLocalCache cache, CacheContext context) { - var dummy = cache.nodeFactory.newNode( - new WeakKeyReference<>(null, null), null, null, 1, 0); - cache.frequencySketch().ensureCapacity(1); - var buffer = cache.readBuffer; - for (int i = 0; i < BoundedBuffer.BUFFER_SIZE; i++) { - buffer.offer(dummy); - } - assertThat(buffer.offer(dummy)).isEqualTo(Buffer.FULL); - cache.afterRead(dummy, 0, true); - assertThat(buffer.offer(dummy)).isNotEqualTo(Buffer.FULL); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, maximumSize = Maximum.FULL) - public void exceedsMaximumBufferSize_onWrite( - BoundedLocalCache cache, CacheContext context) { - var ran = new boolean[1]; - cache.afterWrite(() -> ran[0] = true); - assertThat(ran[0]).isTrue(); - assertThat(cache.writeBuffer).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - expiry = CacheExpiry.DISABLED, keys = ReferenceType.STRONG, values = ReferenceType.STRONG) - public void fastpath(BoundedLocalCache cache, CacheContext context) { - assertThat(cache.skipReadBuffer()).isTrue(); - IntStream.iterate(0, i -> i < (context.maximumSize() / 2) - 1, i -> i + 1).forEach(i -> cache.put(Int.valueOf(i), Int.valueOf(-i))); - assertThat(cache.skipReadBuffer()).isTrue(); - cache.put(Int.valueOf(-1), Int.valueOf(-1)); - assertThat(cache.skipReadBuffer()).isFalse(); - assertThat(cache.get(Int.valueOf(0))).isNotNull(); - assertThat(cache.readBuffer.writes()).isEqualTo(1); - cache.cleanUp(); - assertThat(cache.readBuffer.reads()).isEqualTo(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, maximumSize = Maximum.FULL) - public void drain_onRead(BoundedLocalCache cache, CacheContext context) { - var buffer = cache.readBuffer; - IntStream.range(0, BoundedBuffer.BUFFER_SIZE).mapToObj(i -> context.firstKey()).forEach(cache::get); - long pending = buffer.size(); - assertThat(buffer.writes()).isEqualTo(pending); - assertThat(pending).isEqualTo(BoundedBuffer.BUFFER_SIZE); - cache.get(context.firstKey()); - assertThat(buffer.size()).isEqualTo(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, maximumSize = Maximum.FULL) - public void drain_onRead_absent(BoundedLocalCache cache, CacheContext context) { - var buffer = cache.readBuffer; - cache.get(context.firstKey()); - assertThat(buffer.size()).isEqualTo(1); - assertThat(cache.get(context.absentKey())).isNull(); - assertThat(buffer.size()).isEqualTo(1); - cache.drainStatus = REQUIRED; - assertThat(cache.get(context.absentKey())).isNull(); - assertThat(buffer.size()).isEqualTo(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, maximumSize = Maximum.FULL) - public void drain_onWrite(BoundedLocalCache cache, CacheContext context) { - cache.put(Int.valueOf(1), Int.valueOf(1)); - int size = cache.accessOrderWindowDeque().size() + cache.accessOrderProbationDeque().size(); - assertThat(cache.writeBuffer).isEmpty(); - assertThat(size).isEqualTo(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, maximumSize = Maximum.FULL) - public void drain_nonblocking(BoundedLocalCache cache, CacheContext context) { - var done = new AtomicBoolean(); - Runnable task = () -> { - cache.setDrainStatusRelease(REQUIRED); - cache.scheduleDrainBuffers(); - done.set(true); - }; - cache.evictionLock.lock(); - try { - ConcurrentTestHarness.execute(task); - await().untilTrue(done); - } finally { - cache.evictionLock.unlock(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, maximumSize = Maximum.FULL) - public void drain_blocksClear(BoundedLocalCache cache, CacheContext context) { - checkDrainBlocks(cache, cache::clear); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, maximumSize = Maximum.FULL) - public void drain_blocksOrderedMap(BoundedLocalCache cache, - CacheContext context, Eviction eviction) { - checkDrainBlocks(cache, () -> eviction.coldest(((int) context.maximumSize()))); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, maximumSize = Maximum.FULL) - public void drain_blocksCapacity(BoundedLocalCache cache, - CacheContext context, Eviction eviction) { - checkDrainBlocks(cache, () -> eviction.setMaximum(0)); - } - - void checkDrainBlocks(BoundedLocalCache cache, Runnable task) { - var done = new AtomicBoolean(); - var lock = cache.evictionLock; - lock.lock(); - try { - ConcurrentTestHarness.execute(() -> { - cache.setDrainStatusRelease(REQUIRED); - task.run(); - done.set(true); - }); - await().until(lock::hasQueuedThreads); - } finally { - lock.unlock(); - } - await().untilTrue(done); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, - maximumSize = Maximum.FULL, weigher = {CacheWeigher.DISABLED, CacheWeigher.TEN}) - public void adapt_increaseWindow(BoundedLocalCache cache, CacheContext context) { - prepareForAdaption(cache, context, false); - int sampleSize = cache.frequencySketch().sampleSize; - long protectedSize = cache.mainProtectedWeightedSize(); - long protectedMaximum = cache.mainProtectedMaximum(); - long windowSize = cache.windowWeightedSize(); - long windowMaximum = cache.windowMaximum(); - adapt(cache, sampleSize); - assertThat(cache.mainProtectedWeightedSize()).isLessThan(protectedSize); - assertThat(cache.mainProtectedMaximum()).isLessThan(protectedMaximum); - assertThat(cache.windowWeightedSize()).isGreaterThan(windowSize); - assertThat(cache.windowMaximum()).isGreaterThan(windowMaximum); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.FULL, - maximumSize = Maximum.FULL, weigher = {CacheWeigher.DISABLED, CacheWeigher.TEN}) - public void adapt_decreaseWindow(BoundedLocalCache cache, CacheContext context) { - prepareForAdaption(cache, context, true); - int sampleSize = cache.frequencySketch().sampleSize; - long protectedSize = cache.mainProtectedWeightedSize(); - long protectedMaximum = cache.mainProtectedMaximum(); - long windowSize = cache.windowWeightedSize(); - long windowMaximum = cache.windowMaximum(); - adapt(cache, sampleSize); - assertThat(cache.mainProtectedWeightedSize()).isGreaterThan(protectedSize); - assertThat(cache.mainProtectedMaximum()).isGreaterThan(protectedMaximum); - assertThat(cache.windowWeightedSize()).isLessThan(windowSize); - assertThat(cache.windowMaximum()).isLessThan(windowMaximum); - } - - private void prepareForAdaption(BoundedLocalCache cache, - CacheContext context, boolean recencyBias) { - cache.setStepSize((recencyBias ? 1 : -1) * Math.abs(cache.stepSize())); - cache.setWindowMaximum((long) (0.5 * context.maximumWeightOrSize())); - cache.setMainProtectedMaximum((long) - (PERCENT_MAIN_PROTECTED * (context.maximumWeightOrSize() - cache.windowMaximum()))); - cache.clear(); - cache.putAll(context.original()); - cache.keySet().forEach(cache::get); - cache.keySet().forEach(cache::get); - } - - private void adapt(BoundedLocalCache cache, int sampleSize) { - cache.setPreviousSampleHitRate(0.80); - cache.setMissesInSample(sampleSize / 2); - cache.setHitsInSample(sampleSize - cache.missesInSample()); - cache.climb(); - cache.keySet().forEach(cache::get); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - initialCapacity = InitialCapacity.FULL, expireAfterWrite = Expire.ONE_MINUTE) - public void put_expireTolerance_expireAfterWrite( - BoundedLocalCache cache, CacheContext context) { - boolean mayCheckReads = context.isStrongKeys() && context.isStrongValues() - && (cache.readBuffer != Buffer.>disabled()); - - cache.put(Int.valueOf(1), Int.valueOf(1)); - assertThat(cache.writeBuffer.producerIndex).isEqualTo(2); - cache.put(Int.valueOf(1), Int.valueOf(2)); - if (mayCheckReads) { - assertThat(cache.readBuffer.reads()).isEqualTo(0); - assertThat(cache.readBuffer.writes()).isEqualTo(1); - } - assertThat(cache.writeBuffer.producerIndex).isEqualTo(2); - context.ticker().advance(EXPIRE_WRITE_TOLERANCE + 1, TimeUnit.NANOSECONDS); - cache.put(Int.valueOf(1), Int.valueOf(3)); - if (mayCheckReads) { - assertThat(cache.readBuffer.reads()).isEqualTo(1); - assertThat(cache.readBuffer.writes()).isEqualTo(1); - } - assertThat(cache.writeBuffer.producerIndex).isEqualTo(4); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE) - public void put_expireTolerance_expiry(BoundedLocalCache cache, CacheContext context) { - cache.put(Int.valueOf(1), Int.valueOf(1)); - assertThat(cache.writeBuffer.producerIndex).isEqualTo(2); - cache.put(Int.valueOf(1), Int.valueOf(2)); - assertThat(cache.readBuffer.reads()).isEqualTo(0); - assertThat(cache.readBuffer.writes()).isEqualTo(1); - assertThat(cache.writeBuffer.producerIndex).isEqualTo(2); - context.ticker().advance(EXPIRE_WRITE_TOLERANCE + 1, TimeUnit.NANOSECONDS); - cache.put(Int.valueOf(1), Int.valueOf(3)); - assertThat(cache.readBuffer.reads()).isEqualTo(1); - assertThat(cache.readBuffer.writes()).isEqualTo(1); - assertThat(cache.writeBuffer.producerIndex).isEqualTo(4); - when(context.expiry().expireAfterUpdate(any(), any(), anyLong(), anyLong())) - .thenReturn(Expire.ONE_MILLISECOND.timeNanos()); - cache.put(Int.valueOf(1), Int.valueOf(4)); - assertThat(cache.readBuffer.reads()).isEqualTo(1); - assertThat(cache.readBuffer.writes()).isEqualTo(1); - assertThat(cache.writeBuffer.producerIndex).isEqualTo(6); - when(context.expiry().expireAfterUpdate(any(), any(), anyLong(), anyLong())) - .thenReturn(Expire.FOREVER.timeNanos()); - cache.put(Int.valueOf(1), Int.valueOf(4)); - assertThat(cache.readBuffer.reads()).isEqualTo(1); - assertThat(cache.readBuffer.writes()).isEqualTo(1); - assertThat(cache.writeBuffer.producerIndex).isEqualTo(8); - } - - @CheckMaxLogLevel(WARN) - @Test(dataProvider = "caches", groups = "isolated") - @CacheSpec(population = Population.EMPTY, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - refreshAfterWrite = Expire.DISABLED, expiry = CacheExpiry.DISABLED, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.DISABLED, - compute = Compute.SYNC, loader = Loader.DISABLED, stats = Stats.DISABLED, - removalListener = Listener.DISABLED, evictionListener = Listener.DISABLED, - keys = ReferenceType.STRONG, values = ReferenceType.STRONG) - public void put_warnIfEvictionBlocked(BoundedLocalCache cache, CacheContext context) { - var testLogger = new AtomicReference(); - var thread = new AtomicReference(); - var done = new AtomicBoolean(); - cache.evictionLock.lock(); - try { - ConcurrentTestHarness.execute(() -> { - var logger = TestLoggerFactory.getTestLogger(BoundedLocalCache.class); - logger.setEnabledLevels(WARN_LEVELS); - thread.set(Thread.currentThread()); - testLogger.set(logger); - for (int i = 0; true; i++) { - if (done.get()) { - return; - } - cache.put(Int.valueOf(i), Int.valueOf(i)); - } - }); - var halfWaitTime = Duration.ofNanos(WARN_AFTER_LOCK_WAIT_NANOS / 2); - await().until(cache.evictionLock::hasQueuedThreads); - thread.get().interrupt(); - Uninterruptibles.sleepUninterruptibly(halfWaitTime); - assertThat(cache.evictionLock.hasQueuedThreads()).isTrue(); - assertThat(testLogger.get().getAllLoggingEvents()).isEmpty(); - Uninterruptibles.sleepUninterruptibly(halfWaitTime.plusMillis(500)); - await().until(() -> !testLogger.get().getAllLoggingEvents().isEmpty()); - assertThat(cache.evictionLock.hasQueuedThreads()).isTrue(); - } finally { - done.set(true); - cache.evictionLock.unlock(); - } - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, keys = ReferenceType.STRONG) - public void put_spinToCompute(BoundedLocalCache cache, CacheContext context) { - cache.put(context.absentKey(), context.absentValue()); - var node = cache.data.get(context.absentKey()); - node.retire(); - cache.data.compute(context.absentKey(), (k, n) -> { - var writer = new AtomicReference(); - ConcurrentTestHarness.execute(() -> { - writer.set(Thread.currentThread()); - cache.put(context.absentKey(), context.absentKey()); - }); - var threadState = EnumSet.of(BLOCKED, WAITING); - await().untilAtomic(writer, is(not(nullValue()))); - await().until(() -> threadState.contains(writer.get().getState())); - return null; - }); - await().untilAsserted(() -> - assertThat(cache).containsEntry(context.absentKey(), context.absentKey())); - cache.afterWrite(cache.new RemovalTask(node)); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE) - public void putIfAbsent_expireAfterRead(BoundedLocalCache cache, CacheContext context) { - var node = cache.data.get(cache.nodeFactory.newLookupKey(context.firstKey())); - context.ticker().advance(1, TimeUnit.HOURS); - var result = new AtomicReference(); - long currentDuration = 1; - synchronized (node) { - var started = new AtomicBoolean(); - var thread = new AtomicReference(); - ConcurrentTestHarness.execute(() -> { - thread.set(Thread.currentThread()); - started.set(true); - var value = cache.putIfAbsent(context.firstKey(), context.absentValue()); - result.set(value); - }); - await().untilTrue(started); - var threadState = EnumSet.of(BLOCKED, WAITING); - await().until(() -> threadState.contains(thread.get().getState())); - node.setVariableTime(context.ticker().read() + currentDuration); - } - var expected = context.original().get(context.firstKey()); - await().untilAtomic(result, is(expected)); - assertThat(node.getVariableTime()).isEqualTo( - context.ticker().read() + context.expiryTime().timeNanos()); - verify(context.expiry()).expireAfterRead(context.firstKey(), - context.absentValue(), context.ticker().read(), currentDuration); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - scheduler = CacheScheduler.MOCKITO, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void unschedule_cleanUp(BoundedLocalCache cache, CacheContext context) { - var future = mock(Future.class); - doReturn(future).when(context.scheduler()).schedule(any(), any(), anyLong(), any()); - IntStream.range(0, 10).forEach(i -> cache.put(Int.valueOf(i), Int.valueOf(-i))); - assertThat(cache.pacer().nextFireTime).isNotEqualTo(0); - assertThat(cache.pacer().future).isNotNull(); - context.ticker().advance(1, TimeUnit.HOURS); - cache.cleanUp(); - verify(future).cancel(false); - assertThat(cache.pacer().nextFireTime).isEqualTo(0); - assertThat(cache.pacer().future).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, population = Population.EMPTY, - scheduler = CacheScheduler.MOCKITO, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void unschedule_invalidateAll(BoundedLocalCache cache, CacheContext context) { - var future = mock(Future.class); - doReturn(future).when(context.scheduler()).schedule(any(), any(), anyLong(), any()); - IntStream.range(0, 10).forEach(i -> cache.put(Int.valueOf(i), Int.valueOf(-i))); - assertThat(cache.pacer().nextFireTime).isNotEqualTo(0); - assertThat(cache.pacer().future).isNotNull(); - cache.clear(); - verify(future).cancel(false); - assertThat(cache.pacer().nextFireTime).isEqualTo(0); - assertThat(cache.pacer().future).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expireAfterAccess = Expire.ONE_MINUTE, - maximumSize = {Maximum.DISABLED, Maximum.FULL}, weigher = CacheWeigher.DISABLED) - public void expirationDelay_window(BoundedLocalCache cache, CacheContext context) { - int maximum = cache.evicts() ? (int) context.maximumSize() : 100; - long stepSize = context.expireAfterAccess().timeNanos() / (2 * maximum); - IntStream.range(0, maximum).mapToObj(i -> intern(Int.valueOf(i))).forEach(key -> { - cache.put(key, key); - context.ticker().advance(stepSize, TimeUnit.NANOSECONDS); - }); - if (cache.evicts()) { - for (var node : List.copyOf(cache.accessOrderProbationDeque())) { - node.setAccessTime(context.ticker().read()); - } - for (var node : List.copyOf(cache.accessOrderProtectedDeque())) { - node.setAccessTime(context.ticker().read()); - } - for (var node : cache.accessOrderProbationDeque().stream().skip(5).toList()) { - cache.get(node.getKey()); - } - context.ticker().advance(stepSize, TimeUnit.NANOSECONDS); - cache.cleanUp(); - } - - var expectedDelay = context.expireAfterAccess().timeNanos() - - (context.ticker().read() - cache.accessOrderWindowDeque().getFirst().getAccessTime()); - assertThat(cache.getExpirationDelay(context.ticker().read())).isEqualTo(expectedDelay); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expireAfterAccess = Expire.ONE_MINUTE, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED) - public void expirationDelay_probation(BoundedLocalCache cache, CacheContext context) { - long stepSize = context.expireAfterAccess().timeNanos() / (2 * context.maximumSize()); - for (int i = 0; i < (int) context.maximumSize(); i++) { - var key = intern(Int.valueOf(i)); - cache.put(key, key); - context.ticker().advance(stepSize, TimeUnit.NANOSECONDS); - } - - for (var node : List.copyOf(cache.accessOrderWindowDeque())) { - node.setAccessTime(context.ticker().read()); - } - for (var node : List.copyOf(cache.accessOrderProtectedDeque())) { - node.setAccessTime(context.ticker().read()); - } - for (var node : cache.accessOrderProbationDeque().stream().skip(5).toList()) { - cache.get(node.getKey()); - } - context.ticker().advance(stepSize, TimeUnit.NANOSECONDS); - cache.cleanUp(); - - var expectedDelay = context.expireAfterAccess().timeNanos() - - (context.ticker().read() - cache.accessOrderProbationDeque().getFirst().getAccessTime()); - assertThat(cache.getExpirationDelay(context.ticker().read())).isEqualTo(expectedDelay); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expireAfterAccess = Expire.ONE_MINUTE, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED) - public void expirationDelay_protected(BoundedLocalCache cache, CacheContext context) { - long stepSize = context.expireAfterAccess().timeNanos() / (2 * context.maximumSize()); - IntStream.range(0, (int) context.maximumSize()).mapToObj(i -> intern(Int.valueOf(i))).forEach(key -> { - cache.put(key, key); - context.ticker().advance(stepSize, TimeUnit.NANOSECONDS); - }); - cache.accessOrderProbationDeque().stream().skip(5).toList().stream().map(Node::getKey).forEach(cache::get); - context.ticker().advance(stepSize, TimeUnit.NANOSECONDS); - cache.cleanUp(); - List.copyOf(cache.accessOrderWindowDeque()).forEach(node -> node.setAccessTime(context.ticker().read())); - List.copyOf(cache.accessOrderProbationDeque()).forEach(node -> node.setAccessTime(context.ticker().read())); - var expectedDelay = context.expireAfterAccess().timeNanos() - - (context.ticker().read() - cache.accessOrderProtectedDeque().getFirst().getAccessTime()); - assertThat(cache.getExpirationDelay(context.ticker().read())).isEqualTo(expectedDelay); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expireAfterWrite = Expire.ONE_MINUTE, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED) - public void expirationDelay_writeOrder(BoundedLocalCache cache, CacheContext context) { - long stepSize = context.expireAfterWrite().timeNanos() / (2 * context.maximumSize()); - IntStream.range(0, (int) context.maximumSize()).mapToObj(i -> intern(Int.valueOf(i))).forEach(key -> { - cache.put(key, key); - context.ticker().advance(stepSize, TimeUnit.NANOSECONDS); - }); - for (var key : cache.keySet()) { - cache.get(key); - } - cache.cleanUp(); - var expectedDelay = context.expireAfterWrite().timeNanos() - - (context.ticker().read() - cache.writeOrderDeque().getFirst().getWriteTime()); - assertThat(cache.getExpirationDelay(context.ticker().read())).isEqualTo(expectedDelay); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, maximumSize = {Maximum.DISABLED, Maximum.FULL}, - expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void expirationDelay_varTime(BoundedLocalCache cache, CacheContext context) { - long startTime = context.ticker().read(); - int maximum = cache.evicts() ? (int) context.maximumSize() : 100; - long stepSize = context.expiryTime().timeNanos() / (2 * maximum); - IntStream.range(0, maximum).mapToObj(i -> intern(Int.valueOf(i))).forEach(key -> { - cache.put(key, key); - context.ticker().advance(stepSize, TimeUnit.NANOSECONDS); - }); - cache.keySet().forEach(cache::get); - cache.cleanUp(); - var expectedDelay = context.expiryTime().timeNanos() - (context.ticker().read() - startTime); - assertThat(cache.getExpirationDelay(context.ticker().read())).isIn( - Range.closed(expectedDelay - TimerWheel.SPANS[0], expectedDelay)); - } - - /* --------------- Refresh --------------- */ - - @Test(dataProvider = "caches") // Issue #715 - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - refreshAfterWrite = Expire.ONE_MINUTE, executor = CacheExecutor.THREADED, - compute = Compute.SYNC, stats = Stats.DISABLED) - public void refreshIfNeeded_liveliness(CacheContext context) { - var stats = mock(StatsCounter.class); - context.caffeine().recordStats(() -> stats); - var refreshEntry = new AtomicReference>(); - var cache = asBoundedLocalCache(context.build(new CacheLoader<>() { - @Override - public Int load(Object key) { - throw new AssertionError(); - } - - @Override - public CompletableFuture asyncReload( - Object key, Object oldValue, Executor executor) { - refreshEntry.set(new SimpleEntry<>(key, oldValue)); - return CompletableFuture.completedFuture(key); - } - })); - cache.put(context.absentKey(), context.absentValue()); - var node = cache.data.get(context.absentKey()); - doAnswer(invocation -> { - ConcurrentTestHarness.execute(() -> cache.remove(context.absentKey())); - await().until(() -> !cache.containsKey(context.absentKey())); - assertThat(node.isRetired()).isTrue(); - return null; - }).when(stats).recordHits(1); - cache.evictionLock.lock(); - try { - context.ticker().advance(10, TimeUnit.MINUTES); - var value = cache.getIfPresent(context.absentKey(), /* recordStats */ true); - assertThat(value).isEqualTo(context.absentValue()); - assertThat(refreshEntry.get()).isNull(); - } finally { - cache.evictionLock.unlock(); - } - } - - @Test(dataProvider = "caches", groups = "isolated") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - refreshAfterWrite = Expire.ONE_MINUTE, executor = CacheExecutor.THREADED, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - expiry = CacheExpiry.DISABLED, maximumSize = Maximum.DISABLED, compute = Compute.SYNC, - removalListener = Listener.DISABLED, evictionListener = Listener.DISABLED, - stats = Stats.DISABLED) - public void refreshIfNeeded_softLock(CacheContext context) { - var refresh = new AtomicBoolean(); - var reloading = new AtomicBoolean(); - var future = new CompletableFuture(); - var newValue = context.absentValue().add(1); - var cache = context.build(new CacheLoader() { - @Override - public Int load(Int key) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture asyncReload(Int key, Int oldValue, Executor executor) { - reloading.set(true); - await().untilTrue(refresh); - return future; - } - }); - var localCache = asBoundedLocalCache(cache); - cache.put(context.absentKey(), context.absentValue()); - var lookupKey = localCache.nodeFactory.newLookupKey(context.absentKey()); - var node = localCache.data.get(lookupKey); - var refreshes = localCache.refreshes(); - context.ticker().advance(2, TimeUnit.MINUTES); - ConcurrentTestHarness.execute(() -> cache.get(context.absentKey())); - await().untilTrue(reloading); - assertThat(node.getWriteTime() & 1L).isEqualTo(1); - localCache.refreshes = null; - cache.get(context.absentKey()); - assertThat(localCache.refreshes).isNull(); - refresh.set(true); - await().untilAsserted(() -> assertThat(refreshes).isNotEmpty()); - await().untilAsserted(() -> assertThat(node.getWriteTime() & 1L).isEqualTo(0)); - future.complete(newValue); - await().untilAsserted(() -> assertThat(refreshes).isEmpty()); - await().untilAsserted(() -> assertThat(cache).containsEntry(context.absentKey(), newValue)); - } - - @Test(dataProvider = "caches", groups = "isolated") - @CacheSpec(population = Population.EMPTY, executor = CacheExecutor.THREADED, - compute = Compute.ASYNC, stats = Stats.DISABLED) - public void refresh_startReloadBeforeLoadCompletion(CacheContext context) { - var stats = mock(StatsCounter.class); - var beganLoadSuccess = new AtomicBoolean(); - var endLoadSuccess = new CountDownLatch(1); - var beganReloading = new AtomicBoolean(); - var beganLoading = new AtomicBoolean(); - var endReloading = new AtomicBoolean(); - var endLoading = new AtomicBoolean(); - context.ticker().setAutoIncrementStep(Duration.ofSeconds(1)); - context.caffeine().recordStats(() -> stats); - var asyncCache = context.buildAsync(new CacheLoader() { - @Override - public Int load(Int key) { - beganLoading.set(true); - await().untilTrue(endLoading); - return new Int(ThreadLocalRandom.current().nextInt()); - } - - @Override - public Int reload(Int key, Int oldValue) { - beganReloading.set(true); - await().untilTrue(endReloading); - return new Int(ThreadLocalRandom.current().nextInt()); - } - }); - - Answer answer = invocation -> { - beganLoadSuccess.set(true); - endLoadSuccess.await(); - return null; - }; - doAnswer(answer).when(stats).recordLoadSuccess(anyLong()); - var future1 = asyncCache.get(context.absentKey()); - await().untilTrue(beganLoading); - endLoading.set(true); - await().untilTrue(beganLoadSuccess); - var refresh = asyncCache.synchronous().refresh(context.absentKey()); - await().untilTrue(beganReloading); - endLoadSuccess.countDown(); - await().untilAsserted(() -> assertThat(future1.getNumberOfDependents()).isEqualTo(0)); - endReloading.set(true); - await().untilAsserted(() -> assertThat(refresh.getNumberOfDependents()).isEqualTo(0)); - await().untilAsserted(() -> - assertThat(asyncCache.get(context.absentKey())).succeedsWith(refresh.get())); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, keys = ReferenceType.STRONG, - maximumSize = Maximum.FULL, weigher = {CacheWeigher.DISABLED, CacheWeigher.MAX_VALUE}) - public void brokenEquality_eviction(BoundedLocalCache cache, - CacheContext context, Eviction eviction) { - var key = new MutableInt(context.absentKey().intValue()); - cache.put(key, context.absentValue()); - key.increment(); - eviction.setMaximum(0); - assertThat(Map.copyOf(cache)).isEmpty(); - assertThat(context).notifications().isEmpty(); - assertThat(cache.estimatedSize()).isEqualTo(1); - var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()); - assertThat(event.getMessage()).contains("An invalid state was detected"); - assertThat(event.getLevel()).isEqualTo(ERROR); - cache.data.clear(); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, keys = ReferenceType.STRONG, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, - expiry = CacheExpiry.CREATE, expiryTime = Expire.ONE_MINUTE) - public void brokenEquality_expiration( - BoundedLocalCache cache, CacheContext context) { - var key = new MutableInt(context.absentKey().intValue()); - cache.put(key, context.absentValue()); - key.increment(); - context.ticker().advance(1, TimeUnit.DAYS); - cache.cleanUp(); - assertThat(Map.copyOf(cache)).isEmpty(); - assertThat(context).notifications().isEmpty(); - assertThat(cache.estimatedSize()).isEqualTo(1); - var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()); - assertThat(event.getMessage()).contains("An invalid state was detected"); - assertThat(event.getLevel()).isEqualTo(ERROR); - cache.data.clear(); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, keys = ReferenceType.STRONG) - public void brokenEquality_clear(BoundedLocalCache cache, CacheContext context) { - var key = new MutableInt(context.absentKey().intValue()); - cache.put(key, context.absentValue()); - key.increment(); - cache.clear(); - assertThat(Map.copyOf(cache)).isEmpty(); - assertThat(context).notifications().isEmpty(); - assertThat(cache.estimatedSize()).isEqualTo(1); - var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()); - assertThat(event.getMessage()).contains("An invalid state was detected"); - assertThat(event.getLevel()).isEqualTo(ERROR); - cache.data.clear(); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, keys = ReferenceType.STRONG) - public void brokenEquality_put(BoundedLocalCache cache, CacheContext context) { - testForBrokenEquality(cache, context, key -> cache.put(key, context.absentValue())); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, keys = ReferenceType.STRONG) - public void brokenEquality_putIfAbsent( - BoundedLocalCache cache, CacheContext context) { - testForBrokenEquality(cache, context, key -> cache.putIfAbsent(key, context.absentValue())); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, keys = ReferenceType.STRONG) - public void brokenEquality_replace( - BoundedLocalCache cache, CacheContext context) { - testForBrokenEquality(cache, context, key -> cache.replace(key, context.absentValue())); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, keys = ReferenceType.STRONG) - public void brokenEquality_replaceConditionally( - BoundedLocalCache cache, CacheContext context) { - testForBrokenEquality(cache, context, key -> - cache.replace(key, context.absentValue(), context.absentValue().negate())); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, keys = ReferenceType.STRONG) - public void brokenEquality_remove( - BoundedLocalCache cache, CacheContext context) { - testForBrokenEquality(cache, context, cache::remove); - } - - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, keys = ReferenceType.STRONG) - public void brokenEquality_removeConditionally( - BoundedLocalCache cache, CacheContext context) { - testForBrokenEquality(cache, context, key -> cache.remove(key, context.absentValue())); - } - - private static void testForBrokenEquality(BoundedLocalCache cache, - CacheContext context, Consumer task) { - var key = new MutableInt(context.absentKey().intValue()); - cache.put(key, context.absentValue()); - key.increment(); - - cache.clear(); - key.decrement(); - - try { - task.accept(key); - Assert.fail(); - } catch (IllegalStateException e) { - assertThat(e).hasMessageThat().contains("An invalid state was detected"); - } finally { - cache.data.clear(); - } - } - - @Test - public void cacheFactory_invalid() { - try { - LocalCacheFactory.loadFactory(null, null, false, null); - Assert.fail(); - } catch (NullPointerException expected) { - } - try { - LocalCacheFactory.loadFactory(null, null, false, ""); - Assert.fail(); - } catch (IllegalStateException expected) { - assertThat(expected).hasCauseThat().isInstanceOf(ClassNotFoundException.class); - } - } - - @Test - public void nodeFactory_invalid() { - try { - NodeFactory.loadFactory(null); - Assert.fail(); - } catch (NullPointerException expected) { - } - try { - NodeFactory.loadFactory(""); - Assert.fail(); - } catch (IllegalStateException expected) { - assertThat(expected).hasCauseThat().isInstanceOf(ClassNotFoundException.class); - } - } - - @Test - public void unsupported() { - var cache = mock(BoundedLocalCache.class, InvocationOnMock::callRealMethod); - @SuppressWarnings("MethodReferenceUsage") - List methods = List.of( - cache::accessOrderWindowDeque, cache::accessOrderProbationDeque, - cache::accessOrderProtectedDeque, cache::writeOrderDeque, - cache::expiresAfterAccessNanos, () -> cache.setExpiresAfterAccessNanos(1L), - cache::expiresAfterWriteNanos, () -> cache.setExpiresAfterWriteNanos(1L), - cache::refreshAfterWriteNanos, () -> cache.setRefreshAfterWriteNanos(1L), - cache::timerWheel, cache::frequencySketch, - cache::maximum, cache::windowMaximum, - cache::mainProtectedMaximum, () -> cache.setMaximum(1L), - () -> cache.setWindowMaximum(1L), () -> cache.setMainProtectedMaximum(1L), - cache::weightedSize, cache::windowWeightedSize, - cache::mainProtectedWeightedSize, () -> cache.setWeightedSize(1L), - () -> cache.setWindowWeightedSize(0), () -> cache.setMainProtectedWeightedSize(1L), - cache::hitsInSample, cache::missesInSample, - cache::sampleCount, cache::stepSize, - cache::previousSampleHitRate, cache::adjustment, - () -> cache.setHitsInSample(1), () -> cache.setMissesInSample(1), - () -> cache.setSampleCount(1), () -> cache.setStepSize(1.0), - () -> cache.setPreviousSampleHitRate(1.0), () -> cache.setAdjustment(1L)); - methods.forEach(method -> { - try { - method.run(); - Assert.fail(); - } catch (UnsupportedOperationException expected) { - } - }); - } - - @Test - public void cleanupTask_ignore() { - var task = new PerformCleanupTask(null); - assertThat(task.getRawResult()).isNull(); - task.completeExceptionally(null); - task.setRawResult(null); - task.complete(null); - task.cancel(true); - } - - @Test(dataProviderClass = CacheProvider.class, dataProvider = "caches") - @CacheSpec(population = Population.SINGLETON, compute = Compute.SYNC, - initialCapacity = {InitialCapacity.DEFAULT, InitialCapacity.FULL}) - public void node_string(BoundedLocalCache cache, CacheContext context) { - var node = cache.data.values().iterator().next(); - var description = node.toString(); - assertThat(description).contains("key=" + node.getKey()); - assertThat(description).contains("value=" + node.getValue()); - assertThat(description).contains("weight=" + node.getWeight()); - assertThat(description).contains(String.format(US, "accessTimeNS=%,d", node.getAccessTime())); - assertThat(description).contains(String.format(US, "writeTimeNS=%,d", node.getWriteTime())); - assertThat(description).contains(String.format(US, "varTimeNs=%,d", node.getVariableTime())); - } - - @Test - public void node_unsupported() { - @SuppressWarnings("unchecked") - Node node = mock(Node.class, InvocationOnMock::callRealMethod); - @SuppressWarnings("MethodReferenceUsage") - List methods = List.of( - node::getPreviousInVariableOrder, node::getNextInVariableOrder, - () -> node.setPreviousInVariableOrder(node), () -> node.setNextInVariableOrder(node), - () -> node.setPreviousInAccessOrder(node), () -> node.setNextInAccessOrder(node), - () -> node.setPreviousInWriteOrder(node), () -> node.setNextInWriteOrder(node), - () -> node.casVariableTime(1L, 2L), () -> node.casWriteTime(1L, 2L), - () -> node.setQueueType(WINDOW)); - for (var method : methods) { - try { - method.run(); - Assert.fail(); - } catch (UnsupportedOperationException expected) { - } - } - } - - @Test - public void node_ignored() { - var node = mock(Node.class, InvocationOnMock::callRealMethod); - List methods = List.of(() -> node.setVariableTime(1L), - () -> node.setAccessTime(1L), () -> node.setWriteTime(1L)); - for (var method : methods) { - method.run(); - } - } - - @Test - @SuppressWarnings("unchecked") - public void policy_unsupported() { - Policy policy = mock(Policy.class, InvocationOnMock::callRealMethod); - var eviction = mock(Eviction.class, InvocationOnMock::callRealMethod); - var fixedExpiration = mock(FixedExpiration.class, InvocationOnMock::callRealMethod); - var varExpiration = mock(VarExpiration.class, InvocationOnMock::callRealMethod); - List methods = List.of(() -> policy.getEntryIfPresentQuietly(new Object()), - () -> eviction.coldestWeighted(1L), () -> eviction.coldest(identity()), - () -> eviction.hottestWeighted(1L), () -> eviction.hottest(identity()), - () -> fixedExpiration.oldest(identity()), () -> fixedExpiration.youngest(identity()), - () -> varExpiration.compute(new Object(), (k, v) -> v, Duration.ZERO), - () -> varExpiration.oldest(identity()), () -> varExpiration.youngest(identity())); - for (var method : methods) { - try { - method.run(); - Assert.fail(); - } catch (UnsupportedOperationException expected) { - } - } - } - - @Test - public void fixedExpireAfterWrite() { - int key = 1; - int value = 2; - long duration = TimeUnit.DAYS.toNanos(1); - long currentTime = ThreadLocalRandom.current().nextLong(); - long currentDuration = ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); - var expiry = new FixedExpireAfterWrite<>(1, TimeUnit.DAYS); - assertThat(expiry.expireAfterCreate(key, value, currentTime)).isEqualTo(duration); - assertThat(expiry.expireAfterUpdate( - key, value, currentTime, currentDuration)).isEqualTo(duration); - assertThat(expiry.expireAfterRead( - key, value, currentTime, currentDuration)).isEqualTo(currentDuration); - } - - static BoundedLocalCache asBoundedLocalCache(Cache cache) { - return (BoundedLocalCache) cache.asMap(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CacheTest.java deleted file mode 100644 index 8fe4805..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CacheTest.java +++ /dev/null @@ -1,974 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.Policy.Eviction; -import com.github.benmanes.caffeine.cache.Policy.FixedExpiration; -import com.github.benmanes.caffeine.cache.Policy.FixedRefresh; -import com.github.benmanes.caffeine.cache.Policy.VarExpiration; -import com.github.benmanes.caffeine.cache.SnapshotEntry.*; -import com.github.benmanes.caffeine.cache.stats.CacheStats; -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSetMultimap; -import com.google.common.collect.Iterables; -import com.google.common.primitives.Ints; -import com.google.common.testing.EqualsTester; -import com.google.common.testing.NullPointerTester; -import org.apache.commons.lang3.tuple.Triple; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.io.InvalidObjectException; -import java.io.ObjectInputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.*; -import java.util.function.Function; -import java.util.stream.IntStream; - -import static com.github.benmanes.caffeine.cache.RemovalCause.EXPLICIT; -import static com.github.benmanes.caffeine.cache.RemovalCause.REPLACED; -import static com.github.benmanes.caffeine.cache.testing.CacheContext.intern; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; -import static java.util.function.Function.identity; -import static uk.org.lidalia.slf4jext.Level.ERROR; -import static uk.org.lidalia.slf4jext.Level.WARN; - -@CheckNoEvictions -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@Test(dataProviderClass = CacheProvider.class) -public final class CacheTest { - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void estimatedSize(Cache cache, CacheContext context) { - assertThat(cache.estimatedSize()).isEqualTo(context.initialSize()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getIfPresent_nullKey(Cache cache, CacheContext context) { - cache.getIfPresent(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getIfPresent_absent(Cache cache, CacheContext context) { - assertThat(cache.getIfPresent(context.absentKey())).isNull(); - assertThat(context).stats().hits(0).misses(1).success(0).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void getIfPresent_present(Cache cache, CacheContext context) { - assertThat(cache.getIfPresent(context.firstKey())).isNotNull(); - assertThat(cache.getIfPresent(context.middleKey())).isNotNull(); - assertThat(cache.getIfPresent(context.lastKey())).isNotNull(); - assertThat(context).stats().hits(3).misses(0).success(0).failures(0); - } - - /* --------------- get --------------- */ - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void get_nullKey(Cache cache, CacheContext context) { - cache.get(null, identity()); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void get_nullLoader(Cache cache, CacheContext context) { - cache.get(context.absentKey(), null); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void get_nullKeyAndLoader(Cache cache, CacheContext context) { - cache.get(null, null); - } - - @CacheSpec - @Test(dataProvider = "caches") - public void get_absent_null(Cache cache, CacheContext context) { - assertThat(cache.get(context.absentKey(), k -> null)).isNull(); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void get_absent_throwsException(Cache cache, CacheContext context) { - try { - cache.get(context.absentKey(), key -> { - throw new IllegalStateException(); - }); - } finally { - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = UnknownError.class) - public void get_absent_throwsError(Cache cache, CacheContext context) { - try { - cache.get(context.absentKey(), key -> { - throw new UnknownError(); - }); - } finally { - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - } - - @CacheSpec - @Test(dataProvider = "caches") - public void get_absent(Cache cache, CacheContext context) { - Int key = context.absentKey(); - Int value = cache.get(key, k -> context.absentValue()); - assertThat(value).isEqualTo(context.absentValue()); - assertThat(context).stats().hits(0).misses(1).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void get_present(Cache cache, CacheContext context) { - Function loader = key -> { - throw new RuntimeException(); - }; - assertThat(cache.get(context.firstKey(), loader)) - .isEqualTo(context.original().get(context.firstKey())); - assertThat(cache.get(context.middleKey(), loader)) - .isEqualTo(context.original().get(context.middleKey())); - assertThat(cache.get(context.lastKey(), loader)) - .isEqualTo(context.original().get(context.lastKey())); - assertThat(context).stats().hits(3).misses(0).success(0).failures(0); - } - - /* --------------- getAllPresent --------------- */ - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getAllPresent_iterable_null(Cache cache, CacheContext context) { - cache.getAllPresent(null); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getAllPresent_iterable_nullKey(Cache cache, CacheContext context) { - cache.getAllPresent(Collections.singletonList(null)); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllPresent_iterable_empty(Cache cache, CacheContext context) { - assertThat(cache.getAllPresent(List.of())).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllPresent_absent(Cache cache, CacheContext context) { - var result = cache.getAllPresent(context.absentKeys()); - assertThat(result).isExhaustivelyEmpty(); - - int count = context.absentKeys().size(); - assertThat(context).stats().hits(0).misses(count).success(0).failures(0); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void getAllPresent_immutable(Cache cache, CacheContext context) { - cache.getAllPresent(context.absentKeys()).clear(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllPresent_present_partial(Cache cache, CacheContext context) { - var expect = new HashMap(); - expect.put(context.firstKey(), context.original().get(context.firstKey())); - expect.put(context.middleKey(), context.original().get(context.middleKey())); - expect.put(context.lastKey(), context.original().get(context.lastKey())); - var result = cache.getAllPresent(expect.keySet()); - assertThat(result).containsExactlyEntriesIn(expect); - assertThat(context).stats().hits(expect.size()).misses(0).success(0).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllPresent_present_full(Cache cache, CacheContext context) { - var result = cache.getAllPresent(context.original().keySet()); - assertThat(result).containsExactlyEntriesIn(context.original()); - assertThat(context).stats().hits(result.size()).misses(0).success(0).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllPresent_duplicates(Cache cache, CacheContext context) { - var keys = Iterables.concat( - context.absentKeys(), context.absentKeys(), - context.original().keySet(), context.original().keySet()); - var result = cache.getAllPresent(keys); - - long hits, misses; - if (context.isGuava()) { - // Guava does not skip duplicates - hits = 2L * context.initialSize(); - misses = 2L * context.absentKeys().size(); - } else { - hits = context.initialSize(); - misses = context.absentKeys().size(); - } - assertThat(result).containsExactlyEntriesIn(context.original()); - assertThat(context).stats().hits(hits).misses(misses).success(0).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAllPresent_ordered(Cache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - Collections.shuffle(keys); - - var result = cache.getAllPresent(keys); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY) - public void getAllPresent_jdk8186171(Cache cache, CacheContext context) { - class Key { - @Override - public int hashCode() { - return 0; // to put keys in one bucket - } - } - - var keys = intern(new ArrayList()); - for (int i = 0; i < Population.FULL.size(); i++) { - keys.add(new Key()); - } - - Key key = Iterables.getLast(keys); - Int value = context.absentValue(); - cache.put(key, value); - - var result = cache.getAllPresent(keys); - assertThat(result).containsExactly(key, value); - } - - /* --------------- getAll --------------- */ - - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getAll_iterable_null(Cache cache, CacheContext context) { - cache.getAll(null, keys -> { - throw new AssertionError(); - }); - } - - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getAll_iterable_nullKey(Cache cache, CacheContext context) { - cache.getAll(Collections.singletonList(null), keys -> { - throw new AssertionError(); - }); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_iterable_empty(Cache cache, CacheContext context) { - var result = cache.getAll(List.of(), keys -> { - throw new AssertionError(); - }); - assertThat(result).isExhaustivelyEmpty(); - assertThat(context).stats().hits(0).misses(0); - } - - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getAll_function_null(Cache cache, CacheContext context) { - cache.getAll(context.absentKeys(), null); - } - - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getAll_function_nullValue(Cache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys(), keys -> null); - } finally { - int misses = context.loader().isBulk() ? 1 : context.absentKeys().size(); - assertThat(context).stats().hits(0).misses(misses).success(0).failures(1); - } - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void getAll_immutable_keys(Cache cache, CacheContext context) { - cache.getAll(context.absentKeys(), keys -> { - keys.clear(); - return Map.of(); - }); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void getAll_immutable_result(Cache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys(), bulkMappingFunction()); - result.clear(); - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void getAll_absent_throwsException(Cache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys(), keys -> { - throw new IllegalStateException(); - }); - } finally { - int misses = context.absentKeys().size(); - assertThat(context).stats().hits(0).misses(misses).success(0).failures(1); - } - } - - @CacheSpec - @Test(dataProvider = "caches", expectedExceptions = UnknownError.class) - public void getAll_function_throwsError(Cache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys(), keys -> { - throw new UnknownError(); - }); - } finally { - int misses = context.absentKeys().size(); - assertThat(context).stats().hits(0).misses(misses).success(0).failures(1); - } - } - - @CacheSpec - @Test(dataProvider = "caches") - public void getAll_absent(Cache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys(), bulkMappingFunction()); - - int count = context.absentKeys().size(); - assertThat(result).hasSize(count); - assertThat(context).stats().hits(0).misses(count).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_partial(Cache cache, CacheContext context) { - var expect = new HashMap(); - expect.put(context.firstKey(), context.firstKey().negate()); - expect.put(context.middleKey(), context.middleKey().negate()); - expect.put(context.lastKey(), context.lastKey().negate()); - var result = cache.getAll(expect.keySet(), bulkMappingFunction()); - - assertThat(result).containsExactlyEntriesIn(expect); - assertThat(context).stats().hits(expect.size()).misses(0).success(0).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_full(Cache cache, CacheContext context) { - var result = cache.getAll(context.original().keySet(), bulkMappingFunction()); - assertThat(result).containsExactlyEntriesIn(context.original()); - assertThat(context).stats().hits(result.size()).misses(0).success(0).failures(0); - } - - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches") - public void getAll_exceeds(Cache cache, CacheContext context) { - var result = cache.getAll(Set.of(context.absentKey()), keys -> context.absent()); - - var expected = new ImmutableMap.Builder() - .putAll(context.original()) - .putAll(context.absent()) - .build(); - assertThat(cache).containsExactlyEntriesIn(expected); - assertThat(context).stats().hits(0).misses(result.size()).success(1).failures(0); - assertThat(result).containsExactlyEntriesIn(Map.of(context.absentKey(), context.absentValue())); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_different(Cache cache, CacheContext context) { - var actual = context.absentKeys().stream() - .collect(toImmutableMap(key -> intern(key.negate()), identity())); - var result = cache.getAll(context.absentKeys(), keys -> actual); - - assertThat(result).isEmpty(); - assertThat(cache.asMap()).containsAtLeastEntriesIn(actual); - assertThat(cache).hasSize(context.initialSize() + actual.size()); - assertThat(context).stats().hits(0).misses(actual.size()).success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_duplicates(Cache cache, CacheContext context) { - var absentKeys = ImmutableSet.copyOf(Iterables.limit(context.absentKeys(), - Ints.saturatedCast(context.maximum().max() - context.initialSize()))); - var keys = Iterables.concat(absentKeys, absentKeys, - context.original().keySet(), context.original().keySet()); - var result = cache.getAll(keys, bulkMappingFunction()); - assertThat(result).containsExactlyKeys(keys); - - long hits, misses; - if (context.isGuava()) { - // Guava does not skip duplicates - hits = 2L * context.initialSize(); - misses = 2L * absentKeys.size(); - } else { - hits = context.initialSize(); - misses = absentKeys.size(); - } - assertThat(context).stats().hits(hits).misses(misses); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_ordered_absent(Cache cache, CacheContext context) { - var keys = new ArrayList<>(context.absentKeys()); - Collections.shuffle(keys); - - var result = cache.getAll(keys, bulkMappingFunction()); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_ordered_partial(Cache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - keys.addAll(context.absentKeys()); - Collections.shuffle(keys); - - var result = cache.getAll(keys, bulkMappingFunction()); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_ordered_present(Cache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - Collections.shuffle(keys); - - var result = cache.getAll(keys, bulkMappingFunction()); - assertThat(result).containsExactlyKeys(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_ordered_exceeds(Cache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - keys.addAll(context.absentKeys()); - Collections.shuffle(keys); - - var result = List.copyOf(cache.getAll(keys, bulkMappingFunction()).keySet()); - assertThat(result.subList(0, keys.size())).containsExactlyElementsIn(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY) - public void getAll_jdk8186171(CacheContext context) { - class Key { - @Override - public int hashCode() { - return 0; // to put keys in one bucket - } - } - Cache cache = context.build(key -> null); - - var keys = new ArrayList(); - for (int i = 0; i < Population.FULL.size(); i++) { - keys.add(intern(new Key())); - } - Key key = Iterables.getLast(keys); - Int value = context.absentValue(); - cache.put(key, value); - - var result = cache.getAll(keys, keysToLoad -> Map.of()); - assertThat(result.values()).doesNotContain(null); - assertThat(result).containsExactly(key, value); - } - - static Function, ImmutableMap> bulkMappingFunction() { - return keys -> { - ImmutableMap result = keys.stream() - .collect(toImmutableMap(identity(), Int::negate)); - CacheContext.interner().putAll(result); - return result; - }; - } - - /* --------------- put --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void put_insert(Cache cache, CacheContext context) { - cache.put(context.absentKey(), context.absentValue()); - assertThat(cache).hasSize(context.initialSize() + 1); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace_sameValue(Cache cache, CacheContext context) { - var replaced = new HashMap(); - for (Int key : context.firstMiddleLastKeys()) { - Int value = context.original().get(key); - cache.put(key, intern(new Int(value))); - assertThat(cache).containsEntry(key, value); - replaced.put(key, value); - } - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace_sameInstance(Cache cache, CacheContext context) { - var replaced = new HashMap(); - for (Int key : context.firstMiddleLastKeys()) { - Int value = context.original().get(key); - cache.put(key, value); - assertThat(cache).containsEntry(key, value); - replaced.put(key, value); - } - assertThat(cache).hasSize(context.initialSize()); - - if (context.isGuava()) { - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } else { - assertThat(context).removalNotifications().isEmpty(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void put_replace_differentValue(Cache cache, CacheContext context) { - var replaced = new HashMap(); - for (Int key : context.firstMiddleLastKeys()) { - cache.put(key, context.absentValue()); - assertThat(cache).containsEntry(key, context.absentValue()); - replaced.put(key, context.original().get(key)); - } - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(replaced).exclusively(); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void put_nullKey(Cache cache, CacheContext context) { - cache.put(null, context.absentValue()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void put_nullValue(Cache cache, CacheContext context) { - cache.put(context.absentKey(), null); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void put_nullKeyAndValue(Cache cache, CacheContext context) { - cache.put(null, null); - } - - /* --------------- put all --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putAll_insert(Cache cache, CacheContext context) { - int startKey = context.original().size() + 1; - var entries = IntStream - .range(startKey, 100 + startKey) - .mapToObj(Int::valueOf) - .collect(toImmutableMap(identity(), Int::negate)); - cache.putAll(entries); - assertThat(cache).hasSize(100 + context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void putAll_replace(Cache cache, CacheContext context) { - var entries = new HashMap<>(context.original()); - entries.replaceAll((key, value) -> intern(value.add(1))); - cache.putAll(entries); - assertThat(cache).containsExactlyEntriesIn(entries); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}) - public void putAll_mixed(Cache cache, CacheContext context) { - var entries = new HashMap(); - var replaced = new HashMap(); - context.original().forEach((key, value) -> { - if ((key.intValue() % 2) == 0) { - value = intern(value.add(1)); - replaced.put(key, value); - } - entries.put(key, value); - }); - - cache.putAll(entries); - assertThat(cache).containsExactlyEntriesIn(entries); - var expect = context.isGuava() ? entries : replaced; - for (var entry : expect.entrySet()) { - entry.setValue(context.original().get(entry.getKey())); - } - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(expect).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void putAll_empty(Cache cache, CacheContext context) { - cache.putAll(Map.of()); - assertThat(cache).hasSize(context.initialSize()); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void putAll_null(Cache cache, CacheContext context) { - cache.putAll(null); - } - - /* --------------- invalidate --------------- */ - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void invalidate_absent(Cache cache, CacheContext context) { - cache.invalidate(context.absentKey()); - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void invalidate_present(Cache cache, CacheContext context) { - var removed = new HashMap(); - for (Int key : context.firstMiddleLastKeys()) { - cache.invalidate(key); - removed.put(key, context.original().get(key)); - } - int count = context.firstMiddleLastKeys().size(); - assertThat(cache).hasSize(context.initialSize() - count); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(removed).exclusively(); - } - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void invalidate_nullKey(Cache cache, CacheContext context) { - cache.invalidate(null); - } - - /* --------------- invalidateAll --------------- */ - - @CacheSpec - @Test(dataProvider = "caches") - public void invalidateAll(Cache cache, CacheContext context) { - cache.invalidateAll(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void invalidateAll_empty(Cache cache, CacheContext context) { - cache.invalidateAll(Set.of()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}) - public void invalidateAll_partial(Cache cache, CacheContext context) { - var removals = cache.asMap().entrySet().stream() - .filter(entry -> ((entry.getKey().intValue() % 2) == 0)) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); - cache.invalidateAll(removals.keySet()); - assertThat(cache).hasSize(context.initialSize() - removals.size()); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(removals).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void invalidateAll_full(Cache cache, CacheContext context) { - cache.invalidateAll(context.original().keySet()); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void invalidateAll_null(Cache cache, CacheContext context) { - cache.invalidateAll(null); - } - - @CheckNoStats - @CheckMaxLogLevel(ERROR) - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, compute = Compute.SYNC, - executorFailure = ExecutorFailure.IGNORED, executor = CacheExecutor.REJECTING, - removalListener = Listener.CONSUMING) - public void removalListener_rejected(Cache cache, CacheContext context) { - cache.invalidateAll(); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.original()).exclusively(); - } - - /* --------------- cleanup --------------- */ - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void cleanup(Cache cache, CacheContext context) { - cache.cleanUp(); - } - - /* --------------- serialize --------------- */ - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void serialize(Cache cache, CacheContext context) { - assertThat(cache).isReserialize(); - } - - @CheckNoStats - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY) - @Test(dataProvider = "caches", expectedExceptions = InvalidObjectException.class, - expectedExceptionsMessageRegExp = "Proxy required") - public void readObject(CacheContext context) throws Throwable { - var cache = context.isAsync() ? context.asyncCache() : context.cache(); - var readObject = cache.getClass().getDeclaredMethod("readObject", ObjectInputStream.class); - readObject.setAccessible(true); - - try { - readObject.invoke(cache, new ObjectInputStream() { - }); - } catch (InvocationTargetException e) { - throw e.getTargetException(); - } - } - - /* --------------- null parameter --------------- */ - - private final ImmutableSetMultimap, Method> testMethods = getPublicMethods(); - private final NullPointerTester npeTester = new NullPointerTester(); - - @CacheSpec - @Test(dataProvider = "caches") - public void nullParameters(Cache cache, CacheContext context) { - checkNullPointer(cache); - checkNullPointer(cache.asMap()); - checkNullPointer(cache.stats()); - checkNullPointer(cache.policy()); - checkNullPointer(cache.asMap().keySet()); - checkNullPointer(cache.asMap().values()); - checkNullPointer(cache.asMap().entrySet()); - checkNullPointer(cache.policy().eviction().orElse(null)); - checkNullPointer(cache.policy().expireVariably().orElse(null)); - checkNullPointer(cache.policy().expireAfterWrite().orElse(null)); - checkNullPointer(cache.policy().expireAfterAccess().orElse(null)); - checkNullPointer(cache.policy().refreshAfterWrite().orElse(null)); - - if (context.isAsync()) { - checkNullPointer(context.asyncCache()); - checkNullPointer(context.asyncCache().asMap()); - checkNullPointer(context.asyncCache().asMap().keySet()); - checkNullPointer(context.asyncCache().asMap().values()); - checkNullPointer(context.asyncCache().asMap().entrySet()); - } - } - - private void checkNullPointer(Object o) { - if (o == null) { - return; - } - testMethods.asMap().entrySet().stream() - .filter(entry -> entry.getKey().isInstance(o)) - .flatMap(entry -> entry.getValue().stream()) - .forEach(method -> npeTester.testMethod(o, method)); - } - - private ImmutableSetMultimap, Method> getPublicMethods() { - var classes = List.of(Cache.class, LoadingCache.class, AsyncCache.class, - AsyncLoadingCache.class, CacheStats.class, Policy.class, Eviction.class, - FixedRefresh.class, FixedExpiration.class, VarExpiration.class, Map.class, - Collection.class, Set.class); - var ignored = ImmutableSet.of( - Triple.of(Map.class, "equals", List.of(Object.class)), - Triple.of(Set.class, "equals", List.of(Object.class)), - Triple.of(Set.class, "remove", List.of(Object.class)), - Triple.of(Set.class, "contains", List.of(Object.class)), - Triple.of(Collection.class, "equals", List.of(Object.class)), - Triple.of(Collection.class, "remove", List.of(Object.class)), - Triple.of(Collection.class, "contains", List.of(Object.class)), - Triple.of(Map.class, "remove", List.of(Object.class, Object.class)), - Triple.of(Map.class, "getOrDefault", List.of(Object.class, Object.class))); - var builder = new ImmutableSetMultimap.Builder, Method>(); - for (var clazz : classes) { - for (var method : clazz.getMethods()) { - var key = Triple.of(clazz, method.getName(), List.of(method.getParameterTypes())); - if (!ignored.contains(key) && !Modifier.isStatic(method.getModifiers())) { - builder.put(clazz, method); - } - } - } - return builder.build(); - } - - /* --------------- Policy: stats --------------- */ - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void stats(Cache cache, CacheContext context) { - var stats = cache.stats() - .plus(CacheStats.of(1, 2, 3, 4, 5, 6, 7) - .minus(CacheStats.of(6, 5, 4, 3, 2, 1, 0))); - assertThat(stats).isEqualTo(CacheStats.of(0, 0, 0, 1, 3, 5, 7)); - assertThat(cache.policy().isRecordingStats()).isEqualTo(context.isRecordingStats()); - } - - /* --------------- Policy: getIfPresentQuietly --------------- */ - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getIfPresentQuietly_nullKey(Cache cache, CacheContext context) { - cache.policy().getIfPresentQuietly(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getIfPresentQuietly_absent(Cache cache, CacheContext context) { - assertThat(cache.policy().getIfPresentQuietly(context.absentKey())).isNull(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getIfPresentQuietly_present(Cache cache, CacheContext context) { - for (Int key : context.firstMiddleLastKeys()) { - assertThat(cache.policy().getIfPresentQuietly(key)).isEqualTo(context.original().get(key)); - } - } - - /* --------------- Policy: getEntryIfPresentQuietly --------------- */ - - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getEntryIfPresentQuietly_nullKey(Cache cache, CacheContext context) { - cache.policy().getEntryIfPresentQuietly(null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getEntryIfPresentQuietly_absent(Cache cache, CacheContext context) { - assertThat(cache.policy().getEntryIfPresentQuietly(context.absentKey())).isNull(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getEntryIfPresentQuietly_present(Cache cache, CacheContext context) { - for (Int key : context.firstMiddleLastKeys()) { - var entry = cache.policy().getEntryIfPresentQuietly(key); - assertThat(context).containsEntry(entry); - } - } - - /* --------------- Policy: refreshes --------------- */ - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches") - public void refreshes_empty(Cache cache, CacheContext context) { - assertThat(cache.policy().refreshes()).isExhaustivelyEmpty(); - } - - @CacheSpec - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void refreshes_unmodifiable(Cache cache, CacheContext context) { - cache.policy().refreshes().clear(); - } - - /* --------------- Policy: CacheEntry --------------- */ - - @Test(expectedExceptions = UnsupportedOperationException.class) - public void cacheEntry_setValue() { - SnapshotEntry.forEntry(1, 2).setValue(3); - } - - @Test - public void cacheEntry_equals_hashCode_toString() { - long snapshot = 100; - int weight = 200; - long expiresAt = 300; - long refreshableAt = 400; - var tester = new EqualsTester() - .addEqualityGroup(1, 1, 1); - - for (int i = 0; i < 10; i++) { - var key = i; - var value = i + 1; - var group = List.of(Map.entry(key, value), - new SnapshotEntry<>(key, value, snapshot), - new WeightedEntry<>(key, value, snapshot, weight), - new ExpirableEntry<>(key, value, snapshot, expiresAt), - new ExpirableWeightedEntry<>(key, value, snapshot, weight, expiresAt), - new RefreshableExpirableEntry<>(key, value, snapshot, expiresAt, refreshableAt), - new CompleteEntry<>(key, value, snapshot, weight, expiresAt, refreshableAt)); - for (var entry : group) { - assertWithMessage("%s", entry.getClass()) - .that(entry.toString()).isEqualTo(key + "=" + value); - } - tester.addEqualityGroup(group.toArray()); - } - tester.testEquals(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecGuavaTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecGuavaTest.java deleted file mode 100644 index 5cef5cb..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecGuavaTest.java +++ /dev/null @@ -1,483 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.Caffeine.Strength; -import com.google.common.testing.EqualsTester; -import junit.framework.TestCase; - -import java.time.Duration; -import java.util.concurrent.TimeUnit; - -import static com.github.benmanes.caffeine.cache.Caffeine.UNSET_INT; -import static com.github.benmanes.caffeine.cache.CaffeineSpec.parse; - -@SuppressWarnings({"CheckReturnValue", "PreferJavaTimeOverload"}) -public class CaffeineSpecGuavaTest extends TestCase { - - public void testParse_empty() { - CaffeineSpec spec = parse(""); - assertEquals(spec.initialCapacity, UNSET_INT); - assertEquals(spec.maximumSize, UNSET_INT); - assertEquals(spec.maximumWeight, UNSET_INT); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.expireAfterAccess); - assertNull(spec.expireAfterWrite); - assertNull(spec.refreshAfterWrite); - assertCaffeineEquivalence(Caffeine.newBuilder(), Caffeine.from(spec)); - } - - public void testParse_initialCapacity() { - CaffeineSpec spec = parse("initialCapacity=10"); - assertEquals(10, spec.initialCapacity); - assertEquals(spec.maximumSize, UNSET_INT); - assertEquals(spec.maximumWeight, UNSET_INT); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.expireAfterWrite); - assertNull(spec.expireAfterAccess); - assertNull(spec.refreshAfterWrite); - assertCaffeineEquivalence( - Caffeine.newBuilder().initialCapacity(10), Caffeine.from(spec)); - } - - public void testParse_initialCapacityRepeated() { - try { - parse("initialCapacity=10, initialCapacity=20"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - public void testParse_maximumSize() { - CaffeineSpec spec = parse("maximumSize=9000"); - assertEquals(spec.initialCapacity, UNSET_INT); - assertEquals(9000, spec.maximumSize); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.expireAfterWrite); - assertNull(spec.expireAfterAccess); - assertNull(spec.refreshAfterWrite); - assertCaffeineEquivalence( - Caffeine.newBuilder().maximumSize(9000), Caffeine.from(spec)); - } - - public void testParse_maximumSizeRepeated() { - try { - parse("maximumSize=10, maximumSize=20"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } - } - - public void testParse_maximumWeight() { - CaffeineSpec spec = parse("maximumWeight=9000"); - assertEquals(spec.initialCapacity, UNSET_INT); - assertEquals(9000, spec.maximumWeight); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.expireAfterWrite); - assertNull(spec.expireAfterAccess); - assertNull(spec.refreshAfterWrite); - assertCaffeineEquivalence( - Caffeine.newBuilder().maximumWeight(9000), Caffeine.from(spec)); - } - - public void testParse_maximumWeightRepeated() { - try { - parse("maximumWeight=10, maximumWeight=20"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } - } - - public void testParse_maximumSizeAndMaximumWeight() { - try { - parse("maximumSize=10, maximumWeight=20"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - } - } - - public void testParse_weakKeys() { - CaffeineSpec spec = parse("weakKeys"); - assertEquals(spec.initialCapacity, UNSET_INT); - assertEquals(spec.maximumSize, UNSET_INT); - assertEquals(spec.maximumWeight, UNSET_INT); - assertEquals(Strength.WEAK, spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.expireAfterWrite); - assertNull(spec.expireAfterAccess); - assertNull(spec.refreshAfterWrite); - assertCaffeineEquivalence(Caffeine.newBuilder().weakKeys(), Caffeine.from(spec)); - } - - public void testParse_weakKeysCannotHaveValue() { - try { - parse("weakKeys=true"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - public void testParse_repeatedKeyStrength() { - try { - parse("weakKeys, weakKeys"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - public void testParse_softValues() { - CaffeineSpec spec = parse("softValues"); - assertEquals(spec.initialCapacity, UNSET_INT); - assertEquals(spec.maximumSize, UNSET_INT); - assertEquals(spec.maximumWeight, UNSET_INT); - assertNull(spec.keyStrength); - assertEquals(Strength.SOFT, spec.valueStrength); - assertNull(spec.expireAfterWrite); - assertNull(spec.expireAfterAccess); - assertNull(spec.refreshAfterWrite); - assertCaffeineEquivalence( - Caffeine.newBuilder().softValues(), Caffeine.from(spec)); - } - - public void testParse_softValuesCannotHaveValue() { - try { - parse("softValues=true"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - public void testParse_weakValues() { - CaffeineSpec spec = parse("weakValues"); - assertEquals(spec.initialCapacity, UNSET_INT); - assertEquals(spec.maximumSize, UNSET_INT); - assertEquals(spec.maximumWeight, UNSET_INT); - assertNull(spec.keyStrength); - assertEquals(Strength.WEAK, spec.valueStrength); - assertNull(spec.expireAfterWrite); - assertNull(spec.expireAfterAccess); - assertNull(spec.refreshAfterWrite); - assertCaffeineEquivalence( - Caffeine.newBuilder().weakValues(), Caffeine.from(spec)); - } - - public void testParse_weakValuesCannotHaveValue() { - try { - parse("weakValues=true"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - public void testParse_repeatedValueStrength() { - try { - parse("softValues, softValues"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - - try { - parse("softValues, weakValues"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - - try { - parse("weakValues, softValues"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - - try { - parse("weakValues, weakValues"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - public void testParse_writeExpirationDays() { - CaffeineSpec spec = parse("expireAfterWrite=10d"); - assertEquals(spec.initialCapacity, UNSET_INT); - assertEquals(spec.maximumSize, UNSET_INT); - assertEquals(spec.maximumWeight, UNSET_INT); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertEquals(Duration.ofDays(10), spec.expireAfterWrite); - assertNull(spec.expireAfterAccess); - assertNull(spec.refreshAfterWrite); - assertCaffeineEquivalence( - Caffeine.newBuilder().expireAfterWrite(10L, TimeUnit.DAYS), Caffeine.from(spec)); - } - - public void testParse_writeExpirationHours() { - CaffeineSpec spec = parse("expireAfterWrite=150h"); - assertEquals(Duration.ofHours(150), spec.expireAfterWrite); - assertCaffeineEquivalence( - Caffeine.newBuilder().expireAfterWrite(150L, TimeUnit.HOURS), Caffeine.from(spec)); - } - - public void testParse_writeExpirationMinutes() { - CaffeineSpec spec = parse("expireAfterWrite=10m"); - assertEquals(Duration.ofMinutes(10), spec.expireAfterWrite); - assertCaffeineEquivalence( - Caffeine.newBuilder().expireAfterWrite(10L, TimeUnit.MINUTES), Caffeine.from(spec)); - } - - public void testParse_writeExpirationSeconds() { - CaffeineSpec spec = parse("expireAfterWrite=10s"); - assertEquals(Duration.ofSeconds(10), spec.expireAfterWrite); - assertCaffeineEquivalence( - Caffeine.newBuilder().expireAfterWrite(10L, TimeUnit.SECONDS), Caffeine.from(spec)); - } - - public void testParse_writeExpirationRepeated() { - try { - parse( - "expireAfterWrite=10s,expireAfterWrite=10m"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - public void testParse_accessExpirationDays() { - CaffeineSpec spec = parse("expireAfterAccess=10d"); - assertEquals(spec.initialCapacity, UNSET_INT); - assertEquals(spec.maximumSize, UNSET_INT); - assertEquals(spec.maximumWeight, UNSET_INT); - assertNull(spec.keyStrength); - assertNull(spec.valueStrength); - assertNull(spec.expireAfterWrite); - assertEquals(Duration.ofDays(10), spec.expireAfterAccess); - assertCaffeineEquivalence( - Caffeine.newBuilder().expireAfterAccess(10L, TimeUnit.DAYS), Caffeine.from(spec)); - } - - public void testParse_accessExpirationHours() { - CaffeineSpec spec = parse("expireAfterAccess=150h"); - assertEquals(Duration.ofHours(150), spec.expireAfterAccess); - assertCaffeineEquivalence( - Caffeine.newBuilder().expireAfterAccess(150L, TimeUnit.HOURS), Caffeine.from(spec)); - } - - public void testParse_accessExpirationMinutes() { - CaffeineSpec spec = parse("expireAfterAccess=10m"); - assertEquals(Duration.ofMinutes(10), spec.expireAfterAccess); - assertCaffeineEquivalence( - Caffeine.newBuilder().expireAfterAccess(10L, TimeUnit.MINUTES), - Caffeine.from(spec)); - } - - public void testParse_accessExpirationSeconds() { - CaffeineSpec spec = parse("expireAfterAccess=10s"); - assertEquals(Duration.ofSeconds(10), spec.expireAfterAccess); - assertCaffeineEquivalence( - Caffeine.newBuilder().expireAfterAccess(10L, TimeUnit.SECONDS), - Caffeine.from(spec)); - } - - public void testParse_accessExpirationRepeated() { - try { - parse( - "expireAfterAccess=10s,expireAfterAccess=10m"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - public void testParse_recordStats() { - CaffeineSpec spec = parse("recordStats"); - assertTrue(spec.recordStats); - assertCaffeineEquivalence(Caffeine.newBuilder().recordStats(), Caffeine.from(spec)); - } - - public void testParse_recordStatsValueSpecified() { - try { - parse("recordStats=True"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - public void testParse_recordStatsRepeated() { - try { - parse("recordStats,recordStats"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - public void testParse_accessExpirationAndWriteExpiration() { - CaffeineSpec spec = parse("expireAfterAccess=10s,expireAfterWrite=9m"); - assertEquals(Duration.ofMinutes(9), spec.expireAfterWrite); - assertEquals(Duration.ofSeconds(10), spec.expireAfterAccess); - assertCaffeineEquivalence( - Caffeine.newBuilder() - .expireAfterAccess(10L, TimeUnit.SECONDS) - .expireAfterWrite(9L, TimeUnit.MINUTES), - Caffeine.from(spec)); - } - - public void testParse_multipleKeys() { - CaffeineSpec spec = parse("initialCapacity=10,maximumSize=20," - + "weakKeys,weakValues,expireAfterAccess=10m,expireAfterWrite=1h"); - assertEquals(10, spec.initialCapacity); - assertEquals(20, spec.maximumSize); - assertEquals(spec.maximumWeight, UNSET_INT); - assertEquals(Strength.WEAK, spec.keyStrength); - assertEquals(Strength.WEAK, spec.valueStrength); - assertEquals(Duration.ofHours(1), spec.expireAfterWrite); - assertEquals(Duration.ofMinutes(10), spec.expireAfterAccess); - Caffeine expected = Caffeine.newBuilder() - .initialCapacity(10) - .maximumSize(20) - .weakKeys() - .weakValues() - .expireAfterAccess(10L, TimeUnit.MINUTES) - .expireAfterWrite(1L, TimeUnit.HOURS); - assertCaffeineEquivalence(expected, Caffeine.from(spec)); - } - - public void testParse_whitespaceAllowed() { - CaffeineSpec spec = parse(""" - initialCapacity=10, - maximumSize=20,\t\rweakKeys \t ,softValues\s - , \r expireAfterWrite \t = 15s - - """); - assertEquals(10, spec.initialCapacity); - assertEquals(20, spec.maximumSize); - assertEquals(spec.maximumWeight, UNSET_INT); - assertEquals(Strength.WEAK, spec.keyStrength); - assertEquals(Strength.SOFT, spec.valueStrength); - assertEquals(Duration.ofSeconds(15), spec.expireAfterWrite); - assertNull(spec.expireAfterAccess); - Caffeine expected = Caffeine.newBuilder() - .initialCapacity(10) - .maximumSize(20) - .weakKeys() - .softValues() - .expireAfterWrite(15L, TimeUnit.SECONDS); - assertCaffeineEquivalence(expected, Caffeine.from(spec)); - } - - public void testParse_unknownKey() { - try { - parse("foo=17"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - // Allowed by Caffeine - public void disabled_testParse_extraCommaIsInvalid() { - try { - parse("weakKeys,"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - - try { - parse(",weakKeys"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - - try { - parse("weakKeys,,softValues"); - fail("Expected exception"); - } catch (IllegalArgumentException expected) { - // expected - } - } - - public void testEqualsAndHashCode() { - new EqualsTester() - .addEqualityGroup(parse(""), parse("")) - .addEqualityGroup(parse("initialCapacity=7"), parse("initialCapacity=7")) - .addEqualityGroup(parse("initialCapacity=15"), parse("initialCapacity=15")) - .addEqualityGroup(parse("maximumSize=7"), parse("maximumSize=7")) - .addEqualityGroup(parse("maximumSize=15"), parse("maximumSize=15")) - .addEqualityGroup(parse("maximumWeight=7"), parse("maximumWeight=7")) - .addEqualityGroup(parse("maximumWeight=15"), parse("maximumWeight=15")) - .addEqualityGroup(parse("expireAfterAccess=60s"), parse("expireAfterAccess=1m")) - .addEqualityGroup(parse("expireAfterAccess=60m"), parse("expireAfterAccess=1h")) - .addEqualityGroup(parse("expireAfterWrite=60s"), parse("expireAfterWrite=1m")) - .addEqualityGroup(parse("expireAfterWrite=60m"), parse("expireAfterWrite=1h")) - .addEqualityGroup(parse("weakKeys"), parse("weakKeys")) - .addEqualityGroup(parse("softValues"), parse("softValues")) - .addEqualityGroup(parse("weakValues"), parse("weakValues")) - .addEqualityGroup(parse("recordStats"), parse("recordStats")) - .testEquals(); - } - - public void testMaximumWeight_withWeigher() { - Caffeine builder = Caffeine.from(parse("maximumWeight=9000")); - builder.weigher((k, v) -> 42).build(k -> null); - } - - public void testMaximumWeight_withoutWeigher() { - Caffeine builder = Caffeine.from(parse("maximumWeight=9000")); - try { - builder.build(k -> null); - fail(); - } catch (IllegalStateException expected) { - } - } - - public void testMaximumSize_withWeigher() { - Caffeine builder = Caffeine.from(parse("maximumSize=9000")); - builder.weigher((k, v) -> 42).build(k -> null); - } - - public void testMaximumSize_withoutWeigher() { - Caffeine builder = Caffeine.from(parse("maximumSize=9000")); - builder.build(k -> null); - } - - public void testCaffeineFrom_string() { - Caffeine fromString = Caffeine.from( - "initialCapacity=10,maximumSize=20,weakKeys,weakValues,expireAfterAccess=10m"); - Caffeine expected = Caffeine.newBuilder() - .initialCapacity(10) - .maximumSize(20) - .weakKeys() - .weakValues() - .expireAfterAccess(10L, TimeUnit.MINUTES); - assertCaffeineEquivalence(expected, fromString); - } - - private static void assertCaffeineEquivalence(Caffeine a, Caffeine b) { - assertEquals("expireAfterAccessNanos", a.expireAfterAccessNanos, b.expireAfterAccessNanos); - assertEquals("expireAfterWriteNanos", a.expireAfterWriteNanos, b.expireAfterWriteNanos); - assertEquals("initialCapacity", a.initialCapacity, b.initialCapacity); - assertEquals("maximumSize", a.maximumSize, b.maximumSize); - assertEquals("maximumWeight", a.maximumWeight, b.maximumWeight); - assertEquals("refreshNanos", a.refreshAfterWriteNanos, b.refreshAfterWriteNanos); - assertEquals("keyStrength", a.keyStrength, b.keyStrength); - assertEquals("removalListener", a.removalListener, b.removalListener); - assertEquals("weigher", a.weigher, b.weigher); - assertEquals("valueStrength", a.valueStrength, b.valueStrength); - assertEquals("statsCounterSupplier", a.statsCounterSupplier, b.statsCounterSupplier); - assertEquals("ticker", a.ticker, b.ticker); - assertEquals("recordStats", a.isRecordingStats(), b.isRecordingStats()); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecTest.java deleted file mode 100644 index 4272597..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineSpecTest.java +++ /dev/null @@ -1,258 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.Caffeine.Strength; -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.*; -import com.github.benmanes.caffeine.cache.testing.CacheValidationListener; -import com.google.common.base.Joiner; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collections; -import java.util.concurrent.TimeUnit; -import java.util.function.LongFunction; - -import static com.github.benmanes.caffeine.cache.Caffeine.UNSET_INT; -import static com.google.common.truth.Truth.assertThat; -import static java.util.Locale.US; -import static java.util.Objects.requireNonNull; - - -@Listeners(CacheValidationListener.class) -@Test(dataProviderClass = CacheProvider.class) -public final class CaffeineSpecTest { - static final long UNSET_LONG = UNSET_INT; - - @Test(expectedExceptions = IllegalArgumentException.class) - public void parseInt_exception() { - CaffeineSpec.parseInt("key", "value"); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void parseLong_exception() { - CaffeineSpec.parseLong("key", "value"); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void parseTimeUnit_exception() { - CaffeineSpec.parseTimeUnit("key", "value"); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - initialCapacity = {InitialCapacity.DEFAULT, InitialCapacity.FULL}, - compute = Compute.SYNC, removalListener = Listener.DISABLED) - public void seconds(CacheContext context) { - runScenarios(context, new Epoch(TimeUnit.SECONDS, "s")); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - initialCapacity = {InitialCapacity.DEFAULT, InitialCapacity.FULL}, - compute = Compute.SYNC, removalListener = Listener.DISABLED) - public void minutes(CacheContext context) { - runScenarios(context, new Epoch(TimeUnit.MINUTES, "m")); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - initialCapacity = {InitialCapacity.DEFAULT, InitialCapacity.FULL}, - compute = Compute.SYNC, removalListener = Listener.DISABLED) - public void hours(CacheContext context) { - runScenarios(context, new Epoch(TimeUnit.HOURS, "h")); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - initialCapacity = {InitialCapacity.DEFAULT, InitialCapacity.FULL}, - compute = Compute.SYNC, removalListener = Listener.DISABLED) - public void days(CacheContext context) { - runScenarios(context, new Epoch(TimeUnit.DAYS, "d")); - } - - private void runScenarios(CacheContext context, Epoch epoch) { - runTest(context, epoch, nanos -> epoch.toUnitString(nanos).toLowerCase(US)); - runTest(context, epoch, nanos -> epoch.toUnitString(nanos).toUpperCase(US)); - runTest(context, epoch, nanos -> epoch.toDuration(nanos).toString().toLowerCase(US)); - runTest(context, epoch, nanos -> epoch.toDuration(nanos).toString().toUpperCase(US)); - } - - private void runTest(CacheContext context, Epoch epoch, LongFunction nanosToString) { - CaffeineSpec spec = toSpec(context, epoch, nanosToString); - Caffeine builder = Caffeine.from(spec); - - checkInitialCapacity(spec, context, builder); - checkMaximumWeight(spec, context, builder); - checkMaximumSize(spec, context, builder); - checkWeakKeys(spec, context, builder); - checkValueStrength(spec, context, builder); - checkExpireAfterWrite(spec, context, builder, epoch); - checkExpireAfterAccess(spec, context, builder, epoch); - checkRefreshAfterWrite(spec, context, builder, epoch); - - assertThat(spec).isEqualTo(CaffeineSpec.parse(spec.toParsableString())); - } - - static CaffeineSpec toSpec(CacheContext context, - Epoch epoch, LongFunction nanosToString) { - var options = new ArrayList(); - if (context.initialCapacity() != InitialCapacity.DEFAULT) { - options.add("initialCapacity=" + context.initialCapacity().size()); - } - if (context.maximum() != Maximum.DISABLED) { - String key = context.isWeighted() ? "maximumWeight" : "maximumSize"; - options.add(key + "=" + context.maximum().max()); - } - if (context.isWeakKeys()) { - options.add("weakKeys"); - } - if (context.isWeakValues()) { - options.add("weakValues"); - } - if (context.isSoftValues()) { - options.add("softValues"); - } - if (context.expireAfterWrite() != Expire.DISABLED) { - String duration = nanosToString.apply(context.expireAfterWrite().timeNanos()); - options.add("expireAfterWrite=" + duration); - } - if (context.expireAfterAccess() != Expire.DISABLED) { - String duration = nanosToString.apply(context.expireAfterAccess().timeNanos()); - options.add("expireAfterAccess=" + duration); - } - if (context.refreshAfterWrite() != Expire.DISABLED) { - String duration = nanosToString.apply(context.refreshAfterWrite().timeNanos()); - options.add("refreshAfterWrite=" + duration); - } - if (context.isRecordingStats()) { - options.add("recordStats"); - } - Collections.shuffle(options); - String specString = Joiner.on(',').join(options); - return CaffeineSpec.parse(specString); - } - - static void checkInitialCapacity(CaffeineSpec spec, - CacheContext context, Caffeine builder) { - if (context.initialCapacity() == InitialCapacity.DEFAULT) { - assertThat(spec.initialCapacity).isEqualTo(UNSET_INT); - assertThat(builder.initialCapacity).isEqualTo(UNSET_INT); - } else { - assertThat(spec.initialCapacity).isEqualTo(context.initialCapacity().size()); - assertThat(builder.initialCapacity).isEqualTo(context.initialCapacity().size()); - } - } - - static void checkMaximumSize(CaffeineSpec spec, CacheContext context, Caffeine builder) { - if (context.isWeighted()) { - assertThat(spec.maximumSize).isEqualTo(UNSET_LONG); - assertThat(builder.maximumSize).isEqualTo(UNSET_LONG); - return; - } - if (context.maximum() == Maximum.DISABLED) { - assertThat(spec.maximumSize).isEqualTo(UNSET_LONG); - assertThat(builder.maximumSize).isEqualTo(UNSET_LONG); - } else { - assertThat(spec.maximumSize).isEqualTo(context.maximum().max()); - assertThat(builder.maximumSize).isEqualTo(context.maximum().max()); - } - } - - static void checkMaximumWeight(CaffeineSpec spec, CacheContext context, Caffeine builder) { - if (!context.isWeighted()) { - assertThat(spec.maximumWeight).isEqualTo(UNSET_LONG); - assertThat(builder.maximumWeight).isEqualTo(UNSET_LONG); - return; - } - if (context.maximum() == Maximum.DISABLED) { - assertThat(spec.maximumWeight).isEqualTo(UNSET_LONG); - assertThat(builder.maximumWeight).isEqualTo(UNSET_LONG); - } else { - assertThat(spec.maximumWeight).isEqualTo(context.maximum().max()); - assertThat(builder.maximumWeight).isEqualTo(context.maximum().max()); - } - } - - static void checkWeakKeys(CaffeineSpec spec, CacheContext context, Caffeine builder) { - if (context.isWeakKeys()) { - assertThat(spec.keyStrength).isEqualTo(Strength.WEAK); - assertThat(builder.keyStrength).isEqualTo(Strength.WEAK); - } else { - assertThat(spec.keyStrength).isNull(); - assertThat(builder.keyStrength).isNull(); - } - } - - static void checkValueStrength(CaffeineSpec spec, CacheContext context, Caffeine builder) { - if (context.isWeakValues()) { - assertThat(spec.valueStrength).isEqualTo(Strength.WEAK); - assertThat(builder.valueStrength).isEqualTo(Strength.WEAK); - } else if (context.isSoftValues()) { - assertThat(spec.valueStrength).isEqualTo(Strength.SOFT); - assertThat(builder.valueStrength).isEqualTo(Strength.SOFT); - } else { - assertThat(spec.valueStrength).isNull(); - assertThat(builder.valueStrength).isNull(); - } - } - - static void checkExpireAfterAccess(CaffeineSpec spec, - CacheContext context, Caffeine builder, Epoch epoch) { - if (context.expireAfterAccess() == Expire.DISABLED) { - assertThat(spec.expireAfterAccess).isNull(); - assertThat(builder.expireAfterAccessNanos).isEqualTo(UNSET_LONG); - } else { - long nanos = context.expireAfterAccess().timeNanos(); - assertThat(spec.expireAfterAccess).isEqualTo(epoch.toDuration(nanos)); - assertThat(builder.expireAfterAccessNanos).isEqualTo(epoch.truncate(nanos)); - } - } - - static void checkExpireAfterWrite(CaffeineSpec spec, - CacheContext context, Caffeine builder, Epoch epoch) { - if (context.expireAfterWrite() == Expire.DISABLED) { - assertThat(spec.expireAfterWrite).isNull(); - assertThat(builder.expireAfterWriteNanos).isEqualTo(UNSET_LONG); - } else { - long nanos = context.expireAfterWrite().timeNanos(); - assertThat(spec.expireAfterWrite).isEqualTo(epoch.toDuration(nanos)); - assertThat(builder.expireAfterWriteNanos).isEqualTo(epoch.truncate(nanos)); - } - } - - static void checkRefreshAfterWrite(CaffeineSpec spec, - CacheContext context, Caffeine builder, Epoch epoch) { - if (context.refreshAfterWrite() == Expire.DISABLED) { - assertThat(spec.refreshAfterWrite).isNull(); - assertThat(builder.refreshAfterWriteNanos).isEqualTo(UNSET_LONG); - } else { - long nanos = context.refreshAfterWrite().timeNanos(); - assertThat(spec.refreshAfterWrite).isEqualTo(epoch.toDuration(nanos)); - assertThat(builder.refreshAfterWriteNanos).isEqualTo(epoch.truncate(nanos)); - } - } - - record Epoch(TimeUnit unit, String symbol) { - Epoch(TimeUnit unit, String symbol) { - this.symbol = requireNonNull(symbol); - this.unit = requireNonNull(unit); - } - - long truncate(long nanos) { - return unit.toNanos(unit.convert(nanos, TimeUnit.NANOSECONDS)); - } - - public String toUnitString(long nanos) { - return unit.convert(nanos, TimeUnit.NANOSECONDS) + symbol; - } - - public Duration toDuration(long nanos) { - return Duration.ofNanos(truncate(nanos)); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java deleted file mode 100644 index 83ef3ca..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/CaffeineTest.java +++ /dev/null @@ -1,802 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.stats.StatsCounter; -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.*; -import com.google.common.testing.FakeTicker; -import com.google.common.testing.NullPointerTester; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.testng.Assert; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static org.mockito.Mockito.verify; - - -@SuppressWarnings({"CheckReturnValue", "PreferJavaTimeOverload"}) -public final class CaffeineTest { - @Mock - StatsCounter statsCounter; - @Mock - Expiry expiry; - @Mock - CacheLoader loader; - - @BeforeClass - public void beforeClass() throws Exception { - MockitoAnnotations.openMocks(this).close(); - } - - @Test - public void nullParameters() { - var npeTester = new NullPointerTester(); - npeTester.testAllPublicInstanceMethods(Caffeine.newBuilder()); - } - - @Test - public void unconfigured() { - assertThat(Caffeine.newBuilder().build()).isNotNull(); - assertThat(Caffeine.newBuilder().build(loader)).isNotNull(); - assertThat(Caffeine.newBuilder().buildAsync()).isNotNull(); - assertThat(Caffeine.newBuilder().buildAsync(loader)).isNotNull(); - assertThat(Caffeine.newBuilder().toString()).isEqualTo(Caffeine.newBuilder().toString()); - } - - @Test - public void configured() { - var configured = Caffeine.newBuilder() - .initialCapacity(1).weakKeys() - .expireAfterAccess(1, TimeUnit.SECONDS).expireAfterWrite(1, TimeUnit.SECONDS) - .removalListener((k, v, c) -> { - }).recordStats(); - assertThat(configured.build()).isNotNull(); - assertThat(configured.buildAsync()).isNotNull(); - assertThat(configured.build(loader)).isNotNull(); - assertThat(configured.buildAsync(loader)).isNotNull(); - - assertThat(configured.refreshAfterWrite(1, TimeUnit.SECONDS).toString()) - .isNotEqualTo(Caffeine.newBuilder().toString()); - assertThat(Caffeine.newBuilder().maximumSize(1).toString()) - .isNotEqualTo(Caffeine.newBuilder().maximumWeight(1).toString()); - } - - @Test(expectedExceptions = NullPointerException.class) - public void fromSpec_null() { - Caffeine.from((CaffeineSpec) null); - } - - @Test - public void fromSpec_lenientParsing() { - Caffeine.from(CaffeineSpec.parse("maximumSize=100")).weigher((k, v) -> 0).build(); - } - - @Test - public void fromSpec() { - assertThat(Caffeine.from(CaffeineSpec.parse(""))).isNotNull(); - } - - @Test(expectedExceptions = NullPointerException.class) - public void fromString_null() { - Caffeine.from((String) null); - } - - @Test - public void fromString_lenientParsing() { - Caffeine.from("maximumSize=100").weigher((k, v) -> 0).build(); - } - - @Test - public void fromString() { - assertThat(Caffeine.from("")).isNotNull(); - } - - @Test(dataProviderClass = CacheProvider.class, dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - initialCapacity = {InitialCapacity.DEFAULT, InitialCapacity.FULL}, compute = Compute.SYNC) - public void string(CacheContext context) { - var description = context.caffeine().toString(); - if (context.initialCapacity() != InitialCapacity.DEFAULT) { - assertThat(description).contains("initialCapacity=" + context.initialCapacity().size()); - } - if (context.maximum() != Maximum.DISABLED) { - String key = context.isWeighted() ? "maximumWeight" : "maximumSize"; - assertThat(description).contains(key + "=" + context.maximumWeightOrSize()); - } - if (context.expireAfterWrite() != Expire.DISABLED) { - assertThat(description).contains( - "expireAfterWrite=" + context.expireAfterWrite().timeNanos() + "ns"); - } - if (context.expireAfterAccess() != Expire.DISABLED) { - assertThat(description).contains( - "expireAfterAccess=" + context.expireAfterAccess().timeNanos() + "ns"); - } - if (context.expiresVariably()) { - assertThat(description).contains("expiry"); - } - if (context.refreshAfterWrite() != Expire.DISABLED) { - assertThat(description).contains( - "refreshAfterWrite=" + context.refreshAfterWrite().timeNanos() + "ns"); - } - if (context.isWeakKeys()) { - assertThat(description).contains("keyStrength=weak"); - } - if (context.isWeakValues()) { - assertThat(description).contains("valueStrength=weak"); - } - if (context.isSoftValues()) { - assertThat(description).contains("valueStrength=soft"); - } - if (context.evictionListenerType() != Listener.DISABLED) { - assertThat(description).contains("evictionListener"); - } - if (context.removalListenerType() != Listener.DISABLED) { - assertThat(description).contains("removalListener"); - } - } - - @Test - public void calculateHashMapCapacity() { - Iterable iterable = List.of(1, 2, 3); - assertThat(Caffeine.calculateHashMapCapacity(iterable)).isEqualTo(16); - assertThat(Caffeine.calculateHashMapCapacity(List.of(1, 2, 3))).isEqualTo(4); - } - - /* --------------- loading --------------- */ - - @Test(expectedExceptions = NullPointerException.class) - public void loading_nullLoader() { - Caffeine.newBuilder().build(null); - } - - /* --------------- async --------------- */ - - @Test(expectedExceptions = IllegalStateException.class) - public void async_weakValues() { - Caffeine.newBuilder().weakValues().buildAsync(loader); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void async_softValues() { - Caffeine.newBuilder().softValues().buildAsync(loader); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void async_weakKeys_evictionListener() { - RemovalListener evictionListener = (k, v, c) -> { - }; - Caffeine.newBuilder().weakKeys().evictionListener(evictionListener).buildAsync(); - } - - /* --------------- async loader --------------- */ - - @Test - public void asyncLoader_nullLoader() { - try { - Caffeine.newBuilder().buildAsync(null); - Assert.fail(); - } catch (NullPointerException expected) { - } - - try { - Caffeine.newBuilder().buildAsync((AsyncCacheLoader) null); - Assert.fail(); - } catch (NullPointerException expected) { - } - } - - @Test - @SuppressWarnings("UnnecessaryMethodReference") - public void asyncLoader() { - Caffeine.newBuilder().buildAsync(loader::asyncLoad); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void asyncLoader_weakValues() { - Caffeine.newBuilder().weakValues().buildAsync(loader); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void asyncLoader_softValues() { - Caffeine.newBuilder().softValues().buildAsync(loader); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void async_asyncLoader_weakKeys_evictionListener() { - RemovalListener evictionListener = (k, v, c) -> { - }; - Caffeine.newBuilder().weakKeys().evictionListener(evictionListener).buildAsync(loader); - } - - /* --------------- initialCapacity --------------- */ - - @Test(expectedExceptions = IllegalArgumentException.class) - public void initialCapacity_negative() { - Caffeine.newBuilder().initialCapacity(-1); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void initialCapacity_twice() { - Caffeine.newBuilder().initialCapacity(1).initialCapacity(1); - } - - @Test - public void initialCapacity_small() { - // can't check, so just assert that it builds - var builder = Caffeine.newBuilder().initialCapacity(0); - assertThat(builder.initialCapacity).isEqualTo(0); - builder.build(); - } - - @Test - public void initialCapacity_large() { - // don't build! just check that it configures - var builder = Caffeine.newBuilder().initialCapacity(Integer.MAX_VALUE); - assertThat(builder.initialCapacity).isEqualTo(Integer.MAX_VALUE); - } - - /* --------------- maximumSize --------------- */ - - @Test(expectedExceptions = IllegalArgumentException.class) - public void maximumSize_negative() { - Caffeine.newBuilder().maximumSize(-1); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void maximumSize_twice() { - Caffeine.newBuilder().maximumSize(1).maximumSize(1); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void maximumSize_maximumWeight() { - Caffeine.newBuilder().maximumWeight(1).maximumSize(1); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void maximumSize_weigher() { - Caffeine.newBuilder().weigher(Weigher.singletonWeigher()).maximumSize(1); - } - - @Test - public void maximumSize_small() { - var builder = Caffeine.newBuilder().maximumSize(0); - assertThat(builder.maximumSize).isEqualTo(0); - var cache = builder.build(); - assertThat(cache.policy().eviction().orElseThrow().getMaximum()).isEqualTo(0); - } - - @Test - public void maximumSize_large() { - var builder = Caffeine.newBuilder().maximumSize(Integer.MAX_VALUE); - assertThat(builder.maximumSize).isEqualTo(Integer.MAX_VALUE); - var cache = builder.build(); - assertThat(cache.policy().eviction().orElseThrow().getMaximum()).isEqualTo(Integer.MAX_VALUE); - } - - /* --------------- maximumWeight --------------- */ - - @Test(expectedExceptions = IllegalArgumentException.class) - public void maximumWeight_negative() { - Caffeine.newBuilder().maximumWeight(-1); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void maximumWeight_twice() { - Caffeine.newBuilder().maximumWeight(1).maximumWeight(1); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void maximumWeight_noWeigher() { - Caffeine.newBuilder().maximumWeight(1).build(); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void maximumWeight_maximumSize() { - Caffeine.newBuilder().maximumSize(1).maximumWeight(1); - } - - @Test - public void maximumWeight_small() { - var builder = Caffeine.newBuilder() - .maximumWeight(0).weigher(Weigher.singletonWeigher()); - assertThat(builder.weigher).isSameInstanceAs(Weigher.singletonWeigher()); - assertThat(builder.maximumWeight).isEqualTo(0); - var eviction = builder.build().policy().eviction().orElseThrow(); - assertThat(eviction.getMaximum()).isEqualTo(0); - assertThat(eviction.isWeighted()).isTrue(); - } - - @Test - public void maximumWeight_large() { - var builder = Caffeine.newBuilder() - .maximumWeight(Integer.MAX_VALUE).weigher(Weigher.singletonWeigher()); - assertThat(builder.maximumWeight).isEqualTo(Integer.MAX_VALUE); - assertThat(builder.weigher).isSameInstanceAs(Weigher.singletonWeigher()); - - var eviction = builder.build().policy().eviction().orElseThrow(); - assertThat(eviction.getMaximum()).isEqualTo(Integer.MAX_VALUE); - assertThat(eviction.isWeighted()).isTrue(); - } - - /* --------------- weigher --------------- */ - - @Test(expectedExceptions = NullPointerException.class) - public void weigher_null() { - Caffeine.newBuilder().weigher(null); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void weigher_twice() { - Caffeine.newBuilder().weigher(Weigher.singletonWeigher()).weigher(Weigher.singletonWeigher()); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void weigher_maximumSize() { - Caffeine.newBuilder().maximumSize(1).weigher(Weigher.singletonWeigher()); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void weigher_noMaximumWeight() { - Caffeine.newBuilder().weigher(Weigher.singletonWeigher()).build(); - } - - @Test - public void weigher() { - Weigher weigher = (k, v) -> 0; - var builder = Caffeine.newBuilder().maximumWeight(0).weigher(weigher); - assertThat(builder.weigher).isSameInstanceAs(weigher); - builder.build(); - } - - /* --------------- expireAfterAccess --------------- */ - - @Test(expectedExceptions = IllegalArgumentException.class) - public void expireAfterAccess_negative() { - Caffeine.newBuilder().expireAfterAccess(-1, TimeUnit.MILLISECONDS); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void expireAfterAccess_expiry() { - Caffeine.newBuilder().expireAfter(expiry).expireAfterAccess(1, TimeUnit.MILLISECONDS); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void expireAfterAccess_twice() { - Caffeine.newBuilder().expireAfterAccess(1, TimeUnit.MILLISECONDS) - .expireAfterAccess(1, TimeUnit.MILLISECONDS); - } - - @Test - public void expireAfterAccess_small() { - var builder = Caffeine.newBuilder().expireAfterAccess(0, TimeUnit.MILLISECONDS); - assertThat(builder.expireAfterAccessNanos).isEqualTo(0); - var expiration = builder.build().policy().expireAfterAccess().orElseThrow(); - assertThat(expiration.getExpiresAfter(TimeUnit.MILLISECONDS)).isEqualTo(0); - } - - @Test - public void expireAfterAccess_large() { - var builder = Caffeine.newBuilder().expireAfterAccess(Integer.MAX_VALUE, TimeUnit.NANOSECONDS); - assertThat(builder.expireAfterAccessNanos).isEqualTo(Integer.MAX_VALUE); - var expiration = builder.build().policy().expireAfterAccess().orElseThrow(); - assertThat(expiration.getExpiresAfter(TimeUnit.NANOSECONDS)).isEqualTo(Integer.MAX_VALUE); - } - - /* --------------- expireAfterAccess: java.time --------------- */ - - @Test(expectedExceptions = IllegalArgumentException.class) - public void expireAfterAccess_duration_negative() { - Caffeine.newBuilder().expireAfterAccess(Duration.ofMillis(-1)); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void expireAfterAccess_duration_expiry() { - Caffeine.newBuilder().expireAfter(expiry).expireAfterAccess(Duration.ofMillis(1)); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void expireAfterAccess_duration_twice() { - Caffeine.newBuilder().expireAfterAccess(Duration.ofMillis(1)) - .expireAfterAccess(Duration.ofMillis(1)); - } - - @Test - public void expireAfterAccess_duration() { - var builder = Caffeine.newBuilder().expireAfterAccess(Duration.ofMinutes(1)); - assertThat(builder.expireAfterAccessNanos).isEqualTo(Duration.ofMinutes(1).toNanos()); - var expiration = builder.build().policy().expireAfterAccess().orElseThrow(); - assertThat(expiration.getExpiresAfter()).isEqualTo(Duration.ofMinutes(1)); - } - - @Test - public void expireAfterAccess_duration_immediate() { - var builder = Caffeine.newBuilder().expireAfterAccess(Duration.ZERO); - assertThat(builder.expireAfterAccessNanos).isEqualTo(0); - var expiration = builder.build().policy().expireAfterAccess().orElseThrow(); - assertThat(expiration.getExpiresAfter(TimeUnit.MILLISECONDS)).isEqualTo(0); - } - - @Test - public void expireAfterAccess_duration_excessive() { - var builder = Caffeine.newBuilder().expireAfterAccess(ChronoUnit.FOREVER.getDuration()); - assertThat(builder.expireAfterAccessNanos).isEqualTo(Long.MAX_VALUE); - var expiration = builder.build().policy().expireAfterAccess().orElseThrow(); - assertThat(expiration.getExpiresAfter(TimeUnit.NANOSECONDS)).isEqualTo(Long.MAX_VALUE); - } - - /* --------------- expireAfterWrite --------------- */ - - @Test(expectedExceptions = IllegalArgumentException.class) - public void expireAfterWrite_negative() { - Caffeine.newBuilder().expireAfterWrite(-1, TimeUnit.MILLISECONDS); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void expireAfterWrite_expiry() { - Caffeine.newBuilder().expireAfter(expiry).expireAfterWrite(1, TimeUnit.MILLISECONDS); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void expireAfterWrite_twice() { - Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MILLISECONDS) - .expireAfterWrite(1, TimeUnit.MILLISECONDS); - } - - @Test - public void expireAfterWrite_small() { - var builder = Caffeine.newBuilder().expireAfterWrite(0, TimeUnit.MILLISECONDS); - assertThat(builder.expireAfterWriteNanos).isEqualTo(0); - var expiration = builder.build().policy().expireAfterWrite().orElseThrow(); - assertThat(expiration.getExpiresAfter(TimeUnit.MILLISECONDS)).isEqualTo(0); - } - - @Test - public void expireAfterWrite_large() { - var builder = Caffeine.newBuilder() - .expireAfterWrite(Integer.MAX_VALUE, TimeUnit.NANOSECONDS); - assertThat(builder.expireAfterWriteNanos).isEqualTo(Integer.MAX_VALUE); - var expiration = builder.build().policy().expireAfterWrite().orElseThrow(); - assertThat(expiration.getExpiresAfter(TimeUnit.NANOSECONDS)).isEqualTo(Integer.MAX_VALUE); - } - - /* --------------- expireAfterWrite: java.time --------------- */ - - @Test(expectedExceptions = IllegalArgumentException.class) - public void expireAfterWrite_duration_negative() { - Caffeine.newBuilder().expireAfterWrite(Duration.ofMillis(-1)); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void expireAfterWrite_duration_expiry() { - Caffeine.newBuilder().expireAfter(expiry).expireAfterWrite(Duration.ofMillis(1)); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void expireAfterWrite_duration_twice() { - Caffeine.newBuilder().expireAfterWrite(Duration.ofMillis(1)) - .expireAfterWrite(Duration.ofMillis(1)); - } - - @Test - public void expireAfterWrite_duration() { - var builder = Caffeine.newBuilder().expireAfterWrite(Duration.ofMinutes(1)); - assertThat(builder.expireAfterWriteNanos).isEqualTo(Duration.ofMinutes(1).toNanos()); - var expiration = builder.build().policy().expireAfterWrite().orElseThrow(); - assertThat(expiration.getExpiresAfter()).isEqualTo(Duration.ofMinutes(1)); - } - - @Test - public void expireAfterWrite_duration_immediate() { - var builder = Caffeine.newBuilder().expireAfterWrite(Duration.ZERO); - assertThat(builder.expireAfterWriteNanos).isEqualTo(0); - var expiration = builder.build().policy().expireAfterWrite().orElseThrow(); - assertThat(expiration.getExpiresAfter(TimeUnit.MILLISECONDS)).isEqualTo(0); - } - - @Test - public void expireAfterWrite_duration_excessive() { - var builder = Caffeine.newBuilder().expireAfterWrite(ChronoUnit.FOREVER.getDuration()); - assertThat(builder.expireAfterWriteNanos).isEqualTo(Long.MAX_VALUE); - var expiration = builder.build().policy().expireAfterWrite().orElseThrow(); - assertThat(expiration.getExpiresAfter(TimeUnit.NANOSECONDS)).isEqualTo(Long.MAX_VALUE); - } - - /* --------------- expiry --------------- */ - - @Test(expectedExceptions = NullPointerException.class) - public void expireAfter_null() { - Caffeine.newBuilder().expireAfter(null); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void expireAfter_twice() { - Caffeine.newBuilder().expireAfter(expiry).expireAfter(expiry); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void expireAfter_access() { - Caffeine.newBuilder().expireAfterAccess(1, TimeUnit.MILLISECONDS).expireAfter(expiry); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void expireAfter_write() { - Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MILLISECONDS).expireAfter(expiry); - } - - @Test - public void expireAfter() { - var builder = Caffeine.newBuilder().expireAfter(expiry); - assertThat(builder.expiry).isSameInstanceAs(expiry); - builder.build(); - } - - /* --------------- refreshAfterWrite --------------- */ - - @Test(expectedExceptions = IllegalArgumentException.class) - public void refreshAfterWrite_negative() { - Caffeine.newBuilder().refreshAfterWrite(-1, TimeUnit.MILLISECONDS); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void refreshAfterWrite_twice() { - Caffeine.newBuilder().refreshAfterWrite(1, TimeUnit.MILLISECONDS) - .refreshAfterWrite(1, TimeUnit.MILLISECONDS); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void refreshAfterWrite_noCacheLoader() { - Caffeine.newBuilder().refreshAfterWrite(1, TimeUnit.MILLISECONDS).build(); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void refreshAfterWrite_zero() { - Caffeine.newBuilder().refreshAfterWrite(0, TimeUnit.MILLISECONDS); - } - - @Test - public void refreshAfterWrite() { - var builder = Caffeine.newBuilder() - .refreshAfterWrite(1, TimeUnit.MILLISECONDS); - assertThat(builder.getRefreshAfterWriteNanos()).isEqualTo(TimeUnit.MILLISECONDS.toNanos(1)); - builder.build(k -> k); - } - - /* --------------- refreshAfterWrite: java.time --------------- */ - - @Test(expectedExceptions = IllegalArgumentException.class) - public void refreshAfterWrite_duration_negative() { - Caffeine.newBuilder().refreshAfterWrite(Duration.ofMillis(-1)); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void refreshAfterWrite_duration_twice() { - Caffeine.newBuilder().refreshAfterWrite(Duration.ofMillis(1)) - .refreshAfterWrite(Duration.ofMillis(1)); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void refreshAfterWrite_duration_noCacheLoader() { - Caffeine.newBuilder().refreshAfterWrite(Duration.ofMillis(1)).build(); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void refreshAfterWrite_duration_zero() { - Caffeine.newBuilder().refreshAfterWrite(Duration.ZERO); - } - - @Test - public void refreshAfterWrite_duration() { - var builder = Caffeine.newBuilder().refreshAfterWrite(Duration.ofMinutes(1)); - assertThat(builder.getRefreshAfterWriteNanos()).isEqualTo(Duration.ofMinutes(1).toNanos()); - builder.build(k -> k); - } - - @Test - public void refreshAfterWrite_excessive() { - var builder = Caffeine.newBuilder().refreshAfterWrite(ChronoUnit.FOREVER.getDuration()); - assertThat(builder.getRefreshAfterWriteNanos()).isEqualTo(Long.MAX_VALUE); - builder.build(k -> k); - } - - /* --------------- weakKeys --------------- */ - - @Test(expectedExceptions = IllegalStateException.class) - public void weakKeys_twice() { - Caffeine.newBuilder().weakKeys().weakKeys(); - } - - @Test - public void weakKeys() { - Caffeine.newBuilder().weakKeys().build(); - } - - /* --------------- weakValues --------------- */ - - @Test(expectedExceptions = IllegalStateException.class) - public void weakValues_twice() { - Caffeine.newBuilder().weakValues().weakValues(); - } - - @Test - public void weakValues() { - Caffeine.newBuilder().weakValues().build(); - } - - /* --------------- softValues --------------- */ - - @Test(expectedExceptions = IllegalStateException.class) - public void softValues_twice() { - Caffeine.newBuilder().softValues().softValues(); - } - - @Test - public void softValues() { - Caffeine.newBuilder().softValues().build(); - } - - /* --------------- scheduler --------------- */ - - @Test(expectedExceptions = NullPointerException.class) - public void scheduler_null() { - Caffeine.newBuilder().scheduler(null); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void scheduler_twice() { - Caffeine.newBuilder().scheduler(Scheduler.disabledScheduler()) - .scheduler(Scheduler.disabledScheduler()); - } - - @Test - public void scheduler_system() { - var builder = Caffeine.newBuilder().scheduler(Scheduler.systemScheduler()); - assertThat(builder.getScheduler()).isSameInstanceAs(Scheduler.systemScheduler()); - builder.build(); - } - - @Test - public void scheduler_custom() { - Scheduler scheduler = (executor, task, delay, unit) -> DisabledFuture.INSTANCE; - var builder = Caffeine.newBuilder().scheduler(scheduler); - assertThat(((GuardedScheduler) builder.getScheduler()).delegate).isSameInstanceAs(scheduler); - builder.build(); - } - - /* --------------- executor --------------- */ - - @Test(expectedExceptions = NullPointerException.class) - public void executor_null() { - Caffeine.newBuilder().executor(null); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void executor_twice() { - Caffeine.newBuilder().executor(directExecutor()) - .executor(directExecutor()); - } - - @Test - public void executor() { - var builder = Caffeine.newBuilder().executor(directExecutor()); - assertThat(builder.getExecutor()).isSameInstanceAs(directExecutor()); - builder.build(); - } - - /* --------------- ticker --------------- */ - - @Test(expectedExceptions = NullPointerException.class) - public void ticker_null() { - Caffeine.newBuilder().ticker(null); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void ticker_twice() { - Caffeine.newBuilder().ticker(Ticker.systemTicker()).ticker(Ticker.systemTicker()); - } - - @Test - public void ticker() { - Ticker ticker = new FakeTicker()::read; - var builder = Caffeine.newBuilder().ticker(ticker); - assertThat(builder.ticker).isSameInstanceAs(ticker); - builder.build(); - } - - /* --------------- stats --------------- */ - - @Test(expectedExceptions = NullPointerException.class) - public void recordStats_null() { - Caffeine.newBuilder().recordStats(null); - } - - @Test - public void recordStats_twice() { - Supplier supplier = () -> statsCounter; - Runnable[] tasks = { - () -> Caffeine.newBuilder().recordStats().recordStats(), - () -> Caffeine.newBuilder().recordStats(supplier).recordStats(), - () -> Caffeine.newBuilder().recordStats().recordStats(supplier), - () -> Caffeine.newBuilder().recordStats(supplier).recordStats(supplier), - }; - for (Runnable task : tasks) { - try { - task.run(); - Assert.fail(); - } catch (IllegalStateException expected) { - } - } - } - - @Test - public void recordStats() { - var builder = Caffeine.newBuilder().recordStats(); - assertThat(builder.statsCounterSupplier).isEqualTo(Caffeine.ENABLED_STATS_COUNTER_SUPPLIER); - builder.build(); - } - - @Test - public void recordStats_custom() { - Supplier supplier = () -> statsCounter; - var builder = Caffeine.newBuilder().recordStats(supplier); - builder.statsCounterSupplier.get().recordEviction(1, RemovalCause.SIZE); - verify(statsCounter).recordEviction(1, RemovalCause.SIZE); - builder.build(); - } - - /* --------------- removalListener --------------- */ - - @Test(expectedExceptions = NullPointerException.class) - public void removalListener_null() { - Caffeine.newBuilder().removalListener(null); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void removalListener_twice() { - Caffeine.newBuilder().removalListener((k, v, c) -> { - }).removalListener((k, v, c) -> { - }); - } - - @Test - public void removalListener() { - RemovalListener removalListener = (k, v, c) -> { - }; - var builder = Caffeine.newBuilder().removalListener(removalListener); - assertThat(builder.getRemovalListener(false)).isSameInstanceAs(removalListener); - builder.build(); - } - - /* --------------- removalListener --------------- */ - - @Test(expectedExceptions = NullPointerException.class) - public void evictionListener_null() { - Caffeine.newBuilder().evictionListener(null); - } - - @Test(expectedExceptions = IllegalStateException.class) - public void evictionListener_twice() { - Caffeine.newBuilder().evictionListener((k, v, c) -> { - }).evictionListener((k, v, c) -> { - }); - } - - @Test - public void evictionListener() { - RemovalListener removalListener = (k, v, c) -> { - }; - var builder = Caffeine.newBuilder().evictionListener(removalListener); - assertThat(builder.evictionListener).isSameInstanceAs(removalListener); - builder.build(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/EvictionTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/EvictionTest.java deleted file mode 100644 index bdeaf8d..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/EvictionTest.java +++ /dev/null @@ -1,1236 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import static com.github.benmanes.caffeine.cache.RemovalCause.SIZE; -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContext.intern; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -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.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.ConcurrentTestHarness.executor; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static uk.org.lidalia.slf4jext.Level.WARN; - -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.testng.Assert; -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.CacheExpiry; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheWeigher; -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.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.CacheValidationListener; -import com.github.benmanes.caffeine.cache.testing.CheckMaxLogLevel; -import com.github.benmanes.caffeine.cache.testing.CheckNoStats; -import com.github.benmanes.caffeine.cache.testing.RemovalListeners.RejectingRemovalListener; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; -import com.google.common.collect.Range; - -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@Test(dataProviderClass = CacheProvider.class) -public final class EvictionTest { - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, maximumSize = Maximum.FULL, - weigher = {CacheWeigher.DISABLED, CacheWeigher.TEN}, removalListener = Listener.REJECTING) - public void removalListener_fails(Cache cache, CacheContext context) { - var removalListener = (RejectingRemovalListener) context.removalListener(); - removalListener.rejected = 0; - long size = cache.estimatedSize(); - for (Int key : context.absentKeys()) { - cache.put(key, key); - if (cache.estimatedSize() != ++size) { - break; - } - } - assertThat(removalListener.rejected).isEqualTo(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, maximumSize = {Maximum.ZERO, Maximum.ONE, Maximum.FULL}, - weigher = {CacheWeigher.DISABLED, CacheWeigher.TEN}) - public void evict(Cache cache, CacheContext context) { - cache.putAll(context.absent()); - if (context.isWeighted()) { - assertThat(context).hasWeightedSize(context.maximumWeight()); - } else { - assertThat(cache).hasSize(context.maximumSize()); - } - - var evicted = new HashMap(); - evicted.putAll(Maps.difference(context.original(), cache.asMap()).entriesOnlyOnLeft()); - evicted.putAll(Maps.difference(context.absent(), cache.asMap()).entriesOnlyOnLeft()); - assertThat(context).stats().evictions(evicted.size()); - assertThat(evicted).hasSize(context.absentKeys().size()); - assertThat(context).notifications().withCause(SIZE) - .contains(evicted).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - maximumSize = Maximum.TEN, weigher = CacheWeigher.COLLECTION, - initialCapacity = InitialCapacity.EXCESSIVE) - public void evict_weighted(Cache> cache, CacheContext context) { - // Enforce full initialization of internal structures - for (int i = 0; i < context.maximumSize(); i++) { - cache.put(Int.valueOf(i), List.of()); - } - cache.invalidateAll(); - - var value1 = intern(Int.listOf(8, 9, 10)); - var value2 = intern(Int.listOf(3, 4, 5, 6, 7)); - var value3 = intern(Int.listOf(1, 2)); - var value4 = intern(Int.listOf(11)); - var value5 = intern(Int.listOf(12, 13, 14, 15, 16, 17, 18, 19, 20)); - - // Never evicted - cache.put(Int.valueOf(0), List.of()); - - cache.put(Int.valueOf(1), value1); - cache.put(Int.valueOf(2), value2); - cache.put(Int.valueOf(3), value3); - assertThat(cache).hasSize(4); - assertThat(context).hasWeightedSize(10); - - // [0(0) | 1(3), 2(5), 3(2)] -> [4(1), 0(0) | 3(2), 2(5), 1(3)] -> [0(0) | 3(2), 2(5), 1(3)] - // #1: candidate=0 vs victim=1 -> skip candidate due to zero weight - // #2: candidate=4 vs victim=1 -> f(4) == f(1) -> evict candidate - cache.put(Int.valueOf(4), value4); - assertThat(cache).hasSize(4); - assertThat(context).hasWeightedSize(10); - assertThat(cache).doesNotContainKey(Int.valueOf(4)); - - // [0(0) | 3(2), 2(5), 1(3)] -> [4(1), 0(0) | 3(2), 2(5), 1(3)] -> [4(1), 0(0) | 3(2), 2(5)] - // #1: candidate=0 vs victim=1 => skip candidate due to zero weight - // #2: candidate=4 vs victim=1 -> f(4) > f(1) -> evict victim - cache.put(Int.valueOf(4), value4); - assertThat(cache).hasSize(4); - assertThat(context).hasWeightedSize(8); - assertThat(cache).doesNotContainKey(Int.valueOf(1)); - - // [4(1), 0(0) | 3(2), 2(5)] -> [4(1), 0(0), 5(9) | 3(2), 2(5)] -> [4(1), 0(0) | 3(2), 2(5)] - // 5 exceeds window (9 >> 1), so it is inserted at the LRU position for immediate promotion - // #1: candidate=5 vs victim=2 => f(5) == f(2) -> evict candidate - cache.put(Int.valueOf(5), value5); - assertThat(cache).hasSize(4); - assertThat(context).hasWeightedSize(8); - assertThat(context).stats().evictions(3).evictionWeight(13); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, initialCapacity = InitialCapacity.EXCESSIVE, - maximumSize = Maximum.TEN, weigher = CacheWeigher.COLLECTION) - public void evict_weighted_reorder(Cache> cache, - CacheContext context, Eviction eviction) { - eviction.setMaximum(3); - for (int i = 1; i <= 3; i++) { - cache.put(Int.valueOf(i), intern(Int.listOf(1))); - } - cache.asMap().computeIfPresent(Int.valueOf(1), (k, v) -> intern(Int.listOf(1, 2))); - assertThat(cache).containsEntry(Int.valueOf(1), intern(Int.listOf(1, 2))); - assertThat(context).hasWeightedSize(3); - assertThat(cache).hasSize(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = Listener.CONSUMING, - maximumSize = Maximum.TEN, weigher = CacheWeigher.VALUE) - public void evict_weighted_entryTooBig(Cache cache, CacheContext context) { - cache.put(Int.valueOf(9), Int.valueOf(9)); - cache.put(Int.valueOf(1), Int.valueOf(1)); - assertThat(cache).hasSize(2); - cache.policy().eviction().ifPresent(eviction -> assertThat(context).hasWeightedSize(10)); - - cache.put(Int.valueOf(20), Int.valueOf(20)); - assertThat(cache).hasSize(2); - cache.policy().eviction().ifPresent(eviction -> assertThat(context).hasWeightedSize(10)); - assertThat(context).notifications().withCause(SIZE) - .contains(Int.valueOf(20), Int.valueOf(20)).exclusively(); - if (context.isCaffeine()) { - assertThat(context).stats().evictionWeight(20); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, maximumSize = Maximum.TEN, - weigher = CacheWeigher.VALUE, removalListener = Listener.CONSUMING) - @SuppressWarnings("FutureReturnValueIgnored") - public void evict_weighted_async(AsyncCache cache, CacheContext context) { - var ready = new AtomicBoolean(); - var done = new AtomicBoolean(); - var valueFuture = CompletableFuture.supplyAsync(() -> { - await().untilTrue(ready); - return Int.valueOf(6); - }, executor); - valueFuture.whenComplete((r, e) -> done.set(true)); - - cache.put(Int.valueOf(5), Int.futureOf(5)); - cache.put(Int.valueOf(4), Int.futureOf(4)); - cache.put(Int.valueOf(6), valueFuture); - assertThat(context).hasWeightedSize(9); - assertThat(cache).hasSize(3); - - ready.set(true); - await().untilTrue(done); - await().untilAsserted(() -> assertThat(cache).hasSize(2)); - await().untilAsserted(() -> assertThat(context) - .hasWeightedSizeLessThan(context.maximumWeight() + 1)); - - int expected = 15 - cache.synchronous().asMap().values().stream().mapToInt(Int::intValue).sum(); - assertThat(context).stats().evictionWeight(expected); - assertThat(context).notifications().withCause(SIZE) - .contains(Int.valueOf(expected), Int.valueOf(expected)).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.ZERO, weigher = CacheWeigher.COLLECTION) - @SuppressWarnings("FutureReturnValueIgnored") - public void evict_zero_async(AsyncCache> cache, CacheContext context) { - var ready = new AtomicBoolean(); - var done = new AtomicBoolean(); - var valueFuture = CompletableFuture.supplyAsync(() -> { - await().untilTrue(ready); - return Int.listOf(1, 2, 3, 4, 5); - }, executor); - valueFuture.whenComplete((r, e) -> done.set(true)); - - cache.put(context.absentKey(), valueFuture); - assertThat(context).hasWeightedSize(0); - assertThat(cache).hasSize(1); - - ready.set(true); - await().untilTrue(done); - await().untilAsserted(() -> assertThat(cache).isEmpty()); - assertThat(context).notifications().withCause(SIZE) - .contains(Map.entry(context.absentKey(), Int.listOf(1, 2, 3, 4, 5))) - .exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, maximumSize = Maximum.FULL, - weigher = CacheWeigher.DISABLED, evictionListener = Listener.MOCKITO, - removalListener = Listener.REJECTING) - public void evict_evictionListenerFails(Cache cache, CacheContext context) { - doThrow(RuntimeException.class) - .when(context.evictionListener()).onRemoval(any(), any(), any()); - cache.policy().eviction().ifPresent(policy -> policy.setMaximum(0)); - verify(context.evictionListener(), times((int) context.initialSize())) - .onRemoval(any(), any(), any()); - } - - /* --------------- Weighted --------------- */ - - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.NEGATIVE) - @Test(dataProvider = "caches", - expectedExceptions = {IllegalArgumentException.class, IllegalStateException.class}) - public void put_negativeWeight(Cache cache, CacheContext context) { - try { - cache.put(context.absentKey(), context.absentValue()); - } finally { - assertThat(cache).doesNotContainKey(context.absentKey()); - } - } - - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void put_weigherFails_insert(Cache cache, CacheContext context) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.put(context.absentKey(), context.absentValue()); - } finally { - assertThat(cache).doesNotContainKey(context.absentKey()); - } - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void put_weigherFails_update(Cache cache, - CacheContext context, Eviction eviction) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.put(context.firstKey(), context.absentValue()); - } finally { - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(eviction.weightOf(context.firstKey())).hasValue(1); - } - } - - @CacheSpec(maximumSize = Maximum.FULL, - weigher = CacheWeigher.ZERO, population = Population.EMPTY) - @Test(dataProvider = "caches") - public void put_zeroWeight(Cache cache, CacheContext context) { - cache.put(context.absentKey(), context.absentValue()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void put(Cache> cache, CacheContext context) { - cache.put("a", intern(Int.listOf(1, 2, 3))); - assertThat(context).hasWeightedSize(3); - assertThat(cache).hasSize(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void put_sameWeight(Cache> cache, CacheContext context) { - cache.putAll(intern(Map.of("a", Int.listOf(1, 2, 3), "b", Int.listOf(1)))); - cache.put("a", intern(Int.listOf(-1, -2, -3))); - assertThat(context).hasWeightedSize(4); - assertThat(cache).hasSize(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void put_changeWeight(Cache> cache, CacheContext context) { - cache.putAll(intern(Map.of("a", Int.listOf(1, 2, 3), "b", Int.listOf(1)))); - cache.put("a", intern(Int.listOf(-1, -2, -3, -4))); - assertThat(context).hasWeightedSize(5); - assertThat(cache).hasSize(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - @SuppressWarnings("FutureReturnValueIgnored") - public void put_asyncWeight(AsyncCache> cache, CacheContext context) { - var ready = new AtomicBoolean(); - var done = new AtomicBoolean(); - var valueFuture = CompletableFuture.supplyAsync(() -> { - await().untilTrue(ready); - return Int.listOf(1, 2, 3, 4, 5); - }, executor); - valueFuture.whenComplete((r, e) -> done.set(true)); - - cache.put(context.absentKey(), valueFuture); - assertThat(context).hasWeightedSize(0); - assertThat(cache).hasSize(1); - - ready.set(true); - await().untilTrue(done); - await().untilAsserted(() -> assertThat(cache).hasSize(1)); - await().untilAsserted(() -> assertThat(context).hasWeightedSize(5)); - } - - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches") - public void replace_weigherFails_absent(Cache cache, CacheContext context) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.asMap().replace(context.absentKey(), context.absentValue()); - } catch (IllegalStateException expected) { - // optionally thrown - } finally { - assertThat(cache).doesNotContainKey(context.absentKey()); - } - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void replace_weigherFails_present(Cache cache, - CacheContext context, Eviction eviction) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.asMap().replace(context.firstKey(), context.absentValue()); - } finally { - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(eviction.weightOf(context.firstKey())).hasValue(1); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void replace_sameWeight(Cache> cache, CacheContext context) { - cache.putAll(intern(Map.of("a", Int.listOf(1, 2, 3), "b", Int.listOf(1)))); - cache.asMap().replace("a", intern(Int.listOf(-1, -2, -3))); - assertThat(context).hasWeightedSize(4); - assertThat(cache).hasSize(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void replace_changeWeight(Cache> cache, CacheContext context) { - cache.putAll(intern(Map.of("a", Int.listOf(1, 2, 3), "b", Int.listOf(1)))); - cache.asMap().replace("a", intern(Int.listOf(-1, -2, -3, -4))); - assertThat(context).hasWeightedSize(5); - assertThat(cache).hasSize(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void replaceConditionally_sameWeight( - Cache> cache, CacheContext context) { - var oldValue = intern(Int.listOf(1, 2, 3)); - var newValue = intern(Int.listOf(4, 5, 6)); - cache.putAll(intern(Map.of("a", oldValue, "b", Int.listOf(1)))); - assertThat(cache.asMap().replace("a", oldValue, newValue)).isTrue(); - assertThat(context).hasWeightedSize(4); - assertThat(cache).hasSize(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void replaceConditionally_changeWeight( - Cache> cache, CacheContext context) { - List oldValue = intern(Int.listOf(1, 2, 3)); - List newValue = intern(Int.listOf(-1, -2, -3, -4)); - cache.putAll(intern(Map.of("a", oldValue, "b", Int.listOf(1)))); - cache.asMap().replace("a", oldValue, newValue); - assertThat(context).hasWeightedSize(5); - assertThat(cache).hasSize(2); - } - - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches") - public void replaceConditionally_weigherFails_absent( - Cache cache, CacheContext context) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.asMap().replace(context.absentKey(), context.absentValue(), context.absentValue()); - } catch (IllegalStateException expected) { - // optionally thrown - } finally { - assertThat(cache).doesNotContainKey(context.absentKey()); - } - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void replaceConditionally_weigherFails_presentKey( - Cache cache, CacheContext context, Eviction eviction) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.asMap().replace(context.firstKey(), context.absentValue(), context.absentValue()); - } finally { - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(eviction.weightOf(context.firstKey())).hasValue(1); - } - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void replaceConditionally_weigherFails_presentKeyAndValue( - Cache cache, CacheContext context, Eviction eviction) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.asMap().replace(context.firstKey(), - context.original().get(context.firstKey()), context.absentValue()); - } finally { - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(eviction.weightOf(context.firstKey())).hasValue(1); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void replaceConditionally_fails(Cache> cache, CacheContext context) { - List oldValue = intern(Int.listOf(1)); - List newValue = intern(Int.listOf(4, 5)); - cache.putAll(intern(Map.of("a", Int.listOf(1, 2, 3), "b", oldValue))); - assertThat(cache.asMap().replace("a", oldValue, newValue)).isFalse(); - assertThat(context).hasWeightedSize(4); - assertThat(cache).hasSize(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void remove(Cache> cache, CacheContext context) { - cache.putAll(intern(Map.of("a", Int.listOf(1, 2, 3), "b", Int.listOf(1)))); - assertThat(cache.asMap().remove("a")).containsExactlyElementsIn(Int.listOf(1, 2, 3)).inOrder(); - assertThat(context).hasWeightedSize(1); - assertThat(cache).hasSize(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void removeConditionally(Cache> cache, CacheContext context) { - var oldValue = intern(Int.listOf(1, 2, 3)); - cache.putAll(intern(Map.of("a", oldValue, "b", Int.listOf(1)))); - assertThat(cache.asMap().remove("a", oldValue)).isTrue(); - assertThat(context).hasWeightedSize(1); - assertThat(cache).hasSize(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void removeConditionally_fails(Cache> cache, CacheContext context) { - cache.putAll(intern(Map.of("a", Int.listOf(1, 2, 3), "b", Int.listOf(1)))); - assertThat(cache.asMap().remove("a", Int.listOf(-1, -2, -3))).isFalse(); - assertThat(context).hasWeightedSize(4); - assertThat(cache).hasSize(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION) - public void invalidateAll(Cache> cache, CacheContext context) { - cache.putAll(Map.of("a", intern(Int.listOf(1, 2, 3)), "b", intern(Int.listOf(1)))); - cache.invalidateAll(); - assertThat(cache).isEmpty(); - } - - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void computeIfAbsent_weigherFails(Cache cache, CacheContext context) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.asMap().computeIfAbsent(context.absentKey(), key -> context.absentValue()); - } finally { - assertThat(cache).doesNotContainKey(context.absentKey()); - } - } - - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.TEN) - @Test(dataProvider = "caches") - public void computeIfAbsent(Cache cache, - CacheContext context, Eviction eviction) { - cache.asMap().computeIfAbsent(context.absentKey(), key -> context.absentValue()); - assertThat(eviction.weightOf(context.absentKey())).hasValue(10); - assertThat(context).hasWeightedSize(10); - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void computeIfPresent_weigherFails(Cache cache, - CacheContext context, Eviction eviction) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.asMap().computeIfPresent(context.firstKey(), (key, value) -> context.absentValue()); - } finally { - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(eviction.weightOf(context.firstKey())).hasValue(1); - } - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.VALUE) - @Test(dataProvider = "caches") - public void computeIfPresent(Cache cache, - CacheContext context, Eviction eviction) { - long weightedSize = eviction.weightedSize().getAsLong() - + Math.abs(context.absentValue().intValue()) - context.firstKey().intValue(); - cache.asMap().computeIfPresent(context.firstKey(), (key, value) -> context.absentValue()); - assertThat(eviction.weightOf(context.firstKey())) - .hasValue(Math.abs(context.absentValue().intValue())); - assertThat(context).hasWeightedSize(weightedSize); - } - - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void compute_weigherFails_absent(Cache cache, CacheContext context) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.asMap().compute(context.absentKey(), (key, value) -> context.absentValue()); - } finally { - assertThat(cache).doesNotContainKey(context.absentKey()); - } - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void compute_weigherFails_present(Cache cache, - CacheContext context, Eviction eviction) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.asMap().compute(context.firstKey(), (key, value) -> context.absentValue()); - } finally { - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(eviction.weightOf(context.firstKey())).hasValue(1); - } - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.VALUE) - @Test(dataProvider = "caches") - public void compute_insert(Cache cache, - CacheContext context, Eviction eviction) { - long weightedSize = eviction.weightedSize().getAsLong() - + Math.abs(context.absentValue().intValue()); - cache.asMap().compute(context.absentKey(), (key, value) -> context.absentValue()); - assertThat(eviction.weightOf(context.absentKey())) - .hasValue(Math.abs(context.absentValue().intValue())); - assertThat(context).hasWeightedSize(weightedSize); - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.VALUE) - @Test(dataProvider = "caches") - public void compute_update(Cache cache, - CacheContext context, Eviction eviction) { - long weightedSize = eviction.weightedSize().getAsLong() - + Math.abs(context.absentValue().intValue()) - context.firstKey().intValue(); - cache.asMap().compute(context.firstKey(), (key, value) -> context.absentValue()); - assertThat(eviction.weightOf(context.firstKey())) - .hasValue(Math.abs(context.absentValue().intValue())); - assertThat(context).hasWeightedSize(weightedSize); - } - - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void merge_weigherFails_absent(Cache cache, CacheContext context) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.asMap().merge(context.absentKey(), - context.absentKey(), (key, value) -> context.absentValue()); - } finally { - assertThat(cache).doesNotContainKey(context.absentKey()); - } - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void merge_weigherFails_present(Cache cache, - CacheContext context, Eviction eviction) { - try { - when(context.weigher().weigh(any(), any())).thenThrow(IllegalStateException.class); - cache.asMap().merge(context.firstKey(), - context.absentValue(), (key, value) -> context.absentValue()); - } finally { - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(eviction.weightOf(context.firstKey())).hasValue(1); - } - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.VALUE) - @Test(dataProvider = "caches") - public void merge_absent(Cache cache, - CacheContext context, Eviction eviction) { - long weightedSize = eviction.weightedSize().getAsLong() - + Math.abs(context.absentValue().intValue()); - cache.asMap().merge(context.absentKey(), - context.absentKey(), (key, value) -> context.absentValue()); - assertThat(eviction.weightOf(context.absentKey())) - .hasValue(Math.abs(context.absentValue().intValue())); - assertThat(context).hasWeightedSize(weightedSize); - } - - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.VALUE) - @Test(dataProvider = "caches") - public void merge_update(Cache cache, - CacheContext context, Eviction eviction) { - long weightedSize = eviction.weightedSize().getAsLong() - + Math.abs(context.absentValue().intValue()) - context.firstKey().intValue(); - cache.asMap().merge(context.firstKey(), - context.absentKey(), (key, value) -> context.absentValue()); - assertThat(eviction.weightOf(context.firstKey())) - .hasValue(Math.abs(context.absentValue().intValue())); - assertThat(context).hasWeightedSize(weightedSize); - } - - /* --------------- Policy --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, maximumSize = Maximum.UNREACHABLE) - public void getIfPresentQuietly(Cache cache, - CacheContext context, Eviction eviction) { - var expected = eviction.hottest(Integer.MAX_VALUE).keySet(); - assertThat(cache.policy().getIfPresentQuietly(context.firstKey())).isNotNull(); - assertThat(cache.policy().getIfPresentQuietly(context.middleKey())).isNotNull(); - assertThat(cache.policy().getIfPresentQuietly(context.lastKey())).isNotNull(); - var actual = eviction.hottest(Integer.MAX_VALUE).keySet(); - assertThat(actual).containsExactlyElementsIn(expected).inOrder(); - } - - /* --------------- Policy: IsWeighted --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL, population = Population.EMPTY) - public void isWeighted(CacheContext context, Eviction eviction) { - assertThat(eviction.isWeighted()).isEqualTo(context.isWeighted()); - } - - /* --------------- Policy: WeightOf --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.VALUE) - public void weightOf(Cache cache, CacheContext context, Eviction eviction) { - Int key = Int.valueOf(1); - cache.put(key, Int.valueOf(1)); - assertThat(eviction.weightOf(key)).hasValue(1); - - cache.put(key, Int.valueOf(2)); - assertThat(eviction.weightOf(key)).hasValue(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL) - public void weightOf_absent(Cache cache, - CacheContext context, Eviction eviction) { - assertThat(eviction.weightOf(context.absentKey())).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, maximumSize = Maximum.FULL, - weigher = CacheWeigher.VALUE, mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void weightOf_expired(Cache cache, - CacheContext context, Eviction eviction) { - cache.put(context.absentKey(), Int.valueOf(1)); - context.ticker().advance(2, TimeUnit.MINUTES); - assertThat(eviction.weightOf(context.absentKey())).isEmpty(); - } - - /* --------------- Policy: WeightedSize --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - maximumSize = Maximum.FULL, weigher = CacheWeigher.DISABLED) - public void weightedSize_absent(Cache cache, - CacheContext context, Eviction eviction) { - assertThat(eviction.weightOf(context.firstKey())).isEmpty(); - assertThat(eviction.weightOf(context.absentKey())).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL, weigher = CacheWeigher.TEN) - public void weightedSize(Cache cache, - CacheContext context, Eviction eviction) { - long weightedSize = 0; - for (Int key : cache.asMap().keySet()) { - weightedSize += eviction.weightOf(key).getAsInt(); - } - assertThat(eviction.weightedSize()).hasValue(weightedSize); - assertThat(weightedSize).isEqualTo(10 * cache.estimatedSize()); - } - - /* --------------- Policy: MaximumSize --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL) - public void maximumSize_decrease(Cache cache, - CacheContext context, Eviction eviction) { - long newSize = context.maximumWeightOrSize() / 2; - eviction.setMaximum(newSize); - assertThat(eviction.getMaximum()).isEqualTo(newSize); - if (context.initialSize() > newSize) { - if (context.isZeroWeighted()) { - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).notifications().isEmpty(); - } else { - assertThat(cache).hasSize(newSize); - assertThat(context).notifications().withCause(SIZE) - .contains(Maps.difference(context.original(), cache.asMap()).entriesOnlyOnLeft()) - .exclusively(); - } - } - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL, weigher = {CacheWeigher.DISABLED, CacheWeigher.TEN}) - public void maximumSize_decrease_min(Cache cache, - CacheContext context, Eviction eviction) { - eviction.setMaximum(0); - assertThat(eviction.getMaximum()).isEqualTo(0); - if (context.initialSize() > 0) { - long expectedSize = context.isZeroWeighted() ? context.initialSize() : 0; - assertThat(cache).hasSize(expectedSize); - } - assertThat(context).notifications().withCause(SIZE) - .contains(context.original()).exclusively(); - } - - @CacheSpec(maximumSize = Maximum.FULL) - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void maximumSize_decrease_negative(Cache cache, - CacheContext context, Eviction eviction) { - try { - eviction.setMaximum(-1); - } finally { - assertThat(eviction.getMaximum()).isEqualTo(context.maximumWeightOrSize()); - assertThat(context).notifications().isEmpty(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void maximumSize_increase(Cache cache, - CacheContext context, Eviction eviction) { - eviction.setMaximum(2 * context.maximumWeightOrSize()); - assertThat(cache).hasSize(context.initialSize()); - assertThat(eviction.getMaximum()).isEqualTo(2 * context.maximumWeightOrSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL, removalListener = Listener.REJECTING) - public void maximumSize_increase_max(Cache cache, - CacheContext context, Eviction eviction) { - eviction.setMaximum(Long.MAX_VALUE); - assertThat(cache).hasSize(context.initialSize()); - assertThat(eviction.getMaximum()).isEqualTo(Long.MAX_VALUE - Integer.MAX_VALUE); // impl detail - } - - /* --------------- Policy: Coldest --------------- */ - - @CacheSpec(maximumSize = Maximum.FULL) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void coldest_unmodifiable(CacheContext context, Eviction eviction) { - eviction.coldest(Integer.MAX_VALUE).clear(); - } - - @CacheSpec(maximumSize = Maximum.FULL) - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void coldest_negative(CacheContext context, Eviction eviction) { - eviction.coldest(-1); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL) - public void coldest_zero(CacheContext context, Eviction eviction) { - assertThat(eviction.coldest(0)).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldest_partial(CacheContext context, Eviction eviction) { - int count = context.original().size() / 2; - assertThat(eviction.coldest(count)).hasSize(count); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, initialCapacity = InitialCapacity.EXCESSIVE, - maximumSize = Maximum.FULL, weigher = {CacheWeigher.DISABLED, CacheWeigher.TEN}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void coldest_order(CacheContext context, Eviction eviction) { - var keys = new LinkedHashSet<>(context.original().keySet()); - var 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).containsExactlyElementsIn(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldest_snapshot(Cache cache, - CacheContext context, Eviction eviction) { - var coldest = eviction.coldest(Integer.MAX_VALUE); - cache.invalidateAll(); - assertThat(coldest).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldestFunc_null(CacheContext context, Eviction eviction) { - eviction.coldest(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldestFunc_nullResult(CacheContext context, Eviction eviction) { - var result = eviction.coldest(stream -> null); - assertThat(result).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldestFunc_throwsException(CacheContext context, Eviction eviction) { - var expected = new IllegalStateException(); - try { - eviction.coldest(stream -> { - throw expected; - }); - Assert.fail(); - } catch (IllegalStateException e) { - assertThat(e).isSameInstanceAs(expected); - } - } - - @Test(dataProvider = "caches", expectedExceptions = ConcurrentModificationException.class) - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldestFunc_concurrentModification(Cache cache, - CacheContext context, Eviction eviction) { - eviction.coldest(stream -> { - cache.put(context.absentKey(), context.absentValue()); - return stream.count(); - }); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class, - expectedExceptionsMessageRegExp = "source already consumed or closed") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldestFunc_closed(CacheContext context, Eviction eviction) { - eviction.coldest(stream -> stream).forEach(e -> { - }); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldestFunc_partial(Cache cache, - CacheContext context, Eviction eviction) { - var result = eviction.coldest(stream -> stream - .limit(context.initialSize() / 2) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache.asMap()).containsAtLeastEntriesIn(result); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldestFunc_full(Cache cache, - CacheContext context, Eviction eviction) { - var result = eviction.coldest(stream -> stream - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache).containsExactlyEntriesIn(result); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, initialCapacity = InitialCapacity.EXCESSIVE, - maximumSize = Maximum.FULL, weigher = {CacheWeigher.DISABLED, CacheWeigher.TEN}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void coldestFunc_order(CacheContext context, Eviction eviction) { - var keys = new LinkedHashSet<>(context.original().keySet()); - var coldest = new LinkedHashSet<>(eviction.coldest(stream -> - stream.map(Map.Entry::getKey).collect(toImmutableList()))); - - // Ignore the last key; hard to predict with W-TinyLFU - keys.remove(context.lastKey()); - coldest.remove(context.lastKey()); - assertThat(coldest).containsExactlyElementsIn(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldestFunc_metadata(Cache cache, - CacheContext context, Eviction eviction) { - var entries = eviction.coldest(stream -> stream.collect(toImmutableList())); - for (var entry : entries) { - assertThat(context).containsEntry(entry); - } - } - - @CacheSpec(maximumSize = Maximum.FULL) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void coldestWeight_unmodifiable(CacheContext context, Eviction eviction) { - eviction.coldestWeighted(Long.MAX_VALUE).clear(); - } - - @CacheSpec(maximumSize = Maximum.FULL) - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void coldestWeighted_negative(CacheContext context, Eviction eviction) { - eviction.coldestWeighted(-1); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL) - public void coldestWeighted_zero(CacheContext context, Eviction eviction) { - if (context.cacheWeigher() == CacheWeigher.ZERO) { - assertThat(eviction.coldestWeighted(0)).isEqualTo(context.original()); - } else { - assertThat(eviction.coldestWeighted(0)).isExhaustivelyEmpty(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldestWeighted_partial(CacheContext context, Eviction eviction) { - long weightedSize = context.original().entrySet().stream() - .mapToLong(entry -> context.weigher().weigh(entry.getKey(), entry.getValue())) - .limit(context.original().size() / 2) - .sum(); - var coldest = eviction.coldestWeighted(weightedSize); - var actualWeighedSize = coldest.entrySet().stream() - .mapToLong(entry -> context.weigher().weigh(entry.getKey(), entry.getValue())) - .sum(); - if (context.isWeighted()) { - assertThat(coldest).hasSizeIn(Range.closed(0, context.original().size())); - } else { - assertThat(coldest).hasSize(context.original().size() / 2); - } - assertThat(actualWeighedSize).isIn(Range.closed(0L, weightedSize)); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, initialCapacity = InitialCapacity.EXCESSIVE, - maximumSize = Maximum.FULL, weigher = {CacheWeigher.DISABLED, CacheWeigher.TEN}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void coldestWeighted_order(CacheContext context, Eviction eviction) { - var keys = new LinkedHashSet<>(context.original().keySet()); - var coldest = new LinkedHashSet<>(eviction.coldestWeighted(Long.MAX_VALUE).keySet()); - - // Ignore the last key; hard to predict with W-TinyLFU - keys.remove(context.lastKey()); - coldest.remove(context.lastKey()); - assertThat(coldest).containsExactlyElementsIn(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void coldestWeighted_snapshot(Cache cache, - CacheContext context, Eviction eviction) { - var coldest = eviction.coldestWeighted(Long.MAX_VALUE); - cache.invalidateAll(); - assertThat(coldest).containsExactlyEntriesIn(context.original()); - } - - /* --------------- Policy: Hottest --------------- */ - - @CacheSpec(maximumSize = Maximum.FULL) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void hottest_unmodifiable(CacheContext context, Eviction eviction) { - eviction.hottest(Integer.MAX_VALUE).clear(); - } - - @CacheSpec(maximumSize = Maximum.FULL) - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void hottest_negative(CacheContext context, Eviction eviction) { - eviction.hottest(-1); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL) - public void hottest_zero(CacheContext context, Eviction eviction) { - assertThat(eviction.hottest(0)).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottest_partial(CacheContext context, Eviction eviction) { - int count = context.original().size() / 2; - assertThat(eviction.hottest(count)).hasSize(count); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, initialCapacity = InitialCapacity.EXCESSIVE, - maximumSize = Maximum.FULL, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void hottest_order(CacheContext context, Eviction eviction) { - var keys = new LinkedHashSet<>(context.original().keySet()); - var hottest = eviction.hottest(Integer.MAX_VALUE).keySet(); - var 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).containsExactlyElementsIn(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottest_snapshot(Cache cache, - CacheContext context, Eviction eviction) { - var hottest = eviction.hottest(Integer.MAX_VALUE); - cache.invalidateAll(); - assertThat(hottest).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottestFunc_null(CacheContext context, Eviction eviction) { - eviction.hottest(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottestFunc_nullResult(CacheContext context, Eviction eviction) { - var result = eviction.hottest(stream -> null); - assertThat(result).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottestFunc_throwsException(CacheContext context, Eviction eviction) { - var expected = new IllegalStateException(); - try { - eviction.hottest(stream -> { - throw expected; - }); - Assert.fail(); - } catch (IllegalStateException e) { - assertThat(e).isSameInstanceAs(expected); - } - } - - @Test(dataProvider = "caches", expectedExceptions = ConcurrentModificationException.class) - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottestFunc_concurrentModification(Cache cache, - CacheContext context, Eviction eviction) { - eviction.hottest(stream -> { - cache.put(context.absentKey(), context.absentValue()); - return stream.count(); - }); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class, - expectedExceptionsMessageRegExp = "source already consumed or closed") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottestFunc_closed(CacheContext context, Eviction eviction) { - eviction.hottest(stream -> stream).forEach(e -> { - }); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottestFunc_partial(Cache cache, - CacheContext context, Eviction eviction) { - var result = eviction.hottest(stream -> stream - .limit(context.initialSize() / 2) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache.asMap()).containsAtLeastEntriesIn(result); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottestFunc_full(Cache cache, - CacheContext context, Eviction eviction) { - var result = eviction.hottest(stream -> stream - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache).containsExactlyEntriesIn(result); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, initialCapacity = InitialCapacity.EXCESSIVE, - maximumSize = Maximum.FULL, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void hottestFunc_order(CacheContext context, Eviction eviction) { - var keys = new LinkedHashSet<>(context.original().keySet()); - var hottest = eviction.hottest(stream -> - stream.map(Map.Entry::getKey).collect(toImmutableList())); - var coldest = new LinkedHashSet<>(hottest.reverse()); - - // Ignore the last key; hard to predict with W-TinyLFU - keys.remove(context.lastKey()); - coldest.remove(context.lastKey()); - assertThat(coldest).containsExactlyElementsIn(keys).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottestFunc_metadata(Cache cache, - CacheContext context, Eviction eviction) { - var entries = eviction.hottest(stream -> stream.collect(toImmutableList())); - for (var entry : entries) { - assertThat(context).containsEntry(entry); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottestWeighted_snapshot(Cache cache, - CacheContext context, Eviction eviction) { - var hottest = eviction.hottestWeighted(Integer.MAX_VALUE); - cache.invalidateAll(); - assertThat(hottest).containsExactlyEntriesIn(context.original()); - } - - @CacheSpec(maximumSize = Maximum.FULL) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void hottestWeighted_unmodifiable(CacheContext context, Eviction eviction) { - eviction.hottestWeighted(Long.MAX_VALUE).clear(); - } - - @CacheSpec(maximumSize = Maximum.FULL) - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void hottestWeighted_negative(CacheContext context, Eviction eviction) { - eviction.hottestWeighted(-1); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL) - public void hottestWeighted_zero(CacheContext context, Eviction eviction) { - if (context.cacheWeigher() == CacheWeigher.ZERO) { - assertThat(eviction.hottestWeighted(0)).isEqualTo(context.original()); - } else { - assertThat(eviction.hottestWeighted(0)).isExhaustivelyEmpty(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - initialCapacity = InitialCapacity.EXCESSIVE, maximumSize = Maximum.FULL) - public void hottestWeighted_partial(CacheContext context, Eviction eviction) { - long weightedSize = context.original().entrySet().stream() - .mapToLong(entry -> context.weigher().weigh(entry.getKey(), entry.getValue())) - .limit(context.original().size() / 2) - .sum(); - var hottest = eviction.hottestWeighted(weightedSize); - var actualWeighedSize = hottest.entrySet().stream() - .mapToLong(entry -> context.weigher().weigh(entry.getKey(), entry.getValue())) - .sum(); - if (context.isWeighted()) { - assertThat(hottest).hasSizeIn(Range.closed(0, context.original().size())); - } else { - assertThat(hottest).hasSize(context.original().size() / 2); - } - assertThat(actualWeighedSize).isIn(Range.closed(0L, weightedSize)); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, initialCapacity = InitialCapacity.EXCESSIVE, - maximumSize = Maximum.FULL, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void hottestWeighted_order(CacheContext context, Eviction eviction) { - var keys = new LinkedHashSet<>(context.original().keySet()); - var hottest = eviction.hottestWeighted(Long.MAX_VALUE).keySet(); - var 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).containsExactlyElementsIn(keys).inOrder(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java deleted file mode 100644 index 9bb9ec9..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java +++ /dev/null @@ -1,1677 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.base.Splitter; -import com.google.common.collect.Maps; -import com.google.common.collect.Range; -import com.google.common.util.concurrent.Futures; -import org.mockito.ArgumentCaptor; -import org.mockito.stubbing.Answer; -import org.testng.Assert; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -import static com.github.benmanes.caffeine.cache.Pacer.TOLERANCE; -import static com.github.benmanes.caffeine.cache.RemovalCause.EXPIRED; -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.*; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.FutureSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.base.Functions.identity; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; -import static java.util.Map.entry; -import static org.mockito.Mockito.*; -import static uk.org.lidalia.slf4jext.Level.WARN; - - -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@SuppressWarnings("PreferJavaTimeOverload") -@Test(dataProviderClass = CacheProvider.class) -public final class ExpirationTest { - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.IMMEDIATELY}, - expireAfterWrite = {Expire.DISABLED, Expire.IMMEDIATELY}, - expiryTime = Expire.IMMEDIATELY, population = Population.EMPTY, - evictionListener = {Listener.CONSUMING, Listener.DISABLED, Listener.REJECTING}) - public void expire_zero(Cache 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).hasSize(1); - assertThat(context).notifications().isEmpty(); - } else { - runVariableExpiration(context); - assertThat(cache).isEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.absentKey(), context.absentValue()) - .exclusively(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, scheduler = CacheScheduler.MOCKITO, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void schedule(Cache cache, CacheContext context) { - var delay = ArgumentCaptor.forClass(long.class); - var task = ArgumentCaptor.forClass(Runnable.class); - doReturn(DisabledFuture.INSTANCE).when(context.scheduler()).schedule( - eq(context.executor()), task.capture(), delay.capture(), eq(TimeUnit.NANOSECONDS)); - - cache.put(context.absentKey(), context.absentValue()); - - long minError = TimeUnit.MINUTES.toNanos(1) - TOLERANCE; - long maxError = TimeUnit.MINUTES.toNanos(1) + TOLERANCE; - assertThat(delay.getValue()).isIn(Range.closed(minError, maxError)); - - context.ticker().advance(delay.getValue()); - task.getValue().run(); - - if (context.expiresVariably()) { - // scheduled a timerWheel cascade, run next schedule - assertThat(delay.getAllValues()).hasSize(2); - context.ticker().advance(delay.getValue()); - task.getValue().run(); - } - - assertThat(cache).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, scheduler = CacheScheduler.MOCKITO, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void schedule_immediate(Cache cache, CacheContext context) { - doAnswer(invocation -> { - invocation.getArgument(1, Runnable.class).run(); - return DisabledFuture.INSTANCE; - }).when(context.scheduler()).schedule(any(), any(), anyLong(), any()); - - cache.put(context.absentKey(), context.absentValue()); - verify(context.scheduler(), atMostOnce()).schedule(any(), any(), anyLong(), any()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, scheduler = CacheScheduler.MOCKITO, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, - removalListener = Listener.MOCKITO) - public void schedule_delay(Cache cache, CacheContext context) { - var actualExpirationPeriods = new HashMap(); - var delay = ArgumentCaptor.forClass(long.class); - var task = ArgumentCaptor.forClass(Runnable.class); - Answer onRemoval = invocation -> { - var key = invocation.getArgument(0, Int.class); - var value = invocation.getArgument(1, Duration.class); - actualExpirationPeriods.put(key, Duration.ofNanos(context.ticker().read()).minus(value)); - return null; - }; - doAnswer(onRemoval).when(context.removalListener()).onRemoval(any(), any(), any()); - when(context.scheduler().schedule(any(), task.capture(), delay.capture(), any())) - .thenReturn(Futures.immediateFuture(null)); - var original = new HashMap(); - - Int key1 = Int.valueOf(1); - var value1 = Duration.ofNanos(context.ticker().read()); - original.put(key1, value1); - cache.put(key1, value1); - - var insertDelay = Duration.ofMillis(10); - context.ticker().advance(insertDelay); - - Int key2 = Int.valueOf(2); - var value2 = Duration.ofNanos(context.ticker().read()); - original.put(key2, value2); - cache.put(key2, value2); - - var expireKey1 = Duration.ofNanos(1 + delay.getValue()).minus(insertDelay); - context.ticker().advance(expireKey1); - task.getValue().run(); - - var expireKey2 = Duration.ofNanos(1 + delay.getValue()); - context.ticker().advance(expireKey2); - task.getValue().run(); - - if (context.expiresVariably()) { - context.ticker().advance(Pacer.TOLERANCE); - task.getValue().run(); - } - - var maxExpirationPeriod = Duration.ofNanos(context.expiryTime().timeNanos() + Pacer.TOLERANCE); - assertThat(actualExpirationPeriods.get(key1)).isAtMost(maxExpirationPeriod); - assertThat(actualExpirationPeriods.get(key2)).isAtMost(maxExpirationPeriod); - assertThat(actualExpirationPeriods).hasSize(original.size()); - } - - /* --------------- Cache --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void get_writeTime(Cache cache, CacheContext context) { - Int key = context.absentKey(); - Int value = context.absentValue(); - - cache.get(key, k -> { - context.ticker().advance(5, TimeUnit.MINUTES); - return value; - }); - assertThat(cache).hasSize(1); - assertThat(cache).containsEntry(key, value); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.MOCKITO}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void put_insert(Cache cache, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - cache.put(context.firstKey(), context.absentValue()); - - runVariableExpiration(context); - assertThat(cache).hasSize(1); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - - if (context.expiryType() == CacheExpiry.MOCKITO) { - verify(context.expiry()).expireAfterCreate(any(), any(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.MOCKITO}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void put_replace(Cache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - - cache.put(context.firstKey(), context.absentValue()); - cache.put(context.absentKey(), context.absentValue()); - context.clearRemovalNotifications(); // Ignore replacement notification - if (context.expiryType() == CacheExpiry.MOCKITO) { - verify(context.expiry()).expireAfterUpdate( - eq(context.firstKey()), any(), anyLong(), anyLong()); - verify(context.expiry()).expireAfterCreate(eq(context.absentKey()), any(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache).containsEntry(context.firstKey(), context.absentValue()); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - assertThat(cache).doesNotContainKey(context.middleKey()); - - cache.cleanUp(); - assertThat(cache).hasSize(2); - - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(expected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void put_weighted(Cache> cache, CacheContext context) { - var value = CacheContext.intern(List.of(context.absentValue())); - cache.put(context.absentKey(), value); - assertThat(context).hasWeightedSize(1); - - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(context).hasWeightedSize(1); - - var newValue = CacheContext.intern(List.copyOf(context.absent().values())); - cache.put(context.absentKey(), newValue); - assertThat(context).hasWeightedSize(context.absent().size()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void putAll_insert(Cache cache, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - cache.putAll(Map.of(context.firstKey(), context.absentValue(), - context.middleKey(), context.absentValue(), context.lastKey(), context.absentValue())); - - assertThat(cache).hasSize(3); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {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 cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - - cache.putAll(Map.of( - context.firstKey(), context.absentValue(), - context.absentKey(), context.absentValue())); - context.clearRemovalNotifications(); // Ignore replacement notification - - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache).containsEntry(context.firstKey(), context.absentValue()); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - assertThat(cache).doesNotContainKey(context.middleKey()); - - cache.cleanUp(); - assertThat(cache).hasSize(2); - - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(context).notifications().withCause(EXPIRED).contains(expected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void invalidate(Cache cache, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - cache.invalidate(context.firstKey()); - - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void invalidateAll_iterable(Cache cache, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - cache.invalidateAll(context.firstMiddleLastKeys()); - - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void invalidateAll_full(Cache cache, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - cache.invalidateAll(); - - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, - expiryTime = Expire.ONE_MINUTE) - public void estimatedSize(Cache cache, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, - expiryTime = Expire.ONE_MINUTE) - public void cleanUp(Cache cache, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - cache.cleanUp(); - - assertThat(cache).isEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - /* --------------- LoadingCache --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, loader = Loader.IDENTITY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void refresh(LoadingCache cache, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - Int key = context.firstKey(); - - assertThat(cache.refresh(key)).succeedsWith(key); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - /* --------------- AsyncCache --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void getIfPresent_inFlight(AsyncCache cache, CacheContext context) { - var future = new CompletableFuture(); - cache.put(context.absentKey(), future); - assertThat(cache.getIfPresent(context.absentKey())).isSameInstanceAs(future); - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.getIfPresent(context.absentKey())).isSameInstanceAs(future); - future.complete(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - removalListener = Listener.CONSUMING, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void get(AsyncCache cache, CacheContext context) { - context.ticker().advance(2, TimeUnit.MINUTES); - - cache.get(context.firstKey(), k -> k).join(); - cache.get(context.middleKey(), k -> context.absentValue()).join(); - cache.get(context.lastKey(), (k, executor) -> context.absentValue().asFuture()).join(); - - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void get_writeTime(AsyncCache cache, CacheContext context) { - Int key = context.absentKey(); - Int value = context.absentValue(); - - cache.get(key, k -> { - context.ticker().advance(5, TimeUnit.MINUTES); - return value; - }).join(); - assertThat(cache).hasSize(1); - assertThat(cache).containsEntry(key, value); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = Listener.CONSUMING, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, expiryTime = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void get_async(AsyncCache cache, CacheContext context) { - var future = cache.get(context.absentKey(), (k, e) -> new CompletableFuture<>()); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.synchronous().cleanUp(); - - assertThat(context).notifications().isEmpty(); - future.complete(context.absentValue()); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(cache).containsEntry(context.absentKey(), future); - - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(cache).doesNotContainKey(context.absentKey()); - - cache.synchronous().cleanUp(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.absentKey(), context.absentValue()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void get_inFlight(AsyncCache cache, CacheContext context) { - var future = new CompletableFuture(); - cache.put(context.absentKey(), future); - assertThat(cache.get(context.absentKey(), k -> k)).isSameInstanceAs(future); - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.get(context.absentKey(), k -> k)).isSameInstanceAs(future); - future.complete(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.SINGLETON, removalListener = Listener.CONSUMING, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) - public void getAll(AsyncCache cache, CacheContext context) { - var keys = context.firstMiddleLastKeys(); - context.ticker().advance(1, TimeUnit.MINUTES); - cache.getAll(context.firstMiddleLastKeys(), - keysToLoad -> Maps.asMap(keysToLoad, identity())).join(); - - var expected = Maps.asMap(keys, identity()); - assertThat(cache.getAll(keys, keysToLoad -> Maps.asMap(keysToLoad, identity())).join()) - .containsExactlyEntriesIn(expected).inOrder(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void put_insert(AsyncCache cache, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - cache.put(context.firstKey(), CacheContext.intern(context.absentValue().asFuture())); - - runVariableExpiration(context); - assertThat(cache).hasSize(1); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, removalListener = Listener.CONSUMING, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void put_insert_async(AsyncCache cache, CacheContext context) { - var future = new CompletableFuture(); - cache.put(context.absentKey(), future); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.synchronous().cleanUp(); - - assertThat(context).notifications().isEmpty(); - future.complete(context.absentValue()); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(cache).containsEntry(context.absentKey(), future); - - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(cache).doesNotContainKey(context.absentKey()); - - cache.synchronous().cleanUp(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.absentKey(), context.absentValue()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {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(AsyncCache cache, CacheContext context) { - var future = context.absentValue().asFuture(); - context.ticker().advance(30, TimeUnit.SECONDS); - - cache.put(context.firstKey(), future); - cache.put(context.absentKey(), future); - context.clearRemovalNotifications(); // Ignore replacement notification - - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache).containsEntry(context.firstKey(), context.absentValue()); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - assertThat(cache).doesNotContainKey(context.middleKey()); - - cache.synchronous().cleanUp(); - assertThat(cache).hasSize(2); - - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(expected).exclusively(); - } - - /* --------------- Map --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void isEmpty(Map map, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(map.isEmpty()).isFalse(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void size(Map map, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(map.size()).isEqualTo(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void containsKey(Map map, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(map.containsKey(context.firstKey())).isFalse(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void containsKey_inFlight(AsyncCache cache, CacheContext context) { - var future = new CompletableFuture(); - cache.put(context.absentKey(), future); - assertThat(cache.asMap().containsKey(context.absentKey())).isTrue(); - assertThat(cache.synchronous().asMap().containsKey(context.absentKey())).isTrue(); - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.asMap().containsKey(context.absentKey())).isTrue(); - assertThat(cache.synchronous().asMap().containsKey(context.absentKey())).isTrue(); - future.complete(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void containsValue(Map map, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(map.containsValue(context.original().get(context.firstKey()))).isFalse(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void containsValue_inFlight(AsyncCache cache, CacheContext context) { - var future = new CompletableFuture(); - cache.put(context.absentKey(), future); - assertThat(cache.asMap().containsValue(future)).isTrue(); - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.asMap().containsValue(future)).isTrue(); - future.complete(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void clear(Map map, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - map.clear(); - - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - maximumSize = Maximum.FULL, weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void putIfAbsent_weighted(Map> map, CacheContext context) { - var value = CacheContext.intern(List.of(context.absentValue())); - map.put(context.absentKey(), value); - context.ticker().advance(1, TimeUnit.MINUTES); - - var newValue = CacheContext.intern(List.copyOf(context.absent().values())); - map.putIfAbsent(context.absentKey(), newValue); - assertThat(context).hasWeightedSize(context.absent().size()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.MOCKITO}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void put_insert(Map map, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(map.put(context.firstKey(), context.absentValue())).isNull(); - - assertThat(map).hasSize(1); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - - if (context.expiryType() == CacheExpiry.MOCKITO) { - verify(context.expiry()).expireAfterCreate(any(), any(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.MOCKITO}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void put_replace(Map map, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - - assertThat(map.put(context.firstKey(), context.absentValue())).isNotNull(); - assertThat(map.put(context.absentKey(), context.absentValue())).isNull(); - context.clearRemovalNotifications(); // Ignore replacement notification - if (context.expiryType() == CacheExpiry.MOCKITO) { - verify(context.expiry()).expireAfterUpdate( - eq(context.firstKey()), any(), anyLong(), anyLong()); - verify(context.expiry()).expireAfterCreate(eq(context.absentKey()), any(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(map).containsEntry(context.firstKey(), context.absentValue()); - assertThat(map).containsEntry(context.absentKey(), context.absentValue()); - assertThat(map).doesNotContainKey(context.middleKey()); - - context.cleanUp(); - assertThat(map).hasSize(2); - - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(expected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void put_inFlight(AsyncCache cache, CacheContext context) { - var f1 = new CompletableFuture(); - var f2 = new CompletableFuture(); - var f3 = new CompletableFuture(); - cache.put(context.absentKey(), f1); - assertThat(cache.asMap().put(context.absentKey(), f2)).isSameInstanceAs(f1); - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.asMap().put(context.absentKey(), f3)).isSameInstanceAs(f2); - f3.complete(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void replace(Map map, CacheContext context) { - context.ticker().advance(60, TimeUnit.SECONDS); - assertThat(map.replace(context.firstKey(), context.absentValue())).isNull(); - - if (!map.isEmpty()) { - context.cleanUp(); - } - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {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 map, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(map.replace(context.firstKey(), context.absentValue())).isNotNull(); - context.ticker().advance(30, TimeUnit.SECONDS); - - context.cleanUp(); - assertThat(map).hasSize(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void replace_inFlight(AsyncCache cache, CacheContext context) { - var f1 = new CompletableFuture(); - var f2 = new CompletableFuture(); - var f3 = new CompletableFuture(); - cache.put(context.absentKey(), f1); - assertThat(cache.asMap().replace(context.absentKey(), f2)).isSameInstanceAs(f1); - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.asMap().replace(context.absentKey(), f3)).isSameInstanceAs(f2); - f3.complete(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void replaceConditionally(Map map, CacheContext context) { - Int key = context.firstKey(); - context.ticker().advance(60, TimeUnit.SECONDS); - assertThat(map.replace(key, context.original().get(key), context.absentValue())).isFalse(); - - if (!map.isEmpty()) { - context.cleanUp(); - } - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {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 map, CacheContext context) { - Int key = context.firstKey(); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(map.replace(key, context.original().get(key), context.absentValue())).isTrue(); - context.ticker().advance(30, TimeUnit.SECONDS); - - context.cleanUp(); - assertThat(map).hasSize(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void replaceConditionally_inFlight(AsyncCache cache, CacheContext context) { - var f1 = new CompletableFuture(); - var f2 = new CompletableFuture(); - var f3 = new CompletableFuture(); - cache.put(context.absentKey(), f1); - assertThat(cache.asMap().replace(context.absentKey(), f1, f2)).isTrue(); - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.asMap().replace(context.absentKey(), f2, f3)).isTrue(); - f3.complete(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void remove(Map map, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(map.remove(context.firstKey())).isNull(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void remove_inFlight(AsyncCache cache, CacheContext context) { - var f1 = new CompletableFuture(); - cache.put(context.absentKey(), f1); - assertThat(cache.asMap().remove(context.absentKey())).isSameInstanceAs(f1); - - var f2 = new CompletableFuture(); - cache.put(context.absentKey(), f2); - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.asMap().remove(context.absentKey())).isSameInstanceAs(f2); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void removeConditionally(Map map, CacheContext context) { - Int key = context.firstKey(); - context.ticker().advance(1, TimeUnit.MINUTES); - - assertThat(map.remove(key, context.original().get(key))).isFalse(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void removeConditionally_inFlight(AsyncCache cache, CacheContext context) { - var f1 = new CompletableFuture(); - cache.put(context.absentKey(), f1); - assertThat(cache.asMap().remove(context.absentKey(), f1)).isTrue(); - - var f2 = new CompletableFuture(); - cache.put(context.absentKey(), f2); - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.asMap().remove(context.absentKey(), f2)).isTrue(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.MOCKITO}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void computeIfAbsent_prescreen(Map map, CacheContext context) { - Int key = context.firstKey(); - context.ticker().advance(1, TimeUnit.MINUTES); - var result = map.computeIfAbsent(key, k -> context.absentValue()); - assertThat(result).isEqualTo(context.absentValue()); - - assertThat(map).hasSize(1); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - - if (context.expiryType() == CacheExpiry.MOCKITO) { - verify(context.expiry()).expireAfterCreate(any(), any(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, maximumSize = Maximum.FULL, - expiryTime = Expire.ONE_MINUTE, mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void computeIfAbsent_expiresInCompute(Map map, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - - assertThat(map.computeIfAbsent(context.firstKey(), k -> null)).isNull(); - assertThat(context.cache()).whenCleanedUp().isEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, maximumSize = Maximum.FULL, - weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void computeIfAbsent_weighted(Map> map, CacheContext context) { - var value = CacheContext.intern(List.of(context.absentValue())); - map.put(context.absentKey(), value); - context.ticker().advance(1, TimeUnit.MINUTES); - - var newValue = CacheContext.intern(List.copyOf(context.absent().values())); - map.computeIfAbsent(context.absentKey(), k -> newValue); - assertThat(context).hasWeightedSize(context.absent().size()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void computeIfAbsent_writeTime(Map map, CacheContext context) { - Int key = context.absentKey(); - Int value = context.absentValue(); - - map.computeIfAbsent(key, k -> { - context.ticker().advance(5, TimeUnit.MINUTES); - return value; - }); - assertThat(map).hasSize(1); - assertThat(map).containsKey(key); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS, CacheExpiry.WRITE}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void computeIfAbsent_error(Cache cache, CacheContext context) { - context.ticker().advance(2, TimeUnit.MINUTES); - try { - cache.asMap().computeIfAbsent(context.firstKey(), - key -> { - throw new IllegalStateException(); - }); - Assert.fail(); - } catch (IllegalStateException expected) { - } - - assertThat(cache.policy().expireAfterAccess() - .flatMap(policy -> policy.ageOf(context.firstKey()))).isEmpty(); - assertThat(cache.policy().expireAfterWrite() - .flatMap(policy -> policy.ageOf(context.firstKey()))).isEmpty(); - assertThat(cache.policy().expireVariably() - .flatMap(policy -> policy.getExpiresAfter(context.firstKey()))).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.MOCKITO}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void computeIfAbsent_inFlight(AsyncCache cache, CacheContext context) { - var f1 = new CompletableFuture(); - cache.put(context.absentKey(), f1); - assertThat(cache.asMap().computeIfAbsent( - context.absentKey(), key -> null)).isSameInstanceAs(f1); - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.asMap().computeIfAbsent( - context.absentKey(), key -> null)).isSameInstanceAs(f1); - f1.complete(null); - - if (context.expiryType() == CacheExpiry.MOCKITO) { - verifyNoInteractions(context.expiry()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.MOCKITO}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void computeIfPresent_prescreen(Map map, CacheContext context) { - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(map.computeIfPresent(context.firstKey(), (k, v) -> { - throw new AssertionError("Should never be called"); - })).isNull(); - - assertThat(map).isExhaustivelyEmpty(); - if (context.isGuava()) { - context.cleanUp(); - } - - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - - if (context.expiryType() == CacheExpiry.MOCKITO) { - verifyNoInteractions(context.expiry()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, maximumSize = Maximum.FULL, - expiryTime = Expire.ONE_MINUTE, mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void computeIfPresent_expiresInCompute(Map map, CacheContext context) { - context.ticker().advance(59, TimeUnit.SECONDS); - context.ticker().setAutoIncrementStep(1, TimeUnit.SECONDS); - - assertThat(map.computeIfPresent(context.firstKey(), (k, v) -> { - throw new AssertionError("Should never be called"); - })).isNull(); - assertThat(context.cache()).whenCleanedUp().isEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void computeIfPresent_writeTime(Map map, CacheContext context) { - Int key = context.firstKey(); - Int value = context.absentValue(); - - map.computeIfPresent(key, (k, v) -> { - context.ticker().advance(5, TimeUnit.MINUTES); - return value; - }); - context.cleanUp(); - assertThat(map).hasSize(1); - assertThat(map).containsKey(key); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, compute = Compute.SYNC, - expiryTime = Expire.ONE_MINUTE, mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS, CacheExpiry.WRITE}) - public void computeIfPresent_error(Cache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - var access = cache.policy().expireAfterAccess() - .flatMap(policy -> policy.ageOf(context.firstKey())); - var write = cache.policy().expireAfterWrite() - .flatMap(policy -> policy.ageOf(context.firstKey())); - var variable = cache.policy().expireVariably() - .flatMap(policy -> policy.getExpiresAfter(context.firstKey())); - try { - cache.asMap().computeIfPresent(context.firstKey(), - (key, value) -> { - throw new IllegalStateException(); - }); - Assert.fail(); - } catch (IllegalStateException expected) { - } - - assertThat(access).isEqualTo(cache.policy().expireAfterAccess() - .flatMap(policy -> policy.ageOf(context.firstKey()))); - assertThat(write).isEqualTo(cache.policy().expireAfterWrite() - .flatMap(policy -> policy.ageOf(context.firstKey()))); - assertThat(variable).isEqualTo(cache.policy().expireVariably() - .flatMap(policy -> policy.getExpiresAfter(context.firstKey()))); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.MOCKITO}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void computeIfPresent_inFlight(AsyncCache cache, CacheContext context) { - var f1 = new CompletableFuture(); - var f2 = new CompletableFuture(); - cache.put(context.absentKey(), f1); - cache.asMap().computeIfPresent(context.absentKey(), (k, f) -> { - assertThat(f).isSameInstanceAs(f1); - return f2; - }); - - var f3 = new CompletableFuture(); - context.ticker().advance(5, TimeUnit.MINUTES); - cache.asMap().computeIfPresent(context.absentKey(), (k, f) -> { - assertThat(f).isSameInstanceAs(f2); - return f3; - }); - f3.complete(null); - - if (context.expiryType() == CacheExpiry.MOCKITO) { - verifyNoInteractions(context.expiry()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.MOCKITO}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void compute(Map map, CacheContext context) { - Int key = context.firstKey(); - Int value = context.absentValue(); - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(map.compute(key, (k, v) -> { - assertThat(v).isNull(); - return value; - })).isEqualTo(value); - - var evicted = new ArrayList>(context.original().size()); - var difference = Maps.difference(context.original(), map); - evicted.addAll(difference.entriesOnlyOnRight().entrySet()); - evicted.addAll(difference.entriesOnlyOnLeft().entrySet()); - evicted.add(entry(key, context.original().get(key))); - - assertThat(evicted).hasSize(context.original().size() - map.size() + 1); - assertThat(context).notifications().withCause(EXPIRED) - .contains(evicted.toArray(Map.Entry[]::new)) - .exclusively(); - - if (context.expiryType() == CacheExpiry.MOCKITO) { - verify(context.expiry()).expireAfterCreate(any(), any(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, maximumSize = Maximum.FULL, - weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void compute_weighted(Map> map, CacheContext context) { - var value = CacheContext.intern(List.of(context.absentValue())); - map.put(context.absentKey(), value); - context.ticker().advance(1, TimeUnit.MINUTES); - - var newValue = CacheContext.intern(List.copyOf(context.absent().values())); - map.compute(context.absentKey(), (k, v) -> newValue); - assertThat(context).hasWeightedSize(context.absent().size()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void compute_writeTime(Map map, CacheContext context) { - Int key = context.firstKey(); - Int value = context.absentValue(); - - map.compute(key, (k, v) -> { - context.ticker().advance(5, TimeUnit.MINUTES); - return value; - }); - context.cleanUp(); - assertThat(map).hasSize(1); - assertThat(map).containsKey(key); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS, CacheExpiry.WRITE}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void compute_absent_error(Cache cache, CacheContext context) { - context.ticker().advance(2, TimeUnit.MINUTES); - try { - cache.asMap().compute(context.firstKey(), - (key, value) -> { - throw new IllegalStateException(); - }); - Assert.fail(); - } catch (IllegalStateException expected) { - } - - assertThat(cache.policy().expireAfterAccess() - .flatMap(policy -> policy.ageOf(context.firstKey()))).isEmpty(); - assertThat(cache.policy().expireAfterWrite() - .flatMap(policy -> policy.ageOf(context.firstKey()))).isEmpty(); - assertThat(cache.policy().expireVariably() - .flatMap(policy -> policy.getExpiresAfter(context.firstKey()))).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, compute = Compute.SYNC, - expiryTime = Expire.ONE_MINUTE, mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS, CacheExpiry.WRITE}) - public void compute_present_error(Cache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - var access = cache.policy().expireAfterAccess() - .flatMap(policy -> policy.ageOf(context.firstKey())); - var write = cache.policy().expireAfterWrite() - .flatMap(policy -> policy.ageOf(context.firstKey())); - var variable = cache.policy().expireVariably() - .flatMap(policy -> policy.getExpiresAfter(context.firstKey())); - try { - cache.asMap().compute(context.firstKey(), - (key, value) -> { - throw new IllegalStateException(); - }); - Assert.fail(); - } catch (IllegalStateException expected) { - } - - assertThat(access).isEqualTo(cache.policy().expireAfterAccess() - .flatMap(policy -> policy.ageOf(context.firstKey()))); - assertThat(write).isEqualTo(cache.policy().expireAfterWrite() - .flatMap(policy -> policy.ageOf(context.firstKey()))); - assertThat(variable).isEqualTo(cache.policy().expireVariably() - .flatMap(policy -> policy.getExpiresAfter(context.firstKey()))); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.MOCKITO}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void compute_inFlight(AsyncCache cache, CacheContext context) { - var f1 = new CompletableFuture(); - var f2 = new CompletableFuture(); - cache.put(context.absentKey(), f1); - cache.asMap().compute(context.absentKey(), (k, f) -> { - assertThat(f).isSameInstanceAs(f1); - return f2; - }); - - var f3 = new CompletableFuture(); - context.ticker().advance(5, TimeUnit.MINUTES); - cache.asMap().compute(context.absentKey(), (k, f) -> { - assertThat(f).isSameInstanceAs(f2); - return f3; - }); - f3.complete(null); - - if (context.expiryType() == CacheExpiry.MOCKITO) { - verifyNoInteractions(context.expiry()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void merge(Map map, CacheContext context) { - Int key = context.firstKey(); - Int value = context.absentValue(); - context.ticker().advance(1, TimeUnit.MINUTES); - assertThat(map.merge(key, value, (oldValue, v) -> { - throw new AssertionError("Should never be called"); - })).isEqualTo(value); - - var evicted = new ArrayList>(context.original().size()); - var difference = Maps.difference(context.original(), map); - evicted.addAll(difference.entriesOnlyOnRight().entrySet()); - evicted.addAll(difference.entriesOnlyOnLeft().entrySet()); - evicted.add(entry(key, context.original().get(key))); - - assertThat(evicted).hasSize(context.original().size() - map.size() + 1); - assertThat(context).notifications().withCause(EXPIRED) - .contains(evicted.toArray(Map.Entry[]::new)) - .exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, maximumSize = Maximum.FULL, - weigher = CacheWeigher.COLLECTION, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void merge_weighted(Cache> cache, CacheContext context) { - var value = CacheContext.intern(List.of(context.absentValue())); - cache.put(context.absentKey(), value); - context.ticker().advance(1, TimeUnit.MINUTES); - - var newValue = CacheContext.intern(List.copyOf(context.absent().values())); - cache.asMap().merge(context.absentKey(), newValue, - (oldValue, v) -> { - throw new AssertionError("Should never be called"); - }); - assertThat(context).hasWeightedSize(context.absent().size()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void merge_writeTime(Map map, CacheContext context) { - Int key = context.firstKey(); - Int value = context.absentValue(); - - map.merge(key, value, (oldValue, v) -> { - context.ticker().advance(5, TimeUnit.MINUTES); - return value; - }); - context.cleanUp(); - assertThat(map).hasSize(1); - assertThat(map).containsKey(key); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) - public void keySet_toArray(Map map, CacheContext context) { - context.ticker().advance(2 * context.expiryTime().timeNanos(), TimeUnit.NANOSECONDS); - assertThat(map.keySet().toArray(new Int[0])).isEmpty(); - assertThat(map.keySet().toArray(Int[]::new)).isEmpty(); - assertThat(map.keySet().toArray()).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) - public void keySet_iterator(Map map, CacheContext context) { - context.ticker().advance(10, TimeUnit.MINUTES); - assertThat(map.keySet().iterator().hasNext()).isFalse(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) - public void keySet_iterator_traversal(Map map, CacheContext context) { - var iterator = map.keySet().iterator(); - assertThat(iterator.next()).isNotNull(); - assertThat(iterator.hasNext()).isTrue(); - - context.ticker().advance(10, TimeUnit.MINUTES); - assertThat(iterator.hasNext()).isTrue(); - assertThat(iterator.next()).isNotNull(); - assertThat(iterator.hasNext()).isFalse(); - - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void keySet_inFlight(AsyncCache cache, CacheContext context) { - var future = new CompletableFuture(); - cache.put(context.absentKey(), future); - assertThat(cache.asMap().containsKey(context.absentKey())).isTrue(); - - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.asMap().containsKey(context.absentKey())).isTrue(); - future.complete(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) - public void values_toArray(Map map, CacheContext context) { - context.ticker().advance(2 * context.expiryTime().timeNanos(), TimeUnit.NANOSECONDS); - assertThat(map.values().toArray(new Int[0])).isEmpty(); - assertThat(map.values().toArray(Int[]::new)).isEmpty(); - assertThat(map.values().toArray()).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, expiryTime = Expire.ONE_MINUTE, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void values_iterator(Map map, CacheContext context) { - context.ticker().advance(10, TimeUnit.MINUTES); - assertThat(map.values().iterator().hasNext()).isFalse(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) - public void values_iterator_traversal(Map map, CacheContext context) { - var iterator = map.values().iterator(); - assertThat(iterator.next()).isNotNull(); - assertThat(iterator.hasNext()).isTrue(); - - context.ticker().advance(10, TimeUnit.MINUTES); - assertThat(iterator.hasNext()).isTrue(); - assertThat(iterator.next()).isNotNull(); - assertThat(iterator.hasNext()).isFalse(); - - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void values_inFlight(AsyncCache cache, CacheContext context) { - var future = new CompletableFuture(); - cache.put(context.absentKey(), future); - assertThat(cache.asMap().containsValue(future)).isTrue(); - - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.asMap().containsValue(future)).isTrue(); - future.complete(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) - public void entrySet_toArray(Map map, CacheContext context) { - context.ticker().advance(2 * context.expiryTime().timeNanos(), TimeUnit.NANOSECONDS); - assertThat(map.entrySet().toArray(new Map.Entry[0])).isEmpty(); - assertThat(map.entrySet().toArray(Map.Entry[]::new)).isEmpty(); - assertThat(map.entrySet().toArray()).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) - public void entrySet_iterator(Map map, CacheContext context) { - context.ticker().advance(10, TimeUnit.MINUTES); - assertThat(map.keySet().iterator().hasNext()).isFalse(); - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, expiryTime = Expire.ONE_MINUTE) - public void entrySet_iterator_traversal(Map map, CacheContext context) { - var iterator = map.entrySet().iterator(); - assertThat(iterator.next()).isNotNull(); - assertThat(iterator.hasNext()).isTrue(); - - context.ticker().advance(10, TimeUnit.MINUTES); - assertThat(iterator.hasNext()).isTrue(); - assertThat(iterator.next()).isNotNull(); - assertThat(iterator.hasNext()).isFalse(); - - assertThat(map).isExhaustivelyEmpty(); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void entrySet_inFlight(AsyncCache cache, CacheContext context) { - var future = new CompletableFuture(); - cache.put(context.absentKey(), future); - assertThat(cache.asMap().entrySet().contains(Map.entry(context.absentKey(), future))).isTrue(); - - context.ticker().advance(5, TimeUnit.MINUTES); - assertThat(cache.asMap().entrySet().contains(Map.entry(context.absentKey(), future))).isTrue(); - future.complete(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void entrySet_equals(Map map, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - map.putAll(context.absent()); - - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(map.entrySet().equals(context.absent().entrySet())).isFalse(); - assertThat(context.absent().entrySet().equals(map.entrySet())).isFalse(); - - context.cleanUp(); - assertThat(map.entrySet().equals(context.absent().entrySet())).isTrue(); - assertThat(context.absent().entrySet().equals(map.entrySet())).isTrue(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void entrySet_hashCode(Map map, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - map.putAll(context.absent()); - - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(map.hashCode()).isEqualTo(context.absent().hashCode()); - - context.cleanUp(); - assertThat(map.hashCode()).isEqualTo(context.absent().hashCode()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void equals(Map map, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - map.putAll(context.absent()); - - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(map.equals(context.absent())).isFalse(); - assertThat(context.absent().equals(map)).isFalse(); - - context.cleanUp(); - assertThat(map.equals(context.absent())).isTrue(); - assertThat(context.absent().equals(map)).isTrue(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void hashCode(Map map, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - map.putAll(context.absent()); - - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(map.hashCode()).isEqualTo(context.absent().hashCode()); - - context.cleanUp(); - assertThat(map.hashCode()).isEqualTo(context.absent().hashCode()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void toString(Map map, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - map.putAll(context.absent()); - - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(parseToString(map)).containsExactlyEntriesIn(parseToString(context.absent())); - - context.cleanUp(); - assertThat(parseToString(map)).containsExactlyEntriesIn(parseToString(context.absent())); - } - - private static Map parseToString(Map map) { - return Splitter.on(',').trimResults().omitEmptyStrings().withKeyValueSeparator("=") - .split(map.toString().replaceAll("\\{|\\}", "")); - } - - /* --------------- Policy --------------- */ - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - expiryTime = Expire.ONE_MINUTE, mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void getIfPresentQuietly_expired(Cache cache, CacheContext context) { - assertThat(cache.policy().getIfPresentQuietly(context.firstKey())).isNotNull(); - context.ticker().advance(10, TimeUnit.MINUTES); - assertThat(cache.policy().getIfPresentQuietly(context.firstKey())).isNull(); - } - - /** - * 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(); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java deleted file mode 100644 index 1537855..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterAccessTest.java +++ /dev/null @@ -1,644 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.Policy.FixedExpiration; -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; -import org.testng.Assert; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -import static com.github.benmanes.caffeine.cache.RemovalCause.EXPIRED; -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.AFTER_ACCESS; -import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.VARIABLE; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.base.Functions.identity; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; -import static uk.org.lidalia.slf4jext.Level.WARN; - - -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@SuppressWarnings("PreferJavaTimeOverload") -@Test(dataProviderClass = CacheProvider.class) -public final class ExpireAfterAccessTest { - - /* --------------- Cache --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = {AFTER_ACCESS, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS}, expiryTime = Expire.ONE_MINUTE, - expireAfterAccess = Expire.ONE_MINUTE, population = {Population.PARTIAL, Population.FULL}) - public void getIfPresent(Cache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.getIfPresent(context.firstKey()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.getIfPresent(context.firstKey())).isEqualTo(context.firstKey().negate()); - assertThat(cache.getIfPresent(context.lastKey())).isNull(); - - cache.cleanUp(); - assertThat(cache).hasSize(1); - - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(expected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = {AFTER_ACCESS, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS}, expiryTime = Expire.ONE_MINUTE, - expireAfterAccess = Expire.ONE_MINUTE, population = {Population.PARTIAL, Population.FULL}) - public void get(Cache cache, CacheContext context) { - Function mappingFunction = context.original()::get; - context.ticker().advance(30, TimeUnit.SECONDS); - cache.get(context.firstKey(), mappingFunction); - context.ticker().advance(45, TimeUnit.SECONDS); - cache.get(context.firstKey(), mappingFunction); - cache.get(context.lastKey(), mappingFunction); // recreated - - cache.cleanUp(); - assertThat(cache).hasSize(2); - - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(expected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = {AFTER_ACCESS, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS}, expiryTime = Expire.ONE_MINUTE, - expireAfterAccess = Expire.ONE_MINUTE, population = {Population.PARTIAL, Population.FULL}) - public void getAllPresent(Cache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.getAllPresent(context.firstMiddleLastKeys()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.getAllPresent(context.firstMiddleLastKeys())).hasSize(3); - - cache.cleanUp(); - assertThat(cache).hasSize(3); - - var expected = new HashMap<>(context.original()); - expected.keySet().removeAll(context.firstMiddleLastKeys()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(expected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = {AFTER_ACCESS, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS}, expiryTime = Expire.ONE_MINUTE, - expireAfterAccess = Expire.ONE_MINUTE, population = {Population.PARTIAL, Population.FULL}) - public void getAll(Cache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - var result1 = cache.getAll(List.of(context.firstKey(), context.middleKey()), - keys -> Maps.asMap(keys, identity())); - assertThat(result1).containsExactly(context.firstKey(), context.firstKey().negate(), - context.middleKey(), context.middleKey().negate()); - - context.ticker().advance(45, TimeUnit.SECONDS); - cache.cleanUp(); - var result2 = cache.getAll(List.of(context.firstKey(), context.absentKey()), - keys -> Maps.asMap(keys, identity())); - assertThat(result2).containsExactly(context.firstKey(), context.firstKey().negate(), - context.absentKey(), context.absentKey()); - - context.ticker().advance(45, TimeUnit.SECONDS); - cache.cleanUp(); - var result3 = cache.getAll(List.of(context.middleKey(), context.absentKey()), - keys -> Maps.asMap(keys, identity())); - assertThat(result3).containsExactly(context.middleKey(), context.middleKey(), - context.absentKey(), context.absentKey()); - assertThat(cache).hasSize(3); - - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(expected).exclusively(); - } - - /* --------------- LoadingCache --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = {AFTER_ACCESS, VARIABLE}, expiryTime = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS}, expireAfterAccess = Expire.ONE_MINUTE, - loader = Loader.IDENTITY, population = {Population.PARTIAL, Population.FULL}) - public void get_loading(LoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.get(context.firstKey()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.get(context.lastKey())).isEqualTo(context.lastKey()); - cache.cleanUp(); - assertThat(cache).hasSize(2); - - context.ticker().advance(45, TimeUnit.SECONDS); - cache.cleanUp(); - - assertThat(cache).hasSize(1); - assertThat(context).notifications().withCause(EXPIRED) - .contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = {AFTER_ACCESS, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS}, expiryTime = Expire.ONE_MINUTE, - expireAfterAccess = Expire.ONE_MINUTE, loader = {Loader.IDENTITY, Loader.BULK_IDENTITY}, - population = {Population.PARTIAL, Population.FULL}) - public void getAll_loading(LoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(cache.getAll(List.of(context.firstKey(), context.middleKey()))) - .containsExactly(context.firstKey(), context.firstKey().negate(), - context.middleKey(), context.middleKey().negate()); - - context.ticker().advance(45, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache.getAll(List.of(context.firstKey(), context.absentKey()))) - .containsExactly(context.firstKey(), context.firstKey().negate(), - context.absentKey(), context.absentKey()); - - context.ticker().advance(45, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache.getAll(List.of(context.middleKey(), context.absentKey()))) - .containsExactly(context.middleKey(), context.middleKey(), - context.absentKey(), context.absentKey()); - assertThat(cache).hasSize(3); - - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(expected).exclusively(); - } - - /* --------------- AsyncCache --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = {AFTER_ACCESS, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS}, expiryTime = Expire.ONE_MINUTE, - expireAfterAccess = Expire.ONE_MINUTE, population = {Population.PARTIAL, Population.FULL}) - public void getIfPresent_async(AsyncCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.getIfPresent(context.firstKey()).join(); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache).containsKey(context.firstKey()); - assertThat(cache).doesNotContainKey(context.lastKey()); - - context.cleanUp(); - assertThat(cache).hasSize(1); - - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(expected).exclusively(); - } - - /* --------------- Map --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = {AFTER_ACCESS, VARIABLE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.ACCESS}, expiryTime = Expire.ONE_MINUTE, - expireAfterAccess = Expire.ONE_MINUTE, population = Population.FULL) - public void putIfAbsent(Map map, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(map.putIfAbsent(context.firstKey(), context.absentValue())).isNotNull(); - - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(map.putIfAbsent(context.lastKey(), context.absentValue())).isNull(); - - assertThat(map).hasSize(2); - var expected = new HashMap<>(context.original()); - expected.remove(context.firstKey()); - assertThat(context).notifications().withCause(EXPIRED) - .contains(expected).exclusively(); - } - - /* --------------- Policy --------------- */ - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expireAfterAccess = Expire.ONE_MINUTE) - public void getIfPresentQuietly(Cache cache, CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var original = expireAfterAccess.ageOf(context.firstKey()).orElseThrow(); - var advancement = Duration.ofSeconds(30); - context.ticker().advance(advancement); - cache.policy().getIfPresentQuietly(context.firstKey()); - var current = cache.policy().expireAfterAccess() - .flatMap(policy -> policy.ageOf(context.firstKey())).orElseThrow(); - assertThat(current.minus(advancement)).isEqualTo(original); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void getExpiresAfter(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - assertThat(expireAfterAccess.getExpiresAfter(TimeUnit.MINUTES)).isEqualTo(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void getExpiresAfter_duration(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - assertThat(expireAfterAccess.getExpiresAfter()).isEqualTo(Duration.ofMinutes(1)); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void setExpiresAfter_negative(Cache cache, CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.setExpiresAfter(Duration.ofMinutes(-2)); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void setExpiresAfter_excessive(Cache cache, CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.setExpiresAfter(ChronoUnit.FOREVER.getDuration()); - assertThat(expireAfterAccess.getExpiresAfter(TimeUnit.NANOSECONDS)).isEqualTo(Long.MAX_VALUE); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void setExpiresAfter(Cache cache, CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.setExpiresAfter(2, TimeUnit.MINUTES); - assertThat(expireAfterAccess.getExpiresAfter(TimeUnit.MINUTES)).isEqualTo(2); - - context.ticker().advance(90, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void setExpiresAfter_duration(Cache cache, CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.setExpiresAfter(Duration.ofMinutes(2)); - assertThat(expireAfterAccess.getExpiresAfter()).isEqualTo(Duration.ofMinutes(2)); - - context.ticker().advance(90, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - expireAfterAccess = Expire.ONE_MINUTE) - public void ageOf(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - assertThat(expireAfterAccess.ageOf(context.firstKey(), TimeUnit.SECONDS)).hasValue(0); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(expireAfterAccess.ageOf(context.firstKey(), TimeUnit.SECONDS)).hasValue(30); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(expireAfterAccess.ageOf(context.firstKey(), TimeUnit.SECONDS)).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - expireAfterAccess = Expire.ONE_MINUTE) - public void ageOf_duration(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - assertThat(expireAfterAccess.ageOf(context.firstKey())).hasValue(Duration.ZERO); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(expireAfterAccess.ageOf(context.firstKey())).hasValue(Duration.ofSeconds(30)); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(expireAfterAccess.ageOf(context.firstKey())).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void ageOf_absent(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - assertThat(expireAfterAccess.ageOf(context.absentKey())).isEmpty(); - assertThat(expireAfterAccess.ageOf(context.absentKey(), TimeUnit.SECONDS)).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expireAfterAccess = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}) - public void ageOf_expired(Cache cache, CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - cache.put(context.absentKey(), context.absentValue()); - context.ticker().advance(2, TimeUnit.MINUTES); - assertThat(expireAfterAccess.ageOf(context.absentKey(), TimeUnit.SECONDS)).isEmpty(); - } - - /* --------------- Policy: oldest --------------- */ - - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void oldest_unmodifiable(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.oldest(Integer.MAX_VALUE).clear(); - } - - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void oldest_negative(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.oldest(-1); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void oldest_zero(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - assertThat(expireAfterAccess.oldest(0)).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expireAfterAccess = Expire.ONE_MINUTE) - public void oldest_partial(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - int count = (int) context.initialSize() / 2; - assertThat(expireAfterAccess.oldest(count)).hasSize(count); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}, - expireAfterAccess = Expire.ONE_MINUTE) - public void oldest_order(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var oldest = expireAfterAccess.oldest(Integer.MAX_VALUE); - assertThat(oldest.keySet()).containsExactlyElementsIn(context.original().keySet()).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void oldest_snapshot(Cache cache, CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var oldest = expireAfterAccess.oldest(Integer.MAX_VALUE); - cache.invalidateAll(); - assertThat(oldest).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void oldestFunc_null(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.oldest(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void oldestFunc_nullResult(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var result = expireAfterAccess.oldest(stream -> null); - assertThat(result).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void oldestFunc_throwsException(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var expected = new IllegalStateException(); - try { - expireAfterAccess.oldest(stream -> { - throw expected; - }); - Assert.fail(); - } catch (IllegalStateException e) { - assertThat(e).isSameInstanceAs(expected); - } - } - - @Test(dataProvider = "caches", expectedExceptions = ConcurrentModificationException.class) - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void oldestFunc_concurrentModification(Cache cache, - CacheContext context, @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.oldest(stream -> { - cache.put(context.absentKey(), context.absentValue()); - return stream.count(); - }); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class, - expectedExceptionsMessageRegExp = "source already consumed or closed") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void oldestFunc_closed(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.oldest(stream -> stream).forEach(e -> { - }); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void oldestFunc_partial(Cache cache, - CacheContext context, @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var result = expireAfterAccess.oldest(stream -> stream - .limit(context.initialSize() / 2) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache.asMap()).containsAtLeastEntriesIn(result); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void oldestFunc_full(Cache cache, - CacheContext context, @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var result = expireAfterAccess.oldest(stream -> stream - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache).containsExactlyEntriesIn(result); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}, - expireAfterAccess = Expire.ONE_MINUTE) - public void oldestFunc_order(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var oldest = expireAfterAccess.oldest(stream -> - stream.map(Map.Entry::getKey).collect(toImmutableList())); - assertThat(oldest).containsExactlyElementsIn(context.original().keySet()).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = AFTER_ACCESS, population = {Population.PARTIAL, Population.FULL}) - public void oldestFunc_metadata(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var entries = expireAfterAccess.oldest(stream -> stream.collect(toImmutableList())); - for (var entry : entries) { - assertThat(context).containsEntry(entry); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expireAfterAccess = Expire.ONE_MINUTE) - public void oldestFunc_metadata_expiresInTraversal(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - context.ticker().setAutoIncrementStep(30, TimeUnit.SECONDS); - var entries = expireAfterAccess.oldest(stream -> stream.collect(toImmutableList())); - assertThat(context.cache()).hasSize(context.initialSize()); - assertThat(entries).hasSize(1); - } - - /* --------------- Policy: youngest --------------- */ - - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void youngest_unmodifiable(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.youngest(Integer.MAX_VALUE).clear(); - } - - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void youngest_negative(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.youngest(-1); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void youngest_zero(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - assertThat(expireAfterAccess.youngest(0)).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expireAfterAccess = Expire.ONE_MINUTE) - public void youngest_partial(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - int count = context.original().size() / 2; - assertThat(expireAfterAccess.youngest(count)).hasSize(count); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}, - expireAfterAccess = Expire.ONE_MINUTE) - public void youngest_order(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var youngest = expireAfterAccess.youngest(Integer.MAX_VALUE); - var expected = ImmutableList.copyOf(context.original().keySet()).reverse(); - assertThat(youngest.keySet()).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void youngest_snapshot(Cache cache, CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var youngest = expireAfterAccess.youngest(Integer.MAX_VALUE); - cache.invalidateAll(); - assertThat(youngest).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void youngestFunc_null(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.youngest(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void youngestFunc_nullResult(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var result = expireAfterAccess.youngest(stream -> null); - assertThat(result).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void youngestFunc_throwsException(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var expected = new IllegalStateException(); - try { - expireAfterAccess.youngest(stream -> { - throw expected; - }); - Assert.fail(); - } catch (IllegalStateException e) { - assertThat(e).isSameInstanceAs(expected); - } - } - - @Test(dataProvider = "caches", expectedExceptions = ConcurrentModificationException.class) - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void youngestFunc_concurrentModification(Cache cache, - CacheContext context, @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.youngest(stream -> { - cache.put(context.absentKey(), context.absentValue()); - return stream.count(); - }); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class, - expectedExceptionsMessageRegExp = "source already consumed or closed") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void youngestFunc_closed(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - expireAfterAccess.youngest(stream -> stream).forEach(e -> { - }); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void youngestFunc_partial(Cache cache, - CacheContext context, @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var result = expireAfterAccess.youngest(stream -> stream - .limit(context.initialSize() / 2) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache.asMap()).containsAtLeastEntriesIn(result); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterAccess = Expire.ONE_MINUTE) - public void youngestFunc_full(Cache cache, - CacheContext context, @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var result = expireAfterAccess.youngest(stream -> stream - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache).containsExactlyEntriesIn(result); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}, - expireAfterAccess = Expire.ONE_MINUTE) - public void youngestFunc_order(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var youngest = expireAfterAccess.youngest( - stream -> stream.map(Map.Entry::getKey).collect(toImmutableList())); - var expected = ImmutableList.copyOf(context.original().keySet()).reverse(); - assertThat(youngest).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = AFTER_ACCESS, population = {Population.PARTIAL, Population.FULL}) - public void youngestFunc_metadata(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - var entries = expireAfterAccess.youngest(stream -> stream.collect(toImmutableList())); - for (var entry : entries) { - assertThat(context).containsEntry(entry); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expireAfterAccess = Expire.ONE_MINUTE) - public void youngestFunc_metadata_expiresInTraversal(CacheContext context, - @ExpireAfterAccess FixedExpiration expireAfterAccess) { - context.ticker().setAutoIncrementStep(30, TimeUnit.SECONDS); - var entries = expireAfterAccess.youngest(stream -> stream.collect(toImmutableList())); - assertThat(context.cache()).hasSize(context.initialSize()); - assertThat(entries).hasSize(1); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java deleted file mode 100644 index 45181b8..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java +++ /dev/null @@ -1,1388 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.Policy.VarExpiration; -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.collect.ImmutableList; -import org.mockito.Mockito; -import org.testng.Assert; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiFunction; - -import static com.github.benmanes.caffeine.cache.BoundedLocalCache.MAXIMUM_EXPIRY; -import static com.github.benmanes.caffeine.cache.RemovalCause.*; -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContext.intern; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.*; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.IntSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; -import static java.util.function.Function.identity; -import static org.mockito.Mockito.*; -import static uk.org.lidalia.slf4jext.Level.WARN; - -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@SuppressWarnings("PreferJavaTimeOverload") -@Test(dataProviderClass = CacheProvider.class) -public final class ExpireAfterVarTest { - - @Test(dataProvider = "caches") - @CacheSpec(expiryTime = Expire.FOREVER, - expiry = {CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}) - public void expiry_bounds(Cache cache, CacheContext context) { - context.ticker().advance(System.nanoTime()); - var running = new AtomicBoolean(); - var done = new AtomicBoolean(); - Int key = context.absentKey(); - cache.put(key, key); - - try { - ConcurrentTestHarness.execute(() -> { - while (!done.get()) { - context.ticker().advance(1, TimeUnit.MINUTES); - cache.get(key, Int::new); - running.set(true); - } - }); - await().untilTrue(running); - cache.cleanUp(); - - assertThat(cache.get(key, Int::new)).isSameInstanceAs(key); - } finally { - done.set(true); - } - } - - /* --------------- Get --------------- */ - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void getIfPresent(Cache cache, CacheContext context) { - cache.getIfPresent(context.firstKey()); - - verify(context.expiry()).expireAfterRead(any(), any(), anyLong(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void get(LoadingCache cache, CacheContext context) { - cache.get(context.firstKey()); - verify(context.expiry()).expireAfterRead(any(), any(), anyLong(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void getAll_present(LoadingCache cache, CacheContext context) { - cache.getAll(context.firstMiddleLastKeys()); - verify(context.expiry(), times(3)).expireAfterRead(any(), any(), anyLong(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiry = CacheExpiry.MOCKITO, - loader = {Loader.IDENTITY, Loader.BULK_IDENTITY}) - public void getAll_absent(LoadingCache cache, CacheContext context) { - cache.getAll(context.absentKeys()); - verify(context.expiry(), times(context.absent().size())) - .expireAfterCreate(any(), any(), anyLong()); - if (context.isAsync() && !context.loader().isBulk()) { - verify(context.expiry(), times(context.absent().size())).expireAfterUpdate(any(), any(), anyLong(), anyLong()); - } - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) - public void put_replace(Cache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.put(context.firstKey(), context.absentValue()); - cache.put(context.absentKey(), context.absentValue()); - context.clearRemovalNotifications(); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache).doesNotContainKey(context.firstKey()); - assertThat(cache).doesNotContainKey(context.middleKey()); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - context.cleanUp(); - assertThat(cache).hasSize(1); - var expected = new HashMap<>(context.original()); - expected.put(context.firstKey(), context.absentValue()); - assertThat(context).notifications().withCause(EXPIRED).contains(expected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) - public void put_replace(AsyncCache cache, CacheContext context) { - var future = context.absentValue().asFuture(); - context.ticker().advance(30, TimeUnit.SECONDS); - cache.put(context.firstKey(), future); - cache.put(context.absentKey(), future); - context.clearRemovalNotifications(); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache).doesNotContainKey(context.firstKey()); - assertThat(cache).doesNotContainKey(context.middleKey()); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - context.cleanUp(); - assertThat(cache).hasSize(1); - var expected = new HashMap<>(context.original()); - expected.put(context.firstKey(), context.absentValue()); - assertThat(context).notifications().withCause(EXPIRED).contains(expected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) - public void put_replace(Map map, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(map.put(context.firstKey(), context.absentValue())).isNotNull(); - assertThat(map.put(context.absentKey(), context.absentValue())).isNull(); - context.clearRemovalNotifications(); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(map).doesNotContainKey(context.firstKey()); - assertThat(map).doesNotContainKey(context.middleKey()); - assertThat(map).containsEntry(context.absentKey(), context.absentValue()); - context.cleanUp(); - assertThat(map).hasSize(1); - var expected = new HashMap<>(context.original()); - expected.put(context.firstKey(), context.absentValue()); - assertThat(context).notifications().withCause(EXPIRED).contains(expected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) - public void putAll_replace(Cache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.putAll(Map.of(context.firstKey(), context.absentValue(), context.absentKey(), context.absentValue())); - context.clearRemovalNotifications(); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache).doesNotContainKey(context.firstKey()); - assertThat(cache).doesNotContainKey(context.middleKey()); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - context.cleanUp(); - assertThat(cache).hasSize(1); - var expected = new HashMap<>(context.original()); - expected.put(context.firstKey(), context.absentValue()); - assertThat(context).notifications().withCause(EXPIRED).contains(expected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) - public void replace_updated(Map map, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(map.replace(context.firstKey(), context.absentValue())).isNotNull(); - context.ticker().advance(30, TimeUnit.SECONDS); - context.cleanUp(); - assertThat(map).isExhaustivelyEmpty(); - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void replace_expiryFails(Map map, CacheContext context) { - try { - when(context.expiry().expireAfterUpdate(any(), any(), anyLong(), anyLong())).thenThrow(ExpirationException.class); - map.replace(context.firstKey(), context.absentValue()); - } finally { - assertThat(map).containsExactlyEntriesIn(context.original()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) - public void replaceConditionally_updated(Map map, CacheContext context) { - Int key = context.firstKey(); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(map.replace(key, context.original().get(key), context.absentValue())).isTrue(); - context.ticker().advance(30, TimeUnit.SECONDS); - context.cleanUp(); - assertThat(map).isExhaustivelyEmpty(); - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void replaceConditionally_expiryFails(Map map, CacheContext context) { - try { - when(context.expiry().expireAfterUpdate(any(), any(), anyLong(), anyLong())).thenThrow(ExpirationException.class); - Int oldValue = context.original().get(context.firstKey()); - map.replace(context.firstKey(), oldValue, context.absentValue()); - } finally { - assertThat(map).containsExactlyEntriesIn(context.original()); - } - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void getIfPresent_expiryFails(Cache cache, CacheContext context) { - try { - context.ticker().advance(1, TimeUnit.HOURS); - when(context.expiry().expireAfterRead(any(), any(), anyLong(), anyLong())).thenThrow(ExpirationException.class); - cache.getIfPresent(context.firstKey()); - } finally { - context.ticker().advance(-1, TimeUnit.HOURS); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void get_expiryFails_create(Cache cache, CacheContext context) { - try { - context.ticker().advance(1, TimeUnit.HOURS); - when(context.expiry().expireAfterCreate(any(), any(), anyLong())).thenThrow(ExpirationException.class); - cache.get(context.absentKey(), identity()); - } finally { - context.ticker().advance(-1, TimeUnit.HOURS); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void get_expiryFails_read(Cache cache, CacheContext context) { - try { - context.ticker().advance(1, TimeUnit.HOURS); - when(context.expiry().expireAfterRead(any(), any(), anyLong(), anyLong())).thenThrow(ExpirationException.class); - cache.get(context.firstKey(), identity()); - } finally { - context.ticker().advance(-1, TimeUnit.HOURS); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void getAllPresent_expiryFails(Cache cache, CacheContext context) { - try { - context.ticker().advance(1, TimeUnit.HOURS); - when(context.expiry().expireAfterRead(any(), any(), anyLong(), anyLong())).thenThrow(ExpirationException.class); - cache.getAllPresent(context.firstMiddleLastKeys()); - } finally { - context.ticker().advance(-1, TimeUnit.HOURS); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void put_insert_expiryFails(Cache cache, CacheContext context) { - try { - context.ticker().advance(1, TimeUnit.HOURS); - when(context.expiry().expireAfterCreate(any(), any(), anyLong())).thenThrow(ExpirationException.class); - cache.put(context.absentKey(), context.absentValue()); - } finally { - context.ticker().advance(-1, TimeUnit.HOURS); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - } - - @CacheSpec(population = Population.FULL, - expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void put_insert_replaceExpired_expiryFails(Cache cache, CacheContext context, VarExpiration expireVariably) { - var expectedDuration = expireVariably.getExpiresAfter(context.firstKey(), TimeUnit.NANOSECONDS); - try { - context.ticker().advance(1, TimeUnit.HOURS); - when(context.expiry().expireAfterCreate(any(), any(), anyLong())).thenThrow(ExpirationException.class); - cache.put(context.firstKey(), context.absentValue()); - } finally { - context.ticker().advance(-1, TimeUnit.HOURS); - assertThat(cache).containsExactlyEntriesIn(context.original()); - var currentDuration = expireVariably.getExpiresAfter(context.firstKey(), TimeUnit.NANOSECONDS); - assertThat(currentDuration).isEqualTo(expectedDuration); - } - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void put_update_expiryFails(Cache cache, CacheContext context, VarExpiration expireVariably) { - var expectedDuration = expireVariably.getExpiresAfter(context.firstKey(), TimeUnit.NANOSECONDS); - try { - context.ticker().advance(1, TimeUnit.HOURS); - when(context.expiry().expireAfterUpdate(any(), any(), anyLong(), anyLong())) - .thenThrow(ExpirationException.class); - cache.put(context.firstKey(), context.absentValue()); - } finally { - context.ticker().advance(-1, TimeUnit.HOURS); - assertThat(cache).containsExactlyEntriesIn(context.original()); - var currentDuration = expireVariably.getExpiresAfter(context.firstKey(), TimeUnit.NANOSECONDS); - assertThat(currentDuration).isEqualTo(expectedDuration); - } - } - - static final class ExpirationException extends RuntimeException { - private static final long serialVersionUID = 1L; - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void computeIfAbsent_absent(Map map, CacheContext context) { - map.computeIfAbsent(context.absentKey(), identity()); - verify(context.expiry()).expireAfterCreate(any(), any(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void computeIfAbsent_nullValue(Map map, CacheContext context) { - map.computeIfAbsent(context.absentKey(), key -> null); - verifyNoInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void computeIfAbsent_present(Map map, CacheContext context) { - map.computeIfAbsent(context.firstKey(), identity()); - verify(context.expiry()).expireAfterRead(any(), any(), anyLong(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void computeIfAbsent_expiryFails(Map map, CacheContext context) { - try { - when(context.expiry().expireAfterCreate(any(), any(), anyLong())).thenThrow(ExpirationException.class); - map.computeIfAbsent(context.absentKey(), identity()); - } finally { - assertThat(map).containsExactlyEntriesIn(context.original()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void computeIfPresent_absent(Map map, CacheContext context) { - map.computeIfPresent(context.absentKey(), (key, value) -> value); - verifyNoInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void computeIfPresent_nullValue(Map map, CacheContext context) { - map.computeIfPresent(context.firstKey(), (key, value) -> null); - verifyNoInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void computeIfPresent_present_differentValue(Map map, CacheContext context) { - map.computeIfPresent(context.firstKey(), (key, value) -> context.absentValue()); - verify(context.expiry()).expireAfterUpdate(any(), any(), anyLong(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void computeIfPresent_present_sameInstance(Map map, CacheContext context) { - map.computeIfPresent(context.firstKey(), (key, value) -> value); - verify(context.expiry()).expireAfterUpdate(any(), any(), anyLong(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void computeIfPresent_expiryFails(Map map, CacheContext context) { - try { - when(context.expiry().expireAfterUpdate(any(), any(), anyLong(), anyLong())).thenThrow(ExpirationException.class); - map.computeIfPresent(context.firstKey(), (k, v) -> v.negate()); - } finally { - assertThat(map).containsExactlyEntriesIn(context.original()); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void compute_absent(Map map, CacheContext context) { - map.compute(context.absentKey(), (key, value) -> context.absentValue()); - verify(context.expiry()).expireAfterCreate(any(), any(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void compute_nullValue(Map map, CacheContext context) { - map.compute(context.absentKey(), (key, value) -> null); - verifyNoInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void compute_present_differentValue(Map map, CacheContext context) { - map.compute(context.firstKey(), (key, value) -> context.absentValue()); - verify(context.expiry()).expireAfterUpdate(any(), any(), anyLong(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void compute_present_sameInstance(Map map, CacheContext context) { - map.compute(context.firstKey(), (key, value) -> value); - verify(context.expiry()).expireAfterUpdate(any(), any(), anyLong(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void merge_absent(Map map, CacheContext context) { - map.merge(context.absentKey(), context.absentValue(), (key, value) -> context.absentValue()); - verify(context.expiry()).expireAfterCreate(any(), any(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void merge_nullValue(Map map, CacheContext context) { - map.merge(context.firstKey(), context.absentValue(), (key, value) -> null); - verifyNoInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void merge_present_differentValue(Map map, CacheContext context) { - map.merge(context.firstKey(), context.absentKey(), (key, value) -> context.absentValue()); - verify(context.expiry()).expireAfterUpdate(any(), any(), anyLong(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void merge_present_sameInstance(Map map, CacheContext context) { - map.merge(context.firstKey(), context.absentKey(), (key, value) -> value); - verify(context.expiry()).expireAfterUpdate(any(), any(), anyLong(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiry = CacheExpiry.MOCKITO) - public void refresh_absent(LoadingCache cache, CacheContext context) { - cache.refresh(context.absentKey()).join(); - - verify(context.expiry()).expireAfterCreate(any(), any(), anyLong()); - if (context.isAsync()) { - verify(context.expiry()).expireAfterUpdate(any(), any(), anyLong(), anyLong()); - } - verifyNoMoreInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - public void refresh_present(LoadingCache cache, CacheContext context) { - cache.refresh(context.firstKey()).join(); - verify(context.expiry()).expireAfterUpdate(any(), any(), anyLong(), anyLong()); - verifyNoMoreInteractions(context.expiry()); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, expiry = {CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}) - public void getIfPresentQuietly(Cache cache, CacheContext context) { - var original = cache.policy().expireVariably().orElseThrow().getExpiresAfter(context.firstKey()).orElseThrow(); - var advancement = Duration.ofSeconds(30); - context.ticker().advance(advancement); - cache.policy().getIfPresentQuietly(context.firstKey()); - var current = cache.policy().expireVariably().orElseThrow().getExpiresAfter(context.firstKey()).orElseThrow(); - assertThat(current.plus(advancement)).isEqualTo(original); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiry = CacheExpiry.DISABLED) - public void expireVariably_notEnabled(Cache cache) { - assertThat(cache.policy().expireVariably()).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE) - public void getExpiresAfter(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - assertThat(expireAfterVar.getExpiresAfter(context.absentKey(), TimeUnit.MINUTES)).isEmpty(); - assertThat(expireAfterVar.getExpiresAfter(context.firstKey(), TimeUnit.MINUTES)).hasValue(1); - when(context.expiry().expireAfterUpdate(any(), any(), anyLong(), anyLong())).thenReturn(TimeUnit.HOURS.toNanos(1)); - cache.put(context.firstKey(), context.absentValue()); - assertThat(expireAfterVar.getExpiresAfter(context.firstKey(), TimeUnit.MINUTES)).hasValue(60); - assertThat(expireAfterVar.getExpiresAfter(context.lastKey(), TimeUnit.MINUTES)).hasValue(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE) - public void getExpiresAfter_duration(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - assertThat(expireAfterVar.getExpiresAfter(context.absentKey())).isEmpty(); - assertThat(expireAfterVar.getExpiresAfter(context.firstKey())).hasValue(Duration.ofMinutes(1)); - when(context.expiry().expireAfterUpdate(any(), any(), anyLong(), anyLong())).thenReturn(TimeUnit.HOURS.toNanos(1)); - cache.put(context.firstKey(), context.absentValue()); - assertThat(expireAfterVar.getExpiresAfter(context.firstKey())).hasValue(Duration.ofHours(1)); - assertThat(expireAfterVar.getExpiresAfter(context.lastKey())).hasValue(Duration.ofMinutes(1)); - } - - @SuppressWarnings("unchecked") - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE) - public void getExpiresAfter_absent(CacheContext context, VarExpiration expireAfterVar) { - Mockito.reset(context.expiry()); - assertThat(expireAfterVar.getExpiresAfter(context.absentKey(), TimeUnit.SECONDS)).isEmpty(); - verifyNoInteractions(context.expiry()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, expiry = {CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, refreshAfterWrite = Expire.ONE_MINUTE) - public void getExpiresAfter_expired(Cache cache, - CacheContext context, VarExpiration expireAfterVar) { - cache.put(context.absentKey(), context.absentValue()); - context.ticker().advance(2, TimeUnit.MINUTES); - assertThat(expireAfterVar.getExpiresAfter(context.absentKey(), TimeUnit.SECONDS)).isEmpty(); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void setExpiresAfter_negative(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.setExpiresAfter(context.absentKey(), Duration.ofMinutes(-2)); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.ACCESS) - public void setExpiresAfter_excessive(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.setExpiresAfter(context.firstKey(), ChronoUnit.FOREVER.getDuration()); - assertThat(expireAfterVar.getExpiresAfter(context.firstKey(), TimeUnit.NANOSECONDS)) - .hasValue(MAXIMUM_EXPIRY); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE) - public void setExpiresAfter(Cache cache, - CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.setExpiresAfter(context.firstKey(), 2, TimeUnit.MINUTES); - assertThat(expireAfterVar.getExpiresAfter(context.firstKey(), TimeUnit.MINUTES)).hasValue(2); - expireAfterVar.setExpiresAfter(context.absentKey(), 4, TimeUnit.MINUTES); - assertThat(expireAfterVar.getExpiresAfter(context.absentKey(), TimeUnit.MINUTES)).isEmpty(); - context.ticker().advance(90, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).hasSize(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE) - public void setExpiresAfter_duration(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.setExpiresAfter(context.firstKey(), Duration.ofMinutes(2)); - assertThat(expireAfterVar.getExpiresAfter(context.firstKey())).hasValue(Duration.ofMinutes(2)); - expireAfterVar.setExpiresAfter(context.absentKey(), Duration.ofMinutes(4)); - assertThat(expireAfterVar.getExpiresAfter(context.absentKey())).isEmpty(); - context.ticker().advance(90, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).hasSize(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE) - public void setExpiresAfter_absent(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.setExpiresAfter(context.absentKey(), 1, TimeUnit.SECONDS); - context.ticker().advance(30, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiryTime = Expire.ONE_MINUTE, expiry = {CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, refreshAfterWrite = Expire.ONE_MINUTE) - public void setExpiresAfter_expired(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - cache.put(context.absentKey(), context.absentValue()); - context.ticker().advance(2, TimeUnit.MINUTES); - expireAfterVar.setExpiresAfter(context.absentKey(), 1, TimeUnit.MINUTES); - cache.cleanUp(); - assertThat(cache).isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void putIfAbsent_nullKey(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.putIfAbsent(null, Int.valueOf(2), 3, TimeUnit.SECONDS); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = Population.FULL, - expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void putIfAbsent_nullValue(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.putIfAbsent(Int.valueOf(1), null, 3, TimeUnit.SECONDS); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void putIfAbsent_nullTimeUnit(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.putIfAbsent(Int.valueOf(1), Int.valueOf(2), 3, null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void putIfAbsent_negativeDuration(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.putIfAbsent(Int.valueOf(1), Int.valueOf(2), -10, TimeUnit.SECONDS); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void putIfAbsent_nullDuration(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.putIfAbsent(Int.valueOf(1), Int.valueOf(2), null); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void putIfAbsent_excessiveDuration(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.putIfAbsent(context.absentKey(), - context.absentValue(), ChronoUnit.FOREVER.getDuration()); - assertThat(expireAfterVar.getExpiresAfter(context.absentKey(), TimeUnit.NANOSECONDS)) - .hasValue(MAXIMUM_EXPIRY); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void putIfAbsent_insert(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - Int key = context.absentKey(); - Int value = context.absentValue(); - Int result = expireAfterVar.putIfAbsent(key, value, Duration.ofMinutes(2)); - assertThat(result).isNull(); - assertThat(cache).containsEntry(key, value); - assertThat(expireAfterVar.getExpiresAfter(key)).hasValue(Duration.ofMinutes(2)); - context.ticker().advance(90, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).hasSize(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void putIfAbsent_present(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - Int key = context.firstKey(); - Int value = context.absentValue(); - Int result = expireAfterVar.putIfAbsent(key, value, Duration.ofMinutes(2)); - assertThat(result).isEqualTo(context.original().get(key)); - assertThat(cache).containsEntry(key, context.original().get(key)); - assertThat(expireAfterVar.getExpiresAfter(key)).hasValue(Duration.ofMinutes(1)); - context.ticker().advance(90, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void put_nullKey(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.put(null, Int.valueOf(2), 3, TimeUnit.SECONDS); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void put_nullValue(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.put(Int.valueOf(1), null, 3, TimeUnit.SECONDS); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void put_nullTimeUnit(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.put(Int.valueOf(1), Int.valueOf(2), 3, null); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void put_negativeDuration(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.put(Int.valueOf(1), Int.valueOf(2), -10, TimeUnit.SECONDS); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, - expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void put_excessiveDuration(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.put(context.absentKey(), context.absentValue(), ChronoUnit.FOREVER.getDuration()); - assertThat(expireAfterVar.getExpiresAfter(context.absentKey(), TimeUnit.NANOSECONDS)).hasValue(MAXIMUM_EXPIRY); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void put_nullDuration(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.put(Int.valueOf(1), Int.valueOf(2), null); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void put_insert(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - Int key = context.absentKey(); - Int value = context.absentValue(); - Int oldValue = expireAfterVar.put(key, value, Duration.ofMinutes(2)); - assertThat(oldValue).isNull(); - assertThat(cache).containsEntry(key, value); - assertThat(expireAfterVar.getExpiresAfter(key)).hasValue(Duration.ofMinutes(2)); - context.ticker().advance(90, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).hasSize(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void put_replace(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - Int key = context.firstKey(); - Int value = context.absentValue(); - Int oldValue = expireAfterVar.put(key, value, Duration.ofMinutes(2)); - assertThat(oldValue).isEqualTo(context.original().get(key)); - assertThat(cache).containsEntry(key, value); - assertThat(expireAfterVar.getExpiresAfter(key)).hasValue(Duration.ofMinutes(2)); - context.ticker().advance(90, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).hasSize(1); - } - - @CacheSpec(expiry = CacheExpiry.ACCESS, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void compute_nullKey(CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.compute(null, (key, value) -> key.negate(), Duration.ZERO); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(expiry = CacheExpiry.ACCESS, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void compute_nullMappingFunction(CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.compute(Int.valueOf(1), null, Duration.ZERO); - } - - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - @CacheSpec(expiry = CacheExpiry.ACCESS, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void compute_negativeDuration(CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.compute(Int.valueOf(1), (key, value) -> key.negate(), Duration.ofMinutes(-1)); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void compute_excessiveDuration(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.compute(context.absentKey(), (k, v) -> context.absentValue(), ChronoUnit.FOREVER.getDuration()); - assertThat(expireAfterVar.getExpiresAfter(context.absentKey(), TimeUnit.NANOSECONDS)).hasValue(MAXIMUM_EXPIRY); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_remove(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - var removed = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - assertThat(expireAfterVar.compute(key, (k, v) -> { - assertThat(v).isNotNull(); - return null; - }, Duration.ofDays(1))).isNull(); - removed.put(key, context.original().get(key)); - }); - verifyNoInteractions(context.expiry()); - int count = context.firstMiddleLastKeys().size(); - assertThat(cache).hasSize(context.initialSize() - count); - assertThat(context).stats().hits(0).misses(0).success(0).failures(count); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(removed).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void compute_recursive(CacheContext context, VarExpiration expireAfterVar) { - var mappingFunction = new BiFunction() { - @Override - public Int apply(Int key, Int value) { - return expireAfterVar.compute(key, this, Duration.ofDays(1)); - } - }; - try { - expireAfterVar.compute(context.absentKey(), mappingFunction, Duration.ofDays(1)); - Assert.fail(); - } catch (StackOverflowError | IllegalStateException e) { /* ignored */ } - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS, population = Population.EMPTY) - public void compute_pingpong(CacheContext context, VarExpiration expireAfterVar) { - var key1 = Int.valueOf(1); - var key2 = Int.valueOf(2); - var mappingFunction = new BiFunction() { - @Override - public Int apply(Int key, Int value) { - return expireAfterVar.compute(key.equals(key1) ? key2 : key1, this, Duration.ofDays(1)); - } - }; - try { - expireAfterVar.compute(key1, mappingFunction, Duration.ofDays(1)); - Assert.fail(); - } catch (StackOverflowError | IllegalStateException e) { /* ignored */ } - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO) - public void compute_error(Cache cache, - CacheContext context, VarExpiration expireAfterVar) { - try { - expireAfterVar.compute(context.absentKey(), - (key, value) -> { - throw new IllegalStateException(); - }, Duration.ofDays(1)); - Assert.fail(); - } catch (IllegalStateException ignored) { - } - verifyNoInteractions(context.expiry()); - assertThat(cache).containsExactlyEntriesIn(context.original()); - assertThat(context).stats().hits(0).misses(0).success(0).failures(1); - Int result = expireAfterVar.compute(context.absentKey(), (k, v) -> k.negate(), Duration.ofDays(1)); - assertThat(result).isEqualTo(context.absentKey().negate()); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void compute_absent_nullValue(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - Int result = expireAfterVar.compute(context.absentKey(), (key, value) -> null, Duration.ofDays(1)); - verifyNoInteractions(context.expiry()); - assertThat(result).isNull(); - assertThat(cache).hasSize(context.initialSize()); - assertThat(cache).doesNotContainKey(context.absentKey()); - assertThat(context).stats().hits(0).misses(0).success(0).failures(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void compute_absent(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - var duration = Duration.ofNanos(context.expiryTime().timeNanos() / 2); - Int result = expireAfterVar.compute(context.absentKey(), (key, value) -> context.absentValue(), duration); - verifyNoInteractions(context.expiry()); - assertThat(expireAfterVar.getExpiresAfter(context.absentKey())).hasValue(duration); - assertThat(result).isEqualTo(context.absentValue()); - assertThat(cache).hasSize(1 + context.initialSize()); - assertThat(context).stats().hits(0).misses(0).success(1).failures(0); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO) - public void compute_absent_expires(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.compute(context.absentKey(), - (k, v) -> context.absentValue(), Duration.ofMinutes(1)); - context.ticker().advance(Duration.ofMinutes(5)); - context.cleanUp(); - verifyNoInteractions(context.expiry()); - assertThat(cache).hasSize(context.initialSize()); - assertThat(cache).doesNotContainKey(context.absentKey()); - assertThat(context).notifications().withCause(EXPIRED).contains(context.absentKey(), context.absentValue()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO) - public void compute_absent_expiresImmediately(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.compute(context.absentKey(), (k, v) -> context.absentValue(), Duration.ZERO); - assertThat(cache).doesNotContainKey(context.absentKey()); - context.ticker().advance(Duration.ofMinutes(1)); - context.cleanUp(); - verifyNoInteractions(context.expiry()); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).notifications().withCause(EXPIRED).contains(context.absentKey(), context.absentValue()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE) - public void compute_absent_expiresLater(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.compute(context.absentKey(), (k, v) -> context.absentValue(), Duration.ofMinutes(5)); - context.ticker().advance(Duration.ofMinutes(3)); - context.cleanUp(); - assertThat(cache).hasSize(1); - verifyNoInteractions(context.expiry()); - assertThat(cache).containsKey(context.absentKey()); - assertThat(context).notifications().withCause(EXPIRED).contains(context.original()).exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_sameValue(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - var duration = Duration.ofNanos(context.expiryTime().timeNanos() / 2); - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - Int value = intern(new Int(context.original().get(key))); - Int result = expireAfterVar.compute(key, (k, v) -> value, duration); - replaced.put(key, value); - assertThat(result).isSameInstanceAs(value); - assertThat(expireAfterVar.getExpiresAfter(key)).hasValue(duration); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - verifyNoInteractions(context.expiry()); - assertThat(cache).hasSize(context.initialSize()); - assertThat(cache.asMap()).containsAtLeastEntriesIn(replaced); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - removalListener = Listener.REJECTING) - public void compute_sameInstance(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - var duration = Duration.ofNanos(context.expiryTime().timeNanos() / 2); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - Int result = expireAfterVar.compute(key, (k, v) -> value, duration); - assertThat(result).isSameInstanceAs(value); - assertThat(expireAfterVar.getExpiresAfter(key)).hasValue(duration); - }); - verifyNoInteractions(context.expiry()); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - assertThat(cache).containsEntry(key, value); - }); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).notifications().isEmpty(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_differentValue(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - var replaced = new HashMap(); - var duration = Duration.ofNanos(context.expiryTime().timeNanos() / 2); - context.firstMiddleLastKeys().forEach(key -> { - Int value = context.original().get(key); - Int result = expireAfterVar.compute(key, (k, v) -> value.negate(), duration); - replaced.put(key, value); - assertThat(result).isEqualTo(key); - assertThat(expireAfterVar.getExpiresAfter(key)).hasValue(duration); - }); - verifyNoInteractions(context.expiry()); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> assertThat(cache).containsEntry(key, key)); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_present_expires(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.compute(context.firstKey(), (k, v) -> { - assertThat(v).isNotNull(); - return context.absentValue(); - }, Duration.ofMinutes(1)); - context.ticker().advance(Duration.ofMinutes(5)); - context.cleanUp(); - verifyNoInteractions(context.expiry()); - assertThat(cache).hasSize(context.initialSize() - 1); - assertThat(cache).doesNotContainKey(context.firstKey()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(context.firstKey(), context.original().get(context.firstKey())); - assertThat(context).removalNotifications().withCause(EXPIRED).contains(context.firstKey(), context.absentValue()); - assertThat(context).removalNotifications().hasSize(2); - assertThat(context).evictionNotifications().withCause(EXPIRED).contains(context.firstKey(), context.absentValue()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_present_expiresImmediately(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.compute(context.firstKey(), (k, v) -> { - assertThat(v).isNotNull(); - return context.absentValue(); - }, Duration.ZERO); - assertThat(cache).doesNotContainKey(context.firstKey()); - context.ticker().advance(Duration.ofMinutes(1)); - context.cleanUp(); - verifyNoInteractions(context.expiry()); - assertThat(cache).hasSize(context.initialSize() - 1); - assertThat(context).removalNotifications().withCause(REPLACED).contains(context.firstKey(), context.original().get(context.firstKey())); - assertThat(context).removalNotifications().withCause(EXPIRED).contains(context.firstKey(), context.absentValue()); - assertThat(context).removalNotifications().hasSize(2); - assertThat(context).evictionNotifications().withCause(EXPIRED).contains(context.firstKey(), context.absentValue()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO, expiryTime = Expire.ONE_MINUTE, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void compute_present_expiresLater(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.compute(context.firstKey(), (k, v) -> { - assertThat(v).isNotNull(); - return context.absentValue(); - }, Duration.ofMinutes(5)); - context.ticker().advance(Duration.ofMinutes(3)); - context.cleanUp(); - assertThat(cache).hasSize(1); - verifyNoInteractions(context.expiry()); - assertThat(cache).containsKey(context.firstKey()); - var expired = new HashMap<>(context.original()); - expired.remove(context.firstKey()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(context.firstKey(), context.original().get(context.firstKey())); - assertThat(context).removalNotifications().withCause(EXPIRED).contains(expired); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - assertThat(context).evictionNotifications().withCause(EXPIRED).contains(expired).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.MOCKITO, population = Population.EMPTY, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void compute_async_null(AsyncCache cache, CacheContext context, VarExpiration expireAfterVar) { - Int key = context.absentKey(); - Int newValue = context.absentValue(); - var future = new CompletableFuture(); - cache.put(key, future); - var start = new AtomicBoolean(); - var done = new AtomicBoolean(); - ConcurrentTestHarness.execute(() -> { - start.set(true); - Int result = expireAfterVar.compute(key, (k, oldValue) -> newValue, Duration.ofDays(1)); - assertThat(result).isEqualTo(newValue); - done.set(true); - }); - await().untilTrue(start); - future.complete(null); - await().untilTrue(done); - verifyNoInteractions(context.expiry()); - assertThat(cache).containsEntry(key, newValue); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void compute_writeTime(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - Int key = context.firstKey(); - Int value = context.absentValue(); - var duration = Duration.ofNanos(context.expiryTime().timeNanos()); - expireAfterVar.compute(key, (k, v) -> { - context.ticker().advance(5, TimeUnit.MINUTES); - return value; - }, duration); - context.cleanUp(); - assertThat(cache).hasSize(1); - assertThat(cache).containsKey(key); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void compute_absent_error(CacheContext context, VarExpiration expireAfterVar) { - context.ticker().advance(2, TimeUnit.MINUTES); - try { - expireAfterVar.compute(context.firstKey(), (key, value) -> { - throw new IllegalStateException(); - }, Duration.ofDays(1)); - Assert.fail(); - } catch (IllegalStateException expected) { - } - - assertThat(expireAfterVar.getExpiresAfter(context.firstKey())).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, compute = Compute.SYNC, - expiry = CacheExpiry.WRITE, expiryTime = Expire.ONE_MINUTE) - public void compute_present_error(CacheContext context, VarExpiration expireAfterVar) { - context.ticker().advance(30, TimeUnit.SECONDS); - var variable = expireAfterVar.getExpiresAfter(context.firstKey()); - try { - expireAfterVar.compute(context.firstKey(), (key, value) -> { - throw new IllegalStateException(); - }, Duration.ofDays(1)); - Assert.fail(); - } catch (IllegalStateException expected) { - } - assertThat(variable).isEqualTo(expireAfterVar.getExpiresAfter(context.firstKey())); - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void compute_absent_expiryFails(Map map, CacheContext context) { - try { - when(context.expiry().expireAfterCreate(any(), any(), anyLong())).thenThrow(ExpirationException.class); - map.compute(context.absentKey(), (k, v) -> k); - } finally { - assertThat(map).containsExactlyEntriesIn(context.original()); - } - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO) - @Test(dataProvider = "caches", expectedExceptions = ExpirationException.class) - public void compute_present_expiryFails(Map map, CacheContext context) { - try { - when(context.expiry().expireAfterUpdate(any(), any(), anyLong(), anyLong())).thenThrow(ExpirationException.class); - map.compute(context.firstKey(), (k, v) -> v.negate()); - } finally { - assertThat(map).containsExactlyEntriesIn(context.original()); - } - } - - @CacheSpec(expiry = CacheExpiry.ACCESS) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void oldest_unmodifiable(CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.oldest(Integer.MAX_VALUE).clear(); - } - - @CacheSpec(expiry = CacheExpiry.ACCESS) - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void oldest_negative(CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.oldest(-1); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void oldest_zero(CacheContext context, VarExpiration expireAfterVar) { - assertThat(expireAfterVar.oldest(0)).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.ACCESS) - public void oldest_partial(CacheContext context, VarExpiration expireAfterVar) { - int count = context.original().size() / 2; - assertThat(expireAfterVar.oldest(count)).hasSize(count); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, expiry = CacheExpiry.ACCESS, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void oldest_order(CacheContext context, VarExpiration expireAfterVar) { - var oldest = expireAfterVar.oldest(Integer.MAX_VALUE); - assertThat(oldest.keySet()).containsExactlyElementsIn(context.original().keySet()).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void oldest_snapshot(Cache cache, - CacheContext context, VarExpiration expireAfterVar) { - var oldest = expireAfterVar.oldest(Integer.MAX_VALUE); - cache.invalidateAll(); - assertThat(oldest).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void oldestFunc_null(CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.oldest(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void oldestFunc_nullResult(CacheContext context, VarExpiration expireAfterVar) { - var result = expireAfterVar.oldest(stream -> null); - assertThat(result).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void oldestFunc_throwsException(CacheContext context, VarExpiration expireAfterVar) { - var expected = new IllegalStateException(); - try { - expireAfterVar.oldest(stream -> { - throw expected; - }); - Assert.fail(); - } catch (IllegalStateException e) { - assertThat(e).isSameInstanceAs(expected); - } - } - - @Test(dataProvider = "caches", expectedExceptions = ConcurrentModificationException.class) - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void oldestFunc_concurrentModification(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.oldest(stream -> { - context.ticker().advance(1, TimeUnit.NANOSECONDS); - cache.put(context.absentKey(), context.absentValue()); - return stream.count(); - }); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "source already consumed or closed") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void oldestFunc_closed(CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.oldest(stream -> stream).forEach(e -> { - }); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void oldestFunc_partial(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - var result = expireAfterVar.oldest(stream -> stream.limit(context.initialSize() / 2) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache.asMap()).containsAtLeastEntriesIn(result); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void oldestFunc_full(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - var result = expireAfterVar.oldest(stream -> stream.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache).containsExactlyEntriesIn(result); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}, - expiry = CacheExpiry.ACCESS) - public void oldestFunc_order(CacheContext context, VarExpiration expireAfterVar) { - var oldest = expireAfterVar.oldest(stream -> stream.map(Map.Entry::getKey).collect(toImmutableList())); - assertThat(oldest).containsExactlyElementsIn(context.original().keySet()).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = VARIABLE, population = {Population.PARTIAL, Population.FULL}) - public void oldestFunc_metadata(CacheContext context, VarExpiration expireAfterVar) { - var entries = expireAfterVar.oldest(stream -> stream.collect(toImmutableList())); - entries.forEach(entry -> assertThat(context).containsEntry(entry)); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.ACCESS, expiryTime = Expire.ONE_MINUTE) - public void oldestFunc_metadata_expiresInTraversal(CacheContext context, VarExpiration expireAfterVar) { - context.ticker().setAutoIncrementStep(30, TimeUnit.SECONDS); - var entries = expireAfterVar.oldest(stream -> stream.collect(toImmutableList())); - assertThat(context.cache()).hasSize(context.initialSize()); - assertThat(entries).hasSize(1); - } - - @CacheSpec(expiry = CacheExpiry.ACCESS) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void youngest_unmodifiable(CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.youngest(Integer.MAX_VALUE).clear(); - } - - @CacheSpec(expiry = CacheExpiry.ACCESS) - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void youngest_negative(CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.youngest(-1); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void youngest_zero(CacheContext context, VarExpiration expireAfterVar) { - assertThat(expireAfterVar.youngest(0)).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.ACCESS) - public void youngest_partial(CacheContext context, VarExpiration expireAfterVar) { - int count = context.original().size() / 2; - assertThat(expireAfterVar.youngest(count)).hasSize(count); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, expiry = CacheExpiry.ACCESS, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void youngest_order(CacheContext context, VarExpiration expireAfterVar) { - var youngest = expireAfterVar.youngest(Integer.MAX_VALUE); - var expected = ImmutableList.copyOf(context.original().keySet()).reverse(); - assertThat(youngest.keySet()).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void youngest_snapshot(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - var youngest = expireAfterVar.youngest(Integer.MAX_VALUE); - cache.invalidateAll(); - assertThat(youngest).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void youngestFunc_null(CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.youngest(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void youngestFunc_nullResult(CacheContext context, VarExpiration expireAfterVar) { - var result = expireAfterVar.youngest(stream -> null); - assertThat(result).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void youngestFunc_throwsException(CacheContext context, VarExpiration expireAfterVar) { - var expected = new IllegalStateException(); - try { - expireAfterVar.youngest(stream -> { - throw expected; - }); - Assert.fail(); - } catch (IllegalStateException e) { - assertThat(e).isSameInstanceAs(expected); - } - } - - @Test(dataProvider = "caches", expectedExceptions = ConcurrentModificationException.class) - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void youngestFunc_concurrentModification(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.youngest(stream -> { - context.ticker().advance(1, TimeUnit.NANOSECONDS); - cache.put(context.absentKey(), context.absentValue()); - return stream.count(); - }); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class, - expectedExceptionsMessageRegExp = "source already consumed or closed") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void youngestFunc_closed(CacheContext context, VarExpiration expireAfterVar) { - expireAfterVar.youngest(stream -> stream).forEach(e -> { - }); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void youngestFunc_partial(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - var result = expireAfterVar.youngest(stream -> stream - .limit(context.initialSize() / 2) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache.asMap()).containsAtLeastEntriesIn(result); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches") - @CacheSpec(expiry = CacheExpiry.ACCESS) - public void youngestFunc_full(Cache cache, CacheContext context, VarExpiration expireAfterVar) { - var result = expireAfterVar.youngest(stream -> stream.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache).containsExactlyEntriesIn(result); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}, expiry = CacheExpiry.ACCESS) - public void youngestFunc_order(CacheContext context, VarExpiration expireAfterVar) { - var youngest = expireAfterVar.youngest(stream -> stream.map(Map.Entry::getKey).collect(toImmutableList())); - var expected = ImmutableList.copyOf(context.original().keySet()).reverse(); - assertThat(youngest).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = VARIABLE, population = Population.FULL) - public void youngestFunc_metadata(CacheContext context, VarExpiration expireAfterVar) { - var entries = expireAfterVar.youngest(stream -> stream.collect(toImmutableList())); - entries.forEach(entry -> assertThat(context).containsEntry(entry)); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - expiry = CacheExpiry.ACCESS, expiryTime = Expire.ONE_MINUTE) - public void youngestFunc_metadata_expiresInTraversal(CacheContext context, VarExpiration expireAfterVar) { - context.ticker().setAutoIncrementStep(30, TimeUnit.SECONDS); - var entries = expireAfterVar.youngest(stream -> stream.collect(toImmutableList())); - assertThat(context.cache()).hasSize(context.initialSize()); - assertThat(entries).hasSize(1); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java deleted file mode 100644 index b06bce4..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterWriteTest.java +++ /dev/null @@ -1,505 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.Policy.FixedExpiration; -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; -import org.testng.Assert; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.ConcurrentModificationException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -import static com.github.benmanes.caffeine.cache.RemovalCause.EXPIRED; -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -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.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.base.Functions.identity; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; -import static uk.org.lidalia.slf4jext.Level.WARN; - -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@SuppressWarnings("PreferJavaTimeOverload") -@Test(dataProviderClass = CacheProvider.class) -public final class ExpireAfterWriteTest { - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, expiryTime = Expire.ONE_MINUTE, - population = {Population.PARTIAL, Population.FULL}) - public void getIfPresent(Cache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.getIfPresent(context.firstKey()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.getIfPresent(context.firstKey())).isNull(); - cache.cleanUp(); - assertThat(cache).isEmpty(); - assertThat(context).notifications().withCause(EXPIRED).contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, expireAfterWrite = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, expiryTime = Expire.ONE_MINUTE) - public void get(Cache cache, CacheContext context) { - Function mappingFunction = context.original()::get; - context.ticker().advance(30, TimeUnit.SECONDS); - cache.get(context.firstKey(), mappingFunction); - context.ticker().advance(45, TimeUnit.SECONDS); - cache.get(context.lastKey(), mappingFunction); - cache.cleanUp(); - assertThat(cache).hasSize(1); - assertThat(context).notifications().withCause(EXPIRED).contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, - mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, expireAfterWrite = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, expiryTime = Expire.ONE_MINUTE) - public void getAllPresent(Cache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.getAllPresent(context.firstMiddleLastKeys()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.getAllPresent(context.firstMiddleLastKeys())).isExhaustivelyEmpty(); - cache.cleanUp(); - assertThat(cache).isEmpty(); - assertThat(context).notifications().withCause(EXPIRED).contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, expireAfterWrite = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, expiryTime = Expire.ONE_MINUTE) - public void getAll(Cache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - var result1 = cache.getAll(List.of(context.firstKey(), context.absentKey()), keys -> Maps.asMap(keys, identity())); - assertThat(result1).containsExactly(context.firstKey(), context.firstKey().negate(), context.absentKey(), context.absentKey()); - context.ticker().advance(45, TimeUnit.SECONDS); - cache.cleanUp(); - var result2 = cache.getAll(List.of(context.firstKey(), context.absentKey()), keys -> Maps.asMap(keys, identity())); - assertThat(result2).containsExactly(context.firstKey(), context.firstKey(), context.absentKey(), context.absentKey()); - assertThat(cache).hasSize(2); - assertThat(context).notifications().withCause(EXPIRED).contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, expireAfterWrite = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, expiryTime = Expire.ONE_MINUTE) - public void get_loading(LoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.get(context.firstKey()); - cache.get(context.absentKey()); - context.ticker().advance(45, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).hasSize(1); - assertThat(cache).containsEntry(context.absentKey(), context.absentKey().negate()); - assertThat(context).notifications().withCause(EXPIRED).contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, - mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, expireAfterWrite = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, expiryTime = Expire.ONE_MINUTE, - loader = {Loader.IDENTITY, Loader.BULK_IDENTITY}) - public void getAll_loading(LoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(cache.getAll(List.of(context.firstKey(), context.absentKey()))) - .containsExactly(context.firstKey(), context.firstKey().negate(), context.absentKey(), context.absentKey()); - context.ticker().advance(45, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache.getAll(List.of(context.firstKey(), context.absentKey()))).containsExactly(context.firstKey(), context.firstKey(), - context.absentKey(), context.absentKey()); - assertThat(context).notifications().withCause(EXPIRED).contains(context.original()).exclusively(); - assertThat(cache).hasSize(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, expireAfterWrite = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, expiryTime = Expire.ONE_MINUTE) - public void getIfPresent(AsyncCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.getIfPresent(context.firstKey()).join(); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache).doesNotContainKey(context.firstKey()); - assertThat(cache).doesNotContainKey(context.lastKey()); - context.cleanUp(); - assertThat(cache).isEmpty(); - assertThat(context).notifications().withCause(EXPIRED).contains(context.original()).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, mustExpireWithAnyOf = {AFTER_WRITE, VARIABLE}, expireAfterWrite = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.WRITE}, expiryTime = Expire.ONE_MINUTE) - public void putIfAbsent(Map map, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(map.putIfAbsent(context.firstKey(), context.absentValue())).isNotNull(); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(map.putIfAbsent(context.lastKey(), context.absentValue())).isNull(); - assertThat(map).hasSize(1); - assertThat(context).notifications().withCause(EXPIRED).contains(context.original()).exclusively(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expireAfterWrite = Expire.ONE_MINUTE) - public void getIfPresentQuietly(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var original = expireAfterWrite.ageOf(context.firstKey()).orElseThrow(); - var advancement = Duration.ofSeconds(30); - context.ticker().advance(advancement); - cache.policy().getIfPresentQuietly(context.firstKey()); - var current = expireAfterWrite.ageOf(context.firstKey()).orElseThrow(); - assertThat(current.minus(advancement)).isEqualTo(original); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = AFTER_WRITE, expireAfterWrite = Expire.ONE_MINUTE) - public void getExpiresAfter(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - assertThat(expireAfterWrite.getExpiresAfter(TimeUnit.MINUTES)).isEqualTo(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = AFTER_WRITE, expireAfterWrite = Expire.ONE_MINUTE) - public void getExpiresAfter_duration(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - assertThat(expireAfterWrite.getExpiresAfter()).isEqualTo(Duration.ofMinutes(1)); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void setExpiresAfter_negative(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.setExpiresAfter(Duration.ofMinutes(-2)); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void setExpiresAfter_excessive(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.setExpiresAfter(ChronoUnit.FOREVER.getDuration()); - assertThat(expireAfterWrite.getExpiresAfter(TimeUnit.NANOSECONDS)).isEqualTo(Long.MAX_VALUE); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void setExpiresAfter(Cache cache, CacheContext context, - @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.setExpiresAfter(2, TimeUnit.MINUTES); - assertThat(expireAfterWrite.getExpiresAfter(TimeUnit.MINUTES)).isEqualTo(2); - context.ticker().advance(90, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void setExpiresAfter_duration(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.setExpiresAfter(Duration.ofMinutes(2)); - assertThat(expireAfterWrite.getExpiresAfter()).isEqualTo(Duration.ofMinutes(2)); - context.ticker().advance(90, TimeUnit.SECONDS); - cache.cleanUp(); - assertThat(cache).hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, expireAfterWrite = Expire.ONE_MINUTE) - public void ageOf(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - assertThat(expireAfterWrite.ageOf(context.firstKey(), TimeUnit.SECONDS)).hasValue(0); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(expireAfterWrite.ageOf(context.firstKey(), TimeUnit.SECONDS)).hasValue(30); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(expireAfterWrite.ageOf(context.firstKey(), TimeUnit.SECONDS)).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, expireAfterWrite = Expire.ONE_MINUTE) - public void ageOf_duration(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - assertThat(expireAfterWrite.ageOf(context.firstKey()).orElseThrow().toSeconds()).isEqualTo(0); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(expireAfterWrite.ageOf(context.firstKey()).orElseThrow().toSeconds()).isEqualTo(30); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(expireAfterWrite.ageOf(context.firstKey())).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void ageOf_absent(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - assertThat(expireAfterWrite.ageOf(context.absentKey())).isEmpty(); - assertThat(expireAfterWrite.ageOf(context.absentKey(), TimeUnit.SECONDS)).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, expireAfterWrite = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}) - public void ageOf_expired(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - cache.put(context.absentKey(), context.absentValue()); - context.ticker().advance(2, TimeUnit.MINUTES); - assertThat(expireAfterWrite.ageOf(context.absentKey(), TimeUnit.SECONDS)).isEmpty(); - } - - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void oldest_unmodifiable(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.oldest(Integer.MAX_VALUE).clear(); - } - - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void oldest_negative(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.oldest(-1); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void oldest_zero(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - assertThat(expireAfterWrite.oldest(0)).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expireAfterWrite = Expire.ONE_MINUTE) - public void oldest_partial(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - int count = context.original().size() / 2; - assertThat(expireAfterWrite.oldest(count)).hasSize(count); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}, - expireAfterWrite = Expire.ONE_MINUTE) - public void oldest_order(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var oldest = expireAfterWrite.oldest(Integer.MAX_VALUE); - assertThat(oldest.keySet()).containsExactlyElementsIn(context.original().keySet()).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void oldest_snapshot(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var oldest = expireAfterWrite.oldest(Integer.MAX_VALUE); - cache.invalidateAll(); - assertThat(oldest).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void oldestFunc_null(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.oldest(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void oldestFunc_nullResult(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var result = expireAfterWrite.oldest(stream -> null); - assertThat(result).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void oldestFunc_throwsException(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var expected = new IllegalStateException(); - try { - expireAfterWrite.oldest(stream -> { - throw expected; - }); - Assert.fail(); - } catch (IllegalStateException e) { - assertThat(e).isSameInstanceAs(expected); - } - } - - @Test(dataProvider = "caches", expectedExceptions = ConcurrentModificationException.class) - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void oldestFunc_concurrentModification(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.oldest(stream -> { - cache.put(context.absentKey(), context.absentValue()); - return stream.count(); - }); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "source already consumed or closed") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void oldestFunc_closed(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.oldest(stream -> stream).forEach(e -> { - }); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void oldestFunc_partial(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var result = expireAfterWrite.oldest(stream -> stream - .limit(context.initialSize() / 2) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache.asMap()).containsAtLeastEntriesIn(result); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void oldestFunc_full(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var result = expireAfterWrite.oldest(stream -> stream.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache).containsExactlyEntriesIn(result); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}, - expireAfterWrite = Expire.ONE_MINUTE) - public void oldestFunc_order(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var oldest = expireAfterWrite.oldest(stream -> stream.map(Map.Entry::getKey).collect(toImmutableList())); - assertThat(oldest).containsExactlyElementsIn(context.original().keySet()).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = AFTER_WRITE, population = {Population.PARTIAL, Population.FULL}) - public void oldestFunc_metadata(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var entries = expireAfterWrite.oldest(stream -> stream.collect(toImmutableList())); - entries.forEach(entry -> assertThat(context).containsEntry(entry)); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expireAfterWrite = Expire.ONE_MINUTE) - public void oldestFunc_metadata_expiresInTraversal(CacheContext context, - @ExpireAfterWrite FixedExpiration expireAfterWrite) { - context.ticker().setAutoIncrementStep(30, TimeUnit.SECONDS); - var entries = expireAfterWrite.oldest(stream -> stream.collect(toImmutableList())); - assertThat(context.cache()).hasSize(context.initialSize()); - assertThat(entries).hasSize(1); - } - - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void youngest_unmodifiable(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.youngest(Integer.MAX_VALUE).clear(); - } - - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - public void youngest_negative(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.youngest(-1); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void youngest_zero(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - assertThat(expireAfterWrite.youngest(0)).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expireAfterWrite = Expire.ONE_MINUTE) - public void youngest_partial(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - int count = context.original().size() / 2; - assertThat(expireAfterWrite.youngest(count)).hasSize(count); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}, - expireAfterWrite = Expire.ONE_MINUTE) - public void youngest_order(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var youngest = expireAfterWrite.youngest(Integer.MAX_VALUE); - var expected = ImmutableList.copyOf(context.original().keySet()).reverse(); - assertThat(youngest.keySet()).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void youngest_snapshot(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var youngest = expireAfterWrite.youngest(Integer.MAX_VALUE); - cache.invalidateAll(); - assertThat(youngest).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void youngestFunc_null(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.youngest(null); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void youngestFunc_nullResult(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var result = expireAfterWrite.youngest(stream -> null); - assertThat(result).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void youngestFunc_throwsException(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var expected = new IllegalStateException(); - try { - expireAfterWrite.youngest(stream -> { - throw expected; - }); - Assert.fail(); - } catch (IllegalStateException e) { - assertThat(e).isSameInstanceAs(expected); - } - } - - @Test(dataProvider = "caches", expectedExceptions = ConcurrentModificationException.class) - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void youngestFunc_concurrentModification(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.youngest(stream -> { - cache.put(context.absentKey(), context.absentValue()); - return stream.count(); - }); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class, - expectedExceptionsMessageRegExp = "source already consumed or closed") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void youngestFunc_closed(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - expireAfterWrite.youngest(stream -> stream).forEach(e -> { - }); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void youngestFunc_partial(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var result = expireAfterWrite.youngest(stream -> stream.limit(context.initialSize() / 2) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache.asMap()).containsAtLeastEntriesIn(result); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches") - @CacheSpec(expireAfterWrite = Expire.ONE_MINUTE) - public void youngestFunc_full(Cache cache, CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var result = expireAfterWrite.youngest(stream -> stream.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); - assertThat(cache).containsExactlyEntriesIn(result); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.PARTIAL, Population.FULL}, - removalListener = {Listener.DISABLED, Listener.REJECTING}, - expireAfterWrite = Expire.ONE_MINUTE) - public void youngestFunc_order(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var youngest = expireAfterWrite.youngest(stream -> stream.map(Map.Entry::getKey).collect(toImmutableList())); - var expected = ImmutableList.copyOf(context.original().keySet()).reverse(); - assertThat(youngest).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "caches") - @CacheSpec(mustExpireWithAnyOf = AFTER_WRITE, population = {Population.PARTIAL, Population.FULL}) - public void youngestFunc_metadata(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - var entries = expireAfterWrite.youngest(stream -> stream.collect(toImmutableList())); - entries.forEach(entry -> assertThat(context).containsEntry(entry)); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, expireAfterWrite = Expire.ONE_MINUTE) - public void youngestFunc_metadata_expiresInTraversal(CacheContext context, @ExpireAfterWrite FixedExpiration expireAfterWrite) { - context.ticker().setAutoIncrementStep(30, TimeUnit.SECONDS); - var entries = expireAfterWrite.youngest(stream -> stream.collect(toImmutableList())); - assertThat(context.cache()).hasSize(context.initialSize()); - assertThat(entries).hasSize(1); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/FrequencySketchTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/FrequencySketchTest.java deleted file mode 100644 index 04c2f37..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/FrequencySketchTest.java +++ /dev/null @@ -1,158 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.util.concurrent.ThreadLocalRandom; - -import static com.google.common.truth.Truth.assertThat; - - -public final class FrequencySketchTest { - final Integer item = ThreadLocalRandom.current().nextInt(); - - @Test - public void construct() { - var sketch = new FrequencySketch(); - assertThat(sketch.table).isNull(); - assertThat(sketch.isNotInitialized()).isTrue(); - - sketch.increment(item); - assertThat(sketch.frequency(item)).isEqualTo(0); - } - - @Test(dataProvider = "sketch", expectedExceptions = IllegalArgumentException.class) - public void ensureCapacity_negative(FrequencySketch sketch) { - sketch.ensureCapacity(-1); - } - - @Test(dataProvider = "sketch") - public void ensureCapacity_smaller(FrequencySketch sketch) { - int size = sketch.table.length; - sketch.ensureCapacity(size / 2); - assertThat(sketch.table).hasLength(size); - assertThat(sketch.sampleSize).isEqualTo(10 * size); - assertThat(sketch.blockMask).isEqualTo((size >> 3) - 1); - } - - @Test(dataProvider = "sketch") - public void ensureCapacity_larger(FrequencySketch sketch) { - int size = sketch.table.length; - sketch.ensureCapacity(2 * size); - assertThat(sketch.table).hasLength(2 * size); - assertThat(sketch.sampleSize).isEqualTo(10 * 2 * size); - assertThat(sketch.blockMask).isEqualTo(((2 * size) >> 3) - 1); - } - - @Test(dataProvider = "sketch", groups = "isolated") - public void ensureCapacity_maximum(FrequencySketch sketch) { - int size = Integer.MAX_VALUE / 10 + 1; - sketch.ensureCapacity(size); - assertThat(sketch.sampleSize).isEqualTo(Integer.MAX_VALUE); - assertThat(sketch.table).hasLength(Caffeine.ceilingPowerOfTwo(size)); - assertThat(sketch.blockMask).isEqualTo((sketch.table.length >> 3) - 1); - } - - @Test(dataProvider = "sketch") - public void increment_once(FrequencySketch sketch) { - sketch.increment(item); - assertThat(sketch.frequency(item)).isEqualTo(1); - } - - @Test(dataProvider = "sketch") - public void increment_max(FrequencySketch sketch) { - for (int i = 0; i < 20; i++) { - sketch.increment(item); - } - assertThat(sketch.frequency(item)).isEqualTo(15); - } - - @Test(dataProvider = "sketch") - public void increment_distinct(FrequencySketch sketch) { - sketch.increment(item); - sketch.increment(item + 1); - assertThat(sketch.frequency(item)).isEqualTo(1); - assertThat(sketch.frequency(item + 1)).isEqualTo(1); - assertThat(sketch.frequency(item + 2)).isEqualTo(0); - } - - @Test(dataProvider = "sketch") - public void increment_zero(FrequencySketch sketch) { - sketch.increment(0); - assertThat(sketch.frequency(0)).isEqualTo(1); - } - - @Test - public void reset() { - boolean reset = false; - var sketch = new FrequencySketch(); - sketch.ensureCapacity(64); - - for (int i = 1; i < 20 * sketch.table.length; i++) { - sketch.increment(i); - if (sketch.size != i) { - reset = true; - break; - } - } - assertThat(reset).isTrue(); - assertThat(sketch.size).isAtMost(sketch.sampleSize / 2); - } - - @Test - public void full() { - FrequencySketch sketch = makeSketch(512); - sketch.sampleSize = Integer.MAX_VALUE; - for (int i = 0; i < 100_000; i++) { - sketch.increment(i); - } - for (long item : sketch.table) { - assertThat(Long.bitCount(item)).isEqualTo(64); - } - - sketch.reset(); - for (long item : sketch.table) { - assertThat(item).isEqualTo(FrequencySketch.RESET_MASK); - } - } - - @Test - public void heavyHitters() { - FrequencySketch sketch = makeSketch(512); - for (int i = 100; i < 100_000; i++) { - sketch.increment((double) i); - } - for (int i = 0; i < 10; i += 2) { - for (int j = 0; j < i; j++) { - sketch.increment((double) i); - } - } - int[] popularity = new int[10]; - for (int i = 0; i < 10; i++) { - popularity[i] = sketch.frequency((double) i); - } - for (int i = 0; i < popularity.length; i++) { - if ((i == 0) || (i == 1) || (i == 3) || (i == 5) || (i == 7) || (i == 9)) { - assertThat(popularity[i]).isAtMost(popularity[2]); - } else if (i == 2) { - assertThat(popularity[2]).isAtMost(popularity[4]); - } else if (i == 4) { - assertThat(popularity[4]).isAtMost(popularity[6]); - } else if (i == 6) { - assertThat(popularity[6]).isAtMost(popularity[8]); - } - } - } - - @DataProvider(name = "sketch") - public Object[][] providesSketch() { - return new Object[][]{{makeSketch(512)}}; - } - - private static FrequencySketch makeSketch(long maximumSize) { - var sketch = new FrequencySketch(); - sketch.ensureCapacity(maximumSize); - return sketch; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/InternerTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/InternerTest.java deleted file mode 100644 index 59c9491..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/InternerTest.java +++ /dev/null @@ -1,151 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.References.WeakKeyEqualsReference; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.collect.testing.SetTestSuiteBuilder; -import com.google.common.collect.testing.TestStringSetGenerator; -import com.google.common.collect.testing.features.CollectionFeature; -import com.google.common.collect.testing.features.CollectionSize; -import com.google.common.testing.GcFinalization; -import com.google.common.testing.NullPointerTester; -import junit.framework.TestCase; -import junit.framework.TestSuite; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.lang.ref.WeakReference; -import java.util.Arrays; -import java.util.Collections; -import java.util.Set; - -import static com.github.benmanes.caffeine.cache.LocalCacheSubject.mapLocal; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.truth.Truth.assertAbout; -import static com.google.common.truth.Truth.assertThat; - - -public final class InternerTest extends TestCase { - public static TestSuite suite() { - return SetTestSuiteBuilder.using(new TestStringSetGenerator() { - @Override - protected Set create(String[] elements) { - var set = Collections.newSetFromMap(new WeakInterner().cache); - set.addAll(Arrays.asList(elements)); - return set; - } - }) - .named("Interner").withFeatures(CollectionSize.ANY, CollectionFeature.GENERAL_PURPOSE).createTestSuite(); - } - - @Test(dataProvider = "interners", expectedExceptions = NullPointerException.class) - public void intern_null(Interner interner) { - interner.intern(null); - } - - @Test(dataProvider = "interners") - public void intern(Interner interner) { - var canonical = new Int(1); - var other = new Int(1); - assertThat(interner.intern(canonical)).isSameInstanceAs(canonical); - assertThat(interner.intern(other)).isSameInstanceAs(canonical); - checkSize(interner, 1); - var next = new Int(2); - assertThat(interner.intern(next)).isSameInstanceAs(next); - checkSize(interner, 2); - checkState(interner); - } - - @Test - public void intern_weak_replace() { - var canonical = new Int(1); - var other = new Int(1); - Interner interner = Interner.newWeakInterner(); - assertThat(interner.intern(canonical)).isSameInstanceAs(canonical); - var signal = new WeakReference<>(canonical); - canonical = null; - GcFinalization.awaitClear(signal); - assertThat(interner.intern(other)).isSameInstanceAs(other); - checkSize(interner, 1); - checkState(interner); - } - - @Test - public void intern_weak_remove() { - var canonical = new Int(1); - var next = new Int(2); - Interner interner = Interner.newWeakInterner(); - assertThat(interner.intern(canonical)).isSameInstanceAs(canonical); - var signal = new WeakReference<>(canonical); - canonical = null; - GcFinalization.awaitClear(signal); - assertThat(interner.intern(next)).isSameInstanceAs(next); - checkSize(interner, 1); - checkState(interner); - } - - @Test - public void intern_weak_cleanup() { - var interner = (WeakInterner) Interner.newWeakInterner(); - interner.cache.drainStatus = BoundedLocalCache.REQUIRED; - var canonical = new Int(1); - interner.intern(canonical); - assertThat(interner.cache.drainStatus).isEqualTo(BoundedLocalCache.IDLE); - interner.cache.drainStatus = BoundedLocalCache.REQUIRED; - interner.intern(canonical); - assertThat(interner.cache.drainStatus).isEqualTo(BoundedLocalCache.IDLE); - } - - @Test - public void nullPointerExceptions() { - new NullPointerTester().testAllPublicStaticMethods(Interner.class); - } - - @Test - public void interned() { - var node = new Interned(new WeakReference<>(null)); - assertThat(node.getValue()).isTrue(); - assertThat(node.getValueReference()).isTrue(); - assertThat(node.newNode(null, null, null, 1, 1)).isInstanceOf(Interned.class); - assertThat(node.newReferenceKey(new Object(), null)).isInstanceOf(WeakKeyEqualsReference.class); - assertThat(node.isRetired()).isFalse(); - node.retire(); - assertThat(node.isRetired()).isTrue(); - node.die(); - assertThat(node.isDead()).isTrue(); - } - - private void checkSize(Interner interner, int size) { - if (interner instanceof StrongInterner) { - assertThat(((StrongInterner) interner).map).hasSize(size); - } else if (interner instanceof WeakInterner) { - var cache = new LocalManualCache() { - @Override - public LocalCache cache() { - return ((WeakInterner) interner).cache; - } - - @Override - public Policy policy() { - throw new UnsupportedOperationException(); - } - }; - assertThat(cache).whenCleanedUp().hasSize(size); - } else { - Assert.fail(); - } - } - - private void checkState(Interner interner) { - if (interner instanceof WeakInterner) { - assertAbout(mapLocal()).that(((WeakInterner) interner).cache).isValid(); - } - } - - @DataProvider(name = "interners") - Object[] providesInterners() { - return new Object[]{Interner.newStrongInterner(), Interner.newWeakInterner()}; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeSubject.java deleted file mode 100644 index d9bc19e..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeSubject.java +++ /dev/null @@ -1,63 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.testing.CollectionSubject; -import com.google.common.collect.Sets; -import com.google.common.truth.FailureMetadata; - -import java.util.Iterator; - -import static com.google.common.truth.Truth.assertAbout; - - -final class LinkedDequeSubject extends CollectionSubject { - private final LinkedDeque actual; - - @SuppressWarnings("unchecked") - private LinkedDequeSubject(FailureMetadata metadata, LinkedDeque subject) { - super(metadata, subject); - this.actual = (LinkedDeque) subject; - } - - public static Factory> deque() { - return LinkedDequeSubject::new; - } - - public static LinkedDequeSubject assertThat(LinkedDeque actual) { - return assertAbout(deque()).that(actual); - } - - public void isValid() { - if (actual.isEmpty()) { - isExhaustivelyEmpty(); - } - checkIterator(actual.iterator()); - checkIterator(actual.descendingIterator()); - } - - private void checkIterator(Iterator iterator) { - var seen = Sets.newIdentityHashSet(); - while (iterator.hasNext()) { - var element = iterator.next(); - checkElement(element); - check("loop").withMessage("Loop detected: %s in %s", element, seen) - .that(seen.add(element)).isTrue(); - } - hasSize(seen.size()); - } - - private void checkElement(Object element) { - var first = actual.peekFirst(); - var last = actual.peekLast(); - if (element == first) { - check("getPrevious(e)").that(actual.getPrevious(element)).isNull(); - } - if (element == last) { - check("getNext(e)").that(actual.getNext(element)).isNull(); - } - if ((element != first) && (element != last)) { - check("getPrevious(e)").that(actual.getPrevious(element)).isNotNull(); - check("getNext(e)").that(actual.getNext(element)).isNotNull(); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTest.java deleted file mode 100644 index a659707..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTest.java +++ /dev/null @@ -1,907 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import static com.github.benmanes.caffeine.testing.CollectionSubject.assertThat; -import static com.google.common.collect.Iterators.elementsEqual; -import static com.google.common.truth.Truth.assertThat; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Deque; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.concurrent.ThreadLocalRandom; - -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import com.github.benmanes.caffeine.cache.AccessOrderDeque.AccessOrder; -import com.github.benmanes.caffeine.cache.LinkedDeque.PeekingIterator; -import com.github.benmanes.caffeine.cache.WriteOrderDeque.WriteOrder; -import com.google.common.base.MoreObjects; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.collect.Iterators; - -public final class LinkedDequeTest { - static final int SIZE = 100; - - @Test(dataProvider = "empty") - public void clear_whenEmpty(Deque deque) { - deque.clear(); - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "full") - public void clear_whenPopulated(Deque deque) { - deque.clear(); - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "empty") - public void isEmpty_whenEmpty(Deque deque) { - assertThat(deque.isEmpty()).isTrue(); - } - - @Test(dataProvider = "full") - public void isEmpty_whenPopulated(Deque deque) { - assertThat(deque.isEmpty()).isFalse(); - } - - @Test(dataProvider = "empty") - public void size_whenEmpty(Deque deque) { - assertThat(deque.size()).isEqualTo(0); - } - - @Test(dataProvider = "full") - public void size_whenPopulated(Deque deque) { - assertThat(deque.size()).isEqualTo(SIZE); - } - - @Test(dataProvider = "empty") - public void contains_withNull(Deque deque) { - assertThat(deque.contains(null)).isFalse(); - } - - @Test(dataProvider = "full") - public void contains_whenFound(LinkedDeque deque) { - int index = ThreadLocalRandom.current().nextInt(deque.size()); - assertThat(deque.contains(Iterables.get(deque, index))).isTrue(); - } - - @Test(dataProvider = "full") - public void contains_whenNotFound(LinkedDeque deque) { - var unlinked = new LinkedValue(1); - assertThat(deque.contains(unlinked)).isFalse(); - } - - /* --------------- Move --------------- */ - - @Test(dataProvider = "full") - public void moveToFront_first(LinkedDeque deque) { - checkMoveToFront(deque, deque.getFirst()); - } - - @Test(dataProvider = "full") - public void moveToFront_middle(LinkedDeque deque) { - checkMoveToFront(deque, Iterables.get(deque, SIZE / 2)); - } - - @Test(dataProvider = "full") - public void moveToFront_last(LinkedDeque deque) { - checkMoveToFront(deque, deque.getLast()); - } - - private void checkMoveToFront(LinkedDeque deque, LinkedValue element) { - deque.moveToFront(element); - assertThat(deque).hasSize(SIZE); - assertThat(deque.peekFirst()).isEqualTo(element); - } - - @Test(dataProvider = "full") - public void moveToBack_first(LinkedDeque deque) { - checkMoveToBack(deque, deque.getFirst()); - } - - @Test(dataProvider = "full") - public void moveToBack_middle(LinkedDeque deque) { - checkMoveToBack(deque, Iterables.get(deque, SIZE / 2)); - } - - @Test(dataProvider = "full") - public void moveToBack_last(LinkedDeque deque) { - checkMoveToBack(deque, deque.getLast()); - } - - private void checkMoveToBack(LinkedDeque deque, LinkedValue element) { - deque.moveToBack(element); - assertThat(deque).hasSize(SIZE); - assertThat(deque.getLast()).isEqualTo(element); - } - - /* --------------- First / Last --------------- */ - - @Test(dataProvider = "empty") - public void isFirst_whenEmpty(LinkedDeque deque) { - assertThat(deque.isFirst(new LinkedValue(0))).isFalse(); - assertThat(deque.isFirst(null)).isFalse(); - } - - @Test(dataProvider = "full") - public void isFirst_whenPopulated(AbstractLinkedDeque deque) { - var first = deque.first; - assertThat(deque).hasSize(SIZE); - assertThat(deque.isFirst(first)).isTrue(); - assertThat(deque.contains(first)).isTrue(); - assertThat(deque.first).isSameInstanceAs(first); - } - - @Test(dataProvider = "empty") - public void isLast_whenEmpty(LinkedDeque deque) { - assertThat(deque.isLast(new LinkedValue(0))).isFalse(); - assertThat(deque.isLast(null)).isFalse(); - } - - @Test(dataProvider = "full") - public void isLast_whenPopulated(AbstractLinkedDeque deque) { - var last = deque.last; - assertThat(deque).hasSize(SIZE); - assertThat(deque.isLast(last)).isTrue(); - assertThat(deque.contains(last)).isTrue(); - assertThat(deque.last).isSameInstanceAs(last); - } - - /* --------------- Peek --------------- */ - - @Test(dataProvider = "empty") - public void peek_whenEmpty(LinkedDeque deque) { - assertThat(deque.peek()).isNull(); - } - - @Test(dataProvider = "full") - public void peek_whenPopulated(AbstractLinkedDeque deque) { - var first = deque.first; - assertThat(deque).hasSize(SIZE); - assertThat(deque.contains(first)).isTrue(); - assertThat(deque.first).isSameInstanceAs(first); - assertThat(deque.peek()).isSameInstanceAs(first); - } - - @Test(dataProvider = "empty") - public void peekFirst_whenEmpty(LinkedDeque deque) { - assertThat(deque.peekFirst()).isNull(); - } - - @Test(dataProvider = "full") - public void peekFirst_whenPopulated(AbstractLinkedDeque deque) { - var first = deque.first; - assertThat(deque).hasSize(SIZE); - assertThat(deque.contains(first)).isTrue(); - assertThat(deque.first).isSameInstanceAs(first); - assertThat(deque.peekFirst()).isSameInstanceAs(first); - } - - @Test(dataProvider = "empty") - public void peekLast_whenEmpty(LinkedDeque deque) { - assertThat(deque.peekLast()).isNull(); - } - - @Test(dataProvider = "full") - public void peekLast_whenPopulated(AbstractLinkedDeque deque) { - var last = deque.last; - assertThat(deque).hasSize(SIZE); - assertThat(deque.contains(last)).isTrue(); - assertThat(deque.last).isSameInstanceAs(last); - assertThat(deque.peekLast()).isSameInstanceAs(last); - } - - /* --------------- Get --------------- */ - - @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) - public void getFirst_whenEmpty(LinkedDeque deque) { - deque.getFirst(); - } - - @Test(dataProvider = "full") - public void getFirst_whenPopulated(AbstractLinkedDeque deque) { - var first = deque.first; - assertThat(deque).hasSize(SIZE); - assertThat(deque.contains(first)).isTrue(); - assertThat(deque.first).isSameInstanceAs(first); - assertThat(deque.getFirst()).isSameInstanceAs(first); - } - - @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) - public void getLast_whenEmpty(LinkedDeque deque) { - deque.getLast(); - } - - @Test(dataProvider = "full") - public void getLast_whenPopulated(AbstractLinkedDeque deque) { - var last = deque.last; - assertThat(deque).hasSize(SIZE); - assertThat(deque.contains(last)).isTrue(); - assertThat(deque.last).isSameInstanceAs(last); - assertThat(deque.getLast()).isSameInstanceAs(last); - } - - /* --------------- Element --------------- */ - - @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) - public void element_whenEmpty(LinkedDeque deque) { - deque.element(); - } - - @Test(dataProvider = "full") - public void element_whenPopulated(AbstractLinkedDeque deque) { - var first = deque.first; - assertThat(deque).hasSize(SIZE); - assertThat(deque.contains(first)).isTrue(); - assertThat(deque.first).isSameInstanceAs(first); - assertThat(deque.element()).isSameInstanceAs(first); - } - - /* --------------- Offer --------------- */ - - @Test(dataProvider = "empty") - public void offer_whenEmpty(LinkedDeque deque) { - var value = new LinkedValue(1); - assertThat(deque.offer(value)).isTrue(); - assertThat(deque.peekFirst()).isSameInstanceAs(value); - assertThat(deque.peekLast()).isSameInstanceAs(value); - assertThat(deque).hasSize(1); - } - - @Test(dataProvider = "full") - public void offer_whenPopulated(LinkedDeque deque) { - var value = new LinkedValue(SIZE); - assertThat(deque.offer(value)).isTrue(); - assertThat(deque.peekFirst()).isNotSameInstanceAs(value); - assertThat(deque.peekLast()).isSameInstanceAs(value); - assertThat(deque).hasSize(SIZE + 1); - } - - @Test(dataProvider = "full") - public void offer_whenLinked(LinkedDeque deque) { - assertThat(deque.offer(deque.peek())).isFalse(); - assertThat(deque).hasSize(SIZE); - } - - @Test(dataProvider = "empty") - public void offerFirst_whenEmpty(LinkedDeque deque) { - var value = new LinkedValue(1); - assertThat(deque.offerFirst(value)).isTrue(); - assertThat(deque.peekFirst()).isSameInstanceAs(value); - assertThat(deque.peekLast()).isSameInstanceAs(value); - assertThat(deque).hasSize(1); - } - - @Test(dataProvider = "full") - public void offerFirst_whenPopulated(LinkedDeque deque) { - var value = new LinkedValue(SIZE); - assertThat(deque.offerFirst(value)).isTrue(); - assertThat(deque.peekFirst()).isSameInstanceAs(value); - assertThat(deque.peekLast()).isNotSameInstanceAs(value); - assertThat(deque).hasSize(SIZE + 1); - } - - @Test(dataProvider = "full") - public void offerFirst_whenLinked(LinkedDeque deque) { - assertThat(deque.offerFirst(deque.peek())).isFalse(); - assertThat(deque).hasSize(SIZE); - } - - @Test(dataProvider = "empty") - public void offerLast_whenEmpty(LinkedDeque deque) { - var value = new LinkedValue(1); - assertThat(deque.offerLast(value)).isTrue(); - assertThat(deque.peekFirst()).isSameInstanceAs(value); - assertThat(deque.peekLast()).isSameInstanceAs(value); - assertThat(deque).hasSize(1); - } - - @Test(dataProvider = "full") - public void offerLast_whenPopulated(LinkedDeque deque) { - var value = new LinkedValue(SIZE); - assertThat(deque.offerLast(value)).isTrue(); - assertThat(deque.peekFirst()).isNotSameInstanceAs(value); - assertThat(deque.peekLast()).isSameInstanceAs(value); - assertThat(deque).hasSize(SIZE + 1); - } - - @Test(dataProvider = "full") - public void offerLast_whenLinked(LinkedDeque deque) { - assertThat(deque.offerLast(deque.peek())).isFalse(); - assertThat(deque).hasSize(SIZE); - } - - /* --------------- Add --------------- */ - - @Test(dataProvider = "empty") - public void add_whenEmpty(LinkedDeque deque) { - var value = new LinkedValue(1); - assertThat(deque.add(value)).isTrue(); - assertThat(deque.peekFirst()).isSameInstanceAs(value); - assertThat(deque.peekLast()).isSameInstanceAs(value); - assertThat(deque).hasSize(1); - } - - @Test(dataProvider = "full") - public void add_whenPopulated(LinkedDeque deque) { - var value = new LinkedValue(SIZE); - assertThat(deque.add(value)).isTrue(); - assertThat(deque.peekFirst()).isNotSameInstanceAs(value); - assertThat(deque.peekLast()).isSameInstanceAs(value); - assertThat(deque).hasSize(SIZE + 1); - } - - @Test(dataProvider = "full") - public void add_whenLinked(LinkedDeque deque) { - assertThat(deque.add(deque.peek())).isFalse(); - } - - @Test(dataProvider = "empty") - public void addFirst_whenEmpty(LinkedDeque deque) { - var value = new LinkedValue(1); - deque.addFirst(value); - assertThat(deque.peekFirst()).isSameInstanceAs(value); - assertThat(deque.peekLast()).isSameInstanceAs(value); - assertThat(deque).hasSize(1); - } - - @Test(dataProvider = "full") - public void addFirst_whenPopulated(LinkedDeque deque) { - var value = new LinkedValue(SIZE); - deque.addFirst(value); - assertThat(deque.peekFirst()).isSameInstanceAs(value); - assertThat(deque.peekLast()).isNotSameInstanceAs(value); - assertThat(deque).hasSize(SIZE + 1); - } - - @Test(dataProvider = "full", expectedExceptions = IllegalArgumentException.class) - public void addFirst_whenLinked(LinkedDeque deque) { - deque.addFirst(deque.peek()); - } - - @Test(dataProvider = "empty") - public void addLast_whenEmpty(LinkedDeque deque) { - var value = new LinkedValue(1); - deque.addLast(value); - assertThat(deque.peekFirst()).isSameInstanceAs(value); - assertThat(deque.peekLast()).isSameInstanceAs(value); - assertThat(deque).hasSize(1); - } - - @Test(dataProvider = "full") - public void addLast_whenPopulated(LinkedDeque deque) { - var value = new LinkedValue(SIZE); - deque.addLast(value); - assertThat(deque.peekFirst()).isNotSameInstanceAs(value); - assertThat(deque.peekLast()).isSameInstanceAs(value); - assertThat(deque).hasSize(SIZE + 1); - } - - @Test(dataProvider = "full", expectedExceptions = IllegalArgumentException.class) - public void addLast_whenLinked(LinkedDeque deque) { - deque.addLast(deque.peek()); - } - - @Test(dataProvider = "empty") - public void addAll_withEmpty(LinkedDeque deque) { - assertThat(deque.addAll(List.of())).isFalse(); - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "empty") - public void addAll_withPopulated(LinkedDeque deque) { - var expected = new ArrayList(); - populate(expected); - assertThat(deque.addAll(expected)).isTrue(); - assertThat(deque).containsExactlyElementsIn(expected).inOrder(); - } - - @Test(dataProvider = "full") - @SuppressWarnings("ModifyingCollectionWithItself") - public void addAll_withSelf(LinkedDeque deque) { - assertThat(deque.addAll(deque)).isFalse(); - } - - /* --------------- Poll --------------- */ - - @Test(dataProvider = "empty") - public void poll_whenEmpty(LinkedDeque deque) { - assertThat(deque.poll()).isNull(); - } - - @Test(dataProvider = "full") - public void poll_whenPopulated(LinkedDeque deque) { - var first = deque.peek(); - assertThat(deque.poll()).isSameInstanceAs(first); - assertThat(deque.contains(first)).isFalse(); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void poll_toEmpty(LinkedDeque deque) { - LinkedValue value; - while ((value = deque.poll()) != null) { - assertThat(deque.contains(value)).isFalse(); - } - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "empty") - public void pollFirst_whenEmpty(LinkedDeque deque) { - assertThat(deque.pollFirst()).isNull(); - } - - @Test(dataProvider = "full") - public void pollFirst_whenPopulated(LinkedDeque deque) { - var first = deque.peekFirst(); - assertThat(deque.pollFirst()).isSameInstanceAs(first); - assertThat(deque.contains(first)).isFalse(); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void pollFirst_toEmpty(LinkedDeque deque) { - LinkedValue value; - while ((value = deque.pollFirst()) != null) { - assertThat(deque.contains(value)).isFalse(); - } - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "empty") - public void pollLast_whenEmpty(LinkedDeque deque) { - assertThat(deque.pollLast()).isNull(); - } - - @Test(dataProvider = "full") - public void pollLast_whenPopulated(LinkedDeque deque) { - var last = deque.peekLast(); - assertThat(deque.pollLast()).isSameInstanceAs(last); - assertThat(deque.contains(last)).isFalse(); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void pollLast_toEmpty(LinkedDeque deque) { - LinkedValue value; - while ((value = deque.pollLast()) != null) { - assertThat(deque.contains(value)).isFalse(); - } - assertThat(deque).isExhaustivelyEmpty(); - } - - /* --------------- Remove --------------- */ - - @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) - public void remove_whenEmpty(LinkedDeque deque) { - deque.remove(); - } - - @Test(dataProvider = "full") - public void remove_whenPopulated(LinkedDeque deque) { - var first = deque.peekFirst(); - assertThat(deque.remove()).isSameInstanceAs(first); - assertThat(deque.contains(first)).isFalse(); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void remove_toEmpty(LinkedDeque deque) { - while (!deque.isEmpty()) { - var value = deque.remove(); - assertThat(deque.contains(value)).isFalse(); - } - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "empty") - public void removeElement_notFound(LinkedDeque deque) { - assertThat(deque.remove(new LinkedValue(0))).isFalse(); - } - - @Test(dataProvider = "full") - public void removeElement_whenFound(LinkedDeque deque) { - var first = deque.peekFirst(); - assertThat(deque.remove(first)).isTrue(); - assertThat(deque.contains(first)).isFalse(); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void removeElement_toEmpty(LinkedDeque deque) { - while (!deque.isEmpty()) { - var value = deque.peek(); - assertThat(deque.remove(value)).isTrue(); - assertThat(deque.contains(value)).isFalse(); - } - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) - public void removeFirst_whenEmpty(LinkedDeque deque) { - deque.removeFirst(); - } - - @Test(dataProvider = "full") - public void removeFirst_whenPopulated(LinkedDeque deque) { - var first = deque.peekFirst(); - assertThat(deque.removeFirst()).isSameInstanceAs(first); - assertThat(deque.contains(first)).isFalse(); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void removeFirst_toEmpty(LinkedDeque deque) { - while (!deque.isEmpty()) { - var value = deque.removeFirst(); - assertThat(deque.contains(value)).isFalse(); - } - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) - public void removeLast_whenEmpty(LinkedDeque deque) { - deque.removeLast(); - } - - @Test(dataProvider = "full") - public void removeLast_whenPopulated(LinkedDeque deque) { - var last = deque.peekLast(); - assertThat(deque.removeLast()).isSameInstanceAs(last); - assertThat(deque.contains(last)).isFalse(); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void removeLast_toEmpty(LinkedDeque deque) { - while (!deque.isEmpty()) { - var value = deque.removeLast(); - assertThat(deque.contains(value)).isFalse(); - } - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "empty") - public void removeFirstOccurrence_notFound(LinkedDeque deque) { - assertThat(deque.removeFirstOccurrence(new LinkedValue(0))).isFalse(); - } - - @Test(dataProvider = "full") - public void removeFirstOccurrence_whenFound(LinkedDeque deque) { - var first = deque.peekFirst(); - assertThat(deque.removeFirstOccurrence(first)).isTrue(); - assertThat(deque.contains(first)).isFalse(); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void removeFirstOccurrence_toEmpty(LinkedDeque deque) { - while (!deque.isEmpty()) { - var value = deque.peek(); - assertThat(deque.removeFirstOccurrence(value)).isTrue(); - assertThat(deque.contains(value)).isFalse(); - } - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "empty") - public void removeLastOccurrence_notFound(LinkedDeque deque) { - assertThat(deque.removeLastOccurrence(new LinkedValue(0))).isFalse(); - } - - @Test(dataProvider = "full") - public void removeLastOccurrence_whenFound(LinkedDeque deque) { - var first = deque.peekFirst(); - assertThat(deque.removeLastOccurrence(first)).isTrue(); - assertThat(deque.contains(first)).isFalse(); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void removeLastOccurrence_toEmpty(LinkedDeque deque) { - while (!deque.isEmpty()) { - var value = deque.peek(); - assertThat(deque.removeLastOccurrence(value)).isTrue(); - assertThat(deque.contains(value)).isFalse(); - } - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "empty") - public void removeAll_withEmpty(LinkedDeque deque) { - assertThat(deque.removeAll(List.of())).isFalse(); - assertThat(deque).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "full") - public void remove_withPopulated(LinkedDeque deque) { - var first = deque.peekFirst(); - assertThat(deque.removeAll(List.of(first))).isTrue(); - assertThat(deque.contains(first)).isFalse(); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void removeAll_toEmpty(LinkedDeque deque) { - assertThat(deque.removeAll(List.copyOf(deque))).isTrue(); - assertThat(deque).isExhaustivelyEmpty(); - } - - /* --------------- Stack --------------- */ - - @Test(dataProvider = "empty") - public void push_whenEmpty(LinkedDeque deque) { - var value = new LinkedValue(1); - deque.push(value); - assertThat(deque.peekFirst()).isSameInstanceAs(value); - assertThat(deque.peekLast()).isSameInstanceAs(value); - assertThat(deque).hasSize(1); - } - - @Test(dataProvider = "full") - public void push_whenPopulated(LinkedDeque deque) { - var value = new LinkedValue(SIZE); - deque.push(value); - assertThat(deque.peekFirst()).isSameInstanceAs(value); - assertThat(deque.peekLast()).isNotSameInstanceAs(value); - assertThat(deque).hasSize(SIZE + 1); - } - - @Test(dataProvider = "full", expectedExceptions = IllegalArgumentException.class) - public void push_whenLinked(LinkedDeque deque) { - deque.push(deque.peek()); - } - - @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) - public void pop_whenEmpty(LinkedDeque deque) { - deque.pop(); - } - - @Test(dataProvider = "full") - public void pop_whenPopulated(LinkedDeque deque) { - var first = deque.peekFirst(); - assertThat(deque.pop()).isSameInstanceAs(first); - assertThat(deque.contains(first)).isFalse(); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void pop_toEmpty(LinkedDeque deque) { - while (!deque.isEmpty()) { - var value = deque.pop(); - assertThat(deque.contains(value)).isFalse(); - } - assertThat(deque).isExhaustivelyEmpty(); - } - - /* --------------- Iterators --------------- */ - - @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) - public void iterator_noMoreElements(LinkedDeque deque) { - deque.iterator().next(); - } - - @Test(dataProvider = "empty") - public void iterator_whenEmpty(LinkedDeque deque) { - assertThat(deque.iterator().hasNext()).isFalse(); - assertThat(deque.iterator().peek()).isNull(); - } - - @Test(dataProvider = "full") - public void iterator_whenWarmed(LinkedDeque deque) { - var expected = new ArrayList(); - populate(expected); - - assertThat(deque.peek()).isNotNull(); - assertThat(Iterators.size(deque.iterator())).isEqualTo(deque.size()); - assertThat(elementsEqual(deque.iterator(), expected.iterator())).isTrue(); - } - - @Test(dataProvider = "full") - public void iterator_removal(LinkedDeque deque) { - var iterator = deque.iterator(); - var value = iterator.next(); - iterator.remove(); - - int remaining = 0; - while (iterator.hasNext()) { - assertThat(iterator.next()).isNotSameInstanceAs(value); - remaining++; - } - assertThat(remaining).isEqualTo(SIZE - 1); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full", expectedExceptions = IllegalStateException.class) - public void iterator_removal_exception(LinkedDeque deque) { - var iterator = deque.iterator(); - iterator.next(); - iterator.remove(); - iterator.remove(); - } - - @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) - public void descendingIterator_noMoreElements(LinkedDeque deque) { - deque.descendingIterator().next(); - } - - @Test(dataProvider = "empty") - public void descendingIterator_whenEmpty(LinkedDeque deque) { - assertThat(deque.descendingIterator().hasNext()).isFalse(); - assertThat(deque.descendingIterator().peek()).isNull(); - } - - @Test(dataProvider = "full") - public void descendingIterator_whenWarmed(LinkedDeque deque) { - var expected = new ArrayList(); - populate(expected); - Collections.reverse(expected); - - assertThat(deque.descendingIterator().peek()).isNotNull(); - assertThat(elementsEqual(deque.descendingIterator(), expected.iterator())).isTrue(); - } - - @Test(dataProvider = "full") - public void descendingIterator_removal(LinkedDeque deque) { - var iterator = deque.descendingIterator(); - var value = iterator.next(); - iterator.remove(); - - int remaining = 0; - while (iterator.hasNext()) { - assertThat(iterator.next()).isNotEqualTo(value); - remaining++; - } - assertThat(remaining).isEqualTo(SIZE - 1); - assertThat(deque).hasSize(SIZE - 1); - } - - @Test(dataProvider = "full") - public void concat(LinkedDeque deque) { - var expect = ImmutableList.copyOf( - Iterators.concat(deque.iterator(), deque.descendingIterator())); - Iterable actual = () -> PeekingIterator.concat( - deque.iterator(), deque.descendingIterator()); - assertThat(actual).containsExactlyElementsIn(expect).inOrder(); - } - - @Test(dataProvider = "full") - public void concat_peek(LinkedDeque deque) { - var iterator = PeekingIterator.concat(deque.iterator(), deque.iterator()); - while (iterator.hasNext()) { - var expected = iterator.peek(); - assertThat(iterator.next()).isEqualTo(expected); - } - assertThat(iterator.peek()).isNull(); - } - - @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class) - public void concat_noMoreElements(LinkedDeque deque) { - PeekingIterator.concat(deque.iterator(), deque.iterator()).next(); - } - - @Test(dataProvider = "full") - public void comparing(LinkedDeque deque) { - var expect = ImmutableList.copyOf( - Iterators.concat(deque.iterator(), deque.descendingIterator())); - var actual = PeekingIterator.comparing( - deque.iterator(), deque.descendingIterator(), comparator().reversed()); - assertThat(actual.peek()).isEqualTo(expect.get(0)); - assertThat(ImmutableList.copyOf(actual)).containsExactlyElementsIn(expect).inOrder(); - } - - @Test(dataProvider = "full") - public void comparing_uneven(LinkedDeque deque) { - var empty = new AccessOrderDeque().iterator(); - var left = PeekingIterator.comparing(deque.iterator(), empty, comparator().reversed()); - var right = PeekingIterator.comparing(empty, deque.iterator(), comparator().reversed()); - - assertThat(left.peek()).isEqualTo(deque.getFirst()); - assertThat(right.peek()).isEqualTo(deque.getFirst()); - } - - private static Comparator comparator() { - return Comparator.comparingInt((LinkedValue v) -> v.value); - } - - /* --------------- Deque providers --------------- */ - - @DataProvider(name = "empty") - public Object[][] providesEmptyDeque() { - return new Object[][]{ - {new AccessOrderDeque()}, - {new WriteOrderDeque()}, - }; - } - - @DataProvider(name = "full") - public Object[][] providesWarmedDeque() { - var accessOrder = new AccessOrderDeque(); - var writeOrder = new WriteOrderDeque(); - populate(accessOrder); - populate(writeOrder); - return new Object[][]{{accessOrder}, {writeOrder}}; - } - - void populate(Collection collection) { - for (int i = 0; i < SIZE; i++) { - collection.add(new LinkedValue(i)); - } - } - - static final class LinkedValue implements AccessOrder, WriteOrder { - final int value; - - LinkedValue prev; - LinkedValue next; - - LinkedValue(int value) { - this.value = value; - } - - @Override - public LinkedValue getPreviousInAccessOrder() { - return prev; - } - - @Override - public void setPreviousInAccessOrder(LinkedValue prev) { - this.prev = prev; - } - - @Override - public LinkedValue getNextInAccessOrder() { - return next; - } - - @Override - public void setNextInAccessOrder(LinkedValue next) { - this.next = next; - } - - @Override - public LinkedValue getPreviousInWriteOrder() { - return prev; - } - - @Override - public void setPreviousInWriteOrder(LinkedValue prev) { - this.prev = prev; - } - - @Override - public LinkedValue getNextInWriteOrder() { - return next; - } - - @Override - public void setNextInWriteOrder(LinkedValue next) { - this.next = next; - } - - @Override - public boolean equals(Object o) { - return (o instanceof LinkedValue) && (value == ((LinkedValue) o).value); - } - - @Override - public int hashCode() { - return value; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("value", value) - .add("prev", (prev == null) ? null : prev.value) - .add("next", (next == null) ? null : next.value) - .toString(); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTests.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTests.java deleted file mode 100644 index acf6b70..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LinkedDequeTests.java +++ /dev/null @@ -1,156 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.AccessOrderDeque.AccessOrder; -import com.github.benmanes.caffeine.cache.WriteOrderDeque.WriteOrder; -import com.google.common.base.MoreObjects; -import com.google.common.collect.testing.MinimalCollection; -import com.google.common.collect.testing.QueueTestSuiteBuilder; -import com.google.common.collect.testing.SampleElements; -import com.google.common.collect.testing.TestQueueGenerator; -import com.google.common.collect.testing.features.CollectionFeature; -import com.google.common.collect.testing.features.CollectionSize; -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -import java.util.ArrayDeque; -import java.util.List; -import java.util.Queue; -import java.util.function.Supplier; - -public final class LinkedDequeTests extends TestCase { - static final LinkedValue a = new LinkedValue("a"); - static final LinkedValue b = new LinkedValue("b"); - static final LinkedValue c = new LinkedValue("c"); - static final LinkedValue d = new LinkedValue("d"); - static final LinkedValue e = new LinkedValue("e"); - static final ThreadLocal useTarget = ThreadLocal.withInitial(() -> false); - - public static Test suite() { - var suite = new TestSuite(); - suite.addTest(suite("AccessOrderDeque", AccessOrderDeque::new)); - suite.addTest(suite("WriteOrderDeque", WriteOrderDeque::new)); - return suite; - } - - static Test suite(String name, Supplier> supplier) { - return QueueTestSuiteBuilder.using(new TestLinkedValueGenerator() { - @Override - public Queue create(LinkedValue[] elements) { - var deque = useTarget.get() ? supplier.get() : new ArrayDeque(); - deque.addAll(MinimalCollection.of(elements)); - useTarget.set(false); - return deque; - } - }).named(name).withFeatures( - CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION, - CollectionFeature.ALLOWS_NULL_QUERIES, - CollectionFeature.GENERAL_PURPOSE, - CollectionFeature.KNOWN_ORDER, - CollectionSize.ANY) - .withSetUp(() -> useTarget.set(true)) - .withTearDown(() -> List.of(a, b, c, d, e).forEach(value -> { - value.setNextInAccessOrder(null); - value.setPreviousInAccessOrder(null); - })).createTestSuite(); - } - - abstract static class TestLinkedValueGenerator implements TestQueueGenerator { - @Override - public SampleElements samples() { - return new SampleElements<>(b, a, c, d, e); - } - - @Override - public Queue create(Object... elements) { - var array = new LinkedValue[elements.length]; - int i = 0; - for (Object e : elements) { - array[i++] = (LinkedValue) e; - } - return create(array); - } - - protected abstract Queue create(LinkedValue[] elements); - - @Override - public LinkedValue[] createArray(int length) { - return new LinkedValue[length]; - } - - @Override - public List order(List insertionOrder) { - return insertionOrder; - } - } - - static final class LinkedValue implements AccessOrder, WriteOrder { - final String value; - LinkedValue prev; - LinkedValue next; - - LinkedValue(String value) { - this.value = value; - } - - @Override - public LinkedValue getPreviousInAccessOrder() { - return prev; - } - - @Override - public void setPreviousInAccessOrder(LinkedValue prev) { - this.prev = prev; - } - - @Override - public LinkedValue getNextInAccessOrder() { - return next; - } - - @Override - public void setNextInAccessOrder(LinkedValue next) { - this.next = next; - } - - @Override - public LinkedValue getPreviousInWriteOrder() { - return prev; - } - - @Override - public void setPreviousInWriteOrder(LinkedValue prev) { - this.prev = prev; - } - - @Override - public LinkedValue getNextInWriteOrder() { - return next; - } - - @Override - public void setNextInWriteOrder(LinkedValue next) { - this.next = next; - } - - @Override - public boolean equals(Object o) { - return (o instanceof LinkedValue) && value.equals(((LinkedValue) o).value); - } - - @Override - public int hashCode() { - return value.hashCode(); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("value", value) - .add("prev", (prev == null) ? null : prev.value) - .add("next", (next == null) ? null : next.value) - .toString(); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LoadingCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LoadingCacheTest.java deleted file mode 100644 index d017988..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LoadingCacheTest.java +++ /dev/null @@ -1,1017 +0,0 @@ -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.Int; -import com.github.valfirst.slf4jtest.TestLoggerFactory; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.primitives.Ints; -import org.testng.Assert; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.IntStream; - -import static com.github.benmanes.caffeine.cache.RemovalCause.*; -import static com.github.benmanes.caffeine.cache.testing.CacheContext.intern; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.CollectionSubject.assertThat; -import static com.github.benmanes.caffeine.testing.FutureSubject.assertThat; -import static com.github.benmanes.caffeine.testing.IntSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.truth.Truth.assertThat; -import static java.util.function.Function.identity; -import static uk.org.lidalia.slf4jext.Level.TRACE; -import static uk.org.lidalia.slf4jext.Level.WARN; - -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@SuppressWarnings("FutureReturnValueIgnored") -@Test(dataProviderClass = CacheProvider.class) -public final class LoadingCacheTest { - @CacheSpec - @CheckNoEvictions - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void get_null(LoadingCache cache, CacheContext context) { - cache.get(null); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.NULL) - public void get_absent_null(LoadingCache cache, CacheContext context) { - assertThat(cache.get(context.absentKey())).isNull(); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - - @CheckNoEvictions - @CacheSpec(loader = Loader.EXCEPTIONAL) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void get_absent_throwsException(LoadingCache cache, CacheContext context) { - try { - cache.get(context.absentKey()); - } finally { - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.CHECKED_EXCEPTIONAL) - public void get_absent_throwsCheckedException(LoadingCache cache, CacheContext context) { - try { - cache.get(context.absentKey()); - Assert.fail(); - } catch (CompletionException e) { - assertThat(e).hasCauseThat().isInstanceOf(ExecutionException.class); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(compute = Compute.SYNC, loader = Loader.INTERRUPTED) - public void get_absent_interrupted(LoadingCache cache, CacheContext context) { - try { - cache.get(context.absentKey()); - Assert.fail(); - } catch (CompletionException e) { - assertThat(Thread.interrupted()).isTrue(); - assertThat(e).hasCauseThat().isInstanceOf(InterruptedException.class); - assertThat(context).stats().hits(0).misses(1).success(0).failures(1); - } - } - - @CacheSpec - @CheckNoEvictions - @Test(dataProvider = "caches") - public void get_absent(LoadingCache cache, CacheContext context) { - Int key = context.absentKey(); - assertThat(cache.get(key)).isEqualTo(key.negate()); - assertThat(context).stats().hits(0).misses(1).success(1).failures(0); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void get_present(LoadingCache cache, CacheContext context) { - assertThat(cache.get(context.firstKey())).isEqualTo(context.firstKey().negate()); - assertThat(cache.get(context.middleKey())).isEqualTo(context.middleKey().negate()); - assertThat(cache.get(context.lastKey())).isEqualTo(context.lastKey().negate()); - assertThat(context).stats().hits(3).misses(0).success(0).failures(0); - } - - @CheckNoEvictions - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getAll_iterable_null(LoadingCache cache, CacheContext context) { - cache.getAll(null); - } - - @CheckNoEvictions - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void getAll_iterable_nullKey(LoadingCache cache, CacheContext context) { - cache.getAll(Collections.singletonList(null)); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_iterable_empty(LoadingCache cache, CacheContext context) { - assertThat(cache.getAll(List.of())).isExhaustivelyEmpty(); - assertThat(context).stats().hits(0).misses(0); - } - - @CheckNoEvictions - @CacheSpec(loader = Loader.BULK_MODIFY_KEYS) - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void getAll_immutable_keys(LoadingCache cache, CacheContext context) { - cache.getAll(context.absentKeys()); - } - - @CacheSpec - @CheckNoEvictions - @Test(dataProvider = "caches", expectedExceptions = UnsupportedOperationException.class) - public void getAll_immutable_result(LoadingCache cache, CacheContext context) { - cache.getAll(context.absentKeys()).clear(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.NULL) - public void getAll_absent_null(LoadingCache cache, CacheContext context) { - assertThat(cache.getAll(context.absentKeys())).isExhaustivelyEmpty(); - } - - @CheckNoEvictions - @CacheSpec(loader = Loader.BULK_NULL) - @Test(dataProvider = "caches", expectedExceptions = Exception.class) - public void getAll_absent_bulkNull(LoadingCache cache, CacheContext context) { - cache.getAll(context.absentKeys()); - } - - @CheckNoEvictions - @CacheSpec(loader = {Loader.EXCEPTIONAL, Loader.BULK_EXCEPTIONAL}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void getAll_absent_throwsExecption(LoadingCache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys()); - } finally { - int misses = context.absentKeys().size(); - int loadFailures = (context.loader().isBulk() || context.isSync()) ? 1 : misses; - assertThat(context).stats().hits(0).misses(misses).success(0).failures(loadFailures); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.CHECKED_EXCEPTIONAL, Loader.BULK_CHECKED_EXCEPTIONAL}) - public void getAll_absent_throwsCheckedExecption(LoadingCache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys()); - Assert.fail(); - } catch (CompletionException e) { - assertThat(e).hasCauseThat().isInstanceOf(ExecutionException.class); - int misses = context.absentKeys().size(); - int loadFailures = (context.loader().isBulk() || context.isSync()) ? 1 : misses; - assertThat(context).stats().hits(0).misses(misses).success(0).failures(loadFailures); - } - } - - @CheckNoEvictions - @CacheSpec(loader = {Loader.EXCEPTIONAL, Loader.BULK_EXCEPTIONAL}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void getAll_absent_throwsExecption_iterable(LoadingCache cache, CacheContext context) { - try { - cache.getAll(() -> context.absentKeys().iterator()); - } finally { - int misses = context.absentKeys().size(); - int loadFailures = (context.loader().isBulk() || context.isSync()) ? 1 : misses; - assertThat(context).stats().hits(0).misses(misses).success(0).failures(loadFailures); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.INTERRUPTED, Loader.BULK_INTERRUPTED}) - public void getAll_absent_interrupted(LoadingCache cache, CacheContext context) { - try { - cache.getAll(context.absentKeys()); - Assert.fail(); - } catch (CompletionException e) { - if (context.isSync()) { - assertThat(Thread.interrupted()).isTrue(); - } - assertThat(e).hasCauseThat().isInstanceOf(InterruptedException.class); - int misses = context.absentKeys().size(); - int loadFailures = (context.loader().isBulk() || context.isSync()) ? 1 : misses; - assertThat(context).stats().hits(0).misses(misses).success(0).failures(loadFailures); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_absent(LoadingCache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys()); - int count = context.absentKeys().size(); - int loads = context.loader().isBulk() ? 1 : count; - assertThat(result).hasSize(count); - assertThat(context).stats().hits(0).misses(count).success(loads).failures(0); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_partial(LoadingCache cache, CacheContext context) { - var expect = new HashMap(); - expect.put(context.firstKey(), context.firstKey().negate()); - expect.put(context.middleKey(), context.middleKey().negate()); - expect.put(context.lastKey(), context.lastKey().negate()); - var result = cache.getAll(expect.keySet()); - assertThat(result).containsExactlyEntriesIn(expect); - assertThat(context).stats().hits(expect.size()).misses(0).success(0).failures(0); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_full(LoadingCache cache, CacheContext context) { - var result = cache.getAll(context.original().keySet()); - assertThat(result).containsExactlyEntriesIn(context.original()); - assertThat(context).stats().hits(result.size()).misses(0).success(0).failures(0); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.BULK_NEGATIVE_EXCEEDS, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_exceeds(LoadingCache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys()); - assertThat(result.keySet()).containsExactlyElementsIn(context.absentKeys()); - assertThat(cache).hasSizeGreaterThan(context.initialSize() + context.absentKeys().size()); - assertThat(context).stats().hits(0).misses(result.size()).success(1).failures(0); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.BULK_DIFFERENT, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_different(LoadingCache cache, CacheContext context) { - var result = cache.getAll(context.absentKeys()); - assertThat(result).isEmpty(); - assertThat(cache.asMap()).containsAtLeastEntriesIn(result); - assertThat(context).stats().hits(0).misses(context.absent().size()).success(1).failures(0); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_duplicates(LoadingCache cache, CacheContext context) { - var absentKeys = ImmutableSet.copyOf(Iterables.limit(context.absentKeys(), Ints.saturatedCast(context.maximum().max() - context.initialSize()))); - var keys = Iterables.concat(absentKeys, absentKeys, context.original().keySet(), context.original().keySet()); - var result = cache.getAll(keys); - assertThat(result).containsExactlyKeys(keys); - int loads = context.loader().isBulk() ? 1 : absentKeys.size(); - assertThat(context).stats().hits(context.initialSize()).misses(absentKeys.size()).success(loads).failures(0); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_ordered_absent(LoadingCache cache, CacheContext context) { - var keys = new ArrayList<>(context.absentKeys()); - Collections.shuffle(keys); - assertThat(cache.getAll(keys).keySet()).containsExactlyElementsIn(keys).inOrder(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, population = {Population.SINGLETON, Population.PARTIAL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_ordered_partial(LoadingCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - keys.addAll(context.absentKeys()); - Collections.shuffle(keys); - assertThat(cache.getAll(keys).keySet()).containsExactlyElementsIn(keys).inOrder(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_ordered_present(LoadingCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - Collections.shuffle(keys); - assertThat(cache.getAll(keys).keySet()).containsExactlyElementsIn(keys).inOrder(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.BULK_NEGATIVE_EXCEEDS, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void getAll_present_ordered_exceeds(LoadingCache cache, CacheContext context) { - var keys = new ArrayList<>(context.original().keySet()); - keys.addAll(context.absentKeys()); - Collections.shuffle(keys); - var result = new ArrayList<>(cache.getAll(keys).keySet()); - assertThat(result.subList(0, keys.size())).containsExactlyElementsIn(keys).inOrder(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY) - public void getAll_jdk8186171(CacheContext context) { - class Key { - @Override - public int hashCode() { - return 0; - } - } - LoadingCache cache = context.build(key -> null); - var keys = intern(new ArrayList()); - IntStream.iterate(0, i -> i < Population.FULL.size(), i -> i + 1).mapToObj(i -> new Key()).forEach(keys::add); - Key key = Iterables.getLast(keys); - Int value = context.absentValue(); - cache.put(key, value); - - var result = cache.getAll(keys); - assertThat(result).containsExactly(key, value); - assertThat(result.values()).doesNotContain(null); - } - - @CheckNoEvictions - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void refresh_null(LoadingCache cache, CacheContext context) { - cache.refresh(null); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.ASYNC_INCOMPLETE) - public void refresh_dedupe(LoadingCache cache, CacheContext context) { - var key = context.original().isEmpty() ? context.absentKey() : context.firstKey(); - var future1 = cache.refresh(key); - var future2 = cache.refresh(key); - assertThat(future1).isSameInstanceAs(future2); - future1.complete(context.absentValue()); - assertThat(cache).containsEntry(key, context.absentValue()); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.NULL, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void refresh_remove(LoadingCache cache, CacheContext context) { - var future = cache.refresh(context.firstKey()); - assertThat(future).succeedsWithNull(); - assertThat(cache).hasSize(context.initialSize() - 1); - assertThat(cache).doesNotContainKey(context.firstKey()); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(context.firstKey(), context.original().get(context.firstKey())).exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.NULL, population = Population.EMPTY) - public void refresh_ignored(LoadingCache cache, CacheContext context) { - var future = cache.refresh(context.absentKey()); - assertThat(future).succeedsWithNull(); - assertThat(cache).isEmpty(); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, loader = Loader.EXCEPTIONAL, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void refresh_failure(LoadingCache cache, CacheContext context) { - var future1 = cache.refresh(context.absentKey()); - var future2 = cache.refresh(context.firstKey()); - var future3 = cache.refresh(context.lastKey()); - assertThat(future2).isNotSameInstanceAs(future3); - assertThat(future1).hasCompletedExceptionally(); - assertThat(future2).hasCompletedExceptionally(); - assertThat(future3).hasCompletedExceptionally(); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).stats().success(0).failures(3); - } - - @CheckNoEvictions - @CheckNoStats - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.REFRESH_EXCEPTIONAL) - public void refresh_throwsException(LoadingCache cache, CacheContext context) { - cache.refresh(context.absentKey()); - } - - @CheckNoEvictions - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.REFRESH_CHECKED_EXCEPTIONAL) - public void refresh_throwsCheckedException(LoadingCache cache, CacheContext context) { - try { - cache.refresh(context.absentKey()); - Assert.fail(); - } catch (CompletionException e) { - assertThat(e).hasCauseThat().isInstanceOf(ExecutionException.class); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.REFRESH_INTERRUPTED) - public void refresh_interrupted(LoadingCache cache, CacheContext context) { - try { - cache.refresh(context.absentKey()); - Assert.fail(); - } catch (CompletionException e) { - assertThat(Thread.interrupted()).isTrue(); - assertThat(e).hasCauseThat().isInstanceOf(InterruptedException.class); - int failures = context.isGuava() ? 1 : 0; - assertThat(context).stats().hits(0).misses(0).success(0).failures(failures); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.ASYNC_INCOMPLETE, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void refresh_cancel(LoadingCache cache, CacheContext context) { - var key = context.original().isEmpty() ? context.absentKey() : context.firstKey(); - var future1 = cache.refresh(key); - assertThat(future1).isNotDone(); - future1.cancel(true); - var future2 = cache.refresh(key); - assertThat(future1).isNotSameInstanceAs(future2); - future2.cancel(false); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.NULL) - public void refresh_absent_null(LoadingCache cache, CacheContext context) { - var future = cache.refresh(context.absentKey()); - assertThat(future).succeedsWithNull(); - assertThat(cache).hasSize(context.initialSize()); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.UNREACHABLE, removalListener = {Listener.DISABLED, Listener.REJECTING}, population = Population.SINGLETON) - public void refresh_absent(LoadingCache cache, CacheContext context) { - Int key = context.absentKey(); - var future = cache.refresh(key); - assertThat(future).succeedsWith(key.negate()); - assertThat(cache).hasSize(1 + context.initialSize()); - assertThat(cache).containsEntry(context.absentKey(), key.negate()); - assertThat(context).stats().hits(0).misses(0).success(1).failures(0); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.NULL, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void refresh_present_null(LoadingCache cache, CacheContext context) { - var removed = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var future = cache.refresh(key); - assertThat(future).succeedsWithNull(); - removed.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(0).failures(count); - context.firstMiddleLastKeys().forEach(key -> assertThat(cache).doesNotContainKey(key)); - assertThat(cache).hasSize(context.initialSize() - count); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(removed).exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void refresh_present_sameValue(LoadingCache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var future = cache.refresh(key); - assertThat(future).succeedsWith(context.original().get(key)); - replaced.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - context.firstMiddleLastKeys().forEach(key -> assertThat(cache).containsEntry(key, context.original().get(key))); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, loader = Loader.IDENTITY) - public void refresh_present_sameInstance(LoadingCache cache, CacheContext context) { - cache.put(context.absentKey(), context.absentKey()); - var future = cache.refresh(context.absentKey()); - assertThat(cache).hasSize(1); - assertThat(future).succeedsWith(context.absentKey()); - assertThat(context).stats().hits(0).misses(0).success(1).failures(0); - assertThat(cache).containsEntry(context.absentKey(), context.absentKey()); - if (context.isGuava()) { - assertThat(context).removalNotifications().withCause(REPLACED).contains(context.absentKey(), context.absentKey()).exclusively(); - } else { - assertThat(context).removalNotifications().isEmpty(); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.IDENTITY, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void refresh_present_differentValue(LoadingCache cache, CacheContext context) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - var future = cache.refresh(key); - assertThat(future).succeedsWith(key); - assertThat(cache).containsEntry(key, key); - replaced.put(key, context.original().get(key)); - }); - int count = context.firstMiddleLastKeys().size(); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).stats().hits(0).misses(0).success(count).failures(0); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, executor = CacheExecutor.THREADED, removalListener = Listener.CONSUMING) - public void refresh_conflict(CacheContext context) { - var refresh = new AtomicBoolean(); - Int key = context.absentKey(); - Int original = Int.valueOf(1); - Int updated = Int.valueOf(2); - Int refreshed = Int.valueOf(3); - LoadingCache cache = context.build(k -> { - await().untilTrue(refresh); - return refreshed; - }); - cache.put(key, original); - var future = cache.refresh(key); - assertThat(cache.asMap().put(key, updated)).isEqualTo(original); - refresh.set(true); - if (context.isGuava()) { - future.join(); - } else { - assertThat(future).succeedsWith(refreshed); - } - await().untilAsserted(() -> assertThat(context).removalNotifications().hasSize(2)); - assertThat(cache).containsEntry(key, updated); - assertThat(context).stats().success(1).failures(0); - assertThat(context).removalNotifications().withCause(REPLACED).contains(Map.entry(key, original), Map.entry(key, refreshed)).exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, executor = CacheExecutor.THREADED, removalListener = Listener.CONSUMING) - public void refresh_put(CacheContext context) { - var started = new AtomicBoolean(); - var refresh = new AtomicBoolean(); - Int key = context.absentKey(); - Int original = Int.valueOf(1); - Int refreshed = Int.valueOf(2); - Int updated = Int.valueOf(3); - LoadingCache cache = context.build(k -> { - started.set(true); - await().untilTrue(refresh); - return refreshed; - }); - cache.put(key, original); - assertThat(started.get()).isFalse(); - var future = cache.refresh(key); - await().untilTrue(started); - cache.put(key, updated); - refresh.set(true); - if (context.isGuava()) { - future.join(); - } else { - assertThat(future).succeedsWith(refreshed); - } - await().untilAsserted(() -> assertThat(context).removalNotifications().withCause(REPLACED).contains(key, refreshed)); - assertThat(context).stats().success(1).failures(0); - assertThat(context).removalNotifications().withCause(REPLACED).contains(Map.entry(key, original), Map.entry(key, refreshed)).exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, executor = CacheExecutor.THREADED, removalListener = Listener.CONSUMING) - public void refresh_invalidate(CacheContext context) { - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - Int key = context.absentKey(); - Int original = Int.valueOf(1); - Int refreshed = Int.valueOf(2); - LoadingCache cache = context.build(k -> { - started.set(true); - await().untilTrue(done); - return refreshed; - }); - cache.put(key, original); - var future = cache.refresh(key); - await().untilTrue(started); - cache.invalidate(key); - done.set(true); - if (context.isGuava()) { - future.join(); - } else { - assertThat(future).succeedsWith(refreshed); - } - if (context.isGuava()) { - await().untilAsserted(() -> assertThat(cache).containsEntry(key, refreshed)); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(key, original).exclusively(); - } else { - await().untilAsserted(() -> assertThat(cache).doesNotContainKey(key)); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(Map.entry(key, original), Map.entry(key, refreshed)).exclusively(); - } - assertThat(context).stats().success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, executor = CacheExecutor.THREADED, expireAfterWrite = Expire.ONE_MINUTE, removalListener = Listener.CONSUMING) - public void refresh_expired(CacheContext context) { - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - Int key = context.absentKey(); - Int original = Int.valueOf(1); - Int refreshed = Int.valueOf(2); - LoadingCache cache = context.build(k -> { - started.set(true); - await().untilTrue(done); - return refreshed; - }); - cache.put(key, original); - var future = cache.refresh(key); - await().untilTrue(started); - context.ticker().advance(10, TimeUnit.MINUTES); - assertThat(cache).doesNotContainKey(key); - done.set(true); - if (context.isGuava()) { - future.join(); - } else { - assertThat(future).succeedsWith(refreshed); - } - if (context.isGuava()) { - await().untilAsserted(() -> assertThat(cache).containsEntry(key, refreshed)); - assertThat(context).removalNotifications().withCause(EXPIRED).contains(key, original).exclusively(); - } else { - await().untilAsserted(() -> assertThat(cache).doesNotContainKey(key)); - assertThat(context).removalNotifications().withCause(EXPIRED).contains(key, original); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(key, refreshed); - assertThat(context).removalNotifications().hasSize(2); - } - assertThat(context).stats().success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, executor = CacheExecutor.THREADED, maximumSize = Maximum.ONE, weigher = CacheWeigher.DISABLED, removalListener = Listener.CONSUMING) - public void refresh_evicted(CacheContext context) { - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - Int key1 = context.absentKey(); - Int key2 = key1.add(1); - Int original = Int.valueOf(1); - Int refreshed = Int.valueOf(2); - LoadingCache cache = context.build(k -> { - started.set(true); - await().forever().untilTrue(done); - return refreshed; - }); - cache.put(key1, original); - var future = cache.refresh(key1); - await().untilTrue(started); - cache.put(key2, original); - cache.cleanUp(); - assertThat(cache).doesNotContainKey(key1); - done.set(true); - if (context.isGuava()) { - future.join(); - } else { - assertThat(future).succeedsWith(refreshed); - } - if (context.isGuava()) { - await().untilAsserted(() -> assertThat(cache).containsEntry(key1, refreshed)); - await().untilAsserted(() -> assertThat(cache).doesNotContainKey(key2)); - assertThat(context).removalNotifications().withCause(SIZE).contains(Map.of(key1, original, key2, original)).exclusively(); - } else { - await().untilAsserted(() -> assertThat(cache).doesNotContainKey(key1)); - await().untilAsserted(() -> assertThat(cache).containsEntry(key2, original)); - assertThat(context).removalNotifications().withCause(SIZE).contains(key1, original); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(key1, original); - assertThat(context).removalNotifications().hasSize(2); - } - assertThat(context).stats().success(1).failures(0); - } - - @Test(dataProvider = "caches") - @CheckNoEvictions - @CheckMaxLogLevel(TRACE) - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY) - public void refresh_cancel_noLog(CacheContext context) { - var cacheLoader = new CacheLoader() { - @Override - public Int load(Int key) { - throw new AssertionError(); - } - - @Override - public CompletableFuture asyncLoad(Int key, Executor executor) { - var future = new CompletableFuture(); - future.cancel(false); - return future; - } - }; - LoadingCache cache = context.isAsync() ? context.buildAsync(cacheLoader).synchronous() : context.build(cacheLoader); - cache.refresh(context.absentKey()); - assertThat(TestLoggerFactory.getLoggingEvents()).isEmpty(); - } - - @Test(dataProvider = "caches") - @CheckNoEvictions - @CheckMaxLogLevel(TRACE) - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY) - public void refresh_timeout_noLog(CacheContext context) { - var cacheLoader = new CacheLoader() { - @Override - public Int load(Int key) { - throw new AssertionError(); - } - - @Override - public CompletableFuture asyncLoad(Int key, Executor executor) { - var future = new CompletableFuture(); - future.orTimeout(0, TimeUnit.SECONDS); - await().until(future::isDone); - return future; - } - }; - LoadingCache cache = context.isAsync() ? context.buildAsync(cacheLoader).synchronous() : context.build(cacheLoader); - cache.refresh(context.absentKey()); - assertThat(TestLoggerFactory.getLoggingEvents()).isEmpty(); - } - - @Test(dataProvider = "caches") - @CheckNoEvictions - @CheckMaxLogLevel(WARN) - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY) - public void refresh_error_log(CacheContext context) { - var expected = new RuntimeException(); - CacheLoader cacheLoader = key -> { - throw expected; - }; - LoadingCache cache = context.isAsync() ? context.buildAsync(cacheLoader).synchronous() : context.build(cacheLoader); - cache.refresh(context.absentKey()); - var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()); - assertThat(event.getThrowable().orElseThrow()).hasCauseThat().isSameInstanceAs(expected); - assertThat(event.getLevel()).isEqualTo(WARN); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY) - public void refresh_nullFuture_load(CacheContext context) { - var cache = context.build(new CacheLoader() { - @Override - public Int load(Int key) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture asyncLoad(Int key, Executor executor) { - return null; - } - }); - cache.refresh(context.absentKey()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY) - public void refresh_nullFuture_reload(CacheContext context) { - var cache = context.build(new CacheLoader() { - @Override - public Int load(Int key) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture asyncReload(Int key, Int oldValue, Executor executor) { - return null; - } - }); - cache.put(context.absentKey(), context.absentValue()); - cache.refresh(context.absentKey()); - } - - @CheckNoEvictions - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void refreshAll_null(LoadingCache cache, CacheContext context) { - cache.refreshAll(null); - } - - @CheckNoEvictions - @CheckNoStats - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - public void refreshAll_nullKey(LoadingCache cache, CacheContext context) { - cache.refreshAll(Collections.singletonList(null)); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void refreshAll_absent(LoadingCache cache, CacheContext context) { - var result = cache.refreshAll(context.absentKeys()).join(); - int count = context.absentKeys().size(); - assertThat(result).hasSize(count); - assertThat(cache).hasSize(context.initialSize() + count); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, loader = Loader.IDENTITY, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void refreshAll_present(LoadingCache cache, CacheContext context) { - var result = cache.refreshAll(context.original().keySet()).join(); - int count = context.original().keySet().size(); - assertThat(result).hasSize(count); - var expected = context.original().keySet().stream().collect(toImmutableMap(identity(), identity())); - assertThat(cache).containsExactlyEntriesIn(expected); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, loader = Loader.EXCEPTIONAL, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void refreshAll_failure(LoadingCache cache, CacheContext context) { - var future = cache.refreshAll(List.of(context.absentKey(), context.firstKey(), context.lastKey())); - assertThat(future).hasCompletedExceptionally(); - assertThat(cache).hasSize(context.initialSize()); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(loader = Loader.REFRESH_INTERRUPTED) - public void refreshAll_interrupted(LoadingCache cache, CacheContext context) { - try { - cache.refreshAll(context.absentKeys()); - Assert.fail(); - } catch (CompletionException e) { - assertThat(Thread.interrupted()).isTrue(); - assertThat(e).hasCauseThat().isInstanceOf(InterruptedException.class); - - int failures = context.isGuava() ? 1 : 0; - assertThat(context).stats().hits(0).misses(0).success(0).failures(failures); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.ASYNC_INCOMPLETE, removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void refreshAll_cancel(LoadingCache cache, CacheContext context) { - var key = context.original().isEmpty() ? context.absentKey() : context.firstKey(); - var future1 = cache.refresh(key); - var future2 = cache.refreshAll(List.of(key)); - assertThat(future1).isNotDone(); - future1.cancel(true); - assertThat(future2).hasCompletedExceptionally(); - assertThat(cache).containsExactlyEntriesIn(context.original()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY) - public void refreshAll_nullFuture_load(CacheContext context) { - var cache = context.build(new CacheLoader() { - @Override - public Int load(Int key) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture asyncLoad(Int key, Executor executor) { - return null; - } - }); - cache.refreshAll(context.absent().keySet()); - } - - @Test(dataProvider = "caches", expectedExceptions = NullPointerException.class) - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY) - public void refreshAll_nullFuture_reload(CacheContext context) { - var cache = context.build(new CacheLoader() { - @Override - public Int load(Int key) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture asyncReload(Int key, Int oldValue, Executor executor) { - return null; - } - }); - cache.put(context.absentKey(), context.absentValue()); - cache.refreshAll(context.absent().keySet()); - } - - @Test(expectedExceptions = UnsupportedOperationException.class) - public void loadAll() throws Exception { - CacheLoader loader = key -> key; - loader.loadAll(Set.of()); - } - - @Test - public void reload() throws Exception { - CacheLoader loader = key -> key; - assertThat(loader.reload(Int.valueOf(1), Int.valueOf(1))).isEqualTo(1); - } - - @Test - public void asyncLoad_exception() throws Exception { - var e = new Exception(); - CacheLoader loader = key -> { - throw e; - }; - assertThat(loader.asyncLoad(Int.valueOf(1), Runnable::run)).failsWith(CompletionException.class).hasCauseThat().isSameInstanceAs(e); - } - - @Test - public void asyncLoad() throws Exception { - CacheLoader loader = key -> key; - assertThat(loader.asyncLoad(Int.valueOf(1), Runnable::run)).succeedsWith(1); - } - - @Test - public void asyncLoadAll_exception() throws Exception { - var e = new Exception(); - var loader = new CacheLoader() { - @Override - public Int load(Int key) { - throw new AssertionError(); - } - - @Override - public Map loadAll(Set keys) throws Exception { - throw e; - } - }; - assertThat(loader.asyncLoadAll(Int.setOf(1), Runnable::run)).failsWith(CompletionException.class).hasCauseThat().isSameInstanceAs(e); - } - - @Test - public void asyncLoadAll() throws Throwable { - CacheLoader loader = key -> key; - assertThat(loader.asyncLoadAll(Set.of(), Runnable::run)).failsWith(CompletionException.class).hasCauseThat().isInstanceOf(UnsupportedOperationException.class); - } - - @Test - public void asyncReload_exception() throws Exception { - for (var e : List.of(new Exception(), new RuntimeException())) { - CacheLoader loader = key -> { - throw e; - }; - assertThat(loader.asyncReload(Int.valueOf(1), Int.valueOf(1), Runnable::run)).failsWith(CompletionException.class).hasCauseThat().isSameInstanceAs(e); - } - } - - @Test - public void asyncReload() throws Exception { - CacheLoader loader = Int::negate; - var future = loader.asyncReload(Int.valueOf(1), Int.valueOf(2), Runnable::run); - assertThat(future).succeedsWith(-1); - } - - @SuppressWarnings("CheckReturnValue") - @Test(expectedExceptions = NullPointerException.class) - public void bulk_null() { - CacheLoader.bulk(null); - } - - @Test - public void bulk_absent() throws Exception { - CacheLoader loader = CacheLoader.bulk(keys -> Map.of()); - assertThat(loader.loadAll(Int.setOf(1))).isEmpty(); - assertThat(loader.load(Int.valueOf(1))).isNull(); - } - - @Test - public void bulk_present() throws Exception { - CacheLoader loader = CacheLoader.bulk(keys -> keys.stream().collect(toImmutableMap(identity(), identity()))); - assertThat(loader.loadAll(Int.setOf(1, 2))).containsExactlyEntriesIn(Int.mapOf(1, 1, 2, 2)); - assertThat(loader.load(Int.valueOf(1))).isEqualTo(1); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.ASYNC_INCOMPLETE) - public void refreshes(LoadingCache cache, CacheContext context) { - var key1 = Iterables.get(context.absentKeys(), 0); - var key2 = context.original().isEmpty() ? Iterables.get(context.absentKeys(), 1) : context.firstKey(); - var future1 = cache.refresh(key1); - var future2 = cache.refresh(key2); - assertThat(cache.policy().refreshes()).containsExactly(key1, future1, key2, future2); - future1.complete(Int.valueOf(1)); - future2.cancel(true); - assertThat(cache.policy().refreshes()).isExhaustivelyEmpty(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LocalCacheSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LocalCacheSubject.java deleted file mode 100644 index ab7b898..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/LocalCacheSubject.java +++ /dev/null @@ -1,350 +0,0 @@ -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.Async.AsyncWeigher; -import com.github.benmanes.caffeine.cache.BoundedLocalCache.BoundedLocalAsyncCache; -import com.github.benmanes.caffeine.cache.BoundedLocalCache.BoundedLocalAsyncLoadingCache; -import com.github.benmanes.caffeine.cache.BoundedLocalCache.BoundedLocalManualCache; -import com.github.benmanes.caffeine.cache.LocalAsyncLoadingCache.LoadingCacheView; -import com.github.benmanes.caffeine.cache.References.WeakKeyEqualsReference; -import com.github.benmanes.caffeine.cache.References.WeakKeyReference; -import com.github.benmanes.caffeine.cache.TimerWheel.Sentinel; -import com.github.benmanes.caffeine.cache.UnboundedLocalCache.UnboundedLocalAsyncCache; -import com.github.benmanes.caffeine.cache.UnboundedLocalCache.UnboundedLocalAsyncLoadingCache; -import com.github.benmanes.caffeine.cache.UnboundedLocalCache.UnboundedLocalManualCache; -import com.github.benmanes.caffeine.cache.testing.Weighers; -import com.google.common.collect.ImmutableTable; -import com.google.common.collect.Sets; -import com.google.common.truth.FailureMetadata; -import com.google.common.truth.Subject; - -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -import static com.github.benmanes.caffeine.cache.LinkedDequeSubject.deque; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.MapSubject.map; - -@SuppressWarnings("GuardedBy") -public final class LocalCacheSubject extends Subject { - private final Object actual; - - private LocalCacheSubject(FailureMetadata metadata, Object subject) { - super(metadata, subject); - this.actual = subject; - } - - public static Factory> asyncLocal() { - return LocalCacheSubject::new; - } - - public static Factory> syncLocal() { - return LocalCacheSubject::new; - } - - public static Factory> mapLocal() { - return LocalCacheSubject::new; - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - public void isValid() { - if (actual instanceof BoundedLocalCache) { - var bounded = (BoundedLocalCache) actual; - checkBounded(bounded); - } else if (actual instanceof BoundedLocalManualCache) { - var bounded = (BoundedLocalManualCache) actual; - checkBounded(bounded.cache); - } else if (actual instanceof BoundedLocalAsyncCache) { - var bounded = (BoundedLocalAsyncCache) actual; - checkBounded(bounded.cache); - } else if (actual instanceof UnboundedLocalCache unbounded) { - checkUnbounded(unbounded); - } else if (actual instanceof UnboundedLocalAsyncCache unbounded) { - checkUnbounded(unbounded.cache); - } else if (actual instanceof UnboundedLocalManualCache unbounded) { - checkUnbounded(unbounded.cache); - } else if (actual instanceof LoadingCacheView) { - var async = ((LoadingCacheView) actual).asyncCache(); - if (async instanceof BoundedLocalAsyncLoadingCache) { - var bounded = (BoundedLocalAsyncLoadingCache) async; - checkBounded(bounded.cache); - } else if (async instanceof UnboundedLocalAsyncLoadingCache unbounded) { - checkUnbounded(unbounded.cache); - } - } else if (actual instanceof LocalAsyncCache.AsMapView asMap) { - if (asMap.delegate instanceof BoundedLocalCache) { - var bounded = (BoundedLocalCache) asMap.delegate; - checkBounded(bounded); - } else if (asMap.delegate instanceof UnboundedLocalCache unbounded) { - checkUnbounded(unbounded); - } - } - } - - - private void checkBounded(BoundedLocalCache bounded) { - drain(bounded); - checkReadBuffer(bounded); - - checkCache(bounded); - checkTimerWheel(bounded); - checkEvictionDeque(bounded); - } - - private void drain(BoundedLocalCache bounded) { - long adjustment = 0; - for (; ; ) { - bounded.cleanUp(); - if (!bounded.writeBuffer.isEmpty()) { - continue; - } else if (bounded.evicts() && (bounded.adjustment() != adjustment)) { - adjustment = bounded.adjustment(); - continue; - } - break; - } - } - - private void checkReadBuffer(BoundedLocalCache bounded) { - if (!tryDrainBuffers(bounded)) { - await().pollInSameThread().until(() -> tryDrainBuffers(bounded)); - } - } - - private Boolean tryDrainBuffers(BoundedLocalCache bounded) { - bounded.cleanUp(); - var buffer = bounded.readBuffer; - return (buffer.size() == 0) && (buffer.reads() == buffer.writes()); - } - - private void checkCache(BoundedLocalCache bounded) { - long remainingNanos = TimeUnit.SECONDS.toNanos(5); - for (; ; ) { - long end = System.nanoTime() + remainingNanos; - try { - if (bounded.evictionLock.tryLock(remainingNanos, TimeUnit.NANOSECONDS)) { - bounded.evictionLock.unlock(); - } else { - failWithActual("Maintenance lock cannot be acquired", bounded.evictionLock); - } - break; - } catch (InterruptedException ignored) { - remainingNanos = end - System.nanoTime(); - } - } - - check("size").withMessage("cache.size() equal to data.size()").that(bounded).hasSize(bounded.data.size()); - if (bounded.evicts()) { - bounded.evictionLock.lock(); - try { - check("weightedSize()").that(bounded.weightedSize()).isAtMost(bounded.maximum()); - check("windowWeightedSize()").that(bounded.windowWeightedSize()).isAtMost(bounded.windowMaximum()); - check("mainProtectedWeightedSize()").that(bounded.mainProtectedWeightedSize()).isAtMost(bounded.mainProtectedMaximum()); - } finally { - bounded.evictionLock.unlock(); - } - } - - if (bounded.isEmpty()) { - check("bounded").about(map()).that(bounded).isExhaustivelyEmpty(); - } - - for (var node : bounded.data.values()) { - checkNode(bounded, node); - } - } - - private void checkTimerWheel(BoundedLocalCache bounded) { - if (!bounded.expiresVariable()) { - return; - } else if (!doesTimerWheelMatch(bounded)) { - await().pollInSameThread().until(() -> doesTimerWheelMatch(bounded)); - } - - var seen = Sets.newIdentityHashSet(); - for (int i = 0; i < bounded.timerWheel().wheel.length; i++) { - for (int j = 0; j < bounded.timerWheel().wheel[i].length; j++) { - var sentinel = bounded.timerWheel().wheel[i][j]; - check("first").that(sentinel).isInstanceOf(Sentinel.class); - check("previousInVariableOrder").that(sentinel.getPreviousInVariableOrder().getNextInVariableOrder()).isSameInstanceAs(sentinel); - check("nextInVariableOrder").that(sentinel.getNextInVariableOrder().getPreviousInVariableOrder()).isSameInstanceAs(sentinel); - - var node = sentinel.getNextInVariableOrder(); - while (node != sentinel) { - var next = node.getNextInVariableOrder(); - var prev = node.getPreviousInVariableOrder(); - long duration = node.getVariableTime() - bounded.timerWheel().nanos; - check("notExpired").that(duration).isGreaterThan(0); - check("loopDetected").that(seen.add(node)).isTrue(); - check("wrongPrev").that(prev.getNextInVariableOrder()).isSameInstanceAs(node); - check("wrongNext").that(next.getPreviousInVariableOrder()).isSameInstanceAs(node); - node = node.getNextInVariableOrder(); - } - } - } - check("cache.size() == timerWheel.size()").that(bounded).hasSize(seen.size()); - } - - private boolean doesTimerWheelMatch(BoundedLocalCache bounded) { - bounded.evictionLock.lock(); - try { - var seen = Sets.newIdentityHashSet(); - for (int i = 0; i < bounded.timerWheel().wheel.length; i++) { - for (int j = 0; j < bounded.timerWheel().wheel[i].length; j++) { - var sentinel = bounded.timerWheel().wheel[i][j]; - var node = sentinel.getNextInVariableOrder(); - while (node != sentinel) { - if (!seen.add(node)) { - return false; - } - node = node.getNextInVariableOrder(); - } - } - } - return (bounded.size() == seen.size()); - } finally { - bounded.evictionLock.unlock(); - } - } - - private void checkEvictionDeque(BoundedLocalCache bounded) { - if (bounded.evicts()) { - long mainProbation = bounded.weightedSize() - bounded.windowWeightedSize() - bounded.mainProtectedWeightedSize(); - var deques = new ImmutableTable.Builder>>().put("window", bounded.windowWeightedSize(), bounded.accessOrderWindowDeque()).put("probation", mainProbation, bounded.accessOrderProbationDeque()).put("protected", bounded.mainProtectedWeightedSize(), bounded.accessOrderProtectedDeque()).build(); - checkLinks(bounded, deques); - check("accessOrderWindowDeque()").about(deque()).that(bounded.accessOrderWindowDeque()).isValid(); - check("accessOrderProbationDeque()").about(deque()).that(bounded.accessOrderProbationDeque()).isValid(); - } else if (bounded.expiresAfterAccess()) { - checkLinks(bounded, ImmutableTable.of("window", bounded.estimatedSize(), bounded.accessOrderWindowDeque())); - check("accessOrderWindowDeque()").about(deque()).that(bounded.accessOrderWindowDeque()).isValid(); - } - - if (bounded.expiresAfterWrite()) { - long expectedSize = bounded.evicts() ? bounded.weightedSize() : bounded.estimatedSize(); - checkLinks(bounded, ImmutableTable.of("writeOrder", expectedSize, bounded.writeOrderDeque())); - check("writeOrderDeque()").about(deque()).that(bounded.writeOrderDeque()).isValid(); - } - } - - private void checkLinks(BoundedLocalCache bounded, ImmutableTable>> deques) { - if (!doLinksMatch(bounded, deques.values())) { - await().pollInSameThread().until(() -> doLinksMatch(bounded, deques.values())); - } - - int totalSize = 0; - long totalWeightedSize = 0; - Set> seen = Sets.newIdentityHashSet(); - for (var cell : deques.cellSet()) { - long weightedSize = scanLinks(bounded, cell.getValue(), seen); - check(cell.getRowKey()).that(weightedSize).isEqualTo(cell.getColumnKey()); - totalSize += cell.getValue().size(); - totalWeightedSize += weightedSize; - } - check("linkSize").withMessage("cache.size() != links").that(bounded).hasSize(seen.size()); - check("totalSize").withMessage("cache.size() == deque.size()").that(bounded).hasSize(totalSize); - - if (bounded.evicts()) { - check("linkWeight").withMessage("weightedSize != link weights").that(totalWeightedSize).isEqualTo(bounded.weightedSize()); - check("nonNegativeWeight").that(totalWeightedSize).isAtLeast(0); - } - } - - private boolean doLinksMatch(BoundedLocalCache bounded, Collection>> deques) { - bounded.evictionLock.lock(); - try { - Set> seen = Sets.newIdentityHashSet(); - for (var deque : deques) { - scanLinks(bounded, deque, seen); - } - return (bounded.size() == seen.size()); - } finally { - bounded.evictionLock.unlock(); - } - } - - private long scanLinks(BoundedLocalCache bounded, LinkedDeque> deque, Set> seen) { - long weightedSize = 0; - Node prev = null; - for (var node : deque) { - check("scanLinks").withMessage("Loop detected: %s, saw %s in %s", node, seen, bounded).that(seen.add(node)).isTrue(); - check("getPolicyWeight()").that(node.getPolicyWeight()).isEqualTo(node.getWeight()); - check("getPrevious(node)").that(deque.getPrevious(node)).isEqualTo(prev); - weightedSize += node.getWeight(); - prev = node; - } - return weightedSize; - } - - private void checkNode(BoundedLocalCache bounded, Node node) { - var key = node.getKey(); - var value = node.getValue(); - checkKey(bounded, node, key, value); - checkValue(bounded, node, key, value); - checkWeight(bounded, node, key, value); - checkRefreshAfterWrite(bounded, node); - } - - private void checkKey(BoundedLocalCache bounded, Node node, Object key, Object value) { - if (bounded.collectKeys()) { - if ((key != null) && (value != null)) { - check("bounded").that(bounded).containsKey(key); - } - var clazz = node instanceof Interned ? WeakKeyEqualsReference.class : WeakKeyReference.class; - check("keyReference").that(node.getKeyReference()).isInstanceOf(clazz); - } else { - check("key").that(key).isNotNull(); - } - check("data").that(bounded.data).containsEntry(node.getKeyReference(), node); - } - - private void checkValue(BoundedLocalCache bounded, Node node, Object key, Object value) { - if (!bounded.collectValues()) { - check("value").that(value).isNotNull(); - if ((key != null) && !bounded.hasExpired(node, bounded.expirationTicker().read())) { - check("containsValue(value) for key %s", key).about(map()).that(bounded).containsValue(value); - } - } - checkIfAsyncValue(value); - } - - private void checkIfAsyncValue(Object value) { - if (value instanceof CompletableFuture future) { - if (!future.isDone() || future.isCompletedExceptionally()) { - failWithActual("future was not completed successfully", actual); - } - check("future").that(future.join()).isNotNull(); - } - } - - private void checkWeight(BoundedLocalCache bounded, Node node, Object key, Object value) { - check("node.getWeight").that(node.getWeight()).isAtLeast(0); - - var weigher = bounded.weigher; - boolean canCheckWeight = (weigher == Weighers.random()); - if (weigher instanceof @SuppressWarnings("rawtypes")AsyncWeigher asyncWeigher) { - canCheckWeight = (asyncWeigher.delegate == Weighers.random()); - } - if (canCheckWeight) { - check("node.getWeight()").that(node.getWeight()).isEqualTo(weigher.weigh(key, value)); - } - } - - private void checkRefreshAfterWrite(BoundedLocalCache bounded, Node node) { - if (bounded.refreshAfterWrite()) { - check("node.getWriteTime()").that(node.getWriteTime()).isNotEqualTo(Long.MAX_VALUE); - } - } - - public void checkUnbounded(UnboundedLocalCache unbounded) { - if (unbounded.isEmpty()) { - check("unbounded").about(map()).that(unbounded).isExhaustivelyEmpty(); - } - unbounded.data.forEach((key, value) -> { - check("key").that(key).isNotNull(); - check("value").that(value).isNotNull(); - checkIfAsyncValue(value); - }); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MpscGrowableArrayQueueLincheckTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MpscGrowableArrayQueueLincheckTest.java deleted file mode 100644 index 1ec339e..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MpscGrowableArrayQueueLincheckTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.github.benmanes.caffeine.cache; - -import org.jetbrains.kotlinx.lincheck.LinChecker; -import org.jetbrains.kotlinx.lincheck.annotations.OpGroupConfig; -import org.jetbrains.kotlinx.lincheck.annotations.Operation; -import org.jetbrains.kotlinx.lincheck.annotations.Param; -import org.jetbrains.kotlinx.lincheck.paramgen.IntGen; -import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.ModelCheckingOptions; -import org.jetbrains.kotlinx.lincheck.strategy.stress.StressOptions; -import org.jetbrains.kotlinx.lincheck.verifier.VerifierState; -import org.testng.annotations.Test; - -import java.util.ArrayList; -import java.util.Queue; - -@OpGroupConfig(name = "consumer", nonParallel = true) -@Param(name = "element", gen = IntGen.class, conf = "1:5") -public final class MpscGrowableArrayQueueLincheckTest extends VerifierState { - private final Queue queue; - - public MpscGrowableArrayQueueLincheckTest() { - queue = new org.jctools.queues.MpscGrowableArrayQueue<>(4, 65_536); - } - - @Operation - public boolean offer(@Param(name = "element") int e) { - return queue.offer(e); - } - - @Operation(group = "consumer") - public Integer poll() { - return queue.poll(); - } - - @Test(groups = "lincheck") - public void modelCheckingTest() { - var options = new ModelCheckingOptions() - .iterations(100) - .invocationsPerIteration(10_000); - new LinChecker(getClass(), options).check(); - } - - @Test(groups = "lincheck") - public void stressTest() { - var options = new StressOptions() - .iterations(100) - .invocationsPerIteration(10_000); - new LinChecker(getClass(), options).check(); - } - - @Override - protected Object extractState() { - return new ArrayList<>(queue); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MpscGrowableArrayQueueTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MpscGrowableArrayQueueTest.java deleted file mode 100644 index dac7b97..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MpscGrowableArrayQueueTest.java +++ /dev/null @@ -1,265 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.util.concurrent.atomic.AtomicInteger; - -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.google.common.truth.Truth.assertThat; -import static org.hamcrest.Matchers.is; - -public final class MpscGrowableArrayQueueTest { - private static final int NUM_PRODUCERS = 10; - private static final int PRODUCE = 100; - - private static final int POPULATED_SIZE = 10; - private static final int FULL_SIZE = 32; - - - @Test(expectedExceptions = IllegalArgumentException.class) - public void constructor_initialCapacity_tooSmall() { - new MpscGrowableArrayQueue(1, 4); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void constructor_maxCapacity_tooSmall() { - new MpscGrowableArrayQueue(4, 1); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void constructor_inverted() { - new MpscGrowableArrayQueue(8, 4); - } - - @Test - public void constructor() { - var buffer = new MpscGrowableArrayQueue(4, 8); - assertThat(buffer.capacity()).isEqualTo(8); - } - - @Test(dataProvider = "empty") - public void size_whenEmpty(MpscGrowableArrayQueue buffer) { - assertThat(buffer.size()).isEqualTo(0); - } - - @Test(dataProvider = "populated") - public void size_whenPopulated(MpscGrowableArrayQueue buffer) { - assertThat(buffer.size()).isEqualTo(POPULATED_SIZE); - } - - @Test(dataProvider = "empty") - public void offer_whenEmpty(MpscGrowableArrayQueue buffer) { - assertThat(buffer.offer(1)).isTrue(); - assertThat(buffer).hasSize(1); - } - - @Test(dataProvider = "populated") - public void offer_whenPopulated(MpscGrowableArrayQueue buffer) { - assertThat(buffer.offer(1)).isTrue(); - assertThat(buffer).hasSize(POPULATED_SIZE + 1); - } - - @Test(dataProvider = "full") - public void offer_whenFull(MpscGrowableArrayQueue buffer) { - assertThat(buffer.offer(1)).isFalse(); - assertThat(buffer).hasSize(FULL_SIZE); - } - - @Test(dataProvider = "empty") - public void relaxedOffer_whenEmpty(MpscGrowableArrayQueue buffer) { - assertThat(buffer.relaxedOffer(1)).isTrue(); - assertThat(buffer).hasSize(1); - } - - @Test(dataProvider = "populated") - public void relaxedOffer_whenPopulated(MpscGrowableArrayQueue buffer) { - assertThat(buffer.relaxedOffer(1)).isTrue(); - assertThat(buffer).hasSize(POPULATED_SIZE + 1); - } - - @Test(dataProvider = "full") - public void relaxedOffer_whenFull(MpscGrowableArrayQueue buffer) { - assertThat(buffer.relaxedOffer(1)).isFalse(); - assertThat(buffer).hasSize(FULL_SIZE); - } - - @Test(dataProvider = "empty") - public void poll_whenEmpty(MpscGrowableArrayQueue buffer) { - assertThat(buffer.poll()).isNull(); - } - - @Test(dataProvider = "populated") - public void poll_whenPopulated(MpscGrowableArrayQueue buffer) { - assertThat(buffer.poll()).isNotNull(); - assertThat(buffer).hasSize(POPULATED_SIZE - 1); - } - - @Test(dataProvider = "full") - public void poll_toEmpty(MpscGrowableArrayQueue buffer) { - while (buffer.poll() != null) { - } - assertThat(buffer).isEmpty(); - } - - @Test(dataProvider = "empty") - public void relaxedPoll_whenEmpty(MpscGrowableArrayQueue buffer) { - assertThat(buffer.relaxedPoll()).isNull(); - } - - @Test(dataProvider = "populated") - public void relaxedPoll_whenPopulated(MpscGrowableArrayQueue buffer) { - assertThat(buffer.relaxedPoll()).isNotNull(); - assertThat(buffer).hasSize(POPULATED_SIZE - 1); - } - - @Test(dataProvider = "full") - public void relaxedPoll_toEmpty(MpscGrowableArrayQueue buffer) { - while (buffer.relaxedPoll() != null) { - } - assertThat(buffer).isEmpty(); - } - - @Test(dataProvider = "empty") - public void peek_whenEmpty(MpscGrowableArrayQueue buffer) { - assertThat(buffer.peek()).isNull(); - } - - @Test(dataProvider = "populated") - public void peek_whenPopulated(MpscGrowableArrayQueue buffer) { - assertThat(buffer.peek()).isNotNull(); - assertThat(buffer).hasSize(POPULATED_SIZE); - } - - @Test(dataProvider = "full") - public void peek_toEmpty(MpscGrowableArrayQueue buffer) { - for (int i = 0; i < FULL_SIZE; i++) { - assertThat(buffer.peek()).isNotNull(); - buffer.poll(); - } - assertThat(buffer.peek()).isNull(); - } - - @Test(dataProvider = "empty") - public void relaxedPeek_whenEmpty(MpscGrowableArrayQueue buffer) { - assertThat(buffer.relaxedPeek()).isNull(); - } - - @Test(dataProvider = "populated") - public void relaxedPeek_whenPopulated(MpscGrowableArrayQueue buffer) { - assertThat(buffer.relaxedPeek()).isNotNull(); - assertThat(buffer).hasSize(POPULATED_SIZE); - } - - @Test(dataProvider = "full") - public void relaxedPeek_toEmpty(MpscGrowableArrayQueue buffer) { - for (int i = 0; i < FULL_SIZE; i++) { - assertThat(buffer.relaxedPeek()).isNotNull(); - buffer.poll(); - } - assertThat(buffer.relaxedPeek()).isNull(); - } - - @SuppressWarnings("ReturnValueIgnored") - @Test(dataProvider = "full", expectedExceptions = UnsupportedOperationException.class) - public void iterator(MpscGrowableArrayQueue buffer) { - buffer.iterator(); - } - - @Test(dataProvider = "populated") - public void inspection(MpscGrowableArrayQueue buffer) { - assertThat(buffer.currentConsumerIndex()).isEqualTo(0); - assertThat(buffer.currentProducerIndex()).isEqualTo(POPULATED_SIZE); - } - - @Test(dataProvider = "empty") - public void oneProducer_oneConsumer(MpscGrowableArrayQueue buffer) { - var started = new AtomicInteger(); - var finished = new AtomicInteger(); - ConcurrentTestHarness.execute(() -> { - started.incrementAndGet(); - await().untilAtomic(started, is(2)); - for (int i = 0; i < PRODUCE; i++) { - while (!buffer.offer(i)) { - } - } - finished.incrementAndGet(); - }); - ConcurrentTestHarness.execute(() -> { - started.incrementAndGet(); - await().untilAtomic(started, is(2)); - for (int i = 0; i < PRODUCE; i++) { - while (buffer.poll() == null) { - } - } - finished.incrementAndGet(); - }); - await().untilAtomic(finished, is(2)); - assertThat(buffer).isEmpty(); - } - - @Test(dataProvider = "empty") - public void manyProducers_noConsumer(MpscGrowableArrayQueue buffer) { - var count = new AtomicInteger(); - ConcurrentTestHarness.timeTasks(NUM_PRODUCERS, () -> { - for (int i = 0; i < PRODUCE; i++) { - if (buffer.offer(i)) { - count.incrementAndGet(); - } - } - }); - assertThat(buffer).hasSize(count.get()); - } - - @Test(dataProvider = "empty") - public void manyProducers_oneConsumer(MpscGrowableArrayQueue buffer) { - var started = new AtomicInteger(); - var finished = new AtomicInteger(); - ConcurrentTestHarness.execute(() -> { - started.incrementAndGet(); - await().untilAtomic(started, is(NUM_PRODUCERS + 1)); - for (int i = 0; i < (NUM_PRODUCERS * PRODUCE); i++) { - while (buffer.poll() == null) { - } - } - finished.incrementAndGet(); - }); - ConcurrentTestHarness.timeTasks(NUM_PRODUCERS, () -> { - started.incrementAndGet(); - await().untilAtomic(started, is(NUM_PRODUCERS + 1)); - for (int i = 0; i < PRODUCE; i++) { - while (!buffer.offer(i)) { - } - } - finished.incrementAndGet(); - }); - - await().untilAtomic(finished, is(NUM_PRODUCERS + 1)); - assertThat(buffer).isEmpty(); - } - - @DataProvider(name = "empty") - public Object[][] providesEmpty() { - return new Object[][]{{makePopulated(0)}}; - } - - @DataProvider(name = "populated") - public Object[][] providesPopulated() { - return new Object[][]{{makePopulated(POPULATED_SIZE)}}; - } - - @DataProvider(name = "full") - public Object[][] providesFull() { - return new Object[][]{{makePopulated(FULL_SIZE)}}; - } - - static MpscGrowableArrayQueue makePopulated(int items) { - var buffer = new MpscGrowableArrayQueue(4, FULL_SIZE); - for (int i = 0; i < items; i++) { - buffer.offer(i); - } - return buffer; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MpscGrowableQueueSanityTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MpscGrowableQueueSanityTest.java deleted file mode 100644 index 560d8c6..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MpscGrowableQueueSanityTest.java +++ /dev/null @@ -1,26 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; - -@RunWith(Parameterized.class) -public final class MpscGrowableQueueSanityTest extends QueueSanityTest { - - public MpscGrowableQueueSanityTest(Queue queue, - Ordering ordering, int capacity, boolean isBounded) { - super(queue, ordering, capacity, isBounded); - } - - @Parameterized.Parameters - public static List parameters() { - var list = new ArrayList(); - list.add(new Object[]{new MpscGrowableArrayQueue<>(2, 4), Ordering.FIFO, 4, true}); - list.add(new Object[]{new MpscGrowableArrayQueue<>(8, SIZE), Ordering.FIFO, SIZE, true}); - return list; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MultiThreadedTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MultiThreadedTest.java deleted file mode 100644 index 28e6bb6..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/MultiThreadedTest.java +++ /dev/null @@ -1,133 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.Int; -import com.github.benmanes.caffeine.testing.Threads; -import com.google.common.testing.SerializableTester; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ThreadLocalRandom; -import java.util.function.BiConsumer; - -import static com.google.common.base.Preconditions.checkState; -import static java.util.function.Function.identity; -import static uk.org.lidalia.slf4jext.Level.WARN; - -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@Test(groups = "isolated", dataProviderClass = CacheProvider.class) -public final class MultiThreadedTest { - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.DISABLED, stats = Stats.DISABLED, - population = Population.EMPTY, expireAfterAccess = Expire.DISABLED, - expireAfterWrite = Expire.DISABLED, removalListener = Listener.DISABLED, - refreshAfterWrite = {Expire.DISABLED, Expire.ONE_MILLISECOND}, - keys = ReferenceType.STRONG, values = ReferenceType.STRONG, - evictionListener = Listener.DISABLED) - public void concurrent_unbounded(LoadingCache cache, CacheContext context) { - Threads.runTest(cache, operations); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL, weigher = {CacheWeigher.DISABLED, CacheWeigher.RANDOM}, - stats = Stats.DISABLED, population = Population.EMPTY, removalListener = Listener.DISABLED, - refreshAfterWrite = {Expire.DISABLED, Expire.ONE_MILLISECOND}, - keys = ReferenceType.STRONG, values = ReferenceType.STRONG, - evictionListener = Listener.DISABLED) - public void concurrent_bounded(LoadingCache cache, CacheContext context) { - Threads.runTest(cache, operations); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.DISABLED, stats = Stats.DISABLED, - population = Population.EMPTY, expireAfterAccess = Expire.DISABLED, - expireAfterWrite = Expire.DISABLED, removalListener = Listener.DISABLED, - refreshAfterWrite = {Expire.DISABLED, Expire.ONE_MILLISECOND}, - keys = ReferenceType.STRONG, values = ReferenceType.STRONG, - evictionListener = Listener.DISABLED) - public void async_concurrent_unbounded( - AsyncLoadingCache cache, CacheContext context) { - Threads.runTest(cache, asyncOperations); - } - - @Test(dataProvider = "caches") - @CacheSpec(maximumSize = Maximum.FULL, weigher = {CacheWeigher.DISABLED, CacheWeigher.RANDOM}, - stats = Stats.DISABLED, population = Population.EMPTY, removalListener = Listener.DISABLED, - refreshAfterWrite = {Expire.DISABLED, Expire.ONE_MILLISECOND}, - keys = ReferenceType.STRONG, values = ReferenceType.STRONG, - evictionListener = Listener.DISABLED) - public void async_concurrent_bounded( - AsyncLoadingCache cache, CacheContext context) { - Threads.runTest(cache, asyncOperations); - } - - @SuppressWarnings({"FutureReturnValueIgnored", "MethodReferenceUsage", - "ReturnValueIgnored", "SelfEquals", "SizeGreaterThanOrEqualsZero"}) - List, Int>> operations = List.of( - LoadingCache::get, - (cache, key) -> cache.getAll(List.of(key)), - LoadingCache::refresh, - Cache::getIfPresent, - (cache, key) -> cache.get(key, identity()), - (cache, key) -> cache.getAllPresent(List.of(key)), - (cache, key) -> cache.put(key, key), - (cache, key) -> cache.putAll(Map.of(key, key)), - Cache::invalidate, - (cache, key) -> cache.invalidateAll(List.of(key)), - (cache, key) -> { - int random = ThreadLocalRandom.current().nextInt(); - if ((random & 255) == 0) { - cache.invalidateAll(); - } - }, - (cache, key) -> checkState(cache.estimatedSize() >= 0), - (cache, key) -> cache.stats(), - (cache, key) -> cache.cleanUp(), - (cache, key) -> cache.asMap().containsKey(key), - (cache, key) -> cache.asMap().containsValue(key), - (cache, key) -> cache.asMap().isEmpty(), - (cache, key) -> checkState(cache.asMap().size() >= 0), - (cache, key) -> cache.asMap().get(key), - (cache, key) -> cache.asMap().put(key, key), - (cache, key) -> cache.asMap().put(key, key), - (cache, key) -> cache.asMap().putIfAbsent(key, key), - (cache, key) -> cache.asMap().remove(key), - (cache, key) -> cache.asMap().remove(key, key), - (cache, key) -> cache.asMap().replace(key, key), - (cache, key) -> cache.asMap().computeIfAbsent(key, k -> k), - (cache, key) -> cache.asMap().computeIfPresent(key, (k, v) -> v), - (cache, key) -> cache.asMap().compute(key, (k, v) -> v), - (cache, key) -> cache.asMap().merge(key, key, (k, v) -> v), - (cache, key) -> { - int random = ThreadLocalRandom.current().nextInt(); - if ((random & 255) == 0) { - cache.asMap().clear(); - } - }, - (cache, key) -> cache.asMap().keySet().toArray(new Object[cache.asMap().size()]), - (cache, key) -> cache.asMap().values().toArray(new Object[cache.asMap().size()]), - (cache, key) -> cache.asMap().entrySet().toArray(new Map.Entry[cache.asMap().size()]), - (cache, key) -> cache.hashCode(), - (cache, key) -> cache.equals(cache), - (cache, key) -> cache.toString(), - (cache, key) -> { - int random = ThreadLocalRandom.current().nextInt(); - if ((random & 255) == 0) { - SerializableTester.reserialize(cache); - } - }); - - @SuppressWarnings({"FutureReturnValueIgnored", "MethodReferenceUsage"}) - List, Int>> asyncOperations = List.of(AsyncCache::getIfPresent, - (cache, key) -> cache.get(key, k -> key), - (cache, key) -> cache.get(key, (k, e) -> CompletableFuture.completedFuture(key)), - AsyncLoadingCache::get, - (cache, key) -> cache.getAll(List.of(key)), - (cache, key) -> cache.put(key, CompletableFuture.completedFuture(key))); -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PacerTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PacerTest.java deleted file mode 100644 index 51ee3d0..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PacerTest.java +++ /dev/null @@ -1,173 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.google.common.primitives.Ints; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.util.Random; -import java.util.concurrent.Executor; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.*; - -@Test(singleThreaded = true) -public final class PacerTest { - private static final long ONE_MINUTE_IN_NANOS = TimeUnit.MINUTES.toNanos(1); - private static final Random random = new Random(); - private static final long NOW = random.nextLong(); - - @Mock - Scheduler scheduler; - @Mock - Executor executor; - @Mock - Runnable command; - @Mock - Future future; - - Pacer pacer; - - @BeforeMethod - public void beforeMethod() throws Exception { - MockitoAnnotations.openMocks(this).close(); - pacer = new Pacer(scheduler); - } - - @Test - public void schedule_initialize() { - long delay = random.nextInt(Ints.saturatedCast(Pacer.TOLERANCE)); - doReturn(DisabledFuture.INSTANCE) - .when(scheduler).schedule(executor, command, Pacer.TOLERANCE, TimeUnit.NANOSECONDS); - pacer.schedule(executor, command, NOW, delay); - - assertThat(pacer.future).isSameInstanceAs(DisabledFuture.INSTANCE); - assertThat(pacer.nextFireTime).isEqualTo(NOW + Pacer.TOLERANCE); - } - - @Test - public void schedule_initialize_recurse() { - long delay = random.nextInt(Ints.saturatedCast(Pacer.TOLERANCE)); - doAnswer(invocation -> { - assertThat(pacer.future).isNull(); - assertThat(pacer.nextFireTime).isNotEqualTo(0); - pacer.schedule(executor, command, NOW, delay); - return DisabledFuture.INSTANCE; - }).when(scheduler).schedule(executor, command, Pacer.TOLERANCE, TimeUnit.NANOSECONDS); - - pacer.schedule(executor, command, NOW, delay); - assertThat(pacer.future).isSameInstanceAs(DisabledFuture.INSTANCE); - assertThat(pacer.nextFireTime).isEqualTo(NOW + Pacer.TOLERANCE); - } - - @Test - public void schedule_cancel_schedule() { - long fireTime = NOW + Pacer.TOLERANCE; - long delay = random.nextInt(Ints.saturatedCast(Pacer.TOLERANCE)); - doReturn(future) - .when(scheduler).schedule(executor, command, Pacer.TOLERANCE, TimeUnit.NANOSECONDS); - - pacer.schedule(executor, command, NOW, delay); - assertThat(pacer.nextFireTime).isEqualTo(fireTime); - assertThat(pacer.future).isSameInstanceAs(future); - - pacer.cancel(); - verify(future).cancel(false); - assertThat(pacer.nextFireTime).isEqualTo(0); - assertThat(pacer.future).isNull(); - - pacer.schedule(executor, command, NOW, delay); - assertThat(pacer.nextFireTime).isEqualTo(fireTime); - assertThat(pacer.future).isSameInstanceAs(future); - } - - @Test - public void scheduled_afterNextFireTime_skip() { - pacer.nextFireTime = NOW + ONE_MINUTE_IN_NANOS; - pacer.future = future; - - long expectedNextFireTime = pacer.nextFireTime; - pacer.schedule(executor, command, NOW, ONE_MINUTE_IN_NANOS); - - assertThat(pacer.future).isSameInstanceAs(future); - assertThat(pacer.nextFireTime).isEqualTo(expectedNextFireTime); - verifyNoInteractions(scheduler, executor, command, future); - } - - @Test - public void schedule_beforeNextFireTime_skip() { - pacer.nextFireTime = NOW + ONE_MINUTE_IN_NANOS; - pacer.future = future; - - long expectedNextFireTime = pacer.nextFireTime; - long delay = ONE_MINUTE_IN_NANOS - - Math.max(1, random.nextInt(Ints.saturatedCast(Pacer.TOLERANCE))); - pacer.schedule(executor, command, NOW, delay); - - assertThat(pacer.future).isSameInstanceAs(future); - assertThat(pacer.nextFireTime).isEqualTo(expectedNextFireTime); - verifyNoInteractions(scheduler, executor, command, future); - } - - @Test - public void schedule_beforeNextFireTime_minimumDelay() { - pacer.nextFireTime = NOW + ONE_MINUTE_IN_NANOS; - pacer.future = future; - - long delay = random.nextInt(Ints.saturatedCast(Pacer.TOLERANCE)); - doReturn(DisabledFuture.INSTANCE) - .when(scheduler).schedule(executor, command, Pacer.TOLERANCE, TimeUnit.NANOSECONDS); - pacer.schedule(executor, command, NOW, delay); - - assertThat(pacer.future).isSameInstanceAs(DisabledFuture.INSTANCE); - assertThat(pacer.nextFireTime).isEqualTo(NOW + Pacer.TOLERANCE); - - verify(future).cancel(false); - verify(scheduler).schedule(executor, command, Pacer.TOLERANCE, TimeUnit.NANOSECONDS); - - verifyNoInteractions(executor, command); - verifyNoMoreInteractions(scheduler, future); - } - - @Test - public void schedule_beforeNextFireTime_customDelay() { - pacer.nextFireTime = NOW + ONE_MINUTE_IN_NANOS; - pacer.future = future; - - long delay = (Pacer.TOLERANCE + Math.max(1, random.nextInt())); - doReturn(DisabledFuture.INSTANCE) - .when(scheduler).schedule(executor, command, delay, TimeUnit.NANOSECONDS); - pacer.schedule(executor, command, NOW, delay); - - assertThat(pacer.future).isSameInstanceAs(DisabledFuture.INSTANCE); - assertThat(pacer.nextFireTime).isEqualTo(NOW + delay); - - verify(future).cancel(false); - verify(scheduler).schedule(executor, command, delay, TimeUnit.NANOSECONDS); - - verifyNoInteractions(executor, command); - verifyNoMoreInteractions(scheduler, future); - } - - @Test - public void cancel_initialize() { - pacer.cancel(); - assertThat(pacer.nextFireTime).isEqualTo(0); - assertThat(pacer.future).isNull(); - } - - @Test - public void cancel_scheduled() { - pacer.nextFireTime = NOW + ONE_MINUTE_IN_NANOS; - pacer.future = future; - - pacer.cancel(); - verify(future).cancel(false); - assertThat(pacer.future).isNull(); - assertThat(pacer.nextFireTime).isEqualTo(0); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PackageSanityTests.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PackageSanityTests.java deleted file mode 100644 index 8a8ca62..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/PackageSanityTests.java +++ /dev/null @@ -1,19 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.google.common.testing.AbstractPackageSanityTests; - -public final class PackageSanityTests extends AbstractPackageSanityTests { - public PackageSanityTests() { - publicApiOnly(); - setDefault(CacheLoader.class, key -> key); - setDefault(Caffeine.class, Caffeine.newBuilder()); - ignoreClasses(clazz -> clazz == CaffeineSpec.class || - clazz.getSimpleName().startsWith("Is") || - clazz.getSimpleName().endsWith("Test") || - clazz.getSimpleName().contains("Stresser") || - clazz.getSimpleName().endsWith("Generator") || - clazz.getSimpleName().endsWith("Benchmark") - ); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/QueueSanityTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/QueueSanityTest.java deleted file mode 100644 index 9af6fb7..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/QueueSanityTest.java +++ /dev/null @@ -1,234 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import org.hamcrest.Matcher; -import org.jctools.util.Pow2; -import org.junit.Before; -import org.junit.Test; - -import java.util.Collection; -import java.util.Queue; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.IntStream; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.junit.Assume.assumeThat; - -@SuppressWarnings({"ThreadPriorityCheck"}) -public abstract class QueueSanityTest { - public static final int SIZE = 8192 * 2; - private final Queue queue; - private final Ordering ordering; - private final boolean isBounded; - private final int capacity; - - protected QueueSanityTest(Queue queue, Ordering ordering, int capacity, boolean isBounded) { - this.queue = queue; - this.ordering = ordering; - this.capacity = capacity; - this.isBounded = isBounded; - } - - @Before - public void clear() { - queue.clear(); - } - - @Test - public void sanity() { - IntStream.range(0, SIZE).forEach(i -> { - assertNull(queue.poll()); - assertThat(queue, emptyAndZeroSize()); - }); - int i = 0; - while (i < SIZE && queue.offer(i)) { - i++; - } - int size = i; - assertEquals(size, queue.size()); - if (ordering == Ordering.FIFO) { - i = 0; - Integer p; - Integer e; - while ((p = queue.peek()) != null) { - e = queue.poll(); - assertEquals(p, e); - assertEquals(size - (i + 1), queue.size()); - assertEquals(i++, e.intValue()); - } - assertEquals(size, i); - } else { - int sum = (size - 1) * size / 2; - i = 0; - Integer e; - while ((e = queue.poll()) != null) { - assertEquals(--size, queue.size()); - sum -= e; - } - assertEquals(0, sum); - } - assertNull(queue.poll()); - assertThat(queue, emptyAndZeroSize()); - } - - @Test - public void testSizeIsTheNumberOfOffers() { - int currentSize = 0; - while (currentSize < SIZE && queue.offer(currentSize)) { - currentSize++; - assertThat(queue, hasSize(currentSize)); - } - } - - @Test - public void whenFirstInThenFirstOut() { - assumeThat(ordering, is(Ordering.FIFO)); - int i = 0; - while (i < SIZE && queue.offer(i)) { - i++; - } - final int size = queue.size(); - i = 0; - Integer prev; - while ((prev = queue.peek()) != null) { - final Integer item = queue.poll(); - assertThat(item, is(prev)); - assertThat(queue, hasSize(size - (i + 1))); - assertThat(item, is(i)); - i++; - } - assertThat(i, is(size)); - } - - @Test(expected = NullPointerException.class) - public void offerNullResultsInNPE() { - queue.offer(null); - } - - @Test - public void whenOfferItemAndPollItemThenSameInstanceReturnedAndQueueIsEmpty() { - assertThat(queue, emptyAndZeroSize()); - final Integer e = 1876876; - queue.offer(e); - assertFalse(queue.isEmpty()); - assertEquals(1, queue.size()); - final Integer oh = queue.poll(); - assertEquals(e, oh); - assertThat(oh, sameInstance(e)); - assertThat(queue, emptyAndZeroSize()); - } - - @Test - public void testPowerOf2Capacity() { - assumeThat(isBounded, is(true)); - int n = Pow2.roundToPowerOfTwo(capacity); - IntStream.range(0, n).forEach(i -> assertTrue("Failed to insert:" + i, queue.offer(i))); - assertFalse(queue.offer(n)); - } - - static final class Val { - public int value; - } - - @Test - @SuppressWarnings({"rawtypes", "unchecked"}) - public void testHappensBefore() throws InterruptedException { - final AtomicBoolean stop = new AtomicBoolean(); - final Queue q = queue; - final Val fail = new Val(); - Thread t1 = new Thread(() -> { - while (!stop.get()) { - for (int i = 1; i <= 10; i++) { - Val v = new Val(); - v.value = i; - q.offer(v); - } - Thread.yield(); - } - }); - Thread t2 = new Thread(() -> { - while (!stop.get()) { - for (int i = 0; i < 10; i++) { - Val v = (Val) q.peek(); - if (v != null && v.value == 0) { - fail.value = 1; - stop.set(true); - } - q.poll(); - } - } - }); - t1.start(); - t2.start(); - Thread.sleep(1000); - stop.set(true); - t1.join(); - t2.join(); - assertEquals("reordering detected", 0, fail.value); - } - - @Test - public void testSize() throws InterruptedException { - final AtomicBoolean stop = new AtomicBoolean(); - final Queue q = queue; - final Val fail = new Val(); - Thread t1 = new Thread(() -> { - while (!stop.get()) { - q.offer(1); - q.poll(); - } - }); - Thread t2 = new Thread(() -> { - while (!stop.get()) { - int size = q.size(); - if (size != 0 && size != 1) { - fail.value++; - } - } - }); - t1.start(); - t2.start(); - Thread.sleep(1000); - stop.set(true); - t1.join(); - t2.join(); - assertEquals("Unexpected size observed", 0, fail.value); - } - - @Test - public void testPollAfterIsEmpty() throws InterruptedException { - final AtomicBoolean stop = new AtomicBoolean(); - final Queue q = queue; - final Val fail = new Val(); - Thread t1 = new Thread(() -> { - while (!stop.get()) { - q.offer(1); - Thread.yield(); - } - }); - Thread t2 = new Thread(() -> { - while (!stop.get()) { - if (!q.isEmpty() && q.poll() == null) { - fail.value++; - } - } - }); - t1.start(); - t2.start(); - Thread.sleep(1000); - stop.set(true); - t1.join(); - t2.join(); - assertEquals("Observed no element in non-empty queue", 0, fail.value); - } - - public static Matcher> emptyAndZeroSize() { - return allOf(hasSize(0), empty()); - } - - enum Ordering { - FIFO - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReferenceTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReferenceTest.java deleted file mode 100644 index 8fc1e26..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReferenceTest.java +++ /dev/null @@ -1,1126 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.References.*; -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.base.Splitter; -import com.google.common.collect.Maps; -import com.google.common.testing.EqualsTester; -import com.google.common.testing.GcFinalization; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.util.*; -import java.util.AbstractMap.SimpleEntry; -import java.util.Map.Entry; - -import static com.github.benmanes.caffeine.cache.RemovalCause.*; -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContext.intern; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.FutureSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.base.Predicates.equalTo; -import static com.google.common.base.Predicates.not; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.truth.Truth.assertThat; -import static java.util.function.Function.identity; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.*; -import static uk.org.lidalia.slf4jext.Level.WARN; - -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@Test(groups = "slow", dataProviderClass = CacheProvider.class) -public final class ReferenceTest { - - @Test(dataProvider = "caches") - @CacheSpec(keys = ReferenceType.WEAK, population = Population.FULL) - public void identity_keys(Cache cache, CacheContext context) { - Int key = new Int(context.firstKey()); - assertThat(cache).doesNotContainKey(key); - } - - @Test(dataProvider = "caches") - @CacheSpec(values = {ReferenceType.WEAK, ReferenceType.SOFT}, population = Population.FULL) - public void identity_values(Cache cache, CacheContext context) { - Int value = new Int(context.original().get(context.firstKey())); - assertThat(cache).doesNotContainKey(value); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, evictionListener = Listener.MOCKITO) - public void collect_evictionListenerFails(CacheContext context) { - context.clear(); - GcFinalization.awaitFullGc(); - - doThrow(RuntimeException.class) - .when(context.evictionListener()).onRemoval(any(), any(), any()); - assertThat(context.cache()).whenCleanedUp().isEmpty(); - verify(context.evictionListener(), times((int) context.initialSize())) - .onRemoval(any(), any(), any()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - keys = ReferenceType.STRONG, values = {ReferenceType.WEAK, ReferenceType.SOFT}, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void getIfPresent(Cache cache, CacheContext context) { - Int key = context.firstKey(); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(cache.getIfPresent(key)).isNull(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - keys = ReferenceType.STRONG, values = {ReferenceType.WEAK, ReferenceType.SOFT}, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void get(Cache cache, CacheContext context) { - Int key = context.firstKey(); - var collected = getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(cache.get(key, k -> context.absentValue())).isEqualTo(context.absentValue()); - assertThat(cache).whenCleanedUp().hasSize(1); - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO, - values = {ReferenceType.WEAK, ReferenceType.SOFT}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void get_expiryFails(Cache cache, CacheContext context) { - Int key = context.firstKey(); - - context.clear(); - GcFinalization.awaitFullGc(); - try { - when(context.expiry().expireAfterCreate(any(), any(), anyLong())).thenThrow(IllegalStateException.class); - cache.get(key, identity()); - } finally { - assertThat(cache).doesNotContainKey(key); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - keys = ReferenceType.STRONG, values = {ReferenceType.WEAK, ReferenceType.SOFT}, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void getAllPresent(Cache cache, CacheContext context) { - var keys = context.firstMiddleLastKeys(); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(cache.getAllPresent(keys)).isExhaustivelyEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void getAll(Cache cache, CacheContext context) { - var keys = context.firstMiddleLastKeys(); - var collected = getExpectedAfterGc(context, context.isStrongValues() - ? Maps.filterKeys(context.original(), not(keys::contains)) - : context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(cache.getAll(keys, keysToLoad -> Maps.asMap(keysToLoad, Int::negate))) - .containsExactlyEntriesIn(Maps.asMap(keys, Int::negate)); - assertThat(cache).whenCleanedUp().hasSize(keys.size()); - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void put(Cache cache, CacheContext context) { - var collected = getExpectedAfterGc(context, context.isStrongValues() - ? Maps.filterKeys(context.original(), not(equalTo(context.firstKey()))) - : context.original()); - var key = intern(context.firstKey()); - context.clear(); - GcFinalization.awaitFullGc(); - cache.put(key, context.absentValue()); - assertThat(cache).whenCleanedUp().hasSize(1); - if (context.isStrongValues()) { - assertThat(context).evictionNotifications().withCause(COLLECTED).contains(collected).exclusively(); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - assertThat(context).removalNotifications().withCause(REPLACED).contains(key, key.negate()); - } else { - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - keys = ReferenceType.STRONG, values = {ReferenceType.WEAK, ReferenceType.SOFT}, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void putAll(Cache cache, CacheContext context) { - var collected = getExpectedAfterGc(context, context.original()); - var entries = Map.of(context.firstKey(), context.absentValue(), - context.middleKey(), context.absentValue(), context.absentKey(), context.absentValue()); - context.clear(); - GcFinalization.awaitFullGc(); - cache.putAll(entries); - assertThat(context.cache()).whenCleanedUp().hasSize(entries.size()); - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void invalidate(Cache cache, CacheContext context) { - Int key = context.firstKey(); - Int value = cache.getIfPresent(key); - var collected = getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(equalTo(key)))); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(cache).whenCleanedUp().hasSize(1); - cache.invalidate(key); - assertThat(cache).isEmpty(); - assertThat(value).isNotNull(); - assertThat(context).evictionNotifications().withCause(COLLECTED).contains(collected).exclusively(); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(key, value); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, - keys = ReferenceType.STRONG, values = {ReferenceType.WEAK, ReferenceType.SOFT}, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void invalidateAll_iterable(Cache cache, CacheContext context) { - Map retained; - Entry[] collected; - var keys = context.firstMiddleLastKeys(); - if (context.isStrongValues()) { - retained = context.firstMiddleLastKeys().stream().collect(toImmutableMap(identity(), key -> context.original().get(key))); - collected = getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(keys::contains))); - } else { - retained = Map.of(); - collected = getExpectedAfterGc(context, context.original()); - } - context.clear(); - GcFinalization.awaitFullGc(); - cache.invalidateAll(keys); - assertThat(cache).whenCleanedUp().isEmpty(); - if (context.isStrongValues()) { - assertThat(context).evictionNotifications().withCause(COLLECTED).contains(collected).exclusively(); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(retained); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - } else { - assertThat(context).notifications().withCause(COLLECTED) - .contains(collected).exclusively(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void invalidateAll_full(Cache cache, CacheContext context) { - Map retained; - Entry[] collected; - var keys = context.firstMiddleLastKeys(); - if (context.isStrongValues()) { - retained = context.firstMiddleLastKeys().stream().collect(toImmutableMap(identity(), key -> context.original().get(key))); - collected = getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(keys::contains))); - } else { - retained = Map.of(); - collected = getExpectedAfterGc(context, context.original()); - } - context.clear(); - GcFinalization.awaitFullGc(); - cache.invalidateAll(); - if (context.isStrongValues()) { - assertThat(context).evictionNotifications().withCause(COLLECTED).contains(collected).exclusively(); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(retained); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - } else { - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void cleanUp(CacheContext context) { - var collected = getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(context.cache()).whenCleanedUp().isEmpty(); - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void get_loading(LoadingCache cache, CacheContext context) { - Int key = context.firstKey(); - Int value = context.original().get(key); - var collected = getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(equalTo(key)))); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(cache).whenCleanedUp().hasSize(1); - assertThat(cache.get(key)).isEqualTo(value); - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO, - values = {ReferenceType.WEAK, ReferenceType.SOFT}, loader = Loader.IDENTITY) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void get_loading_expiryFails(LoadingCache cache, CacheContext context) { - Int key = context.firstKey(); - context.clear(); - GcFinalization.awaitFullGc(); - try { - when(context.expiry().expireAfterCreate(any(), any(), anyLong())) - .thenThrow(IllegalStateException.class); - cache.get(key); - } finally { - assertThat(cache).doesNotContainKey(key); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING, - loader = {Loader.NEGATIVE, Loader.BULK_NEGATIVE}) - public void getAll_loading(LoadingCache cache, CacheContext context) { - var keys = context.firstMiddleLastKeys(); - var collected = context.isStrongValues() - ? getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(keys::contains))) - : getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(cache.getAll(keys)).containsExactlyEntriesIn(Maps.asMap(keys, Int::negate)); - assertThat(cache).whenCleanedUp().hasSize(keys.size()); - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING, loader = Loader.IDENTITY) - public void refresh(LoadingCache cache, CacheContext context) { - Int key = context.firstKey(); - Int value = context.original().get(key); - var collected = getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(equalTo(key)))); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(context.cache()).whenCleanedUp().hasSize(1); - assertThat(cache.refresh(key)).succeedsWith(key); - assertThat(cache).doesNotContainEntry(key, value); - assertThat(context).evictionNotifications().withCause(COLLECTED).contains(collected).exclusively(); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - assertThat(context).removalNotifications().withCause(REPLACED).contains(key, value); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void getIfPresent_async(AsyncCache cache, CacheContext context) { - Int key = context.firstKey(); - Int value = context.original().get(key); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(cache.synchronous()).whenCleanedUp().hasSize(1); - assertThat(cache.synchronous().getIfPresent(key)).isEqualTo(value); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, keys = ReferenceType.WEAK, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void get_async(AsyncCache cache, CacheContext context) { - var collected = getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(cache.get(context.absentKey(), identity())).succeedsWith(context.absentKey()); - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, keys = ReferenceType.WEAK, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void getAll_async(AsyncCache cache, CacheContext context) { - var keys = Set.of(context.firstKey(), context.lastKey(), context.absentKey()); - var collected = getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(keys::contains))); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(cache.getAll(keys, keysToLoad -> Maps.asMap(keysToLoad, Int::negate)).join()).containsExactlyEntriesIn(Maps.asMap(keys, Int::negate)); - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, keys = ReferenceType.WEAK, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void put_async(AsyncCache cache, CacheContext context) { - var collected = getExpectedAfterGc(context, context.original()); - Int key = context.absentKey(); - context.clear(); - GcFinalization.awaitFullGc(); - cache.put(key, context.absentValue().asFuture()); - assertThat(cache).hasSize(1); - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void isEmpty(Map map, CacheContext context) { - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map).isNotEmpty(); - assertThat(context.cache()).whenCleanedUp().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void size(Map map, CacheContext context) { - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map).hasSize(context.initialSize()); - assertThat(context.cache()).whenCleanedUp().isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void containsKey(Map map, CacheContext context) { - Int key = context.firstKey(); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.containsKey(key)).isEqualTo(context.isStrongValues()); - } - - @Test(dataProvider = "caches") - @SuppressWarnings("UnusedVariable") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void containsValue(Map map, CacheContext context) { - Int key = context.firstKey(); - Int value = context.original().get(key); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.containsValue(value)).isTrue(); - key = null; - GcFinalization.awaitFullGc(); - assertThat(map.containsValue(value)).isNotEqualTo(context.isWeakKeys()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void clear(Map map, CacheContext context) { - var retained = context.firstMiddleLastKeys().stream().collect(toImmutableMap(identity(), key -> context.original().get(key))); - var collected = getExpectedAfterGc(context, Maps.difference(context.original(), retained).entriesOnlyOnLeft()); - context.clear(); - GcFinalization.awaitFullGc(); - map.clear(); - assertThat(context).evictionNotifications().withCause(COLLECTED).contains(collected).exclusively(); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(retained); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void putIfAbsent(Map map, CacheContext context) { - Int key = context.firstKey(); - Entry[] collected = context.isStrongValues() - ? getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(equalTo(key)))) - : getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - Int value = map.putIfAbsent(key, context.absentValue()); - if (context.isStrongValues()) { - assertThat(value).isNotNull(); - } else { - assertThat(value).isNull(); - } - assertThat(context.cache()).whenCleanedUp().hasSize(1); - assertThat(map).containsKey(key); - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.COLLECTION, - stats = Stats.ENABLED, removalListener = Listener.DISABLED) - public void put_weighted(Cache> cache, CacheContext context) { - Int key = context.absentKey(); - cache.put(key, Int.listOf(1)); - GcFinalization.awaitFullGc(); - var value = cache.asMap().put(key, Int.listOf(1, 2, 3)); - if (context.isStrongValues()) { - assertThat(value).isEqualTo(Int.listOf(1)); - } else { - assertThat(value).isNull(); - } - assertThat(context).hasWeightedSize(3); - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO, - values = {ReferenceType.WEAK, ReferenceType.SOFT}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void put_expiryFails(Cache cache, CacheContext context) { - Int key = context.firstKey(); - context.clear(); - GcFinalization.awaitFullGc(); - try { - when(context.expiry().expireAfterCreate(any(), any(), anyLong())) - .thenThrow(IllegalStateException.class); - cache.put(key, key); - } finally { - assertThat(cache).doesNotContainKey(key); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void put_map(Map map, CacheContext context) { - Int key = context.firstKey(); - Int replaced = new Int(context.original().get(key)); - var collected = context.isStrongValues() - ? getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(equalTo(key)))) - : getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - Int value = map.put(key, context.absentValue()); - if (context.isStrongValues()) { - assertThat(value).isNotNull(); - } else { - assertThat(value).isNull(); - } - assertThat(context.cache()).whenCleanedUp().hasSize(1); - assertThat(map).containsKey(key); - if (context.isStrongValues()) { - assertThat(context).evictionNotifications().withCause(COLLECTED) - .contains(collected).exclusively(); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - assertThat(context).removalNotifications().withCause(REPLACED).contains(key, replaced); - } else { - assertThat(context).notifications().withCause(COLLECTED) - .contains(collected).exclusively(); - } - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO, - values = {ReferenceType.WEAK, ReferenceType.SOFT}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void put_map_expiryFails(Map map, CacheContext context) { - Int key = context.firstKey(); - context.clear(); - GcFinalization.awaitFullGc(); - try { - when(context.expiry().expireAfterCreate(any(), any(), anyLong())) - .thenThrow(IllegalStateException.class); - map.put(key, key); - } finally { - assertThat(map).doesNotContainKey(key); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void replace(Map map, CacheContext context) { - Int key = context.firstKey(); - context.clear(); - GcFinalization.awaitFullGc(); - Int value = map.replace(key, context.absentValue()); - if (context.isStrongValues()) { - assertThat(value).isNotNull(); - } else { - assertThat(value).isNull(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void replaceConditionally(Map map, CacheContext context) { - Int key = context.firstKey(); - Int value = context.original().get(key); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.replace(key, value, context.absentValue())).isTrue(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void remove(Map map, CacheContext context) { - Int key = context.firstKey(); - Int removed = new Int(context.original().get(key)); - Entry[] collected = context.isStrongValues() - ? getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(equalTo(key)))) - : getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - Int value = map.remove(key); - if (context.isStrongValues()) { - assertThat(value).isNotNull(); - } else { - assertThat(value).isNull(); - } - assertThat(context.cache()).whenCleanedUp().isEmpty(); - if (context.isStrongValues()) { - assertThat(context).evictionNotifications().withCause(COLLECTED) - .contains(collected).exclusively(); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(key, removed); - } else { - assertThat(context).notifications().withCause(COLLECTED) - .contains(collected).exclusively(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void removeConditionally_found(Map map, CacheContext context) { - Int key = context.firstKey(); - Int value = context.original().get(key); - var collected = getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(equalTo(key)))); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.remove(key, value)).isTrue(); - assertThat(context.cache()).whenCleanedUp().isEmpty(); - assertThat(context).evictionNotifications().withCause(COLLECTED).contains(collected).exclusively(); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(key, value); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, values = {ReferenceType.WEAK, ReferenceType.SOFT}, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void removeConditionally_notFound(Map map, CacheContext context) { - var collected = getExpectedAfterGc(context, context.original()); - Int key = context.firstKey(); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.remove(key, context.absentValue())).isFalse(); - assertThat(context.cache()).whenCleanedUp().isEmpty(); - assertThat(context).notifications().withCause(COLLECTED) - .contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void computeIfAbsent(Map map, CacheContext context) { - Int key = context.firstKey(); - var collected = context.isStrongValues() - ? getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(equalTo(key)))) - : getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - Int value = map.computeIfAbsent(key, k -> context.absentValue()); - assertThat(context.cache()).whenCleanedUp().hasSize(1); - if (context.isStrongValues()) { - assertThat(value).isNotEqualTo(context.absentValue()); - } else { - assertThat(value).isEqualTo(context.absentValue()); - } - assertThat(context).notifications().withCause(COLLECTED) - .contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, values = {ReferenceType.WEAK, ReferenceType.SOFT}, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void computeIfAbsent_nullValue(Map map, CacheContext context) { - Int key = context.firstKey(); - var collected = getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.computeIfAbsent(key, k -> null)).isNull(); - assertThat(context.cache()).whenCleanedUp().isEmpty(); - assertThat(context).notifications().withCause(COLLECTED) - .contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.COLLECTION, - stats = Stats.ENABLED, removalListener = Listener.DISABLED) - public void computeIfAbsent_weighted(Cache> cache, CacheContext context) { - Int key = context.absentKey(); - cache.put(key, Int.listOf(1)); - GcFinalization.awaitFullGc(); - cache.asMap().computeIfAbsent(key, k -> Int.listOf(1, 2, 3)); - if (context.isStrongValues()) { - assertThat(context).hasWeightedSize(1); - } else { - assertThat(context).hasWeightedSize(3); - } - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO, - values = {ReferenceType.WEAK, ReferenceType.SOFT}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void computeIfAbsent_expiryFails(Map map, CacheContext context) { - Int key = context.firstKey(); - context.clear(); - GcFinalization.awaitFullGc(); - try { - when(context.expiry().expireAfterCreate(any(), any(), anyLong())) - .thenThrow(IllegalStateException.class); - map.computeIfAbsent(key, identity()); - } finally { - assertThat(map).doesNotContainKey(key); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void computeIfPresent(Map map, CacheContext context) { - Int key = context.firstKey(); - Int replaced = new Int(context.original().get(key)); - var collected = context.isStrongValues() - ? getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(equalTo(key)))) - : getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - Int value = map.computeIfPresent(key, (k, v) -> context.absentValue()); - if (context.isStrongValues()) { - assertThat(value).isEqualTo(context.absentValue()); - assertThat(context.cache()).whenCleanedUp().hasSize(1); - - assertThat(context).evictionNotifications().withCause(COLLECTED) - .contains(collected).exclusively(); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - assertThat(context).removalNotifications().withCause(REPLACED).contains(key, replaced); - } else { - assertThat(value).isNull(); - assertThat(context.cache()).whenCleanedUp().isEmpty(); - assertThat(context).notifications().withCause(COLLECTED) - .contains(collected).exclusively(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void compute(Map map, CacheContext context) { - Int key = context.firstKey(); - Int replaced = new Int(context.original().get(key)); - Entry[] collected = context.isStrongValues() - ? getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(equalTo(key)))) - : getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - Int value = map.compute(key, (k, v) -> { - if (context.isStrongValues()) { - assertThat(v).isNotNull(); - } else { - assertThat(v).isNull(); - } - return context.absentValue(); - }); - assertThat(value).isEqualTo(context.absentValue()); - assertThat(context.cache()).whenCleanedUp().hasSize(1); - if (context.isStrongValues()) { - assertThat(context).evictionNotifications().withCause(COLLECTED).contains(collected).exclusively(); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - assertThat(context).removalNotifications().withCause(REPLACED).contains(key, replaced); - } else { - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, values = {ReferenceType.WEAK, ReferenceType.SOFT}, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void compute_nullValue(Map map, CacheContext context) { - Int key = context.firstKey(); - var collected = getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.compute(key, (k, v) -> { - assertThat(v).isNull(); - return null; - })).isNull(); - assertThat(context.cache()).whenCleanedUp().isEmpty(); - assertThat(context).notifications().withCause(COLLECTED).contains(collected).exclusively(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.COLLECTION, - stats = Stats.ENABLED, removalListener = Listener.DISABLED) - public void compute_weighted(Cache> cache, CacheContext context) { - Int key = context.absentKey(); - cache.put(key, Int.listOf(1)); - GcFinalization.awaitFullGc(); - cache.asMap().compute(key, (k, v) -> Int.listOf(1, 2, 3)); - assertThat(context).hasWeightedSize(3); - } - - @CacheSpec(population = Population.FULL, expiry = CacheExpiry.MOCKITO, - values = {ReferenceType.WEAK, ReferenceType.SOFT}) - @Test(dataProvider = "caches", expectedExceptions = IllegalStateException.class) - public void compute_expiryFails(Map map, CacheContext context) { - Int key = context.firstKey(); - context.clear(); - GcFinalization.awaitFullGc(); - try { - when(context.expiry().expireAfterCreate(any(), any(), anyLong())).thenThrow(IllegalStateException.class); - map.compute(key, (k, v) -> k); - } finally { - assertThat(map).doesNotContainKey(key); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.CONSUMING) - public void merge(Map map, CacheContext context) { - Int key = context.firstKey(); - Int replaced = new Int(context.original().get(key)); - var collected = context.isStrongValues() - ? getExpectedAfterGc(context, Maps.filterKeys(context.original(), not(equalTo(key)))) - : getExpectedAfterGc(context, context.original()); - context.clear(); - GcFinalization.awaitFullGc(); - Int value = map.merge(key, context.absentValue(), (oldValue, v) -> { - if (context.isWeakKeys() && !context.isStrongValues()) { - Assert.fail("Should not be invoked"); - } - return context.absentValue(); - }); - assertThat(value).isEqualTo(context.absentValue()); - assertThat(context.cache()).whenCleanedUp().hasSize(1); - if (context.isStrongValues()) { - assertThat(context).evictionNotifications().withCause(COLLECTED) - .contains(collected).exclusively(); - assertThat(context).removalNotifications().hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(COLLECTED).contains(collected); - assertThat(context).removalNotifications().withCause(REPLACED).contains(key, replaced); - } else { - assertThat(context).notifications().withCause(COLLECTED) - .contains(collected).exclusively(); - } - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.COLLECTION, - stats = Stats.ENABLED, removalListener = Listener.DISABLED) - public void merge_weighted(Cache> cache, CacheContext context) { - Int key = context.absentKey(); - cache.put(key, Int.listOf(1)); - GcFinalization.awaitFullGc(); - cache.asMap().merge(key, Int.listOf(1, 2, 3), (oldValue, v) -> { - if (context.isWeakKeys() && !context.isStrongValues()) { - Assert.fail("Should never be called"); - } - return v; - }); - assertThat(context).hasWeightedSize(3); - } - - @Test(dataProvider = "caches") - @CacheSpec(requiresWeakOrSoft = true) - public void iterators(Map map, CacheContext context) { - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.keySet().iterator().hasNext()).isFalse(); - assertThat(map.values().iterator().hasNext()).isFalse(); - assertThat(map.entrySet().iterator().hasNext()).isFalse(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.DISABLED) - public void keySet_toArray(Map map, CacheContext context) { - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.keySet().toArray()).isEmpty(); - assertThat(map.keySet().toArray(new Int[0])).isEmpty(); - assertThat(map.keySet().toArray(Int[]::new)).isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, keys = ReferenceType.WEAK, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void keySet_contains(Map map, CacheContext context) { - assertThat(map.keySet().contains(new Int(context.firstKey()))).isFalse(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.DISABLED) - public void values_toArray(Map map, CacheContext context) { - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.values().toArray()).isEmpty(); - assertThat(map.values().toArray(new Int[0])).isEmpty(); - assertThat(map.values().toArray(Int[]::new)).isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, values = {ReferenceType.WEAK, ReferenceType.SOFT}, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void values_contains(Map map, CacheContext context) { - Int value = new Int(context.original().get(context.firstKey())); - assertThat(map.values().contains(value)).isFalse(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.UNREACHABLE, weigher = CacheWeigher.DISABLED, - stats = Stats.ENABLED, removalListener = Listener.DISABLED) - public void entrySet_toArray(Map map, CacheContext context) { - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.entrySet().toArray()).isEmpty(); - assertThat(map.entrySet().toArray(new Map.Entry[0])).isEmpty(); - assertThat(map.entrySet().toArray(Map.Entry[]::new)).isEmpty(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_contains(Map map, CacheContext context) { - var entry = Map.entry(new Int(context.firstKey()), new Int(context.original().get(context.firstKey()))); - assertThat(map.entrySet().contains(entry)).isFalse(); - } - - @CheckNoStats - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true, - removalListener = {Listener.DISABLED, Listener.REJECTING}) - public void entrySet_contains_nullValue(Map map, CacheContext context) { - var entry = new AbstractMap.SimpleEntry<>(context.firstKey(), null); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.entrySet().contains(entry)).isFalse(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true) - public void entrySet_equals(Map map, CacheContext context) { - var expected = context.absent(); - map.putAll(expected); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.entrySet().equals(expected.entrySet())).isFalse(); - assertThat(expected.entrySet().equals(map.entrySet())).isFalse(); - assertThat(context.cache()).whenCleanedUp().hasSize(expected.size()); - assertThat(map.entrySet().equals(expected.entrySet())).isTrue(); - assertThat(expected.entrySet().equals(map.entrySet())).isTrue(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true) - public void equals(Map map, CacheContext context) { - var expected = context.absent(); - map.putAll(expected); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.equals(expected)).isFalse(); - assertThat(expected.equals(map)).isFalse(); - assertThat(context.cache()).whenCleanedUp().hasSize(expected.size()); - assertThat(map.equals(expected)).isTrue(); - assertThat(expected.equals(map)).isTrue(); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - population = Population.FULL, requiresWeakOrSoft = true) - public void equals_cleanUp(Map map, CacheContext context) { - var copy = context.original().entrySet().stream().collect(toImmutableMap(entry -> new Int(entry.getKey()), entry -> new Int(entry.getValue()))); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.equals(copy)).isFalse(); - assertThat(context.cache()).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true) - public void hashCode(Map map, CacheContext context) { - var expected = context.absent(); - map.putAll(expected); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(map.hashCode()).isEqualTo(expected.hashCode()); - assertThat(context.cache()).whenCleanedUp().hasSize(expected.size()); - assertThat(map.hashCode()).isEqualTo(expected.hashCode()); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.FULL, requiresWeakOrSoft = true) - public void toString(Map map, CacheContext context) { - var expected = context.absent(); - map.putAll(expected); - context.clear(); - GcFinalization.awaitFullGc(); - assertThat(parseToString(map)).containsExactlyEntriesIn(parseToString(expected)); - assertThat(context.cache()).whenCleanedUp().hasSize(expected.size()); - assertThat(parseToString(map)).containsExactlyEntriesIn(parseToString(expected)); - } - - private static Map parseToString(Map map) { - return Splitter.on(',').trimResults().omitEmptyStrings().withKeyValueSeparator("=") - .split(map.toString().replaceAll("[{}]", "")); - } - - @Test(dataProviderClass = ReferenceTest.class, dataProvider = "references") - public void reference(InternalReference reference, - Int item, boolean identity, boolean isKey) { - assertThat(reference.get()).isSameInstanceAs(item); - if (isKey) { - int hash = identity ? System.identityHashCode(item) : item.hashCode(); - assertThat(reference.getKeyReference()).isSameInstanceAs(reference); - assertThat(reference.toString()).contains("key=" + item); - assertThat(reference.hashCode()).isEqualTo(hash); - } else { - assertThat(reference.getKeyReference()).isSameInstanceAs(item); - assertThat(reference.toString()).contains("value=" + item); - } - } - - @Test - public void reference_equality() { - var first = new Int(1); - var second = new Int(1); - new EqualsTester() - .addEqualityGroup(new LookupKeyReference<>(first), new WeakKeyReference<>(first, null)) - .addEqualityGroup(new LookupKeyReference<>(second), new WeakKeyReference<>(second, null)) - .testEquals(); - new EqualsTester() - .addEqualityGroup( - new LookupKeyEqualsReference<>(first), new WeakKeyEqualsReference<>(first, null), - new LookupKeyEqualsReference<>(second), new WeakKeyEqualsReference<>(second, null)) - .testEquals(); - new EqualsTester() - .addEqualityGroup(new WeakValueReference<>(first, first, null), - new SoftValueReference<>(first, first, null)) - .addEqualityGroup(new WeakValueReference<>(second, second, null), - new SoftValueReference<>(second, second, null)) - .testEquals(); - } - - @DataProvider(name = "references") - public Object[][] providesReferences() { - var item = new Int(1); - return new Object[][]{ - new Object[]{new LookupKeyReference<>(item), item, true, true}, - new Object[]{new WeakKeyReference<>(item, null), item, true, true}, - new Object[]{new LookupKeyEqualsReference<>(item), item, false, true}, - new Object[]{new WeakKeyEqualsReference<>(item, null), item, false, true}, - new Object[]{new WeakValueReference<>(item, item, null), item, true, false}, - new Object[]{new SoftValueReference<>(item, item, null), item, true, false}, - }; - } - - private Entry[] getExpectedAfterGc(CacheContext context, Map original) { - var expected = new ArrayList>(); - original.forEach((key, value) -> { - key = context.isStrongKeys() ? new Int(key) : null; - value = context.isStrongValues() ? new Int(value) : null; - expected.add(new SimpleEntry<>(key, value)); - }); - return expected.toArray(Map.Entry[]::new); - } -} \ No newline at end of file diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java deleted file mode 100644 index fdd99cf..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java +++ /dev/null @@ -1,817 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.Policy.FixedRefresh; -import com.github.benmanes.caffeine.cache.testing.*; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import com.github.benmanes.caffeine.testing.Int; -import com.github.valfirst.slf4jtest.TestLoggerFactory; -import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.stream.IntStream; - -import static com.github.benmanes.caffeine.cache.RemovalCause.EXPLICIT; -import static com.github.benmanes.caffeine.cache.RemovalCause.REPLACED; -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContext.intern; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheSpec.Expiration.*; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.FutureSubject.assertThat; -import static com.github.benmanes.caffeine.testing.MapSubject.assertThat; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; -import static java.util.function.Function.identity; -import static org.hamcrest.Matchers.is; -import static uk.org.lidalia.slf4jext.Level.WARN; - -@CheckMaxLogLevel(WARN) -@Listeners(CacheValidationListener.class) -@SuppressWarnings("PreferJavaTimeOverload") -@Test(dataProviderClass = CacheProvider.class) -public final class RefreshAfterWriteTest { - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - refreshAfterWrite = Expire.ONE_MINUTE, executor = CacheExecutor.THREADED) - public void refreshIfNeeded_nonblocking(CacheContext context) { - Int key = context.absentKey(); - Int original = intern(Int.valueOf(1)); - Int refresh1 = intern(original.add(1)); - Int refresh2 = intern(refresh1.add(1)); - var duration = Duration.ofMinutes(2); - var refresh = new AtomicBoolean(); - var reloads = new AtomicInteger(); - var cache = context.build(new CacheLoader() { - @Override - public Int load(Int key) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture asyncReload(Int key, Int oldValue, Executor executor) { - reloads.incrementAndGet(); - await().untilTrue(refresh); - return oldValue.add(1).asFuture(); - } - }); - cache.put(key, original); - context.ticker().advance(duration); - ConcurrentTestHarness.execute(() -> cache.get(key)); - await().untilAtomic(reloads, is(1)); - assertThat(cache.get(key)).isEqualTo(original); - refresh.set(true); - cache.get(key); - await().untilAsserted(() -> assertThat(reloads.get()).isEqualTo(1)); - await().untilAsserted(() -> assertThat(cache).containsEntry(key, refresh1)); - await().untilAsserted(() -> assertThat(cache.policy().refreshes()).isEmpty()); - context.ticker().advance(duration); - assertThat(cache.get(key)).isEqualTo(refresh2); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - refreshAfterWrite = Expire.ONE_MINUTE, executor = CacheExecutor.THREADED) - public void refreshIfNeeded_failure(CacheContext context) { - Int key = context.absentKey(); - var reloads = new AtomicInteger(); - var cache = context.build(new CacheLoader() { - @Override - public Int load(Int key) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture asyncReload(Int key, Int oldValue, Executor executor) { - reloads.incrementAndGet(); - throw new IllegalStateException(); - } - }); - cache.put(key, key); - IntStream.range(0, 5).forEach(i -> { - context.ticker().advance(2, TimeUnit.MINUTES); - Int value = cache.get(key); - assertThat(value).isEqualTo(key); - await().untilAtomic(reloads, is(i + 1)); - }); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, - loader = Loader.REFRESH_INTERRUPTED, refreshAfterWrite = Expire.ONE_MINUTE, - executor = CacheExecutor.DIRECT) - public void refreshIfNeeded_interrupted(LoadingCache cache, CacheContext context) { - context.ticker().advance(2, TimeUnit.MINUTES); - cache.get(context.firstKey()); - assertThat(Thread.interrupted()).isTrue(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, removalListener = Listener.CONSUMING) - public void refreshIfNeeded_replace(LoadingCache cache, CacheContext context) { - cache.put(context.absentKey(), context.absentKey()); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.get(context.absentKey()); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.absentKey(), context.absentKey()) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, refreshAfterWrite = Expire.ONE_MINUTE, - removalListener = Listener.CONSUMING, loader = Loader.NULL) - public void refreshIfNeeded_remove(LoadingCache cache, CacheContext context) { - cache.put(context.absentKey(), context.absentValue()); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.get(context.absentKey()); - assertThat(cache).doesNotContainKey(context.absentKey()); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.absentKey(), context.absentValue()) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - refreshAfterWrite = Expire.ONE_MINUTE, population = Population.EMPTY, - removalListener = Listener.CONSUMING) - public void refreshIfNeeded_noChange(CacheContext context) { - var cache = context.build(new CacheLoader() { - @Override - public Int load(Int key) { - throw new IllegalStateException(); - } - - @Override - public Int reload(Int key, Int oldValue) { - return oldValue; - } - }); - cache.put(context.absentKey(), context.absentValue()); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.get(context.absentKey()); - assertThat(context).removalNotifications().isEmpty(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, - refreshAfterWrite = Expire.ONE_MINUTE, removalListener = Listener.CONSUMING, - loader = Loader.IDENTITY, executor = CacheExecutor.THREADED) - public void refreshIfNeeded_discard(LoadingCache cache, CacheContext context) { - context.executor().pause(); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.get(context.firstKey()); - assertThat(cache.policy().refreshes()).isNotEmpty(); - cache.put(context.firstKey(), context.absentValue()); - context.executor().resume(); - await().until(() -> context.executor().submitted() == context.executor().completed()); - assertThat(cache).containsEntry(context.firstKey(), context.absentValue()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(Map.entry(context.firstKey(), context.original().get(context.firstKey())), - Map.entry(context.firstKey(), context.firstKey())) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, - refreshAfterWrite = Expire.ONE_MINUTE, removalListener = Listener.CONSUMING, - loader = Loader.IDENTITY, executor = CacheExecutor.THREADED) - public void refreshIfNeeded_absent_newValue(LoadingCache cache, CacheContext context) { - context.executor().pause(); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.get(context.firstKey()); - assertThat(cache.policy().refreshes()).isNotEmpty(); - cache.invalidate(context.firstKey()); - context.executor().resume(); - await().until(() -> context.executor().submitted() == context.executor().completed()); - assertThat(cache).doesNotContainKey(context.firstKey()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.firstKey(), context.firstKey()); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.firstKey(), context.firstKey()); - assertThat(context).removalNotifications().hasSize(2); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.FULL, - refreshAfterWrite = Expire.ONE_MINUTE, removalListener = Listener.CONSUMING, - loader = Loader.NULL, executor = CacheExecutor.THREADED) - public void refreshIfNeeded_absent_nullValue(LoadingCache cache, CacheContext context) { - context.executor().pause(); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.get(context.firstKey()); - assertThat(cache.policy().refreshes()).isNotEmpty(); - cache.invalidate(context.firstKey()); - context.executor().resume(); - await().until(() -> context.executor().submitted() == context.executor().completed()); - assertThat(cache).doesNotContainKey(context.firstKey()); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(context.firstKey(), context.original().get(context.firstKey())) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - population = Population.EMPTY, refreshAfterWrite = Expire.ONE_MINUTE) - public void refreshIfNeeded_cancel_noLog(CacheContext context) { - var cacheLoader = new CacheLoader() { - @Override - public Int load(Int key) { - throw new AssertionError(); - } - - @Override - public CompletableFuture asyncReload( - Int key, Int oldValue, Executor executor) { - var future = new CompletableFuture(); - future.cancel(false); - return future; - } - }; - LoadingCache cache = context.isAsync() - ? context.buildAsync(cacheLoader).synchronous() - : context.build(cacheLoader); - cache.put(context.absentKey(), context.absentValue()); - context.ticker().advance(2, TimeUnit.MINUTES); - - cache.get(context.absentKey()); - assertThat(TestLoggerFactory.getLoggingEvents()).isEmpty(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - population = Population.EMPTY, refreshAfterWrite = Expire.ONE_MINUTE) - public void refreshIfNeeded_timeout_noLog(CacheContext context) { - var cacheLoader = new CacheLoader() { - @Override - public Int load(Int key) { - throw new AssertionError(); - } - - @Override - public CompletableFuture asyncReload(Int key, Int oldValue, Executor executor) { - var future = new CompletableFuture(); - future.orTimeout(0, TimeUnit.SECONDS); - await().until(future::isDone); - return future; - } - }; - LoadingCache cache = context.isAsync() - ? context.buildAsync(cacheLoader).synchronous() - : context.build(cacheLoader); - cache.put(context.absentKey(), context.absentValue()); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.get(context.absentKey()); - assertThat(TestLoggerFactory.getLoggingEvents()).isEmpty(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - population = Population.EMPTY, refreshAfterWrite = Expire.ONE_MINUTE) - public void refreshIfNeeded_error_log(CacheContext context) { - var expected = new RuntimeException(); - CacheLoader cacheLoader = key -> { - throw expected; - }; - LoadingCache cache = context.isAsync() - ? context.buildAsync(cacheLoader).synchronous() - : context.build(cacheLoader); - cache.put(context.absentKey(), context.absentValue()); - context.ticker().advance(2, TimeUnit.MINUTES); - - cache.get(context.absentKey()); - var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()); - assertThat(event.getThrowable().orElseThrow()).hasCauseThat().isSameInstanceAs(expected); - assertThat(event.getLevel()).isEqualTo(WARN); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - population = Population.EMPTY, refreshAfterWrite = Expire.ONE_MINUTE) - public void refreshIfNeeded_nullFuture(CacheContext context) { - var refreshed = new AtomicBoolean(); - CacheLoader loader = new CacheLoader<>() { - @Override - public Int load(Int key) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture asyncReload( - Int key, Int oldValue, Executor executor) { - refreshed.set(true); - return null; - } - }; - var cache = context.isAsync() - ? context.buildAsync(loader).synchronous() - : context.build(loader); - cache.put(context.absentKey(), context.absentValue()); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.get(context.absentKey()); - var event = Iterables.getOnlyElement(TestLoggerFactory.getLoggingEvents()); - assertThat(event.getThrowable().orElseThrow()).isInstanceOf(NullPointerException.class); - assertThat(event.getLevel()).isEqualTo(WARN); - assertThat(refreshed.get()).isTrue(); - assertThat(cache.policy().refreshes()).isEmpty(); - assertThat(cache).containsEntry(context.absentKey(), context.absentValue()); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.IDENTITY, population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void getIfPresent_immediate(LoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(cache.getIfPresent(context.middleKey())).isEqualTo(context.middleKey().negate()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.getIfPresent(context.middleKey())).isEqualTo(context.middleKey()); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.middleKey(), context.original().get(context.middleKey())) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.ASYNC_INCOMPLETE, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void getIfPresent_delayed(LoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(cache.getIfPresent(context.middleKey())).isEqualTo(context.middleKey().negate()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.getIfPresent(context.middleKey())).isEqualTo(context.middleKey().negate()); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().isEmpty(); - if (context.isCaffeine()) { - cache.policy().refreshes().get(context.middleKey()).complete(context.middleKey()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.middleKey(), context.original().get(context.middleKey())) - .exclusively(); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.NEGATIVE, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void getIfPresent_async(AsyncLoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(cache.getIfPresent(context.middleKey())).succeedsWith(context.middleKey().negate()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.getIfPresent(context.middleKey())).succeedsWith(context.middleKey().negate()); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.middleKey(), context.original().get(context.middleKey())) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.IDENTITY, - population = {Population.PARTIAL, Population.FULL}) - public void getAllPresent_immediate(LoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.getAllPresent(context.firstMiddleLastKeys()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.getAllPresent(context.firstMiddleLastKeys())).containsExactlyEntriesIn(Maps.asMap(context.firstMiddleLastKeys(), key -> key)); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(Maps.filterKeys(context.original(), context.firstMiddleLastKeys()::contains)) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.ASYNC_INCOMPLETE, - population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}) - public void getAllPresent_delayed(LoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - var expected = cache.getAllPresent(context.firstMiddleLastKeys()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.getAllPresent(context.firstMiddleLastKeys())).containsExactlyEntriesIn(expected); - if (context.isCaffeine()) { - var replaced = new HashMap(); - context.firstMiddleLastKeys().forEach(key -> { - cache.policy().refreshes().get(key).complete(key); - replaced.put(key, context.original().get(key)); - }); - assertThat(context).removalNotifications().withCause(REPLACED).contains(replaced).exclusively(); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.IDENTITY, - population = {Population.PARTIAL, Population.FULL}) - public void getFunc_immediate(LoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.get(context.firstKey(), identity()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.get(context.lastKey(), identity())).isEqualTo(context.lastKey()); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.lastKey(), context.original().get(context.lastKey())) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.ASYNC_INCOMPLETE, - population = {Population.PARTIAL, Population.FULL}) - public void getFunc_delayed(LoadingCache cache, CacheContext context) { - Function mappingFunction = context.original()::get; - context.ticker().advance(30, TimeUnit.SECONDS); - cache.get(context.firstKey(), mappingFunction); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.get(context.lastKey(), mappingFunction)).isEqualTo(context.lastKey().negate()); - if (context.isCaffeine()) { - cache.policy().refreshes().get(context.lastKey()).complete(context.lastKey()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.lastKey(), context.original().get(context.lastKey())) - .exclusively(); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, - population = {Population.PARTIAL, Population.FULL}) - public void getFunc_async(AsyncLoadingCache cache, CacheContext context) { - Function mappingFunction = context.original()::get; - context.ticker().advance(30, TimeUnit.SECONDS); - cache.get(context.firstKey(), mappingFunction).join(); - context.ticker().advance(45, TimeUnit.SECONDS); - cache.get(context.lastKey(), mappingFunction).join(); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.lastKey(), context.original().get(context.lastKey())) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.IDENTITY, population = {Population.PARTIAL, Population.FULL}) - public void get_immediate(LoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.get(context.firstKey()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.get(context.firstKey())).isEqualTo(context.firstKey()); - assertThat(cache).hasSize(context.initialSize()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.firstKey(), context.original().get(context.firstKey())) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.ASYNC_INCOMPLETE, - population = {Population.PARTIAL, Population.FULL}) - public void get_delayed(LoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.get(context.firstKey()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.get(context.firstKey())).isEqualTo(context.firstKey().negate()); - if (context.isCaffeine()) { - cache.policy().refreshes().get(context.firstKey()).complete(context.firstKey()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.firstKey(), context.original().get(context.firstKey())) - .exclusively(); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.IDENTITY, - population = {Population.PARTIAL, Population.FULL}) - public void get_async(AsyncLoadingCache cache, CacheContext context) { - context.ticker().advance(30, TimeUnit.SECONDS); - cache.get(context.firstKey()).join(); - cache.get(context.absentKey()).join(); - context.ticker().advance(45, TimeUnit.SECONDS); - var value = cache.getIfPresent(context.firstKey()); - assertThat(value).succeedsWith(context.firstKey()); - assertThat(cache).containsEntry(context.firstKey(), context.firstKey()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.firstKey(), context.original().get(context.firstKey())) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - refreshAfterWrite = Expire.ONE_MINUTE, executor = CacheExecutor.THREADED, - compute = Compute.ASYNC) - public void get_sameFuture(CacheContext context) { - var done = new AtomicBoolean(); - var cache = context.buildAsync((Int key) -> { - await().untilTrue(done); - return intern(key.negate()); - }); - Int key = Int.valueOf(1); - cache.synchronous().put(key, key); - var original = cache.get(key); - IntStream.range(0, 10).forEach(i -> { - context.ticker().advance(1, TimeUnit.MINUTES); - var next = cache.get(key); - assertThat(next).isSameInstanceAs(original); - }); - done.set(true); - await().untilAsserted(() -> assertThat(cache).containsEntry(key, key.negate())); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, population = Population.EMPTY, - refreshAfterWrite = Expire.ONE_MINUTE, executor = CacheExecutor.THREADED) - public void get_slowRefresh(CacheContext context) { - Int key = context.absentKey(); - Int originalValue = context.absentValue(); - Int refreshedValue = intern(originalValue.add(1)); - var started = new AtomicBoolean(); - var done = new AtomicBoolean(); - var cache = context.build((Int k) -> { - started.set(true); - await().untilTrue(done); - return refreshedValue; - }); - cache.put(key, originalValue); - context.ticker().advance(2, TimeUnit.MINUTES); - assertThat(cache.get(key)).isEqualTo(originalValue); - await().untilTrue(started); - assertThat(cache).containsEntry(key, originalValue); - context.ticker().advance(2, TimeUnit.MINUTES); - assertThat(cache.get(key)).isEqualTo(originalValue); - done.set(true); - await().untilAsserted(() -> assertThat(cache).containsEntry(key, refreshedValue)); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.NULL) - public void get_null(AsyncLoadingCache cache, CacheContext context) { - Int key = Int.valueOf(1); - cache.synchronous().put(key, key); - context.ticker().advance(2, TimeUnit.MINUTES); - assertThat(cache.get(key)).succeedsWith(key); - assertThat(cache.get(key)).succeedsWithNull(); - assertThat(cache).doesNotContainKey(key); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.IDENTITY, - population = {Population.PARTIAL, Population.FULL}) - public void getAll_immediate(LoadingCache cache, CacheContext context) { - var keys = List.of(context.firstKey(), context.absentKey()); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(cache.getAll(keys)).containsExactly(context.firstKey(), context.firstKey().negate(), context.absentKey(), context.absentKey()); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.getAll(keys)).containsExactly(context.firstKey(), context.firstKey(), context.absentKey(), context.absentKey()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.firstKey(), context.original().get(context.firstKey())) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.ASYNC_INCOMPLETE, - population = {Population.PARTIAL, Population.FULL}) - public void getAll_delayed(LoadingCache cache, CacheContext context) { - var keys = context.firstMiddleLastKeys(); - var expected = Maps.toMap(context.firstMiddleLastKeys(), Int::negate); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(cache.getAll(keys)).containsExactlyEntriesIn(expected); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(cache.getAll(keys)).containsExactlyEntriesIn(expected); - if (context.isCaffeine()) { - keys.forEach(key -> cache.policy().refreshes().get(key).complete(key)); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(Maps.filterKeys(context.original(), context.firstMiddleLastKeys()::contains)) - .exclusively(); - } - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE, loader = Loader.IDENTITY, population = {Population.PARTIAL, Population.FULL}) - public void getAll_async(AsyncLoadingCache cache, CacheContext context) { - var keys = List.of(context.firstKey(), context.absentKey()); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(cache.getAll(keys).join()).containsExactly( - context.firstKey(), context.firstKey().negate(), - context.absentKey(), context.absentKey()); - context.ticker().advance(45, TimeUnit.SECONDS); - cache.getAll(keys).join(); - assertThat(cache.getAll(keys).join()).containsExactly(context.firstKey(), context.firstKey(), context.absentKey(), context.absentKey()); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(context.firstKey(), context.original().get(context.firstKey())) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, refreshAfterWrite = Expire.ONE_MINUTE, - executor = CacheExecutor.THREADED, removalListener = Listener.CONSUMING) - public void put(CacheContext context) { - var started = new AtomicBoolean(); - var refresh = new AtomicBoolean(); - Int key = context.absentKey(); - Int original = Int.valueOf(1); - Int updated = Int.valueOf(2); - Int refreshed = Int.valueOf(3); - var cache = context.build((Int k) -> { - started.set(true); - await().untilTrue(refresh); - return refreshed; - }); - cache.put(key, original); - context.ticker().advance(2, TimeUnit.MINUTES); - assertThat(started.get()).isFalse(); - assertThat(cache.getIfPresent(key)).isEqualTo(original); - await().untilTrue(started); - assertThat(cache.asMap().put(key, updated)).isEqualTo(original); - refresh.set(true); - await().untilAsserted(() -> assertThat(context).removalNotifications().hasSize(2)); - assertThat(cache).containsEntry(key, updated); - assertThat(context).stats().success(1).failures(0); - assertThat(context).removalNotifications().withCause(REPLACED) - .contains(Map.entry(key, original), Map.entry(key, refreshed)) - .exclusively(); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, refreshAfterWrite = Expire.ONE_MINUTE, executor = CacheExecutor.THREADED, removalListener = Listener.CONSUMING) - public void invalidate(CacheContext context) { - var started = new AtomicBoolean(); - var refresh = new AtomicBoolean(); - Int key = context.absentKey(); - Int original = Int.valueOf(1); - Int refreshed = Int.valueOf(2); - var cache = context.build((Int k) -> { - started.set(true); - await().untilTrue(refresh); - return refreshed; - }); - cache.put(key, original); - context.ticker().advance(2, TimeUnit.MINUTES); - assertThat(started.get()).isFalse(); - assertThat(cache.getIfPresent(key)).isEqualTo(original); - await().untilTrue(started); - cache.invalidate(key); - refresh.set(true); - await().until(() -> context.executor().submitted() == context.executor().completed()); - if (context.isGuava()) { - assertThat(cache.getIfPresent(key)).isEqualTo(refreshed); - assertThat(context).removalNotifications().withCause(EXPLICIT) - .contains(key, original).exclusively(); - } else { - assertThat(cache.getIfPresent(key)).isNull(); - assertThat(context).removalNotifications().hasSize(2); - assertThat(context).removalNotifications().withCause(REPLACED).contains(key, original); - assertThat(context).removalNotifications().withCause(EXPLICIT).contains(key, refreshed); - } - assertThat(context).stats().success(1).failures(0); - } - - @CheckNoEvictions - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, - loader = Loader.ASYNC_INCOMPLETE, refreshAfterWrite = Expire.ONE_MINUTE) - public void refresh(LoadingCache cache, CacheContext context) { - cache.put(context.absentKey(), context.absentValue()); - int submitted; - submitted = context.executor().submitted(); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.getIfPresent(context.absentKey()); - assertThat(context.executor().submitted()).isEqualTo(submitted + 1); - var future1 = cache.refresh(context.absentKey()); - assertThat(context.executor().submitted()).isEqualTo(submitted + 1); - future1.complete(intern(context.absentValue().negate())); - submitted = context.executor().submitted(); - context.ticker().advance(2, TimeUnit.MINUTES); - cache.getIfPresent(context.absentKey()); - assertThat(context.executor().submitted()).isEqualTo(submitted + 1); - var future2 = cache.refresh(context.absentKey()); - assertThat(future2).isNotSameInstanceAs(future1); - future2.cancel(true); - } - - @Test(dataProvider = "caches") - @CacheSpec(implementation = Implementation.Caffeine, loader = Loader.ASYNC_INCOMPLETE, refreshAfterWrite = Expire.ONE_MINUTE, population = Population.FULL) - public void refreshes(LoadingCache cache, CacheContext context) { - context.ticker().advance(2, TimeUnit.MINUTES); - cache.getIfPresent(context.firstKey()); - assertThat(cache.policy().refreshes()).hasSize(1); - var future = cache.policy().refreshes().get(context.firstKey()); - assertThat(future).isNotNull(); - future.complete(Int.MAX_VALUE); - assertThat(cache.policy().refreshes()).isExhaustivelyEmpty(); - assertThat(cache).containsEntry(context.firstKey(), Int.MAX_VALUE); - } - - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE) - public void getRefreshesAfter(CacheContext context, FixedRefresh refreshAfterWrite) { - assertThat(refreshAfterWrite.getRefreshesAfter().toMinutes()).isEqualTo(1); - assertThat(refreshAfterWrite.getRefreshesAfter(TimeUnit.MINUTES)).isEqualTo(1); - } - - @Test(dataProvider = "caches", expectedExceptions = IllegalArgumentException.class) - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE) - public void setRefreshAfter_negative(Cache cache, CacheContext context, FixedRefresh refreshAfterWrite) { - refreshAfterWrite.setRefreshesAfter(Duration.ofMinutes(-2)); - } - - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE) - public void setRefreshAfter_excessive(Cache cache, - CacheContext context, FixedRefresh refreshAfterWrite) { - refreshAfterWrite.setRefreshesAfter(ChronoUnit.FOREVER.getDuration()); - assertThat(refreshAfterWrite.getRefreshesAfter(TimeUnit.NANOSECONDS)).isEqualTo(Long.MAX_VALUE); - } - - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE) - public void setRefreshesAfter(CacheContext context, FixedRefresh refreshAfterWrite) { - refreshAfterWrite.setRefreshesAfter(2, TimeUnit.MINUTES); - assertThat(refreshAfterWrite.getRefreshesAfter().toMinutes()).isEqualTo(2); - assertThat(refreshAfterWrite.getRefreshesAfter(TimeUnit.MINUTES)).isEqualTo(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE) - public void setRefreshesAfter_duration(CacheContext context, - FixedRefresh refreshAfterWrite) { - refreshAfterWrite.setRefreshesAfter(Duration.ofMinutes(2)); - assertThat(refreshAfterWrite.getRefreshesAfter().toMinutes()).isEqualTo(2); - assertThat(refreshAfterWrite.getRefreshesAfter(TimeUnit.MINUTES)).isEqualTo(2); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, - refreshAfterWrite = Expire.ONE_MINUTE) - public void ageOf(CacheContext context, FixedRefresh refreshAfterWrite) { - assertThat(refreshAfterWrite.ageOf(context.firstKey(), TimeUnit.SECONDS)).hasValue(0); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(refreshAfterWrite.ageOf(context.firstKey(), TimeUnit.SECONDS)).hasValue(30); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(refreshAfterWrite.ageOf(context.firstKey(), TimeUnit.SECONDS)).hasValue(75); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = {Population.SINGLETON, Population.PARTIAL, Population.FULL}, refreshAfterWrite = Expire.ONE_MINUTE) - public void ageOf_duration(CacheContext context, FixedRefresh refreshAfterWrite) { - assertThat(refreshAfterWrite.ageOf(context.firstKey()).orElseThrow().toSeconds()).isEqualTo(0); - context.ticker().advance(30, TimeUnit.SECONDS); - assertThat(refreshAfterWrite.ageOf(context.firstKey()).orElseThrow().toSeconds()).isEqualTo(30); - context.ticker().advance(45, TimeUnit.SECONDS); - assertThat(refreshAfterWrite.ageOf(context.firstKey()).orElseThrow().toSeconds()).isEqualTo(75); - } - - @Test(dataProvider = "caches") - @CacheSpec(refreshAfterWrite = Expire.ONE_MINUTE) - public void ageOf_absent(CacheContext context, FixedRefresh refreshAfterWrite) { - assertThat(refreshAfterWrite.ageOf(context.absentKey())).isEmpty(); - assertThat(refreshAfterWrite.ageOf(context.absentKey(), TimeUnit.SECONDS)).isEmpty(); - } - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, refreshAfterWrite = Expire.ONE_MINUTE, - expiry = {CacheExpiry.DISABLED, CacheExpiry.CREATE, CacheExpiry.WRITE, CacheExpiry.ACCESS}, - mustExpireWithAnyOf = {AFTER_ACCESS, AFTER_WRITE, VARIABLE}, expiryTime = Expire.ONE_MINUTE, - expireAfterAccess = {Expire.DISABLED, Expire.ONE_MINUTE}, - expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) - public void ageOf_expired(Cache cache, CacheContext context, FixedRefresh refreshAfterWrite) { - cache.put(context.absentKey(), context.absentValue()); - context.ticker().advance(2, TimeUnit.MINUTES); - assertThat(refreshAfterWrite.ageOf(context.absentKey(), TimeUnit.SECONDS)).isEmpty(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReserializableSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReserializableSubject.java deleted file mode 100644 index a5b37d6..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReserializableSubject.java +++ /dev/null @@ -1,231 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.Async.AsyncEvictionListener; -import com.github.benmanes.caffeine.cache.Async.AsyncExpiry; -import com.github.benmanes.caffeine.cache.Async.AsyncRemovalListener; -import com.github.benmanes.caffeine.cache.Async.AsyncWeigher; -import com.github.benmanes.caffeine.cache.BoundedLocalCache.BoundedLocalAsyncLoadingCache; -import com.github.benmanes.caffeine.cache.BoundedLocalCache.BoundedLocalManualCache; -import com.github.benmanes.caffeine.cache.LocalAsyncCache.AbstractCacheView; -import com.github.benmanes.caffeine.cache.UnboundedLocalCache.UnboundedLocalAsyncLoadingCache; -import com.github.benmanes.caffeine.cache.UnboundedLocalCache.UnboundedLocalManualCache; -import com.google.common.testing.SerializableTester; -import com.google.common.truth.FailureMetadata; -import com.google.common.truth.Subject; - -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.asyncCache; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.cache; - -public final class ReserializableSubject extends Subject { - private final Object actual; - - private ReserializableSubject(FailureMetadata metadata, Object subject) { - super(metadata, subject); - this.actual = subject; - } - - public static Factory> asyncReserializable() { - return ReserializableSubject::new; - } - - public static Factory> syncReserializable() { - return ReserializableSubject::new; - } - - public void isReserialize() { - var reserialized = SerializableTester.reserialize(actual); - checkIfAsyncCache(reserialized); - checkIfSyncCache(reserialized); - } - - private void checkIfSyncCache(Object reserialized) { - if (actual instanceof Cache) { - var copy = (Cache) reserialized; - check("valid").about(cache()).that(copy).isValid(); - check("empty").about(cache()).that(copy).isEmpty(); - } - if (actual instanceof LocalLoadingCache original) { - var copy = (LocalLoadingCache) reserialized; - check("cacheLoader").that(copy.cacheLoader()).isEqualTo(original.cacheLoader()); - } - if (actual instanceof BoundedLocalManualCache original) { - var copy = (BoundedLocalManualCache) reserialized; - checkBoundedLocalCache(original.cache, copy.cache); - } - if (actual instanceof UnboundedLocalManualCache original) { - var copy = (UnboundedLocalManualCache) reserialized; - checkUnboundedLocalCache(original.cache, copy.cache); - } - } - - private void checkIfAsyncCache(Object reserialized) { - if (actual instanceof AsyncCache) { - var copy = (AsyncCache) reserialized; - check("valid").about(asyncCache()).that(copy).isValid(); - check("empty").about(asyncCache()).that(copy).isEmpty(); - } - if (actual instanceof LocalAsyncLoadingCache original) { - var copy = (LocalAsyncLoadingCache) reserialized; - check("cacheLoader").that(copy.cacheLoader).isEqualTo(original.cacheLoader); - } - if (actual instanceof AbstractCacheView) { - var original = ((AbstractCacheView) actual).asyncCache(); - var copy = ((AbstractCacheView) reserialized).asyncCache(); - if (original instanceof BoundedLocalAsyncLoadingCache) { - checkBoundedAsyncLocalLoadingCache( - (BoundedLocalAsyncLoadingCache) original, - (BoundedLocalAsyncLoadingCache) copy); - } else if (original instanceof UnboundedLocalAsyncLoadingCache) { - checkUnboundedAsyncLocalLoadingCache( - (UnboundedLocalAsyncLoadingCache) original, - (UnboundedLocalAsyncLoadingCache) copy); - } - } - if (actual instanceof BoundedLocalAsyncLoadingCache original) { - var copy = (BoundedLocalAsyncLoadingCache) reserialized; - checkBoundedLocalCache(original.cache, copy.cache); - } - if (actual instanceof UnboundedLocalAsyncLoadingCache original) { - var copy = (UnboundedLocalAsyncLoadingCache) reserialized; - checkUnboundedLocalCache(original.cache, copy.cache); - } - } - - private void checkBoundedAsyncLocalLoadingCache( - BoundedLocalAsyncLoadingCache original, - BoundedLocalAsyncLoadingCache copy) { - check("cacheLoader").that(copy.cacheLoader).isEqualTo(original.cacheLoader); - checkBoundedLocalCache(original.cache, copy.cache); - } - - private void checkBoundedLocalCache(BoundedLocalCache original, BoundedLocalCache copy) { - check("nodeFactory").that(copy.nodeFactory).isInstanceOf(original.nodeFactory.getClass()); - checkEviction(original, copy); - checkExpiresAfterAccess(original, copy); - checkExpiresAfterWrite(original, copy); - checkExpiresVariable(original, copy); - checkRefreshAfterWrite(original, copy); - checkRemovalListener(original, copy); - checkEvictionListener(original, copy); - } - - private void checkRefreshAfterWrite(BoundedLocalCache original, BoundedLocalCache copy) { - check("refreshAfterWrite()").that(copy.refreshAfterWrite()) - .isEqualTo(original.refreshAfterWrite()); - if (original.refreshAfterWrite()) { - check("refreshAfterWriteNanos()").that(copy.refreshAfterWriteNanos()) - .isEqualTo(original.refreshAfterWriteNanos()); - } - } - - private void checkEviction(BoundedLocalCache original, BoundedLocalCache copy) { - check("evicts()").that(copy.evicts()).isEqualTo(original.evicts()); - check("weigher()").that(unwrapWeigher(copy.weigher)) - .isInstanceOf(unwrapWeigher(original.weigher).getClass()); - if (original.evicts()) { - check("maximum()").that(copy.maximum()).isEqualTo(original.maximum()); - } - } - - private void checkExpiresAfterAccess(BoundedLocalCache original, BoundedLocalCache copy) { - check("expiresAfterAccess()").that(copy.expiresAfterAccess()) - .isEqualTo(original.expiresAfterAccess()); - if (original.expiresAfterAccess()) { - check("expiresAfterAccessNanos()").that(copy.expiresAfterAccessNanos()) - .isEqualTo(original.expiresAfterAccessNanos()); - } - } - - private void checkExpiresAfterWrite(BoundedLocalCache original, BoundedLocalCache copy) { - check("expiresAfterWrite()").that(copy.expiresAfterWrite()) - .isEqualTo(original.expiresAfterWrite()); - if (original.expiresAfterWrite()) { - check("expiresAfterWriteNanos()").that(copy.expiresAfterWriteNanos()) - .isEqualTo(original.expiresAfterWriteNanos()); - } - } - - private void checkExpiresVariable(BoundedLocalCache original, BoundedLocalCache copy) { - check("expiresVariable()").that(copy.expiresVariable()) - .isEqualTo(original.expiresVariable()); - if (original.expiresVariable()) { - check("expiry()").that(unwrapExpiry(copy.expiry())) - .isInstanceOf(unwrapExpiry(original.expiry()).getClass()); - } - } - - private void checkRemovalListener( - BoundedLocalCache original, BoundedLocalCache copy) { - if (original.removalListener() == null) { - check("removalListener()").that(copy.removalListener()).isNull(); - } else if (copy.removalListener() == null) { - check("removalListener()").that(copy.removalListener()).isNotNull(); - } else if (original.evictionListener instanceof AsyncRemovalListener) { - var source = ((AsyncRemovalListener) original.removalListener()).delegate; - var target = ((AsyncRemovalListener) copy.removalListener()).delegate; - check("removalListener()").that(target).isInstanceOf(source.getClass()); - } else { - check("removalListener()").that(copy.removalListener()) - .isInstanceOf(original.removalListener().getClass()); - } - } - - private void checkEvictionListener( - BoundedLocalCache original, BoundedLocalCache copy) { - if (original.evictionListener == null) { - check("evictionListener").that(copy.evictionListener).isNull(); - } else if (copy.evictionListener == null) { - check("evictionListener").that(copy.evictionListener).isNotNull(); - } else if (original.evictionListener instanceof AsyncEvictionListener) { - var source = ((AsyncEvictionListener) original.evictionListener).delegate; - var target = ((AsyncEvictionListener) copy.evictionListener).delegate; - check("evictionListener").that(target).isInstanceOf(source.getClass()); - } else { - check("evictionListener").that(copy.evictionListener) - .isInstanceOf(original.evictionListener.getClass()); - } - } - - private Weigher unwrapWeigher(Weigher weigher) { - for (; ; ) { - if (weigher instanceof BoundedWeigher) { - weigher = ((BoundedWeigher) weigher).delegate; - } else if (weigher instanceof AsyncWeigher) { - weigher = ((AsyncWeigher) weigher).delegate; - } else { - return weigher; - } - } - } - - private Expiry unwrapExpiry(Expiry expiry) { - for (; ; ) { - if (expiry instanceof AsyncExpiry) { - expiry = ((AsyncExpiry) expiry).delegate; - } else { - return expiry; - } - } - } - - private void checkUnboundedAsyncLocalLoadingCache( - UnboundedLocalAsyncLoadingCache original, - UnboundedLocalAsyncLoadingCache copy) { - check("cacheLoader").that(copy.cacheLoader).isEqualTo(original.cacheLoader); - checkUnboundedLocalCache(original.cache, copy.cache); - } - - private void checkUnboundedLocalCache( - UnboundedLocalCache original, UnboundedLocalCache copy) { - check("ticker").that(copy.ticker).isEqualTo(original.ticker); - check("isRecordingStats").that(copy.isRecordingStats).isEqualTo(original.isRecordingStats); - if (original.removalListener == null) { - check("removalListener").that(copy.removalListener).isNull(); - } else if (copy.removalListener.getClass() != original.removalListener.getClass()) { - check("removalListener").that(copy.removalListener).isNotNull(); - check("removalListenerClass").that(copy.removalListener) - .isInstanceOf(original.removalListener.getClass()); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Reset.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Reset.java deleted file mode 100644 index 0057d3d..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Reset.java +++ /dev/null @@ -1,73 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.LocalAsyncCache.AbstractCacheView; -import org.jctools.util.UnsafeAccess; - -import java.util.Arrays; -import java.util.stream.IntStream; - -public final class Reset { - static final long PROBE = UnsafeAccess.fieldOffset(Thread.class, "threadLocalRandomProbe"); - static final long SEED = UnsafeAccess.fieldOffset(Thread.class, "threadLocalRandomSeed"); - static final int RANDOM_PROBE = 0x9e3779b9; - static final int RANDOM_SEED = 1033096058; - - private Reset() { - } - - public static void resetThreadLocalRandom() { - setThreadLocalRandom(RANDOM_PROBE, RANDOM_SEED); - } - - public static void setThreadLocalRandom(int probe, int seed) { - UnsafeAccess.UNSAFE.putInt(Thread.currentThread(), PROBE, probe); - UnsafeAccess.UNSAFE.putLong(Thread.currentThread(), SEED, seed); - } - - public static void destroy(Cache cache) { - var local = (cache instanceof AbstractCacheView) - ? ((AbstractCacheView) cache).asyncCache().cache() - : (LocalCache) cache.asMap(); - if (local instanceof UnboundedLocalCache unbounded) { - unbounded.data.clear(); - } else if (local instanceof BoundedLocalCache) { - @SuppressWarnings("unchecked") - var bounded = (BoundedLocalCache) local; - bounded.evictionLock.lock(); - try { - bounded.data.values().forEach(node -> destroyNode(bounded, node)); - if (bounded.expiresVariable()) { - destroyTimerWheel(bounded); - } - bounded.data.clear(); - } finally { - bounded.evictionLock.unlock(); - } - } - } - - @SuppressWarnings("GuardedBy") - private static void destroyNode(BoundedLocalCache bounded, Node node) { - if (bounded.expiresAfterAccess()) { - node.setPreviousInAccessOrder(null); - node.setNextInAccessOrder(null); - } - if (bounded.expiresAfterWrite()) { - node.setPreviousInWriteOrder(null); - node.setNextInWriteOrder(null); - } - if (bounded.expiresVariable()) { - node.setPreviousInVariableOrder(null); - node.setNextInVariableOrder(null); - } - node.die(); - } - - private static void destroyTimerWheel(BoundedLocalCache bounded) { - IntStream.range(0, bounded.timerWheel().wheel.length).forEach(i -> Arrays.stream(bounded.timerWheel().wheel[i]).forEach(sentinel -> { - sentinel.setPreviousInVariableOrder(sentinel); - sentinel.setNextInVariableOrder(sentinel); - })); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/SchedulerTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/SchedulerTest.java deleted file mode 100644 index ca8b19f..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/SchedulerTest.java +++ /dev/null @@ -1,172 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.google.common.testing.NullPointerTester; -import com.google.common.util.concurrent.Futures; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.util.Iterator; -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.ConcurrentTestHarness.executor; -import static com.github.benmanes.caffeine.testing.ConcurrentTestHarness.scheduledExecutor; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.util.concurrent.testing.TestingExecutors.sameThreadScheduledExecutor; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - - -@SuppressWarnings("FutureReturnValueIgnored") -public final class SchedulerTest { - private final NullPointerTester npeTester = new NullPointerTester(); - - @Test(dataProvider = "schedulers") - public void scheduler_null(Scheduler scheduler) { - npeTester.testAllPublicInstanceMethods(scheduler); - } - - @Test(dataProvider = "runnableSchedulers") - public void scheduler_exception(Scheduler scheduler) { - var executed = new AtomicBoolean(); - Executor executor = task -> { - executed.set(true); - throw new IllegalStateException(); - }; - scheduler.schedule(executor, () -> { - }, 1L, TimeUnit.NANOSECONDS); - await().untilTrue(executed); - } - - @Test(dataProvider = "runnableSchedulers") - public void scheduler(Scheduler scheduler) { - var executed = new AtomicBoolean(); - Runnable task = () -> executed.set(true); - scheduler.schedule(executor, task, 1L, TimeUnit.NANOSECONDS); - await().untilTrue(executed); - } - - @Test - public void disabledScheduler() { - var future = Scheduler.disabledScheduler().schedule(Runnable::run, () -> { - }, 1, TimeUnit.MINUTES); - assertThat(future).isSameInstanceAs(DisabledFuture.INSTANCE); - } - - @Test - public void disabledFuture() { - assertThat(DisabledFuture.INSTANCE.get(0, TimeUnit.SECONDS)).isNull(); - assertThat(DisabledFuture.INSTANCE.isCancelled()).isFalse(); - assertThat(DisabledFuture.INSTANCE.cancel(false)).isFalse(); - assertThat(DisabledFuture.INSTANCE.cancel(true)).isFalse(); - assertThat(DisabledFuture.INSTANCE.isDone()).isTrue(); - assertThat(DisabledFuture.INSTANCE.get()).isNull(); - } - - @Test - public void disabledFuture_null() { - npeTester.testAllPublicInstanceMethods(DisabledFuture.INSTANCE); - } - - @Test(expectedExceptions = NullPointerException.class) - public void guardedScheduler_null() { - Scheduler.guardedScheduler(null); - } - - @Test - public void guardedScheduler_nullFuture() { - var scheduledExecutor = Mockito.mock(ScheduledExecutorService.class); - var scheduler = Scheduler.forScheduledExecutorService(scheduledExecutor); - var executor = Mockito.mock(Executor.class); - Runnable command = () -> { - }; - var future = Scheduler.guardedScheduler(scheduler).schedule(executor, command, 1L, TimeUnit.MINUTES); - verify(scheduledExecutor).schedule(any(Runnable.class), eq(1L), eq(TimeUnit.MINUTES)); - assertThat(future).isSameInstanceAs(DisabledFuture.INSTANCE); - } - - @Test - public void guardedScheduler() { - var future = Scheduler.guardedScheduler((r, e, d, u) -> Futures.immediateVoidFuture()).schedule(Runnable::run, () -> { - }, 1, TimeUnit.MINUTES); - assertThat(future).isSameInstanceAs(Futures.immediateVoidFuture()); - } - - @Test - public void guardedScheduler_exception() { - var future = Scheduler.guardedScheduler((r, e, d, u) -> { - throw new RuntimeException(); - }).schedule(Runnable::run, () -> { - }, 1, TimeUnit.MINUTES); - assertThat(future).isSameInstanceAs(DisabledFuture.INSTANCE); - } - - @Test(expectedExceptions = NullPointerException.class) - public void scheduledExecutorService_null() { - Scheduler.forScheduledExecutorService(null); - } - - @Test - public void scheduledExecutorService_schedule() { - var scheduledExecutor = Mockito.mock(ScheduledExecutorService.class); - var task = ArgumentCaptor.forClass(Runnable.class); - var executor = Mockito.mock(Executor.class); - Runnable command = () -> { - }; - var scheduler = Scheduler.forScheduledExecutorService(scheduledExecutor); - var future = scheduler.schedule(executor, command, 1L, TimeUnit.MINUTES); - assertThat(future).isNotSameInstanceAs(DisabledFuture.INSTANCE); - verify(scheduledExecutor).isShutdown(); - verify(scheduledExecutor).schedule(task.capture(), eq(1L), eq(TimeUnit.MINUTES)); - verifyNoMoreInteractions(scheduledExecutor); - task.getValue().run(); - verify(executor).execute(command); - verifyNoMoreInteractions(executor); - } - - @Test - public void scheduledExecutorService_shutdown() { - var scheduledExecutor = Mockito.mock(ScheduledExecutorService.class); - var executor = Mockito.mock(Executor.class); - when(scheduledExecutor.isShutdown()).thenReturn(true); - var scheduler = Scheduler.forScheduledExecutorService(scheduledExecutor); - var future = scheduler.schedule(executor, () -> { - }, 1L, TimeUnit.MINUTES); - assertThat(future).isSameInstanceAs(DisabledFuture.INSTANCE); - verify(scheduledExecutor).isShutdown(); - verifyNoMoreInteractions(scheduledExecutor); - verifyNoInteractions(executor); - } - - @DataProvider(name = "schedulers") - public Iterator providesSchedulers() { - var schedulers = Set.of( - Scheduler.forScheduledExecutorService(sameThreadScheduledExecutor()), - Scheduler.forScheduledExecutorService(scheduledExecutor), - Scheduler.disabledScheduler(), - Scheduler.systemScheduler()); - return schedulers.iterator(); - } - - @DataProvider(name = "runnableSchedulers") - public Iterator providesRunnableSchedulers() { - var schedulers = Set.of( - Scheduler.forScheduledExecutorService(sameThreadScheduledExecutor()), - Scheduler.forScheduledExecutorService(scheduledExecutor), - Scheduler.systemScheduler()); - return schedulers.stream() - .filter(scheduler -> scheduler != Scheduler.disabledScheduler()) - .iterator(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java deleted file mode 100644 index e6e465f..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/Stresser.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import com.google.common.base.Stopwatch; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import org.apache.commons.lang3.StringUtils; -import picocli.CommandLine; -import picocli.CommandLine.Command; -import picocli.CommandLine.Help; -import picocli.CommandLine.Option; - -import java.time.LocalTime; -import java.util.Arrays; -import java.util.concurrent.Executors; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -import static java.util.Locale.US; -import static java.util.concurrent.TimeUnit.SECONDS; - - -@Command(mixinStandardHelpOptions = true) -public final class Stresser implements Runnable { - private static final String[] STATUS = {"Idle", "Required", "Processing -> Idle", "Processing -> Required"}; - private static final int MAX_THREADS = 2 * Runtime.getRuntime().availableProcessors(); - private static final int WRITE_MAX_SIZE = (1 << 12); - private static final int TOTAL_KEYS = (1 << 20); - private static final int MASK = TOTAL_KEYS - 1; - private static final int STATUS_INTERVAL = 5; - - @Option(names = "--workload", required = true, - description = "The workload type: ${COMPLETION-CANDIDATES}") - private Workload workload; - - private BoundedLocalCache local; - private LoadingCache cache; - private Stopwatch stopwatch; - private Integer[] ints; - - @Override - public void run() { - initialize(); - execute(); - } - - @SuppressWarnings("FutureReturnValueIgnored") - private void initialize() { - var threadFactory = new ThreadFactoryBuilder() - .setPriority(Thread.MAX_PRIORITY) - .setDaemon(true) - .build(); - Executors.newSingleThreadScheduledExecutor(threadFactory) - .scheduleAtFixedRate(this::status, STATUS_INTERVAL, STATUS_INTERVAL, SECONDS); - cache = Caffeine.newBuilder() - .maximumSize(workload.maxEntries) - .recordStats() - .build(key -> key); - local = (BoundedLocalCache) cache.asMap(); - ints = new Integer[TOTAL_KEYS]; - Arrays.setAll(ints, key -> { - cache.put(key, key); - return key; - }); - cache.cleanUp(); - local.refreshes(); - stopwatch = Stopwatch.createStarted(); - status(); - } - - @SuppressWarnings("FutureReturnValueIgnored") - private void execute() { - ConcurrentTestHarness.timeTasks(workload.maxThreads, () -> { - int index = ThreadLocalRandom.current().nextInt(); - for (; ; ) { - Integer key = ints[index++ & MASK]; - switch (workload) { - case READ -> cache.getIfPresent(key); - case WRITE -> cache.put(key, key); - case REFRESH -> cache.refresh(key); - } - } - }); - } - - private void status() { - int drainStatus; - int pendingWrites; - local.evictionLock.lock(); - try { - pendingWrites = local.writeBuffer.size(); - drainStatus = local.drainStatusAcquire(); - } finally { - local.evictionLock.unlock(); - } - var elapsedTime = LocalTime.ofSecondOfDay(stopwatch.elapsed(TimeUnit.SECONDS)); - System.out.printf(US, "---------- %s ----------%n", elapsedTime); - System.out.printf(US, "Pending reads: %,d; writes: %,d%n", local.readBuffer.size(), pendingWrites); - System.out.printf(US, "Drain status = %s (%s)%n", STATUS[drainStatus], drainStatus); - System.out.printf(US, "Evictions = %,d%n", cache.stats().evictionCount()); - System.out.printf(US, "Size = %,d (max: %,d)%n", local.data.mappingCount(), workload.maxEntries); - System.out.printf(US, "Lock = [%s%n", StringUtils.substringAfter( - local.evictionLock.toString(), "[")); - System.out.printf(US, "Pending reloads = %,d%n", local.refreshes.size()); - System.out.printf(US, "Pending tasks = %,d%n", ForkJoinPool.commonPool().getQueuedSubmissionCount()); - long maxMemory = Runtime.getRuntime().maxMemory(); - long freeMemory = Runtime.getRuntime().freeMemory(); - long allocatedMemory = Runtime.getRuntime().totalMemory(); - System.out.printf(US, "Max Memory = %,d bytes%n", maxMemory); - System.out.printf(US, "Free Memory = %,d bytes%n", freeMemory); - System.out.printf(US, "Allocated Memory = %,d bytes%n", allocatedMemory); - System.out.println(); - } - - public static void main(String[] args) { - new CommandLine(Stresser.class).setCommandName(Stresser.class.getSimpleName()).setColorScheme(Help.defaultColorScheme(Help.Ansi.ON)) - .setCaseInsensitiveEnumValuesAllowed(true).execute(args); - } - - private enum Workload { - READ(MAX_THREADS, TOTAL_KEYS), - WRITE(MAX_THREADS, WRITE_MAX_SIZE), - REFRESH(MAX_THREADS, TOTAL_KEYS / 4); - private final int maxThreads; - private final int maxEntries; - - Workload(int maxThreads, int maxEntries) { - this.maxThreads = maxThreads; - this.maxEntries = maxEntries; - } - - @Override - public String toString() { - return name().toLowerCase(US); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/StripedBufferTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/StripedBufferTest.java deleted file mode 100644 index 70aaa25..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/StripedBufferTest.java +++ /dev/null @@ -1,123 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import com.google.common.base.MoreObjects; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.stream.IntStream; - -import static com.github.benmanes.caffeine.cache.StripedBuffer.MAXIMUM_TABLE_SIZE; -import static com.github.benmanes.caffeine.cache.StripedBuffer.NCPU; -import static com.google.common.truth.Truth.assertThat; - -public final class StripedBufferTest { - static final Integer ELEMENT = 1; - - @Test(dataProvider = "buffers") - public void init(FakeBuffer buffer) { - assertThat(buffer.table).isNull(); - var result = buffer.offer(ELEMENT); - assertThat(buffer.table).hasLength(1); - assertThat(result).isEqualTo(Buffer.SUCCESS); - } - - @Test - public void expand() { - var buffer = new FakeBuffer(Buffer.FAILED); - assertThat(buffer.offer(ELEMENT)).isEqualTo(Buffer.SUCCESS); - if (IntStream.range(0, 64) - .map(i -> buffer.offer(ELEMENT)) - .anyMatch(result -> result == Buffer.SUCCESS)) { - return; - } - Assert.fail(); - } - - @Test - @SuppressWarnings("ThreadPriorityCheck") - public void expand_concurrent() { - var buffer = new FakeBuffer(Buffer.FAILED); - ConcurrentTestHarness.timeTasks(10 * NCPU, () -> IntStream.range(0, 1000).forEach(i -> { - buffer.offer(Boolean.TRUE); - Thread.yield(); - })); - assertThat(buffer.table).hasLength(MAXIMUM_TABLE_SIZE); - } - - @Test(dataProvider = "buffers") - @SuppressWarnings("ThreadPriorityCheck") - public void produce(FakeBuffer buffer) { - ConcurrentTestHarness.timeTasks(NCPU, () -> IntStream.range(0, 10).forEach(i -> { - buffer.offer(ELEMENT); - Thread.yield(); - })); - assertThat(buffer.table.length).isAtMost(MAXIMUM_TABLE_SIZE); - } - - @Test(dataProvider = "buffers") - public void drain(FakeBuffer buffer) { - buffer.drainTo(e -> { - }); - assertThat(buffer.drains).isEqualTo(0); - buffer.offer(ELEMENT); - buffer.drainTo(e -> { - }); - assertThat(buffer.drains).isEqualTo(1); - } - - @DataProvider(name = "buffers") - public Object[] providesBuffers() { - var results = List.of(Buffer.SUCCESS, Buffer.FAILED, Buffer.FULL); - return results.stream().>map(FakeBuffer::new).toArray(); - } - - static final class FakeBuffer extends StripedBuffer { - final int result; - int drains = 0; - - FakeBuffer(int result) { - this.result = result; - } - - @Override - protected Buffer create(E e) { - return new Buffer<>() { - @Override - public int offer(E e) { - return result; - } - - @Override - public void drainTo(Consumer consumer) { - drains++; - } - - @Override - public long size() { - return 0L; - } - - @Override - public long reads() { - return 0L; - } - - @Override - public long writes() { - return 0L; - } - }; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this).addValue(result).toString(); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java deleted file mode 100644 index ea931dc..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/TimerWheelTest.java +++ /dev/null @@ -1,576 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -import com.github.benmanes.caffeine.cache.TimerWheel.Sentinel; -import com.google.common.collect.Streams; -import it.unimi.dsi.fastutil.longs.LongArrayList; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.testng.Assert; -import org.testng.ITestResult; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.lang.ref.ReferenceQueue; -import java.time.Duration; -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.stream.IntStream; -import java.util.stream.LongStream; -import java.util.stream.Stream; - -import static com.github.benmanes.caffeine.cache.TimerWheel.SHIFT; -import static com.github.benmanes.caffeine.cache.TimerWheel.SPANS; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; -import static java.util.Locale.US; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.*; - - -@Test(singleThreaded = true) -@SuppressWarnings("GuardedBy") -public final class TimerWheelTest { - private static final Random random = new Random(); - private static final long[] CLOCKS = {Long.MIN_VALUE, -SPANS[0] + 1, 0L, 0xfffffffc0000000L, Long.MAX_VALUE - SPANS[0] + 1, Long.MAX_VALUE, random.nextLong()}; - - @Captor - ArgumentCaptor> captor; - @Mock - BoundedLocalCache cache; - TimerWheel timerWheel; - - @BeforeMethod - public void beforeMethod() throws Exception { - Reset.setThreadLocalRandom(random.nextInt(), random.nextInt()); - MockitoAnnotations.openMocks(this).close(); - timerWheel = new TimerWheel<>(); - } - - @AfterMethod - public void afterMethod(ITestResult testResult) { - if (!testResult.isSuccess()) { - printTimerWheel(); - } - } - - @Test(dataProvider = "schedule") - public void schedule(long clock, long duration, int expired) { - when(cache.evictEntry(captor.capture(), any(), anyLong())).thenReturn(true); - timerWheel.nanos = clock; - IntStream.of(25, 90, 240).forEach(timeout -> timerWheel.schedule(new Timer(clock + TimeUnit.SECONDS.toNanos(timeout)))); - timerWheel.advance(cache, clock + duration); - verify(cache, times(expired)).evictEntry(any(), any(), anyLong()); - captor.getAllValues().forEach(node -> assertThat(node.getVariableTime()).isAtMost(clock + duration)); - } - - @Test(dataProvider = "fuzzySchedule") - public void schedule_fuzzy(long clock, long duration, long[] times) { - when(cache.evictEntry(captor.capture(), any(), anyLong())).thenReturn(true); - timerWheel.nanos = clock; - int expired = 0; - for (long timeout : times) { - if (timeout <= duration) { - expired++; - } - timerWheel.schedule(new Timer(timeout)); - } - timerWheel.advance(cache, duration); - verify(cache, times(expired)).evictEntry(any(), any(), anyLong()); - captor.getAllValues().forEach(node -> assertThat(node.getVariableTime()).isAtMost(duration)); - checkTimerWheel(duration); - } - - @Test(dataProvider = "clock") - public void advance(long clock) { - when(cache.evictEntry(captor.capture(), any(), anyLong())).thenReturn(true); - timerWheel.nanos = clock; - timerWheel.schedule(new Timer(timerWheel.nanos + SPANS[0])); - timerWheel.advance(cache, clock + 13 * SPANS[0]); - verify(cache).evictEntry(any(), any(), anyLong()); - } - - @Test - public void advance_overflow() { - when(cache.evictEntry(captor.capture(), any(), anyLong())).thenReturn(true); - var timerWheel = new TimerWheel(); - timerWheel.nanos = -TimeUnit.DAYS.toNanos(365) / 2; - timerWheel.schedule(new Timer(timerWheel.nanos + SPANS[0])); - timerWheel.advance(cache, timerWheel.nanos + TimeUnit.DAYS.toNanos(365)); - verify(cache).evictEntry(any(), any(), anyLong()); - } - - @Test(dataProvider = "clock") - public void advance_backwards(long clock) { - var timerWheel = new TimerWheel(); - timerWheel.nanos = clock; - IntStream.range(0, 1_000).mapToLong(i -> ThreadLocalRandom.current().nextLong(TimeUnit.DAYS.toNanos(10))).mapToObj(duration -> new Timer(clock + duration)).forEach(timerWheel::schedule); - IntStream.range(0, TimerWheel.BUCKETS.length).forEach(i -> timerWheel.advance(cache, clock - 3 * SPANS[i])); - verifyNoInteractions(cache); - } - - @Test - public void advance_exception() { - doThrow(new IllegalStateException()) - .when(cache).evictEntry(captor.capture(), any(), anyLong()); - var timer = new Timer(timerWheel.nanos + SPANS[1]); - timerWheel.nanos = 0L; - timerWheel.schedule(timer); - try { - timerWheel.advance(cache, Long.MAX_VALUE); - Assert.fail(); - } catch (IllegalStateException e) { - assertThat(timerWheel.nanos).isEqualTo(0); - assertThat(timerWheel.wheel[1][1].getNextInVariableOrder()).isSameInstanceAs(timer); - } - } - - @Test(dataProvider = "clock") - public void getExpirationDelay_empty(long clock) { - when(cache.evictEntry(any(), any(), anyLong())).thenReturn(true); - timerWheel.nanos = clock; - assertThat(timerWheel.getExpirationDelay()).isEqualTo(Long.MAX_VALUE); - } - - @Test(dataProvider = "clock") - public void getExpirationDelay_firstWheel(long clock) { - when(cache.evictEntry(any(), any(), anyLong())).thenReturn(true); - timerWheel.nanos = clock; - long delay = Duration.ofSeconds(1).toNanos(); - timerWheel.schedule(new Timer(clock + delay)); - assertThat(timerWheel.getExpirationDelay()).isAtMost(SPANS[0]); - } - - @Test(dataProvider = "clock") - public void getExpirationDelay_lastWheel(long clock) { - when(cache.evictEntry(any(), any(), anyLong())).thenReturn(true); - timerWheel.nanos = clock; - long delay = Duration.ofDays(14).toNanos(); - timerWheel.schedule(new Timer(clock + delay)); - assertThat(timerWheel.getExpirationDelay()).isAtMost(delay); - } - - @Test(dataProvider = "clock") - public void getExpirationDelay_hierarchy(long clock) { - when(cache.evictEntry(any(), any(), anyLong())).thenReturn(true); - timerWheel.nanos = clock; - long t15 = clock + Duration.ofSeconds(15).toNanos(); - long t80 = clock + Duration.ofSeconds(80).toNanos(); - timerWheel.schedule(new Timer(t15)); - timerWheel.schedule(new Timer(t80)); - long t45 = clock + Duration.ofSeconds(45).toNanos(); - timerWheel.advance(cache, t45); - long t95 = clock + Duration.ofSeconds(95).toNanos(); - timerWheel.schedule(new Timer(t95)); - long expectedDelay = (t80 - t45); - long delay = timerWheel.getExpirationDelay(); - assertThat(delay).isLessThan(expectedDelay + SPANS[0]); - } - - @Test(dataProvider = "fuzzySchedule", invocationCount = 25) - public void getExpirationDelay_fuzzy(long clock, long duration, long[] times) { - when(cache.evictEntry(any(), any(), anyLong())).thenReturn(true); - timerWheel.nanos = clock; - for (long timeout : times) { - timerWheel.schedule(new Timer(timeout)); - } - timerWheel.advance(cache, duration); - long minDelay = Long.MAX_VALUE; - int minSpan = Integer.MAX_VALUE; - int minBucket = Integer.MAX_VALUE; - for (int i = 0; i < timerWheel.wheel.length; i++) { - for (int j = 0; j < timerWheel.wheel[i].length; j++) { - var timers = getTimers(timerWheel.wheel[i][j]); - for (int k = 0; k < timers.size(); k++) { - long delay = timers.getLong(k); - if (delay < minDelay) { - minDelay = delay; - minBucket = j; - minSpan = i; - } - } - } - } - long delay = timerWheel.getExpirationDelay(); - if (minDelay == Long.MAX_VALUE) { - var format = "delay=%s but minDelay=%s, minSpan=%s, minBucket=%s"; - assertWithMessage(format, delay, minDelay, minSpan, minBucket).that(delay).isEqualTo(Long.MAX_VALUE); - return; - } - long maxError = minDelay + SPANS[minSpan]; - if (maxError > delay) { - var format = "delay=%s but minDelay=%s, minSpan=%s, minBucket=%s"; - assertWithMessage(format, delay, minDelay, minSpan, minBucket).that(delay).isLessThan(maxError); - } - } - - @DataProvider(name = "clock") - public Iterator providesClock() { - return LongStream.of(CLOCKS).mapToObj(o -> (Object) o).iterator(); - } - - @DataProvider(name = "schedule") - public Iterator providesSchedule() { - var args = new ArrayList(); - Arrays.stream(CLOCKS).forEach(clock -> { - args.add(new Object[]{clock, TimeUnit.SECONDS.toNanos(10), 0}); - args.add(new Object[]{clock, TimeUnit.MINUTES.toNanos(3), 2}); - args.add(new Object[]{clock, TimeUnit.MINUTES.toNanos(10), 3}); - }); - return args.iterator(); - } - - @DataProvider(name = "fuzzySchedule") - public Object[][] providesFuzzySchedule() { - long[] times; - long clock = ThreadLocalRandom.current().nextLong(); - long bound = clock + TimeUnit.DAYS.toNanos(1) + SPANS[SPANS.length - 1]; - times = IntStream.range(0, 5_000).mapToLong(i -> ThreadLocalRandom.current().nextLong(clock + 1, bound)).toArray(); - long duration = ThreadLocalRandom.current().nextLong(clock + 1, bound); - return new Object[][]{{clock, duration, times}}; - } - - private void checkTimerWheel(long duration) { - for (int i = 0; i < timerWheel.wheel.length; i++) { - for (int j = 0; j < timerWheel.wheel[i].length; j++) { - for (long timer : getTimers(timerWheel.wheel[i][j])) { - if (timer <= duration) { - throw new AssertionError(String.format(US, "wheel[%s][%d] by %ss", i, j, - TimeUnit.NANOSECONDS.toSeconds(duration - timer))); - } - } - } - } - } - - private LongArrayList getTimers(Node sentinel) { - var timers = new LongArrayList(); - Stream.iterate(sentinel.getNextInVariableOrder(), node -> node != sentinel, Node::getNextInVariableOrder) - .mapToLong(Node::getVariableTime).forEach(timers::add); - return timers; - } - - @Test(dataProvider = "clock") - public void reschedule(long clock) { - when(cache.evictEntry(captor.capture(), any(), anyLong())).thenReturn(true); - timerWheel.nanos = clock; - var timer = new Timer(clock + TimeUnit.MINUTES.toNanos(15)); - timerWheel.schedule(timer); - var startBucket = timer.getNextInVariableOrder(); - timer.setVariableTime(clock + TimeUnit.HOURS.toNanos(2)); - timerWheel.reschedule(timer); - assertThat(timer.getNextInVariableOrder()).isNotSameInstanceAs(startBucket); - timerWheel.advance(cache, clock + TimeUnit.DAYS.toNanos(1)); - checkEmpty(); - } - - private void checkEmpty() { - for (int i = 0; i < timerWheel.wheel.length; i++) { - for (int j = 0; j < timerWheel.wheel[i].length; j++) { - var sentinel = timerWheel.wheel[i][j]; - assertThat(sentinel.getNextInVariableOrder()).isSameInstanceAs(sentinel); - assertThat(sentinel.getPreviousInVariableOrder()).isSameInstanceAs(sentinel); - } - } - } - - @Test(dataProvider = "clock") - public void deschedule(long clock) { - var timer = new Timer(clock + 100); - timerWheel.nanos = clock; - timerWheel.schedule(timer); - timerWheel.deschedule(timer); - assertThat(timer.getNextInVariableOrder()).isNull(); - assertThat(timer.getPreviousInVariableOrder()).isNull(); - } - - @Test(dataProvider = "clock") - public void deschedule_notScheduled(long clock) { - timerWheel.nanos = clock; - timerWheel.deschedule(new Timer(clock + 100)); - } - - @Test(dataProvider = "fuzzySchedule") - public void deschedule_fuzzy(long clock, long nanos, long[] times) { - var timers = new ArrayList(); - timerWheel.nanos = clock; - Arrays.stream(times).mapToObj(Timer::new).forEach(timer -> { - timerWheel.schedule(timer); - timers.add(timer); - }); - timers.forEach(timer -> timerWheel.deschedule(timer)); - checkTimerWheel(nanos); - } - - @Test(dataProvider = "clock") - public void expire_reschedule(long clock) { - when(cache.evictEntry(captor.capture(), any(), anyLong())).thenAnswer(invocation -> { - var timer = (Timer) invocation.getArgument(0); - timer.setVariableTime(timerWheel.nanos + 100); - return false; - }); - timerWheel.nanos = clock; - timerWheel.schedule(new Timer(clock + 100)); - timerWheel.advance(cache, clock + SPANS[0]); - verify(cache).evictEntry(any(), any(), anyLong()); - assertThat(captor.getValue().getNextInVariableOrder()).isNotNull(); - assertThat(captor.getValue().getPreviousInVariableOrder()).isNotNull(); - } - - @Test(dataProvider = "cascade") - public void cascade(long clock, long duration, long timeout, int span) { - timerWheel.nanos = clock; - timerWheel.schedule(new Timer(clock + timeout)); - timerWheel.advance(cache, clock + duration); - int count = 0; - for (int i = 0; i <= span; i++) { - for (int j = 0; j < timerWheel.wheel[i].length; j++) { - count += getTimers(timerWheel.wheel[i][j]).size(); - } - } - assertThat(count).isEqualTo(1); - } - - @DataProvider(name = "cascade") - public Iterator providesCascade() { - var args = new ArrayList(); - IntStream.range(1, SPANS.length - 1).forEach(i -> { - long span = SPANS[i]; - long timeout = ThreadLocalRandom.current().nextLong(span + 1, 2 * span); - long duration = ThreadLocalRandom.current().nextLong(span + 1, timeout - 1); - Arrays.stream(CLOCKS).mapToObj(clock -> new Object[]{clock, duration, timeout, i}).forEach(args::add); - }); - return args.iterator(); - } - - @Test(dataProvider = "iterator") - public void iterator_hasNext(Iterable> iterable) { - var iterator = iterable.iterator(); - assertThat(iterator.hasNext()).isFalse(); - timerWheel.schedule(new Timer(1)); - assertThat(iterator.hasNext()).isFalse(); - iterator = iterable.iterator(); - assertThat(iterator.hasNext()).isTrue(); - assertThat(iterator.hasNext()).isTrue(); - iterator.next(); - assertThat(iterator.hasNext()).isFalse(); - try { - iterator.next(); - Assert.fail(); - } catch (NoSuchElementException expected) { - } - } - - @DataProvider(name = "iterator") - @SuppressWarnings("MethodReferenceUsage") - public Object[][] providesIterators() { - Iterable> descending = () -> timerWheel.descendingIterator(); - Iterable> ascending = () -> timerWheel.iterator(); - return new Object[][]{{ascending}, {descending}}; - } - - @Test(dataProvider = "clock") - public void iterator_fixed(long clock) { - timerWheel.nanos = clock; - var input = IntStream.range(0, 21).mapToLong(i -> { - long time = clock + TimeUnit.SECONDS.toNanos(2L << i); - timerWheel.schedule(new Timer(time)); - return time; - }).boxed().collect(toImmutableList()); - var ascending = Streams.stream(timerWheel.iterator()).limit(input.size() + 1).map(Node::getKey).collect(toImmutableList()); - assertThat(ascending).containsExactlyElementsIn(input).inOrder(); - var descending = Streams.stream(timerWheel.descendingIterator()).limit(input.size() + 1).map(Node::getKey).collect(toImmutableList()); - assertThat(descending).containsExactlyElementsIn(input.reverse()).inOrder(); - } - - @Test(invocationCount = 25) - public void iterator_random() { - int range = ThreadLocalRandom.current().nextInt(0, 1000); - timerWheel.nanos = ThreadLocalRandom.current().nextLong(TimeUnit.MILLISECONDS.toNanos(500), TimeUnit.DAYS.toNanos(7)); - var input = IntStream.range(0, range).mapToLong(i -> ThreadLocalRandom.current().nextLong( - TimeUnit.SECONDS.toNanos(1), TimeUnit.DAYS.toNanos(7))).boxed().sorted().collect(toImmutableList()); - input.forEach(time -> timerWheel.schedule(new Timer(time))); - var ascending = Streams.stream(timerWheel.iterator()).limit(range + 1).map(Node::getKey).collect(toImmutableList()); - assertThat(ascending).containsExactlyElementsIn(input); - assertThat(ascending).containsExactlyElementsIn(snapshot(true)).inOrder(); - var descending = Streams.stream(timerWheel.descendingIterator()).limit(range + 1).map(Node::getKey).collect(toImmutableList()); - assertThat(descending).containsExactlyElementsIn(snapshot(false)).inOrder(); - assertThat(descending).containsExactlyElementsIn(input); - } - - @Test - public void sentinel_ignored() { - var node = new Sentinel<>(); - node.setValue(new Object(), null); - node.retire(); - node.die(); - assertThat(node.getKey()).isNull(); - assertThat(node.getValue()).isNull(); - assertThat(node.containsValue(new Object())).isFalse(); - assertThat(node.isAlive()).isFalse(); - assertThat(node.isRetired()).isFalse(); - assertThat(node.isDead()).isFalse(); - } - - @Test - public void sentinel_unsupported() { - var node = new Sentinel<>(); - List methods = List.of(node::getKeyReference, node::getValueReference); - methods.forEach(method -> { - try { - method.run(); - Assert.fail(); - } catch (UnsupportedOperationException expected) { - } - }); - } - - private List snapshot(boolean ascending) { - var snapshot = new ArrayList(); - int startLevel = ascending ? 0 : timerWheel.wheel.length - 1; - Function, Node> successor = - ascending ? Node::getNextInVariableOrder : Node::getPreviousInVariableOrder; - for (int i = 0; i < timerWheel.wheel.length; i++) { - int indexOffset = ascending ? i : -i; - int index = startLevel + indexOffset; - int ticks = (int) (timerWheel.nanos >>> SHIFT[index]); - int bucketMask = (timerWheel.wheel[index].length - 1); - int startBucket = (ticks & bucketMask) + (ascending ? 1 : 0); - for (int j = 0; j < timerWheel.wheel[index].length; j++) { - int bucketOffset = ascending ? j : -j; - var sentinel = timerWheel.wheel[index][(startBucket + bucketOffset) & bucketMask]; - for (var node = successor.apply(sentinel); node != sentinel; node = successor.apply(node)) { - if ((node.getKey() != null) && (node.getValue() != null) && node.isAlive()) { - snapshot.add(node.getVariableTime()); - } - } - } - } - return snapshot; - } - - private void printTimerWheel() { - var builder = new StringBuilder(); - for (int i = 0; i < timerWheel.wheel.length; i++) { - int ticks = (int) (timerWheel.nanos >>> SHIFT[i]); - int bucketMask = (timerWheel.wheel[i].length - 1); - int index = (ticks & bucketMask); - var buckets = new TreeMap>(); - for (int j = 0; j < timerWheel.wheel[i].length; j++) { - var events = new ArrayList(); - for (var node = timerWheel.wheel[i][j].getNextInVariableOrder(); - node != timerWheel.wheel[i][j]; node = node.getNextInVariableOrder()) { - events.add(node.getKey()); - } - if (j == index) { - buckets.put("*" + j, events); - } else if (!events.isEmpty()) { - buckets.put(Integer.toString(j), events); - } - } - builder.append(" - Wheel #").append(i + 1).append(": ").append(buckets).append('\n'); - } - System.err.printf(US, "%nCurrent state:%n%s%n%n", builder.deleteCharAt(builder.length() - 1)); - } - - private static final class Timer extends Node { - Node prev; - Node next; - long variableTime; - - Timer(long accessTime) { - setVariableTime(accessTime); - } - - @Override - public long getVariableTime() { - return variableTime; - } - - @Override - public void setVariableTime(long variableTime) { - this.variableTime = variableTime; - } - - @Override - public Node getPreviousInVariableOrder() { - return prev; - } - - @Override - public void setPreviousInVariableOrder(@Nullable Node prev) { - this.prev = prev; - } - - @Override - public Node getNextInVariableOrder() { - return next; - } - - @Override - public void setNextInVariableOrder(@Nullable Node next) { - this.next = next; - } - - @Override - public Long getKey() { - return variableTime; - } - - @Override - public Object getKeyReference() { - return null; - } - - @Override - public Long getValue() { - return variableTime; - } - - @Override - public Object getValueReference() { - return null; - } - - @Override - public void setValue(Long value, ReferenceQueue referenceQueue) { - } - - @Override - public boolean containsValue(Object value) { - return false; - } - - @Override - public boolean isAlive() { - return true; - } - - @Override - public boolean isRetired() { - return false; - } - - @Override - public boolean isDead() { - return false; - } - - @Override - public void retire() { - } - - @Override - public void die() { - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/UnboundedLocalCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/UnboundedLocalCacheTest.java deleted file mode 100644 index 151aee4..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/UnboundedLocalCacheTest.java +++ /dev/null @@ -1,35 +0,0 @@ - -package com.github.benmanes.caffeine.cache; - -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.Expire; -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.CacheValidationListener; -import com.github.benmanes.caffeine.cache.testing.CheckMaxLogLevel; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import static com.google.common.truth.Truth8.assertThat; -import static uk.org.lidalia.slf4jext.Level.TRACE; - -@CheckMaxLogLevel(TRACE) -@Listeners(CacheValidationListener.class) -@Test(dataProviderClass = CacheProvider.class) -public final class UnboundedLocalCacheTest { - @CacheSpec(population = Population.EMPTY, refreshAfterWrite = Expire.DISABLED, - expireAfterAccess = Expire.DISABLED, expireAfterWrite = Expire.DISABLED, - maximumSize = Maximum.DISABLED, weigher = CacheWeigher.DISABLED, - keys = ReferenceType.STRONG, values = ReferenceType.STRONG) - @Test(dataProvider = "caches") - public void noPolicy(Cache cache, CacheContext context) { - assertThat(cache.policy().eviction()).isEmpty(); - assertThat(cache.policy().expireAfterWrite()).isEmpty(); - assertThat(cache.policy().expireAfterAccess()).isEmpty(); - assertThat(cache.policy().refreshAfterWrite()).isEmpty(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/BufferTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/BufferTest.java deleted file mode 100644 index 874ecdd..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/BufferTest.java +++ /dev/null @@ -1,62 +0,0 @@ - -package com.github.benmanes.caffeine.cache.buffer; - -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.util.Arrays; -import java.util.Iterator; - -import static com.google.common.truth.Truth.assertThat; - -public final class BufferTest { - @DataProvider - public Iterator buffers() { - return Arrays.stream(BufferType.values()) - .map(factory -> new Object[]{factory.create()}) - .iterator(); - } - - @Test(dataProvider = "buffers") - @SuppressWarnings("ThreadPriorityCheck") - public void record(ReadBuffer buffer) { - ConcurrentTestHarness.timeTasks(100, () -> { - for (int i = 0; i < 1000; i++) { - buffer.offer(Boolean.TRUE); - Thread.yield(); - } - }); - long recorded = buffer.writes(); - assertThat(recorded).isEqualTo(ReadBuffer.BUFFER_SIZE); - } - - @Test(dataProvider = "buffers") - public void drain(ReadBuffer buffer) { - for (int i = 0; i < 2 * ReadBuffer.BUFFER_SIZE; i++) { - buffer.offer(Boolean.TRUE); - } - buffer.drain(); - long drained = buffer.reads(); - long recorded = buffer.writes(); - assertThat(drained).isEqualTo(recorded); - } - - @Test(dataProvider = "buffers") - @SuppressWarnings("ThreadPriorityCheck") - public void recordAndDrain(ReadBuffer buffer) { - ConcurrentTestHarness.timeTasks(100, () -> { - for (int i = 0; i < 1000; i++) { - int result = buffer.offer(Boolean.TRUE); - if (result == ReadBuffer.FULL) { - buffer.drain(); - } - Thread.yield(); - } - }); - buffer.drain(); - long drained = buffer.reads(); - long recorded = buffer.writes(); - assertThat(drained).isEqualTo(recorded); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/BufferType.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/BufferType.java deleted file mode 100644 index f95fab4..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/BufferType.java +++ /dev/null @@ -1,26 +0,0 @@ - -package com.github.benmanes.caffeine.cache.buffer; - -import java.util.function.Supplier; - - -@SuppressWarnings("ImmutableEnumChecker") -public enum BufferType { - Ticket(TicketBuffer::new), - FastFlow(FastFlowBuffer::new), - MpscArray(MpscArrayBuffer::new), - ManyToOne(ManyToOneBuffer::new), - ManyToOne_spaced(ManyToOneSpacedBuffer::new), - MpmcArray(MpmcArrayBuffer::new), - MpscCompound(MpscCompoundBuffer::new); - - private final Supplier> factory; - - BufferType(Supplier> factory) { - this.factory = factory; - } - - public ReadBuffer create() { - return factory.get(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/FastFlowBuffer.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/FastFlowBuffer.java deleted file mode 100644 index 2637ebc..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/FastFlowBuffer.java +++ /dev/null @@ -1,164 +0,0 @@ - -package com.github.benmanes.caffeine.cache.buffer; - -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; - - -final class FastFlowBuffer extends FastFlowHeader.ReadAndWriteCounterRef { - static final VarHandle BUFFER = MethodHandles.arrayElementVarHandle(Object[].class); - - final Object[] buffer; - - public FastFlowBuffer() { - buffer = new Object[BUFFER_SIZE]; - } - - @Override - public int offer(E e) { - long head = readCache; - long tail = writeCounterOpaque(); - long wrap = (tail - BUFFER_SIZE); - if (head <= wrap) { - head = readCounter; - if (head <= wrap) { - return FULL; - } - READ_CACHE.setRelease(this, head); - } - if (casWriteCounter(tail, tail + 1)) { - int index = (int) (tail & BUFFER_MASK); - BUFFER.setRelease(buffer, index, e); - return SUCCESS; - } - return FAILED; - } - - @Override - public void drainTo(Consumer consumer) { - long head = readCounter; - long tail = writeCounterOpaque(); - long size = (tail - head); - if (size == 0) { - return; - } - do { - int index = (int) (head & BUFFER_MASK); - E e = (E) BUFFER.getAcquire(buffer, index); - if (e == null) { - break; - } - BUFFER.setRelease(buffer, index, null); - consumer.accept(e); - head++; - } while (head != tail); - setReadCounterOpaque(head); - } - - @Override - public long reads() { - return readCounter; - } - - @Override - public long writes() { - return writeCounter; - } -} - -final class FastFlowHeader { - - abstract static class PadReadCache extends ReadBuffer { - byte p000, p001, p002, p003, p004, p005, p006, p007; - byte p008, p009, p010, p011, p012, p013, p014, p015; - byte p016, p017, p018, p019, p020, p021, p022, p023; - byte p024, p025, p026, p027, p028, p029, p030, p031; - byte p032, p033, p034, p035, p036, p037, p038, p039; - byte p040, p041, p042, p043, p044, p045, p046, p047; - byte p048, p049, p050, p051, p052, p053, p054, p055; - byte p056, p057, p058, p059, p060, p061, p062, p063; - byte p064, p065, p066, p067, p068, p069, p070, p071; - byte p072, p073, p074, p075, p076, p077, p078, p079; - byte p080, p081, p082, p083, p084, p085, p086, p087; - byte p088, p089, p090, p091, p092, p093, p094, p095; - byte p096, p097, p098, p099, p100, p101, p102, p103; - byte p104, p105, p106, p107, p108, p109, p110, p111; - byte p112, p113, p114, p115, p116, p117, p118, p119; - } - - /** - * Enforces a memory layout to avoid false sharing by padding the read count. - */ - abstract static class ReadCacheRef extends PadReadCache { - volatile long readCache; - } - - abstract static class PadReadCounter extends ReadCacheRef { - byte p120, p121, p122, p123, p124, p125, p126, p127; - byte p128, p129, p130, p131, p132, p133, p134, p135; - byte p136, p137, p138, p139, p140, p141, p142, p143; - byte p144, p145, p146, p147, p148, p149, p150, p151; - byte p152, p153, p154, p155, p156, p157, p158, p159; - byte p160, p161, p162, p163, p164, p165, p166, p167; - byte p168, p169, p170, p171, p172, p173, p174, p175; - byte p176, p177, p178, p179, p180, p181, p182, p183; - byte p184, p185, p186, p187, p188, p189, p190, p191; - byte p192, p193, p194, p195, p196, p197, p198, p199; - byte p200, p201, p202, p203, p204, p205, p206, p207; - byte p208, p209, p210, p211, p212, p213, p214, p215; - byte p216, p217, p218, p219, p220, p221, p222, p223; - byte p224, p225, p226, p227, p228, p229, p230, p231; - byte p232, p233, p234, p235, p236, p237, p238, p239; - } - - abstract static class ReadCounterRef extends PadReadCounter { - volatile long readCounter; - } - - abstract static class PadWriteCounter extends ReadCounterRef { - byte p240, p241, p242, p243, p244, p245, p246, p247; - byte p248, p249, p250, p251, p252, p253, p254, p255; - byte p256, p257, p258, p259, p260, p261, p262, p263; - byte p264, p265, p266, p267, p268, p269, p270, p271; - byte p272, p273, p274, p275, p276, p277, p278, p279; - byte p280, p281, p282, p283, p284, p285, p286, p287; - byte p288, p289, p290, p291, p292, p293, p294, p295; - byte p296, p297, p298, p299, p300, p301, p302, p303; - byte p304, p305, p306, p307, p308, p309, p310, p311; - byte p312, p313, p314, p315, p316, p317, p318, p319; - byte p320, p321, p322, p323, p324, p325, p326, p327; - byte p328, p329, p330, p331, p332, p333, p334, p335; - byte p336, p337, p338, p339, p340, p341, p342, p343; - byte p344, p345, p346, p347, p348, p349, p350, p351; - byte p352, p353, p354, p355, p356, p357, p358, p359; - } - - abstract static class ReadAndWriteCounterRef extends PadWriteCounter { - static final VarHandle READ_CACHE, READ, WRITE; - - volatile long writeCounter; - - void setReadCounterOpaque(long count) { - READ.setOpaque(this, count); - } - - long writeCounterOpaque() { - return (long) WRITE.getOpaque(this); - } - - boolean casWriteCounter(long expect, long update) { - return WRITE.weakCompareAndSet(this, expect, update); - } - - static { - var lookup = MethodHandles.lookup(); - try { - READ = lookup.findVarHandle(ReadCounterRef.class, "readCounter", long.class); - READ_CACHE = lookup.findVarHandle(ReadCacheRef.class, "readCache", long.class); - WRITE = lookup.findVarHandle(ReadAndWriteCounterRef.class, "writeCounter", long.class); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/ManyToOneBuffer.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/ManyToOneBuffer.java deleted file mode 100644 index 7e6e95c..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/ManyToOneBuffer.java +++ /dev/null @@ -1,133 +0,0 @@ - -package com.github.benmanes.caffeine.cache.buffer; - -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; - -final class ManyToOneBuffer extends ManyToOneHeader.ReadAndWriteCounterRef { - static final VarHandle BUFFER = MethodHandles.arrayElementVarHandle(Object[].class); - - final Object[] buffer; - - ManyToOneBuffer() { - buffer = new Object[BUFFER_SIZE]; - } - - @Override - public int offer(E e) { - long head = readCounter; - long tail = writeCounterOpaque(); - long size = (tail - head); - if (size >= BUFFER_SIZE) { - return FULL; - } - if (casWriteCounter(tail, tail + 1)) { - int index = (int) (tail & BUFFER_MASK); - BUFFER.setRelease(buffer, index, e); - return SUCCESS; - } - return FAILED; - } - - @Override - public void drainTo(Consumer consumer) { - long head = readCounter; - long tail = writeCounterOpaque(); - long size = (tail - head); - if (size == 0) { - return; - } - do { - int index = (int) (head & BUFFER_MASK); - E e = (E) BUFFER.getAcquire(buffer, index); - if (e == null) { - break; - } - BUFFER.setRelease(buffer, index, null); - consumer.accept(e); - head++; - } while (head != tail); - setReadCounterOpaque(head); - } - - @Override - public long reads() { - return readCounter; - } - - @Override - public long writes() { - return writeCounter; - } -} - -final class ManyToOneHeader { - - @SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod") - abstract static class PadReadCounter extends ReadBuffer { - byte p000, p001, p002, p003, p004, p005, p006, p007; - byte p008, p009, p010, p011, p012, p013, p014, p015; - byte p016, p017, p018, p019, p020, p021, p022, p023; - byte p024, p025, p026, p027, p028, p029, p030, p031; - byte p032, p033, p034, p035, p036, p037, p038, p039; - byte p040, p041, p042, p043, p044, p045, p046, p047; - byte p048, p049, p050, p051, p052, p053, p054, p055; - byte p056, p057, p058, p059, p060, p061, p062, p063; - byte p064, p065, p066, p067, p068, p069, p070, p071; - byte p072, p073, p074, p075, p076, p077, p078, p079; - byte p080, p081, p082, p083, p084, p085, p086, p087; - byte p088, p089, p090, p091, p092, p093, p094, p095; - byte p096, p097, p098, p099, p100, p101, p102, p103; - byte p104, p105, p106, p107, p108, p109, p110, p111; - byte p112, p113, p114, p115, p116, p117, p118, p119; - } - - abstract static class ReadCounterRef extends PadReadCounter { - volatile long readCounter; - } - - abstract static class PadWriteCounter extends ReadCounterRef { - byte p120, p121, p122, p123, p124, p125, p126, p127; - byte p128, p129, p130, p131, p132, p133, p134, p135; - byte p136, p137, p138, p139, p140, p141, p142, p143; - byte p144, p145, p146, p147, p148, p149, p150, p151; - byte p152, p153, p154, p155, p156, p157, p158, p159; - byte p160, p161, p162, p163, p164, p165, p166, p167; - byte p168, p169, p170, p171, p172, p173, p174, p175; - byte p176, p177, p178, p179, p180, p181, p182, p183; - byte p184, p185, p186, p187, p188, p189, p190, p191; - byte p192, p193, p194, p195, p196, p197, p198, p199; - byte p200, p201, p202, p203, p204, p205, p206, p207; - byte p208, p209, p210, p211, p212, p213, p214, p215; - byte p216, p217, p218, p219, p220, p221, p222, p223; - byte p224, p225, p226, p227, p228, p229, p230, p231; - byte p232, p233, p234, p235, p236, p237, p238, p239; - } - - abstract static class ReadAndWriteCounterRef extends PadWriteCounter { - static final VarHandle READ, WRITE; - volatile long writeCounter; - - void setReadCounterOpaque(long count) { - READ.setOpaque(this, count); - } - - long writeCounterOpaque() { - return (long) WRITE.getOpaque(this); - } - - boolean casWriteCounter(long expect, long update) { - return WRITE.weakCompareAndSet(this, expect, update); - } - - static { - var lookup = MethodHandles.lookup(); - try { - READ = lookup.findVarHandle(ReadCounterRef.class, "readCounter", long.class); - WRITE = lookup.findVarHandle(ReadAndWriteCounterRef.class, "writeCounter", long.class); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/ManyToOneSpacedBuffer.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/ManyToOneSpacedBuffer.java deleted file mode 100644 index d4e14da..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/ManyToOneSpacedBuffer.java +++ /dev/null @@ -1,139 +0,0 @@ - -package com.github.benmanes.caffeine.cache.buffer; - -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; - - -final class ManyToOneSpacedBuffer extends ManyToOneSpacedHeader.ReadAndWriteCounterRef { - static final VarHandle BUFFER = MethodHandles.arrayElementVarHandle(Object[].class); - static final int SPACED_SIZE = BUFFER_SIZE << 4; - static final int SPACED_MASK = SPACED_SIZE - 1; - static final int OFFSET = 16; - - final Object[] buffer; - - ManyToOneSpacedBuffer() { - buffer = new Object[SPACED_SIZE]; - } - - @Override - public int offer(E e) { - long head = readCounter; - long tail = writeCounterOpaque(); - long size = (tail - head); - if (size >= SPACED_SIZE) { - return FULL; - } - if (casWriteCounter(tail, tail + OFFSET)) { - int index = (int) (tail & SPACED_MASK); - BUFFER.setRelease(buffer, index, e); - return SUCCESS; - } - return FAILED; - } - - @Override - @SuppressWarnings("unchecked") - public void drainTo(Consumer consumer) { - long head = readCounter; - long tail = writeCounterOpaque(); - long size = (tail - head); - if (size == 0) { - return; - } - do { - int index = (int) (head & SPACED_MASK); - E e = (E) BUFFER.getAcquire(buffer, index); - if (e == null) { - break; - } - BUFFER.setRelease(buffer, index, null); - consumer.accept(e); - head += OFFSET; - } while (head != tail); - setReadCounterOpaque(head); - } - - @Override - public long reads() { - return readCounter / OFFSET; - } - - @Override - public long writes() { - return writeCounter / OFFSET; - } -} - -final class ManyToOneSpacedHeader { - - @SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod") - abstract static class PadReadCounter extends ReadBuffer { - byte p000, p001, p002, p003, p004, p005, p006, p007; - byte p008, p009, p010, p011, p012, p013, p014, p015; - byte p016, p017, p018, p019, p020, p021, p022, p023; - byte p024, p025, p026, p027, p028, p029, p030, p031; - byte p032, p033, p034, p035, p036, p037, p038, p039; - byte p040, p041, p042, p043, p044, p045, p046, p047; - byte p048, p049, p050, p051, p052, p053, p054, p055; - byte p056, p057, p058, p059, p060, p061, p062, p063; - byte p064, p065, p066, p067, p068, p069, p070, p071; - byte p072, p073, p074, p075, p076, p077, p078, p079; - byte p080, p081, p082, p083, p084, p085, p086, p087; - byte p088, p089, p090, p091, p092, p093, p094, p095; - byte p096, p097, p098, p099, p100, p101, p102, p103; - byte p104, p105, p106, p107, p108, p109, p110, p111; - byte p112, p113, p114, p115, p116, p117, p118, p119; - } - - abstract static class ReadCounterRef extends PadReadCounter { - volatile long readCounter; - } - - abstract static class PadWriteCounter extends ReadCounterRef { - byte p120, p121, p122, p123, p124, p125, p126, p127; - byte p128, p129, p130, p131, p132, p133, p134, p135; - byte p136, p137, p138, p139, p140, p141, p142, p143; - byte p144, p145, p146, p147, p148, p149, p150, p151; - byte p152, p153, p154, p155, p156, p157, p158, p159; - byte p160, p161, p162, p163, p164, p165, p166, p167; - byte p168, p169, p170, p171, p172, p173, p174, p175; - byte p176, p177, p178, p179, p180, p181, p182, p183; - byte p184, p185, p186, p187, p188, p189, p190, p191; - byte p192, p193, p194, p195, p196, p197, p198, p199; - byte p200, p201, p202, p203, p204, p205, p206, p207; - byte p208, p209, p210, p211, p212, p213, p214, p215; - byte p216, p217, p218, p219, p220, p221, p222, p223; - byte p224, p225, p226, p227, p228, p229, p230, p231; - byte p232, p233, p234, p235, p236, p237, p238, p239; - } - - abstract static class ReadAndWriteCounterRef extends PadWriteCounter { - static final VarHandle READ, WRITE; - - volatile long writeCounter; - - void setReadCounterOpaque(long count) { - READ.setOpaque(this, count); - } - - long writeCounterOpaque() { - return (long) WRITE.getOpaque(this); - } - - boolean casWriteCounter(long expect, long update) { - return WRITE.weakCompareAndSet(this, expect, update); - } - - static { - var lookup = MethodHandles.lookup(); - try { - READ = lookup.findVarHandle(ReadCounterRef.class, "readCounter", long.class); - WRITE = lookup.findVarHandle(ReadAndWriteCounterRef.class, "writeCounter", long.class); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/MpmcArrayBuffer.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/MpmcArrayBuffer.java deleted file mode 100644 index 60e856b..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/MpmcArrayBuffer.java +++ /dev/null @@ -1,33 +0,0 @@ - -package com.github.benmanes.caffeine.cache.buffer; - -import org.jctools.queues.MpmcArrayQueue; - -final class MpmcArrayBuffer extends ReadBuffer { - final MpmcArrayQueue queue; - long reads; - - MpmcArrayBuffer() { - queue = new MpmcArrayQueue<>(BUFFER_SIZE); - } - - @Override - public int offer(E e) { - return queue.offer(e) ? SUCCESS : FULL; - } - - @Override - public void drainTo(Consumer consumer) { - reads += queue.drain(consumer); - } - - @Override - public long reads() { - return reads; - } - - @Override - public long writes() { - return reads + queue.size(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/MpscArrayBuffer.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/MpscArrayBuffer.java deleted file mode 100644 index 751b37a..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/MpscArrayBuffer.java +++ /dev/null @@ -1,34 +0,0 @@ - -package com.github.benmanes.caffeine.cache.buffer; - -import org.jctools.queues.MpscArrayQueue; - - -final class MpscArrayBuffer extends ReadBuffer { - final MpscArrayQueue queue; - long reads; - - MpscArrayBuffer() { - queue = new MpscArrayQueue<>(BUFFER_SIZE); - } - - @Override - public int offer(E e) { - return queue.failFastOffer(e); - } - - @Override - public void drainTo(Consumer consumer) { - reads += queue.drain(consumer); - } - - @Override - public long reads() { - return reads; - } - - @Override - public long writes() { - return reads + queue.size(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/MpscCompoundBuffer.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/MpscCompoundBuffer.java deleted file mode 100644 index 63c2229..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/MpscCompoundBuffer.java +++ /dev/null @@ -1,34 +0,0 @@ - -package com.github.benmanes.caffeine.cache.buffer; - -import org.jctools.queues.MpscCompoundQueue; - - -final class MpscCompoundBuffer extends ReadBuffer { - final MpscCompoundQueue queue; - long reads; - - MpscCompoundBuffer() { - queue = new MpscCompoundQueue<>(BUFFER_SIZE); - } - - @Override - public int offer(E e) { - return queue.offer(e) ? SUCCESS : FULL; - } - - @Override - public void drainTo(Consumer consumer) { - reads += queue.drain(consumer); - } - - @Override - public long reads() { - return reads; - } - - @Override - public long writes() { - return reads + queue.size(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/ReadBuffer.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/ReadBuffer.java deleted file mode 100644 index 59a71f0..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/ReadBuffer.java +++ /dev/null @@ -1,43 +0,0 @@ - -package com.github.benmanes.caffeine.cache.buffer; - -import com.google.errorprone.annotations.concurrent.GuardedBy; -import org.jctools.queues.MessagePassingQueue; - -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -public abstract class ReadBuffer { - public static final int FULL = 1; - public static final int FAILED = -1; - public static final int SUCCESS = 0; - - public static final int BUFFER_SIZE = 16; - public static final int BUFFER_MASK = BUFFER_SIZE - 1; - - final Consumer consumer = any -> { - }; - final Lock lock = new ReentrantLock(); - - public abstract long reads(); - - public abstract long writes(); - - public abstract int offer(E e); - - @GuardedBy("lock") - protected abstract void drainTo(Consumer consumer); - - public void drain() { - if (lock.tryLock()) { - try { - drainTo(consumer); - } finally { - lock.unlock(); - } - } - } - - public interface Consumer extends java.util.function.Consumer, MessagePassingQueue.Consumer { - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/TicketBuffer.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/TicketBuffer.java deleted file mode 100644 index a45f106..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/buffer/TicketBuffer.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.github.benmanes.caffeine.cache.buffer; - -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.IntStream; - -final class TicketBuffer extends ReadBuffer { - final AtomicLong writeCounter; - final AtomicReference[] buffer; - - long readCounter; - - @SuppressWarnings({"unchecked"}) - TicketBuffer() { - writeCounter = new AtomicLong(); - buffer = new AtomicReference[BUFFER_SIZE]; - for (int i = 0; i < BUFFER_SIZE; i++) { - buffer[i] = new AtomicReference<>(new Turn(i)); - } - } - - @Override - public int offer(E e) { - final long writeCount = writeCounter.get(); - final int index = (int) (writeCount & BUFFER_MASK); - AtomicReference slot = buffer[index]; - Object value = slot.get(); - if (!(value instanceof Turn)) { - return FULL; - } else if (((Turn) value).id != writeCount) { - return FAILED; - } - if (slot.compareAndSet(value, e)) { - writeCounter.lazySet(writeCount + 1); - return SUCCESS; - } - return FAILED; - } - - @Override - public void drainTo(Consumer consumer) { - IntStream.range(0, BUFFER_SIZE).map(i -> (int) (readCounter & BUFFER_MASK)).mapToObj(index -> buffer[index]).takeWhile(slot -> !(slot.get() instanceof Turn)).forEach(slot -> { - long next = readCounter + BUFFER_SIZE; - slot.lazySet(new Turn(next)); - readCounter++; - }); - } - - @Override - public long reads() { - return readCounter; - } - - @Override - public long writes() { - return writeCounter.get(); - } - - record Turn(long id) { - - @Override - public String toString() { - return Long.toString(id); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/HashClashTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/HashClashTest.java deleted file mode 100644 index 9f87652..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/HashClashTest.java +++ /dev/null @@ -1,68 +0,0 @@ - -package com.github.benmanes.caffeine.cache.issues; - -import com.github.benmanes.caffeine.cache.Cache; -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.Maximum; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.Population; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.Stats; -import com.github.benmanes.caffeine.cache.testing.CacheValidationListener; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.util.Set; -import java.util.stream.LongStream; - -import static com.google.common.truth.Truth.assertThat; -import static java.util.function.Function.identity; - -@Listeners(CacheValidationListener.class) -@Test(dataProviderClass = CacheProvider.class) -public final class HashClashTest { - private static final int STEP = 5; - private static final Long LONG_1 = 1L; - private static final long ITERS = 200_000; - private static final boolean debug = false; - - @Test(dataProvider = "caches") - @CacheSpec(population = Population.EMPTY, maximumSize = Maximum.ONE_FIFTY, stats = Stats.ENABLED) - public void testCache(Cache cache, CacheContext context) { - LongStream.range(0, 300).forEach(j -> { - cache.get(1L, identity()); - cache.get(j, identity()); - }); - printStats(cache); - printKeys(cache); - Long CLASH = (ITERS << 32) ^ ITERS ^ 1; - assertThat(CLASH.hashCode()).isEqualTo(LONG_1.hashCode()); - cache.get(CLASH, identity()); - printKeys(cache); - LongStream.range(0, 300).forEach(j -> { - cache.get(1L, identity()); - cache.get(j, identity()); - }); - printKeys(cache); - LongStream.iterate(0, i -> i < ITERS, i -> (long) (i + STEP)).forEach(i -> { - cache.get(1L, identity()); - LongStream.range(0, STEP).forEach(j -> cache.get(-j, identity())); - }); - printKeys(cache); - printStats(cache); - assertThat(cache.stats().hitRate()).isGreaterThan(0.99d); - } - - static void printStats(Cache cache) { - if (debug) { - System.out.printf("size %,d requests %,d hit ratio %f%n", cache.estimatedSize(), cache.stats().requestCount(), cache.stats().hitRate()); - } - } - - static void printKeys(Cache cache) { - if (debug) { - Set keys = cache.policy().eviction().map(policy -> policy.hottest(Integer.MAX_VALUE).keySet()).orElseGet(cache.asMap()::keySet); - System.out.println(keys); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue193Test.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue193Test.java deleted file mode 100644 index cd66eff..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue193Test.java +++ /dev/null @@ -1,62 +0,0 @@ - -package com.github.benmanes.caffeine.cache.issues; - -import com.github.benmanes.caffeine.cache.AsyncCacheLoader; -import com.github.benmanes.caffeine.cache.AsyncLoadingCache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.RemovalCause; -import com.google.common.testing.FakeTicker; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFutureTask; -import org.testng.annotations.Test; - -import java.util.ArrayList; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.google.common.truth.Truth.assertThat; - -public final class Issue193Test { - private final AtomicLong counter = new AtomicLong(0); - private final FakeTicker ticker = new FakeTicker(); - - private ListenableFutureTask loadingTask; - - private final AsyncCacheLoader loader = (key, exec) -> { - loadingTask = ListenableFutureTask.create(counter::getAndIncrement); - var f = new CompletableFuture(); - loadingTask.addListener(() -> f.complete(Futures.getUnchecked(loadingTask)), exec); - return f; - }; - - private final String key = Issue193Test.class.getSimpleName(); - - private long loadGet(AsyncLoadingCache cache, String key) { - CompletableFuture future = cache.get(key); - if (!loadingTask.isDone()) { - loadingTask.run(); - } - return future.join(); - } - - @Test - public void invalidateDuringRefreshRemovalCheck() { - var removed = new ArrayList(); - AsyncLoadingCache cache = Caffeine.newBuilder() - .removalListener((String key, Long value, RemovalCause reason) -> removed.add(value)) - .refreshAfterWrite(10, TimeUnit.NANOSECONDS) - .executor(Runnable::run) - .ticker(ticker::read) - .buildAsync(loader); - assertThat(loadGet(cache, key)).isEqualTo(0); - ticker.advance(11); - assertThat(cache).containsEntry(key, 0); - cache.synchronous().invalidate(key); - assertThat(cache).doesNotContainKey(key); - loadingTask.run(); - assertThat(cache).doesNotContainKey(key); - assertThat(removed).containsExactly(0L, 1L).inOrder(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue298Test.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue298Test.java deleted file mode 100644 index 9a4098a..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue298Test.java +++ /dev/null @@ -1,111 +0,0 @@ - -package com.github.benmanes.caffeine.cache.issues; - -import com.github.benmanes.caffeine.cache.AsyncLoadingCache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.Expiry; -import com.github.benmanes.caffeine.cache.Policy.VarExpiration; -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import javax.annotation.Nonnull; -import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; - -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.ConcurrentTestHarness.executor; -import static com.google.common.truth.Truth.assertThat; - -@Test(groups = "isolated") -public final class Issue298Test { - static final long EXPIRE_NS = Duration.ofDays(1).toNanos(); - - AtomicBoolean startedLoad; - AtomicBoolean doLoad; - - AtomicBoolean startedCreate; - AtomicBoolean doCreate; - - AtomicBoolean startedRead; - AtomicBoolean doRead; - AtomicBoolean endRead; - - AsyncLoadingCache cache; - VarExpiration policy; - String key; - - @BeforeMethod - public void before() { - startedCreate = new AtomicBoolean(); - startedLoad = new AtomicBoolean(); - startedRead = new AtomicBoolean(); - doCreate = new AtomicBoolean(); - endRead = new AtomicBoolean(); - doLoad = new AtomicBoolean(); - doRead = new AtomicBoolean(); - - key = "key"; - cache = makeAsyncCache(); - policy = cache.synchronous().policy().expireVariably().orElseThrow(); - } - - @AfterMethod - public void after() { - endRead.set(true); - } - - @Test - @SuppressWarnings("FutureReturnValueIgnored") - public void readDuringCreate() { - cache.get(key); - await().untilTrue(startedLoad); - doLoad.set(true); - await().untilTrue(startedCreate); - var reader = CompletableFuture.runAsync(() -> { - do { - cache.get(key); - } while (!endRead.get()); - }, executor); - doCreate.set(true); - await().until(() -> policy.getExpiresAfter(key).orElseThrow().toNanos() <= EXPIRE_NS); - await().untilTrue(startedRead); - doRead.set(true); - endRead.set(true); - reader.join(); - assertThat(policy.getExpiresAfter(key).orElseThrow().toNanos()).isAtMost(EXPIRE_NS); - } - - private AsyncLoadingCache makeAsyncCache() { - return Caffeine.newBuilder().executor(ConcurrentTestHarness.executor).expireAfter(new Expiry() { - @Override - public long expireAfterCreate(@Nonnull String key, - @Nonnull String value, long currentTime) { - startedCreate.set(true); - await().untilTrue(doCreate); - return EXPIRE_NS; - } - - @Override - public long expireAfterUpdate(@Nonnull String key, - @Nonnull String value, long currentTime, long currentDuration) { - return currentDuration; - } - - @Override - public long expireAfterRead(@Nonnull String key, - @Nonnull String value, long currentTime, long currentDuration) { - startedRead.set(true); - await().untilTrue(doRead); - return currentDuration; - } - }) - .buildAsync(key -> { - startedLoad.set(true); - await().untilTrue(doLoad); - return key + "'s value"; - }); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue30Test.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue30Test.java deleted file mode 100644 index 70ade1a..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue30Test.java +++ /dev/null @@ -1,132 +0,0 @@ - -package com.github.benmanes.caffeine.cache.issues; - -import com.github.benmanes.caffeine.cache.AsyncCacheLoader; -import com.github.benmanes.caffeine.cache.AsyncLoadingCache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.testing.CacheValidationListener; -import com.github.benmanes.caffeine.testing.FutureSubject; -import com.google.common.util.concurrent.MoreExecutors; -import org.testng.annotations.AfterClass; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.concurrent.*; - -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.github.benmanes.caffeine.testing.FutureSubject.future; -import static com.google.common.truth.Truth.assertWithMessage; -import static java.time.ZoneOffset.UTC; -import static java.util.Locale.US; - -@Test(groups = "isolated") -@Listeners(CacheValidationListener.class) -public final class Issue30Test { - private static final boolean DEBUG = false; - private static final String A_KEY = "foo"; - private static final String A_ORIGINAL = "foo0"; - private static final String A_UPDATE_1 = "foo1"; - private static final String A_UPDATE_2 = "foo2"; - private static final String B_KEY = "bar"; - private static final String B_ORIGINAL = "bar0"; - private static final String B_UPDATE_1 = "bar1"; - private static final String B_UPDATE_2 = "bar2"; - private static final int TTL = 100; - private static final int EPSILON = 10; - private static final int N_THREADS = 10; - - private final ExecutorService executor = Executors.newFixedThreadPool(N_THREADS); - - @AfterClass - public void afterClass() { - MoreExecutors.shutdownAndAwaitTermination(executor, 1, TimeUnit.MINUTES); - } - - @DataProvider(name = "params") - public Object[][] providesCache() { - var source = new ConcurrentHashMap(); - var lastLoad = new ConcurrentHashMap(); - AsyncLoadingCache cache = Caffeine.newBuilder() - .expireAfterWrite(Duration.ofMillis(TTL)) - .executor(executor) - .buildAsync(new Loader(source, lastLoad)); - return new Object[][]{{cache, source, lastLoad}}; - } - - @Test(dataProvider = "params", invocationCount = 100, threadPoolSize = N_THREADS) - public void expiration(AsyncLoadingCache cache, - ConcurrentMap source, ConcurrentMap lastLoad) - throws InterruptedException { - initialValues(cache, source, lastLoad); - firstUpdate(cache, source); - secondUpdate(cache, source); - } - - private void initialValues(AsyncLoadingCache cache, - ConcurrentMap source, ConcurrentMap lastLoad) { - source.put(A_KEY, A_ORIGINAL); - source.put(B_KEY, B_ORIGINAL); - lastLoad.clear(); - assertThat("should serve initial value", cache.get(A_KEY)).succeedsWith(A_ORIGINAL); - assertThat("should serve initial value", cache.get(B_KEY)).succeedsWith(B_ORIGINAL); - } - - private void firstUpdate(AsyncLoadingCache cache, ConcurrentMap source) throws InterruptedException { - source.put(A_KEY, A_UPDATE_1); - source.put(B_KEY, B_UPDATE_1); - assertThat("should serve cached initial value", cache.get(A_KEY)).succeedsWith(A_ORIGINAL); - assertThat("should serve cached initial value", cache.get(B_KEY)).succeedsWith(B_ORIGINAL); - Thread.sleep(EPSILON); - assertThat("still serve cached initial value", cache.get(A_KEY)).succeedsWith(A_ORIGINAL); - assertThat("still serve cached initial value", cache.get(B_KEY)).succeedsWith(B_ORIGINAL); - Thread.sleep(TTL + EPSILON); - assertThat("now serve first updated value", cache.get(A_KEY)).succeedsWith(A_UPDATE_1); - await().untilAsserted(() -> assertThat("now serve first updated value", cache.get(B_KEY)).succeedsWith(B_UPDATE_1)); - } - - private void secondUpdate(AsyncLoadingCache cache, ConcurrentMap source) throws InterruptedException { - source.put(A_KEY, A_UPDATE_2); - source.put(B_KEY, B_UPDATE_2); - assertThat("serve cached first updated value", cache.get(A_KEY)).succeedsWith(A_UPDATE_1); - assertThat("serve cached first updated value", cache.get(B_KEY)).succeedsWith(B_UPDATE_1); - Thread.sleep(EPSILON); - assertThat("serve cached first updated value", cache.get(A_KEY)).succeedsWith(A_UPDATE_1); - assertThat("serve cached first updated value", cache.get(A_KEY)).succeedsWith(A_UPDATE_1); - } - - private static FutureSubject assertThat(String message, CompletableFuture actual) { - return assertWithMessage(message).about(future()).that(actual); - } - - record Loader(ConcurrentMap source, - ConcurrentMap lastLoad) implements AsyncCacheLoader { - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("hh:MM:ss.SSS", US); - - @Override - public CompletableFuture asyncLoad(String key, Executor executor) { - reportCacheMiss(key); - return CompletableFuture.completedFuture(source.get(key)); - } - - @SuppressWarnings("TimeZoneUsage") - private void reportCacheMiss(String key) { - Instant now = Instant.now(); - Instant last = lastLoad.get(key); - lastLoad.put(key, now); - if (DEBUG) { - String time = FORMATTER.format(LocalDateTime.now(UTC)); - if (last == null) { - System.out.println(key + ": first load @ " + time); - } else { - long duration = (now.toEpochMilli() - last.toEpochMilli()); - System.out.println(key + ": " + duration + "ms after last load @ " + time); - } - } - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue412Test.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue412Test.java deleted file mode 100644 index 5a21cd6..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue412Test.java +++ /dev/null @@ -1,70 +0,0 @@ - -package com.github.benmanes.caffeine.cache.issues; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.RemovalCause; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; -import site.ycsb.generator.ScrambledZipfianGenerator; - -import java.time.Duration; -import java.util.Random; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.IntStream; - -import static com.github.benmanes.caffeine.testing.ConcurrentTestHarness.DAEMON_FACTORY; -import static com.github.benmanes.caffeine.testing.ConcurrentTestHarness.timeTasks; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.util.concurrent.MoreExecutors.shutdownAndAwaitTermination; - -@Test(groups = "isolated") -public final class Issue412Test { - private static final int NUM_THREADS = 5; - - private Cache cache; - private ExecutorService executor; - private AtomicBoolean collected; - private Integer[] ints; - private Random random; - - @BeforeMethod - public void before() { - executor = Executors.newCachedThreadPool(DAEMON_FACTORY); - collected = new AtomicBoolean(); - cache = Caffeine.newBuilder().expireAfterWrite(Duration.ofNanos(10)).removalListener((k, v, cause) -> { - if (cause == RemovalCause.COLLECTED) { - collected.set(true); - } - }).executor(executor).build(); - ints = generateSequence(); - random = new Random(); - } - - @Test - public void expire_remove() { - timeTasks(NUM_THREADS, this::addRemoveAndExpire); - shutdownAndAwaitTermination(executor, 1, TimeUnit.MINUTES); - assertThat(collected.get()).isFalse(); - } - - private void addRemoveAndExpire() { - int mask = (ints.length - 1); - int index = random.nextInt(); - for (int i = 0; i < (10 * ints.length); i++) { - Integer key = ints[index++ & mask]; - cache.put(key, Boolean.TRUE); - cache.invalidate(key); - } - } - - private static Integer[] generateSequence() { - var ints = new Integer[2 << 14]; - var generator = new ScrambledZipfianGenerator(ints.length / 3); - IntStream.range(0, ints.length).forEach(i -> ints[i] = generator.nextValue().intValue()); - return ints; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue568Test.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue568Test.java deleted file mode 100644 index 20150e7..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Issue568Test.java +++ /dev/null @@ -1,82 +0,0 @@ - -package com.github.benmanes.caffeine.cache.issues; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.RemovalCause; -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import org.testng.annotations.Test; - -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.IntStream; - -@Test(groups = "isolated") -public class Issue568Test { - @Test - public void intermittentNull() throws InterruptedException { - Cache cache = Caffeine.newBuilder().executor(ConcurrentTestHarness.executor).weakValues().build(); - String key = "key"; - String val = "val"; - cache.put("key", "val"); - var error = new AtomicReference(); - var threads = new ArrayList(); - IntStream.range(0, 10).mapToObj(name -> new Thread(() -> { - for (int j = 0; j < 1000; j++) { - if (Math.random() < .5) { - cache.put(key, val); - } else if (cache.getIfPresent(key) == null) { - error.compareAndSet(null, new IllegalStateException( - "Thread " + name + " observed null on iteration " + j)); - break; - } - } - })).forEach(thread -> { - threads.add(thread); - thread.start(); - }); - for (Thread thread : threads) { - thread.join(); - } - if (error.get() != null) { - throw error.get(); - } - } - @Test - public void resurrect() throws InterruptedException { - var error = new AtomicReference(); - Cache cache = Caffeine.newBuilder() - .weakValues() - .executor(ConcurrentTestHarness.executor) - .removalListener((k, v, cause) -> { - if (cause == RemovalCause.COLLECTED && (v != null)) { - error.compareAndSet(null, new IllegalStateException("Evicted a live value: " + v)); - } - }).build(); - String key = "key"; - cache.put(key, new Object()); - var missing = new AtomicBoolean(); - var threads = new ArrayList(); - IntStream.range(0, 100).mapToObj(i -> new Thread(() -> { - IntStream.range(0, 1000).takeWhile(j -> error.get() == null).forEach(j -> { - if (Math.random() < .01) { - System.gc(); - cache.cleanUp(); - } else if ((cache.getIfPresent(key) == null) && !missing.getAndSet(true)) { - cache.put(key, new Object()); - missing.set(false); - } - }); - })).forEach(thread -> { - threads.add(thread); - thread.start(); - }); - for (var thread : threads) { - thread.join(); - } - if (error.get() != null) { - throw error.get(); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Solr10141Test.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Solr10141Test.java deleted file mode 100644 index 0a042a4..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/issues/Solr10141Test.java +++ /dev/null @@ -1,149 +0,0 @@ - -package com.github.benmanes.caffeine.cache.issues; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.RemovalListener; -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import org.testng.annotations.Test; - -import java.util.Random; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; - -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.google.common.truth.Truth.assertThat; -import static java.util.Locale.US; - -@Test(groups = "isolated") -public final class Solr10141Test { - static final int blocksInTest = 400; - static final int maxEntries = blocksInTest / 2; - static final int nThreads = 64; - static final int nReads = 10000000; - static final int readsPerThread = nReads / nThreads; - static final int readLastBlockOdds = 10; - static final boolean updateAnyway = true; - final Random rnd = new Random(); - - @Test - public void eviction() { - var hits = new AtomicLong(); - var inserts = new AtomicLong(); - var removals = new AtomicLong(); - RemovalListener listener = (k, v, removalCause) -> { - assertThat(v.key).isEqualTo(k); - if (!v.live.compareAndSet(true, false)) { - throw new RuntimeException(String.format(US, - "listener called more than once! k=%s, v=%s, removalCause=%s", k, v, removalCause)); - } - removals.incrementAndGet(); - }; - Cache cache = Caffeine.newBuilder() - .executor(ConcurrentTestHarness.executor) - .removalListener(listener) - .maximumSize(maxEntries) - .build(); - var lastBlock = new AtomicLong(); - var failed = new AtomicBoolean(); - var maxObservedSize = new AtomicLong(); - ConcurrentTestHarness.timeTasks(nThreads, new Runnable() { - @Override - public void run() { - try { - var r = new Random(rnd.nextLong()); - for (int i = 0; i < readsPerThread; i++) { - test(r); - } - } catch (Throwable e) { - failed.set(true); - e.printStackTrace(); - } - } - - void test(Random r) { - long block = r.nextInt(blocksInTest); - if (readLastBlockOdds > 0 && r.nextInt(readLastBlockOdds) == 0) { - block = lastBlock.get(); - } - lastBlock.set(block); - Long k = block; - Val v = cache.getIfPresent(k); - if (v != null) { - hits.incrementAndGet(); - assertThat(k).isEqualTo(v.key); - } - if ((v == null) || (updateAnyway && r.nextBoolean())) { - v = new Val(); - v.key = k; - cache.put(k, v); - inserts.incrementAndGet(); - } - long sz = cache.estimatedSize(); - if (sz > maxObservedSize.get()) { - maxObservedSize.set(sz); - } - } - }); - await().until(() -> (inserts.get() - removals.get()) == cache.estimatedSize()); - System.out.printf(US, "Done!%n" + "entries=%,d inserts=%,d removals=%,d hits=%,d maxEntries=%,d maxObservedSize=%,d%n", - cache.estimatedSize(), inserts.get(), removals.get(), - hits.get(), maxEntries, maxObservedSize.get()); - assertThat(failed.get()).isFalse(); - } - - @Test - public void clear() { - var inserts = new AtomicLong(); - var removals = new AtomicLong(); - var failed = new AtomicBoolean(); - RemovalListener listener = (k, v, removalCause) -> { - assertThat(v.key).isEqualTo(k); - if (!v.live.compareAndSet(true, false)) { - throw new RuntimeException(String.format(US, - "listener called more than once! k=%s, v=%s, removalCause=%s", k, v, removalCause)); - } - removals.incrementAndGet(); - }; - Cache cache = Caffeine.newBuilder().maximumSize(Integer.MAX_VALUE).removalListener(listener).build(); - ConcurrentTestHarness.timeTasks(nThreads, new Runnable() { - @Override - public void run() { - try { - var r = new Random(rnd.nextLong()); - for (int i = 0; i < readsPerThread; i++) { - test(r); - } - } catch (Throwable e) { - failed.set(true); - e.printStackTrace(); - } - } - - void test(Random r) { - Long k = (long) r.nextInt(blocksInTest); - Val v = cache.getIfPresent(k); - if (v != null) { - assertThat(k).isEqualTo(v.key); - } - if ((v == null) || (updateAnyway && r.nextBoolean())) { - v = new Val(); - v.key = k; - cache.put(k, v); - inserts.incrementAndGet(); - } - if (r.nextInt(10) == 0) { - cache.asMap().clear(); - } - } - }); - cache.asMap().clear(); - await().until(() -> inserts.get() == removals.get()); - assertThat(failed.get()).isFalse(); - } - - static class Val { - long key; - AtomicBoolean live = new AtomicBoolean(true); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/stats/CacheStatsTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/stats/CacheStatsTest.java deleted file mode 100644 index 24d429d..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/stats/CacheStatsTest.java +++ /dev/null @@ -1,109 +0,0 @@ - -package com.github.benmanes.caffeine.cache.stats; - -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import static com.google.common.truth.Truth.assertThat; - -public final class CacheStatsTest { - @Test(dataProvider = "badArgs", expectedExceptions = IllegalArgumentException.class) - public void invalid(int hitCount, int missCount, int loadSuccessCount, int loadFailureCount, - int totalLoadTime, int evictionCount, int evictionWeight) { - CacheStats.of(hitCount, missCount, loadSuccessCount, - loadFailureCount, totalLoadTime, evictionCount, evictionWeight); - } - - @Test - public void empty() { - var stats = CacheStats.of(0, 0, 0, 0, 0, 0, 0); - checkStats(stats, 0, 0, 1.0, 0, 0.0, 0, 0, 0.0, 0, 0, 0.0, 0, 0); - assertThat(stats).isEqualTo(CacheStats.empty()); - assertThat(stats.equals(null)).isFalse(); - assertThat(stats).isNotEqualTo(new Object()); - assertThat(stats).isEqualTo(CacheStats.empty()); - assertThat(stats.hashCode()).isEqualTo(CacheStats.empty().hashCode()); - assertThat(stats.toString()).isEqualTo(CacheStats.empty().toString()); - } - - @Test - public void populated() { - var stats = CacheStats.of(11, 13, 17, 19, 23, 27, 54); - checkStats(stats, 24, 11, 11.0 / 24, 13, 13.0 / 24, - 17, 19, 19.0 / 36, 17 + 19, 23, 23.0 / (17 + 19), 27, 54); - assertThat(stats.equals(stats)).isTrue(); - assertThat(stats).isNotEqualTo(CacheStats.empty()); - assertThat(stats.hashCode()).isNotEqualTo(CacheStats.empty().hashCode()); - assertThat(stats.toString()).isNotEqualTo(CacheStats.empty().toString()); - var expected = CacheStats.of(11, 13, 17, 19, 23, 27, 54); - assertThat(stats.equals(expected)).isTrue(); - assertThat(stats.hashCode()).isEqualTo(expected.hashCode()); - assertThat(stats.toString()).isEqualTo(expected.toString()); - } - - @Test - public void minus() { - var one = CacheStats.of(11, 13, 17, 19, 23, 27, 54); - var two = CacheStats.of(53, 47, 43, 41, 37, 31, 62); - var diff = two.minus(one); - checkStats(diff, 76, 42, 42.0 / 76, 34, 34.0 / 76, - 26, 22, 22.0 / 48, 26 + 22, 14, 14.0 / (26 + 22), 4, 8); - assertThat(one.minus(two)).isEqualTo(CacheStats.empty()); - } - - @Test - public void plus() { - var one = CacheStats.of(11, 13, 15, 13, 11, 9, 18); - var two = CacheStats.of(53, 47, 41, 39, 37, 35, 70); - var sum = two.plus(one); - checkStats(sum, 124, 64, 64.0 / 124, 60, 60.0 / 124, - 56, 52, 52.0 / 108, 56 + 52, 48, 48.0 / (56 + 52), 44, 88); - assertThat(sum).isEqualTo(one.plus(two)); - } - - @Test - public void overflow() { - var max = CacheStats.of(Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE); - checkStats(max.plus(max), Long.MAX_VALUE, Long.MAX_VALUE, 1.0, Long.MAX_VALUE, 1.0, - Long.MAX_VALUE, Long.MAX_VALUE, 1.0, Long.MAX_VALUE, Long.MAX_VALUE, 1.0, - Long.MAX_VALUE, Long.MAX_VALUE); - } - - @Test - public void underflow() { - var max = CacheStats.of(Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE); - assertThat(CacheStats.empty().minus(max)).isEqualTo(CacheStats.empty()); - } - - private static void checkStats(CacheStats stats, long requestCount, long hitCount, - double hitRate, long missCount, double missRate, long loadSuccessCount, - long loadFailureCount, double loadFailureRate, long loadCount, long totalLoadTime, - double averageLoadPenalty, long evictionCount, long evictionWeight) { - assertThat(stats.requestCount()).isEqualTo(requestCount); - assertThat(stats.hitCount()).isEqualTo(hitCount); - assertThat(stats.hitRate()).isEqualTo(hitRate); - assertThat(stats.missCount()).isEqualTo(missCount); - assertThat(stats.missRate()).isEqualTo(missRate); - assertThat(stats.loadSuccessCount()).isEqualTo(loadSuccessCount); - assertThat(stats.loadFailureCount()).isEqualTo(loadFailureCount); - assertThat(stats.loadFailureRate()).isEqualTo(loadFailureRate); - assertThat(stats.loadCount()).isEqualTo(loadCount); - assertThat(stats.totalLoadTime()).isEqualTo(totalLoadTime); - assertThat(stats.averageLoadPenalty()).isEqualTo(averageLoadPenalty); - assertThat(stats.evictionCount()).isEqualTo(evictionCount); - assertThat(stats.evictionWeight()).isEqualTo(evictionWeight); - } - - @DataProvider(name = "badArgs") - public Object[][] providesBadArgs() { - return new Object[][]{ - {-1, 0, 0, 0, 0, 0, 0,}, - {0, -1, 0, 0, 0, 0, 0,}, - {0, 0, -1, 0, 0, 0, 0,}, - {0, 0, 0, -1, 0, 0, 0,}, - {0, 0, 0, 0, -1, 0, 0,}, - {0, 0, 0, 0, 0, -1, 0,}, - {0, 0, 0, 0, 0, 0, -1,}, - }; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/stats/PackageSanityTests.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/stats/PackageSanityTests.java deleted file mode 100644 index 96f1aec..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/stats/PackageSanityTests.java +++ /dev/null @@ -1,11 +0,0 @@ - -package com.github.benmanes.caffeine.cache.stats; - -import com.google.common.testing.AbstractPackageSanityTests; - -public final class PackageSanityTests extends AbstractPackageSanityTests { - - public PackageSanityTests() { - ignoreClasses(clazz -> clazz.getSimpleName().endsWith("Test")); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/stats/StatsCounterTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/stats/StatsCounterTest.java deleted file mode 100644 index 7b58a4c..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/stats/StatsCounterTest.java +++ /dev/null @@ -1,123 +0,0 @@ - -package com.github.benmanes.caffeine.cache.stats; - -import com.github.benmanes.caffeine.cache.RemovalCause; -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import org.mockito.Mockito; -import org.testng.annotations.Test; - -import java.util.Arrays; - -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public final class StatsCounterTest { - - @Test - public void disabled() { - var counter = DisabledStatsCounter.INSTANCE; - counter.recordHits(1); - counter.recordMisses(1); - counter.recordEviction(1, RemovalCause.SIZE); - counter.recordLoadSuccess(1); - counter.recordLoadFailure(1); - assertThat(counter.snapshot()).isEqualTo(CacheStats.of(0, 0, 0, 0, 0, 0, 0)); - assertThat(counter.toString()).isEqualTo(CacheStats.of(0, 0, 0, 0, 0, 0, 0).toString()); - Arrays.stream(DisabledStatsCounter.values()).forEach(type -> assertThat(DisabledStatsCounter.valueOf(type.name())).isEqualTo(counter)); - } - - @Test - public void enabled() { - var counter = new ConcurrentStatsCounter(); - counter.recordHits(1); - counter.recordMisses(1); - counter.recordEviction(10, RemovalCause.SIZE); - counter.recordLoadSuccess(1); - counter.recordLoadFailure(1); - var expected = CacheStats.of(1, 1, 1, 1, 2, 1, 10); - assertThat(counter.snapshot()).isEqualTo(expected); - assertThat(counter.toString()).isEqualTo(expected.toString()); - assertThat(counter.snapshot().toString()).isEqualTo(expected.toString()); - counter.incrementBy(counter); - assertThat(counter.snapshot()).isEqualTo(CacheStats.of(2, 2, 2, 2, 4, 2, 20)); - } - - @Test - public void concurrent() { - var counter = new ConcurrentStatsCounter(); - ConcurrentTestHarness.timeTasks(5, () -> { - counter.recordHits(1); - counter.recordMisses(1); - counter.recordEviction(10, RemovalCause.SIZE); - counter.recordLoadSuccess(1); - counter.recordLoadFailure(1); - }); - assertThat(counter.snapshot()).isEqualTo(CacheStats.of(5, 5, 5, 5, 10, 5, 50)); - } - - @Test - public void guarded() { - var counter = StatsCounter.guardedStatsCounter(new ConcurrentStatsCounter()); - counter.recordHits(1); - counter.recordMisses(1); - counter.recordEviction(10, RemovalCause.SIZE); - counter.recordLoadSuccess(1); - counter.recordLoadFailure(1); - var expected = CacheStats.of(1, 1, 1, 1, 2, 1, 10); - assertThat(counter.snapshot()).isEqualTo(expected); - assertThat(counter.toString()).isEqualTo(expected.toString()); - assertThat(counter.snapshot().toString()).isEqualTo(expected.toString()); - } - - @Test - public void guarded_sameInstance() { - var counter = StatsCounter.guardedStatsCounter(new ConcurrentStatsCounter()); - assertThat(StatsCounter.guardedStatsCounter(counter)).isSameInstanceAs(counter); - } - - @Test - public void guarded_exception() { - var statsCounter = Mockito.mock(StatsCounter.class); - when(statsCounter.snapshot()).thenThrow(new NullPointerException()); - doThrow(NullPointerException.class).when(statsCounter).recordHits(anyInt()); - doThrow(NullPointerException.class).when(statsCounter).recordMisses(anyInt()); - doThrow(NullPointerException.class).when(statsCounter).recordEviction(anyInt(), any()); - doThrow(NullPointerException.class).when(statsCounter).recordLoadSuccess(anyLong()); - doThrow(NullPointerException.class).when(statsCounter).recordLoadFailure(anyLong()); - var guarded = StatsCounter.guardedStatsCounter(statsCounter); - guarded.recordHits(1); - guarded.recordMisses(1); - guarded.recordEviction(10, RemovalCause.SIZE); - guarded.recordLoadSuccess(1); - guarded.recordLoadFailure(1); - assertThat(guarded.snapshot()).isEqualTo(CacheStats.empty()); - verify(statsCounter).recordHits(1); - verify(statsCounter).recordMisses(1); - verify(statsCounter).recordEviction(10, RemovalCause.SIZE); - verify(statsCounter).recordLoadSuccess(1); - verify(statsCounter).recordLoadFailure(1); - } - - @Test - public void overflow_loadSuccess() { - var counter = new ConcurrentStatsCounter(); - counter.recordLoadSuccess(Long.MAX_VALUE); - counter.recordLoadSuccess(1); - CacheStats stats = counter.snapshot(); - assertThat(stats.totalLoadTime()).isEqualTo(Long.MAX_VALUE); - } - - @Test - public void overflow_loadFailure() { - var counter = new ConcurrentStatsCounter(); - counter.recordLoadFailure(Long.MAX_VALUE); - counter.recordLoadFailure(1); - CacheStats stats = counter.snapshot(); - assertThat(stats.totalLoadTime()).isEqualTo(Long.MAX_VALUE); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/AsyncCacheSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/AsyncCacheSubject.java deleted file mode 100644 index 64c5213..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/AsyncCacheSubject.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.AsyncCache; -import com.google.common.truth.FailureMetadata; -import com.google.common.truth.Subject; - -import java.util.Map; -import java.util.concurrent.Future; - -import static com.github.benmanes.caffeine.cache.LocalCacheSubject.asyncLocal; -import static com.github.benmanes.caffeine.cache.ReserializableSubject.asyncReserializable; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.cache; -import static com.github.benmanes.caffeine.testing.MapSubject.map; -import static com.google.common.truth.Truth.assertAbout; - -public final class AsyncCacheSubject extends Subject { - private final AsyncCache actual; - - private AsyncCacheSubject(FailureMetadata metadata, AsyncCache subject) { - super(metadata, subject); - this.actual = subject; - } - - public static Factory> asyncCache() { - return AsyncCacheSubject::new; - } - - public static AsyncCacheSubject assertThat(AsyncCache actual) { - return assertAbout(asyncCache()).that(actual); - } - - public void isEmpty() { - check("cache").about(map()).that(actual.asMap()).isExhaustivelyEmpty(); - check("cache").about(cache()).that(actual.synchronous()).isEmpty(); - } - - public void hasSize(long expectedSize) { - check("estimatedSize()").about(cache()).that(actual.synchronous()).hasSize(expectedSize); - } - - public void hasSizeLessThan(long other) { - check("estimatedSize()").about(cache()).that(actual.synchronous()).hasSizeLessThan(other); - } - - - public void hasSizeGreaterThan(long other) { - check("estimatedSize()").about(cache()).that(actual.synchronous()).hasSizeGreaterThan(other); - } - - - public void containsKey(Object key) { - check("cache").that(actual.asMap()).containsKey(key); - } - - - public void doesNotContainKey(Object key) { - check("cache").that(actual.asMap()).doesNotContainKey(key); - } - - - public void containsValue(Object value) { - if (value instanceof Future) { - check("cache").about(map()).that(actual.asMap()).containsValue(value); - } else { - check("cache").about(cache()).that(actual.synchronous()).containsValue(value); - } - } - - public void containsEntry(Object key, Object value) { - if (value instanceof Future) { - check("cache").that(actual.asMap()).containsEntry(key, value); - } else { - check("cache").about(cache()).that(actual.synchronous()).containsEntry(key, value); - } - } - - public void containsExactlyEntriesIn(Map expectedMap) { - if (expectedMap.values().stream().anyMatch(value -> value instanceof Future)) { - check("cache").that(actual.asMap()).containsExactlyEntriesIn(expectedMap); - } else { - check("cache").about(cache()) - .that(actual.synchronous()).containsExactlyEntriesIn(expectedMap); - } - } - - public void isReserialize() { - check("reserializable").about(asyncReserializable()).that(actual).isReserialize(); - } - - public void isValid() { - check("cache").about(asyncLocal()).that(actual).isValid(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContext.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContext.java deleted file mode 100644 index 8be1bb4..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContext.java +++ /dev/null @@ -1,466 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.*; -import com.github.benmanes.caffeine.cache.stats.CacheStats; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.cache.testing.GuavaCacheFromContext.GuavaLoadingCache; -import com.github.benmanes.caffeine.cache.testing.GuavaCacheFromContext.SingleLoader; -import com.github.benmanes.caffeine.cache.testing.RemovalListeners.ConsumingRemovalListener; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.base.MoreObjects; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import com.google.common.testing.FakeTicker; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ThreadLocalRandom; -import java.util.function.Function; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.cache.CacheLoader.asyncReloading; -import static com.google.common.truth.Truth.assertWithMessage; -import static java.util.Objects.requireNonNull; -import static java.util.function.Function.identity; - -public final class CacheContext { - private static final ThreadLocal> interner = ThreadLocal.withInitial(HashMap::new); - - final RemovalListener evictionListener; - final RemovalListener removalListener; - final Weigher weigher; - final InitialCapacity initialCapacity; - final Implementation implementation; - final CacheScheduler cacheScheduler; - final SerializableFakeTicker ticker; - final Listener evictionListenerType; - final Listener removalListenerType; - final CacheExecutor cacheExecutor; - final ReferenceType valueStrength; - final TrackingExecutor executor; - final ReferenceType keyStrength; - final CacheWeigher cacheWeigher; - final Expiry expiry; - final Map original; - final CacheExpiry expiryType; - final Population population; - final Maximum maximumSize; - final Scheduler scheduler; - final Expire afterAccess; - final Expire afterWrite; - final Expire expiryTime; - final Compute compute; - final Expire refresh; - final Loader loader; - final Stats stats; - final boolean isAsyncLoader; - CacheBuilder guava; - Caffeine caffeine; - AsyncCache asyncCache; - Cache cache; - - @Nullable Int firstKey; - @Nullable Int middleKey; - @Nullable Int lastKey; - long initialSize; - Int absentKey; - Int absentValue; - - Map absent; - - public CacheContext(InitialCapacity initialCapacity, Stats stats, CacheWeigher cacheWeigher, - Maximum maximumSize, CacheExpiry expiryType, Expire afterAccess, Expire afterWrite, - Expire refresh, ReferenceType keyStrength, ReferenceType valueStrength, - CacheExecutor cacheExecutor, CacheScheduler cacheScheduler, Listener removalListenerType, - Listener evictionListenerType, Population population, boolean isAsyncLoader, Compute compute, - Loader loader, Implementation implementation, CacheSpec cacheSpec) { - this.initialCapacity = requireNonNull(initialCapacity); - this.stats = requireNonNull(stats); - this.weigher = cacheWeigher.create(); - this.cacheWeigher = cacheWeigher; - this.maximumSize = requireNonNull(maximumSize); - this.afterAccess = requireNonNull(afterAccess); - this.afterWrite = requireNonNull(afterWrite); - this.refresh = requireNonNull(refresh); - this.keyStrength = requireNonNull(keyStrength); - this.valueStrength = requireNonNull(valueStrength); - this.cacheExecutor = requireNonNull(cacheExecutor); - this.executor = cacheExecutor.create(); - this.cacheScheduler = requireNonNull(cacheScheduler); - this.scheduler = cacheScheduler.create(); - this.removalListenerType = removalListenerType; - this.removalListener = removalListenerType.create(); - this.evictionListenerType = evictionListenerType; - this.evictionListener = evictionListenerType.create(); - this.population = requireNonNull(population); - this.loader = requireNonNull(loader); - this.isAsyncLoader = isAsyncLoader; - this.ticker = new SerializableFakeTicker(); - this.implementation = requireNonNull(implementation); - this.original = new LinkedHashMap<>(); - this.initialSize = -1; - this.compute = compute; - this.expiryType = expiryType; - this.expiryTime = cacheSpec.expiryTime(); - this.expiry = expiryType.createExpiry(expiryTime); - } - - public static Map interner() { - return interner.get(); - } - - @SuppressWarnings("unchecked") - public static T intern(T o) { - return (T) interner.get().computeIfAbsent(o, identity()); - } - - @SuppressWarnings("unchecked") - public static V intern(K key, Function mappingFunction) { - return (V) interner.get().computeIfAbsent(key, k -> mappingFunction.apply((K) k)); - } - - public Caffeine caffeine() { - return caffeine; - } - - public CacheBuilder guava() { - return guava; - } - - public AsyncCache asyncCache() { - return asyncCache; - } - - public Cache cache() { - return cache; - } - - public InitialCapacity initialCapacity() { - return initialCapacity; - } - - public boolean isAsync() { - return (compute == Compute.ASYNC); - } - - public boolean isSync() { - return (compute == Compute.SYNC); - } - - public Population population() { - return population; - } - - public Int firstKey() { - assertWithMessage("Invalid usage of context").that(firstKey).isNotNull(); - return firstKey; - } - - public Int middleKey() { - assertWithMessage("Invalid usage of context").that(middleKey).isNotNull(); - return middleKey; - } - - public Int lastKey() { - assertWithMessage("Invalid usage of context").that(lastKey).isNotNull(); - return lastKey; - } - - public ImmutableSet firstMiddleLastKeys() { - return ImmutableSet.of(firstKey(), middleKey(), lastKey()); - } - - public void cleanUp() { - cache.cleanUp(); - } - - public void clear() { - interner().clear(); - initialSize(); - original.clear(); - absent = null; - absentKey = null; - absentValue = null; - firstKey = null; - middleKey = null; - lastKey = null; - } - - public Int absentKey() { - return (absentKey == null) ? (absentKey = nextAbsentKey()) : absentKey; - } - - public Int absentValue() { - return (absentValue == null) ? (absentValue = absentKey().negate()) : absentValue; - } - - public Map absent() { - if (absent != null) { - return absent; - } - absent = new LinkedHashMap<>(); - absent.put(absentKey(), absentValue()); - do { - Int key = nextAbsentKey(); - absent.put(key, key.negate()); - } while (absent.size() < 10); - return absent; - } - - public Set absentKeys() { - return absent().keySet(); - } - - private Int nextAbsentKey() { - return Int.valueOf(ThreadLocalRandom.current().nextInt( - (Integer.MAX_VALUE / 2), Integer.MAX_VALUE)); - } - - public long initialSize() { - return (initialSize < 0) ? (initialSize = original.size()) : initialSize; - } - - public Maximum maximum() { - return maximumSize; - } - - public long maximumSize() { - return maximumSize.max(); - } - - public long maximumWeight() { - assertWithMessage("Invalid usage of context").that(isWeighted()).isTrue(); - long maximum = cacheWeigher.unitsPerEntry() * maximumSize.max(); - return (maximum < 0) ? Long.MAX_VALUE : maximum; - } - - public long maximumWeightOrSize() { - return isWeighted() ? maximumWeight() : maximumSize(); - } - - public Weigher weigher() { - return weigher; - } - - public CacheWeigher cacheWeigher() { - return cacheWeigher; - } - - public boolean isWeighted() { - return (cacheWeigher != CacheWeigher.DISABLED); - } - - public boolean isZeroWeighted() { - return (cacheWeigher == CacheWeigher.ZERO); - } - - public boolean refreshes() { - return (refresh != Expire.DISABLED); - } - - public Expire refreshAfterWrite() { - return refresh; - } - - public Map original() { - initialSize(); - return original; - } - - public boolean isStrongKeys() { - return keyStrength == ReferenceType.STRONG; - } - - public boolean isWeakKeys() { - return keyStrength == ReferenceType.WEAK; - } - - public boolean isStrongValues() { - return valueStrength == ReferenceType.STRONG; - } - - public boolean isWeakValues() { - return valueStrength == ReferenceType.WEAK; - } - - public boolean isSoftValues() { - return valueStrength == ReferenceType.SOFT; - } - - public boolean isLoading() { - return (loader != Loader.DISABLED); - } - - public boolean isAsyncLoader() { - return isAsyncLoader; - } - - public Loader loader() { - return loader; - } - - public Listener removalListenerType() { - return removalListenerType; - } - - public RemovalListener removalListener() { - return requireNonNull(removalListener); - } - - public void clearRemovalNotifications() { - if (removalListenerType() == Listener.CONSUMING) { - var listener = (ConsumingRemovalListener) removalListener; - listener.removed().clear(); - } - } - - public Listener evictionListenerType() { - return evictionListenerType; - } - - public RemovalListener evictionListener() { - return requireNonNull(evictionListener); - } - - public boolean isRecordingStats() { - return (stats == Stats.ENABLED); - } - - public CacheStats stats() { - return cache.stats(); - } - - public boolean expires(Expiration expiration) { - return (expiresAfterAccess() && (expiration == Expiration.AFTER_ACCESS)) - || (expiresAfterWrite() && (expiration == Expiration.AFTER_WRITE)) - || (expiresVariably() && (expiration == Expiration.VARIABLE)); - } - - public boolean expires() { - return expiresVariably() || expiresAfterAccess() || expiresAfterWrite(); - } - - public boolean expiresAfterAccess() { - return (afterAccess != Expire.DISABLED); - } - - public boolean expiresAfterWrite() { - return (afterWrite != Expire.DISABLED); - } - - public boolean expiresVariably() { - return (expiryType != CacheExpiry.DISABLED); - } - - public Expiry expiry() { - return expiry; - } - - public CacheExpiry expiryType() { - return expiryType; - } - - public Expire expiryTime() { - return expiryTime; - } - - public Expire expireAfterAccess() { - return afterAccess; - } - - public Expire expireAfterWrite() { - return afterWrite; - } - - public FakeTicker ticker() { - return ticker; - } - - public LoadingCache build(CacheLoader loader) { - LoadingCache cache; - if (isCaffeine()) { - cache = isAsync() ? caffeine.buildAsync(loader).synchronous() : caffeine.build(loader); - } else { - var guavaLoader = new SingleLoader<>(loader); - cache = new GuavaLoadingCache<>(guava.build(asyncReloading(guavaLoader, executor)), this); - } - this.cache = cache; - return cache; - } - - public AsyncLoadingCache buildAsync(CacheLoader loader) { - checkState(isCaffeine() && isAsync()); - AsyncLoadingCache cache = caffeine.buildAsync(loader); - this.cache = cache.synchronous(); - return cache; - } - - public AsyncLoadingCache buildAsync(AsyncCacheLoader loader) { - checkState(isCaffeine() && isAsync()); - AsyncLoadingCache cache = caffeine.buildAsync(loader); - this.cache = cache.synchronous(); - return cache; - } - - public boolean isCaffeine() { - return (implementation == Implementation.Caffeine); - } - - public boolean isGuava() { - return (implementation == Implementation.Guava); - } - - public CacheExecutor executorType() { - return cacheExecutor; - } - - public TrackingExecutor executor() { - return executor; - } - - public Scheduler scheduler() { - return scheduler; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("population", population) - .add("maximumSize", maximumSize) - .add("weigher", cacheWeigher) - .add("expiry", expiryType) - .add("expiryTime", expiryTime) - .add("afterAccess", afterAccess) - .add("afterWrite", afterWrite) - .add("refreshAfterWrite", refresh) - .add("keyStrength", keyStrength) - .add("valueStrength", valueStrength) - .add("compute", compute) - .add("loader", loader) - .add("isAsyncLoader", isAsyncLoader) - .add("cacheExecutor", cacheExecutor) - .add("cacheScheduler", cacheScheduler) - .add("removalListener", removalListenerType) - .add("evictionListener", evictionListenerType) - .add("initialCapacity", initialCapacity) - .add("stats", stats) - .add("implementation", implementation) - .add("startTime", ticker.startTime) - .toString(); - } - - static final class SerializableFakeTicker extends FakeTicker implements Serializable { - final long startTime; - - @SuppressWarnings("PreferJavaTimeOverload") - public SerializableFakeTicker() { - startTime = ThreadLocalRandom.current().nextLong(Long.MIN_VALUE, Long.MAX_VALUE); - advance(startTime); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContextSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContextSubject.java deleted file mode 100644 index b567d1a..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheContextSubject.java +++ /dev/null @@ -1,346 +0,0 @@ -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Policy.CacheEntry; -import com.github.benmanes.caffeine.cache.RemovalCause; -import com.github.benmanes.caffeine.cache.RemovalListener; -import com.github.benmanes.caffeine.cache.stats.CacheStats; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExecutor; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.Listener; -import com.github.benmanes.caffeine.cache.testing.RemovalListeners.ConsumingRemovalListener; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.base.CaseFormat; -import com.google.common.collect.ImmutableMultiset; -import com.google.common.truth.FailureMetadata; -import com.google.common.truth.StandardSubjectBuilder; -import com.google.common.truth.Subject; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.errorprone.annotations.CheckReturnValue; - -import java.util.Map; -import java.util.Map.Entry; -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.function.ToLongFunction; -import java.util.stream.Stream; - -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.ListenerSubject.*; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.StatsSubject.STATS_FACTORY; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.cache; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static com.google.common.collect.ImmutableMultiset.toImmutableMultiset; -import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.truth.OptionalLongSubject.optionalLongs; -import static com.google.common.truth.StreamSubject.streams; -import static com.google.common.truth.Truth.assertAbout; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static java.util.function.Function.identity; - - -public final class CacheContextSubject extends Subject { - private final CacheContext actual; - - CacheContextSubject(FailureMetadata metadata, CacheContext subject) { - super(metadata, subject); - this.actual = subject; - } - - public static Factory context() { - return CacheContextSubject::new; - } - - public static CacheContextSubject assertThat(CacheContext actual) { - return assertAbout(context()).that(actual); - } - - public void hasWeightedSize(long expectedSize) { - checkArgument(expectedSize >= 0, "expectedSize (%s) must be >= 0", expectedSize); - actual.cache().policy().eviction().ifPresentOrElse(policy -> check("weightedSize()").about(optionalLongs()).that(policy.weightedSize()).hasValue(expectedSize), () -> { - long weight = actual.cache().asMap().entrySet().stream().mapToLong(entry -> actual.weigher().weigh(entry.getKey(), entry.getValue())).sum(); - check("weight").that(weight).isEqualTo(expectedSize); - }); - } - - public void hasWeightedSizeLessThan(long other) { - checkArgument(other >= 0, "other (%s) must be >= 0", other); - actual.cache().policy().eviction().ifPresentOrElse(policy -> { - check("weightedSize()").about(optionalLongs()).that(policy.weightedSize()).isPresent(); - check("weightedSize()").that(policy.weightedSize().orElseThrow()).isLessThan(other); - }, () -> { - long weight = actual.cache().asMap().entrySet().stream().mapToLong(entry -> actual.weigher().weigh(entry.getKey(), entry.getValue())).sum(); - check("weight").that(weight).isLessThan(other); - }); - } - - public void containsEntry(CacheEntry entry) { - checkBasic(entry); - checkWeight(entry); - checkExpiresAt(entry); - checkRefreshableAt(entry); - } - - @CheckReturnValue - public StatsSubject stats() { - return actual.isRecordingStats() ? check("stats").about(STATS_FACTORY).that(actual) : ignoreCheck().about(STATS_FACTORY).that(actual); - } - - @CheckReturnValue - public ListenerSubject removalNotifications() { - return check("context").about(REMOVAL_LISTENER_FACTORY).that(actual); - } - - @CheckReturnValue - public ListenerSubject evictionNotifications() { - return check("context").about(EVICTION_LISTENER_FACTORY).that(actual); - } - - @CheckReturnValue - public ListenerSubject notifications() { - return check("context").about(LISTENERS_FACTORY).that(actual); - } - - private void checkBasic(CacheEntry entry) { - check("entry").about(cache()).that(actual.cache()).containsEntry(entry.getKey(), entry.getValue()); - check("snapshotAt").that(entry.snapshotAt()).isEqualTo((actual.expires() || actual.refreshes()) ? actual.ticker().read() : 0L); - try { - entry.setValue(entry.getValue()); - failWithActual("setValue", entry); - } catch (UnsupportedOperationException expected) { /* ignored */ } - } - - private void checkWeight(CacheEntry entry) { - @SuppressWarnings("unchecked") var cache = (Cache) actual.cache(); - cache.policy().eviction().ifPresent(policy -> check("weight").that(entry.weight()).isEqualTo(policy.weightOf(entry.getKey()).orElse(1))); - } - - private void checkExpiresAt(CacheEntry entry) { - @SuppressWarnings("unchecked") var cache = (Cache) actual.cache(); - long expiresAt = entry.expiresAt(); - long now = actual.ticker().read(); - - if (!actual.expires()) { - check("expiresAt").that(expiresAt).isEqualTo(entry.snapshotAt() + Long.MAX_VALUE); - } - cache.policy().expireAfterAccess().ifPresent(policy -> { - var duration = policy.getExpiresAfter().minus(policy.ageOf(entry.getKey()).orElseThrow()); - check("expiresAccessAt").that(NANOSECONDS.toSeconds(expiresAt)).isEqualTo(NANOSECONDS.toSeconds(now + duration.toNanos())); - check("expiresAccessAfter").that(entry.expiresAfter().toSeconds()).isEqualTo(duration.toSeconds()); - }); - cache.policy().expireAfterWrite().ifPresent(policy -> { - var duration = policy.getExpiresAfter().minus(policy.ageOf(entry.getKey()).orElseThrow()); - check("expiresWriteAt").that(NANOSECONDS.toSeconds(expiresAt)).isEqualTo(NANOSECONDS.toSeconds(now + duration.toNanos())); - check("expiresWriteAfter").that(entry.expiresAfter().toSeconds()).isEqualTo(duration.toSeconds()); - }); - cache.policy().expireVariably().ifPresent(policy -> { - long expected = now + policy.getExpiresAfter(entry.getKey(), NANOSECONDS).orElseThrow(); - check("expiresVariablyAt").that(expiresAt).isEqualTo(expected); - check("expiresVariablyAfter").that(entry.expiresAfter()).isEqualTo(policy.getExpiresAfter(entry.getKey()).orElseThrow()); - }); - } - - private void checkRefreshableAt(CacheEntry entry) { - @SuppressWarnings("unchecked") var cache = (Cache) actual.cache(); - long refreshableAt = entry.refreshableAt(); - long now = actual.ticker().read(); - if (!actual.refreshes()) { - check("refreshableAt").that(refreshableAt).isEqualTo(entry.snapshotAt() + Long.MAX_VALUE); - } - cache.policy().refreshAfterWrite().ifPresent(policy -> { - var duration = policy.getRefreshesAfter().minus(policy.ageOf(entry.getKey()).orElseThrow()); - check("refreshableAt").that(NANOSECONDS.toSeconds(refreshableAt)).isEqualTo(NANOSECONDS.toSeconds(now + duration.toNanos())); - check("refreshableAfter").that(entry.refreshableAfter().toSeconds()).isEqualTo(duration.toSeconds()); - }); - } - - public static final class StatsSubject extends Subject { - static final Factory STATS_FACTORY = StatsSubject::new; - private final CacheContext actual; - private final boolean isDirect; - - private StatsSubject(FailureMetadata metadata, CacheContext context) { - super(metadata, context); - this.actual = context; - this.isDirect = !context.isRecordingStats() || (context.executorType() == CacheExecutor.DIRECT); - } - - @CanIgnoreReturnValue - public StatsSubject hits(long count) { - return awaitStatistic("hitCount", CacheStats::hitCount, count); - } - - @CanIgnoreReturnValue - public StatsSubject misses(long count) { - return awaitStatistic("missCount", CacheStats::missCount, count); - } - - @CanIgnoreReturnValue - public StatsSubject evictions(long count) { - return awaitStatistic("evictionCount", CacheStats::evictionCount, count); - } - - @CanIgnoreReturnValue - public StatsSubject evictionWeight(long count) { - return awaitStatistic("evictionWeight", CacheStats::evictionWeight, count); - } - - @CanIgnoreReturnValue - public StatsSubject success(long count) { - return awaitStatistic("loadSuccessCount", CacheStats::loadSuccessCount, count); - } - - @CanIgnoreReturnValue - public StatsSubject failures(long count) { - return awaitStatistic("loadFailureCount", CacheStats::loadFailureCount, count); - } - - @CanIgnoreReturnValue - private StatsSubject awaitStatistic(String label, ToLongFunction supplier, long expectedValue) { - if (isDirect) { - checkStatistic(label, supplier, expectedValue); - } else if (supplier.applyAsLong(actual.stats()) != expectedValue) { - await().pollInSameThread().untilAsserted(() -> checkStatistic(label, supplier, expectedValue)); - } - return this; - } - - private void checkStatistic(String label, ToLongFunction supplier, long expectedValue) { - var stats = actual.stats(); - check(label).withMessage("%s", stats).that(supplier.applyAsLong(stats)).isEqualTo(expectedValue); - } - } - - public static final class ListenerSubject extends Subject { - static final Factory REMOVAL_LISTENER_FACTORY = factoryOf(RemovalListenerType.REMOVAL_LISTENER); - static final Factory EVICTION_LISTENER_FACTORY = factoryOf(RemovalListenerType.EVICTION_LISTENER); - static final Factory LISTENERS_FACTORY = factoryOf(RemovalListenerType.values()); - private final Map> actual; - private final boolean isDirect; - - private ListenerSubject(FailureMetadata metadata, CacheContext context, Map> subject) { - super(metadata, subject); - this.actual = subject; - this.isDirect = (context.executorType() == CacheExecutor.DIRECT); - } - - private static Factory factoryOf(RemovalListenerType... removalListenerTypes) { - return (metadata, context) -> { - var subject = Stream.of(removalListenerTypes).filter(listener -> listener.isConsumingListener(context)).collect(toImmutableMap(identity(), listener -> listener.instance(context))); - return new ListenerSubject(metadata, context, subject); - }; - } - - @CheckReturnValue - public WithCause withCause(RemovalCause cause) { - return new WithCause(cause); - } - - public void isEmpty() { - awaitUntil((type, listener) -> check(type).that(listener.removed()).isEmpty()); - } - - public void hasSize(long expectedSize) { - awaitUntil((type, listener) -> check(type).that(listener.removed()).hasSize(Math.toIntExact(expectedSize))); - } - - public void containsExactlyValues(Object... values) { - awaitUntil((type, listener) -> { - var stream = listener.removed().stream().map(RemovalNotification::getValue); - check(type).about(streams()).that(stream).containsExactly(values); - }); - } - - public void hasNoEvictions() { - awaitUntil((type, listener) -> { - var stream = listener.removed().stream().filter(entry -> entry.getCause().wasEvicted()); - check(type).about(streams()).that(stream).isEmpty(); - }); - } - - private void awaitUntil(BiConsumer> consumer) { - actual.forEach((type, listener) -> { - if (!(listener instanceof ConsumingRemovalListener)) { - return; - } - var consuming = (ConsumingRemovalListener) listener; - if (isDirect) { - consumer.accept(type, consuming); - } else { - await().untilAsserted(() -> consumer.accept(type, consuming)); - } - }); - } - - private StandardSubjectBuilder check(RemovalListenerType type) { - return check(type.toString()); - } - - public final class WithCause { - private final RemovalCause cause; - - private WithCause(RemovalCause cause) { - this.cause = requireNonNull(cause); - } - - public Exclusive contains(Int key, Int value) { - awaitUntil((type, listener) -> check(type).withMessage("%s", cause).that(listener.removed()).contains(new RemovalNotification<>(key, value, cause))); - return new Exclusive(1); - } - - public Exclusive contains(Map map) { - return contains(map.entrySet().toArray(Entry[]::new)); - } - - public Exclusive contains(Entry... entries) { - awaitUntil((type, listener) -> { - var notifications = Stream.of(entries).map(entry -> new RemovalNotification<>(entry.getKey(), entry.getValue(), cause)).collect(toImmutableSet()); - check(type).withMessage("%s", cause).that(listener.removed()).containsAtLeastElementsIn(notifications); - }); - return new Exclusive(entries.length); - } - - public final class Exclusive { - private final long expectedSize; - - private Exclusive(long expectedSize) { - this.expectedSize = expectedSize; - } - - public void exclusively() { - awaitUntil((type, listener) -> { - var causes = listener.removed().stream().map(RemovalNotification::getCause).collect(toImmutableMultiset()); - check(type).that(causes).isEqualTo(ImmutableMultiset.builder().addCopies(cause, Math.toIntExact(expectedSize)).build()); - }); - } - } - } - } - - @SuppressWarnings("ImmutableEnumChecker") - private enum RemovalListenerType { - REMOVAL_LISTENER(CacheContext::removalListenerType, CacheContext::removalListener), EVICTION_LISTENER(CacheContext::evictionListenerType, CacheContext::evictionListener); - - private final Function> instance; - private final Function listener; - - RemovalListenerType(Function listener, Function> instance) { - this.listener = listener; - this.instance = instance; - } - - public boolean isConsumingListener(CacheContext context) { - return listener.apply(context) == Listener.CONSUMING; - } - - public RemovalListener instance(CacheContext context) { - return instance.apply(context); - } - - @Override - public String toString() { - return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name()); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheGenerator.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheGenerator.java deleted file mode 100644 index 262da57..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheGenerator.java +++ /dev/null @@ -1,202 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; -import org.mockito.Mockito; - -import java.util.*; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -public final class CacheGenerator { - private static final ImmutableList> INTS = makeInts(); - private static final int BASE = 1_000; - private final Options options; - private final CacheSpec cacheSpec; - private final boolean isAsyncOnly; - private final boolean isLoadingOnly; - private final boolean isGuavaCompatible; - - public CacheGenerator(CacheSpec cacheSpec) { - this(cacheSpec, Options.fromSystemProperties(), false, false, true); - } - - public CacheGenerator(CacheSpec cacheSpec, Options options, - boolean isLoadingOnly, boolean isAsyncOnly, boolean isGuavaCompatible) { - this.isGuavaCompatible = isGuavaCompatible; - this.isLoadingOnly = isLoadingOnly; - this.isAsyncOnly = isAsyncOnly; - this.cacheSpec = cacheSpec; - this.options = options; - } - - public Stream generate() { - return combinations().stream().map(this::newCacheContext).filter(this::isCompatible); - } - - - public static void initialize(CacheContext context) { - populate(context, newCache(context)); - } - - @SuppressWarnings("IdentityConversion") - private Set> combinations() { - var asyncLoader = ImmutableSet.of(true, false); - var loaders = ImmutableSet.copyOf(cacheSpec.loader()); - var keys = filterTypes(options.keys(), cacheSpec.keys()); - var values = filterTypes(options.values(), cacheSpec.values()); - var statistics = filterTypes(options.stats(), cacheSpec.stats()); - var computations = filterTypes(options.compute(), cacheSpec.compute()); - var implementations = filterTypes(options.implementation(), cacheSpec.implementation()); - if (isAsyncOnly) { - values = values.contains(ReferenceType.STRONG) - ? ImmutableSet.of(ReferenceType.STRONG) - : ImmutableSet.of(); - computations = Sets.intersection(computations, Set.of(Compute.ASYNC)); - } - if (!isGuavaCompatible || isAsyncOnly || computations.equals(ImmutableSet.of(Compute.ASYNC))) { - implementations = Sets.difference(implementations, Set.of(Implementation.Guava)); - } - if (computations.equals(ImmutableSet.of(Compute.SYNC))) { - asyncLoader = ImmutableSet.of(false); - } - if (isLoadingOnly) { - loaders = Sets.difference(loaders, Set.of(Loader.DISABLED)).immutableCopy(); - } - if (computations.isEmpty() || implementations.isEmpty() - || keys.isEmpty() || values.isEmpty()) { - return ImmutableSet.of(); - } - return Sets.cartesianProduct( - ImmutableSet.copyOf(cacheSpec.initialCapacity()), - ImmutableSet.copyOf(statistics), - ImmutableSet.copyOf(cacheSpec.weigher()), - ImmutableSet.copyOf(cacheSpec.maximumSize()), - ImmutableSet.copyOf(cacheSpec.expiry()), - ImmutableSet.copyOf(cacheSpec.expireAfterAccess()), - ImmutableSet.copyOf(cacheSpec.expireAfterWrite()), - ImmutableSet.copyOf(cacheSpec.refreshAfterWrite()), - ImmutableSet.copyOf(keys), - ImmutableSet.copyOf(values), - ImmutableSet.copyOf(cacheSpec.executor()), - ImmutableSet.copyOf(cacheSpec.scheduler()), - ImmutableSet.copyOf(cacheSpec.removalListener()), - ImmutableSet.copyOf(cacheSpec.evictionListener()), - ImmutableSet.copyOf(cacheSpec.population()), - ImmutableSet.copyOf(asyncLoader), - ImmutableSet.copyOf(computations), - ImmutableSet.copyOf(loaders), - ImmutableSet.copyOf(implementations)); - } - - private static Set filterTypes(Optional type, T[] options) { - return type.isPresent() - ? Sets.intersection(Set.of(options), Set.of(type.orElseThrow())) - : ImmutableSet.copyOf(options); - } - - private CacheContext newCacheContext(List combination) { - int index = 0; - return new CacheContext( - (InitialCapacity) combination.get(index++), - (Stats) combination.get(index++), - (CacheWeigher) combination.get(index++), - (Maximum) combination.get(index++), - (CacheExpiry) combination.get(index++), - (Expire) combination.get(index++), - (Expire) combination.get(index++), - (Expire) combination.get(index++), - (ReferenceType) combination.get(index++), - (ReferenceType) combination.get(index++), - (CacheExecutor) combination.get(index++), - (CacheScheduler) combination.get(index++), - (Listener) combination.get(index++), - (Listener) combination.get(index++), - (Population) combination.get(index++), - (Boolean) combination.get(index++), - (Compute) combination.get(index++), - (Loader) combination.get(index++), - (Implementation) combination.get(index++), - cacheSpec); - } - - private boolean isCompatible(CacheContext context) { - boolean asyncIncompatible = context.isAsync() - && (!context.isCaffeine() || !context.isStrongValues()); - boolean asyncLoaderIncompatible = context.isAsyncLoader() - && (!context.isAsync() || !context.isLoading()); - boolean refreshIncompatible = context.refreshes() && !context.isLoading(); - boolean weigherIncompatible = (context.maximum() == Maximum.DISABLED) && context.isWeighted(); - boolean referenceIncompatible = cacheSpec.requiresWeakOrSoft() - && context.isStrongKeys() && context.isStrongValues(); - boolean expiryIncompatible = (context.expiryType() != CacheExpiry.DISABLED) - && (!context.isCaffeine() - || (context.expireAfterAccess() != Expire.DISABLED) - || (context.expireAfterWrite() != Expire.DISABLED)); - boolean expirationIncompatible = (cacheSpec.mustExpireWithAnyOf().length > 0) - && Arrays.stream(cacheSpec.mustExpireWithAnyOf()).noneMatch(context::expires); - boolean schedulerIgnored = (context.cacheScheduler != CacheScheduler.DISABLED) - && (!context.expires() || context.isGuava()); - boolean evictionListenerIncompatible = (context.evictionListenerType() != Listener.DISABLED) - && (!context.isCaffeine() || (context.isAsync() && context.isWeakKeys())); - - boolean skip = asyncIncompatible || asyncLoaderIncompatible || evictionListenerIncompatible - || refreshIncompatible || weigherIncompatible || expiryIncompatible - || expirationIncompatible || referenceIncompatible || schedulerIgnored; - return !skip; - } - - private static Cache newCache(CacheContext context) { - if (context.isCaffeine()) { - return CaffeineCacheFromContext.newCaffeineCache(context); - } else if (context.isGuava()) { - return GuavaCacheFromContext.newGuavaCache(context); - } - throw new IllegalStateException(); - } - - @SuppressWarnings({"PreferJavaTimeOverload", "unchecked"}) - private static void populate(CacheContext context, Cache cache) { - if (context.population.size() == 0) { - return; - } - int maximum = (int) Math.min(context.maximumSize(), context.population.size()); - int first = BASE + (int) Math.min(0, context.population.size()); - int last = BASE + maximum - 1; - int middle = Math.max(first, BASE + ((last - first) / 2)); - IntStream.range(0, maximum).forEach(i -> { - Map.Entry entry = INTS.get(i); - var key = context.isStrongKeys() ? entry.getKey() : new Int(BASE + i); - var value = context.isStrongValues() ? entry.getValue() : new Int(-key.intValue()); - if (key.intValue() == first) { - context.firstKey = key; - } - if (key.intValue() == middle) { - context.middleKey = key; - } - if (key.intValue() == last) { - context.lastKey = key; - } - cache.put(key, value); - context.original.put(key, value); - }); - if (context.executorType() != CacheExecutor.DIRECT) { - cache.cleanUp(); - } - if (context.expiryType() == CacheExpiry.MOCKITO) { - Mockito.clearInvocations(context.expiry()); - } - } - - private static ImmutableList> makeInts() { - int size = Stream.of(Population.values()).mapToInt(population -> Math.toIntExact(population.size())).max().getAsInt(); - var builder = new ImmutableList.Builder>(); - IntStream.range(0, size).mapToObj(i -> Int.valueOf(BASE + i)).map(value -> Map.entry(value, value.negate())).forEach(builder::add); - return builder.build(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheProvider.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheProvider.java deleted file mode 100644 index 770c19c..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheProvider.java +++ /dev/null @@ -1,122 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.AsyncCache; -import com.github.benmanes.caffeine.cache.AsyncLoadingCache; -import com.github.benmanes.caffeine.cache.LoadingCache; -import com.github.benmanes.caffeine.cache.Policy; -import com.google.common.collect.ImmutableSet; -import org.testng.annotations.DataProvider; - -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.Arrays; -import java.util.Iterator; -import java.util.Map; -import java.util.stream.Stream; - -import static com.google.common.base.Preconditions.checkNotNull; - -public final class CacheProvider { - private static final Class BOUNDED_LOCAL_CACHE = classForName("com.github.benmanes.caffeine.cache.BoundedLocalCache"); - private static final ImmutableSet> GUAVA_INCOMPATIBLE = ImmutableSet.of( - AsyncCache.class, AsyncLoadingCache.class, BOUNDED_LOCAL_CACHE, Policy.Eviction.class, - Policy.FixedExpiration.class, Policy.VarExpiration.class, Policy.FixedRefresh.class); - - private final Parameter[] parameters; - private final Method testMethod; - - private CacheProvider(Method testMethod) { - this.parameters = testMethod.getParameters(); - this.testMethod = testMethod; - } - - @DataProvider(name = "caches") - public static Iterator providesCaches(Method testMethod) { - return new CacheProvider(testMethod).getTestCases(); - } - - private Iterator getTestCases() { - return scenarios() - .map(this::asTestCases) - .filter(params -> params.length > 0) - .iterator(); - } - - private Stream scenarios() { - var cacheSpec = checkNotNull(testMethod.getAnnotation(CacheSpec.class), "@CacheSpec not found"); - var generator = new CacheGenerator(cacheSpec, Options.fromSystemProperties(), - isLoadingOnly(), isAsyncOnly(), isGuavaCompatible()); - return generator.generate(); - } - - private Object[] asTestCases(CacheContext context) { - boolean intern = true; - CacheGenerator.initialize(context); - var params = new Object[parameters.length]; - for (int i = 0; i < parameters.length; i++) { - Class clazz = parameters[i].getType(); - if (clazz.isInstance(context)) { - params[i] = context; - intern = false; - } else if (clazz.isInstance(context.cache())) { - params[i] = context.cache(); - } else if (clazz.isInstance(context.asyncCache)) { - params[i] = context.asyncCache; - } else if (clazz.isAssignableFrom(Map.class)) { - params[i] = context.cache().asMap(); - } else if (clazz.isAssignableFrom(BOUNDED_LOCAL_CACHE)) { - if (!BOUNDED_LOCAL_CACHE.isInstance(context.cache().asMap())) { - return new Object[]{}; - } - params[i] = context.cache().asMap(); - } else if (clazz.isAssignableFrom(Policy.Eviction.class)) { - params[i] = context.cache().policy().eviction().orElse(null); - } else if (clazz.isAssignableFrom(Policy.VarExpiration.class)) { - params[i] = context.cache().policy().expireVariably().orElseThrow(); - } else if (clazz.isAssignableFrom(Policy.FixedRefresh.class)) { - params[i] = context.cache().policy().refreshAfterWrite().orElseThrow(); - } else if (clazz.isAssignableFrom(Policy.FixedExpiration.class)) { - if (parameters[i].isAnnotationPresent(ExpireAfterAccess.class)) { - params[i] = context.cache().policy().expireAfterAccess().orElseThrow(); - } else if (parameters[i].isAnnotationPresent(ExpireAfterWrite.class)) { - params[i] = context.cache().policy().expireAfterWrite().orElseThrow(); - } else { - throw new AssertionError("FixedExpiration must have a qualifier annotation"); - } - } - if (params[i] == null) { - checkNotNull(params[i], "Unknown parameter type: %s", clazz); - } - } - if (intern) { - CacheContext.intern(context); - } - return params; - } - - private boolean isAsyncOnly() { - return hasParameterOfType(AsyncCache.class); - } - - private boolean isLoadingOnly() { - return hasParameterOfType(AsyncLoadingCache.class) || hasParameterOfType(LoadingCache.class); - } - - private boolean isGuavaCompatible() { - return Arrays.stream(parameters) - .noneMatch(parameter -> GUAVA_INCOMPATIBLE.contains(parameter.getType())); - } - - private boolean hasParameterOfType(Class clazz) { - return Arrays.stream(parameters).map(Parameter::getType).anyMatch(clazz::isAssignableFrom); - } - - private static Class classForName(String className) { - try { - return Class.forName(className); - } catch (ClassNotFoundException e) { - throw new AssertionError(e); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSpec.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSpec.java deleted file mode 100644 index c655f60..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSpec.java +++ /dev/null @@ -1,763 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.*; -import com.github.benmanes.caffeine.testing.ConcurrentTestHarness; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.testing.TestingExecutors; -import org.mockito.Mockito; - -import java.io.Serializable; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import java.util.*; -import java.util.concurrent.*; -import java.util.function.Supplier; -import java.util.stream.IntStream; - -import static com.github.benmanes.caffeine.cache.testing.CacheContext.intern; -import static com.github.benmanes.caffeine.testing.ConcurrentTestHarness.scheduledExecutor; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; -import static java.util.Objects.requireNonNull; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toUnmodifiableMap; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.when; - -@SuppressWarnings("ImmutableEnumChecker") -@Target(METHOD) -@Retention(RUNTIME) -public @interface CacheSpec { - Compute[] compute() default { - Compute.ASYNC, - Compute.SYNC - }; - - enum Compute { - ASYNC, - SYNC, - } - - Implementation[] implementation() default { - Implementation.Caffeine, - Implementation.Guava, - }; - - enum Implementation { - Caffeine, - Guava - } - - - InitialCapacity[] initialCapacity() default { - InitialCapacity.DEFAULT - }; - - enum InitialCapacity { - DEFAULT(16), - ZERO(0), - ONE(1), - FULL(50), - EXCESSIVE(100); - - private final int size; - - InitialCapacity(int size) { - this.size = size; - } - - public int size() { - return size; - } - } - - Stats[] stats() default { - Stats.ENABLED, - Stats.DISABLED - }; - - enum Stats { - ENABLED, - DISABLED - } - - Maximum[] maximumSize() default { - Maximum.DISABLED, - Maximum.UNREACHABLE - }; - - enum Maximum { - DISABLED(Long.MAX_VALUE), - ZERO(0), - ONE(1), - TEN(10), - ONE_FIFTY(150), - FULL(InitialCapacity.FULL.size()), - UNREACHABLE(Long.MAX_VALUE); - - private final long max; - - Maximum(long max) { - this.max = max; - } - - public long max() { - return max; - } - } - - CacheWeigher[] weigher() default { - CacheWeigher.DISABLED, - CacheWeigher.ZERO, - CacheWeigher.TEN - }; - - enum CacheWeigher { - DISABLED(1), - TEN(10), - ZERO(0), - NEGATIVE(-1), - MAX_VALUE(Integer.MAX_VALUE), - VALUE(() -> (key, value) -> Math.abs(((Int) value).intValue()), 1), - COLLECTION(() -> (key, value) -> ((Collection) value).size(), 1), - RANDOM(Weighers::random, 1), - @SuppressWarnings("unchecked") - MOCKITO(() -> { - var weigher = Mockito.mock(Weigher.class); - when(weigher.weigh(any(), any())).thenReturn(1); - return weigher; - }, 1); - - private final Supplier> factory; - private final int units; - - CacheWeigher(int units) { - this.factory = () -> Weighers.constant(units); - this.units = units; - } - - CacheWeigher(Supplier> factory, int units) { - this.factory = factory; - this.units = units; - } - - public int unitsPerEntry() { - return units; - } - - @SuppressWarnings("unchecked") - public Weigher create() { - return (Weigher) factory.get(); - } - } - - Expiration[] mustExpireWithAnyOf() default {}; - - enum Expiration { - AFTER_WRITE, AFTER_ACCESS, VARIABLE - } - - Expire[] expireAfterAccess() default { - Expire.DISABLED, - Expire.FOREVER - }; - - Expire[] expireAfterWrite() default { - Expire.DISABLED, - Expire.FOREVER - }; - - Expire[] refreshAfterWrite() default { - Expire.DISABLED, - Expire.FOREVER - }; - - CacheExpiry[] expiry() default { - CacheExpiry.DISABLED, - CacheExpiry.ACCESS - }; - - Expire expiryTime() default Expire.FOREVER; - - enum CacheExpiry { - DISABLED { - @Override - public Expiry createExpiry(Expire expiryTime) { - return null; - } - }, - MOCKITO { - @Override - public Expiry createExpiry(Expire expiryTime) { - @SuppressWarnings("unchecked") - Expiry mock = Mockito.mock(Expiry.class); - when(mock.expireAfterCreate(any(), any(), anyLong())) - .thenReturn(expiryTime.timeNanos()); - when(mock.expireAfterUpdate(any(), any(), anyLong(), anyLong())) - .thenReturn(expiryTime.timeNanos()); - when(mock.expireAfterRead(any(), any(), anyLong(), anyLong())) - .thenReturn(expiryTime.timeNanos()); - return mock; - } - }, - CREATE { - @Override - public Expiry createExpiry(Expire expiryTime) { - return ExpiryBuilder - .expiringAfterCreate(expiryTime.timeNanos()) - .build(); - } - }, - WRITE { - @Override - public Expiry createExpiry(Expire expiryTime) { - return ExpiryBuilder - .expiringAfterCreate(expiryTime.timeNanos()) - .expiringAfterUpdate(expiryTime.timeNanos()) - .build(); - } - }, - ACCESS { - @Override - public Expiry createExpiry(Expire expiryTime) { - return ExpiryBuilder - .expiringAfterCreate(expiryTime.timeNanos()) - .expiringAfterUpdate(expiryTime.timeNanos()) - .expiringAfterRead(expiryTime.timeNanos()) - .build(); - } - }; - - public abstract Expiry createExpiry(Expire expiryTime); - } - - enum Expire { - DISABLED(Long.MIN_VALUE), - IMMEDIATELY(0), - ONE_MILLISECOND(TimeUnit.MILLISECONDS.toNanos(1)), - ONE_MINUTE(TimeUnit.MINUTES.toNanos(1)), - FOREVER(Long.MAX_VALUE); - - private final long timeNanos; - - Expire(long timeNanos) { - this.timeNanos = timeNanos; - } - - public long timeNanos() { - return timeNanos; - } - } - - boolean requiresWeakOrSoft() default false; - - ReferenceType[] keys() default { - ReferenceType.STRONG, - ReferenceType.WEAK - }; - - ReferenceType[] values() default { - ReferenceType.STRONG, - ReferenceType.WEAK, - ReferenceType.SOFT - }; - - enum ReferenceType { - STRONG, - WEAK, - SOFT - } - - Listener[] removalListener() default { - Listener.CONSUMING, - Listener.DISABLED, - }; - - Listener[] evictionListener() default { - Listener.CONSUMING, - Listener.DISABLED, - }; - - @SuppressWarnings("unchecked") - enum Listener { - DISABLED(() -> null), - REJECTING(RemovalListeners::rejecting), - CONSUMING(RemovalListeners::consuming), - MOCKITO(() -> Mockito.mock(RemovalListener.class)); - - private final Supplier> factory; - - Listener(Supplier> factory) { - this.factory = factory; - } - - public RemovalListener create() { - return (RemovalListener) factory.get(); - } - } - - Loader[] loader() default { - Loader.DISABLED, - Loader.NEGATIVE, - }; - - enum Loader implements CacheLoader { - DISABLED { - @Override - public Int load(Int key) { - throw new AssertionError(); - } - }, - NULL { - @Override - public Int load(Int key) { - return null; - } - }, - IDENTITY { - @Override - public Int load(Int key) { - requireNonNull(key); - return intern(key); - } - }, - NEGATIVE { - @Override - public Int load(Int key) { - - return intern(key, k -> new Int(-k.intValue())); - } - }, - EXCEPTIONAL { - @Override - public Int load(Int key) { - throw new IllegalStateException(); - } - }, - CHECKED_EXCEPTIONAL { - @Override - public Int load(Int key) throws ExecutionException { - throw new ExecutionException(null); - } - }, - INTERRUPTED { - @Override - public Int load(Int key) throws InterruptedException { - throw new InterruptedException(); - } - }, - BULK_NULL { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @SuppressWarnings("ReturnsNullCollection") - @Override - public Map loadAll(Set keys) { - return null; - } - }, - BULK_IDENTITY { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public Map loadAll(Set keys) { - var result = new HashMap(keys.size()); - for (Int key : keys) { - result.put(key, key); - intern(key); - } - return result; - } - }, - BULK_NEGATIVE { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public Map loadAll(Set keys) throws Exception { - var result = new HashMap(keys.size()); - for (Int key : keys) { - result.put(key, NEGATIVE.load(key)); - intern(key); - } - return result; - } - }, - BULK_DIFFERENT { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public Map loadAll(Set keys) { - return keys.stream().collect(toUnmodifiableMap( - key -> intern(intern(key).negate()), identity())); - } - }, - BULK_NEGATIVE_EXCEEDS { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public Map loadAll( - Set keys) throws Exception { - var moreKeys = new LinkedHashSet(keys.size() + 10); - moreKeys.addAll(keys); - IntStream.range(0, 10).mapToObj(i -> Int.valueOf(ThreadLocalRandom.current().nextInt())).forEach(moreKeys::add); - return BULK_NEGATIVE.loadAll(moreKeys); - } - }, - BULK_EXCEPTIONAL { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public Map loadAll(Set keys) { - throw new IllegalStateException(); - } - }, - BULK_CHECKED_EXCEPTIONAL { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public Map loadAll(Set keys) throws ExecutionException { - throw new ExecutionException(null); - } - }, - BULK_INTERRUPTED { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public Map loadAll(Set keys) throws InterruptedException { - throw new InterruptedException(); - } - }, - BULK_MODIFY_KEYS { - @Override - public Int load(Int key) { - return key; - } - - @Override - public Map loadAll(Set keys) { - keys.clear(); - return Map.of(); - } - }, - ASYNC_EXCEPTIONAL { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public CompletableFuture asyncLoad(Int key, Executor executor) { - throw new IllegalStateException(); - } - }, - ASYNC_CHECKED_EXCEPTIONAL { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public CompletableFuture asyncLoad( - Int key, Executor executor) throws ExecutionException { - throw new ExecutionException(null); - } - }, - ASYNC_INCOMPLETE { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public CompletableFuture asyncLoad(Int key, Executor executor) { - executor.execute(() -> { - }); - return new CompletableFuture<>(); - } - - @Override - public CompletableFuture asyncReload( - Int key, Int oldValue, Executor executor) { - executor.execute(() -> { - }); - return new CompletableFuture<>(); - } - }, - ASYNC_INTERRUPTED { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public CompletableFuture asyncLoad( - Int key, Executor executor) throws InterruptedException { - throw new InterruptedException(); - } - }, - ASYNC_BULK_EXCEPTIONAL { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public CompletableFuture> asyncLoadAll( - Set keys, Executor executor) { - throw new IllegalStateException(); - } - }, - ASYNC_BULK_CHECKED_EXCEPTIONAL { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public CompletableFuture> asyncLoadAll( - Set keys, Executor executor) throws ExecutionException { - throw new ExecutionException(null); - } - }, - ASYNC_BULK_MODIFY_KEYS { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public CompletableFuture> asyncLoadAll( - Set keys, Executor executor) { - keys.clear(); - return CompletableFuture.completedFuture(Map.of()); - } - }, - ASYNC_BULK_INTERRUPTED { - @Override - public Int load(Int key) { - throw new UnsupportedOperationException(); - } - - @Override - public CompletableFuture> asyncLoadAll( - Set keys, Executor executor) throws InterruptedException { - throw new InterruptedException(); - } - }, - - REFRESH_EXCEPTIONAL { - @Override - public Int load(Int key) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture asyncLoad(Int key, Executor executor) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture asyncReload( - Int key, Int oldValue, Executor executor) { - throw new IllegalStateException(); - } - }, - REFRESH_CHECKED_EXCEPTIONAL { - @Override - public Int load(Int key) throws ExecutionException { - throw new ExecutionException(null); - } - - @Override - public CompletableFuture asyncLoad( - Int key, Executor executor) throws ExecutionException { - throw new ExecutionException(null); - } - - @Override - public CompletableFuture asyncReload( - Int key, Int oldValue, Executor executor) throws ExecutionException { - throw new ExecutionException(null); - } - }, - REFRESH_INTERRUPTED { - @Override - public Int load(Int key) throws InterruptedException { - throw new InterruptedException(); - } - - @Override - public CompletableFuture asyncLoad( - Int key, Executor executor) throws InterruptedException { - throw new InterruptedException(); - } - - @Override - public CompletableFuture asyncReload( - Int key, Int oldValue, Executor executor) throws InterruptedException { - throw new InterruptedException(); - } - }; - - private final boolean bulk; - private final AsyncCacheLoader asyncLoader; - - Loader() { - bulk = name().contains("BULK"); - asyncLoader = bulk - ? new BulkSeriazableAsyncCacheLoader(this) - : new SeriazableAsyncCacheLoader(this); - } - - public boolean isBulk() { - return bulk; - } - - public AsyncCacheLoader async() { - return asyncLoader; - } - - private static class SeriazableAsyncCacheLoader - implements AsyncCacheLoader, Serializable { - private static final long serialVersionUID = 1L; - - final Loader loader; - - SeriazableAsyncCacheLoader(Loader loader) { - this.loader = loader; - } - - @Override - public CompletableFuture asyncLoad( - Int key, Executor executor) throws Exception { - return loader.asyncLoad(key, executor); - } - - private Object readResolve() { - return loader.asyncLoader; - } - } - - private static final class BulkSeriazableAsyncCacheLoader extends SeriazableAsyncCacheLoader { - private static final long serialVersionUID = 1L; - - BulkSeriazableAsyncCacheLoader(Loader loader) { - super(loader); - } - - @Override - public CompletableFuture asyncLoad(Int key, Executor executor) { - throw new IllegalStateException(); - } - - @Override - public CompletableFuture> asyncLoadAll( - Set keys, Executor executor) throws Exception { - return loader.asyncLoadAll(keys, executor); - } - } - } - - CacheExecutor[] executor() default { - CacheExecutor.DIRECT, - }; - - ExecutorFailure executorFailure() default ExecutorFailure.DISALLOWED; - - enum ExecutorFailure { - EXPECTED, DISALLOWED, IGNORED - } - - enum CacheExecutor { - DEFAULT(() -> null), - DIRECT(() -> new TrackingExecutor(MoreExecutors.newDirectExecutorService())), - DISCARDING(() -> new TrackingExecutor(TestingExecutors.noOpScheduledExecutor())), - THREADED(() -> new TrackingExecutor(ConcurrentTestHarness.executor)), - REJECTING(() -> new TrackingExecutor(new ForkJoinPool() { - @Override - public void execute(Runnable task) { - throw new RejectedExecutionException(); - } - })); - - private final Supplier executor; - - CacheExecutor(Supplier executor) { - this.executor = requireNonNull(executor); - } - - public TrackingExecutor create() { - return executor.get(); - } - } - - CacheScheduler[] scheduler() default { - CacheScheduler.DISABLED, - }; - - enum CacheScheduler { - DISABLED(() -> null), - SYSTEM(Scheduler::systemScheduler), - THREADED(() -> Scheduler.forScheduledExecutorService(scheduledExecutor)), - MOCKITO(() -> Mockito.mock(Scheduler.class)); - - private final Supplier scheduler; - - CacheScheduler(Supplier scheduler) { - this.scheduler = requireNonNull(scheduler); - } - - public Scheduler create() { - return scheduler.get(); - } - } - - Population[] population() default { - Population.EMPTY, - Population.SINGLETON, - Population.PARTIAL, - Population.FULL - }; - - enum Population { - EMPTY(0), - SINGLETON(1), - PARTIAL(InitialCapacity.FULL.size() / 2), - FULL(InitialCapacity.FULL.size()); - private final long size; - - Population(long size) { - this.size = size; - } - - public long size() { - return size; - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSubject.java deleted file mode 100644 index 9dff755..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheSubject.java +++ /dev/null @@ -1,151 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.Cache; -import com.google.common.testing.GcFinalization; -import com.google.common.truth.Correspondence; -import com.google.common.truth.FailureMetadata; -import com.google.common.truth.Ordered; -import com.google.common.truth.Subject; - -import java.util.Map; -import java.util.Objects; - -import static com.github.benmanes.caffeine.cache.LocalCacheSubject.syncLocal; -import static com.github.benmanes.caffeine.cache.ReserializableSubject.syncReserializable; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.CleanUpSubject.CLEANUP_FACTORY; -import static com.github.benmanes.caffeine.testing.MapSubject.map; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.truth.Truth.assertAbout; - -public final class CacheSubject extends Subject { - private static final Correspondence EQUALITY = - Correspondence.from(CacheSubject::tolerantEquals, "is equal to"); - - private final Cache actual; - - CacheSubject(FailureMetadata metadata, Cache subject) { - super(metadata, subject); - this.actual = subject; - } - - public static Factory> cache() { - return CacheSubject::new; - } - - public static CacheSubject assertThat(Cache actual) { - return assertAbout(cache()).that(actual); - } - - public void isEmpty() { - check("cache").about(map()).that(actual.asMap()).isExhaustivelyEmpty(); - hasSize(0); - actual.policy().eviction().ifPresent(policy -> policy.weightedSize().ifPresent(weightedSize -> check("weightedSize()").that(weightedSize).isEqualTo(0))); - } - - public void hasSize(long expectedSize) { - checkArgument(expectedSize >= 0, "expectedSize (%s) must be >= 0", expectedSize); - check("estimatedSize()").that(actual.estimatedSize()).isEqualTo(expectedSize); - } - - public void hasSizeLessThan(long other) { - checkArgument(other >= 0, "expectedSize (%s) must be >= 0", other); - check("estimatedSize()").that(actual.estimatedSize()).isLessThan(other); - } - - public void hasSizeGreaterThan(long other) { - checkArgument(other >= 0, "expectedSize (%s) must be >= 0", other); - check("estimatedSize()").that(actual.estimatedSize()).isGreaterThan(other); - } - - public void containsKey(Object key) { - check("cache").that(actual.asMap()).containsKey(key); - } - - public Ordered containsExactlyKeys(Iterable keys) { - return check("containsKeys").about(map()).that(actual.asMap()).containsExactlyKeys(keys); - } - - public void doesNotContainKey(Object key) { - check("cache").that(actual.asMap()).doesNotContainKey(key); - } - - public void containsValue(Object value) { - check("cache").about(map()).that(actual.asMap()).containsValue(value); - } - - public void doesNotContainValue(Object value) { - check("cache").about(map()).that(actual.asMap()).doesNotContainValue(value); - } - - public void containsEntry(Object key, Object value) { - check("cache").that(actual.asMap()) - .comparingValuesUsing(EQUALITY) - .containsEntry(key, value); - } - - public final void doesNotContainEntry(Object key, Object value) { - check("cache").that(actual.asMap()) - .comparingValuesUsing(EQUALITY) - .doesNotContainEntry(key, value); - } - - public void containsExactlyEntriesIn(Map expectedMap) { - check("cache").that(actual.asMap()) - .comparingValuesUsing(EQUALITY) - .containsExactlyEntriesIn(expectedMap); - } - - public void isReserialize() { - check("reserializable").about(syncReserializable()).that(actual).isReserialize(); - } - - public void isValid() { - check("cache").about(syncLocal()).that(actual).isValid(); - } - - public CleanUpSubject whenCleanedUp() { - return check("cleanUp()").about(CLEANUP_FACTORY).that(actual); - } - - private static boolean tolerantEquals(Object o1, Object o2) { - if ((o1 instanceof Integer) && (o2 instanceof Long)) { - return ((Integer) o1).longValue() == (Long) o2; - } else if ((o1 instanceof Long) && (o2 instanceof Integer)) { - return (Long) o1 == ((Integer) o2).longValue(); - } - return Objects.equals(o1, o2); - } - - public static final class CleanUpSubject extends Subject { - static final Factory> CLEANUP_FACTORY = CleanUpSubject::new; - - private final Cache actual; - - private CleanUpSubject(FailureMetadata metadata, Cache cache) { - super(metadata, cache.asMap()); - this.actual = cache; - } - - public void isEmpty() { - hasSize(0); - check("cache").about(cache()).that(actual).isEmpty(); - } - - public void hasSize(long expectedSize) { - for (int i = 0; i < 100; i++) { - if ((i > 0) && ((i % 10) == 0)) { - GcFinalization.awaitFullGc(); - } - actual.cleanUp(); - long size = actual.estimatedSize(); - if (size == expectedSize) { - return; - } else if (size < expectedSize) { - failWithActual("After cleanup expected size to be at least", expectedSize); - } - } - check("cache").about(cache()).that(actual).hasSize(expectedSize); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheValidationListener.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheValidationListener.java deleted file mode 100644 index 0894c69..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CacheValidationListener.java +++ /dev/null @@ -1,245 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.*; -import com.github.benmanes.caffeine.cache.Policy.Eviction; -import com.github.benmanes.caffeine.cache.Policy.FixedExpiration; -import com.github.benmanes.caffeine.cache.Policy.VarExpiration; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExecutor; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheExpiry; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.CacheScheduler; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.ExecutorFailure; -import com.github.valfirst.slf4jtest.TestLoggerFactory; -import org.apache.commons.lang3.StringUtils; -import org.joor.Reflect; -import org.mockito.Mockito; -import org.testng.*; -import org.testng.internal.TestResult; - -import java.util.*; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.IntStream; - -import static com.github.benmanes.caffeine.cache.testing.AsyncCacheSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheContextSubject.assertThat; -import static com.github.benmanes.caffeine.cache.testing.CacheSubject.assertThat; -import static com.github.benmanes.caffeine.testing.Awaits.await; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; -import static org.testng.ITestResult.FAILURE; -import static uk.org.lidalia.slf4jext.ConventionalLevelHierarchy.TRACE_LEVELS; - - -public final class CacheValidationListener implements ISuiteListener, IInvokedMethodListener { - private static final Cache simpleNames = Caffeine.newBuilder().build(); - private static final ITestContext testngContext = Mockito.mock(ITestContext.class); - private static final AtomicBoolean detailedParams = new AtomicBoolean(false); - private static final Object[] EMPTY_PARAMS = {}; - - private final List> resultQueues = new CopyOnWriteArrayList<>(); - private final AtomicBoolean beforeCleanup = new AtomicBoolean(); - - @Override - public void onStart(ISuite suite) { - if (suite instanceof SuiteRunner) { - var invokedMethods = Reflect.on(suite).fields().get("invokedMethods"); - if ((invokedMethods != null) && (invokedMethods.get() instanceof Collection)) { - resultQueues.add(invokedMethods.get()); - } - } - } - - @Override - public void beforeInvocation(IInvokedMethod method, ITestResult testResult) { - TestLoggerFactory.getAllTestLoggers().values().forEach(logger -> logger.setEnabledLevels(TRACE_LEVELS)); - TestLoggerFactory.clear(); - if (beforeCleanup.get() || !beforeCleanup.compareAndSet(false, true)) { - return; - } - if (testResult.getTestContext() instanceof TestRunner runner) { - runner.getTestListeners().stream() - .filter(listener -> listener.getClass() == TestListenerAdapter.class) - .flatMap(listener -> Reflect.on(listener).fields().values().stream()) - .filter(field -> field.get() instanceof Collection) - .forEach(field -> resultQueues.add(field.get())); - resultQueues.add(runner.getFailedButWithinSuccessPercentageTests().getAllResults()); - resultQueues.add(runner.getSkippedTests().getAllResults()); - resultQueues.add(runner.getPassedTests().getAllResults()); - resultQueues.add(runner.getFailedTests().getAllResults()); - var invokedMethods = Reflect.on(runner).fields().get("m_invokedMethods"); - if ((invokedMethods != null) && (invokedMethods.get() instanceof Collection)) { - resultQueues.add(invokedMethods.get()); - } - } - } - - @Override - public void afterInvocation(IInvokedMethod method, ITestResult testResult) { - try { - if (testResult.isSuccess()) { - validate(testResult); - } else if (!detailedParams.get()) { - detailedParams.set(true); - } - } catch (Throwable caught) { - testResult.setStatus(FAILURE); - testResult.setThrowable(new AssertionError(getTestName(method), caught)); - } finally { - cleanUp(testResult); - } - } - - private void validate(ITestResult testResult) { - CacheContext context = Arrays.stream(testResult.getParameters()) - .filter(param -> param instanceof CacheContext) - .map(param -> (CacheContext) param) - .findFirst().orElse(null); - if (context != null) { - awaitExecutor(context); - checkCache(context); - checkNoStats(testResult, context); - checkExecutor(testResult, context); - checkNoEvictions(testResult, context); - } - checkLogger(testResult); - } - - private void awaitExecutor(CacheContext context) { - if (context.executor() != null) { - context.executor().resume(); - - if ((context.cacheExecutor != CacheExecutor.DIRECT) - && (context.cacheExecutor != CacheExecutor.DISCARDING) - && (context.executor().submitted() != context.executor().completed())) { - await().pollInSameThread().until(() -> - context.executor().submitted() == context.executor().completed()); - } - } - } - - private static String getTestName(IInvokedMethod method) { - return StringUtils.substringAfterLast(method.getTestMethod().getTestClass().getName(), ".") - + "#" + method.getTestMethod().getConstructorOrMethod().getName(); - } - - private static void checkExecutor(ITestResult testResult, CacheContext context) { - var testMethod = testResult.getMethod().getConstructorOrMethod().getMethod(); - var cacheSpec = testMethod.getAnnotation(CacheSpec.class); - if ((cacheSpec == null) || (context.executor() == null)) { - return; - } - if (cacheSpec.executorFailure() == ExecutorFailure.EXPECTED) { - assertThat(context.executor().failed()).isGreaterThan(0); - } else if (cacheSpec.executorFailure() == ExecutorFailure.DISALLOWED) { - assertThat(context.executor().failed()).isEqualTo(0); - } - } - - private void checkCache(CacheContext context) { - if (context.cache != null) { - assertThat(context.cache).isValid(); - } else if (context.asyncCache != null) { - assertThat(context.asyncCache).isValid(); - } else { - Assert.fail("Test requires that the CacheContext holds the cache under test"); - } - } - - private static void checkNoStats(ITestResult testResult, CacheContext context) { - var testMethod = testResult.getMethod().getConstructorOrMethod().getMethod(); - boolean checkNoStats = testMethod.isAnnotationPresent(CheckNoStats.class) - || testResult.getTestClass().getRealClass().isAnnotationPresent(CheckNoStats.class); - if (!checkNoStats) { - return; - } - - assertThat(context).stats().hits(0).misses(0).success(0).failures(0); - } - - private static void checkNoEvictions(ITestResult testResult, CacheContext context) { - var testMethod = testResult.getMethod().getConstructorOrMethod().getMethod(); - boolean checkNoEvictions = testMethod.isAnnotationPresent(CheckNoEvictions.class) - || testResult.getTestClass().getRealClass().isAnnotationPresent(CheckNoEvictions.class); - if (!checkNoEvictions) { - return; - } - assertThat(context).removalNotifications().hasNoEvictions(); - assertThat(context).evictionNotifications().isEmpty(); - } - - private static void checkLogger(ITestResult testResult) { - var testMethod = testResult.getMethod().getConstructorOrMethod().getMethod(); - var checkMaxLogLevel = Optional.ofNullable(testMethod.getAnnotation(CheckMaxLogLevel.class)) - .orElse(testResult.getTestClass().getRealClass().getAnnotation(CheckMaxLogLevel.class)); - if (checkMaxLogLevel != null) { - var events = TestLoggerFactory.getLoggingEvents().stream() - .filter(event -> event.getLevel().ordinal() > checkMaxLogLevel.value().ordinal()) - .collect(toImmutableList()); - assertWithMessage("maxLevel=%s", checkMaxLogLevel.value()).that(events).isEmpty(); - } - } - - private void cleanUp(ITestResult testResult) { - resultQueues.forEach(Collection::clear); - TestLoggerFactory.clear(); - resetMocks(testResult); - resetCache(testResult); - boolean briefParams = !detailedParams.get(); - if (testResult.isSuccess() && briefParams) { - clearTestResults(testResult); - } - stringifyParams(testResult, briefParams); - dedupTestName(testResult, briefParams); - CacheContext.interner().clear(); - } - - private void dedupTestName(ITestResult testResult, boolean briefParams) { - if ((testResult.getName() != null) && briefParams) { - testResult.setTestName(simpleNames.get(testResult.getName(), Object::toString)); - } - } - - @SuppressWarnings("unchecked") - private void resetMocks(ITestResult testResult) { - Arrays.stream(testResult.getParameters()).filter(param -> param instanceof CacheContext).map(param -> (CacheContext) param).forEach(context -> { - if (context.expiryType() == CacheExpiry.MOCKITO) { - Mockito.clearInvocations(context.expiry()); - } - if (context.cacheScheduler == CacheScheduler.MOCKITO) { - Mockito.clearInvocations(context.scheduler()); - } - }); - } - - private void resetCache(ITestResult testResult) { - Arrays.stream(testResult.getParameters()) - .filter(param -> param instanceof CacheContext) - .map(param -> (CacheContext) param) - .filter(CacheContext::isCaffeine).map(CacheContext::cache).forEach(Reset::destroy); - } - - private void clearTestResults(ITestResult testResult) { - var result = (TestResult) testResult; - result.setParameters(EMPTY_PARAMS); - result.setContext(testngContext); - } - - private void stringifyParams(ITestResult testResult, boolean briefParams) { - Object[] params = testResult.getParameters(); - IntStream.range(0, params.length).forEach(i -> { - Object param = params[i]; - if ((param instanceof AsyncCache) || (param instanceof Cache) - || (param instanceof Map) || (param instanceof Policy.Eviction) - || (param instanceof Policy.FixedExpiration) || (param instanceof Policy.VarExpiration) - || ((param instanceof CacheContext) && briefParams)) { - params[i] = simpleNames.get(param.getClass(), key -> ((Class) key).getSimpleName()); - } else if (param instanceof CacheContext) { - params[i] = simpleNames.get(param.toString(), Object::toString); - } else { - params[i] = Objects.toString(param); - } - }); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CaffeineCacheFromContext.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CaffeineCacheFromContext.java deleted file mode 100644 index ee6d6e7..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CaffeineCacheFromContext.java +++ /dev/null @@ -1,95 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.Reset; -import com.github.benmanes.caffeine.cache.Ticker; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; - -import java.io.Serializable; -import java.util.concurrent.TimeUnit; - -@SuppressWarnings("PreferJavaTimeOverload") -public final class CaffeineCacheFromContext { - interface SerializableTicker extends Ticker, Serializable { - } - - private CaffeineCacheFromContext() { - } - - public static Cache newCaffeineCache(CacheContext context) { - Caffeine builder = Caffeine.newBuilder(); - context.caffeine = builder; - if (context.initialCapacity() != InitialCapacity.DEFAULT) { - builder.initialCapacity(context.initialCapacity().size()); - } - if (context.isRecordingStats()) { - builder.recordStats(); - } - if (context.maximum() != Maximum.DISABLED) { - if (context.cacheWeigher() == CacheWeigher.DISABLED) { - builder.maximumSize(context.maximum().max()); - } else { - builder.weigher(context.weigher()); - builder.maximumWeight(context.maximumWeight()); - } - } - if (context.expiryType() != CacheExpiry.DISABLED) { - builder.expireAfter(context.expiry()); - } - if (context.expiresAfterAccess()) { - builder.expireAfterAccess(context.expireAfterAccess().timeNanos(), TimeUnit.NANOSECONDS); - } - if (context.expiresAfterWrite()) { - builder.expireAfterWrite(context.expireAfterWrite().timeNanos(), TimeUnit.NANOSECONDS); - } - if (context.refreshes()) { - builder.refreshAfterWrite(context.refreshAfterWrite().timeNanos(), TimeUnit.NANOSECONDS); - } - if (context.expires() || context.refreshes()) { - SerializableTicker ticker = context.ticker()::read; - builder.ticker(ticker); - } - if (context.isWeakKeys()) { - builder.weakKeys(); - } else if (context.keyStrength == ReferenceType.SOFT) { - throw new IllegalStateException(); - } - if (context.isWeakValues()) { - builder.weakValues(); - } else if (context.isSoftValues()) { - builder.softValues(); - } - if (context.executorType() != CacheExecutor.DEFAULT) { - builder.executor(context.executor()); - } - if (context.cacheScheduler != CacheScheduler.DISABLED) { - builder.scheduler(context.scheduler()); - } - if (context.removalListenerType() != Listener.DISABLED) { - builder.removalListener(context.removalListener()); - } - if (context.evictionListenerType() != Listener.DISABLED) { - builder.evictionListener(context.evictionListener()); - } - if (context.isAsync()) { - if (context.loader() == Loader.DISABLED) { - context.asyncCache = builder.buildAsync(); - } else { - context.asyncCache = builder.buildAsync( - context.isAsyncLoader() ? context.loader().async() : context.loader()); - } - context.cache = context.asyncCache.synchronous(); - } else if (context.loader() == Loader.DISABLED) { - context.cache = builder.build(); - } else { - context.cache = builder.build(context.loader()); - } - - @SuppressWarnings("unchecked") - var castedCache = (Cache) context.cache; - Reset.resetThreadLocalRandom(); - return castedCache; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CheckMaxLogLevel.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CheckMaxLogLevel.java deleted file mode 100644 index 0e426d6..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CheckMaxLogLevel.java +++ /dev/null @@ -1,18 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import uk.org.lidalia.slf4jext.Level; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - - -@Target({METHOD, TYPE}) -@Retention(RUNTIME) -public @interface CheckMaxLogLevel { - Level value(); -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CheckNoEvictions.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CheckNoEvictions.java deleted file mode 100644 index 62cc7ed..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CheckNoEvictions.java +++ /dev/null @@ -1,14 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Target({METHOD, TYPE}) -@Retention(RUNTIME) -public @interface CheckNoEvictions { -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CheckNoStats.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CheckNoStats.java deleted file mode 100644 index f9c0b93..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/CheckNoStats.java +++ /dev/null @@ -1,14 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Target({METHOD, TYPE}) -@Retention(RUNTIME) -public @interface CheckNoStats { -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpireAfterAccess.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpireAfterAccess.java deleted file mode 100644 index 8feb427..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpireAfterAccess.java +++ /dev/null @@ -1,14 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - - -@Target(PARAMETER) -@Retention(RUNTIME) -public @interface ExpireAfterAccess { -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpireAfterWrite.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpireAfterWrite.java deleted file mode 100644 index a1ef2ed..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpireAfterWrite.java +++ /dev/null @@ -1,14 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - - -@Target(PARAMETER) -@Retention(RUNTIME) -public @interface ExpireAfterWrite { -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpiryBuilder.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpiryBuilder.java deleted file mode 100644 index a792bd7..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/ExpiryBuilder.java +++ /dev/null @@ -1,68 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.Expiry; -import com.google.errorprone.annotations.CanIgnoreReturnValue; - -import java.io.Serializable; - -import static java.util.Objects.requireNonNull; - -public final class ExpiryBuilder { - private static final int UNSET = -1; - private final long createNanos; - private long updateNanos; - private long readNanos; - - private ExpiryBuilder(long createNanos) { - this.createNanos = createNanos; - this.updateNanos = UNSET; - this.readNanos = UNSET; - } - - public static ExpiryBuilder expiringAfterCreate(long nanos) { - return new ExpiryBuilder(nanos); - } - - @CanIgnoreReturnValue - public ExpiryBuilder expiringAfterUpdate(long nanos) { - updateNanos = nanos; - return this; - } - - @CanIgnoreReturnValue - public ExpiryBuilder expiringAfterRead(long nanos) { - readNanos = nanos; - return this; - } - - public Expiry build() { - return new FixedExpiry<>(createNanos, updateNanos, readNanos); - } - - private record FixedExpiry(long createNanos, long updateNanos, - long readNanos) implements Expiry, Serializable { - private static final long serialVersionUID = 1L; - - @Override - public long expireAfterCreate(K key, V value, long currentTime) { - requireNonNull(key); - requireNonNull(value); - return createNanos; - } - - @Override - public long expireAfterUpdate(K key, V value, long currentTime, long currentDuration) { - requireNonNull(key); - requireNonNull(value); - return (updateNanos == UNSET) ? currentDuration : updateNanos; - } - - @Override - public long expireAfterRead(K key, V value, long currentTime, long currentDuration) { - requireNonNull(key); - requireNonNull(value); - return (readNanos == UNSET) ? currentDuration : readNanos; - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/GuavaCacheFromContext.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/GuavaCacheFromContext.java deleted file mode 100644 index fab9943..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/GuavaCacheFromContext.java +++ /dev/null @@ -1,692 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.LoadingCache; -import com.github.benmanes.caffeine.cache.Policy; -import com.github.benmanes.caffeine.cache.Policy.CacheEntry; -import com.github.benmanes.caffeine.cache.RemovalCause; -import com.github.benmanes.caffeine.cache.stats.CacheStats; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.*; -import com.github.benmanes.caffeine.testing.Int; -import com.google.common.base.Ticker; -import com.google.common.cache.AbstractCache.SimpleStatsCounter; -import com.google.common.cache.AbstractCache.StatsCounter; -import com.google.common.cache.RemovalNotification; -import com.google.common.cache.*; -import com.google.common.collect.ForwardingConcurrentMap; -import com.google.common.collect.ForwardingSet; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; -import com.google.common.util.concurrent.ExecutionError; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; -import com.google.common.util.concurrent.UncheckedExecutionException; - -import java.io.ObjectInputStream; -import java.io.Serializable; -import java.util.AbstractMap.SimpleImmutableEntry; -import java.util.*; -import java.util.concurrent.*; -import java.util.function.BiFunction; -import java.util.function.Function; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static java.util.Objects.requireNonNull; - -@SuppressWarnings({"PreferJavaTimeOverload"}) -public final class GuavaCacheFromContext { - private GuavaCacheFromContext() { - } - - private static final ThreadLocal error = new ThreadLocal<>(); - - @SuppressWarnings("CheckReturnValue") - public static Cache newGuavaCache(CacheContext context) { - checkState(!context.isAsync(), "Guava caches are synchronous only"); - var builder = CacheBuilder.newBuilder(); - context.guava = builder; - builder.concurrencyLevel(1); - if (context.initialCapacity() != InitialCapacity.DEFAULT) { - builder.initialCapacity(context.initialCapacity().size()); - } - if (context.isRecordingStats()) { - builder.recordStats(); - } - if (context.maximum() != Maximum.DISABLED) { - if (context.cacheWeigher() == CacheWeigher.DISABLED) { - builder.maximumSize(context.maximum().max()); - } else { - builder.weigher(new GuavaWeigher<>(context.weigher())); - builder.maximumWeight(context.maximumWeight()); - } - } - if (context.expiresAfterAccess()) { - builder.expireAfterAccess(context.expireAfterAccess().timeNanos(), TimeUnit.NANOSECONDS); - } - if (context.expiresAfterWrite()) { - builder.expireAfterWrite(context.expireAfterWrite().timeNanos(), TimeUnit.NANOSECONDS); - } - if (context.refreshes()) { - builder.refreshAfterWrite(context.refreshAfterWrite().timeNanos(), TimeUnit.NANOSECONDS); - } - if (context.expires() || context.refreshes()) { - builder.ticker(context.ticker()); - } - if (context.isWeakKeys()) { - builder.weakKeys(); - } else if (context.keyStrength == ReferenceType.SOFT) { - throw new IllegalStateException(); - } - if (context.isWeakValues()) { - builder.weakValues(); - } else if (context.isSoftValues()) { - builder.softValues(); - } - if (context.removalListenerType() != Listener.DISABLED) { - boolean translateZeroExpire = (context.expireAfterAccess() == Expire.IMMEDIATELY) || - (context.expireAfterWrite() == Expire.IMMEDIATELY); - builder.removalListener(new GuavaRemovalListener<>( - translateZeroExpire, context.removalListener())); - } - if (context.loader() == Loader.DISABLED) { - context.cache = new GuavaCache<>(builder.build(), context); - } else if (context.loader().isBulk()) { - var loader = new BulkLoader<>(context.loader()); - context.cache = new GuavaLoadingCache<>(builder.build(loader), context); - } else { - var loader = new SingleLoader<>(context.loader()); - context.cache = new GuavaLoadingCache<>(builder.build(loader), context); - } - @SuppressWarnings("unchecked") - Cache castedCache = (Cache) context.cache; - return castedCache; - } - - static class GuavaCache implements Cache, Serializable { - private static final long serialVersionUID = 1L; - private final com.google.common.cache.Cache cache; - private final boolean isRecordingStats; - private final boolean canSnapshot; - private final Ticker ticker; - - transient ConcurrentMap mapView; - transient StatsCounter statsCounter; - transient Policy policy; - transient Set keySet; - - GuavaCache(com.google.common.cache.Cache cache, CacheContext context) { - this.canSnapshot = context.expires() || context.refreshes(); - this.isRecordingStats = context.isRecordingStats(); - this.statsCounter = new SimpleStatsCounter(); - this.cache = requireNonNull(cache); - this.ticker = context.ticker(); - } - - @Override - public V getIfPresent(Object key) { - return cache.getIfPresent(key); - } - - @Override - public V get(K key, Function mappingFunction) { - requireNonNull(mappingFunction); - try { - return cache.get(key, () -> { - V value = mappingFunction.apply(key); - if (value == null) { - throw new CacheMissException(); - } - return value; - }); - } catch (UncheckedExecutionException e) { - if (e.getCause() instanceof CacheMissException) { - return null; - } - throw (RuntimeException) e.getCause(); - } catch (ExecutionException e) { - throw new CompletionException(e); - } catch (ExecutionError e) { - throw (Error) e.getCause(); - } - } - - @Override - public Map getAllPresent(Iterable keys) { - for (K key : keys) { - requireNonNull(key); - } - return cache.getAllPresent(keys); - } - - @Override - public Map getAll(Iterable keys, Function, - ? extends Map> mappingFunction) { - keys.forEach(Objects::requireNonNull); - requireNonNull(mappingFunction); - Map found = getAllPresent(keys); - Set keysToLoad = Sets.difference(ImmutableSet.copyOf(keys), found.keySet()); - if (keysToLoad.isEmpty()) { - return found; - } - long start = ticker.read(); - try { - var loaded = mappingFunction.apply(keysToLoad); - loaded.forEach(cache::put); - long end = ticker.read(); - statsCounter.recordLoadSuccess(end - start); - Map result = new LinkedHashMap<>(); - for (K key : keys) { - V value = found.get(key); - if (value == null) { - value = loaded.get(key); - } - if (value != null) { - result.put(key, value); - } - } - return Collections.unmodifiableMap(result); - } catch (Throwable t) { - long end = ticker.read(); - statsCounter.recordLoadException(end - start); - throw t; - } - } - - @Override - public void put(K key, V value) { - requireNonNull(key); - requireNonNull(value); - cache.put(key, value); - } - - @Override - public void putAll(Map map) { - cache.putAll(map); - } - - @Override - public void invalidate(K key) { - cache.invalidate(key); - } - - @Override - public void invalidateAll(Iterable keys) { - keys.forEach(this::invalidate); - } - - @Override - public void invalidateAll() { - cache.invalidateAll(); - } - - @Override - public long estimatedSize() { - return cache.size(); - } - - @Override - public CacheStats stats() { - var stats = statsCounter.snapshot().plus(cache.stats()); - return CacheStats.of(stats.hitCount(), stats.missCount(), stats.loadSuccessCount(), - stats.loadExceptionCount(), stats.totalLoadTime(), stats.evictionCount(), 0); - } - - @Override - public ConcurrentMap asMap() { - return (mapView == null) ? (mapView = new AsMapView()) : mapView; - } - - @Override - public void cleanUp() { - cache.cleanUp(); - } - - @Override - public Policy policy() { - return (policy == null) ? (policy = new GuavaPolicy()) : policy; - } - - final class AsMapView extends ForwardingConcurrentMap { - @Override - public boolean containsKey(Object key) { - requireNonNull(key); - return delegate().containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - requireNonNull(value); - return delegate().containsValue(value); - } - - @Override - public V get(Object key) { - requireNonNull(key); - return delegate().get(key); - } - - @Override - public V remove(Object key) { - requireNonNull(key); - return delegate().remove(key); - } - - @Override - public boolean remove(Object key, Object value) { - requireNonNull(key); - return delegate().remove(key, value); - } - - @Override - public boolean replace(K key, V oldValue, V newValue) { - requireNonNull(oldValue); - return delegate().replace(key, oldValue, newValue); - } - - @Override - public V computeIfAbsent(K key, Function mappingFunction) { - requireNonNull(mappingFunction); - V value = getIfPresent(key); - if (value != null) { - return value; - } - long now = ticker.read(); - try { - value = mappingFunction.apply(key); - long loadTime = (ticker.read() - now); - if (value == null) { - statsCounter.recordLoadException(loadTime); - return null; - } else { - statsCounter.recordLoadSuccess(loadTime); - V v = delegate().putIfAbsent(key, value); - return (v == null) ? value : v; - } - } catch (RuntimeException | Error e) { - statsCounter.recordLoadException((ticker.read() - now)); - throw e; - } - } - - @Override - public V computeIfPresent(K key, - BiFunction remappingFunction) { - requireNonNull(remappingFunction); - V oldValue; - long now = ticker.read(); - if ((oldValue = get(key)) != null) { - try { - V newValue = remappingFunction.apply(key, oldValue); - long loadTime = ticker.read() - now; - if (newValue == null) { - statsCounter.recordLoadException(loadTime); - remove(key); - return null; - } else { - statsCounter.recordLoadSuccess(loadTime); - put(key, newValue); - return newValue; - } - } catch (RuntimeException | Error e) { - statsCounter.recordLoadException(ticker.read() - now); - throw e; - } - } else { - return null; - } - } - - @Override - public V compute(K key, BiFunction remappingFunction) { - requireNonNull(remappingFunction); - V oldValue = get(key); - - long now = ticker.read(); - try { - V newValue = remappingFunction.apply(key, oldValue); - if (newValue == null) { - if (oldValue != null || containsKey(key)) { - remove(key); - } - statsCounter.recordLoadException(ticker.read() - now); - return null; - } else { - statsCounter.recordLoadSuccess(ticker.read() - now); - put(key, newValue); - return newValue; - } - } catch (RuntimeException | Error e) { - statsCounter.recordLoadException(ticker.read() - now); - throw e; - } - } - - @Override - public V merge(K key, V value, - BiFunction remappingFunction) { - requireNonNull(remappingFunction); - requireNonNull(value); - V oldValue = get(key); - for (; ; ) { - if (oldValue != null) { - long now = ticker.read(); - try { - V newValue = remappingFunction.apply(oldValue, value); - if (newValue != null) { - if (replace(key, oldValue, newValue)) { - statsCounter.recordLoadSuccess(ticker.read() - now); - return newValue; - } - } else if (remove(key, oldValue)) { - statsCounter.recordLoadException(ticker.read() - now); - return null; - } - } catch (RuntimeException | Error e) { - statsCounter.recordLoadException(ticker.read() - now); - throw e; - } - oldValue = get(key); - } else { - if ((oldValue = putIfAbsent(key, value)) == null) { - return value; - } - } - } - } - - @Override - public Set keySet() { - return (keySet == null) ? (keySet = new KeySetView()) : keySet; - } - - @Override - protected ConcurrentMap delegate() { - return cache.asMap(); - } - - @SuppressWarnings({"UnusedMethod", "UnusedVariable"}) - private void readObject(ObjectInputStream stream) { - statsCounter = new SimpleStatsCounter(); - } - - final class KeySetView extends ForwardingSet { - @Override - public boolean remove(Object o) { - requireNonNull(o); - return delegate().remove(o); - } - - @Override - protected Set delegate() { - return cache.asMap().keySet(); - } - } - } - - final class GuavaPolicy implements Policy { - @Override - public boolean isRecordingStats() { - return isRecordingStats; - } - - @Override - public V getIfPresentQuietly(K key) { - checkNotNull(key); - return cache.asMap().get(key); - } - - @Override - public CacheEntry getEntryIfPresentQuietly(K key) { - checkNotNull(key); - V value = cache.asMap().get(key); - if (value == null) { - return null; - } - long snapshotAt = canSnapshot ? ticker.read() : 0L; - return new GuavaCacheEntry<>(key, value, snapshotAt); - } - - @Override - public Map> refreshes() { - return Map.of(); - } - - @Override - public Optional> eviction() { - return Optional.empty(); - } - - @Override - public Optional> expireAfterAccess() { - return Optional.empty(); - } - - @Override - public Optional> expireAfterWrite() { - return Optional.empty(); - } - - @Override - public Optional> expireVariably() { - return Optional.empty(); - } - - @Override - public Optional> refreshAfterWrite() { - return Optional.empty(); - } - } - } - - static class GuavaLoadingCache extends GuavaCache implements LoadingCache { - private static final long serialVersionUID = 1L; - - private final com.google.common.cache.LoadingCache cache; - - GuavaLoadingCache(com.google.common.cache.LoadingCache cache, CacheContext context) { - super(cache, context); - this.cache = requireNonNull(cache); - } - - @Override - public V get(K key) { - try { - return cache.get(key); - } catch (UncheckedExecutionException e) { - if (e.getCause() instanceof CacheMissException) { - return null; - } - throw (RuntimeException) e.getCause(); - } catch (ExecutionException e) { - throw new CompletionException(e.getCause()); - } catch (ExecutionError e) { - throw (Error) e.getCause(); - } - } - - @Override - public Map getAll(Iterable keys) { - try { - return cache.getAll(keys); - } catch (UncheckedExecutionException e) { - if (e.getCause() instanceof CacheMissException) { - var results = new LinkedHashMap(); - for (K key : keys) { - var value = cache.asMap().get(key); - if (value != null) { - results.put(key, value); - } - } - return Collections.unmodifiableMap(results); - } - throw (RuntimeException) e.getCause(); - } catch (ExecutionException e) { - throw new CompletionException(e.getCause()); - } catch (ExecutionError e) { - throw (Error) e.getCause(); - } - } - - @Override - public CompletableFuture refresh(K key) { - error.set(null); - cache.refresh(key); - - var e = error.get(); - if (e == null) { - return CompletableFuture.completedFuture(cache.asMap().get(key)); - } else if (e instanceof InterruptedException) { - throw new CompletionException(e); - } else if (e instanceof CacheMissException) { - return CompletableFuture.completedFuture(null); - } - - error.remove(); - return CompletableFuture.failedFuture(e); - } - - @Override - public CompletableFuture> refreshAll(Iterable keys) { - Map> result = new LinkedHashMap<>(); - for (K key : keys) { - result.computeIfAbsent(key, this::refresh); - } - return composeResult(result); - } - - CompletableFuture> composeResult(Map> futures) { - if (futures.isEmpty()) { - return CompletableFuture.completedFuture(Map.of()); - } - CompletableFuture[] array = futures.values().toArray(new CompletableFuture[0]); - return CompletableFuture.allOf(array).thenApply(ignored -> { - Map result = new LinkedHashMap<>(futures.size()); - futures.forEach((key, future) -> { - V value = future.getNow(null); - if (value != null) { - result.put(key, value); - } - }); - return Collections.unmodifiableMap(result); - }); - } - } - - record GuavaWeigher( - com.github.benmanes.caffeine.cache.Weigher weigher) implements Weigher, Serializable { - private static final long serialVersionUID = 1L; - - @Override - public int weigh(K key, V value) { - return weigher.weigh(key, value); - } - } - - record GuavaRemovalListener(boolean translateZeroExpire, - com.github.benmanes.caffeine.cache.RemovalListener delegate) implements RemovalListener, Serializable { - private static final long serialVersionUID = 1L; - - @Override - public void onRemoval(RemovalNotification notification) { - RemovalCause cause = RemovalCause.valueOf(notification.getCause().name()); - if (translateZeroExpire && (cause == RemovalCause.SIZE)) { - cause = RemovalCause.EXPIRED; - } - delegate.onRemoval(notification.getKey(), notification.getValue(), cause); - } - } - - static class SingleLoader extends CacheLoader implements Serializable { - private static final long serialVersionUID = 1L; - final com.github.benmanes.caffeine.cache.CacheLoader delegate; - - SingleLoader(com.github.benmanes.caffeine.cache.CacheLoader delegate) { - this.delegate = delegate; - } - - @Override - public V load(K key) throws Exception { - try { - error.set(null); - V value = delegate.load(key); - if (value == null) { - throw new CacheMissException(); - } - return value; - } catch (Exception e) { - error.set(e); - throw e; - } - } - - @Override - @SuppressWarnings("FutureReturnValueIgnored") - public ListenableFuture reload(K key, V oldValue) throws Exception { - error.set(null); - var future = SettableFuture.create(); - delegate.asyncReload(key, oldValue, Runnable::run).whenComplete((r, e) -> { - if (e == null) { - future.set(r); - } else { - future.setException(e); - error.set(e); - } - }); - return future; - } - } - - static class BulkLoader extends SingleLoader { - private static final long serialVersionUID = 1L; - - BulkLoader(com.github.benmanes.caffeine.cache.CacheLoader delegate) { - super(delegate); - } - - @Override - @SuppressWarnings("unchecked") - public Map loadAll(Iterable keys) throws Exception { - var keysToLoad = (keys instanceof Set) ? (Set) keys : ImmutableSet.copyOf(keys); - var loaded = (Map) delegate.loadAll(keysToLoad); - return loaded; - } - } - - static final class GuavaCacheEntry - extends SimpleImmutableEntry implements CacheEntry { - private static final long serialVersionUID = 1L; - - private final long snapshot; - - public GuavaCacheEntry(K key, V value, long snapshot) { - super(key, value); - this.snapshot = snapshot; - } - - @Override - public int weight() { - return 1; - } - - @Override - public long expiresAt() { - return snapshot + Long.MAX_VALUE; - } - - @Override - public long refreshableAt() { - return snapshot + Long.MAX_VALUE; - } - - @Override - public long snapshotAt() { - return snapshot; - } - } - - static final class CacheMissException extends RuntimeException { - private static final long serialVersionUID = 1L; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/Options.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/Options.java deleted file mode 100644 index 498d7e7..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/Options.java +++ /dev/null @@ -1,48 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -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.ReferenceType; -import com.github.benmanes.caffeine.cache.testing.CacheSpec.Stats; -import com.google.common.base.Enums; - -import java.util.Optional; - -import static java.util.Locale.US; -import static org.apache.commons.lang3.StringUtils.capitalize; - -final class Options { - - private Options() { - } - - public static Options fromSystemProperties() { - return new Options(); - } - - Optional compute() { - return Optional.ofNullable(Enums.getIfPresent(Compute.class, - System.getProperty("compute", "").toUpperCase(US)).orNull()); - } - - Optional implementation() { - return Optional.ofNullable(Enums.getIfPresent(Implementation.class, - capitalize(System.getProperty("implementation", "").toLowerCase(US))).orNull()); - } - - Optional stats() { - return Optional.ofNullable(Enums.getIfPresent(Stats.class, - System.getProperty("stats", "").toUpperCase(US)).orNull()); - } - - Optional keys() { - return Optional.ofNullable(Enums.getIfPresent(ReferenceType.class, - System.getProperty("keys", "").toUpperCase(US)).orNull()); - } - - Optional values() { - return Optional.ofNullable(Enums.getIfPresent(ReferenceType.class, - System.getProperty("values", "").toUpperCase(US)).orNull()); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalListeners.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalListeners.java deleted file mode 100644 index 48bf517..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalListeners.java +++ /dev/null @@ -1,73 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.RemovalCause; -import com.github.benmanes.caffeine.cache.RemovalListener; - -import java.io.Serializable; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.RejectedExecutionException; - -import static java.util.Objects.requireNonNull; - - -public final class RemovalListeners { - - private RemovalListeners() { - } - - - public static ConsumingRemovalListener consuming() { - return new ConsumingRemovalListener<>(); - } - - public static RemovalListener rejecting() { - return new RejectingRemovalListener<>(); - } - - private static void validate(Object key, Object value, RemovalCause cause) { - if (cause != RemovalCause.COLLECTED) { - requireNonNull(key); - requireNonNull(value); - } - requireNonNull(cause); - } - - public static final class RejectingRemovalListener - implements RemovalListener, Serializable { - private static final long serialVersionUID = 1L; - public boolean reject = true; - public int rejected; - - @Override - public void onRemoval(K key, V value, RemovalCause cause) { - validate(key, value, cause); - if (reject) { - rejected++; - throw new RejectedExecutionException("Rejected eviction of " + - new RemovalNotification<>(key, value, cause)); - } - } - } - - public static final class ConsumingRemovalListener - implements RemovalListener, Serializable { - private static final long serialVersionUID = 1L; - private final CopyOnWriteArrayList> removed; - - public ConsumingRemovalListener() { - this.removed = new CopyOnWriteArrayList<>(); - } - - @Override - public void onRemoval(K key, V value, RemovalCause cause) { - validate(key, value, cause); - removed.add(new RemovalNotification<>(key, value, cause)); - } - - public List> removed() { - return removed; - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalNotification.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalNotification.java deleted file mode 100644 index 8288d24..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/RemovalNotification.java +++ /dev/null @@ -1,36 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.RemovalCause; -import com.google.errorprone.annotations.Immutable; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.AbstractMap.SimpleImmutableEntry; - -import static java.util.Objects.requireNonNull; - - -@Immutable(containerOf = {"K", "V"}) -public final class RemovalNotification extends SimpleImmutableEntry { - private static final long serialVersionUID = 1L; - - private final RemovalCause cause; - - public RemovalNotification(@Nullable K key, @Nullable V value, RemovalCause cause) { - super(key, value); - this.cause = requireNonNull(cause); - } - - public RemovalCause getCause() { - return cause; - } - - public boolean wasEvicted() { - return cause.wasEvicted(); - } - - @Override - public String toString() { - return getKey() + "=" + getValue() + " [" + cause + "]"; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/TrackingExecutor.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/TrackingExecutor.java deleted file mode 100644 index 3d9760d..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/TrackingExecutor.java +++ /dev/null @@ -1,76 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.google.common.util.concurrent.ForwardingExecutorService; -import com.google.common.util.concurrent.Uninterruptibles; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.atomic.AtomicInteger; - -import static java.util.Objects.requireNonNull; - -public final class TrackingExecutor extends ForwardingExecutorService { - private static final CountDownLatch ZERO = new CountDownLatch(0); - private final ExecutorService delegate; - private final AtomicInteger submitted; - private final AtomicInteger completed; - private final AtomicInteger failed; - - private volatile CountDownLatch latch; - - public TrackingExecutor(ExecutorService executor) { - delegate = requireNonNull(executor); - submitted = new AtomicInteger(); - completed = new AtomicInteger(); - failed = new AtomicInteger(); - latch = ZERO; - } - - @Override - public void execute(Runnable command) { - try { - submitted.incrementAndGet(); - delegate.execute(() -> { - Uninterruptibles.awaitUninterruptibly(latch); - try { - command.run(); - } catch (Throwable t) { - failed.incrementAndGet(); - throw t; - } finally { - completed.incrementAndGet(); - } - }); - } catch (Throwable t) { - completed.incrementAndGet(); - failed.incrementAndGet(); - throw t; - } - } - - public int submitted() { - return submitted.get(); - } - - public int completed() { - return completed.get(); - } - - public int failed() { - return failed.get(); - } - - public void pause() { - latch = new CountDownLatch(1); - } - - public void resume() { - latch.countDown(); - } - - @Override - public ExecutorService delegate() { - return delegate; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/Weighers.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/Weighers.java deleted file mode 100644 index b680b9f..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/testing/Weighers.java +++ /dev/null @@ -1,50 +0,0 @@ - -package com.github.benmanes.caffeine.cache.testing; - -import com.github.benmanes.caffeine.cache.Weigher; - -import java.io.Serializable; -import java.util.concurrent.ThreadLocalRandom; - -import static java.util.Objects.requireNonNull; - -public final class Weighers { - private Weighers() { - } - - public static Weigher constant(int weight) { - return new ConstantWeigher<>(weight); - } - - @SuppressWarnings("unchecked") - public static Weigher random() { - return (Weigher) RandomWeigher.INSTANCE; - } - - static final class ConstantWeigher implements Weigher, Serializable { - private static final long serialVersionUID = 1L; - private final int weight; - - ConstantWeigher(int weight) { - this.weight = weight; - } - - @Override - public int weigh(Object key, Object value) { - requireNonNull(key); - requireNonNull(value); - return weight; - } - } - - enum RandomWeigher implements Weigher { - INSTANCE; - - @Override - public int weigh(Object key, Object value) { - requireNonNull(key); - requireNonNull(value); - return ThreadLocalRandom.current().nextInt(1, 10); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Awaits.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Awaits.java deleted file mode 100644 index 68a0034..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Awaits.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.benmanes.caffeine.testing; - -import org.awaitility.Awaitility; -import org.awaitility.core.ConditionFactory; - -import java.time.Duration; - -public final class Awaits { - private static final Duration ONE_MILLISECOND = Duration.ofMillis(1); - - private Awaits() { - } - - public static ConditionFactory await() { - return Awaitility.with() - .pollDelay(ONE_MILLISECOND) - .pollInterval(ONE_MILLISECOND) - .pollExecutorService(ConcurrentTestHarness.executor); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/CollectionSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/CollectionSubject.java deleted file mode 100644 index b23579c..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/CollectionSubject.java +++ /dev/null @@ -1,99 +0,0 @@ - -package com.github.benmanes.caffeine.testing; - -import com.google.common.truth.FailureMetadata; - -import java.util.Collection; -import java.util.Deque; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Queue; -import java.util.Set; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.truth.Truth.assertAbout; - -public class CollectionSubject extends com.google.common.truth.IterableSubject { - private final Collection actual; - - public CollectionSubject(FailureMetadata metadata, Collection subject) { - super(metadata, subject); - this.actual = subject; - } - - public static Factory> collection() { - return CollectionSubject::new; - } - - public static CollectionSubject assertThat(Collection actual) { - return assertAbout(collection()).that(actual); - } - - public final void hasSize(long expectedSize) { - hasSize(Math.toIntExact(expectedSize)); - } - - public void hasSizeLessThan(long other) { - checkArgument(other >= 0, "expectedSize (%s) must be >= 0", other); - check("size()").that(actual.size()).isLessThan(Math.toIntExact(other)); - } - - public void isExhaustivelyEmpty() { - checkIterable(); - checkCollection(); - if (actual instanceof Set) { - checkSet((Set) actual); - } - if (actual instanceof List) { - checkList((List) actual); - } - if (actual instanceof Queue) { - checkQueue((Queue) actual); - } - if (actual instanceof Deque) { - checkDeque((Deque) actual); - } - } - - private void checkIterable() { - check("iterator().hasNext()").that(actual.iterator().hasNext()).isFalse(); - } - - private void checkCollection() { - check("size()").that(actual).hasSize(0); - check("isEmpty()").that(actual).isEmpty(); - check("toArray()").that(actual.toArray()).isEmpty(); - check("toArray(E[])").that(actual.toArray(new Object[0])).isEmpty(); - check("toArray(IntFunction)").that(actual.toArray(Object[]::new)).isEmpty(); - } - - private void checkSet(Set set) { - check("actual.equals(empty)").that(set).isEqualTo(Set.of()); - check("empty.equals(actual)").that(Set.of()).isEqualTo(set); - check("hashCode()").that(set.hashCode()).isEqualTo(Set.of().hashCode()); - } - - private void checkList(List list) { - check("actual.equals(empty)").that(list).isEqualTo(List.of()); - check("empty.equals(actual)").that(List.of()).isEqualTo(list); - check("hashCode()").that(list.hashCode()).isEqualTo(List.of().hashCode()); - } - - private void checkQueue(Queue queue) { - check("peek()").that(queue.peek()).isNull(); - try { - failWithActual("remove()", queue.remove()); - } catch (NoSuchElementException expected) { - } - try { - failWithActual("element()", queue.element()); - } catch (NoSuchElementException expected) { - } - } - - private void checkDeque(Deque deque) { - check("peekFirst()").that(deque.peekFirst()).isNull(); - check("peekLast()").that(deque.peekLast()).isNull(); - check("descendingIterator().hasNext()").that(deque.descendingIterator().hasNext()).isFalse(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/ConcurrentTestHarness.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/ConcurrentTestHarness.java deleted file mode 100644 index 50cd2f8..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/ConcurrentTestHarness.java +++ /dev/null @@ -1,71 +0,0 @@ - -package com.github.benmanes.caffeine.testing; - -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.google.common.util.concurrent.Uninterruptibles; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicReferenceArray; - -public final class ConcurrentTestHarness { - public static final ThreadFactory DAEMON_FACTORY = new ThreadFactoryBuilder().setPriority(Thread.MIN_PRIORITY).setDaemon(true).build(); - public static final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(DAEMON_FACTORY); - public static final ExecutorService executor = Executors.newCachedThreadPool(DAEMON_FACTORY); - - private ConcurrentTestHarness() { - } - - public static void execute(Runnable task) { - executor.execute(task); - } - - public static long timeTasks(int nThreads, Runnable task) { - return timeTasks(nThreads, Executors.callable(task)).executionTime(); - } - - public static TestResult timeTasks(int nThreads, Callable task) { - var startGate = new CountDownLatch(1); - var endGate = new CountDownLatch(nThreads); - var results = new AtomicReferenceArray(nThreads); - - for (int i = 0; i < nThreads; i++) { - final int index = i; - executor.execute(() -> { - try { - startGate.await(); - try { - results.set(index, task.call()); - } finally { - endGate.countDown(); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - long start = System.nanoTime(); - startGate.countDown(); - Uninterruptibles.awaitUninterruptibly(endGate); - long end = System.nanoTime(); - return new TestResult<>(end - start, toList(results)); - } - - private static List toList(AtomicReferenceArray data) { - var list = new ArrayList(data.length()); - for (int i = 0; i < data.length(); i++) { - list.add(data.get(i)); - } - return list; - } - - public record TestResult(long executionTime, List results) { - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/FutureSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/FutureSubject.java deleted file mode 100644 index 40be00d..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/FutureSubject.java +++ /dev/null @@ -1,75 +0,0 @@ - -package com.github.benmanes.caffeine.testing; - -import com.google.common.truth.FailureMetadata; -import com.google.common.truth.Subject; -import com.google.common.truth.ThrowableSubject; - -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; - -import static com.google.common.truth.Truth.assertAbout; - - -public final class FutureSubject extends Subject { - private final CompletableFuture actual; - - private FutureSubject(FailureMetadata metadata, CompletableFuture subject) { - super(metadata, subject); - this.actual = subject; - } - - public static Factory> future() { - return FutureSubject::new; - } - - public static FutureSubject assertThat(CompletableFuture actual) { - return assertAbout(future()).that(actual); - } - - public void isDone() { - if (!actual.isDone()) { - failWithActual("expected to be done", actual); - } - } - - public void isNotDone() { - if (actual.isDone()) { - failWithActual("expected to not be done", actual); - } - } - - public void hasCompletedExceptionally() { - if (!actual.isCompletedExceptionally()) { - failWithActual("expected to be completed exceptionally", actual.join()); - } - } - - public void succeedsWith(int value) { - var result = actual.join(); - if (result instanceof Int) { - check("future").that(result).isEqualTo(Int.valueOf(value)); - } else { - check("future").that(result).isEqualTo(value); - } - } - - public void succeedsWith(Object value) { - check("future").that(actual.join()).isEqualTo(value); - } - - public void succeedsWithNull() { - check("future").that(actual.join()).isNull(); - } - - public ThrowableSubject failsWith(Class clazz) { - try { - failWithActual("join", actual.join()); - throw new AssertionError(); - } catch (CompletionException | CancellationException e) { - check("future").that(e).isInstanceOf(clazz); - return check("future").that(e); - } - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Int.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Int.java deleted file mode 100644 index ceaa1d7..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Int.java +++ /dev/null @@ -1,154 +0,0 @@ - -package com.github.benmanes.caffeine.testing; - -import com.google.errorprone.annotations.Immutable; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; - -import static com.google.common.base.Preconditions.checkArgument; - -@Immutable -public final class Int implements Serializable { - public static final Int MAX_VALUE = Int.valueOf(Integer.MAX_VALUE); - - private static final long serialVersionUID = 1L; - - private static final int low = -2048; - private static final int high = 2048; - private static final Int[] cache = makeSharedCache(); - - private final int value; - - public Int(Int value) { - this(value.value); - } - - public Int(int value) { - this.value = value; - } - - - public int intValue() { - return value; - } - - - public Int negate() { - return valueOf(-value); - } - - - public Int add(int i) { - return valueOf(value + i); - } - - - public Int add(Int i) { - return add(i.value); - } - - - public CompletableFuture asFuture() { - return CompletableFuture.completedFuture(this); - } - - @Override - public boolean equals(Object o) { - return (o == this) || ((o instanceof Int) && (value == ((Int) o).value)); - } - - @Override - public int hashCode() { - return Integer.hashCode(value); - } - - @Override - public String toString() { - return Integer.toString(value); - } - - - public static Int valueOf(int i) { - return ((i >= low) && (i <= high)) ? cache[i - low] : new Int(i); - } - - public static List listOf(int... values) { - switch (values.length) { - case 1 -> { - return List.of(valueOf(values[0])); - } - case 2 -> { - return List.of(valueOf(values[0]), valueOf(values[1])); - } - case 3 -> { - return List.of(valueOf(values[0]), valueOf(values[1]), valueOf(values[2])); - } - case 4 -> { - return List.of(valueOf(values[0]), valueOf(values[1]), - valueOf(values[2]), valueOf(values[3])); - } - default -> { - var list = new ArrayList(values.length); - for (int value : values) { - list.add(valueOf(value)); - } - return list; - } - } - } - - public static Set setOf(int... values) { - switch (values.length) { - case 1 -> { - return Set.of(valueOf(values[0])); - } - case 2 -> { - return Set.of(valueOf(values[0]), valueOf(values[1])); - } - case 3 -> { - return Set.of(valueOf(values[0]), valueOf(values[1]), valueOf(values[2])); - } - case 4 -> { - return Set.of(valueOf(values[0]), valueOf(values[1]), - valueOf(values[2]), valueOf(values[3])); - } - default -> { - var set = new LinkedHashSet(values.length); - for (int value : values) { - set.add(valueOf(value)); - } - return set; - } - } - } - - public static Map mapOf(int... mappings) { - checkArgument((mappings.length % 2) == 0); - var map = new LinkedHashMap(mappings.length / 2); - for (int i = 0; i < mappings.length; i += 2) { - map.put(valueOf(mappings[i]), valueOf(mappings[i + 1])); - } - return map; - } - - - public static CompletableFuture futureOf(int i) { - return valueOf(i).asFuture(); - } - - - private static Int[] makeSharedCache() { - var array = new Int[high - low + 1]; - for (int i = 0; i < array.length; i++) { - array[i] = new Int(i + low); - } - return array; - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IntSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IntSubject.java deleted file mode 100644 index b5346c7..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/IntSubject.java +++ /dev/null @@ -1,26 +0,0 @@ - -package com.github.benmanes.caffeine.testing; - -import com.google.common.truth.FailureMetadata; -import com.google.common.truth.Subject; - -import static com.google.common.truth.Truth.assertAbout; - -public final class IntSubject extends Subject { - - private IntSubject(FailureMetadata metadata, Int subject) { - super(metadata, subject); - } - - public static Factory integer() { - return IntSubject::new; - } - - public static IntSubject assertThat(Int actual) { - return assertAbout(integer()).that(actual); - } - - public void isEqualTo(int value) { - isEqualTo(Int.valueOf(value)); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/MapSubject.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/MapSubject.java deleted file mode 100644 index cb97552..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/MapSubject.java +++ /dev/null @@ -1,74 +0,0 @@ - -package com.github.benmanes.caffeine.testing; - -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Range; -import com.google.common.truth.FailureMetadata; -import com.google.common.truth.Ordered; - -import java.util.Map; - -import static com.github.benmanes.caffeine.testing.CollectionSubject.collection; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.truth.Truth.assertAbout; - -public class MapSubject extends com.google.common.truth.MapSubject { - private final Map actual; - - public MapSubject(FailureMetadata metadata, Map subject) { - super(metadata, subject); - this.actual = subject; - } - - public static Factory> map() { - return MapSubject::new; - } - - public static MapSubject assertThat(Map actual) { - return assertAbout(map()).that(actual); - } - - - public final void hasSize(long expectedSize) { - super.hasSize(Math.toIntExact(expectedSize)); - } - - - public void hasSizeLessThan(long other) { - checkArgument(other >= 0, "expectedSize (%s) must be >= 0", other); - check("size()").that(actual.size()).isLessThan(Math.toIntExact(other)); - } - - - public void hasSizeIn(Range range) { - check("size()").that(actual.size()).isIn(range); - } - - - public Ordered containsExactlyKeys(Iterable keys) { - return check("containsKeys").that(actual.keySet()) - .containsExactlyElementsIn(ImmutableSet.copyOf(keys)); - } - - - public void containsValue(Object value) { - check("containsValue").that(actual.values()).contains(value); - } - - - public void doesNotContainValue(Object value) { - check("containsValue").that(actual.values()).doesNotContain(value); - } - - public void isExhaustivelyEmpty() { - isEqualTo(Map.of()); - hasSize(0); - isEmpty(); - check("isEmpty()").that(actual.isEmpty()).isTrue(); - check("toString()").that(actual.toString()).isEqualTo(Map.of().toString()); - check("hashCode()").that(actual.hashCode()).isEqualTo(Map.of().hashCode()); - check("keySet()").about(collection()).that(actual.keySet()).isExhaustivelyEmpty(); - check("values()").about(collection()).that(actual.values()).isExhaustivelyEmpty(); - check("entrySet()").about(collection()).that(actual.entrySet()).isExhaustivelyEmpty(); - } -} diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Threads.java b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Threads.java deleted file mode 100644 index 0d08f02..0000000 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Threads.java +++ /dev/null @@ -1,117 +0,0 @@ - -package com.github.benmanes.caffeine.testing; - -import com.google.common.base.Throwables; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import org.testng.log4testng.Logger; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; -import java.util.stream.IntStream; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.truth.Truth.assertThat; -import static java.util.Locale.US; -import static org.testng.Assert.fail; - -public final class Threads { - private static final Logger logger = Logger.getLogger(Threads.class); - public static final int ITERATIONS = 40_000; - public static final int NTHREADS = 20; - public static final int TIMEOUT = 30; - - private Threads() { - } - - public static void runTest(A collection, List> operations) { - var failures = new ConcurrentLinkedQueue(); - var thrasher = new Thrasher<>(collection, failures, operations); - Threads.executeWithTimeOut(failures, () -> - ConcurrentTestHarness.timeTasks(Threads.NTHREADS, thrasher)); - assertThat(failures).isEmpty(); - } - - public static void executeWithTimeOut(Queue failures, Callable task) { - var es = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).build()); - var future = es.submit(task); - try { - long timeNS = future.get(TIMEOUT, TimeUnit.SECONDS); - logger.debug("\nExecuted in " + TimeUnit.NANOSECONDS.toSeconds(timeNS) + " second(s)"); - } catch (ExecutionException e) { - fail("Exception during test: " + e, e); - } catch (TimeoutException e) { - handleTimout(failures, es, e); - } catch (InterruptedException e) { - fail("", e); - } - } - - public static void handleTimout(Queue failures, ExecutorService es, TimeoutException e) { - Thread.getAllStackTraces().values().forEach(trace -> { - for (var element : trace) { - logger.info("\tat " + element); - } - if (trace.length > 0) { - logger.info("------"); - } - }); - MoreExecutors.shutdownAndAwaitTermination(es, 10, TimeUnit.SECONDS); - failures.forEach(logger::debug); - fail("Spun forever", e); - } - - public static List> workingSets(int nThreads, int iterations) { - var keys = IntStream.range(0, iterations) - .map(i -> ThreadLocalRandom.current().nextInt(iterations / 100)) - .mapToObj(Int::valueOf) - .collect(toImmutableList()); - return shuffle(nThreads, keys); - } - - private static List> shuffle(int samples, List baseline) { - var workingSets = new ArrayList>(samples); - var workingSet = new ArrayList<>(baseline); - IntStream.range(0, samples).forEach(i -> { - Collections.shuffle(workingSet); - workingSets.add(List.copyOf(workingSet)); - }); - return List.copyOf(workingSets); - } - - public static final class Thrasher implements Runnable { - private final List> operations; - private final List> sets; - private final Queue failures; - private final AtomicInteger index; - private final A collection; - - public Thrasher(A collection, Queue failures, List> operations) { - this.sets = workingSets(Threads.NTHREADS, Threads.ITERATIONS); - this.index = new AtomicInteger(); - this.operations = operations; - this.collection = collection; - this.failures = failures; - } - - @Override - public void run() { - int id = index.getAndIncrement(); - sets.get(id).forEach(e -> { - var operation = operations.get(ThreadLocalRandom.current().nextInt(operations.size())); - try { - operation.accept(collection, e); - } catch (Throwable t) { - failures.add(String.format(US, "Failed: key %s on operation %s%n%s", - e, operation, Throwables.getStackTraceAsString(t))); - throw t; - } - }); - } - } -} diff --git a/caffeine/src/test/java/com/lingh/CaffeineTest.java b/caffeine/src/test/java/com/lingh/CaffeineTest.java new file mode 100644 index 0000000..c56712a --- /dev/null +++ b/caffeine/src/test/java/com/lingh/CaffeineTest.java @@ -0,0 +1,77 @@ +package com.lingh; + +import com.github.benmanes.caffeine.cache.*; +import com.google.common.testing.FakeTicker; +import org.junit.jupiter.api.Test; + +import java.lang.ref.Cleaner; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CaffeineTest { + @Test + void testRefresh() { + LoadingCache graphs = Caffeine.newBuilder().maximumSize(10_000).refreshAfterWrite(1, TimeUnit.MINUTES) + .build(key -> key.equals("Hello") ? "World" : "Universe"); + assertThat(graphs.get("Hello")).isEqualTo("World"); + assertThat(graphs.getAll(List.of("Hi", "Aloha"))).isEqualTo(Map.of("Hi", "Universe", "Aloha", "Universe")); + } + + @SuppressWarnings("StringEquality") + @Test + void testInterner() { + Interner firstInterner = Interner.newStrongInterner(); + String s1 = firstInterner.intern("value"); + String s2 = firstInterner.intern("value"); + assertThat(s1 == s2).isTrue(); + LoadingCache graphs = Caffeine.newBuilder().weakKeys().build(key -> key.equals("Hello") ? "World" : "Universe"); + Interner secondInterner = Interner.newWeakInterner(); + String canonical = secondInterner.intern("Hello"); + assertThat(graphs.get(canonical)).isEqualTo("World"); + } + + @Test + void testStatistics() { + Cache graphs = Caffeine.newBuilder().maximumSize(10_000).recordStats().build(); + graphs.put("Hello", "World"); + assertThat(graphs.getIfPresent("Hello")).isEqualTo("World"); + } + + @Test + void testSpecification() { + CaffeineSpec spec = CaffeineSpec.parse("maximumWeight=1000, expireAfterWrite=10m, recordStats"); + LoadingCache graphs = Caffeine.from(spec).weigher((String key, String graph) -> graph.length()) + .build(key -> key.equals("Hello") ? "World" : "Universe"); + assertThat(graphs.get("Hello")).isEqualTo("World"); + } + + @Test + void testCleanup() { + LoadingCache firstGraphs = Caffeine.newBuilder().scheduler(Scheduler.systemScheduler()).expireAfterWrite(10, TimeUnit.MINUTES) + .build(key -> key.equals("Hello") ? "World" : "Universe"); + firstGraphs.put("Hello", "World"); + assertThat(firstGraphs.getIfPresent("Hello")).isEqualTo("World"); + Cache secondGraphs = Caffeine.newBuilder().weakValues().build(); + Cleaner cleaner = Cleaner.create(); + cleaner.register("World", secondGraphs::cleanUp); + secondGraphs.put("Hello", "World"); + assertThat(secondGraphs.getIfPresent("Hello")).isEqualTo("World"); + } + + @Test + void testTesting() { + FakeTicker ticker = new FakeTicker(); + Cache cache = Caffeine.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) + .executor(Runnable::run) + .ticker(ticker::read) + .maximumSize(10) + .build(); + cache.put("Hello", "World"); + ticker.advance(30, TimeUnit.MINUTES); + assertThat(cache.getIfPresent("Hello")).isNull(); + } +} diff --git a/caffeine/src/test/java/com/lingh/ComputeTest.java b/caffeine/src/test/java/com/lingh/ComputeTest.java new file mode 100644 index 0000000..c24fad4 --- /dev/null +++ b/caffeine/src/test/java/com/lingh/ComputeTest.java @@ -0,0 +1,91 @@ +package com.lingh; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BinaryOperator; +import java.util.stream.LongStream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ComputeTest { + @Test + public void givenCacheUpdate_writeBehindIsCalled() { + AtomicBoolean writerCalled = new AtomicBoolean(false); + var writer = new WriteBehindCacheWriter.Builder() + .bufferTime(1, TimeUnit.SECONDS) + .coalesce(BinaryOperator.maxBy(ZonedDateTime::compareTo)) + .writeAction(entries -> writerCalled.set(true)) + .build(); + Cache cache = Caffeine.newBuilder().build(); + cache.asMap().computeIfAbsent(1L, key -> { + var value = ZonedDateTime.now(); + writer.write(key, value); + return value; + }); + Awaitility.await().untilTrue(writerCalled); + } + + @Test + public void givenCacheUpdateOnMultipleKeys_writeBehindIsCalled() { + AtomicBoolean writerCalled = new AtomicBoolean(false); + AtomicInteger numberOfEntries = new AtomicInteger(0); + var writer = new WriteBehindCacheWriter.Builder() + .bufferTime(1, TimeUnit.SECONDS) + .coalesce(BinaryOperator.maxBy(ZonedDateTime::compareTo)) + .writeAction(entries -> { + numberOfEntries.set(entries.size()); + writerCalled.set(true); + }).build(); + Cache cache = Caffeine.newBuilder().build(); + LongStream.rangeClosed(1L, 3L).forEach(i -> cache.asMap().computeIfAbsent(i, key -> { + var value = ZonedDateTime.now(); + writer.write(key, value); + return value; + })); + Awaitility.await().untilTrue(writerCalled); + assertThat(numberOfEntries.intValue()).isEqualTo(3); + } + + @Test + public void givenMultipleCacheUpdatesOnSameKey_writeBehindIsCalledWithMostRecentTime() { + AtomicBoolean writerCalled = new AtomicBoolean(false); + AtomicInteger numberOfEntries = new AtomicInteger(0); + AtomicReference timeInWriteBehind = new AtomicReference<>(); + var writer = new WriteBehindCacheWriter.Builder() + .bufferTime(1, TimeUnit.SECONDS) + .coalesce(BinaryOperator.maxBy(ZonedDateTime::compareTo)) + .writeAction(entries -> { + if (entries.isEmpty()) { + return; + } + numberOfEntries.set(entries.size()); + ZonedDateTime zonedDateTime = entries.values().iterator().next(); + timeInWriteBehind.set(zonedDateTime); + writerCalled.set(true); + }).build(); + Cache cache = Caffeine.newBuilder().build(); + var values = List.of( + ZonedDateTime.of(2016, 6, 26, 8, 0, 0, 0, ZoneId.systemDefault()), + ZonedDateTime.of(2016, 6, 26, 8, 0, 0, 100, ZoneId.systemDefault()), + ZonedDateTime.of(2016, 6, 26, 8, 0, 0, 300, ZoneId.systemDefault()), + ZonedDateTime.of(2016, 6, 26, 8, 0, 0, 500, ZoneId.systemDefault())); + values.forEach(value -> cache.asMap().compute(1L, (key, oldValue) -> { + writer.write(key, value); + return value; + })); + var mostRecentTime = values.get(values.size() - 1); + Awaitility.await().untilTrue(writerCalled); + assertThat(numberOfEntries.intValue()).isEqualTo(1); + assertThat(timeInWriteBehind.get()).isEqualTo(mostRecentTime); + } +} diff --git a/caffeine/src/test/java/com/lingh/EvictionTest.java b/caffeine/src/test/java/com/lingh/EvictionTest.java new file mode 100644 index 0000000..3891ee1 --- /dev/null +++ b/caffeine/src/test/java/com/lingh/EvictionTest.java @@ -0,0 +1,56 @@ +package com.lingh; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.Expiry; +import com.github.benmanes.caffeine.cache.LoadingCache; +import org.junit.jupiter.api.Test; + +import java.time.ZonedDateTime; +import java.util.concurrent.TimeUnit; + +import static java.time.temporal.ChronoUnit.MILLIS; +import static org.assertj.core.api.Assertions.assertThat; + +public class EvictionTest { + @Test + void testSizeBased() { + LoadingCache firstGraphs = Caffeine.newBuilder().maximumSize(10_000).build(key -> key.equals("Hello") ? "World" : "Universe"); + assertThat(firstGraphs.get("Hello")).isEqualTo("World"); + LoadingCache secondGraphs = Caffeine.newBuilder().maximumWeight(10_000).weigher((String key, String graph) -> graph.length()) + .build(key -> key.equals("Hello") ? "World" : "Universe"); + assertThat(secondGraphs.get("Hello")).isEqualTo("World"); + } + + @Test + void testTimeBased() { + LoadingCache firstGraphs = Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES) + .build(key -> key.equals("Hello") ? "World" : "Universe"); + assertThat(firstGraphs.get("Hello")).isEqualTo("World"); + LoadingCache secondGraphs = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES) + .build(key -> key.equals("Hello") ? "World" : "Universe"); + assertThat(secondGraphs.get("Hello")).isEqualTo("World"); + LoadingCache thirdGraphs = Caffeine.newBuilder().expireAfter(new Expiry() { + public long expireAfterCreate(String key, String graph, long currentTime) { + long seconds = ZonedDateTime.now().plusHours(5).minus(System.currentTimeMillis(), MILLIS).toEpochSecond(); + return TimeUnit.SECONDS.toNanos(seconds); + } + + public long expireAfterUpdate(String key, String graph, long currentTime, long currentDuration) { + return currentDuration; + } + + public long expireAfterRead(String key, String graph, long currentTime, long currentDuration) { + return currentDuration; + } + }).build(key -> key.equals("Hello") ? "World" : "Universe"); + assertThat(thirdGraphs.get("Hello")).isEqualTo("World"); + } + + @Test + void testReferenceBased() { + LoadingCache firstGraphs = Caffeine.newBuilder().weakKeys().weakValues().build(key -> key.equals("Hello") ? "World" : "Universe"); + assertThat(firstGraphs.get("Hello")).isEqualTo("World"); + LoadingCache secondGraphs = Caffeine.newBuilder().softValues().build(key -> key.equals("Hello") ? "World" : "Universe"); + assertThat(secondGraphs.get("Hello")).isEqualTo("World"); + } +} diff --git a/caffeine/src/test/java/com/lingh/PolicyTest.java b/caffeine/src/test/java/com/lingh/PolicyTest.java new file mode 100644 index 0000000..0da7f12 --- /dev/null +++ b/caffeine/src/test/java/com/lingh/PolicyTest.java @@ -0,0 +1,31 @@ +package com.lingh; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PolicyTest { + @Test + void testSizeBased() { + Cache cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).build(); + cache.policy().eviction().ifPresent(eviction -> eviction.setMaximum(2 * eviction.getMaximum())); + cache.put("Hello", "World"); + assertThat(cache.getIfPresent("Hello")).isEqualTo("World"); + } + + @Test + void testTimeBased() { + Cache cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).build(); + cache.policy().expireAfterAccess().ifPresent(eviction -> eviction.setExpiresAfter(Duration.ofMinutes(5))); + cache.policy().expireAfterWrite().ifPresent(eviction -> eviction.setExpiresAfter(Duration.ofMinutes(5))); + cache.policy().expireVariably().ifPresent(eviction -> eviction.setExpiresAfter("Hello", Duration.ofMinutes(5))); + cache.policy().refreshAfterWrite().ifPresent(refresh -> refresh.setRefreshesAfter(Duration.ofMinutes(5))); + cache.put("Hello", "World"); + assertThat(cache.getIfPresent("Hello")).isEqualTo("World"); + } +} diff --git a/caffeine/src/test/java/com/lingh/PopulationTest.java b/caffeine/src/test/java/com/lingh/PopulationTest.java new file mode 100644 index 0000000..d03a47c --- /dev/null +++ b/caffeine/src/test/java/com/lingh/PopulationTest.java @@ -0,0 +1,58 @@ +package com.lingh; + +import com.github.benmanes.caffeine.cache.*; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PopulationTest { + @Test + void testManual() { + Cache cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).build(); + assertThat(cache.getIfPresent("Hello")).isNull(); + assertThat(cache.get("Hello", k -> "World")).isEqualTo("World"); + cache.put("Hello", "World"); + assertThat(cache.getIfPresent("Hello")).isEqualTo("World"); + cache.invalidate("Hello"); + assertThat(cache.getIfPresent("Hello")).isNull(); + } + + @Test + void testLoading() { + LoadingCache cache = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(10, TimeUnit.MINUTES) + .build(key -> key.equals("Hello") ? "World" : "Universe"); + assertThat(cache.get("Hello")).isEqualTo("World"); + assertThat(cache.getAll(List.of("Hi", "Aloha"))).isEqualTo(Map.of("Hi", "Universe", "Aloha", "Universe")); + } + + @SuppressWarnings("DataFlowIssue") + @Test + void testAsynchronousManual() throws ExecutionException, InterruptedException { + AsyncCache cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).buildAsync(); + assertThat(cache.getIfPresent("Hello")).isNull(); + assertThat(cache.get("Hello", k -> "World").get()).isEqualTo("World"); + cache.put("Hello", CompletableFuture.supplyAsync(() -> "World")); + assertThat(cache.getIfPresent("Hello").get()).isEqualTo("World"); + cache.synchronous().invalidate("Hello"); + assertThat(cache.getIfPresent("Hello")).isNull(); + } + + @Test + void testAsynchronouslyLoading() throws ExecutionException, InterruptedException { + AsyncLoadingCache firstCache = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(10, TimeUnit.MINUTES) + .buildAsync((key, executor) -> + key.equals("Hello") ? CompletableFuture.supplyAsync(() -> "World") : CompletableFuture.supplyAsync(() -> "Universe")); + assertThat(firstCache.get("Hello").get()).isEqualTo("World"); + assertThat(firstCache.getAll(List.of("Hi", "Aloha")).get()).isEqualTo(Map.of("Hi", "Universe", "Aloha", "Universe")); + AsyncLoadingCache secondCache = Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(10, TimeUnit.MINUTES) + .buildAsync(key -> key.equals("Hello") ? "World" : "Universe"); + assertThat(secondCache.get("Hello").get()).isEqualTo("World"); + assertThat(secondCache.getAll(List.of("Hi", "Aloha")).get()).isEqualTo(Map.of("Hi", "Universe", "Aloha", "Universe")); + } +} diff --git a/caffeine/src/test/java/com/lingh/RemovalTest.java b/caffeine/src/test/java/com/lingh/RemovalTest.java new file mode 100644 index 0000000..5c447b4 --- /dev/null +++ b/caffeine/src/test/java/com/lingh/RemovalTest.java @@ -0,0 +1,41 @@ +package com.lingh; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.RemovalCause; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RemovalTest { + @Test + void testExplicitRemovals() { + Cache cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).build(); + cache.putAll(Map.of("Hello", "World", "Hi", "Universe", "Aloha", "Universe")); + assertThat(cache.getAll(List.of("Hello", "Hi", "Aloha"), key -> null)) + .isEqualTo(Map.of("Hello", "World", "Hi", "Universe", "Aloha", "Universe")); + cache.invalidate("Hello"); + assertThat(cache.getIfPresent("Hello")).isNull(); + cache.invalidateAll(List.of("Hi", "Aloha")); + Stream.of("Hi", "Aloha").forEach(s -> assertThat(cache.getIfPresent(s)).isNull()); + cache.invalidateAll(); + Stream.of("Hello", "Hi", "Aloha").forEach(s -> assertThat(cache.getIfPresent(s)).isNull()); + } + + @Test + void testRemovalListeners() { + Cache graphs = Caffeine.newBuilder() + .evictionListener((String key, String graph, RemovalCause cause) -> System.out.printf("Key %s was evicted (%s)%n", key, cause)) + .removalListener((String key, String graph, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) + .build(); + graphs.put("Hello", "World"); + assertThat(graphs.getIfPresent("Hello")).isEqualTo("World"); + graphs.invalidate("Hello"); + assertThat(graphs.getIfPresent("Hello")).isNull(); + } +} diff --git a/caffeine/src/test/java/com/lingh/WriteBehindCacheWriter.java b/caffeine/src/test/java/com/lingh/WriteBehindCacheWriter.java new file mode 100644 index 0000000..d32ae90 --- /dev/null +++ b/caffeine/src/test/java/com/lingh/WriteBehindCacheWriter.java @@ -0,0 +1,53 @@ +package com.lingh; + +import io.reactivex.subjects.PublishSubject; + +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; +import java.util.function.BinaryOperator; +import java.util.function.Consumer; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toMap; + +public final class WriteBehindCacheWriter { + private final PublishSubject> subject; + + private WriteBehindCacheWriter(Builder builder) { + subject = PublishSubject.create(); + subject.buffer(builder.bufferTimeNanos, TimeUnit.NANOSECONDS) + .map(entries -> entries.stream().collect(toMap(Entry::getKey, Entry::getValue, builder.coalescer))) + .subscribe(builder.writeAction::accept); + } + + public void write(K key, V value) { + subject.onNext(new SimpleImmutableEntry<>(key, value)); + } + + public static final class Builder { + private Consumer> writeAction; + private BinaryOperator coalescer; + private long bufferTimeNanos; + + public Builder bufferTime(long duration, TimeUnit unit) { + this.bufferTimeNanos = TimeUnit.NANOSECONDS.convert(duration, unit); + return this; + } + + public Builder writeAction(Consumer> writeAction) { + this.writeAction = requireNonNull(writeAction); + return this; + } + + public Builder coalesce(BinaryOperator coalescer) { + this.coalescer = requireNonNull(coalescer); + return this; + } + + public WriteBehindCacheWriter build() { + return new WriteBehindCacheWriter<>(this); + } + } +}