From 3cf1354d13016bcacf26243364e86fd330e831b7 Mon Sep 17 00:00:00 2001 From: Ben Manes Date: Sat, 4 Jan 2025 10:19:45 -0800 Subject: [PATCH] restores and tests the write-through behavior on entrySet's toArray The previous commit delegated an unbounded cache's toArray to the underlying map. This works for keys and values, but an entry may be modified. This wrote through to the map but bypassed being intercepted for the removal listener. Additional unit tests now assert this to avoid regressions, as the optimization seemed oddly missing and that nagging feeling was a remibder of why. --- .../caffeine/cache/UnboundedLocalCache.java | 10 -- .../benmanes/caffeine/cache/AsMapTest.java | 28 ++++ .../caffeine/cache/AsyncAsMapTest.java | 157 +++++++++++------- .../caffeine/cache/AsyncCacheTest.java | 8 +- .../caffeine/cache/AsyncLoadingCacheTest.java | 12 +- .../caffeine/cache/ExpirationTest.java | 6 +- .../caffeine/cache/ExpireAfterVarTest.java | 2 +- .../caffeine/cache/ReferenceTest.java | 2 +- .../caffeine/cache/RefreshAfterWriteTest.java | 2 +- .../github/benmanes/caffeine/testing/Int.java | 8 +- 10 files changed, 143 insertions(+), 92 deletions(-) diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java index dbde55f09e..ad01570b78 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/UnboundedLocalCache.java @@ -959,16 +959,6 @@ public Iterator> iterator() { public Spliterator> spliterator() { return new EntrySpliterator<>(cache); } - - @Override - public Object[] toArray() { - return cache.data.entrySet().toArray(); - } - - @Override - public T[] toArray(T[] array) { - return cache.data.entrySet().toArray(array); - } } /** An adapter to safely externalize the entry iterator. */ 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 index 7e9226c104..d5b6ed78e2 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsMapTest.java @@ -47,6 +47,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; import java.util.Spliterators; import java.util.concurrent.CompletableFuture; @@ -2586,6 +2587,30 @@ public void entrySet_toArray(Map map, CacheContext context) { assertThat(func).asList().containsExactlyElementsIn(context.original().entrySet()); } + @CheckNoStats + @Test(dataProvider = "caches") + @CacheSpec(population = Population.FULL, + removalListener = { Listener.DISABLED, Listener.CONSUMING }) + public void entrySet_toArray_writeThrough(Map map, CacheContext context) { + var expected = new HashMap<>(context.original()); + expected.putAll(Maps.toMap(context.firstMiddleLastKeys(), key -> context.absentValue())); + + @SuppressWarnings("unchecked") + var array = (Map.Entry[]) map.entrySet().toArray(new Map.Entry[0]); + for (var entry : array) { + var value = expected.get(entry.getKey()); + if (!Objects.equals(entry.getValue(), value)) { + entry.setValue(value); + assertThat(entry.getValue()).isEqualTo(value); + } + } + assertThat(map).isEqualTo(expected); + assertThat(context).removalNotifications().withCause(REPLACED) + .contains(Maps.toMap(context.firstMiddleLastKeys(), + key -> requireNonNull(context.original().get(key)))) + .exclusively(); + } + @CheckNoStats @Test(dataProvider = "caches") @CacheSpec(population = Population.FULL, @@ -2939,6 +2964,9 @@ public void entrySet(Map map, CacheContext context) { assertThat(entries.remove(entry)).isTrue(); assertThat(entries.remove(entry)).isFalse(); assertThat(entries).doesNotContain(entry); + if (context.isCaffeine()) { + assertThat(entry).isInstanceOf(WriteThroughEntry.class); + } }); assertThat(map).isExhaustivelyEmpty(); assertThat(context).removalNotifications().withCause(EXPLICIT) 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 index 8645f6eb4e..c6b6d8728a 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncAsMapTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncAsMapTest.java @@ -47,6 +47,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; import java.util.Spliterators; import java.util.concurrent.CompletableFuture; @@ -160,7 +161,7 @@ public void containsValue_present(AsyncCache cache, CacheContext conte @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(); + assertThat(cache.asMap().containsValue(context.absentValue().toFuture())).isFalse(); } /* ---------------- get -------------- */ @@ -196,14 +197,14 @@ public void get_present(AsyncCache cache, CacheContext context) { @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void getOrDefault_nullKey(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> - cache.asMap().getOrDefault(null, Int.valueOf(1).asFuture())); + cache.asMap().getOrDefault(null, Int.valueOf(1).toFuture())); } @CheckNoStats @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void getOrDefault_absent(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); assertThat(cache.asMap().getOrDefault(context.absentKey(), null)).isNull(); assertThat(cache.asMap().getOrDefault(context.absentKey(), value)).isEqualTo(value); } @@ -213,7 +214,7 @@ public void getOrDefault_absent(AsyncCache cache, CacheContext context @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(); + var value = context.absentValue().toFuture(); for (Int key : context.firstMiddleLastKeys()) { assertThat(cache.asMap().getOrDefault(key, value)).succeedsWith(context.original().get(key)); } @@ -261,7 +262,7 @@ public void forEach_modify(AsyncCache cache, CacheContext context) { @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void put_nullKey(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> - cache.asMap().put(null, Int.valueOf(1).asFuture())); + cache.asMap().put(null, Int.valueOf(1).toFuture())); } @CheckNoStats @@ -282,7 +283,7 @@ public void put_nullKeyAndValue(AsyncCache cache, CacheContext context @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void put_insert(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); assertThat(cache.asMap().put(context.absentKey(), value)).isNull(); assertThat(cache).containsEntry(context.absentKey(), value); assertThat(cache).hasSize(context.initialSize() + 1); @@ -321,7 +322,7 @@ public void put_replace_sameInstance(AsyncCache cache, CacheContext co public void put_replace_differentValue(AsyncCache cache, CacheContext context) { var replaced = new HashMap(); for (Int key : context.firstMiddleLastKeys()) { - var newValue = context.absentValue().asFuture(); + var newValue = context.absentValue().toFuture(); assertThat(cache.asMap().put(key, newValue)).succeedsWith(context.original().get(key)); assertThat(cache).containsEntry(key, newValue); replaced.put(key, context.original().get(key)); @@ -340,7 +341,7 @@ public void put_recursiveUpdate(AsyncCache cache, CacheContext context 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(); + return key.toFuture(); }); assertThat(result).succeedsWith(context.absentKey()); assertThat(cache).containsEntry(context.absentKey(), context.absentKey()); @@ -370,7 +371,7 @@ public void putAll_insert(AsyncCache cache, CacheContext context) { var entries = IntStream .range(startKey, 100 + startKey) .mapToObj(Int::valueOf) - .collect(toImmutableMap(identity(), key -> key.negate().asFuture())); + .collect(toImmutableMap(identity(), key -> key.negate().toFuture())); cache.asMap().putAll(entries); assertThat(cache).hasSize(100 + context.initialSize()); } @@ -378,7 +379,7 @@ public void putAll_insert(AsyncCache cache, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(population = { Population.SINGLETON, Population.PARTIAL, Population.FULL }) public void putAll_replace(AsyncCache cache, CacheContext context) { - var entries = Maps.toMap(context.original().keySet(), Int::asFuture); + var entries = Maps.toMap(context.original().keySet(), Int::toFuture); cache.asMap().putAll(entries); assertThat(cache).containsExactlyEntriesIn(entries); assertThat(context).removalNotifications().withCause(REPLACED) @@ -393,7 +394,7 @@ public void putAll_mixed(AsyncCache cache, CacheContext context) { context.original().forEach((key, value) -> { if ((key.intValue() % 2) == 0) { replaced.put(key, value); - entries.put(key, value.add(1).asFuture()); + entries.put(key, value.add(1).toFuture()); } else { entries.put(key, cache.asMap().get(key)); } @@ -412,7 +413,7 @@ public void putAll_mixed(AsyncCache cache, CacheContext context) { @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void putIfAbsent_nullKey(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> - cache.asMap().putIfAbsent(null, Int.valueOf(2).asFuture())); + cache.asMap().putIfAbsent(null, Int.valueOf(2).toFuture())); } @CheckNoStats @@ -437,7 +438,7 @@ public void putIfAbsent_nullKeyAndValue(AsyncCache cache, CacheContext public void putIfAbsent_present(AsyncCache cache, CacheContext context) { for (Int key : context.firstMiddleLastKeys()) { var value = requireNonNull(cache.asMap().get(key)); - assertThat(cache.asMap().putIfAbsent(key, key.asFuture())).isEqualTo(value); + assertThat(cache.asMap().putIfAbsent(key, key.toFuture())).isEqualTo(value); assertThat(cache).containsEntry(key, value); } assertThat(cache).hasSize(context.initialSize()); @@ -446,7 +447,7 @@ public void putIfAbsent_present(AsyncCache cache, CacheContext context @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void putIfAbsent_insert(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); assertThat(cache.asMap().putIfAbsent(context.absentKey(), value)).isNull(); assertThat(cache).containsEntry(context.absentKey(), value); assertThat(cache).hasSize(context.initialSize() + 1); @@ -490,7 +491,7 @@ public void remove_present(AsyncCache cache, CacheContext context) { @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void removeConditionally_nullKey(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> - cache.asMap().remove(null, context.absentValue().asFuture())); + cache.asMap().remove(null, context.absentValue().toFuture())); } @CheckNoStats @@ -512,7 +513,7 @@ public void removeConditionally_nullKeyAndValue( @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void removeConditionally_absent(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); assertThat(cache.asMap().remove(context.absentKey(), value)).isFalse(); } @@ -522,7 +523,7 @@ public void removeConditionally_absent(AsyncCache cache, CacheContext removalListener = { Listener.DISABLED, Listener.REJECTING }) public void removeConditionally_presentKey(AsyncCache cache, CacheContext context) { for (Int key : context.firstMiddleLastKeys()) { - assertThat(cache.asMap().remove(key, key.asFuture())).isFalse(); + assertThat(cache.asMap().remove(key, key.toFuture())).isFalse(); } assertThat(cache).hasSize(context.initialSize()); } @@ -551,7 +552,7 @@ public void removeConditionally_presentKeyAndValue( @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void replace_null(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> - cache.asMap().replace(null, Int.valueOf(1).asFuture())); + cache.asMap().replace(null, Int.valueOf(1).toFuture())); } @CheckNoStats @@ -572,7 +573,7 @@ public void replace_nullKeyAndValue(AsyncCache cache, CacheContext con @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()); + var result = cache.asMap().replace(context.absentKey(), context.absentValue().toFuture()); assertThat(result).isNull(); } @@ -619,7 +620,7 @@ public void replace_differentValue(AsyncCache cache, CacheContext cont var replaced = new HashMap(); for (Int key : context.firstMiddleLastKeys()) { var oldValue = cache.asMap().get(key); - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); replaced.put(key, context.original().get(key)); assertThat(cache.asMap().replace(key, value)).isEqualTo(oldValue); assertThat(cache).containsEntry(key, value); @@ -635,7 +636,7 @@ public void replace_differentValue(AsyncCache cache, CacheContext cont @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void replaceConditionally_nullKey(AsyncCache cache, CacheContext context) { - var value = Int.valueOf(1).asFuture(); + var value = Int.valueOf(1).toFuture(); assertThrows(NullPointerException.class, () -> cache.asMap().replace(null, value, value)); } @@ -643,7 +644,7 @@ public void replaceConditionally_nullKey(AsyncCache cache, CacheContex @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void replaceConditionally_nullOldValue(AsyncCache cache, CacheContext context) { - var value = Int.valueOf(1).asFuture(); + var value = Int.valueOf(1).toFuture(); assertThrows(NullPointerException.class, () -> cache.asMap().replace(Int.valueOf(1), null, value)); } @@ -696,7 +697,7 @@ public void replaceConditionally_nullKeyAndValues( @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void replaceConditionally_absent(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); assertThat(cache.asMap().replace(context.absentKey(), value, value)).isFalse(); } @@ -706,7 +707,7 @@ public void replaceConditionally_absent(AsyncCache cache, CacheContext public void replaceConditionally_wrongOldValue(AsyncCache cache, CacheContext context) { for (Int key : context.firstMiddleLastKeys()) { var oldValue = cache.asMap().get(key); - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); assertThat(cache.asMap().replace(key, value, value)).isFalse(); assertThat(cache).containsEntry(key, oldValue); } @@ -749,7 +750,7 @@ public void replaceConditionally_differentValue( var replaced = new HashMap(); for (Int key : context.firstMiddleLastKeys()) { var oldValue = cache.asMap().get(key); - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); assertThat(cache.asMap().replace(key, oldValue, value)).isTrue(); assertThat(cache).containsEntry(key, value); replaced.put(key, context.original().get(key)); @@ -787,7 +788,7 @@ public void replaceAll_sameValue(AsyncCache cache, CacheContext contex @CacheSpec @Test(dataProvider = "caches") public void replaceAll_differentValue(AsyncCache cache, CacheContext context) { - cache.asMap().replaceAll((key, value) -> key.asFuture()); + cache.asMap().replaceAll((key, value) -> key.toFuture()); cache.asMap().forEach((key, value) -> { assertThat(value).succeedsWith(key); }); @@ -802,7 +803,7 @@ public void replaceAll_differentValue(AsyncCache cache, CacheContext c @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void computeIfAbsent_nullKey(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> - cache.asMap().computeIfAbsent(null, key -> key.negate().asFuture())); + cache.asMap().computeIfAbsent(null, key -> key.negate().toFuture())); } @CheckNoStats @@ -858,7 +859,7 @@ public void computeIfAbsent_error(AsyncCache cache, CacheContext conte assertThat(actual).isSameInstanceAs(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); + var future = cache.asMap().computeIfAbsent(context.absentKey(), Int::toFuture); assertThat(future).succeedsWith(context.absentKey()); } @@ -879,7 +880,7 @@ public void computeIfAbsent_present(AsyncCache cache, CacheContext con @CacheSpec(stats = CacheSpec.Stats.ENABLED, removalListener = { Listener.DISABLED, Listener.REJECTING }) public void computeIfAbsent_absent(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); assertThat(cache.asMap().computeIfAbsent(context.absentKey(), key -> value)).isEqualTo(value); assertThat(context).stats().hits(0).misses(1).success(1).failures(0); @@ -894,7 +895,7 @@ public void computeIfAbsent_absent(AsyncCache cache, CacheContext cont @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void computeIfPresent_nullKey(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> - cache.asMap().computeIfPresent(null, (key, value) -> key.negate().asFuture())); + cache.asMap().computeIfPresent(null, (key, value) -> key.negate().toFuture())); } @CheckNoStats @@ -976,7 +977,7 @@ public void computeIfPresent_error(AsyncCache cache, CacheContext cont assertThat(context).stats().hits(0).misses(0).success(0).failures(0); var future = cache.asMap().computeIfPresent( - context.firstKey(), (k, v) -> k.negate().asFuture()); + context.firstKey(), (k, v) -> k.negate().toFuture()); assertThat(future).succeedsWith(context.firstKey().negate()); } @@ -994,7 +995,7 @@ public void computeIfPresent_absent(AsyncCache cache, CacheContext con public void computeIfPresent_present_sameValue(AsyncCache cache, CacheContext context) { var replaced = new HashMap>(); for (Int key : context.firstMiddleLastKeys()) { - var value = intern(new Int(context.original().get(key))).asFuture(); + var value = intern(new Int(context.original().get(key))).toFuture(); assertThat(cache.asMap().computeIfPresent(key, (k, v) -> value)).isSameInstanceAs(value); replaced.put(key, value); } @@ -1028,7 +1029,7 @@ public void computeIfPresent_present_differentValue( AsyncCache cache, CacheContext context) { var replaced = new HashMap(); for (Int key : context.firstMiddleLastKeys()) { - var value = key.asFuture(); + var value = key.toFuture(); replaced.put(key, context.original().get(key)); assertThat(cache.asMap().computeIfPresent(key, (k, v) -> value)).isEqualTo(value); } @@ -1050,7 +1051,7 @@ public void computeIfPresent_present_differentValue( @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void compute_nullKey(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> - cache.asMap().compute(null, (key, value) -> key.negate().asFuture())); + cache.asMap().compute(null, (key, value) -> key.negate().toFuture())); } @CheckNoStats @@ -1114,7 +1115,7 @@ public void compute_error(AsyncCache cache, CacheContext context) { assertThat(cache).containsExactlyEntriesIn(context.original()); assertThat(context).stats().hits(0).misses(0).success(0).failures(0); - var future = context.absentKey().negate().asFuture(); + var future = context.absentKey().negate().toFuture(); assertThat(cache.asMap().compute(context.absentKey(), (k, v) -> future)).isEqualTo(future); } @@ -1130,7 +1131,7 @@ public void compute_absent_nullValue(AsyncCache cache, CacheContext co @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void compute_absent(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); 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); @@ -1142,7 +1143,7 @@ public void compute_absent(AsyncCache cache, CacheContext context) { public void compute_sameValue(AsyncCache cache, CacheContext context) { var replaced = new HashMap>(); for (Int key : context.firstMiddleLastKeys()) { - var value = intern(new Int(context.original().get(key))).asFuture(); + var value = intern(new Int(context.original().get(key))).toFuture(); assertThat(cache.asMap().compute(key, (k, v) -> value)).isSameInstanceAs(value); replaced.put(key, value); } @@ -1179,7 +1180,7 @@ public void compute_sameInstance(AsyncCache cache, CacheContext contex public void compute_differentValue(AsyncCache cache, CacheContext context) { var replaced = new HashMap(); for (Int key : context.firstMiddleLastKeys()) { - var value = key.asFuture(); + var value = key.toFuture(); assertThat(cache.asMap().compute(key, (k, v) -> value)).isEqualTo(value); replaced.put(key, context.original().get(key)); } @@ -1201,7 +1202,7 @@ public void compute_differentValue(AsyncCache cache, CacheContext cont @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void merge_nullKey(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> - cache.asMap().merge(null, Int.valueOf(-1).asFuture(), (oldValue, value) -> value)); + cache.asMap().merge(null, Int.valueOf(-1).toFuture(), (oldValue, value) -> value)); } @CheckNoStats @@ -1217,7 +1218,7 @@ public void merge_nullValue(AsyncCache cache, CacheContext context) { @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void merge_nullMappingFunction(AsyncCache cache, CacheContext context) { assertThrows(NullPointerException.class, () -> - cache.asMap().merge(Int.valueOf(1), Int.valueOf(1).asFuture(), null)); + cache.asMap().merge(Int.valueOf(1), Int.valueOf(1).toFuture(), null)); } @CheckNoStats @@ -1283,7 +1284,7 @@ public void merge_pingpong(AsyncCache cache, CacheContext context) { removalListener = { Listener.DISABLED, Listener.REJECTING }) public void merge_error(AsyncCache cache, CacheContext context) { var key = context.firstKey(); - var future = key.asFuture(); + var future = key.toFuture(); var expected = new IllegalStateException(); var actual = assertThrows(IllegalStateException.class, () -> cache.asMap().merge(key, future, (oldValue, value) -> { throw expected; })); @@ -1292,14 +1293,14 @@ public void merge_error(AsyncCache cache, CacheContext context) { assertThat(context).stats().hits(0).misses(0).success(0).failures(0); var result = cache.asMap().merge(context.firstKey(), future, - (oldValue, value) -> context.absentValue().asFuture()); + (oldValue, value) -> context.absentValue().toFuture()); 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 absent = context.absentValue().toFuture(); var result = cache.asMap().merge(context.absentKey(), absent, (oldValue, value) -> value); assertThat(result).isEqualTo(absent); assertThat(cache).containsEntry(context.absentKey(), absent); @@ -1312,7 +1313,7 @@ public void merge_sameValue(AsyncCache cache, CacheContext context) { var replaced = new HashMap>(); for (Int key : context.firstMiddleLastKeys()) { var value = requireNonNull(cache.asMap().get(key)).thenApply(Int::new); - var result = cache.asMap().merge(key, key.negate().asFuture(), (oldValue, v) -> value); + var result = cache.asMap().merge(key, key.negate().toFuture(), (oldValue, v) -> value); assertThat(result).isSameInstanceAs(value); replaced.put(key, value); } @@ -1331,7 +1332,7 @@ public void merge_sameValue(AsyncCache cache, CacheContext context) { public void merge_sameInstance(AsyncCache cache, CacheContext context) { for (Int key : context.firstMiddleLastKeys()) { var value = cache.asMap().get(key); - var result = cache.asMap().merge(key, key.negate().asFuture(), (oldValue, v) -> value); + var result = cache.asMap().merge(key, key.negate().toFuture(), (oldValue, v) -> value); assertThat(result).isSameInstanceAs(value); } int count = context.firstMiddleLastKeys().size(); @@ -1351,8 +1352,8 @@ public void merge_differentValue(AsyncCache cache, CacheContext contex var replaced = new HashMap(); Int mergedValue = context.absentValue(); for (Int key : context.firstMiddleLastKeys()) { - var result = cache.asMap().merge(key, key.asFuture(), - (oldValue, v) -> mergedValue.asFuture()); + var result = cache.asMap().merge(key, key.toFuture(), + (oldValue, v) -> mergedValue.toFuture()); assertThat(result).succeedsWith(mergedValue); replaced.put(key, context.original().get(key)); } @@ -1906,7 +1907,7 @@ public void values_toArray(AsyncCache cache, CacheContext context) { @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void values_contains_absent(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().contains(context.absentValue().asFuture())).isFalse(); + assertThat(cache.asMap().values().contains(context.absentValue().toFuture())).isFalse(); } @CheckNoStats @@ -1931,7 +1932,7 @@ public void values_empty(AsyncCache cache, CacheContext context) { removalListener = { Listener.DISABLED, Listener.REJECTING }) public void values_addNotSupported(AsyncCache cache, CacheContext context) { assertThrows(UnsupportedOperationException.class, () -> - cache.asMap().values().add(Int.valueOf(1).asFuture())); + cache.asMap().values().add(Int.valueOf(1).toFuture())); } @CacheSpec @@ -1973,7 +1974,7 @@ public void values_removeAll_none_empty(AsyncCache cache, CacheContext @Test(dataProvider = "caches") public void values_removeAll_none_populated(AsyncCache cache, CacheContext context) { assertThat(cache.asMap().values().removeAll( - Set.of(context.absentValue().asFuture()))).isFalse(); + Set.of(context.absentValue().toFuture()))).isFalse(); assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); assertThat(context).removalNotifications().isEmpty(); } @@ -2027,7 +2028,7 @@ public void values_remove_null(AsyncCache cache, CacheContext context) @CheckNoStats @Test(dataProvider = "caches") public void values_remove_none(AsyncCache cache, CacheContext context) { - assertThat(cache.asMap().values().remove(context.absentValue().asFuture())).isFalse(); + assertThat(cache.asMap().values().remove(context.absentValue().toFuture())).isFalse(); assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); } @@ -2048,7 +2049,7 @@ public void values_remove(AsyncCache cache, CacheContext context) { @CacheSpec(population = Population.FULL) public void values_remove_once(AsyncCache cache, CacheContext context) { var expected = new HashMap<>(context.original()); - var future = context.absentValue().asFuture(); + var future = context.absentValue().toFuture(); for (Int key : context.firstMiddleLastKeys()) { expected.put(key, context.absentValue()); cache.put(key, future); @@ -2160,7 +2161,7 @@ public void values_retainAll_none_empty(AsyncCache cache, CacheContext @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.asMap().values().retainAll(Set.of(context.absentValue().toFuture()))).isTrue(); assertThat(cache).isEmpty(); assertThat(context).removalNotifications().withCause(EXPLICIT) .contains(context.original()).exclusively(); @@ -2349,6 +2350,35 @@ public void entrySet_toArray(AsyncCache cache, CacheContext context) { } } + @CheckNoStats + @Test(dataProvider = "caches") + @CacheSpec(population = Population.FULL, + removalListener = { Listener.DISABLED, Listener.CONSUMING }) + public void entrySet_toArray_writeThrough(AsyncCache cache, CacheContext context) { + var expected = new HashMap>(); + for (Int key : context.original().keySet()) { + expected.put(key, cache.asMap().get(key)); + } + expected.putAll(Maps.toMap(context.firstMiddleLastKeys(), + key -> context.absentValue().toFuture())); + + @SuppressWarnings("unchecked") + var array = (Map.Entry>[]) + cache.asMap().entrySet().toArray(new Map.Entry[0]); + for (var entry : array) { + var future = expected.get(entry.getKey()); + if (!Objects.equals(entry.getValue(), future)) { + entry.setValue(future); + assertThat(entry.getValue()).isEqualTo(future); + } + } + assertThat(cache.asMap()).isEqualTo(expected); + assertThat(context).removalNotifications().withCause(REPLACED) + .contains(Maps.toMap(context.firstMiddleLastKeys(), + key -> requireNonNull(context.original().get(key)))) + .exclusively(); + } + @CheckNoStats @Test(dataProvider = "caches") @CacheSpec(population = Population.EMPTY, @@ -2379,7 +2409,7 @@ public void entrySet_contains_nullValue(AsyncCache cache, CacheContext @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()); + var entry = Map.entry(context.absentKey(), context.absentValue().toFuture()); assertThat(cache.asMap().entrySet().contains(entry)).isFalse(); } @@ -2397,7 +2427,7 @@ public void entrySet_contains_present(AsyncCache cache, CacheContext c @CacheSpec(population = Population.EMPTY, removalListener = Listener.DISABLED) public void entrySet_addIsNotSupported(AsyncCache cache, CacheContext context) { assertThrows(UnsupportedOperationException.class, () -> - cache.asMap().entrySet().add(Map.entry(Int.valueOf(1), Int.valueOf(2).asFuture()))); + cache.asMap().entrySet().add(Map.entry(Int.valueOf(1), Int.valueOf(2).toFuture()))); assertThat(cache).isEmpty(); } @@ -2440,7 +2470,7 @@ public void entrySet_removeAll_none_empty(AsyncCache cache, CacheConte @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(); + Set.of(Map.entry(context.absentKey(), context.absentKey().toFuture())))).isFalse(); assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); assertThat(context).removalNotifications().isEmpty(); } @@ -2487,7 +2517,7 @@ public void entrySet_removeAll_self(AsyncCache cache, CacheContext con @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()); + Maps.transformValues(context.absent(), Int::toFuture).entrySet()); Collection>> entries = Mockito.mock(); when(entries.iterator()).thenReturn(delegate.iterator()); @@ -2501,7 +2531,7 @@ public void entrySet_removeAll_byCollection(AsyncCache cache, CacheCon @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()); + Maps.transformValues(context.absent(), Int::toFuture).entrySet()); Set>> entries = Mockito.mock(); when(entries.size()).thenReturn(delegate.size()); when(entries.contains(any())).then(invocation -> delegate.contains(invocation.getArgument(0))); @@ -2525,7 +2555,7 @@ public void entrySet_remove_null(AsyncCache cache, CacheContext contex @SuppressWarnings("MapEntry") @Test(dataProvider = "caches") public void entrySet_remove_nullKey(AsyncCache cache, CacheContext context) { - var future = Iterables.getFirst(cache.asMap().values(), context.absentValue().asFuture()); + var future = Iterables.getFirst(cache.asMap().values(), context.absentValue().toFuture()); assertThat(cache.asMap().entrySet().remove(Maps.immutableEntry(null, future))).isFalse(); assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); } @@ -2553,7 +2583,7 @@ public void entrySet_remove_nullKeyValue(AsyncCache cache, CacheContex @CheckNoStats @Test(dataProvider = "caches") public void entrySet_remove_none(AsyncCache cache, CacheContext context) { - var entry = Map.entry(context.absentKey(), context.absentValue().asFuture()); + var entry = Map.entry(context.absentKey(), context.absentValue().toFuture()); assertThat(cache.asMap().entrySet().remove(entry)).isFalse(); assertThat(cache.synchronous().asMap()).isEqualTo(context.original()); } @@ -2654,7 +2684,7 @@ public void entrySet_retainAll_none_empty(AsyncCache cache, CacheConte @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(); + Set.of(Map.entry(context.absentKey(), context.absentValue().toFuture())))).isTrue(); assertThat(cache).isEmpty(); assertThat(context).removalNotifications().withCause(EXPLICIT) .contains(context.original()).exclusively(); @@ -2706,6 +2736,9 @@ public void entrySet(AsyncCache cache, CacheContext context) { assertThat(entries.remove(entry)).isTrue(); assertThat(entries.remove(entry)).isFalse(); assertThat(entries).doesNotContain(entry); + if (context.isCaffeine()) { + assertThat(entry).isInstanceOf(WriteThroughEntry.class); + } }); assertThat(cache).isEmpty(); assertThat(context).removalNotifications().withCause(EXPLICIT) @@ -2827,7 +2860,7 @@ public void entrySpliterator_estimateSize(AsyncCache cache, CacheConte 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(); + var value = Int.valueOf(3).toFuture(); entry.setValue(value); assertThat(cache).hasSize(context.initialSize()); 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 index 8a4daeebfc..874b6ddef5 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncCacheTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncCacheTest.java @@ -361,7 +361,7 @@ public void getBiFunc_absent_cancelled(AsyncCache cache, CacheContext @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()); + var value = cache.get(key, (k, executor) -> context.absentValue().toFuture()); assertThat(value).succeedsWith(context.absentValue()); assertThat(context).stats().hits(0).misses(1).success(1).failures(0); } @@ -1071,7 +1071,7 @@ public void getAllBifunction_early_failure(AsyncCache cache, CacheCont @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void put_nullKey(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); assertThrows(NullPointerException.class, () -> cache.put(null, value)); } @@ -1121,7 +1121,7 @@ public void put_insert_failure_after(AsyncCache cache, CacheContext co @Test(dataProvider = "caches") @CacheSpec(removalListener = { Listener.DISABLED, Listener.REJECTING }) public void put_insert(AsyncCache cache, CacheContext context) { - var value = context.absentValue().asFuture(); + var value = context.absentValue().toFuture(); cache.put(context.absentKey(), value); assertThat(cache).hasSize(context.initialSize() + 1); assertThat(context).stats().hits(0).misses(0).success(1).failures(0); @@ -1173,7 +1173,7 @@ public void put_replace_nullValue(AsyncCache cache, CacheContext conte public void put_replace_differentValue(AsyncCache cache, CacheContext context) { var replaced = new HashMap(); for (Int key : context.firstMiddleLastKeys()) { - var newValue = context.absentValue().asFuture(); + var newValue = context.absentValue().toFuture(); cache.put(key, newValue); assertThat(cache).containsEntry(key, newValue); replaced.put(key, context.original().get(key)); 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 index be0efd1871..69138e971a 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/AsyncLoadingCacheTest.java @@ -503,7 +503,7 @@ private static final class LoadAllException extends RuntimeException { @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(); + var value = context.absentValue().toFuture(); for (Int key : context.firstMiddleLastKeys()) { cache.put(key, value); assertThat(cache.get(key)).succeedsWith(context.absentValue()); @@ -629,7 +629,7 @@ public void refresh_current_inFlight(AsyncLoadingCache cache, CacheCon @Test(dataProvider = "caches") @CacheSpec(compute = Compute.ASYNC, removalListener = Listener.CONSUMING) public void refresh_current_sameInstance(CacheContext context) { - var future = context.absentValue().asFuture(); + var future = context.absentValue().toFuture(); var cache = context.buildAsync((key, executor) -> future); cache.put(context.absentKey(), future); @@ -640,7 +640,7 @@ public void refresh_current_sameInstance(CacheContext context) { @CacheSpec @Test(dataProvider = "caches") public void refresh_current_failed(AsyncLoadingCache cache, CacheContext context) { - var future = context.absentValue().asFuture(); + var future = context.absentValue().toFuture(); cache.put(context.absentKey(), future); future.obtrudeException(new Exception()); @@ -662,7 +662,7 @@ public void refresh_current_removed(CacheContext context) { return key; }); - cache.put(context.absentKey(), context.absentValue().asFuture()); + cache.put(context.absentKey(), context.absentValue().toFuture()); cache.synchronous().refresh(context.absentKey()); await().untilTrue(started); @@ -679,14 +679,14 @@ public void refresh_current_removed(CacheContext context) { @Test public void asyncLoadAll() { - AsyncCacheLoader loader = (key, executor) -> key.negate().asFuture(); + AsyncCacheLoader loader = (key, executor) -> key.negate().toFuture(); assertThrows(UnsupportedOperationException.class, () -> loader.asyncLoadAll(Set.of(), Runnable::run)); } @Test public void asyncReload() throws Exception { - AsyncCacheLoader loader = (key, executor) -> key.negate().asFuture(); + AsyncCacheLoader loader = (key, executor) -> key.negate().toFuture(); var future = loader.asyncReload(Int.valueOf(1), Int.valueOf(2), Runnable::run); assertThat(future).succeedsWith(-1); } 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 index 64a1d3f9f9..f7cce7e8b9 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpirationTest.java @@ -484,7 +484,7 @@ public void get(AsyncCache cache, CacheContext context) { 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(); + cache.get(context.lastKey(), (k, executor) -> context.absentValue().toFuture()).join(); assertThat(context).notifications().withCause(EXPIRED) .contains(context.original()).exclusively(); @@ -573,7 +573,7 @@ public void getAll(AsyncCache cache, CacheContext context) { expireAfterWrite = {Expire.DISABLED, Expire.ONE_MINUTE}) public void put_insert(AsyncCache cache, CacheContext context) { context.ticker().advance(Duration.ofMinutes(1)); - cache.put(context.firstKey(), intern(context.absentValue().asFuture())); + cache.put(context.firstKey(), intern(context.absentValue().toFuture())); runVariableExpiration(context); assertThat(cache).hasSize(1); @@ -613,7 +613,7 @@ public void put_insert_async(AsyncCache cache, CacheContext context) { 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(); + var future = context.absentValue().toFuture(); context.ticker().advance(Duration.ofSeconds(30)); cache.put(context.firstKey(), future); 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 index 16239de406..aa808ab709 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ExpireAfterVarTest.java @@ -200,7 +200,7 @@ public void put_replace(Cache cache, CacheContext context) { @CacheSpec(population = Population.FULL, expiryTime = Expire.ONE_MINUTE, expiry = CacheExpiry.CREATE) public void put_replace(AsyncCache cache, CacheContext context) { - var future = context.absentValue().asFuture(); + var future = context.absentValue().toFuture(); context.ticker().advance(Duration.ofSeconds(30)); cache.put(context.firstKey(), future); 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 index 6c89d6add6..cebe06f9d6 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReferenceTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/ReferenceTest.java @@ -523,7 +523,7 @@ public void put_async(AsyncCache cache, CacheContext context) { Int key = context.absentKey(); context.clear(); GcFinalization.awaitFullGc(); - cache.put(key, context.absentValue().asFuture()); + cache.put(key, context.absentValue().toFuture()); assertThat(cache).hasSize(1); assertThat(context).notifications().withCause(COLLECTED) 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 index d734846c62..d99b1242b8 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/RefreshAfterWriteTest.java @@ -104,7 +104,7 @@ public void refreshIfNeeded_nonblocking(CacheContext context) { public CompletableFuture asyncReload(Int key, Int oldValue, Executor executor) { reloads.incrementAndGet(); await().untilTrue(refresh); - return oldValue.add(1).asFuture(); + return oldValue.add(1).toFuture(); } }); cache.put(key, original); 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 index 92ecba261e..05165d4371 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Int.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/testing/Int.java @@ -83,8 +83,8 @@ public Int add(Int i) { return add(i.value); } - /** Returns a completed future of this value. */ - public CompletableFuture asFuture() { + /** Returns a new completed future for this value. */ + public CompletableFuture toFuture() { return CompletableFuture.completedFuture(this); } @@ -155,9 +155,9 @@ public static Map mapOf(int... mappings) { return map; } - /** Returns a completed future of the value, possibly cached. */ + /** Returns a new completed future of the (possibly cached) value. */ public static CompletableFuture futureOf(int i) { - return valueOf(i).asFuture(); + return valueOf(i).toFuture(); } /** Returns a preallocated range of {@code Int} instances */